geoffr98
geoffr98

Reputation: 111

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))

Upvotes: 0

Views: 370

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177471

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)

Upvotes: 1

Related Questions