Reputation: 43
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
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