Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python ctypes to return an array of function pointers

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.

like image 856
Jiminion Avatar asked Jul 24 '18 20:07

Jiminion


1 Answers

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:

    • I had to add some dummy code in order to test and illustrate the behavior
    • Although you mentioned that it's not modifiable, I changed stuff (mostly naming / coding style, ...):
      • Both letter cases + underscores don't look nice (at least to me)
      • "my" (or any of its variants) in (functions, classes, or any other) names simply scratches my brain
  • Python part:

    • As I stated in my comment, the C stuff would have to be "duplicated" in Python
  • 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))

    • My personal opinion (without having all the context, though) is that (in order to ensure scalability) the function should return a generic structure, with an additional field (e.g. version) that could be checked by client apps.

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
like image 78
CristiFati Avatar answered Nov 11 '22 13:11

CristiFati