1

I cannot access from python ctypes the (fixed length) string from referenced structure allocated by C code (dll library). But the int type I can access and change.

This is simplified code. In reality I'm accessing proprietary dll which I cannot change, therefore please take the content of this dll as fixed. I'm able to access the dll struct string from C code without problems - that's the way I constructed this example dll, so it's interface is the same as interface of proprietary dll.

My assumption was that wrong is either python declaration of "text" in DATA_STRUCT (various tested possibilities are listed), or the way I want to access the string - are commented as they are either returning only object or fails in windows error (access violation).

Just to note, I'm using compiler provided with Dev-C++ TDM-GCC 4.9.2 64-bit (which should use MingW64) and 64bit Python 3.8.2. The reason for using 64 bit python is the proprietary dll, which is 64bit. Everthing is under Windows (7).

dll.h

typedef struct REFERENCE_STRUCT {
    int     type ;
    void * dataP ;
} REFERENCE_STRUCT ;

typedef struct DATA_STRUCT {
    int        number ;
    char       text [41] ; // the string is fixed length
} DATA_STRUCT ;

__declspec(dllexport) int test_alloc (REFERENCE_STRUCT *refP) ;
__declspec(dllexport) int test_print (REFERENCE_STRUCT *refP) ;
__declspec(dllexport) int test_free (REFERENCE_STRUCT *refP) ;

maindll.c

#include "dll.h"
#include <windows.h>
#include <string.h>
#include <stdio.h>

__declspec(dllexport) int test_alloc (REFERENCE_STRUCT *refP) {
    DATA_STRUCT         *dataP ;
    dataP = malloc (sizeof (DATA_STRUCT));
    dataP->number = 5 ;
    strcpy (dataP->text, "number 1");
    refP->type = 40 ;
    refP->dataP = ( void *) dataP ;
    printf ("DLL - alloc: reference type: %d;  data <%d>; <%s>\n", refP->type, dataP->number, dataP->text) ;
    return 0;
} ;

__declspec(dllexport) int test_print (REFERENCE_STRUCT *refP) {
    DATA_STRUCT         *dataP ;
    dataP = (DATA_STRUCT*) refP->dataP ;
    printf ("DLL - print: reference type: %d;  data <%d>; <%s>\n", refP->type, dataP->number, dataP->text) ;
    return 0;
} ;

__declspec(dllexport) int test_free (REFERENCE_STRUCT *refP){
    free(refP->dataP) ;
    printf ("DLL - free\n") ;
    return 0;
} ;

script.py

import sys,os, ctypes, ctypes.util, faulthandler
faulthandler.enable()

os.add_dll_directory("D:/path_to_lib/")
mylib_path = ctypes.util.find_library("mydll")
mylib = ctypes.CDLL(mylib_path)

class REFERENCE_STRUCT(ctypes.Structure):
    _fields_ = [("type", ctypes.c_int),
               ("dataP", ctypes.c_void_p)]

class DATA_STRUCT(ctypes.Structure):
    _fields_ = [("number", ctypes.c_int),
                ("text", ctypes.c_char * (41))]    # !!! THIS declaration is correct for C "char       text [41] ;" !!!
                ("text", ctypes.POINTER(ctypes.c_char * (41)))]    # WHICH declaration is correct for C "char       text [41] ;" ?
#                 ("text", ctypes.POINTER(ctypes.c_char))]           # WHICH declaration is correct for C "char       text [41] ;" ?
#                 ("text", ctypes.c_char_p)]                         # WHICH declaration is correct for C "char       text [41] ;" ?

reference = REFERENCE_STRUCT()

print("test_alloc: ", mylib.test_alloc (ctypes.byref(reference)))
print("reference.type: ", reference.type)

dataP = ctypes.cast(reference.dataP, ctypes.POINTER(DATA_STRUCT))

# accessing the number without problem:

print("dataP.contents.number:      ",dataP.contents.number)
print(ctypes.cast(reference.dataP,         
ctypes.POINTER(DATA_STRUCT)).contents.text)
print("test_print: ", mylib.test_print (ctypes.byref(reference)))
dataP.contents.number = 7
ctypes.cast(reference.dataP, ctypes.POINTER(DATA_STRUCT)).contents.text = b'num 6'
print("dataP.contents.number:      ",dataP.contents.number)
print(ctypes.cast(reference.dataP, ctypes.POINTER(DATA_STRUCT)).contents.text)
print("test_print: ", mylib.test_print (ctypes.byref(reference)))
print("\n")

print("test_free: ", mylib.test_free (ctypes.byref(reference))) # freeing the alocated memory
martin_h
  • 29
  • 5

1 Answers1

1

Listing [Python.Docs]: ctypes - A foreign function library for Python.

There are some problems:

  1. DATA_STRUCT.text definition (as you noticed). It should be: ("text", ctypes.c_char * 41)

  2. argtypes and restype definitions for the foreign functions. Topic detailed in [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)

  3. Accessing the member: ctypes.cast(reference.dataP, ctypes.POINTER(DATA_STRUCT)).contents.text (could store the cast pointer in an additional variable to avoid doing (writing) it multiple times)

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • The printed result after casting is (as before) 'dataP.contents.text: <__main__.LP_c_char_Array_41 object at 0x00000000028123C0>' Any other idea? (trying to print '...contents.text.value/raw' reports AttributeError: 'LP_c_char_Array_41' object has no attribute 'raw' ) – martin_h May 07 '20 at 08:55
  • 1
    You're not paying attention to details. It's ***restype***. The error is missing from the question (please add it). Also the *\_\_main\_\_.LP\_c\_char\_Array\_41* form the error in the comment (and the last question edit), tells me that you actually **didn't do *#1.***. Also I didn;t understand the compiler note. Why do you need a compiler if you already have the *.dll* and *Python*? – CristiFati May 07 '20 at 08:55
  • Thank you @CristiFati, for your patience, your solution works!!! :-) To answer your question - the proprietary .dll has more complicated structure, so to make people easier to help me here I created my own simplified .dll. After solving this problem I will of course use the ctypes to directly access the proprietary dll. – martin_h May 07 '20 at 10:15
  • 1
    Aaa *OK*, so you used the compiler to create an [\[SO\]: How to create a Minimal, Reproducible Example (reprex (mcve))](https://stackoverflow.com/help/mcve). Nice!. Bear in mind that *Python* is built with another compiler (https://wiki.python.org/moin/WindowsCompilers), and sometimes, mixing compilers might raise additional issues (https://stackoverflow.com/questions/51230954/tdm-gcc-compiled-win32x64-dll-cannot-be-loaded-in-python-3-6-but-can-when-using). – CristiFati May 07 '20 at 10:21