zoukankan      html  css  js  c++  java
  • (转)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?
  • 相关阅读:
    GoLang之网络
    GoLang之方法与接口
    GoLang之基础
    Twemproxy 缓存代理服务器
    判断点是否在三角形内
    C++中const 的各种用法
    解决java web中safari浏览器下载后文件中文乱码问题
    Spring MVC如何测试Controller(使用springmvc mock测试)
    java生成指定范围的随机数
    itextpdf添加非自带字体(例如微软雅黑)
  • 原文地址:https://www.cnblogs.com/liangqihui/p/13729834.html
Copyright © 2011-2022 走看看