Handler Issues Applying cdll in Python - python

I'm trying to porting some C dll(FANUC FOCAS Library - for CNC) code via Python using ctypes, so I wrote porting code. (as belows), but get a very strange result when loading the DLL and calling the function. In my case, I don't understand using handler in python.
I would like to apply the following c code in python.
Declaration(for c)
#include "fwlib64.h"
FWLIBAPI short WINAPI cnc_allclibhndl3(const char *ipaddr,unsigned short port,
long timeout, unsigned short *FlibHndl);
Example Code(in focas library manual for c)
#include "fwlib64.h"
void example( void )
{
unsigned short h;
short ret;
ODBST buf;
ret = cnc_allclibhndl3( "192.168.0.100", 8193, 1, &h ) ;
//
if ( !ret ) {
cnc_statinfo( h, &buf ) ;
cnc_freelibhndl( h ) ;
} else {
printf( "ERROR!(%d)\n", ret ) ;
}
}
Testfocas.py
from ctypes import *
mylib = cdll.LoadLibrary('./Fwlib64.dll')
class ODBSYS(Structure):
pass
_fields_ =[
("dummy", c_ushort),
("max_axis", c_char*2),
("cnc_type", c_char*2),
("mt_type",c_char*2),
("series",c_char*4),
("version",c_char*4),
("axes",c_char*2),]
h=c_ushort()
pt=pointer(h)
ret=c_short()
buf=ODBSYS()
ret=mylib.cnc_allclibhndl3('192.168.0.100',8193,1,pt)
mylib.cnc_statinfo(h,buf)
mylib.cnc_freelibhndl(h)
I want the function to return 0 or -16 but, in my case the function return is
cnc_allclibhndl3 = 65520 (i guess open port)
cnc_statinfo = -8
cnc_freelibhndl -8
Return Status of Data Window Functions
EW_OK(0) Normal termination
EW_SOCKET(-16) Socket communication error Check the power supply of CNC, Ethernet I/F board, Ethernet connection cable.
EW_HANDLE(-8) Allocation of handle number is failed.
I do not know what I was wrong with.

CDLL is for __cdecl calling convention. cdll is not recommended for use because it is a shared instance across modules.
WINAPI is defined as __stdcall, so use WinDLL:
mylib = WinDLL.LoadLibrary('./Fwlib64.dll')
Next, define argtypes and restype for your argument and result types for your function:
mylib.cnc_allclibhndl3.argtypes = c_char_p,c_ushort,c_long,POINTER(c_ushort)
mylib.cnc_allclibhndl3.restype = c_short
Finally, pass the output parameter by reference. It is more efficient than creating a pointer:
h = c_ushort()
ret = mylib.cnc_allclibhndl3('192.168.0.100',8193,1,byref(h))
Prototypes for cnc_statinfo and cnc_freelibhndl were not provided. Define argtypes and restype for them as well.

Related

Ctypes calling Go Dll with arguments (C string)

How to pass a string as argument from Python to a Go Dll using ctypes:
Go-code:
package main
import "C"
import "fmt"
//export GetInt
func GetInt() int32 {
return 42
}
//export GetString
func GetString() {
fmt.Println("Foo")
}
//export PrintHello
func PrintHello(name string) {
// if name == "hello" { ... }
fmt.Printf("From DLL: Hello, %s!", name)
}
func main() {
// Need a main function to make CGO compile package as C shared library
}
Compiled on MacOs using: GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -buildmode=c-shared -o ./dist/perf_nlp.dll
Python code:
import ctypes
def getString():
nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
dllFunc = nlp.GetString
dllFunc.restype = ctypes.c_char_p
return dllFunc()
def getInt():
nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
dllFunc = nlp.GetInt
dllFunc.restype = int
return dllFunc()
def readString():
nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
dllFunc = nlp.ReadString
dllFunc.argtypes = [ctypes.c_char_p]
dllFunc.restype = ctypes.c_char_p
return dllFunc(b'Foo')
print(getInt())
print(getString())
print(readString()). # Fails
Out:
42
Foo
None
unexpected fault address 0x871000
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0x871000 pc=0x623e501f]
goroutine 17 [running, locked to thread]:
runtime.throw(0x6245b592, 0x5)
/Users/foobar/.goenv/versions/1.14.15/src/runtime/panic.go:1116 +0x64 fp=0x1242fda4 sp=0x1242fd90 pc=0x623be404
runtime.sigpanic()
/Users/foobar/.goenv/versions/1.14.15/src/runtime/signal_windows.go:249 +0x1ed fp=0x1242fdb8 sp=0x1242fda4 pc=0x623ceb8d
runtime.memmove(0x12500011, 0x800588, 0x32efe4)
/Users/foobar/.goenv/versions/1.14.15/src/runtime/memmove_386.s:89 +0x7f fp=0x1242fdbc sp=0x1242fdb8 pc=0x623e501f
fmt.(*buffer).writeString(...)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:82
fmt.(*fmt).padString(0x124a60b0, 0x800588, 0x32efe4)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:110 +0x6c fp=0x1242fdfc sp=0x1242fdbc pc=0x6241576c
fmt.(*fmt).fmtS(0x124a60b0, 0x800588, 0x32efe4)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/format.go:359 +0x4d fp=0x1242fe14 sp=0x1242fdfc pc=0x6241664d
fmt.(*pp).fmtString(0x124a6090, 0x800588, 0x32efe4, 0x73)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:450 +0x188 fp=0x1242fe38 sp=0x1242fe14 pc=0x62418f58
fmt.(*pp).printArg(0x124a6090, 0x62447c80, 0x1248c110, 0x73)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:698 +0x776 fp=0x1242fe80 sp=0x1242fe38 pc=0x6241ad56
fmt.(*pp).doPrintf(0x124a6090, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:1030 +0x12b fp=0x1242fef0 sp=0x1242fe80 pc=0x6241d81b
fmt.Fprintf(0x62476550, 0x1248c0d8, 0x6245e0a5, 0x14, 0x1242ff48, 0x1, 0x1, 0x623e2fe7, 0x0, 0x12488030)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:204 +0x52 fp=0x1242ff20 sp=0x1242fef0 pc=0x62417bd2
fmt.Printf(...)
/Users/foobar/.goenv/versions/1.14.15/src/fmt/print.go:213
main.ReadString(...)
/Users/foobar/__projects__/heine/db_dll/perf_nlp.go:19
main._cgoexpwrap_c3579cea1e16_ReadString(0x800588, 0x32efe4)
_cgo_gotypes.go:71 +0x8d fp=0x1242ff54 sp=0x1242ff20 pc=0x6241e9bd
runtime.call16(0x0, 0x32ef1c, 0x32ef68, 0x8, 0x0)
/Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:565 +0x30 fp=0x1242ff68 sp=0x1242ff54 pc=0x623e3020
runtime.cgocallbackg1(0x0)
/Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:332 +0x149 fp=0x1242ffb0 sp=0x1242ff68 pc=0x62393f59
runtime.cgocallbackg(0x0)
/Users/foobar/.goenv/versions/1.14.15/src/runtime/cgocall.go:207 +0xb5 fp=0x1242ffe0 sp=0x1242ffb0 pc=0x62393d85
runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
/Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:806 +0x7e fp=0x1242fff0 sp=0x1242ffe0 pc=0x623e419e
runtime.goexit()
/Users/foobar/.goenv/versions/1.14.15/src/runtime/asm_386.s:1337 +0x1 fp=0x1242fff4 sp=0x1242fff0 pc=0x623e4651
Working solution:
//export ReadString
func ReadString(name *C.char) *C.char {
res := "";
goName := C.GoString(name);
if goName == "Foo" {
res = "From DLL: Hello, Foo"
}else{
res = "From DLL: Hello!"
}
return C.CString(res)
}
Python:
def readString():
nlp = ctypes.windll.LoadLibrary("H:/perf_nlp.dll")
dllFunc = nlp.ReadString
dllFunc.argtypes = [ctypes.c_char_p]
dllFunc.restype = ctypes.c_char_p
return dllFunc(b'cFoo')
A Go string and a C string are entirely unrelated (except in that both are called strings, which is a lie for at least one of them).
Here Python is sending a C string because you've told it to, but Go expects a Go string, which has a completely diffrent layout so it blows up. And if it didn't blow up at the callsite it'd probably blow up when the GC tries to handle the string, which it can't, because it's not a Go string.
You want to look at the magical "C" pseudo-package: you need to take in a *C.char and copy that to a Go string using C.GoString before you can pass it to anything expecting a go String. Or something along those lines, my experience with cgo (especially calling into it) is limited to avoiding this as a bad idea.
Regardless you probably want to at the very least read the cgo documentation in full, FFI is tricky at the best of time, and FFI between two managed languages much more so.

Python ctypes how to deal with malloc/free?

I'm trying to convert a .cpp file to .py using the ctypes library.
Some basic operations are working, but I'm struggling when using typedef structs as well as malloc/free.
Here what I got:
module.h:
typedef struct
{
CHAR8 name[M_MODNAMELEN_A]; /**< Name of the module. */
} MODULE_NAME;
typedef struct
{
UINT16 countModules; /**< Count of module names.*/
MODULE_NAME * names; /**< Array of module names. */
} MODULE_LIST;
program.cpp:
UINT16 countModules;
SINT32 ret = GetCountModules(targetHandle, &countModules);
if(ret!=M1C_OK)
return ret;
MODULE_LIST modList;
modList.names = (MODULE_NAME*)malloc(countModules * sizeof(MODULE_NAME));
// do some calculations
free( modList.names );
program.py:
from ctypes import *
c_lib = CDLL('libXXYY.so')
class MODULE_NAME(Structure):
_fields_ = [
('name', c_byte)
]
def __repr__(self):
return '({0})'.format(self.name)
class MODULE_LIST(Structure):
_fields_ = [
('countModules', c_uint),
('names', POINTER(MODULE_NAME)) ### Is this conversion correct?
]
def __repr__(self):
return '({0}, {1})'.format(self.x, self.y)
count_modules = c_uint()
ret = c_long(c_lib.GetCountModules(target_handle, byref(count_modules)))
if ret.value != M1C_OK:
return ret
# Up to here everything works fine..
# Allocating memory for the list of module names
modList = MODULE_LIST()
modList.names = ### How to convert the malloc line?
## Some calcs
### How to convert the free( modList.names ); line?
The python runs ok up to the "modList.names = " line.. There I've tried several things (for example: modList.names = (MODULE_NAME)count_modules.value * sizeof(MODULE_NAME()) ) but all my tries have failed.
How should I translate the malloc line from cpp to python?
How should I translate the free line from cpp to python?
Are the translations of the typedef struct to class correct? Specially the one of "MODULE_LIST".
How should I translate the malloc line from cpp to python?
Usually, a DLL that returns something like MODULE_LIST does the malloc for you,
and provides a function to free it later, but if you have to fill one out yourself
an array type is created by multiplying a type by a size, then calling it to create an instance:
m = MODULE_LIST()
m.countModules = countModules
m.names = (MODULE_NAME * countModules)() # create instance of array
How should I translate the free line from cpp to python?
If allocated as above, Python will free it when there are no more references to the memory, such as when m above goes out of scope. You can call del m to explicitly dereference the memory. If m is the only reference, it will be freed.
Are the translations of the typedef struct to class correct? Specially the one of "MODULE_LIST".
No, CHAR8 name[M_MODNAMELEN_A]; is an array, so multiply a type by a length to make the correct array type:
M_MODNAMELEN_A = 100 # some constant that wasn't defined by OP
class MODULE_NAME(Structure):
_fields_ = ('name',c_char * M_MODNAMELEN_A),
MODULE_LIST has a 16-bit integer, so use the correct 16-bit type:
class MODULE_LIST(Structure):
_fields_ = (('countModules',c_uint16),
('names',POINTER(MODULE_NAME)))
To call the function correctly, best practice is to define .argtypes and .restype for the function to improve error checking:
# Assume "int GetCountModules(HANDLE handle, UINT16* pCount)" exists in dll.
dll = CDLL('./somedll')
dll.GetCountModules.argtypes = c_void_p, POINTER(c_uint16)
dll.GetCountModules.restype = c_int
# Make an instance to the output parameter and pass it by reference:
countModules = c_uint16()
ret = dll.GetCountModules(targetHandle, byref(countModules))

Is there a way to receive the return value in Python through the OUT parameter in C++?

I have a C++ dll, one of the export function is defined as follows:
OPERATEMYSQLSTDCALL_API int __stdcall CheckMac(char * pcMac, OUT LPSTR errorInfo);
I use it in python, use the ctypes library,i read some of the information and call it as follws:
from ctypes import *
lib = WinDLL('OperateMysqlStdcall.dll')
CheckMac = lib.CheckMac
CheckMac.argtypes = [c_char_p, POINTER(c_wchar_p)]
CheckMac.restype = c_int
p1=c_wchar_p()
value = CheckMac('88888888',byref(p1));
print p1.value
but when i execute it,it return None,i'm sure the value "OUT LPSTR errorInfo" in C++ is not NULL,i print it in console and it shows Correctly.could anyone tells me why it can't work in python.Thank U very much!
The type of LPSTR is char* so you should use c_char_p for its type as well. As an output parameter, though, you need a writable string buffer. Ideally, the API should indicate the size of the buffer passed so a buffer overrun could be checked.
Here's some test DLL code:
#include <windows.h>
extern "C" __declspec(dllexport)
int __stdcall CheckMac(char* pcMac, LPSTR errorInfo)
{
strcpy(errorInfo, pcMac);
return 1;
}
And Python:
from ctypes import *
lib = WinDLL('test.dll')
CheckMac = lib.CheckMac
CheckMac.argtypes = [c_char_p, c_char_p]
CheckMac.restype = c_int
errorInfo = create_string_buffer(1024)
value = CheckMac('88888888',errorInfo);
print errorInfo.value
Output:
88888888

Python Wrapper to a C Callback

Trying to create a python callback which needs to be invoked while calling the C callback
from a dll in Windows environment. Please review the code below to understand the issue.
from ctypes import *
#---------qsort Callback-------------#
IntArray5 = c_int * 5
ia = IntArray5(5,1,7,33,99)
libc = cdll.msvcrt
qsort = libc.qsort
qsort.restype = None
CMPFUNC = CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int) )
test = 0
def py_cmp_func(a,b):
#print 'py_cmp_func:',a[0],b[0]
global test
test = 10000
return a[0]-b[0]
cmp_func = CMPFUNC(py_cmp_func)
qsort(ia, len(ia), sizeof(c_int), cmp_func)
print "global test=",test
for item in ia : print item
#----------Load DLL & Connect ------------#
gobiDLL = WinDLL("C:\LMS\QCWWAN2k.dll")
print 'Output of connect : ',gobiDLL.QCWWANConnect()
#----------SetByteTotalsCallback----------#
tx = POINTER(c_ulonglong)
rx = POINTER(c_ulonglong)
proto_callback = WINFUNCTYPE(c_void_p,tx,rx)
gtx = grx = 0 # Used to copy the response in the py_callback
def py_callback(t,r):
sleep(10)
print 'python callback ...'
print "tx=",t,"rx=",r
global gtx,grx
gtx = 5000 # gtx = t
grx = 2000 # grx = r
#return 0
callback = proto_callback(py_callback)
gobiDLL.SetByteTotalsCallback.restype = c_ulong
gobiDLL.SetByteTotalsCallback.argtypes = [proto_callback,c_byte]
print "SetByteTotalsCallback = ",gobiDLL.SetByteTotalsCallback(callback, c_byte(256))
print "gtx = ",gtx
print "grx = ",grx
The DLL Documents the Prototype and the callback for the SetByteTotalsCallback() method as shown below.
Prototype :
ULONG QCWWANAPI2K SetSessionStateCallback( tFNSessionState pCallback );
Callback :
void ByteTotalsCallback( ULONGLONG txTotalBytes, ULONGLONG rxTotalBytes );
OUTPUT :
>>>
global test= 10000
1
5
7
33
99
Output of connect : 0
SetByteTotalsCallback = 0
gtx = 0
grx = 0
>>>>
The current problem is that the whole program gets called properly,
but the python callback does not get called at all. The program exits with 0 status from
gobiDLL.SetByteTotalsCallback(callback, c_byte(256)) method, but the callback() method written
in python does not called during the call.
Could you please point out what could help enter the python callback ?
The other sample qsort() method passes the pointer to the python function pointer wonderfully.
At a loss to get the root cause of the issue here.
TIA,
Anthony
You can't. C/C++ functions can't access Python functions directly - that function prototype is probably expecting a pointer to C. Python will be passing it a pointer to its internal data structure for that particular function.
This is the time to build a C extension to python to wrap that DLL and expose it to Python. What you'd do is essentially have the C callback call the Python callback, since that can be done. To be clearer, what you want to achieve is:
| This side is C land i.e. "real" addresses
|
Python objects --> C extension -- register callback with --> DLL
| |
in the python | Calls callback
| |
interpreter <-------------- Callback in C extension <-------
|
The following is a very quick explanation for building a calling a python function from C. You'll need to build this code with the MSVC (or alternative tool) that was used to build your Python distribution; use depends.exe to find out which msvcXX.dll it is linked against.
Global state is generally considered bad, but for simplicity that's what I used:
static PyObject* pyfunc_event_handler = NULL;
static PyObject* pyfunc_event_args = NULL;
I then added a set handler function to make the process of setting the callback easier. However, you don't need to do that, you just need to
static PyObject* set_event_handler(PyObject *self, PyObject *args)
{
PyObject *result = NULL;
PyObject *temp;
The next line is the important one - I allow passing of two python objects (the O arguments to PyArg_ParseTuple. One object contains the function, the other its parameters.
if (PyArg_ParseTuple(args, "OO", &temp, &pyfunc_event_args)) {
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be a function");
return NULL;
}
Sort out references. Python needs you to do this.
Py_XINCREF(temp); /* Add a reference to new func */
Py_XDECREF(pyfunc_event_handler); /* Dispose of previous callback */
pyfunc_event_handler = temp; /* Remember new callback */
/* Boilerplate to return "None" */
Py_INCREF(Py_None);
result = Py_None;
}
return result;
}
You can then call this elsewhere with:
PyObject* arglist = Py_BuildValue("(O)", pyfunc_event_args);
pyobjresult = PyObject_CallObject(pyfunc_event_handler, arglist);
Py_DECREF(arglist);
Don't forget the DECREF, you need Python to gc the arglist.
From python, using this is as simple as:
set_event_handler(func, some_tuple)
Where func has matching parameters like so:
def func(obj):
/* handle obj */
Things you probably want to read up on:
LoadLibrary (load DLL from C).
GetProcAddress (find a function to call).
Extending Python with C or C++ from the Python docs.

C to Python via SWIG: can't get void** parameters to hold their value

I have a C interface that looks like this (simplified):
extern bool Operation(void ** ppData);
extern float GetFieldValue(void* pData);
extern void Cleanup(p);
which is used as follows:
void * p = NULL;
float theAnswer = 0.0f;
if (Operation(&p))
{
theAnswer = GetFieldValue(p);
Cleanup(p);
}
You'll note that Operation() allocates the buffer p, that GetFieldValue queries p, and that Cleanup frees p. I don't have any control over the C interface -- that code is widely used elsewhere.
I'd like to call this code from Python via SWIG, but I was unable to find any good examples of how to pass a pointer to a pointer -- and retrieve its value.
I think the correct way to do this is by use of typemaps, so I defined an interface that would automatically dereference p for me on the C side:
%typemap(in) void** {
$1 = (void**)&($input);
}
However, I was unable to get the following python code to work:
import test
p = None
theAnswer = 0.0f
if test.Operation(p):
theAnswer = test.GetFieldValue(p)
test.Cleanup(p)
After calling test.Operation(), p always kept its initial value of None.
Any help with figuring out the correct way to do this in SWIG would be much appreciated. Otherwise, I'm likely to just write a C++ wrapper around the C code that stops Python from having to deal with the pointer. And then wrap that wrapper with SWIG. Somebody stop me!
Edit:
Thanks to Jorenko, I now have the following SWIG interface:
% module Test
%typemap (in,numinputs=0) void** (void *temp)
{
$1 = &temp;
}
%typemap (argout) void**
{
PyObject *obj = PyCObject_FromVoidPtr(*$1, Cleanup);
$result = PyTuple_Pack(2, $result, obj);
}
%{
extern bool Operation(void ** ppData);
extern float GetFieldValue(void *p);
extern void Cleanup(void *p);
%}
%inline
%{
float gfv(void *p){ return GetFieldValue(p);}
%}
%typemap (in) void*
{
if (PyCObject_Check($input))
{
$1 = PyCObject_AsVoidPtr($input);
}
}
The python code that uses this SWIG interface is as follows:
import test
success, p = test.Operation()
if success:
f = test.GetFieldValue(p) # This doesn't work
f = test.gvp(p) # This works!
test.Cleanup(p)
Oddly, in the python code, test.GetFieldValue(p) returns gibberish, but test.gfv(p) returns the correct value. I've inserting debugging code into the typemap for void*, and both have the same value of p! The call Any ideas about that?
Update: I've decided to use ctypes. MUCH easier.
I agree with theller, you should use ctypes instead. It's always easier than thinking about typemaps.
But, if you're dead set on using swig, what you need to do is make a typemap for void** that RETURNS the newly allocated void*:
%typemap (in,numinputs=0) void** (void *temp)
{
$1 = &temp;
}
%typemap (argout) void**
{
PyObject *obj = PyCObject_FromVoidPtr(*$1);
$result = PyTuple_Pack(2, $result, obj);
}
Then your python looks like:
import test
success, p = test.Operation()
theAnswer = 0.0f
if success:
theAnswer = test.GetFieldValue(p)
test.Cleanup(p)
Edit:
I'd expect swig to handle a simple by-value void* arg gracefully on its own, but just in case, here's swig code to wrap the void* for GetFieldValue() and Cleanup():
%typemap (in) void*
{
$1 = PyCObject_AsVoidPtr($input);
}
Would you be willing to use ctypes? Here is sample code that should work (although it is untested):
from ctypes import *
test = cdll("mydll")
test.Operation.restype = c_bool
test.Operation.argtypes = [POINTER(c_void_p)]
test.GetFieldValue.restype = c_float
test.GetFieldValue.argtypes = [c_void_p]
test.Cleanup.restype = None
test.Cleanup.argtypes = [c_void_p]
if __name__ == "__main__":
p = c_void_p()
if test.Operation(byref(p)):
theAnswer = test.GetFieldValue(p)
test.Cleanup(p)

Categories

Resources