使用 ctypes 列出从 dll 导出的函数

Posted

技术标签:

【中文标题】使用 ctypes 列出从 dll 导出的函数【英文标题】:list exported functions from dll with ctypes 【发布时间】:2011-02-22 21:43:42 【问题描述】:

有没有办法通过python外部函数库ctypes知道dll导出了哪些函数?

如果可能的话,可以通过 ctypes 了解导出函数的详细信息。

如果是,有人可以提供一个sn-p的代码吗?

【问题讨论】:

【参考方案1】:

我不认为 ctypes 提供这个功能。在带有 Visual Studio 的 Windows 上:

DUMPBIN -EXPORTS XXX.DLL

或者对于 windows 上的 mingw:

objdump -p XXX.dll

【讨论】:

【参考方案2】:

如果您在 Linux 上,有一个方便的实用程序 nm 可以列出共享库的内容(Linux 上总是有一个方便的实用程序,尤其是 C 语言)。

Here is the question about it.

您将它与-D 标志一起使用:nm -D ./libMyLib.so

【讨论】:

【参考方案3】:

一般来说,这是不可能的,因为一般来说,动态加载的库不携带您需要的元信息。在某些特殊情况下,可以通过系统特定的方式获取该信息,但ctypes 本身并不获取该信息。您可以通过ctypes记录此类信息(参见例如函数指针的restype 和argtypes 属性),但前提是您必须通过不同的方式获得它。

【讨论】:

【参考方案4】:

以下方法适用于 Windows 和 Ubuntu。对于 Windows,Cygwin 是必需的。

假设有一个c 文件,如下所示,名称为test.c

int func1(int a, int b)
    return a + b;


int func2(int a, int b)
    return a - b;

上面的c代码用下面的命令编译成test.dll文件:

gcc -shared -Wl,-soname,adder -o test.dll -fPIC test.c

下面的 Python 脚本查找test.dll 的哪些函数可以被 Python 使用。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from subprocess import Popen, PIPE

out = Popen(
    args="nm ./test.dll", 
    shell=True, 
    stdout=PIPE
).communicate()[0].decode("utf-8")

attrs = [
    i.split(" ")[-1].replace("\r", "") 
    for i in out.split("\n") if " T " in i
]

from ctypes import CDLL

functions = [i for i in attrs if hasattr(CDLL("./test.dll"), i)]

print(functions)

我在 Windows 中得到的输出如下:

['func1', 'func2']

我在 Ubuntu 中得到的输出如下:

['_fini', 'func1', 'func2', '_init']

上面列表中的项目是_FuncPtr类的对象。

【讨论】:

【参考方案5】:

@Mark 的回答使用 Visual Studio 工具。

在windows上你也可以使用Dependency Walker来获取dll导出的函数名。

有时名称会被破坏,不能用作有效的 Python 函数名称。

您可以使用getattr 来获取损坏函数的句柄,例如:

mylib = ctypes.cdll('mylib.dll')
my_func = getattr(mylib, '_my_func@0')

my_func()

【讨论】:

【参考方案6】:

如果您也有上述库的源代码,并且您正在寻找一种完全自动化的全 python 方式,您可以使用pycparser

文件:prog.c

typedef short int ret_t;
typedef short int param_t;

ret_t add(param_t a, param_t b) 
    return (ret_t)(a + b);


ret_t passthrough(ret_t (* func)(param_t a, param_t b), param_t a, param_t b) 
    // parameter intentionally altered.
    // if this isn't done, compiler will deem entire function redundant
    return func(a, b + 1);

使用gcc编译

gcc -I. -E ./prog.c > prog-preproc.c

给我们预处理的c文件:prog-preproc.c 然后在python中:

import pycparser
parser = pycparser.c_parser.CParser()

with open('prog-preproc.c', 'r') as fh:
    ast = parser.parse(fh.read())

class FunctionVisitor(pycparser.c_ast.NodeVisitor):
    def visit_FuncDef(self, node):
        print("found function: %s" % node.decl.name)
        #node.show()

FunctionVisitor().visit(ast)

产量

found function: add
found function: passthrough

为了进一步挖掘,您还可以获取参数和返回类型。 取消注释 node.show() 以获取抽象语法树 (AST) 中的更多信息

【讨论】:

是的;应该可以,相信它们是相同的,只是扩展名不同。 测试过了,好像没有【参考方案7】:

是的!有一种非常聪明的本地方法可以做到这一点。

假设您正在使用 Python ctypes。在你的 C 代码中加入这样的东西:

1) 在您的 C 代码中:

#define PYEXPORT extern "C" __declspec(dllexport)

现在将 PYEXPORT 放在要导出的函数上方:

PYEXPORT
int myfunc(params)

2) 编译完成后,回到 Python 并打开你的 .c 文件,然后像这样解析它:

source1_ = open(cfile_name + '.c')
source1 = source1_.read()
source1_.close()
fn = source1.split('PYEXPORT')[-1].split('(')[0].split(' ')[1]

shell 输入:fn

shell 输出:'myfunc'

3) 现在是聪明的部分:在字符串中定义一个新函数:

a1 = """
global get_c_fn
def get_c_fn(dll):
     func = dll."""
a2 = """
return func"""
a3 = a1 + fn + a2

print(a3)
global get_c_fn
def get_c_fn(dll):
    func = dll.myfunc
    return func

现在执行 exec(a3) ,它将将该字符串转换为您可以使用的函数。

4) 照常行事:

mydll = ctypes.CDLL(cfile_name + '.dll')
c_fn = get_cuda_fn(mydll)
c_fn.argtypes =  func_params (an array of C-converted inputs you need)
c_fn( *[params] )

你有一个 C 脚本的 python 包装器,而不必在每次发生变化时修改十个不同的东西。

【讨论】:

【参考方案8】:

ctypes 内部使用动态链接库提供的函数(unix 上为 dlopen/dlsym,windows 上为 LoadLibrary/GetProcAddress)加载库并查找函数名指定的函数地址;然后使用cffi库动态传递参数。

问题是ctypes所依赖的动态链接库不包含从共享库中列出符号的函数,这就是为什么你不能按ctypes列出符号的原因。

为此,您必须使用特定工具来转储 elf 文件(在 unix 上为 readelf)和为 dll 转储 pe 文件(在 Windows 上为转储)。

【讨论】:

这真的很有趣...我使用 Wumpbin 来查找库中的内容...我现在如何使用它?我需要知道它是否可以使用参数...

以上是关于使用 ctypes 列出从 dll 导出的函数的主要内容,如果未能解决你的问题,请参考以下文章

C.dll 如何向 Python 公开变量?

python中的ctypes,在DLL中调用函数的问题

我们可以从 dll 中导入和导出函数吗

Ctypes找不到我加载的dll的dll依赖

我现在想把自己写的python模块源代码封装成dll,然后在别的python脚本里调用,可以吗?

访问 .exe 导出函数时 Python ctypes 访问冲突