使用ctypes从C ++函数返回字符串会得到大整数,而不是字符指针

4
我正在尝试使用Python中的ctypes从dll调用C++函数。我目前的问题是该函数似乎返回一个大整数,无论是正数还是负数,而不是我期望的char指针。如果我将该整数转换为c_char_p并在其上调用.value,则每次都会杀死我的内核。我在这个网站和文档中到处寻找答案,但仍然无法解决这个问题。我在这个网站上看到的很多东西甚至会为我报错,比如向ctypes对象和函数传递字符串时应该是字节对象或类似的内容。以下是我转换为dll的C++代码以及我用于从dll调用函数的Python代码。如果有人能帮助我,那就太棒了。SaySomething是要处理的函数。谢谢。

TestLibrary.h

#pragma once

#ifdef TESTLIBRARY_EXPORTS
#define TESTLIBRARY_API __declspec(dllexport)
#else
#define TESTLIBRARY_API __declspec(dllexport)
#endif

#include <windows.h>
#include <cstring>

#ifdef __cplusplus
extern "C"
{
#endif

    TESTLIBRARY_API char* SaySomething(const char* phrase);

#ifdef __cplusplus
};
#endif

TestLibrary.cpp

#include "stdafx.h"
#include "TestLibrary.h"
#include <iostream>

TESTLIBRARY_API char* SaySomething(const char* phrase)
{
    char* p = new char [100];
    p = "string something";
    return p;
}

tester2.py

import ctypes

dbl = ctypes.c_double
pChar = ctypes.c_char_p
pVoid = ctypes.c_void_p

libName = (r"D:\Documents\Coding Stuff\Visual Studio 2017\Projects\TestLibrary"
           r"Solution\x64\Debug\TestLibrary.dll")
x = ctypes.windll.LoadLibrary(libName)

x.SaySomething.argtypes = [pChar]
x.SaySomething.restypes = pChar

phrase = b"Hi"
phrase = pChar(phrase)

res = x.SaySomething(phrase)

1
一个 C 函数返回单个类型的单个值。它是 restype,而不是 restypes - Eryk Sun
1
请注意,在您的C++函数中,p = "string something";可能不会像您想象的那样工作。令人惊讶的是,结果看起来似乎可以正常工作 ;) - ddbug
1
另一个无关的问题是,当未定义TESTLIBRARY_EXPORTS时,TESTLIBRARY_API的定义使用了dllexport。它应该是dllimport。这不会影响通过ctypes使用库。但对于C/C++库的用户来说,这是个问题。 - Eryk Sun
调用者的dll代码有缺陷,它返回一个const*(并导致内存泄漏),但他的意图是返回一个可写的新分配的缓冲区。 - ddbug
@eryksun,这很傻。我已经在其他函数中使用了ctypes并且没有出现拼写错误。有趣的是,我一直在看这段代码,并且总是忽略它。但是我已经修复了它,现在似乎可以工作了。所以谢谢你。 - ZachR
显示剩余6条评论
1个回答

9

虽然您可以创建一个可以实现您想要的API,但目前会有内存泄漏问题。更好的解决方案是让Python为结果分配和管理内存。

我还根据评论中提到的内容修复了'dllimport',并在.cpp文件中定义了'TESTLIBRARY_EXPORTS',以便从DLL导出该函数。 'restype'也已修复。

TesterLibrary.h

#pragma once

#ifdef TESTLIBRARY_EXPORTS
#define TESTLIBRARY_API __declspec(dllexport)
#else
#define TESTLIBRARY_API __declspec(dllimport)
#endif

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef __cplusplus
extern "C" {
#endif

TESTLIBRARY_API char* SaySomething(const char* phrase, char* result, size_t resultMaxLength);

#ifdef __cplusplus
}
#endif

TesterLibrary.cpp

#define TESTLIBRARY_EXPORTS
#include "TestLibrary.h"
#include <stdio.h>

TESTLIBRARY_API char* SaySomething(const char* phrase, char* result, size_t resultMaxLength)
{
    _snprintf_s(result,resultMaxLength,_TRUNCATE,"Decorated <%s>",phrase);
    return result;
}

tester2.py

import ctypes

libName = (r"TestLibrary.dll")
x = ctypes.CDLL(libName)

x.SaySomething.argtypes = [ctypes.c_char_p,ctypes.c_char_p,ctypes.c_size_t]
x.SaySomething.restype = ctypes.c_char_p

phrase = b"Hi"
result = ctypes.create_string_buffer(100)
res = x.SaySomething(phrase,result,ctypes.sizeof(result))
print(res)
print(result.value)

输出

b'Decorated <Hi>'
b'Decorated <Hi>'

当没有更多的引用时,Python会自动释放result缓冲区。


1
@ZachR,如果你不用这种方式(这是最简单和最可靠的方式),结果类型必须更改为c_void_p,然后进行转换,或者使用非简单指针类型,例如POINTER(c_char)c_char_p的子类。否则,ctypes.c_char_p的getfunc将会将以空字符结尾的字符串结果复制到新的Python字符串对象中,这将导致内存泄漏。另一个复杂之处在于,如果调用者必须释放库分配的内存,则必须由库导出此功能的函数。不能假设DLL和Python使用相同的C运行时。 - Eryk Sun
马克,我一定会试试这个。看起来很可靠。我会告诉你们它的进展情况。 @eryksun 正如我在上面的评论中提到的,还有很多我不知道的东西,所以谢谢你。我知道内存泄漏的问题,并希望在处理它之前让它正常工作。当我处理这个问题时,我会记住所有这些。再次感谢。 - ZachR
@Mark Tolonen 运行得非常好!谢谢。也感谢其他所有人。 - ZachR
1
@JackClayton 在这两种情况下,C源指针是相同的。ctypes将数据转换为Python bytes类型。bytes是不可变的,因此必须复制数据以创建bytes对象。这两种方法都可以通过Python GC进行清理。如果有输出缓冲区,则不需要返回值。那只是为了演示。 - Mark Tolonen
@JackClayton 这不是缺点,而是它在大矩阵处理方面的优势。 - Mark Tolonen
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接