12

最近 ctypes を使い始めたばかりで、ctypes についてあまり知りません。

動的に生成された文字列へのポインターを返す C のような dll の単純な関数があります。
正常に動作していますが、文字列に手動でメモリを割り当てたため、使用後に解放する必要があります。

私はこのようなものを持っています:

extern "C" char* DLL_EXPORT func(const char* str1, const char* str2)
{
    return getSomeString(str1, str2);
}

// Goal is to call this function correctly from Python.    
extern "C" void DLL_EXPORT freeMem(void *mem)
    {
        if(mem!=NULL)
            delete mem;
    }

しかし、私は何の考えも持っていません.Pythonで削除するために受信したポインタを戻すにはどうすればよいですか?

4

3 に答える 3

13

通常、使用する各関数にはctypes引数と戻り値の型が宣言されている必要があります。これにより、Python は正しい数と型の引数をチェックし、Python オブジェクトの引数を正しい C データ オブジェクトに変換できます。残念ながら、この場合、 の通常の戻り値はにfuncなりますc_char_pが、生の C ポインター値へのアクセスを失い、戻り値を Python 文字列にctypes変換しようとします。c_char_p代わりに、戻り値の型を as として宣言し、文字列値を取得するためにPOINTER(c_char)使用できcastます。これにより、戻り値はLP_c_char解放可能なオブジェクトのままになります。

これが例です。デフォルトの戻り値の型は(32 ビット) であり、64 ビットのポインターは切り捨てられる可能性がある.restypeため、64 ビットの Python では正しいと宣言することが特に重要であることに注意してください。c_intこのコードは、32 ビットと 64 ビットの両方のビルドでテストされています。

test.c

#include <string.h>
#include <stdlib.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

API char* func(const char* str1, const char* str2) {
    size_t len = strlen(str1) + strlen(str2) + 1;
    char* tmp = malloc(len);
    strcpy_s(tmp, len, str1);
    strcat_s(tmp, len, str2);
    return tmp;
}

API void freeMem(void *mem) {
    free(mem);
}

test.py

import ctypes as ct

dll = ct.CDLL('./test')
dll.func.argtypes = ct.c_char_p,ct.c_char_p
dll.func.restype = ct.POINTER(ct.c_char)
dll.freeMem.argtypes = ct.c_void_p,
dll.freeMem.restype = None

# Helper function to extract the return value as a Python object
# and always free the pointer.
def freeMem(a,b):
    p = dll.func(b'abcdef', b'ghijkl')
    print(p)
    s = ct.cast(p, ct.c_char_p).value
    dll.freeMem(p)
    return s

print(freeMem(b'abcdef', b'ghijkl'))

出力:

<ctypes.LP_c_char object at 0x00000279D7959DC0>
b'abcdefghijkl'
于 2012-12-17T06:46:55.037 に答える
10

あなたは正しい軌道に乗っています。

// TestDLL.cpp
#include <string.h> // strcpy

extern "C" __declspec(dllexport) char* stringdup(const char* str) {
    char* p = new char[strlen(str)+1];
    strcpy(p,str);
    return p;
}

// if you have no good reason to use void*, use the type
// you've allocated. while it usually works for built-in
// types, it wouldn't work for classes (it wouldn't call
// the destructor)
extern "C" __declspec(dllexport) void stringfree(char* ptr) {
    // you don't need to check for 0 before you delete it,
    // but if you allocate with new[], free with delete[] !
    delete [] ptr; 
}

そしてPythonで:

# Test.py
import ctypes

lib = ctypes.cdll.TestDLL

# this creates a c-style char pointer, initialized with a string whose
# memory is managed by PYTHON! do not attempt to free it through the DLL!
cstr = ctypes.c_char_p("hello ctypes")

# call the dll function that returns a char pointer 
# whose memory is managed by the DLL.
p = lib.stringdup(cstr)

# p is just an integer containing the memory address of the 
# char array. therefore, this just prints the address:
print p

# this prints the actual string
print ctypes.c_char_p(p).value

# free the memory through the DLL
lib.stringfree(p)
于 2012-12-17T06:21:45.970 に答える
0

私のため

#cdll.execute.restype = ctypes.c_char_p               # failed to free
cdll.execute.restype = ctypes.POINTER(ctypes.c_char)  # worked

p = cdll.execute('...')
print(
    ctypes.cast(p, ctypes.c_char_p).value.decode('utf-8')
)

cdll.free_pointer(p)
于 2021-02-18T09:35:46.033 に答える