I am working with a .dll that contains a single call which returns an array of function pointers. GetMyApi() returns a pointer to a struct, which is an array of function pointers. The functions themselves have different individual inputs and outputs. What I have tried so far:
C code that I can't easily alter:
Code in C :
typedef struct My_Api_V2
{
int (__cdecl *IsValidInt)(int i);
int (__cdecl *InvalidInt)();
int (__cdecl *IsValidSize)(size_t i);
} my_Api_V2;
const my_Api_V2* GetMyApi(int version); // This function is accessed from DLL
Python effort:
from ctypes import *
my_dll = cdll.LoadLibrary(path_to_my_dll)
my_api = my_dll.GetMyApi
my_api.argtypes[c_int] #version number
my_api.restypes = c_void_p
firstfuncptr = my_api(2)
firstfunc = prototype(firstfuncptr)
firstfunc.argtypes[c_int]
firstfunc.restypes = c_int
test = firstfunc(23)
At this point, I am just trying to get the first function of the function list returned to work. Any help pointing me in better direction is appreciated.
Things are not as easy as one might think at 1st glance. I'm going to post a dummy example that happens to contain the 2 ways of working with functions from .dlls (.sos) (as explained in [Python 3.Docs]: ctypes - A foreign function library for Python).
dll00.c:
#include <stdio.h>
#if defined(_WIN32)
# define DLL00_EXPORT __declspec(dllexport)
# pragma warning(disable: 4477) // !!! Just to avoid having additional code (macro for size_t), do NOT do this !!!
#else
# define DLL00_EXPORT
#endif
#define PRINT_MSG_0() printf(" [%s] (%d) - [%s]\n", __FILE__, __LINE__, __FUNCTION__)
#define PRINT_MSG_1I(ARG0) printf(" [%s] (%d) - [%s]: ARG0: %d\n", __FILE__, __LINE__, __FUNCTION__, ARG0)
static int IsValidInt(int i) {
PRINT_MSG_1I(i);
return -i;
}
static int InvalidInt() {
PRINT_MSG_0();
return 0;
}
static int IsValidSize (size_t i) {
PRINT_MSG_1I(i);
return -i;
}
typedef struct DllInterfaceV2Struct {
int (__cdecl *IsValidIntFuncPtr)(int i);
int (__cdecl *InvalidIntFuncPtr)();
int (__cdecl *IsValidSizeFuncPtr)(size_t i);
} DllInterfaceV2;
static DllInterfaceV2 intfV2 = { IsValidInt, InvalidInt, IsValidSize };
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT const DllInterfaceV2 *GetInterfaceV2(int version);
#if defined(__cplusplus)
}
#endif
DLL_EXPORT const DllInterfaceV2 *GetInterfaceV2(int version) {
if (version == 2) {
return &intfV2;
} else {
return NULL;
}
}
code00.py:
#!/usr/bin/env python3
import sys
import ctypes
DLL_NAME = "test00.dll"
DLL_FUNC_NAME = "GetInterfaceV2"
# "Define" the Python counterparts for C stuff in order to be able to use it
IsValidIntFuncPtr = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
InvalidIntFuncPtr = ctypes.CFUNCTYPE(ctypes.c_int)
IsValidSizeFuncPtr = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_size_t)
class DllInterfaceV2(ctypes.Structure):
_fields_ = [
("is_valid_int", IsValidIntFuncPtr),
("invalid_int", InvalidIntFuncPtr),
("is_valid_size", IsValidSizeFuncPtr)
]
# Now, play with C stuff
def test_interface_ptr(intf_ptr):
print("Testing returned interface: {:}\n".format(intf_ptr))
if not intf_ptr:
print(" NULL pointer returned from C\n")
return
intf = intf_ptr.contents # Dereference the pointer
res = intf.is_valid_int(-2718281)
print(" `is_valid_int` member returned: {:d}\n".format(res))
res = intf.invalid_int()
print(" `invalid_int` member returned: {:d}\n".format(res))
res = intf.is_valid_size(3141592)
print(" `is_valid_size` member returned: {:d}\n\n".format(res))
def main():
test_dll = ctypes.CDLL(DLL_NAME)
get_interface_v2_func = getattr(test_dll, DLL_FUNC_NAME) # Or simpler: test_dll.GetInterfaceV2
get_interface_v2_func.argtypes = [ctypes.c_int]
get_interface_v2_func.restype = ctypes.POINTER(DllInterfaceV2)
pintf0 = get_interface_v2_func(0)
test_interface_ptr(pintf0)
pintf2 = get_interface_v2_func(2)
test_interface_ptr(pintf2)
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Notes:
C part:
Python part:
Although I consider this a major design flaw, in order to keep things as close as possible to the question, I simply followed it (GetInterfaceV2 (the V2 part) doesn't make any sense considering its arg (version))
Output:
(py35x64_test) e:\Work\Dev\StackOverflow\q051507196>"c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64 (py35x64_test) e:\Work\Dev\StackOverflow\q051507196>dir /b code00.py dll00.c (py35x64_test) e:\Work\Dev\StackOverflow\q051507196>cl /nologo dll00.c /link /DLL /OUT:test00.dll dll00.c Creating library test00.lib and object test00.exp (py35x64_test) e:\Work\Dev\StackOverflow\q051507196>dir /b code00.py dll00.c dll00.obj test00.dll test00.exp test00.lib (py35x64_test) e:\Work\Dev\StackOverflow\q051507196>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.py Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32 Testing returned interface: <__main__.LP_DllInterfaceV2 object at 0x00000219984EBAC8> NULL pointer returned from C Testing returned interface: <__main__.LP_DllInterfaceV2 object at 0x00000219984EBB48> [dll00.c] (16) - [IsValidInt]: ARG0: -2718281 `is_valid_int` member returned: 2718281 [dll00.c] (22) - [InvalidInt] `invalid_int` member returned: 0 [dll00.c] (28) - [IsValidSize]: ARG0: 3141592 `is_valid_size` member returned: -3141592
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With