【问题标题】:python ctypes how to write string to given bufferpython ctypes如何将字符串写入给定的缓冲区
【发布时间】:2021-12-11 21:24:39
【问题描述】:

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]

还有什么好办法吗..?

【问题讨论】:

    标签: python buffer ctypes


    【解决方案1】:

    这里有一个稍微好一点的方法。将指向(单个)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.
    

    【讨论】:

    • 非常感谢您的回答!
    • @Eby 另请注意,如果您想要一个以 null 结尾的字符串,在回调情况下,您还必须显式写入 null,例如`pa.contents.value = '测试字符串\0'。如果有空间,Python 只会将 null 添加到值中。当写入的值恰好缓冲区的长度时,null 将被忽略。
    • 感谢您的帮助,先生!
    • @Tolonen 如果您不介意,我还有一个问题。我把一个unicode字符放到缓冲区然后它没有打印在控制台上,pa.contents.value = 'python string❤?'是编码问题还是wcout的语言环境问题..?
    • @Eby 见this answer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 2012-04-14
    • 1970-01-01
    相关资源
    最近更新 更多