Reputation: 23
Given two functions implemented in a shared library called c-lib.so
, how can I correctly define/wrap the functions using CTypes.
set_list_of_strings(wchar_t** list);
wchar_t** get_list_of_strings();
For the set function, the following works if added to a python wrapper module
import ctypes
_lib = ctypes.CDLL('c-lib.so')
def set_list_of_strings(list):
global _dsl
arr = (ctypes.c_wchar_p * len(list))
arr[:] = list
_lib.set_list_of_strings(arr)
I have two questions related to the above.
Thanks,
Upvotes: 1
Views: 342
Reputation: 8166
I did a simple example where the C (in fact wrapped C++) code gets a list of strings and copy them then the get_list_of_strings
function returns them suffixed with the string position.
As for the definition of the function in python:
# wchar_t** get_list_of_strings()
get_list_of_strings.restype = ctypes.POINTER(ctypes.c_wchar_p)
# void set_list_of_strings(wchar_t** list)
set_list_of_strings.argtypes = (ctypes.POINTER(ctypes.c_wchar_p), )
The way to get the string out of the wchar_t**
is quite simple: you can index the returned pointer itself and get the strings out of it. Since the returned array end with the nullptr
(converted to None
) you can just stop the iteration at that point:
result = get_list_of_strings()
index = 0
while True:
p = result[index]
if p is None:
break
index += 1
print(p)
The tricky part is initializing the array of wchar_t*
pointer:
def list_to_wchar_t_pp(l):
if not l:
return None
buffers = [ctypes.create_unicode_buffer(s) for s in l]
addresses = list(map(ctypes.addressof, buffers))
addresses.append(0) # append nullptr at the end
pp = (ctypes.c_wchar_p * (len(l) + 1))(*addresses)
return pp
We create Unicode buffers from the strings, get the addresses of those buffers, add a null pointer at the end. Then we initialize the array of pointers with those addresses.
Python Code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import string
import ctypes
def list_to_wchar_t_pp(l):
if not l:
return None
buffers = [ctypes.create_unicode_buffer(s) for s in l]
addresses = list(map(ctypes.addressof, buffers))
addresses.append(0) # append nullptr at the end
pp = (ctypes.c_wchar_p * (len(l) + 1))(*addresses)
return pp
def main():
dll = ctypes.WinDLL(r"TestDll.dll")
# wchar_t** get_list_of_strings()
get_list_of_strings = dll.get_list_of_strings
get_list_of_strings.restype = ctypes.POINTER(ctypes.c_wchar_p)
# void set_list_of_strings(wchar_t** list)
set_list_of_strings = dll.set_list_of_strings
set_list_of_strings.argtypes = (ctypes.POINTER(ctypes.c_wchar_p), )
strings = ["foo", "bar", "baz"]
pp_strings = list_to_wchar_t_pp(strings)
if not pp_strings:
return -1
set_list_of_strings(pp_strings)
result = get_list_of_strings()
index = 0
while True:
p = result[index]
if p is None:
break
index += 1
print(p)
return 0
if __name__ == "__main__":
sys.exit(main())
Output:
foo #1
bar #2
baz #3
C++ code for the shared lib (Windows DLL); note that freeing the global buffer and its pointers should be implemented as well.
#include <cstdlib>
#include <string>
#include <iostream>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
static wchar_t** g_list;
EXTERN_DLL_EXPORT void set_list_of_strings(wchar_t** list)
{
auto count = 0;
while(true)
{
const auto p = list[count];
if (p == nullptr)
break;
count++;
}
if (count == 0)
return;
g_list = new wchar_t* [count + 1];
for(int i = 0; i < count; i++)
{
const auto s = list[i];
const auto len = wcslen(s);
auto p = new wchar_t[len + 1];
wcscpy_s(p, len + 1, s);
g_list[i] = p;
}
g_list[count] = nullptr;
}
EXTERN_DLL_EXPORT wchar_t** get_list_of_strings()
{
if (g_list == nullptr)
return nullptr;
auto count = 0;
while(true) {
const auto p = g_list[count];
if (p == nullptr)
break;
count++;
}
if (count == 0)
return nullptr;
const auto new_list = new wchar_t* [count + 1];
for(int i = 0; i < count; i++)
{
std::wstring ws(g_list[i]);
ws += L" #" + std::to_wstring(i + 1);
const auto length = ws.length();
const auto buff_len = ws.length() + 1;
const auto buff = new wchar_t[buff_len];
wcscpy_s(buff, ws.length() + 1, ws.data());
new_list[i] = buff;
}
new_list[count] = nullptr;
return new_list;
}
Upvotes: 1