C Structure rebuild with ctypes

Mark Tolonen metolone+gmane at gmail.com
Mon Dec 21 20:50:22 EST 2009


"Georg" <nobody at nowhere.org> wrote in message 
news:7padi2Fsm5U1 at mid.individual.net...
> Hi Mark,
>
> many thanks for your help. I tried your code in my program and it worked.
>
> I would like to understand what the code is doing and I have some 
> questions to it.
>
>> Are you passing in these values, or are they being returned?  To me the 
>> depth of the pointer references implies numVars, varNames, and varTypes 
>> are out parameters. I'll assume that for now.  If they are in/out 
>> parameters let me know.
>
> Your exactly right: the parameters numVars, varNames and VarTypes are out
> paramters.
>
>> I mocked up a DLL to test returning values of these types.  I used VS2008 
>> and compiled with "cl /LD func.c":
>>
>> --- func.c -------------------------------
>> #include <stdlib.h>
>> #define FUNCDLL
>> #include "func.h"
>>
>> static char* g_data[] = {"one","two","three"};
>> static int g_types[] = {1,2,3};
>>
>> FUNCAPI int func (int handle, int *numVars, char ***varNames, int 
>> **varTypes)
>> {
>> *numVars = _countof(g_data);
>> *varNames = g_data;
>> *varTypes = g_types;
>> return handle + 1;
>> }
>
> What does the signature FUNCAPI do in this context?

In Microsoft's compiler, to export functions from a DLL, one way is to mark 
functions with __declspec(dllexport).  To import functions from a DLL, the 
functions should be marked with __declspec(dllimport).  FUNCAPI is declared 
in func.h below to be one of these definitions.  In the actual DLL source, 
FUNCDLL is defined before including the header to declare all the 
FUNCAPI-tagged functions in the header as exported.

In an executable file that uses the DLL, it can include func.h without 
defining FUNCDLL, and all the FUNCAPI-tagged functions in the header will be 
declared imported.

It was overkill for this example, but I had code skeletons for DLLs already. 
The example could be simplified by replacing FUNCAPI in func.cpp with 
__declspec(dllimport) and not using func.h at all.

>
>
>>
>>
>> --- func.h -------------------------------
>> #ifdef FUNCDLL
>> #    define FUNCAPI __declspec(dllexport)
>> #else
>> #    define FUNCAPI __declspec(dllimport)
>> #endif
>>
>> FUNCAPI int func (int handle, int *numVars, char ***varNames, int 
>> **varTypes);
>>
>
> Do I need to wrap the compiled DLL file this way? I can load the DLL with 
> the CDLL method, make calls to simple functions, e.g. no parameters, 
> returning stirng and get the right values.

No, if the DLL is exporting functions correctly this method doesn't have to 
be used.  As I said above it was a code template I already had.  IIRC, the 
Microsoft code wizard to generate a DLL uses this method.  I didn't have 
your DLL to test with, so I had to make my own and I wanted you to see the 
assumptions I made about its implementation, in case they were wrong :^)

>
>
> I added all the code from your func.py module to the code I already had. 
> And -- it works fine and delivers the expected results.
>
>> --- func.py -------------------------------
> ... cut ...
>>
>> # int func (int handle, int *numVars, char ***varNames, int **varTypes)
>> func = c.CDLL('func').func
>> func.restype = INT
>> func.argtypes = [INT,PINT,PPPCHAR,PPINT]
>
> I added this part to my program.
>
>> # allocate storage for the out parameters
>> numVars = INT()
>> varNames = PPCHAR()
>> varTypes = PINT()
>
> I added this part also.
>
>> print func(5,c.byref(numVars),c.byref(varNames),c.byref(varTypes))
>
> I called the library routine.
>
>> # numVars contains size of returned arrays.  Recast to access.
>> varNamesArray = c.cast(varNames,c.POINTER(PCHAR * numVars.value))
>> varTypesArray = c.cast(varTypes,c.POINTER(INT * numVars.value))
>
> What does this cast? How do I know how I have to cast the objects returned 
> from the library function?

The library is returning a pointer to an array of some size you didn't know 
in advance.  So, for example, we passed varTypes as the address of a pointer 
to int, and a pointer to int was returned.  Since varTypes was declared as a 
pointer to a single int, I recast it to a pointer to "numVars" ints.  This 
allowed me to use indexing syntax ([n]) to access the other elements of the 
returned array.

> What kind of objects do I get? I learned that the values of objects 
> created by the ctypes module are accessed using object.value?

numVars is the name of an INT() object, not a Python int.  numVars.value 
returns the Python int that the INT represents.  An example might be 
helpful:

   >>> import ctypes
   >>> x=ctypes.c_int(5)
   >>> x  # here x is a ctypes object.
   c_long(5)
   >>> x.value  # here is the Python value it represents
   5
   >>> y=(ctypes.c_int * x)()   # I want to create an array of c_ints
   Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
   TypeError: can't multiply sequence by non-int of type 'c_long'
   >>> y=(ctypes.c_int * x.value)()  # It works with Python values.
   >>> y
   <__main__.c_long_Array_5 object at 0x00A04D50>
   >>> y[0]
   0
   >>> y[1]
   0

Hope that helps,
Mark





More information about the Python-list mailing list