'Pass char* array from c dll to python

I have simple dll with function which taks callback as parameter and calls it sometimes passing unsigned char* array. I want to use it in python.

Here is dll source:

typedef void (*buffer_ready_callback_t)(unsigned char* data, int len);

extern "C" void __declspec(dllexport) example(buffer_ready_callback_t cb) {
     unsigned char* data = (unsigned char*)malloc(100);
     memset(data,0xAA,100);
     
     cb(data,100);

     free(data);
}

and in python I use it like this:

library = cdll.LoadLibrary("example.dll")
buffer_ready_callback_t = CFUNCTYPE(None, c_char_p, c_int, c_ulong)

def api_callback_buffer(self, data, ln, ts):    
        #problem here
        pass

function = self.library.example
function.restype = None
function.argtypes = [buffer_ready_callback_t]
api_buffer_cb = buffer_ready_callback_t(api_callback_buffer)        
function(api_buffer_cb);

So question is how to use data received in callback function? Is it possible to convert to bytearray or list or or numpy array or something else that can be used in python?

Thanks



Solution 1:[1]

Listing [Python.Docs]: ctypes - A foreign function library for Python.

The question is a bit unclear. Given existing code, you should already have the data as bytes in your callback function.

Here's an example.

dll00.c:

#include <stdio.h>
#include <stdlib.h>

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


typedef void (*Callback)(unsigned char *pData, int len);

#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API void dll00Func00(Callback cb, unsigned int len, unsigned char fillValue);

#if defined(__cplusplus)
}
#endif


void dll00Func00(Callback cb, unsigned int len, unsigned char fillValue) {
    unsigned char *pData = malloc(len);
    memset(pData, fillValue, len);
    cb(pData, len);
    free(pData);
    pData = NULL;
}

code00.py:

#!/usr/bin/env python

import sys
import ctypes as ct


CharArray = ct.POINTER(ct.c_char)  # #1: Use ct.POINTER(ct.c_char) instead of ct.c_char_p as 2nd arg type
#CharArray = ct.c_char_p
Callback = ct.CFUNCTYPE(None, CharArray, ct.c_int)

DLL_NAME = "./dll00.dll"

DATA_LEN = 0x100


def func(data, length):
    print("Length: {0:d}".format(length))
    print(type(data))
    print("Elements:")
    for idx in [0, DATA_LEN - 1]:
        print("  {0:3d} - {1:}".format(idx, data[idx]))
    b = b"".join((data[i] for i in range(length)))
    print(type(b), len(b), b)


def main(*argv):
    dll00 = ct.CDLL(DLL_NAME)
    dll00Func00 = dll00.dll00Func00
    dll00Func00.argtypes = (Callback, ct.c_uint, ct.c_ubyte)
    dll00Func00.restype = None

    dll00Func00(Callback(func), DATA_LEN, 0xAA)  # #2: Pass 0x00 as 3rd arg and uncomment line #8, and you'll see what I meant in the 1st note


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

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q063190506]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul

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

[prompt]> cl /nologo /MD /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.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]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32

Length: 256
<class 'ctypes.LP_c_char'>
Elements:
    0 - b'\xaa'
  255 - b'\xaa'
<class 'bytes'> 256 b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'

Done.

Notes:

  • c_char_p is to be used with NUL terminated strings, using it here would be incorrect (even if apparently everything seems OK)
  • The example is a bit more complex (to easily exemplify the previous bullet)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1