如何使用 CTypes 将 wchar_t** - 以空结尾的 UNICODE 字符串数组 - 返回到 Python 脚

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

本文关键字:UNICODE 字符串 数组 Python 返回 结尾 CTypes 何使用 wchar      更新时间:2023-10-16

给定在名为c-lib.so的共享库中实现的两个函数,如何使用CTypes正确定义/包装函数。

set_list_of_strings(wchar_t** list);
wchar_t** get_list_of_strings();

对于 set 函数,如果添加到 python 包装器模块中,则以下内容有效

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)

我有两个与上述相关的问题。

  1. 我将如何定义'set_list_of_strings.argtypes[??]来检查/限制输入。
  2. 我将如何定义包装函数以正确返回字符串列表。

谢谢

我做了一个简单的例子,其中 C(实际上是包装的 C++(代码获取字符串列表并复制它们,然后 get_list_of_strings 函数返回它们以字符串位置为后缀。

至于 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), )

wchar_t**中获取字符串的方法非常简单:您可以索引返回的指针本身并从中获取字符串。由于返回的数组以 nullptr 结尾(转换为 None (,您可以在此时停止迭代:

    result = get_list_of_strings()
    index = 0
    while True:
        p = result[index]
        if p is None:
            break
        index += 1
        print(p)

棘手的部分是初始化wchar_t*指针数组:

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

我们从字符串创建 Unicode 缓冲区,获取这些缓冲区的地址,在末尾添加一个空指针。然后我们使用这些地址初始化指针数组。

蟒蛇代码:

#!/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())

输出:

foo #1
bar #2
baz #3

C++共享库(Windows DLL(的代码;请注意,释放全局缓冲区及其指针也应实现。

#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;
}