How to cast a ctypes pointer to an instance of a Python class

Question:

Say you have the following C code:

typedef void (*PythonCallbackFunc)(void* userData);

void cb(PythonCallbackFunc pcf, void* userData)
{
    pcf(userData);
}

and the following Python 3 code:

import ctypes

class PythonClass():
    def foo():
        print("bar")

CALLBACK_TYPE = ctypes.CFUNCTYPE(None, ctypes.c_void_p)

def callback(userData):
    instanceOfPythonClass = ???(userData) # <-- this part right here
    instanceOfPythonClass.foo()

lib = ctypes.cdll.LoadLibrary("path/to/lib.dll")

pc = PythonClass()

lib.cb(ctypes.byref(pc), CALLBACK_TYPE(callback))

Where “path/to/lib.dll” is a compiled binary of the C code up top.

How would one go about casting the userData parameter in “callback” back to an instance of PythonClass, so one could call the function “foo()”?

Asked By: Sossisos

||

Answers:

Based on [Python.Docs]: ctypes – A foreign function library for Python, I did some changes to your code in order to make it work.

dll00.c:

#include <stdio.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif

#define C_TAG "From C"
#define PRINT_MSG_0() printf("%s - [%s] (%d) - [%s]n", C_TAG, __FILE__, __LINE__, __FUNCTION__)


typedef void (*PythonCallbackFuncPtr)(void* userData);


DLL00_EXPORT_API void callPython(PythonCallbackFuncPtr callbackFunc, void* userData)
{
    PRINT_MSG_0();
    callbackFunc(userData);
}

code00.py:

#!/usr/bin/env python

import ctypes as ct
import sys


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")

CallbackFuncType = ct.CFUNCTYPE(None, ct.py_object)


class PythonClass():
    def foo(self):
        print("Dummy Python method")


def callback(userData):
    print("From Python: {:s}".format(callback.__name__))
    userData.foo()


def main(*argv):
    dll = ct.CDLL(DLL_NAME)
    callPython = dll.callPython
    callPython.argtypes = [CallbackFuncType, ct.py_object]
    callPython.rettype = None

    instance = PythonClass()
    callPython(CallbackFuncType(callback), instance)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}n".format(" ".join(elem.strip() for elem in sys.version.split("n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("nDone.")
    sys.exit(rc)

Notes:

  • When dealing with Python types, use ctypes.py_object (which is a wrapper over PyObject) rather than ctypes.c_void_p

  • Always define argtypes (and restype) for C functions that you call from Python (e.g. call_python_func (which wraps callPython)). Check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati’s answer) for more details

  • PythonClass.foo was missing the 1st (self) argument and thus being just a function defined inside PythonClass instead of a method

  • Did other non critical changes (mostly renames)

Output:

(py35x64_test) e:WorkDevStackOverflowq052053434>sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:Installpc032MicrosoftVisual Studio Community2015vcvcvarsall.bat" x64

[prompt]> dir /b
code00.py
dll00.c

[prompt]> cl /nologo /DDLL dll00.c  /link /DLL /OUT:dll00.dll
dll.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]> "e:WorkDevVEnvspy35x64_testScriptspython.exe" code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] 064bit on win32

From C - [dll00.c] (18) - [callPython]
From Python: callback
Dummy Python method
Answered By: CristiFati
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.