Python:使用ctypes访问DLL函数——按函数*名称*访问失败

11 投票
3 回答
20536 浏览
提问于 2025-04-15 12:42

myPythonClient(下面的代码)想要调用一个名为 ringBell 的函数,这个函数是通过 ctypes 从一个 DLL 文件中加载的。但是,当它试图通过函数的 名称 来访问 ringBell 时,出现了一个 AttributeError 的错误。这是为什么呢?

RingBell.h 文件里包含了

namespace MyNamespace
    {
    class MyClass
        {
        public:
            static __declspec(dllexport) int ringBell ( void ) ;
        } ;
    }

RingBell.cpp 文件里包含了

#include <iostream>
#include "RingBell.h"
namespace MyNamespace
    {
    int __cdecl MyClass::ringBell ( void )
        {
        std::cout << "\a" ;
        return 0 ;
        }
    }

myPythonClient.py 文件里包含了

from ctypes import *
cdll.RingBell[1]() # this invocation works fine
cdll.RingBell.ringBell() # however, this invocation errors out
# AttributeError: function 'ringBell' not found

3 个回答

12

现在一切都正常了 :) 总结一下你的帖子:

用C++写DLL:

// Header
extern "C"
{   // Name in DLL will be "MyAdd" - but you won't be able to find parameters etc...
    __declspec(dllexport) int MyAdd(int a, int b);
}  
// Name will be with lot of prefixes but some other info is provided - IMHO better approach
__declspec(dllexport) int MyAdd2(int a, int b);

//.cpp Code
__declspec(dllexport) int MyAdd(int a, int b)
{   return a+b;
}
__declspec(dllexport) int MyAdd2(int a, int b)
{   return a+b;
} 

然后你可以使用程序link.exe来查看DLL中的真实函数名称。 link.exe在MSVC2010中的位置如下:

c:\program files\microsoft visual studio 10.0\VC\bin\link.exe

使用:

link /dump /exports yourFileName.dll

你会看到类似这样的内容:

ordinal hint RVA      name
      1    0 00001040 ?MyAdd2@@YAHHH@Z = ?MyAdd2@@YAHHH@Z (int __cdecl MyAdd2(int,int))
      2    1 00001030 MyAdd = _MyAdd

然后在Python中你可以这样导入它:

import ctypes

mc = ctypes.CDLL('C:\\testDll3.dll')

#mc.MyAdd2(1,2) # this Won't Work - name is different in dll
myAdd2 = getattr(mc,"?MyAdd2@@YAHHH@Z") #to find name use: link.exe /dump /exports fileName.dll 
print myAdd2(1,2)
#p1 = ctypes.c_int (1) #use rather c types
print mc[1](2,3) # use indexing - can be provided using link.exe

print mc.MyAdd(4,5)
print mc[2](6,7) # use indexing - can be provided using link.exe
13

你的C++编译器会把所有外部可见的对象名称进行“混淆”,这样做是为了让它们的命名能够反映出它们所属的命名空间、类和函数的特征(这也是为什么可以实现函数重载的原因)。

为了避免这种名称混淆,如果你希望某些外部可见的名称能够被非C++代码识别,你需要在这些名称前加上extern "C"。这样做的结果是,这些名称就不能被重载了,而且在C++标准中,它们不能是内联的,也不能放在命名空间或类里面,尽管有些C++编译器在这些方面对标准进行了扩展。

7

可能是因为C++的名字在编译时被处理过,所以在DLL中并没有以RingBell这个名字导出。你有没有确认过它在导出的名称中确实是这个样子呢?

撰写回答