C++编译成dll动态链接库
C++代码编译成dll后,可以提供给python调用,提高效率尤其是关键算法代码。当然除了使用c++编译成dll之外,还可以使用numba的AOT编译,编译成pyd文件,然后pyhon调用,效率上仅稍慢
静态库(Static Library)是一种在编译时直接嵌入到程序中的库。
动态库(Dynamic Library)是一种在程序运行时被加载的库。
动态库与静态库的区别
在无 C++ 环境的 Windows 系统上创建和调用 C++ DLL 的完整步骤
步骤 1:安装轻量级 C++ 编译器(MinGW)
无需安装 Visual Studio,直接下载便携版 MinGW:
- 访问 MinGW-w64 官方构建,选择 Standalone Builds → Win64 下的 GCC 13.2.0 + LLVM/Clang/LLD/LLDB 17.0.6 + MinGW-w64 11.0.1 (UCRT) - release 3。
- 下载后解压到任意目录(如
C:\mingw64)。 - 添加环境变量:
• 右键点击“此电脑” → 属性 → 高级系统设置 → 环境变量 → 系统变量中的Path→ 编辑 → 新建 → 输入C:\mingw64\bin。
验证安装:
g++ --version # 应输出 GCC 版本信息
步骤 2:编写 C++ 代码
创建文件 distance.cpp:
#include <cmath>
extern "C" __declspec(dllexport) double distance_sq_segment(
const double* point,
const double* v1,
const double* v2
) {
double v2v1[3] = {v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]};
double len_sq = v2v1[0]*v2v1[0] + v2v1[1]*v2v1[1] + v2v1[2]*v2v1[2];
if (len_sq < 1e-20) {
double dx = point[0] - v1[0], dy = point[1] - v1[1], dz = point[2] - v1[2];
return dx*dx + dy*dy + dz*dz;
}
double pt[3] = {point[0] - v1[0], point[1] - v1[1], point[2] - v1[2]};
double t = (pt[0]*v2v1[0] + pt[1]*v2v1[1] + pt[2]*v2v1[2]) / len_sq;
if (t < 0.0) {
return pt[0]*pt[0] + pt[1]*pt[1] + pt[2]*pt[2];
} else if (t > 1.0) {
double dx = point[0] - v2[0], dy = point[1] - v2[1], dz = point[2] - v2[2];
return dx*dx + dy*dy + dz*dz;
} else {
double proj[3] = {pt[0] - t*v2v1[0], pt[1] - t*v2v1[1], pt[2] - t*v2v1[2]};
return proj[0]*proj[0] + proj[1]*proj[1] + proj[2]*proj[2];
}
}
步骤 3:编译为 DLL
打开命令提示符,进入代码目录,执行:
g++ -shared -O3 -o distance.dll distance.cpp
• -shared:生成动态链接库。
• -O3:开启最高优化级别。
• -o distance.dll:输出文件名。
步骤 4:Python 调用 DLL
创建 test.py:
import ctypes
import numpy as np
# 加载 DLL
lib = ctypes.CDLL('./distance.dll')
# 定义函数参数和返回类型
lib.distance_sq_segment.argtypes = [
np.ctypeslib.ndpointer(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS'),
np.ctypeslib.ndpointer(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS'),
np.ctypeslib.ndpointer(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS')
]
lib.distance_sq_segment.restype = ctypes.c_double
# 测试数据(必须转为 float64 且内存连续)
point = np.array([0.0, 0.0, 0.0], dtype=np.float64)
v1 = np.array([1.0, 0.0, 0.0], dtype=np.float64)
v2 = np.array([0.0, 1.0, 0.0], dtype=np.float64)
# 调用并打印结果
distance = lib.distance_sq_segment(point, v1, v2)
print(f"Minimum squared distance: {distance}")
关键验证点
- DLL 生成检查:确保编译后目录中有
distance.dll文件。 - 数据类型严格匹配:所有 NumPy 数组必须为
np.float64且内存连续。 - 路径正确性:Python 文件与 DLL 需在同一目录,或指定绝对路径。
常见问题解决
1. 报错 ImportError: DLL load failed
• 原因:DLL 依赖的运行时库缺失。
• 解决:将 MinGW 的 bin 目录(如 C:\mingw64\bin)加入系统 Path 环境变量。
2. 计算结果错误
• 原因:输入数据未转为 float64 或内存不连续。
• 解决:强制转换:
def ensure_input(arr):
return np.ascontiguousarray(arr.astype(np.float64))
性能对比
| 方案 | 耗时(1,000,000 次调用) | 开发复杂度 |
|---|---|---|
| C++ DLL | ~15 ms | 中 |
| Numba JIT | ~20 ms | 低 |
| 纯 Python | ~3000 ms | 低 |
通过此方案,即使没有预装 C++ 环境,也能快速实现高性能计算模块,且无需复杂配置。
在 Visual Studio 中编译 C++ 代码为 DLL 的完整步骤
以下是从零开始使用 Visual Studio 2019/2022 创建和编译 C++ DLL 的详细流程:
步骤 1:安装 Visual Studio
- 下载 Visual Studio Community 版
- 安装时勾选:
• Desktop development with C++(必需)
步骤 2:创建 DLL 项目
- 打开 Visual Studio → Create a new project → 搜索
动态链接库(DLL)→ 命名为GeometryDLL→ 创建。 - 删除自动生成的示例文件 (
头文件和源文件中的dllmain.cpp,pch.h,framework.h等),右键项目 → Add → New Item → 创建Geometry.cpp。
步骤 3:编写 C++ 代码
将以下代码粘贴到 Geometry.cpp 中:
#include <cmath>
#define EXPORT __declspec(dllexport) // 显式导出符号
extern "C" {
// --------------------- 基础函数 ---------------------
EXPORT float distance_sq_segment(
const float* point,
const float* v1,
const float* v2
) {
float v2v1[3] = { v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2] };
float len_sq = v2v1[0] * v2v1[0] + v2v1[1] * v2v1[1] + v2v1[2] * v2v1[2];
if (len_sq < 1e-20f) {
float dx = point[0] - v1[0], dy = point[1] - v1[1], dz = point[2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
float pt[3] = { point[0] - v1[0], point[1] - v1[1], point[2] - v1[2] };
float t = (pt[0] * v2v1[0] + pt[1] * v2v1[1] + pt[2] * v2v1[2]) / len_sq;
if (t < 0.0f) {
return pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2];
}
else if (t > 1.0f) {
float dx = point[0] - v2[0], dy = point[1] - v2[1], dz = point[2] - v2[2];
return dx * dx + dy * dy + dz * dz;
}
else {
float proj[3] = { pt[0] - t * v2v1[0], pt[1] - t * v2v1[1], pt[2] - t * v2v1[2] };
return proj[0] * proj[0] + proj[1] * proj[1] + proj[2] * proj[2];
}
}
// --------------------- 主功能函数 ---------------------
EXPORT bool triangleInsideSphere(
const float* point,
float radiusSq,
const float* v1,
const float* v2,
const float* v3
) {
float d1 = distance_sq_segment(point, v1, v2);
float d2 = distance_sq_segment(point, v2, v3);
float d3 = distance_sq_segment(point, v1, v3);
return (d1 < radiusSq) || (d2 < radiusSq) || (d3 < radiusSq);
}
EXPORT bool pointInsideTriangle(
const float* point,
const float* v1,
const float* v2,
const float* v3
) {
const float eps = 1e-10f;
const float* A = v1;
const float* B = v2;
const float* C = v3;
const float* P = point;
// 计算向量
float vec0[3] = { C[0] - A[0], C[1] - A[1], C[2] - A[2] };
float vec1[3] = { B[0] - A[0], B[1] - A[1], B[2] - A[2] };
float vec2[3] = { P[0] - A[0], P[1] - A[1], P[2] - A[2] };
// 计算点积
float dot00 = vec0[0] * vec0[0] + vec0[1] * vec0[1] + vec0[2] * vec0[2];
float dot01 = vec0[0] * vec1[0] + vec0[1] * vec1[1] + vec0[2] * vec1[2];
float dot02 = vec0[0] * vec2[0] + vec0[1] * vec2[1] + vec0[2] * vec2[2];
float dot11 = vec1[0] * vec1[0] + vec1[1] * vec1[1] + vec1[2] * vec1[2];
float dot12 = vec1[0] * vec2[0] + vec1[1] * vec2[1] + vec1[2] * vec2[2];
// 计算重心坐标
float denom = dot00 * dot11 - dot01 * dot01;
if (std::fabs(denom) < eps) return false;
float inv_denom = 1.0f / denom;
float u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
float v_val = (dot00 * dot12 - dot01 * dot02) * inv_denom;
return (u >= -eps) && (v_val >= -eps) && (u + v_val <= 1.0f + eps);
}
}
步骤 4:配置项目属性
- 修改目标平台:
• 顶部工具栏选择 Release 和 x64(必须与 Python 位数一致)。 - 调整编译选项:
• 右键项目 → 属性(Properties) → 配置属性(Configuration Properties) → C/C++ → 代码生成(Code Generation) → 运行库(Runtime Library) 选择/MD(确保与 Python 运行时兼容)。 - 禁用预编译头:
• C/C++ → 预编译头(Precompiled Headers) → 预编译头(Precompiled Header) 选择 不使用预编译头(Not Using Precompiled Headers)。
步骤 5:编译生成 DLL
- 右键项目 → Build,成功后在
x64/Release/目录下生成GeometryDLL.dll(名字和项目名一致)。 - 将 DLL 文件复制到 Python 项目目录。
步骤 6:Python 调用测试
import ctypes
import numpy as np
# 加载 DLL
lib = ctypes.CDLL('./GeometryDLL.dll')
# --------------------- 配置函数参数类型 ---------------------
# triangleInsideSphere
lib.triangleInsideSphere.argtypes = [
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C'),
ctypes.c_float,
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C'),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C'),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C')
]
lib.triangleInsideSphere.restype = ctypes.c_bool
# pointInsideTriangle
lib.pointInsideTriangle.argtypes = [
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C'),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C'),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C'),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C')
]
lib.pointInsideTriangle.restype = ctypes.c_bool
# --------------------- 测试数据 ---------------------
def ensure_float32(arr):
return np.ascontiguousarray(arr.astype(np.float32))
point = ensure_float32([0.5, 0.5, 0.0])
v1 = ensure_float32([1.0, 0.0, 0.0])
v2 = ensure_float32([0.0, 1.0, 0.0])
v3 = ensure_float32([0.0, 0.0, 1.0])
radius_sq = 0.5
# --------------------- 调用函数 ---------------------
in_sphere = lib.triangleInsideSphere(point, radius_sq, v1, v2, v3)
in_triangle = lib.pointInsideTriangle(point, v1, v2, v3)
print(f"In sphere: {in_sphere}, In triangle: {in_triangle}") # 应输出 True, True
常见问题解决
1. 报错 OSError: [WinError 126]
• 原因:DLL 依赖的运行时库(如 MSVCP140.dll)缺失。
• 解决:安装 Visual C++ Redistributable。
2. 函数返回错误结果
• 检查项:
• 确保所有数组输入为 np.float32 且内存连续。
• 验证 C++ 和 Python 的浮点计算精度是否一致。
3. 编译时提示 无法解析的外部符号
• 原因:未正确定义 EXPORT 宏。
• 解决:在函数声明前添加 extern "C" EXPORT。
总结
通过 Visual Studio 编译的 DLL 性能与 MinGW 相当,但更适合需要深度集成 Windows 生态的场景(如调用 DirectX API)。关键步骤为:配置项目属性 → 正确导出符号 → Python 端严格匹配数据类型。