Can Python call Delphi functions in a DLL? - python

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.

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

working with python cdll windows dll got an memory exception

I receive this error when I try to call this function written in Delphi. But another code works fine. Maybe I'm not declaring the args ans result types? I am using 32Bit python 3.7). Related code snippets:
Delphi:
Test(deposit, MarginCall: double; CallBack: TProgrCallBackProc); stdcall;
Python:
self.FTCore = ctypes.WinDLL(self.FTCore_library_path)
self.FTCore.Test.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)]
self.FTCore.Test.restype = ctypes.POINTER(ctypes.c_char)
deposit = ctypes.c_double(100)
callback = ctypes.c_double(1)
self.FTCore.Test(deposit, callback)
Error:
violation reading 0x00000004
Three errors that I can see:
The Delphi function accepts three arguments, you define only two in argtypes. You will need to define the third argument, TProgrCallBackProc defined somewhere in the Delphi code.
The two double parameters are passed by value, but you define them as pointers to double in your argtypes definition. They should be defined as plain ctypes.c_double.
The Delphi function has no return value, but your restype contradicts that. You need to set restype to None.

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.

C++ dll called from 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()

Python ctypes argument errors

I wrote a test dll in C++ to make sure things work before I start using a more important dll that I need. Basically it takes two doubles and adds them, then returns the result. I've been playing around and with other test functions I've gotten returns to work, I just can't pass an argument due to errors.
My code is:
import ctypes
import string
nDLL = ctypes.WinDLL('test.dll')
func = nDLL['haloshg_add']
func.restype = ctypes.c_double
func.argtypes = (ctypes.c_double,ctypes.c_double)
print(func(5.0,5.0))
It returns the error for the line that called "func":
ValueError: Procedure probably called with too many arguments (8 bytes in excess)
What am I doing wrong? Thanks.
You probably got the calling conventions mixed up. I'm guessing you have a C function declared something like this:
double haloshg_add(double d1, double s2)
{
return d1+d2;
}
This will use the C calling convention by default. The simplest approach would be to change the calling convention in your ctypes code:
nDLL = ctypes.CDLL('test.dll')
If you wanted to change the calling convention in the C code to stdcall (to match ctypes.WinDLL) then you would do this:
double __stdcall haloshg_add(double d1, double s2)
Whatever you do, only do one of these changes. If you do both you'll have the reverse failure!
If it were me, I'd just change the Python code to use C calling convention (use CDLL). That change has the least impact.

Categories