(轉)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!\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?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM