吾八哥博客

您现在的位置是:首页 > 码农手记 > Python > 正文

Python

Python下使用ctypes调用DLL的方法简单总结

吾八哥2018-03-29Python1695

以前一直是做的Windows下的编程,经常要与动态链接库DLL打交道,现在开始玩Python了,总想着尝试玩下混合编程,而且Python的很多底层库也是基于C++实现的,所以就尝试了下使用ctypes来调用DLL的方法,今天把这些尝试简单总结下记录下来!本文里的Python版本为:Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32,DLL采用Delphi语言编写的,如果不了解Delphi,希望看C++的例子的童靴可以联系我或者在QQ群里沟通交流!

一、Python加载DLL的方法

Python里的ctypes提供了两种类型的加载DLL的方法,根据DLL导出函数的调用约定分为:

__cdecl:ctypes.cdll.LoadLibrary('TestFunc.dll')

__stdcall:ctypes.windll.LoadLibrary('TestFunc.dll')

当然调用约定是不止这两种的,但常见的也基本就是这两种方式了,我们在调用DLL的时候一定要弄清楚调用约定方式再去使用。那么如何调用DLL里的方法呢?也很简单跟C里调用基本一致,对应函数名和参数类型即可,例如某DLL的导出函数定义为:

function Sum(const A, B: Integer): Integer; stdcall;

Python里的调用方法为:

import ctypes

dll = ctypes.windll.LoadLibrary("C:/Users/5bug/Documents/www.5bug.wang/TestFunc.dll")
x = dll.Sum(8, 6)
print(x)

如果DLL里没有相应的导出函数 'Sum' 的话,则Python代码在执行的时候则会提示:AttributeError: function 'Sum' not found

二、基本数据类型的传参

Python里的ctypes提供了一些数据类型与DLL里的数据类型相对应,有C/C++基础的话就很容易理解这些基本数据类型的对应关系了。这里在网上找了一张数据类型对应关系图:

捕获.PNG

下面来看一个具体的例子吧!Delphi写的DLL的方法为:

type
  TIntegerArray = array of Integer;
  TCharArray = array [0 .. 1023] of WideChar;
  
procedure TestFunc1(const A, B: Double; const ArrData: TIntegerArray; const ArrLen: Integer; const InStr: PWideChar;
  out OutStr: TCharArray); stdcall;
var
  I: Integer;
  Sum: Double;
  Str: string;
begin
  Sum := A + B;
  for I := 0 to ArrLen - 1 do
    Sum := Sum + ArrData[I];
  Str := string(InStr) + Sum.ToString;
  StrPCopy(@OutStr[0], Str);
end;

Python里调用该函数的方法为:

import ctypes
from ctypes import *

dll = ctypes.windll.LoadLibrary("C:/Users/5bug/Documents/www.5bug.wang/TestFunc.dll")
arr = c_int * 5
InStr = ctypes.create_unicode_buffer("测试的字符串")
OutStr = ctypes.create_unicode_buffer(1024)
a = ctypes.c_double(13.5)
b = ctypes.c_double(14.5)
ArrData = pointer(arr(1, 3, 5, 7, 9))
ArrLen = ctypes.c_int(5)
dll.TestFunc1(a, b, ArrData, ArrLen, InStr, OutStr)
print(OutStr[0::])

只要数据类型对应上了,使用起来还是比较简单的!

三、DLL函数参数输出指针的调用

有如下DLL函数,是在参数里输出指针类型的:

function TestFunc2(var pData: Pointer; var pSize: Integer): Boolean; stdcall;
var
  Str: string;
begin
  Str := '测试的文字1234567890abcde';
  if pData <> nil then
  begin
    pSize := Length(Str) * SizeOf(Char);
    CopyMemory(pData, @Str[1], pSize);
    Result := True;
  end
  else
    Result := False;
end;

在Python里的调用方法为:

import ctypes
from ctypes import *

dll = ctypes.windll.LoadLibrary("C:/Users/5bug/Documents/www.5bug.wang/TestFunc.dll")
pData = ctypes.c_wchar_p("")
pSize = ctypes.c_int(0)
if dll.TestFunc2(ctypes.byref(pData), ctypes.byref(pSize)):
    bytes = ctypes.string_at(pData, pSize.value)
    print(bytes.decode("utf-16"))
else:
    print("TestFunc2返回失败")

四、结构体传参的方式

DLL里使用结构体传参也是非常常见的一种方式,例如有如下DLL函数的结构体传参定义:

type
  TMyRecord = record
    ID: Integer;
    Name: PWideChar;
    Money: Double;
    Limited: Boolean;
  end;

  PMyRecord = ^TMyRecord;

procedure TestFunc3(const AMyRecord: PMyRecord; out OutStr: TCharArray); stdcall;
var
  Str: string;
begin
  Str := Format('%d,%s,%.2f,%s', [AMyRecord.ID, string(AMyRecord.Name), AMyRecord.Money, BoolToStr(AMyRecord.Limited)]);
  StrPCopy(@OutStr[0], Str);
end;

Python里的调用方法为:

import ctypes
from ctypes import *

class MyStruct(Structure):
    _fields_ = [("ID", c_int), ("Name", c_wchar_p), ("Money", c_double), ("Limited", c_bool)]
    
Struct = MyStruct()
Struct.ID = ctypes.c_int(123)
Struct.Name = ctypes.c_wchar_p("吾八哥")
Struct.Money = ctypes.c_double(365.88)
Struct.Limited = ctypes.c_bool(True)
OutStr = ctypes.create_unicode_buffer(1024)
dll.TestFunc3(ctypes.byref(Struct), OutStr)
print(OutStr[0::])

五、回调函数的使用

Python里也提供了DLL里的函数使用回调函数的方法,使用CFUNCTYPE的方式。DLL函数定义如下:

type
  TCallbackFunc = function(const wParam, lParam: Integer): Boolean; stdcall;
  
function TestFunc4(const ACallbackFunc: TCallbackFunc): Boolean; stdcall;
begin
  if Assigned(ACallbackFunc) then
    Result := ACallbackFunc(123, 789)
  else
    Result := False;
end;

Python里的调用方法为:

import ctypes
from ctypes import *

def OnTestCallbackFunc(a, b):
    print(a + b)
    return a >= b

CTestFunc = CFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.c_int)
dll.TestFunc4(CTestFunc(OnTestCallbackFunc))

说到Python里的回调函数的调用,吾八哥我是遇到了一个小坑,目前还未找到解决方法,就是上述Python版本和DLL均为64位的完全正常。但如果Python版本为32位的话,将DLL也编译为32位在使用上述代码进行测试的时候发现调用的时候出现了抛地址异常错误,函数调用是成功的,是在清栈的时候出现异常,如果哪位朋友知道解决方法麻烦告知一声,在此先表示感谢!