tags:

views:

240

answers:

2

Hi,

I'm trying to figure out why this works after lots and lots of messing about with

obo.librar_version is a c function which requires char ** as the input and does a strcpy to passed in char.

from ctypes import *
_OBO_C_DLL = 'obo.dll'
STRING = c_char_p

OBO_VERSION = _stdcall_libraries[_OBO_C_DLL].OBO_VERSION
OBO_VERSION.restype = c_int
OBO_VERSION.argtypes = [POINTER(STRING)]

def library_version():
    s = create_string_buffer('\000' * 32)
    t = cast(s, c_char_p)
    res = obo.library_version(byref(t))
    if res != 0:
        raise Error("OBO error %r" % res)
    return t.value, s.raw, s.value

library_version()

The above code returns

('OBO Version 1.0.1', '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', '')

What I don't understand is why 's' does not have any value? Anyone have any ideas? Thx

+1  A: 

When you cast s to c_char_p you store a new object in t, not a reference. So when you pass t to your function by reference, s doesn't get updated.

UPDATE:

You are indeed correct:

cast takes two parameters, a ctypes object that is or can be converted to a pointer of some kind, and a ctypes pointer type. It returns an instance of the second argument, which references the same memory block as the first argument.

In order to get a reference to your string buffer, you need to use the following for your cast:

t = cast(s, POINTER(c_char*33))

I have no idea why c_char_p doesn't create a reference where this does, but there you go.

tgray
Thanks, but according to the documentation cast doesn't create or am I wrong?
obo
You're right, I updated the answer...
tgray
A: 

Because library_version requires a char**, they don't want you to allocate the characters (as you're doing with create_string_buffer. Instead, they just want you to pass in a reference to a pointer so they can return the address of where to find the version string.

So all you need to do is allocate the pointer, and then pass in a reference to that pointer.

The following code should work, although I don't have obo.dll (or know of a suitable replacement) to test it.

from ctypes import *
_OBO_C_DLL = 'obo.dll'
STRING = c_char_p

_stdcall_libraries = dict()
_stdcall_libraries[_OBO_C_DLL] = WinDLL(_OBO_C_DLL)
OBO_VERSION = _stdcall_libraries[_OBO_C_DLL].OBO_VERSION
OBO_VERSION.restype = c_int
OBO_VERSION.argtypes = [POINTER(STRING)]

def library_version():
 s_res = c_char_p()
 res = OBO_VERSION(byref(s_res))
 if res != 0:
  raise Error("OBO error %r" % res)
 return s_res.value

library_version()

[Edit]

I've gone a step further and written my own DLL that implements a possible implementation of OBO_VERSION that does not require an allocated character buffer, and is not subject to any memory leaks.

int OBO_VERSION(char **pp_version)
{
 static char result[] = "Version 2.0";

 *pp_version = result;
 return 0; // success
}

As you can see, OBO_VERSION simply sets the value of *pp_version to a pointer to a null-terminated character array. This is likely how the real OBO_VERSION works. I've tested this against my originally suggested technique above, and it works as prescribed.

Jason R. Coombs
the C function requires an allocated array of char and does a strcpy into that array, so the above is s memory leak by my understanding.
obo
It does not make sense that the C function requires an allocated array of char, but requests a char\**. That doesn't mean it's not possible, but it's bad coding practice. If they wanted an allocated array of characters, the interface should just require a char* that points to such an array. Since they request a char**, they imply that they're supplying the pointer itself. Since it's called OBO_VERSION, I was guessing that they were returning a pointer to a static string in their code. Can you provide the specs for OBO_VERSION (including docs)?
Jason R. Coombs
If the above code works, and the OBO_VERSION spec does not explicitly require the caller to dealloc the memory pointed-to by s_res, then you do not have a memory leak.
Jason R. Coombs
Since c_char_p() is a char* that makes byref(c_char_p()) a char**.
joeforker
You can test to see if the supplied library_version is causing a memory leak by running "while True: library_version()" and watching the process for memory consumption.
Jason R. Coombs