This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: c_char incorrectly treated as bytes in Structure
Type: behavior Stage: resolved
Components: ctypes Versions: Python 3.9
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: ciresnave, eryksun
Priority: normal Keywords:

Created on 2021-09-24 19:23 by ciresnave, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (3)
msg402582 - (view) Author: CireSnave (ciresnave) Date: 2021-09-24 19:23
When dealing with a Structure containing c_char variables, the variables are incorrectly being typed as bytes.  As a result, a pointer to those c_char variables can not be created because bytes is not a ctypes type.

from ctypes import (
    Structure,
    c_char,
    pointer,
)


class MyStruct(Structure):
    _fields_ = [("a", c_char), ("b", c_char), ("c", c_char)]


x: MyStruct = MyStruct(98, 99, 100)

print(type(x.a))
# Prints <class 'bytes'> ???  Both mypy and PyRight agree that x.a is a c_char.

some_variable = pointer(x.a)
# Traceback (most recent call last):
#   File "C:\Users\cires\ctypes_test.py", line 23, in <module>
#     some_variable = pointer(x.a)
# TypeError: _type_ must have storage info
msg402585 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2021-09-24 20:19
A simple ctypes type implements a get function that's called when its value is returned as an attribute of struct/union, index of an array/pointer, or result of a function pointer. For example:

    >>> a = (ctypes.c_char * 1)(97)
    >>> a[0]
    b'a'

    >>> p = ctypes.POINTER(ctypes.c_char)(a)
    >>> p[0]
    b'a'

This behavior can't be changed. However, using a subclass of c_char works around it. For example:

    >>> class my_char(ctypes.c_char): pass
    ... 

    >>> a = (my_char * 1)(97)
    >>> a[0]
    <my_char object at 0x7f007dadf640>
    >>> a[0].value
    b'a'

    >>> p = ctypes.POINTER(my_char)(a)
    >>> p[0]
    <my_char object at 0x7f007dadf6c0>
    >>> p[0].value
    b'a'
msg402642 - (view) Author: CireSnave (ciresnave) Date: 2021-09-25 18:53
Wow.  Thank you Eryk Sun.  While that seems like a convoluted way to make a type act like the type it is...it works.  Verified it with the following code:

from ctypes import POINTER, c_char, sizeof, Structure


class char_from_c(c_char):
    pass


print("Size of c_char vs size of char_from_c:")
print(sizeof(c_char))
print(sizeof(char_from_c))


class my_structure(Structure):
    _fields_ = [
        ("first_char", char_from_c),
        ("second_char", char_from_c),
        ("third_char", char_from_c),
    ]


my_structure_object: my_structure = my_structure(97, 98, 99)

pointer_to_char_from_c_in_my_structure = POINTER(c_char)(my_structure_object.first_char)

print("\nContents of pointer_to_char_from_c_in_my_structure:")
print(pointer_to_char_from_c_in_my_structure.contents)
print("\npointer_to_char_from_c_in_my_Structure[0]:")
print(pointer_to_char_from_c_in_my_structure[0])

print("\nValues from my_structure_object:")
character_counter = 0
while character_counter < sizeof(my_structure_object):
    print(pointer_to_char_from_c_in_my_structure[character_counter])
    character_counter += 1



...which gave me the following results:

Size of c_char vs size of char_from_c:
1
1

Contents of pointer_to_char_from_c_in_my_structure:
c_char(b'a')

pointer_to_char_from_c_in_my_Structure[0]:
b'a'

Values from my_structure_object:
b'a'
b'b'
b'c'


Again...Thank you.  Closing this "bug" as it is weird...but not a bug.
History
Date User Action Args
2022-04-11 14:59:50adminsetgithub: 89448
2021-09-25 18:53:23ciresnavesetstatus: open -> closed
resolution: not a bug
messages: + msg402642

stage: resolved
2021-09-24 20:19:04eryksunsetnosy: + eryksun
messages: + msg402585
2021-09-24 19:23:45ciresnavecreate