从Python调用C++函数

3

我遇到了一个问题,这个dll文件可以被java或php成功调用,但是当我使用python调用时出现访问冲突错误。

我想知道是否c++ char*映射到ctypes c_char_p中有误,以及如何使用python ctypes映射c++ char*。

c++ dll定义

#define Health_API extern "C" __declspec(dllexport)
Health_API unsigned long Initialization(int m_type,char * m_ip,int m_port)
Health_API int GetRecordCount(unsigned long m_handle)

Python 代码

from ctypes import *

lib = cdll.LoadLibrary(r"./Health.dll")

inita = lib.Initialization
inita.argtype = (c_int, c_char_p, c_int)
inita.restype = c_ulong

getcount = lib.GetRecordCount
getcount.argtype = c_ulong
getcount.retype = c_int
# here call
handle = inita(5, '127.0.0.1'.encode(), 4000)
print(handle)
result = getcount(handle)

错误信息:

2675930080 
Traceback (most recent call last):
  File "C:/Users/JayTam/PycharmProjects/DecoratorDemo/demo.py", line 14, in <module>
    print(getcount(handle))
OSError: exception: access violation reading 0x000000009F7F73E0

修改后

我发现如果不定义restype=long,它会返回负数,这是一个值得注意的问题,但我将restype=u_int64更改后,它也无法连接。

令人高兴的是,我的校友在她的计算机(win7 x64)上调用相同的dll成功,并使用以下简单代码:

from ctypes import *

lib = cdll.LoadLibrary("./Health.dll")
handle = lib.Initialization(5, '127.0.0.1'.encode(), 4000)
lib.GetRecordList(handle, '')

我使用的是win10 x64操作系统,python环境是anaconda,我和我的同学使用相同的环境和代码,但仍然返回相同的错误。

Python vs Java

虽然我知道应该是环境引起的问题,但我真的想知道到底是什么导致了这个问题?

Python

每次运行handle时都会得到不规则的数字,而我的同学的电脑得到的是规则的数字。

例如:288584672、199521248、1777824736、-607161376(我没有设置restype)

这是使用Python调用dll,在端口4000上无法连接

Java

也得到了规律的数字,但没有负数。

例如:9462886128、9454193200、9458325520、9451683632

这是Java调用dll

所以我认为应该是这段代码导致了错误,但在其他电脑上没有问题,这太神奇了。

handle = lib.Initialization(5, '127.0.0.1'.encode(), 4000)

c++ dll:

Health_API unsigned long Initialization(int m_type,char* m_ip,int m_port)
{
    CHandleNode* node;
    switch(m_type)
    {
    case BASEINFO:
        {
            CBaseInfo* baseInfo = new CBaseInfo;
            baseInfo->InitModule(m_ip,m_port);
            node = m_info.AddMCUNode((unsigned long)baseInfo);
            node->SetType(m_type);
            return (unsigned long)baseInfo;
        }
        break;
    case BASEINFO_ALLERGENS:
        {
            CBaseInfo_Allergens* baseInfo_Allergens = new CBaseInfo_Allergens;
            baseInfo_Allergens->InitModule(m_ip,m_port);
            node = m_info.AddMCUNode((unsigned long)baseInfo_Allergens);
            node->SetType(m_type);
            return (unsigned long)baseInfo_Allergens;
        }
        break;
    . (case too many, ... represent them)
    .
    .
    }
    return -1;
}


Health_API int GetRecordList(unsigned long m_handle,char* m_index)
{
    CHandleNode* node = m_info.GetMCUNode(m_handle);
    if( node == NULL)
    {
        return -1;
    }
    switch(node->GetType())
    {
    case BASEINFO:
        {
            CBaseInfo* baseInfo = (CBaseInfo*)m_handle;
            return baseInfo->GetRecordList(m_index);
        }
        break;
    case BASEINFO_ALLERGENS:
        {
            CBaseInfo_Allergens* baseInfo_Allergens = (CBaseInfo_Allergens *)m_handle;
            return baseInfo_Allergens->GetRecordList(m_index);
        }
        break;
    . (case too many, ... represent them)
    .
    .
    }
    return -1;
}

Java返回64位值(请注意返回数字的大小)。在Python中,如果您没有指定restype,则默认为“int”,这是有符号整数,因此您会看到负数。在Python中,您必须使用c_void_p,正如我已经解释过的那样,您还应该修复DLL的代码。 - benjarobin
在Python ctypes中,据我所知,不存在u_int64类型。正如我已经解释过的那样,即使C函数原型是整数,您也应该使用c_void_p - benjarobin
你同学的代码有问题,她只是运气好,指针适合在32位整数中。 - benjarobin
谢谢,我很快会修改它。 - JayTam
1个回答

2

可能有C++的源代码会有所帮助,但我可以猜测一些。

我不认为这与此相关,但:

  • getcount.retype缺少一个s,它应该是restype
  • 我总是将argtype定义为数组而不是元组,并且如果只有一个参数,我仍然使用一个包含一个元素的数组。

如果char*是以null结尾的字符串,则确实需要使用c_char_p。在这种情况下,强烈建议在m_ip参数中添加const

如果char*是指向原始缓冲区(由调用函数填充或未填充)的指针(这里不是这种情况),则需要使用POINTER(c_char)而不是c_char_p

根据错误消息,您的handle是一个指针,您应该在C++代码中使用正确的类型: void*。在python中,您应该使用c_void_p

这是64位代码吗?使用哪个工具链/编译器来构建DLL?该工具链中无符号长整型的大小是多少?

编辑:这是您问题的原因:Python的ctypes.c_long在64位系统上是否为64位?

handle是您DLL中的一个指针,并且肯定是使用long为64位的编译器构建的,但是对于Windows上的python 64位,long为32位,无法存储您的指针。您确实应该使用void*

首先,您可以尝试在python中使用c_void_p来处理句柄,而不修改源代码,具体取决于所使用的编译器是否可行。但是,由于任何标准都没有定义long类型的大小,可能无法存储指针,因此您确实应该在C++代码中使用void*uintptr_t

编辑2:如果您正在使用Visual Studio而不是GCC,则长整型的大小仅为32位(x64平台上长整型与int的大小),因此您必须修复DLL的代码。


不,问题不在那里。首先,您的C++代码是错误的,必须进行修正,如已经解释的那样:指针永远不应该放在无符号长整型中,这取决于编译器和平台,它可能无法适应整数。请解释DLL是如何构建的,使用哪个工具链/编译器以及哪个版本。 - benjarobin
DLL使用VS 2010构建,x64,发布版本。我明天会修复DLL的代码,现在已经1:40了,我必须去睡觉了。 - JayTam
你应该再读一遍我的消息,我不知道你正在使用Visual Studio。问题也存在于你的DLL源代码中,因为在Visual Studio中,long无法在64位构建中存储指针。 - benjarobin
谢谢,我会告诉C++代码编写者,我不理解C++。 - JayTam
但是我仍然有一个问题,我们使用Java/PHP/C++多次调用同一个DLL文件,没有问题。 - JayTam
显示剩余4条评论

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