python ctypes 如何将字符串写入给定缓冲区

python ctypes how to write string to given buffer

test.cpp

#include <iostream>
#include <strsafe.h>
using namespace std;

typedef void(__stdcall* Mycallback2)(wchar_t* buff, size_t buffsize);
extern "C" __declspec(dllexport)
void TestMethod5(Mycallback2 callback)
{
    wchar_t* buff = new wchar_t[MAX_PATH]; // MAX_PATH 260
    StringCchPrintf(buff, MAX_PATH, L"%s", L"Test String");
    if (callback) callback(buff, MAX_PATH);
    wcout << buff << endl;
    delete[] buff;
}

test.py

from ctypes import *

dll = CDLL(path)

MYCALLBACKTYPE = CFUNCTYPE(None, c_wchar_p, c_size_t)
dll.TestMethod5.restype = None
dll.TestMethod5.argtypes = [MYCALLBACKTYPE]

def callback(pt: c_wchar_p, ptsize: c_size_t) -> None:
    pt.value = 'python string'
    
mycallback = MYCALLBACKTYPE(callback)
dll.TestMethod5(mycallback)

python 输出

Exception ignored on calling ctypes callback function: <function callback at 0x0000021D50DE7B80>
Traceback (most recent call last):
  File "d:\MyProjects\GitRepo\CodePython\App\dlltest\main.py", line 59, in callback
    pt.value = 'python string'
AttributeError: 'str' object has no attribute 'value'

我不知道如何写入给定的缓冲区。我将 pt 的类型指定为 c_wchar_p,但 pt 的类型已更改为 str。 我试图将字符串分配给 pt,当然没有用。

编辑 1:

我找到了一个可行的方法,但不是很好。

MYCALLBACKTYPE = CFUNCTYPE(None, POINTER(c_wchar), c_size_t)
dll.TestMethod5.restype = None
dll.TestMethod5.argtypes = [MYCALLBACKTYPE]

def callback(pt: POINTER(c_wchar), ptsize: c_size_t) -> None:
    s = 'python string'
    if len(s) < ptsize:
        for i in range(0,len(s)):
            pt[i] = s[i]

还有什么好的方法吗..?

这里有一个稍微好一点的方法。将指向(单个)wchar 的指针转换为指向 wchar 数组的指针。然后你可以取消引用并将值直接写入数组:

def callback(pt: POINTER(c_wchar), ptsize: c_size_t) -> None:
    pa = cast(pt,POINTER(c_wchar * ptsize))
    pa.contents.value = 'python string'

这还有一个好处,如果你向数组写入多于ptsize个字符,Python会抛出异常。例如:

def callback(pt: POINTER(c_wchar), ptsize: c_size_t) -> None:
    s = 'a' * (ptsize + 1)  # one too big
    pa = cast(pt,POINTER(c_wchar * ptsize))
    pa.contents.value = s

这导致:

Exception ignored on calling ctypes callback function: <function callback at 0x0000020B1C7F6310>
Traceback (most recent call last):
  File "C:\test.py", line 13, in callback
    pa.contents.value = s
ValueError: string too long

不过要小心空终止。 Python 只有在数组中有空间时才通过 .value 写入它。这是一个演示:

test.cpp

#include <iostream>
#include <stdio.h>

using namespace std;

typedef void(__stdcall* Mycallback2)(wchar_t* buff, size_t buffsize);

extern "C" __declspec(dllexport)
void TestMethod5(Mycallback2 callback)
{
    wchar_t* buff = new wchar_t[7];        // length 7 buffer
    swprintf_s(buff, 7, L"%s", L"123456"); // init buffer
    if (callback) callback(buff, 5);       // callback alters only 5
    wcout << L'|' << buff << L'|' << endl;
    delete[] buff;
}

test.py

from ctypes import *

dll = CDLL('./test')

MYCALLBACKTYPE = CFUNCTYPE(None, POINTER(c_wchar), c_size_t)
dll.TestMethod5.restype = None
dll.TestMethod5.argtypes = [MYCALLBACKTYPE]

def callback(pt: POINTER(c_wchar), ptsize: c_size_t) -> None:
    s = 'a' * (ptsize if demo else ptsize-1) # 4 or 5 'a' string, no explicit null
    pa = cast(pt,POINTER(c_wchar * ptsize))
    print('pt =',pa.contents.value)
    pa.contents.value = s

mycallback = MYCALLBACKTYPE(callback)
demo = 0
dll.TestMethod5(mycallback)
demo = 1
dll.TestMethod5(mycallback)

输出:

pt = 12345         # only "sees" the size reported
|aaaa|             # null was written since there was room
pt = 12345
|aaaaa6|           # null wasn't written when length 5 value written,
                   # So wcout "saw" the ending 6.