Search code examples
pythonctypes

What is wrong with my Python Ctypes structures?


I'm having a problem with my structure generation to allow the use of a c Library within Python - I'm hoping someone can point out the errors in my code.

C Prototype:

const char *DSpotterVerInfo(char *lpchLicenseFile, VerInfo *lpVerInfo, INT *lpnErr);

C Struct Definition:

typedef struct _VerInfo
{
    const char *SDKName;
    const char *SDKVersion;
    const char *SDKType;
    const char *Date;
    const char *LType;
    BOOL  bTrialVersion;
} VerInfo;

My code:

class DSVerInfo(ctypes.Structure):
  # This works (at least no ugly errors, but I can't get to the structure data..)
  #_fields_ = [('Name',ctypes.c_char_p)]


  # This defintion causes an error and a segdump
  _fields_ = [ \
                 ('Name', ctypes.c_char_p), \
                 ('Version', ctypes.c_char_p), \
                 ('SDKType', ctypes.c_char_p), \
                 ('Date', ctypes.c_char_p), \
                 ('LicType', ctypes.c_char_p), \
                 ('Trial', ctypes.c_bool) \
              ]

  def __init__(self):
    self.Name = cast(ctypes.create_string_buffer(str.encode("")),ctypes.c_char_p)
    self.Version = cast(ctypes.create_string_buffer(str.encode("")),ctypes.c_char_p)
    self.SDKType = cast(ctypes.create_string_buffer(str.encode("")),ctypes.c_char_p)
    self.Date = cast(ctypes.create_string_buffer(str.encode("")),ctypes.c_char_p)
    self.LicType = cast(ctypes.create_string_buffer(str.encode("")),ctypes.c_char_p)
    self.Trial = c_bool()


libc.MyLibVerInfo.argtypes = (ctypes.c_char_p,DSVerInfo,ctypes.POINTER(c_int))

err_no = ctypes.c_int()
Lic_ct = ctypes.create_string_buffer(str.encode(License_file))
VerInfo = DSVerInfo()

result = libc.DSpotterVerInfo(Lic_ct,VerInfo,err_no)

print("Result:\n",result.decode('utf-8'),"Error No:", err_no.value)
print("Version Size: ", sizeof(VerInfo))
print(VerInfo)  #Not really any use.. just an object until I can use VerInfo.x

Here is a sample of the output when it fails (from the print of the errorNo):

 Error No: 155394711
-155394711
Version Size:  48
<__main__.DSVerInfo object at 0x1093287c0>

Done
Segmentation fault: 11
f01898e9b5db0000:Test rpm$

Solution to Question:

From Mark's comment above the problem in the code is that I was not passing DSVerInfo as a pointer. Update the line calling the C funciton to the following and everything works as expected.

# Non-working code:
#libc.DSpotterVerInfo.argtypes = (ctypes.c_char_p,DSVerInfo,ctypes.POINTER(c_int))

#Working code (I had missed that I should be passsing a pointer to DSVerInfo)
libc.DSpotterVerInfo.argtypes = (ctypes.c_char_p,POINTER(DSVerInfo),ctypes.POINTER(c_int))


Solution

  • This is probably what you need. Here's a sample implementation of the function call:

    test.c

    #include <windows.h>
    #include <stdio.h>
    
    typedef struct _VerInfo
    {
        const char *SDKName;
        const char *SDKVersion;
        const char *SDKType;
        const char *Date;
        const char *LType;
        BOOL  bTrialVersion;
    } VerInfo;
    
    __declspec(dllexport)
    const char *DSpotterVerInfo(char *lpchLicenseFile, VerInfo *lpVerInfo, INT *lpnErr) {
        printf("LicenseFile = %s\n", lpchLicenseFile);
        lpVerInfo->SDKName = "SDKName";
        lpVerInfo->SDKVersion = "SDKVersion";
        lpVerInfo->SDKType = "SDKType";
        lpVerInfo->Date = "Date";
        lpVerInfo->LType = "LType";
        lpVerInfo->bTrialVersion = TRUE;
        *lpnErr = 123;
        return "something";
    }
    

    To call it:

    test.py

    import ctypes as ct
    from ctypes import wintypes as w
    
    class DSVerInfo(ct.Structure):
        _fields_ = [('Name', ct.c_char_p),
                    ('Version', ct.c_char_p),
                    ('SDKType', ct.c_char_p),
                    ('Date', ct.c_char_p),
                    ('LicType', ct.c_char_p),
                    ('Trial', w.BOOL)]     # c_bool is only 1 byte, Windows BOOL is 4 bytes.
    
        # Your __init__ is unnecessary.  ctypes inits to null/zero by default
        # and create_string_buffer is only needed if the C function needs
        # pre-allocated buffer to write to.  Since your structure had const
        # char* parameters that wasn't required and pointers to null strings
        # wouldn't be a very big buffer anyway :^)
    
        # Helper function to print this structure.
        def __repr__(self):
            return f'DSVerInfo({self.Name!r}, {self.Version!r}, {self.SDKType!r}, {self.Date!r}, {self.LicType!r}, {self.Trial!r})'
        
    dll = ct.CDLL('./test')
    
    # Make sure to use the correct function name and declare both .argtypes and .restype.
    dll.DSpotterVerInfo.argtypes = ct.c_char_p,ct.POINTER(DSVerInfo),ct.POINTER(ct.c_int)
    dll.DSpotterVerInfo.restype = ct.c_char_p
    
    err_no = ct.c_int()
    Lic_ct = b'licensefile'
    VerInfo = DSVerInfo()
    
    result = dll.DSpotterVerInfo(Lic_ct, ct.byref(VerInfo), ct.byref(err_no))
    
    print('Result:', result.decode())
    print('Error No:', err_no.value)
    print('Version Size: ', ct.sizeof(VerInfo))
    print(VerInfo)
    

    Output:

    LicenseFile = licensefile
    Result: something
    Error No: 123
    Version Size:  48
    DSVerInfo(b'SDKName', b'SDKVersion', b'SDKType', b'Date', b'LType', 1)