C++ dll called from Python - python

I have used a C++ dll using MFC and I would like to call it from python.
This dll contains this header in the .h file
LONG CommOpen(BYTE port, LONG baud_rate);
Then I see in the free software dllexp that my function is called ?CommOpen#CFIPcmd##QAEJEJ#Z in the binary file so no error is reported when I do in python
import ctypes
lib = ctypes.WinDLL('C:\\Users\\toto\\FIProtocol.dll')
prototype = WINFUNCTYPE(c_long, c_byte, c_long)
testPt = ctypes.WINFUNCTYPE (prototype)
testApi = testPt (("?CommOpen#CFIPcmd##QAEJEJ#Z", lib))
Until there it seems to work but then I would like to call the in Python the equivalent in C++ of
Long l= CommOpen(5 ,115200);
But I Didn't find know how to proceed.
Any help would be really appreciated!!

Given the information presented in the question, the solution is:
import ctypes
lib = ctypes.CDLL(r'C:\Users\toto\FIProtocol.dll')
CommOpen = getattr(lib, "?CommOpen#CFIPcmd##QAEJEJ#Z")
CommOpen.argtypes = [c_byte, c_long]
CommOpen.restype = c_long
And now it is ready to call:
l = CommOpen(5 ,115200)
Some notes:
Use CDLL rather than WinDLL because the function used the default cdecl calling convention.
Use getattr to be able to specify the mangled name.
It always pays to specify argtypes and restype explicitly.
However, it transpires that you have a much greater problem. The above was written on the basis that your function is a non-member function. Which is a reasonable assumption given that ctypes requires functions to be either non-member, or static.
However, when I put your managed function name into a demanger (for instance http://pear.warosu.org/c++filtjs/) it seems that the function is in fact:
public: long __thiscall CFIPcmd::CommOpen(unsigned char,long)
That is a member function of a C++ object. That cannot be accessed from ctypes. You'll need to create a plain C style wrapper, or find a different method of interop.

According to http://docs.python.org/2/library/ctypes.html#calling-functions "You can call these functions like any other Python callable." I would suggest to run an interactive Python console (like ipython) and check it yourself.
Well, I've just installed python into VirtualBox Win32 and checked the example:
>>> from ctypes import *
>>> f = getattr(cdll.msvcrt, "??2#YAPAXI#Z")
>>> f
<_FuncPtr object at 0x00B7EDC8>
>>> f()
24969248
>>> _
So, yes, you may call those function objects like any other function in the python environment. Just as the documentation claims :)
Likewise _cputws works:
>>> cputws = getattr(cdll.msvcrt, "_cputws")
>>> r = cputws("Hello, World!\n")
Hello, World!
>>> r
0
>>>

Here is my how-to sample. Strange why similar missed here.
import ctypes
def StrArg(pyStr = "") -> ctypes.c_char_p:
return ctypes.c_char_p(pyStr.encode('utf-8'))
def main():
testlib = ctypes.CDLL("../bin/Debug/MyCoolLib.dll");
#int ProcessStr(const char*);
pyProcessStr = testlib.ProcessStr
pyProcessStr.restype = ctypes.c_int
pyProcessStr.argtypes = [ctypes.c_char_p]
nCount = pyProcessStr(StrArg("aaa,bbb,ccc"))
print(nCount)
# int DoWithTwoArgs(int nX, unsigned char ucY, const char* szZ);
pyDoWithTwoArgs = testlib.DoWithTwoArgs
pyDoWithTwoArgs.restype = ctypes.c_int
pyDoWithTwoArgs.argtypes = [ctypes.c_int,ctypes.c_byte, ctypes.c_char_p]
results = pyDoWithTwoArgs(100, 2, StrArg("Trololo"))
print(results)
print("Finsh")
if __name__ == "__main__":
main()

Related

Calling the C++ function in python using ctypes

I have C++ code which has a function which takes, three arguments
Pointer argument
LPWSTR argument
(LPWSTR variable)reference as argument
Below is the C++ Code syntax
HRESULT WINAPIV GIDItem (Address* pa, LPWSTR cmdvalue, LPWSTR* string_value)
I am able to load the dll in Ctypes
import ctypes
uDl = ctypes.CDLL(r"test1")
uDl_function=uDl.CAIA # we are creating function pointer
pa=uDl_function('something')
uD = ctypes.CDLL(r"test.dll")
uD_function =uD.GIDItem # we are creating function pointer
string_value= ctypes.c_wchar_p()
cmdvalue=ctypes.c_wchar_p("CM")
dI=uD_function(ctypes.POINTER(pa),cmdvalue,ctypes.byref(string_value))
I am getting below error,
dI=uD_function(ctypes.POINTER(pa),cmdvalue,ctypes.byref(string_value))
TypeError: must be a ctypes type
I was just looking some article about that DLL, in C++ the Dll been called like below
fpGIDItem (pA, L"CMD", &cD)
When you look to the above code "CMD" is the cmdvalue and string value is sent with &cD
Please help me over this
ctypes.POINTER(pa) is a type instead of an instance which is why it is giving the error you see.
If you create a working example of the use in C, it would be easier to identify how to write it in Python, but I'll take a stab at it.
Set .argtypes and restype on the function correctly so ctypes will type check for you.
Something like the following should work.
from ctypes import *
dll = CDLL(r'test1')
dll.CAIA.argtypes = c_wchar_p,
dll.CAIA.restype = c_void_p # generic void* should work.
dll2 = ctypes.CDLL(r'test.dll')
dll2.GIDItem.argtypes = c_void_p,c_wchar_p,POINTER(c_wchar_p)
dll2.GIDItem.restype = c_long
pa = dll.CAIA('something')
string_value = c_wchar_p()
result = dll2.GIDItem(pa,'CM',byref(string_value))

Reading C structures in Python with ctypes

I'm using ctypes to call foreign functions in Python3.
The C function is supposed to return the pointer to the structure:
struct sLinkedList {
void* data;
struct sLinkedList* next;
};
typedef struct sLinkedList* LinkedList;
And the function as it is defined in C is as follows.
LinkedList IedConnection_getServerDirectory (IedConnection self, IedClientError * error, bool getFileNames )
So I have my Python3 code as follows:
from ctypes import *
...
class LinkedList(Structure):
pass
LinkedList._fields_ = [("data",c_void_p), ("next", POINTER(LinkedList))]
...
IedConnection_getServerDirectory=dlllib.IedConnection_getServerDirectory
IedConnection_getServerDirectory.argtypes=[c_void_p, c_void_p]
IedConnection_getServerDirectory.restype = c_int
LogicalDevicesPtr = IedConnection_getServerDirectory(IedConnection,iedClientError)
The IedConnection parameter is retrieved by other function as pointer, and I'm pretty sure it works fine. Also I can see that function itself works fine (it initiates communications, that could be seen in Wireshark).
Then I try to get the information as result of function:
LogicalDevicesList_p = cast(LogicalDevicesPtr,POINTER(LinkedList))
LogicalDeviceList = LogicalDevicesList_p.contents
This lines passes and the following line fails:
Rdata = LogicalDeviceList.data
with "Segmentation fault: 11"
I suppose the problem if with types definitions, but I have no idea where the mistake is. Could anyone help?
Well, looks like i've solved it by myself:
IedConnection_getServerDirectory.restype = c_int
is incorrect and shall was changed to:
IedConnection_getServerDirectory.restype = c_void_p
And it started working fine already.
But additionally I've added third argument to function call to make it more neat:
IedConnection_getServerDirectory.argtypes=[c_void_p, c_void_p, c_bool]
LogicalDevicesPtr = IedConnection_getServerDirectory(IedConnection,iedClientError,c_bool(False))

How do I call a delphi function that takes and returns pointers of custom type from Python?

This question is similar to How to access with ctypes to functions returning custom types coded in a Delphi dll?. Where I think this one is different is that the delphi function signature that I'm looking at takes pointers as arguments instead of delphi types.
It's also similar to Python pass Pointer to Delphi function except as mentioned in the comments, that question lacks necessary information.
How do I call a delphi function that takes and returns pointers of custom type from Python?
I'm calling delphi functions by loading a DLL with ctypes in Python.
>>> from ctypes import *
>>> path = "C:\\test.dll"
>>> lib = windll.LoadLibrary(path)
>>> lib.myFunc
<_FuncPtr object at 0x21324905>
The delphi function "myFunc" has a signature like:
Procedure myFunc(Ptr:Pointer;Var Result:Pointer);Export;
Where both pointers should be custom data types, e.g.
Type
PSingleArray = ^SingleArray;
SingleArray = Record
LowIndex : SmallInt;
HighIndex : SmallInt;
Data : Array [0..1000] of Single;
end;
Going through the ctypes tutorial and the docs it seems like the way to solve this is to use "Structure" to create similar types in Python. I think I'm doing it properly; however, when I go to call myFunc an Access Violation Error is raised.
>>> class SingleArray(Structure):
>>> _fields_ = [("HighIndex", c_int), ("LowIndex", c_int), ("Data", c_int * 10)]
>>> ...
>>> lib.myFunc.argtypes = [POINTER(SingleArray)]
>>> lib.myFunc.restype = POINTER(SingleArray)
>>> # initialize the input values
>>> input = SingleArray()
>>> a = c_int * 10
>>> data = a(1,2,3,4,5,6,7,8,9,10)
>>> input.Data = data
>>> input.HighIndex = 2
>>> input.LowIndex = 1
>>> # Here comes the access violation error
>>> ret = lib.myFunc(input)
WindowsError: exception: access violation reading 0x00000002
I'm new to both ctypes and delphi, so I could be missing something obvious.
I can see the following simple problems:
Delphi Smallint is a signed 16 bit type. That matches up to c_short.
Delphi Single is an IEEE-754 floating point value. That matches up to c_float.
The Delphi type has an array of length 1001. Yours has length 10.
The Delphi function has two parameters and no return value. Your argtypes and restype assignments do not match.
Your ctypes code uses stdcall calling convention but the Delphi function would appear to use the Delphi specific register calling convention. This makes the function impossible to call from anything other than another Delphi module.
It's not obvious who owns the memory returned via the Result parameter, nor how to deallocate it.
More significantly, it seems likely that the array in the structure is of variable length. That will be quite tricky to marshal with ctypes. You certainly cannot do so using Structure._fields_.
If you can change the Delphi DLL, do so. In its current form it is essentially unusable from ctypes. Even from Delphi code it is appalling to use.
If you cannot change the Delphi DLL then I think you'll need to write an adapter DLL, in Delphi or FPC, that presents a more sane interface for your Python code to work with.

Am I using ctypes correctly to pythonify this struct?

I'm trying to talk to this DLL using python's ctypes. Many of the functions take or return an HGRABBER type:
typedef struct HGRABBER_t__ { int unused; } HGRABBER_t;
#define HGRABBER HGRABBER_t*
(the full header file can be viewed here). Here's an example of a function prototype that returns an HGRABBER type:
HGRABBER __stdcall IC_CreateGrabber();
Here's my attempt at implementing this struct in python, and using it to call that function from the DLL:
import ctypes as C
class GrabberHandle(C.Structure):
_fields_ = [('unused', C.c_int)]
dll = C.windll.LoadLibrary('tisgrabber_x64.dll')
dll.create_grabber = dll.IC_CreateGrabber
dll.create_grabber.argtypes = []
dll.create_grabber.restype = GrabberHandle
my_handle = dll.create_grabber()
This seems to work, but I'm worried that I'm doing this wrong. I'm not experienced with C, and I don't think I understand the typedef and #define statements which define the HGRABBER type. Am I calling IC_CreateGrabber correctly? Should I have defined GrabberHandle to be a pointer to a struct, instead of a struct?
Thanks for reading, please let me know if I can clarify my question somehow.
You're right that you actually want a POINTER to the Structure, not the Structure itself.
Translating the C into English, being very loose (in a way that would be dangerous if you were trying to learn C but is good enough for using ctypes):
The struct defines a type named struct HGRABBER_t__, as a structure with one int in it.
The typedef defines a type named HGRABBER_t, as a synonym for struct HGRABBER_t__.
The #define defines a type named HGRABBER as a pointer to HGRABBER_t.
So, your GrabberHandle is the equivalent of HGRABBER_t; the equivalent of HGRABBER is:
GrabberHandlePtr = C.POINTER(GrabberHandle)
So you want this:
dll.create_grabber.restype = GrabberHandlePtr
It may be hard to debug the difference. A C struct with nothing but an int in it looks identical to an int in memory. And on Win32, an int and a pointer are both 32-bit values. And an int named unused is likely to be filled with meaningless garbage, making it hard to distinguish it from a pointer you've accidentally treated as an int. So everything will look fine, until you segfault 30 lines later in your code and have no idea what's wrong. :)
This library does what you are trying to do: https://github.com/morefigs/py-ic-imaging-control :)
But to answer your question, the library uses the code:
from ctypes import *
import os
class GrabberHandle(Structure):
pass
GrabberHandle._fields_ = [('unused', c_int)]
# set and check path
dll_path = os.path.join(os.path.expanduser('~'),
'Documents\\The Imaging Source Europe GmbH\\TIS Grabber DLL\\bin\\win32\\tisgrabber.dll')
with open(dll_path) as thefile:
pass
# open DLL
_ic_grabber_dll = windll.LoadLibrary(dll_path)
# create grabber
create_grabber = _ic_grabber_dll.IC_CreateGrabber
create_grabber.restype = POINTER(GrabberHandle)
create_grabber.argtypes = None
# get handle
handle = create_grabber()
Edit: changed code to use a pointer to GrabberHandle as per abarnert's answer as this is correct. However, in this particular case I have found no practical difference (with the 32-bit DLL), probably because the GrabberHandle structure is so simple.

Can Python call Delphi functions in a DLL?

I am trying to call functions from a DLL which seems to be created in Delphi. An example of a some functions supported by the DLL are:
function oziDeleteWpByName(var name:pansichar):integer;stdcall
The Python code I have written to access the above functions is not working.
from ctypes import *
libc = cdll.OziAPI
name ='test'
pi = pointer(name)
delname = libc.oziDeleteWpByName
delname(name)
It seems I am passing the wrong data type to the function. Any ideas on how to do it right?
Thanks it worked. Now please help with this function:
function oziGetOziVersion(var Version:pansichar;var DataLength:integer):integer;stdcall;
The version of OziExplorer is returned in the Version variable.
Now how do I pass 'var version' when it the one which will also be returned.
from ctypes import *
# Not strictly needed but it's good to be explicit.
windll.OziAPI.oziDeleteWpByName.argtypes = [POINTER(c_char_p)]
windll.OziAPI.oziDeleteWpByName.restype = c_int
p = c_char_p('test')
retval = windll.OziAPI.oziDeleteWpByName(byref(p))
In Delphi, a var parameter is passed by reference. So what you have there is a pointer to a PAnsiChar (aka C-style string pointer). If you're passing it a string pointer, instead of a pointer to a string pointer, it won't work.

Categories