• (转)python调用dll文件


    (转)python调用dll文件

     

    转自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!
    ")
    

    注释部分效果等同于未注释的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!
    ")
    
    # 第三种
    dll = ctypes.CDLL('msvcrt.dll')
    lib = ctypes.CDLL(None, handle=dll._handle)
    lib.printf(b"hello world!
    ")
    
    # 第四种
    ctypes.cdll.msvcrt.printf(b'hello world!
    ') 
    

    第二种打印了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!
    ")
      File "C:ProgramDataAnaconda3libctypes\__init__.py", line 357, in __getattr__
        func = self.__getitem__(name)
      File "C:ProgramDataAnaconda3libctypes\__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!
    ")
    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文件中写声明,似乎是可有可无的。点击编译,在项目的binDebug目录就能找到编译好的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:ProgramDataAnaconda3libctypes\__init__.py", line 357, in __getattr__
        func = self.__getitem__(name)
      File "C:ProgramDataAnaconda3libctypes\__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:ProgramDataAnaconda3libctypes\__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?
  • 相关阅读:
    JavaEE——SpringMVC(11)--拦截器
    JavaEE——SpringMVC(10)--文件上传 CommonsMultipartResovler
    codeforces 460A Vasya and Socks 解题报告
    hdu 1541 Stars 解题报告
    hdu 1166 敌兵布阵 解题报告
    poj 2771 Guardian of Decency 解题报告
    hdu 1514 Free Candies 解题报告
    poj 3020 Antenna Placement 解题报告
    BestCoder5 1001 Poor Hanamichi(hdu 4956) 解题报告
    poj 1325 Machine Schedule 解题报告
  • 原文地址:https://www.cnblogs.com/liangqihui/p/13729834.html
Copyright © 2020-2023  润新知