Python调用windows下DLL详解

本文介绍了如何使用Python中的ctypes模块方便地调用Windows的DLL,包括加载DLL、调用函数、处理不同数据类型及结构体的方法。并通过两个实际例子演示了GUID生成器和启动记事本进程的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在python中某些时候需要C做效率上的补充,在实际应用中,需要做部分数据的交互。使用python中的ctypes模块可以很方便的调用windows的dll(也包括linux下的so等文件),下面将详细的讲解这个模块(以windows平台为例子),当然我假设你们已经对windows下怎么写一个DLL是没有问题的。

引入ctypes库


from ctypes import *

假设你有了一个符合cdecl(这里强调调用约定是因为,stdcall调用约定和cdecl调用约定声明的导出函数,在用python加载使用的加载函数是不同的,后面会说明)调用约定的DLL(名字是add.dll),且有一个导出函数Add。

建立一个Python文件DllCall.py测试:

from ctypes import *
dll = CDLL("add.dll")
print dll.Add(1, 102)
结果:103

上面是一个简单的例子。
1、加载DLL
上面已经说过,加载的时候要根据你将要调用的函数是符合什么调用约定的。

stdcall调用约定:两种加载方式
Objdll = ctypes.windll.LoadLibrary("dllpath")
Objdll = ctypes.WinDLL("dllpath")

cdecl调用约定:也有两种加载方式
Objdll = ctypes.cdll.LoadLibrary("dllpath")
Objdll = ctypes.CDLL("dllpath")

其实windll和cdll分别是WinDLL类和CDll类的对象。


2、调用dll中的方法

在1中加载dll的时候会返回一个DLL对象(假设名字叫Objdll),利用该对象就可以调用dll中的方法。
e.g.如果dll中有个方法名字叫Add(注意如果经过stdcall声明的方法,如果不是用def文件声明的导出函数的话,编译器会对函数名进行修改,这个要注意)
调用:nRet = Objdll.Add(12, 15) 即完成一次调用。
看起来调用似乎很简单,不要只看表象,呵呵,这是因为Add这个函数太简单了,现在假设函数需要你传入一个int类型的指针(int*),可以通过库中的byref关键字来实现,假设现在调用的函数的第三个参数是个int类型的指针。
intPara = c_int(9)
dll.sub(23, 102, byref(intPara))
print intPara.value
如果是要传入一个char缓冲区指针,和缓冲区长度,方法至少有四种:
# char* -- 1
szPara = create_string_buffer('\0'*100)
dll.PrintInfo(byref(szPara), 100);
print szPara.value
 
# char* -- 2
sBuf = 'aaaaaaaaaabbbbbbbbbbbbbb'
pStr = c_char_p( )
pStr.value = sBuf
#pVoid = ctypes.cast( pStr, ctypes.c_void_p ).value
dll.PrintInfo(pStr, len(pStr.value))
print pStr.value
 
# char* -- 3
strMa = "\0"*20
FunPrint = dll.PrintInfo
FunPrint.argtypes = [c_char_p, c_int]
#FunPrint.restypes = c_void_p
nRst = FunPrint(strMa, len(strMa))
print strMa,len(strMa)
 
# char* -- 4
pStr2 = c_char_p("\0")
print pStr2.value
#pVoid = ctypes.cast( pStr, ctypes.c_void_p ).value
dll.PrintInfo(pStr2, len(pStr.value))
print pStr2.value

3、C基本类型和ctypes中实现的类型映射表

ctypes数据类型 C数据类型

c_char              char
c_short             short
c_int                 int
c_long               long
c_ulong             unsign long
c_float               float
c_double            double
c_void_p             void

对应的指针类型是在后面加上"_p",如int*是c_int_p等等。
在python中要实现c语言中的结构,需要用到类。

4、DLL中的函数返回一个指针。

虽然这不是个好的编程方法,不过这种情况的处理方法也很简单,其实返回的都是地址,把他们转换相应的python类型,在通过value属性访问。
pchar = dll.getbuffer()
szbuffer = c_char_p(pchar)
print szbuffer.value

5、处理C中的结构体类型

为什么把这个单独提出来说呢,因为这个是最麻烦也是最复杂的,在python里面申明一个类似c的结构体,要用到类,并且这个类必须继承自Structure。
先看一个简单的例子:

C里面dll的定义如下:
01 typedef struct _SimpleStruct
02 {
03 int nNo;
04 float fVirus;
05 char szBuffer[512];
06 } SimpleStruct, *PSimpleStruct;
07 typedef const SimpleStruct* PCSimpleStruct;
08 extern "C"int __declspec(dllexport) PrintStruct(PSimpleStruct simp);
09 int PrintStruct(PSimpleStruct simp)
10 {
11 printf ("nMaxNum=%f, szContent=%s", simp->fVirus, simp->szBuffer);
12 return simp->nNo;
13 }
Python的定义:
from ctypes import *
class SimpStruct(Structure):
_fields_ = [ ("nNo", c_int),
("fVirus", c_float),
("szBuffer", c_char * 512)]
dll = CDLL("AddDll.dll")
simple = SimpStruct();
simple.nNo = 16
simple.fVirus = 3.1415926
simple.szBuffer = "magicTong\0"
print dll.PrintStruct(byref(simple))
上面例子结构体很简单,如果结构体里面有指针,甚至是指向结构体的指针,python里面也有相应的处理方法。

下面这个例子来自网上,本来想自己写个,懒得写了,能说明问题就行:

C代码如下:
typedef struct
{
char words[10];
}keywords;
typedef struct
{
keywords *kws;
unsigned int len;
}outStruct;
extern "C"int __declspec(dllexport) test(outStruct *o);
int test(outStruct *o)
{
unsigned int i = 4;
o->kws = (keywords *)malloc(sizeof(unsigned char) * 10 * i);
strcpy(o->kws[0].words, "The First Data");
strcpy(o->kws[1].words, "The Second Data");
o->len = i;
return 1;
}

Python代码如下:
class keywords(Structure):
_fields_ = [('words', c_char *10),]
class outStruct(Structure):
_fields_ = [('kws', POINTER(keywords)),
('len', c_int),]
o = outStruct()
dll.test(byref(o))
print o.kws[0].words;
print o.kws[1].words;
print o.len

6、例子

说得天花乱坠,嘿嘿,还是看两个实际的例子。

例子一:

这是一个GUID生成器,其实很多第三方的python库已经有封装好的库可以调用,不过这得装了那个库才行,如果想直接调用一些API,对于python来说,也要借助一个第三方库才行,这个例子比较简单,就是用C++调用win32 API来产生GUID,然后python通过调用c++写的dll来获得这个GUID

C++代码如下:
extern "C"__declspec(dllexport) char* newGUID();
char* newGUID()
{
static char buf[64] = {0};
statc GUID guid;
if (S_OK == ::CoCreateGuid(&guid))
{
// "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"
_snprintf(buf, sizeof(buf),
"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
guid.Data1,
guid.Data2,
guid.Data3,
guid.Data4[0], guid.Data4[1],
guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5],
guid.Data4[6], guid.Data4[7]
);
::MessageBox(NULL, buf, "GUID", MB_OK);
}
return (char*)buf;
}

Python代码如下:
def CreateGUID():
"""
创建一个全局唯一标识符
类似:E06093E2-699A-4BF2-A325-4F1EADB50E18
NewVersion
"""
try:
# dll path
    strDllPath = sys.path[0] + str(os.sep) + "createguid.dll"
    dll = CDLL(strDllPath)
    b = dll.newGUID()
    a = c_char_p(b)
except Exception, error:
    print error
return ""
return a.value

例子二:

这个例子是调用kernel32.dll中的createprocessA函数来启动一个记事本进程。
# -*- coding:utf-8 -*-
from ctypes import *
# 定义_PROCESS_INFORMATION结构体
class _PROCESS_INFORMATION(Structure):
_fields_ = [('hProcess', c_void_p),
('hThread', c_void_p),
('dwProcessId', c_ulong),
('dwThreadId', c_ulong)]
# 定义_STARTUPINFO结构体
class _STARTUPINFO(Structure):
_fields_ = [('cb',c_ulong),
('lpReserved', c_char_p),
('lpDesktop', c_char_p),
('lpTitle', c_char_p),
('dwX', c_ulong),
('dwY', c_ulong),
('dwXSize', c_ulong),
('dwYSize', c_ulong),
('dwXCountChars', c_ulong),
('dwYCountChars', c_ulong),
('dwFillAttribute', c_ulong),
('dwFlags', c_ulong),
('wShowWindow', c_ushort),
('cbReserved2', c_ushort),
('lpReserved2', c_char_p),
('hStdInput', c_ulong),
('hStdOutput', c_ulong),
('hStdError', c_ulong)]
 
NORMAL_PRIORITY_CLASS = 0x00000020 # 定义NORMAL_PRIORITY_CLASS
kernel32 = windll.LoadLibrary("kernel32.dll") # 加载kernel32.dll
CreateProcess = kernel32.CreateProcessA # 获得CreateProcess函数地址
ReadProcessMemory = kernel32.ReadProcessMemory # 获得ReadProcessMemory函数地址
WriteProcessMemory = kernel32.WriteProcessMemory # 获得WriteProcessMemory函数地址
TerminateProcess = kernel32.TerminateProcess
# 声明结构体
ProcessInfo = _PROCESS_INFORMATION()
StartupInfo = _STARTUPINFO()
fileName = 'c:/windows/notepad.exe' # 要进行修改的文件
address = 0x0040103c # 要修改的内存地址
strbuf = c_char_p("_") # 缓冲区地址
bytesRead = c_ulong(0) # 读入的字节数
bufferSize = len(strbuf.value) # 缓冲区大小
# 创建进程
CreateProcess(fileName, 0, 0, 0, 0, NORMAL_PRIORITY_CLASS,0, 0, byref(StartupInfo), byref(ProcessInfo))

转自:http://www.52sky.org/73004.html
<think>嗯,用户想了解如何在64位的Python环境中调用32位的DLL。这个问题我之前遇到过,可能需要一些步骤来解决。首先,得确认一下为什么会出现这个问题。因为64位的Python和32位的DLL不兼容,对吧?系统架构不同,导致无法直接加载。那解决办法可能有几种。 第一种方法是安装32位的Python,这样就能直接调用32位的DLL了。但用户可能已经用惯了64位的Python,或者有其他依赖必须用64位的环境,这样可能不太方便。所以可能需要其他方法。 第二种方法是使用中间件或者桥接工具。听说有个叫Python的ctypes库,可能不够用,需要第三方工具。比如,wine或者虚拟机,但这样会不会太麻烦?可能用户希望更直接的解决方案。 第三种方法是使用专门的桥接工具,比如Microsoft的Wow64,不过不确定是否支持这种跨架构调用。或者有没有什么第三方库,比如pywin32之类的?或者像CrossBridge这样的工具? 对了,记得之前听说过一个叫“进程间通信”的方法,可以创建一个32位的代理进程,通过进程间通信来调用DLL。这可能比较可行。比如,用Python写一个32位的服务端,然后64位的主程序通过socket或者管道与之通信。不过这样需要编写额外的代码,可能会增加复杂度。 另外,有没有可能用COM组件?如果DLL可以被封装成COM对象,也许可以绕过架构问题?不过这可能需要对DLL进行额外的配置和注册,用户可能没有权限或者觉得麻烦。 再查一下,有没有现成的库或者工具可以帮助解决这个问题。比如,使用ctypes的变通方法?或者使用一些模拟层,比如Wine的某些功能? 或者,使用虚拟机或容器技术,在32位环境中运行Python脚本,但这可能不太方便,特别是需要频繁调用的情况下。 总结一下,可能的解决方案有: 1. 安装32位Python,这是最直接的方法,但需要切换环境。 2. 使用进程间通信,编写32位代理程序,处理DLL调用,然后与64位主程序通信。 3. 使用第三方桥接工具,如一些专门处理跨架构调用的库,可能需要查找是否有现有的解决方案。 需要详细说明每种方法的步骤,优缺点,让用户根据自己的情况选择。 比如,方法一的步骤是下载32位Python,安装,然后使用ctypes调用DLL。优点是简单,缺点是需要维护两个Python环境。 方法二的步骤是创建32位的代理程序,可以用C或Python编写,然后通过socket、管道或RPC进行通信。优点是保持64位环境,缺点是增加开发复杂度,可能有性能损耗。 方法三可能需要具体工具,比如使用crossarch之类的工具,但不确定具体有哪些工具支持,需要进一步调研。 另外,还要提醒用户注意DLL的依赖项是否都是32位的,比如依赖的其他DLL文件也要是32位,否则即使解决了Python的问题,DLL本身可能无法加载。 最后,可能还要建议用户如果可能的话,获取64位版本的DLL,这是最理想的解决方案,避免兼容性问题。如果无法获取,再考虑上述替代方法。</think>在64位Python环境中调用32位DLL需要解决架构不兼容问题。以下是逐步解决方案: --- ### 方法1:安装32位Python环境(最直接) **步骤:** 1. 卸载当前64位Python 2. 从[Python官网](https://www.python.org/)下载32位版本并安装 3. 使用`ctypes`库加载DLL: ```python from ctypes import WinDLL dll = WinDLL("path/to/32bit.dll") # 确保路径正确 ``` **优点**:简单、无需额外工具 **缺点**:需维护独立的32位Python环境 --- ### 方法2:使用进程间通信(IPC) **步骤:** 1. **编写32位代理程序**(使用32位Python): ```python # proxy_32bit.py(需用32位Python运行) from ctypes import WinDLL import socket dll = WinDLL("path/to/32bit.dll") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', 12345)) s.listen() while True: conn, addr = s.accept() # 解析指令并调用DLL函数,返回结果 ``` 2. **64位主程序通过Socket通信**: ```python import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('localhost', 12345)) # 发送指令并接收结果 ``` **优点**:保持主程序为64位 **缺点**:开发复杂度高,需处理通信协议 --- ### 方法3:使用第三方桥接工具 **推荐工具**: - **Windows的Wow64技术**(需C/C++开发) 通过编写C++代码实现32/64位互操作,编译为中间层DLL。 - **PyWin32 + COM组件** 将32位DLL封装为COM对象(需注册DLL): ```python import win32com.client com_obj = win32com.client.Dispatch("Your.COM.Component") ``` **优点**:性能较好 **缺点**:需额外开发或配置 --- ### 关键注意事项 1. **依赖项检查** 确保目标DLL及其依赖的所有文件均为32位版本。 2. **错误处理** 使用`ctypes`时捕获异常: ```python try: dll = WinDLL("32bit.dll") except OSError as e: print("架构不兼容:", e) ``` 3. **终极建议** **优先获取64位版本DLL**,可彻底避免兼容性问题。 --- ### 总结 | 方法 | 复杂度 | 性能 | 适用场景 | |--------------|--------|------|------------------------| | 32位Python | 低 | 高 | 简单调用、无复杂依赖 | | 进程间通信 | 高 | 中 | 长期维护项目 | | 第三方工具 | 中 | 高 | 需高性能或企业级应用 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值