通过 ctypes 调用 C++的 DLL 函数。
一、C++ 头文件
C++ 头文件(HelloWorld.h)
#ifndef HELLO_WORLD_H
#define HELLO_WORLD_H
// 导出 HelloWorld 函数
extern "C" __declspec(dllexport) const char* HelloWorld(const char* str1, const char* str2);
// 导出 Add 函数
extern "C" __declspec(dllexport) int Add(int a, int b);
#endif // HELLO_WORLD_H#pragma once
一定要使用 extern "C",否则函数名会被 C++ 编译器“名称修饰”(mangling),Python 无法识别。
实现文件:
// HelloWorld.cpp
#include "pch.h" // 如果 VS 生成了预编译头文件
#include "HelloWorld.h" // 引入头文件
#include <iostream>
#include <string>
extern "C" __declspec(dllexport) const char* HelloWorld(const char* str1, const char* str2) {
static std::string result; // 使用静态变量存储返回值,确保返回的指针有效
result = std::string(str1) + "," + std::string(str2);
return result.c_str(); // 返回拼接后的 C 字符串
}
// 一个简单的加法函数
extern "C" __declspec(dllexport) int Add(int a, int b) {
return a + b;
}
二、Python 使用 ctypes 的封装类
Python 封装代码:
- 单例模式,避免重复加载
- 参数类型校验、异常封装
- 编码转换(str -> char*)
- 错误处理
import ctypes
import os
import threading
class DllLoadError(Exception):
"""自定义异常:DLL 加载失败"""
pass
class DllCallError(Exception):
"""自定义异常:DLL 调用失败"""
pass
class HelloWorldDllWrapper:
_lock = threading.Lock()
_instance = None
def __new__(cls, dll_path=None):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._init_dll(dll_path)
return cls._instance
def _init_dll(self, dll_path):
if dll_path is None:
dll_path = "E:\\vsproject\\HelloWorld\\x64\\Release\\HelloWorld.dll"
if not os.path.exists(dll_path):
raise DllLoadError(f"DLL 文件不存在:{dll_path}")
try:
self._dll = ctypes.CDLL(dll_path)
# 函数声明
self._dll.HelloWorld.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
self._dll.HelloWorld.restype = ctypes.c_char_p
self._dll.Add.argtypes = [ctypes.c_int, ctypes.c_int]
self._dll.Add.restype = ctypes.c_int
except Exception as e:
raise DllLoadError(f"DLL 加载失败:{e}")
def say_hello(self, str1: str, str2: str) -> str:
try:
b1 = str1.encode("utf-8")
b2 = str2.encode("utf-8")
result_ptr = self._dll.HelloWorld(b1, b2)
if not result_ptr:
raise DllCallError("HelloWorld 返回空指针")
return result_ptr.decode("utf-8")
except Exception as e:
raise DllCallError(f"调用 say_hello 失败:{e}")
def add(self, a: int, b: int) -> int:
try:
return self._dll.Add(a, b)
except Exception as e:
raise DllCallError(f"调用 add 失败:{e}")
三、使用示例
if __name__ == "__main__":
try:
dll = HelloWorldDllWrapper()
result1 = dll.say_hello("Hello", "World")
print("HelloWorld 返回结果:", result1)
result2 = dll.add(52, 23)
print("Add 返回结果:", result2)
except (DllLoadError, DllCallError) as e:
print("错误:", e)
输出:
HelloWorld 返回结果: Hello,World
Add 返回结果: 75
四、适用场景
- 已有 C++ 动态库,快速与 Python 集成。
- 跨语言调用需求,如硬件接口、图像处理库(OpenCV/FFmpeg)等。
- DLL 接口简单稳定,不含复杂结构体或模板。
- 性能要求中等偏低,函数调用频率不是极高。
五、注意事项
1. 位数必须一致(32 位 vs 64 位)
- Python 是 64 位,DLL 也必须是 64 位(或都为 32 位)。
确认 Python 位数:
python -c "import platform; print(platform.architecture())"
2. 字符串需要编码/解码
- Python
str需.encode('utf-8')→ C 的char*。 - 返回的
char*需.decode('utf-8')转为 Python 字符串。
3. DLL 中返回的字符串应是 静态内存
- 不能返回局部变量地址(会悬空)。
- 推荐使用
static std::string或手动分配内存。
4. 函数签名必须正确
argtypes和restype是类型安全保障。- 错误签名可能导致内存崩溃或类型错误。
5. 需要有异常处理
- 调用 DLL 接口时建议封装异常,方便排查。
ctypes 是 Python 调用 C/C++ 动态库最直接有效的方式之一,适合已有C/C++动态库、快速集成的场景。
4559

被折叠的 条评论
为什么被折叠?



