Daan
Daan

Reputation: 2858

How to get a string from C++ to python when using ctypes and wchar_t?

I can:

  1. Get an integer from C++ and use it in python
  2. Send a python string (as a wchar_t) to C++ and do some logic with it

I cannot Step 2 in opposite direction.

Here is my C++ code (compiled with clion and cygwin as a shared library using C++14).

#include <iostream>

wchar_t aa[2];
extern "C" {
    int DoA()
    {
        return 10;
    }
    int DoB(wchar_t  * in)
    {
        if (in[1] == 'a')
        {
            return 25;
        }
        return  30;
    }

    wchar_t * DoC()
    {
        aa[0] = 'a';
        aa[1] = 'b';
        return aa;
    }
}

Here is my python 3.6.1 code that shows what I can and what I cannot do. So how should I get my string and do things with it in python? I expect to use the address with wstring_at to get the value, but it is not working.

from ctypes import *
import os.path
print('Hello')

itExist = os.path.exists('C:/Users/Daan/CLionProjects/stringproblem/cmake-build-release/cygstringproblem.dll')
print(itExist)
lib = cdll.LoadLibrary('C:/Users/Daan/CLionProjects/stringproblem/cmake-build-release/cygstringproblem.dll')
print('dll loaded')

A = lib.DoA()
print(A)
Bx = lib.DoB(c_wchar_p('aaa'))
print(Bx)
By = lib.DoB(c_wchar_p('bbb'))
print(By)
Ca = lib.DoC()
print(Ca)
print('Issue is coming')
Cb = wstring_at(Ca,2)
print(Cb)

Here is the output with error.

Hello

True

dll loaded

10

25

30

-1659080704

Issue is coming
Traceback (most recent call last):
  File "ShowProblem.py", line 19, in <module>
    Cb = wstring_at(Ca,2)
  File "C:\Users\Daan\AppData\Local\Programs\Python\Python36\lib\ctypes\__init__.py", line 504, in wstring_at
    return _wstring_at(ptr, size)
OSError: exception: access violation reading 0xFFFFFFFF9D1C7000

Upvotes: 2

Views: 2117

Answers (2)

Mark Tolonen
Mark Tolonen

Reputation: 177461

If you set the .argtypes and .restype of your wrapped functions, you can call them more naturally. To handle an output string, it will be thread safe if you allocate the buffer in Python instead of using a global variable, or just return a wide string constant. Here's an example coded for the Microsoft compiler:

test.c

#include <wchar.h>
#include <string.h>

__declspec(dllexport) int DoA(void) {
    return 10;
}

__declspec(dllexport) int DoB(const wchar_t* in) {
    if(wcslen(in) > 1 && in[1] == 'a') // Make sure not indexing past the end.
        return 25;
    return  30;
}

// This version good if variable data is returned.
// Need to pass a buffer of sufficient length.
__declspec(dllexport) int DoC(wchar_t* aa, size_t length) {
    if(length < 3)
        return 0;
    aa[0] = 'a';
    aa[1] = 'b';
    aa[2] = '\0';
    return 1;
}

// Safe to return a constant.  No memory leak.
__declspec(dllexport) wchar_t* DoD(void) {
    return L"abcdefg";
}

test.py

from ctypes import *

# Set up the arguments and return type
lib = CDLL('test')
lib.DoA.argtypes = None
lib.DoA.restype = c_int  # default, but just to be thorough.
lib.DoB.argtypes = [c_wchar_p]
lib.DoB.restype = c_int
lib.DoC.argtypes = [c_wchar_p,c_size_t]
lib.DoC.restype = c_int
lib.DoD.argtypes = None
lib.DoD.restype = c_wchar_p

# Map to local namespace functions
DoA = lib.DoA
DoB = lib.DoB
DoD = lib.DoD

# Do some pre- and post-processing to hide the memory details.
def DoC():
    tmp = create_unicode_buffer(3)  # Writable array of wchar_t.
    lib.DoC(tmp,sizeof(tmp))
    return tmp.value  # return a Python string instead of the ctypes array.

print(DoA())
print(DoB('aaa'))
print(DoB('bbb'))
print(DoC())
print(DoD())

Output:

10
25
30
ab
abcdefg

Upvotes: 1

pptaszni
pptaszni

Reputation: 8218

I reproduced your problem on Linux and corrected it by defining the return type from your DoC function:

from ctypes import *
print('Hello')

lib = cdll.LoadLibrary(PATH_TO_TOUR_LIB)
print('dll loaded')
# this line solved the issue for me
lib.DoC.restype = c_wchar_p

A = lib.DoA()
print(A)
Bx = lib.DoB(c_wchar_p('aaa'))
print(Bx)
By = lib.DoB(c_wchar_p('bbb'))
print(By)
Ca = lib.DoC()
print(Ca)
print('Issue is coming')
Cb = wstring_at(Ca,2)
print(Cb)

I also allocated the memory dynamically (some Python expert might comment on this, I guess that this causes a memory leak):

extern "C" {
    int DoA()
    {
        return 10;
    }
    int DoB(wchar_t  * in)
    {
        if (in[1] == 'a')
        {
            return 25;
        }
        return  30;
    }

    wchar_t * DoC()
    {
        wchar_t* aa = new wchar_t[2];
        aa[0] = 'a';
        aa[1] = 'b';
        return aa;
    }
}

Let me know if it works on Windows.

Upvotes: 2

Related Questions