Eby
Eby

Reputation: 43

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 outputs

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'

I dont't know how to write to the given buffer. I specified type of pt as c_wchar_p, but the type of pt was changed to str. I tried to just assign string to pt, of course, not worked.

Edit1:

I found a working way but not good.

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]

Is there another good way..?

Upvotes: 0

Views: 849

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 177735

Here's a slightly better way. Cast the pointer to (single) wchar to a pointer to an array of wchar. Then you can dereference and write the value directly to the array:

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

This also has the advantage that if you write more that ptsize characters to the array, Python will throw an exception. For example:

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

This results in:

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

Be careful of the null termination, though. Python only writes it via .value if it has room in the array. Here's a demo:

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)

Output:

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.

Upvotes: 1

Related Questions