使用 ctypes 包装 DLL 函数

Posted

技术标签:

【中文标题】使用 ctypes 包装 DLL 函数【英文标题】:Wrapping DLL functions using ctypes 【发布时间】:2018-04-27 02:26:19 【问题描述】:

我现在将一些函数包装在 C++ dll 中,这些函数的原始 .h 和 .cpp 文件不可访问。在学习了ctypes的教程之后,我还有一些疑问。让我给你看一个例子: 这是来自dll函数的描述文件,只是告诉函数的名称,效果和参数:

#initialize network:
BOOL InitNetwork(char LocalIP[],char ServerIP[],int LocalDeviceID)

#judging if the server is online after network initialization:
BOOL GetOnlineStatus()

#get song name:
char* GetMusicSongName(int DirIndex,int SongIndex)

#making the device playing music:
int PlayServerMusic(int DirIndex,int MusicIndex,int DeviceCout,PUINT lpDeviceID,int UseListIndex,int UserMusicIndex,UINT TaskCode)

为了将这些函数包装在 Python 中,我想做的是:

import ctypes
import os

#load dll
cppdll = ctypes.WinDLL("C:\\VS_projects\\MusicWebService\\MusicWebService\\NetServerInterface.dll")

#wrap dll functions:

#initialize network:
def InitNetwork(LocalIP, ServerIP, LocalDeviceID):
    return cppdll.InitNetwork(LocalIP, ServerIP, LocalDeviceID)
InitNetwork.argtypes = [c_char, c_char, c_int]

#judging if the server is online after network initialization:
def GetOnlineStatus():
    return cppdll.GetOnlineStatus()

#get song name:
def GetMusicSongName(DirIndex, SongIndex):
    return cppdll.GetMusicSongName(DirIndex, SongIndex)
GetMusicSongName.argtypes = [c_int, c_int]

#making the device playing music:
def PlayServerMusic(DirIndex, MusicIndex, DeviceCout, lpDeviceID, UseListIndex, UserMusicIndex, TaskCode):
    return cppdll.PlayServerMusic(DirIndex, MusicIndex, DeviceCout, lpDeviceID, UseListIndex, UserMusicIndex, TaskCode)

当我在 VS2015 python 交互窗口中键入并定义第一个函数(在导入包和加载 dll 之后)时,它给了我一个错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'c_char' is not defined

我需要指定输入参数的类型吗?如果是这样,该怎么做?我无法识别第四个函数中某些参数的类型,例如PUINT lpDeviceIDUINT TaskCode。如何指定它们? 此外,我是否需要指定函数的返回值类型?如果是这样,如何指定它们? 有人可以给我上面示例的正确包装代码吗?感谢您的关注!

【问题讨论】:

InitNetwork.argtypes = [c_char, c_char, c_int] 看起来很可疑,因为原始的 c 函数接受的是指针,而不是字符。 可能您需要 c_void_p 代替? @Aconcagua 是的,你是对的。 【参考方案1】:

您的问题是您没有完全正确的命名空间。它的

cppdll.InitNetwork.argtypes = [ctypes.c_char, ctypes.c_char, ctypes.c_int]

您可以将您需要的 ctypes 数据直接导入到模块中。由于ctypes 为您创建了函数包装器,因此您实际上并不需要您自己的def 来做同样的事情。您可以使用 cppdll.Whatever 调用它们,但如果您喜欢在命名空间级别使用这些函数,您可以为它们创建变量。

from ctypes import WinDLL, c_char, c_int
import os

#load dll
cppdll = ctypes.WinDLL("C:\\VS_projects\\MusicWebService\\MusicWebService\\NetServerInterface.dll")

#initialize network:
InitNetwork = cppdll.InitNetwork
InitNetwork.argtypes = [c_char, c_char, c_int]

#judging if the server is online after network initialization:
GetOnlineStatus = cppdll.GetOnlineStatus

#get song name:
GetMusicSongName = cppdll.GetMusicSongName
GetMusicSongName.argtypes = [c_int, c_int]

#making the device playing music:
PlayServerMusic = cppdll.PlayServerMusic
PlayServerMusic = ...

【讨论】:

感谢您的友好回答。我需要指定函数的返回值类型吗?例如,如果像GetMusicSongName 这样的函数返回一个指针,Python 会自动返回一个指针吗?再次感谢您的建议。 ctypes 假定返回一个整数。如果是其他类型,则将该类型分配给.restype。详情在这里:docs.python.org/3/library/ctypes.html#return-types【参考方案2】:

这里有一整套应该有效的定义。请注意如何通过创建类型的实例并通过引用传递它来传递输出参数。 ctypes.wintypes 中也定义了许多常见的 Windows 类型。

注意 InitNetwork 的第一个参数类型是 C 中的 char*,因此您需要 c_char_p 而不是 c_char,并且您可以直接传递 Python 字节字符串,因为 C 代码不写入指针。如果需要,ctypes.create_string_buffer() 可用于生成可写字符数组。

from ctypes import *
from ctypes import wintypes as w

dll = WinDLL('path/to/dll')

# BOOL InitNetwork(char LocalIP[],char ServerIP[],int LocalDeviceID)
dll.InitNetwork.argtypes = c_char_p,c_char_p,c_int
dll.InitNetwork.restype = w.BOOL

# BOOL GetOnlineStatus()
dll.GetOnlineStatus.argtypes = None
dll.GetOnlineStatus.restype = w.BOOL

# char* GetMusicSongName(int DirIndex,int SongIndex)
dll.GetMusicSongName.argtypes = c_int,c_int
dll.GetMusicSongName.restype = c_char_p

# int PlayServerMusic(int DirIndex,int MusicIndex,int DeviceCout,PUINT lpDeviceID,int UseListIndex,int UserMusicIndex,UINT TaskCode)
dll.PlayServerMusic.argtypes = c_int,c_int,w.PUINT,c_int,c_int,w.UINT
dll.PlayServerMusic.restype = c_int

dll.InitNetwork(b'1.1.1.1',b'2.2.2.2',7)
status = dll.GetOnlineStatus()
song = dll.GetMusicSongName(1,2)
DeviceCout = w.UINT()
result = dll.PlayServerMusic(1,2,3,byref(DeviceCout),4,5,6)

【讨论】:

非常感谢您的指出。我必须使用.restype 来指定返回值的类型吗? @YQ.Wang 默认是c_int,所以其他的都是可以的。 BOOL 被定义为 c_int,所以在这种情况下没有必要,但最好是具体的。

以上是关于使用 ctypes 包装 DLL 函数的主要内容,如果未能解决你的问题,请参考以下文章

使用ctypes python包装C函数返回未知大小的数组

如何调试 c++ dll 的 ctypes 调用?

Python ctypes:从相对路径加载 DLL

将图像从 python 包装器传递给 c++ 函数

从 Python (ctypes) 指向 C 以保存函数输出的指针

如何使用 ctypes 在 python 中正确包装 C API?