rjhowell44
rjhowell44

Reputation: 23

How can I return a wchar_t** - null terminated array of UNICODE strings - to a Python script using CTypes

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.

  1. How would I define the `set_list_of_strings.argtypes[??] to check/restrict the input.
  2. How would I define the wrapper function to correctly return the list of strings.

Thanks,

Upvotes: 1

Views: 342

Answers (1)

Neitsa
Neitsa

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

Related Questions