0

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..?

1 Answer 1

1

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.
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you so much for your answer!
@Eby Note also that if you want a null-terminated string, in a callback case you will have to explicitly write the null as well, e.g. `pa.contents.value = 'test string\0'. Python only adds the null to the value if there is room. When the value written is exactly the length of the buffer, the null will be left off.
I appreciate your help sir!!
@Tolonen If you don't mind, I have one more question. I put an unicode character to buffer then it did not be printed on console, pa.contents.value = 'python string❤😊' Is it maybe an encoding problem or a locale problem of wcout..?
@Eby See this answer
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.