(转)python调用dll文件

python

转自https://blog.yasking.org/a/python-use-dll.html

最近接触了一个测试,需要手动调用别人提供的DLL文件,想来Python做这个事情应该是很容易,果然,网上搜索解决方案使用ctypes几行代码就可以,然而运行发现各种报错... 或者说我对DLL的了解太少了,任务让开发的同事帮忙封装成命令行执行文件,输出结果后分析文件结果搞定了,但是不琢磨一下很是不舒服...下边记录了生成DLL文件,Python调用DLL文件,还有一些注意事项,当做记录

环境: Windows7(64位)+ Python3.6(32位 / 64位)

调用都是在32位的Python下测试通过的,之后也会用64位上运行,记录一些因为Python版本不同所产生的问题

(一)DLL调用方式 主要有两种函数调用约定(__cdecl__stdcall),__cdecl是C语言默认的调用方式,__stdcall 是C++语言的标准调用方式,VC++项目默认情况下生成的是__cdecl的,__cdecl 支持变长参数而__stdcall方式不支持

这方面有兴趣可以自行多了解一下, 现在,知道DLL是有不同调用方式就行了

(二)Python调用msvcrt.dll

代码很简单,msvcrt.dll__cdecl方式调用的DLL,代码如下

#!/bin/env python

# -*- coding: utf-8 -*-

import ctypes

lib= ctypes.CDLL(\'msvcrt.dll\')

#lib= ctypes.cdll.LoadLibrary(\'msvcrt.dll\')

lib.printf(b"hello world!\n")

注释部分效果等同于未注释的CDLL,类似的加载__stdcall方式的也有两种写法

lib= ctypes.WinDLL(\'a.dll\')

lib= ctypes.windll.LoadLibrary(\'a.dll\')

不仅如此,通过如下这几种方式都能花式调用msvcrt.dll

# 第二种

libHandle = ctypes.windll.kernel32.LoadLibraryW(\'msvcrt.dll\')

print(libHandle)

lib = ctypes.CDLL(None, handle=libHandle)

lib.printf(b"hello world!\n")

# 第三种

dll = ctypes.CDLL(\'msvcrt.dll\')

lib = ctypes.CDLL(None, handle=dll._handle)

lib.printf(b"hello world!\n")

# 第四种

ctypes.cdll.msvcrt.printf(b\'hello world!\n\')

第二种打印了libHandle,在我电脑打印出来是这样的地址1633419264,可以把dll文件加上绝对路径再运行一下,打印出来的值为0,通过这个就可以知道dll是不是已经正确导入了

一般来说LoadLibrary能够正确区分DLL的编码类型,或者显示的进行调用,LoadLibraryW用来打开Unicode编码的DLL,LoadLibraryA用来打开ANSI编码的DLL

然后再使用CDLL指定handle加载DLL,就可以使用了

第三种没有用windos的API加载DLL,而是直接使用CDLL加载,这个返回的对象中的_handle属性才是我们要的handle,再次使用CDLL加载就可以使用了

第四种是msvcrt支持的一种方式,不用显式load

尝试将第二种代码修改些东西,看会报什么错

1)将LoadLibraryW修改为LoadLibraryA

Traceback (most recent call last):

File ".\cmp.py", line 14, in <module>

lib.printf(b"hello world!\n")

File "C:\ProgramData\Anaconda3\lib\ctypes\__init__.py", line 357, in __getattr__

func = self.__getitem__(name)

File "C:\ProgramData\Anaconda3\lib\ctypes\__init__.py", line 362, in __getitem__

func = self._FuncPtr((name_or_ordinal, self))

AttributeError: function \'printf\' not found

没有按照正确的编码方式打开,不能从DLL中获取函数

2)将CDLL修改位WinDLL

Traceback (most recent call last):

File ".\cmp.py", line 14, in <module>

lib.printf(b"hello world!\n")

ValueError: Procedure probably called with too many arguments (4 bytes in excess)

DLL成功加载后,因为使用了错误的调用方式,所以参数这个发生了错误

所以调试的时候分两步,一是

(三)制作一个DLL

我用的是Code::Blocks,在Code::Blocks创建工程,选择Dynamic Link Library,创建后会有两个文件,一个cpp一个h文件,将两个文件清空,cpp文件中写入如下代码:

1)__stdcall 调用方式

//main.cpp

#define DLLEXPORT extern "C" __declspec(dllexport)

DLLEXPORT int __stdcall sum(int a, int b) {

return a + b;

}

2)__cdecl 调用方式

//main.cpp

#define DLLEXPORT extern "C" __declspec(dllexport)

DLLEXPORT int __cdecl sum(int a, int b) {

return a + b;

}

我没在h文件中写声明,似乎是可有可无的。点击编译,在项目的bin\Debug\目录就能找到编译好的DLL文件了

使用最简单的方式调用

lib = ctypes.CDLL(\'cdecl_sum.dll\')

a = lib.sum(1, 2)

print(a)

lib2 = ctypes.WinDLL(\'stdll_sum.dll\')

summmm = getattr(lib2, \'sum@8\')

a = summmm(3, 4)

print(a)

上边导出的__stdcall调用方式的DLL函数名称变为了sum@8,这个是__stdcall的导出函数的命名规则,可以使用getattr来获取

作为辅助,可以使用Dependency Walker查看DLL中函数名

下载:http://www.dependencywalker.com/

用这个工具打开DLL,就可以看到DLL中导出的文件名称

(四)Python 64位运行的问题

Python 64位加载32位的DLL,使用第二种方式,加载DLL返回值是0,输出如下,很难定位问题

Traceback (most recent call last):

File ".\cmp.py", line 31, in <module>

a = lib.sum(1, 2)

File "C:\ProgramData\Anaconda3\lib\ctypes\__init__.py", line 357, in __getattr__

func = self.__getitem__(name)

File "C:\ProgramData\Anaconda3\lib\ctypes\__init__.py", line 362, in __getitem__

func = self._FuncPtr((name_or_ordinal, self))

AttributeError: function \'sum\' not found

使用CDLL那个最简单的方式调用

Traceback (most recent call last):

File ".\cmp.py", line 45, in <module>

lib = ctypes.CDLL(\'cdecl_sum.dll\')

File "C:\ProgramData\Anaconda3\lib\ctypes\__init__.py", line 344, in __init__

self._handle = _dlopen(self._name, mode)

OSError: [WinError 193] %1 不是有效的 Win32 应用程序。

这个提示就挺明显了,是64与32位导致的不识别问题

先留一点坑,有时间生成64位的DLL测试一下,就不会有问题了,先这样


【2017-06-28】补充

关于调用传参和返回值的问题

需要进行手动指定,否则载入成功dll,调用会失败,返回奇奇怪怪的数字

dll.addf.restype = c_float 

dll.addf.argtypes = (c_float, c_float)

具体的参看这篇博文,说的挺详细的:python ctypes 探究 ---- python 与 c 的交互

参考:

  1. 函数调用规约(__stdcall 和 __cdecl 的区别浅析)
  2. The Python Standard Library: ctypes
  3. How do I compile for 64bit using G++ w/ CodeBlocks?

以上是 (转)python调用dll文件 的全部内容, 来源链接: utcz.com/z/387185.html

回到顶部