C++编译成lib静态库
静态库(Static Library)是一种在编译时直接嵌入到程序中的库。
动态库(Dynamic Library)是一种在程序运行时被加载的库。
动态库与静态库的区别
注意:python无法直接调用静态库,因为静态库(.lib)在编译阶段将代码直接嵌入到宿主程序中,而 Python 作为解释型语言,无法在运行时动态链接静态库。需要将静态库封装成动态库后,python才可以调用
在 Visual Studio 中生成静态库(.lib)的完整步骤
步骤 1:创建静态库项目
- 打开 Visual Studio → Create a new project → 搜索 静态库(Lib) → 命名为
GeometryStaticLib→ 创建。 - 删除默认生成的
pch.h,framework.h,dllmain.cpp等文件,右键项目 → Add → New Item → 创建Geometry.cpp和Geometry.h。(注意:生成静态库,需要头文件。而动态库如果是python调用则不需要,如果是c++调用动态库也是需要头文件)
步骤 2:编写代码
Geometry.h(头文件):
#pragma once
// 声明函数为 extern "C" 以避免名称修饰(Name Mangling)
#ifdef __cplusplus
extern "C" {
#endif
// 基础函数
float distance_sq_segment(
const float* point,
const float* v1,
const float* v2
);
// 主功能函数
bool triangleInsideSphere(
const float* point,
float radiusSq,
const float* v1,
const float* v2,
const float* v3
);
bool pointInsideTriangle(
const float* point,
const float* v1,
const float* v2,
const float* v3
);
#ifdef __cplusplus
}
#endif
Geometry.cpp(与之前的 DLL 代码相同,无需 __declspec(dllexport)):
#include "Geometry.h"
#include <cmath>
// --------------------- 基础函数 ---------------------
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];
}
}
// --------------------- 主功能函数 ---------------------
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);
}
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);
}
步骤 3:配置项目属性
- 选择目标平台:
• 顶部工具栏选择 Release 和 x64(与 Python 位数一致)。 - 设置静态库编译选项:
• 右键项目 → 属性(Properties) → 配置属性(Configuration Properties) → 常规(General) → 配置类型(Configuration Type) 选择 Static Library (.lib)。 - 调整运行时库:
• C/C++ → 代码生成(Code Generation) → 运行库(Runtime Library) 选择 /MT(静态链接运行时库)。 - 禁用预编译头:
• C/C++ → 预编译头(Precompiled Headers) → 预编译头(Precompiled Header) 选择 不使用预编译头(Not Using Precompiled Headers)。
步骤 4:编译生成静态库
- 右键项目 → Build,成功后在
x64/Release/目录下生成GeometryStaticLib.lib。 - 重要区别:
• 静态库.lib文件比动态库的.lib(导入库)大,因为它包含所有代码。
步骤 5:测试静态库
- 创建测试项目:
• 新建 Console Application 项目 → 命名为TestStaticLib。 - 配置静态库链接:
• 右键测试项目 → Properties → Linker → Input → Additional Dependencies → 添加GeometryStaticLib.lib的完整路径。
• C/C++ → General → Additional Include Directories → 添加Geometry.h所在目录。 - 测试代码:
#include <iostream>
#include "Geometry.h"
int main() {
float point[3] = {0.5f, 0.5f, 0.0f};
float v1[3] = {1.0f, 0.0f, 0.0f};
float v2[3] = {0.0f, 1.0f, 0.0f};
float v3[3] = {0.0f, 0.0f, 1.0f};
float radiusSq = 0.5f;
bool in_sphere = triangleInsideSphere(point, radiusSq, v1, v2, v3);
bool in_triangle = pointInsideTriangle(point, v1, v2, v3);
std::cout << "In sphere: " << in_sphere << ", In triangle: " << in_triangle << std::endl;
return 0;
}
- 编译运行,应输出
In sphere: 1, In triangle: 1。
关键区别:静态库 vs 动态库
| 特性 | 静态库(.lib) | 动态库(.dll + 导入库 .lib) |
|---|---|---|
| 代码嵌入方式 | 链接时完全嵌入主程序 | 运行时动态加载 |
| 文件部署 | 无需额外文件 | 需分发 .dll |
| 体积 | 主程序体积较大 | 主程序体积较小 |
| 内存占用 | 无法共享代码 | 多个程序可共享同一 .dll |
| 更新维护 | 需重新编译主程序 | 只需替换 .dll |
总结
• 静态库适合需要代码封闭性或避免依赖外部文件的场景。
• 通过上述步骤,你可以将现有代码无缝转换为静态库,并在其他 C++ 项目中直接链接使用。
• Python 无法直接调用静态库(.lib)文件,因为静态库需要在编译时链接到宿主程序(如另一个 DLL 或 EXE)。若想通过 Python 调用静态库中的函数,必须将静态库封装成动态库(.dll),再通过 ctypes 调用。
将静态库封装为动态库
步骤 1:创建动态库项目
- 在 Visual Studio 中新建 Dynamic-Link Library (DLL) 项目。
- 添加封装代码,调用静态库中的函数:
// Wrapper.cpp
#include "StaticLib.h" // 静态库的头文件
extern "C" __declspec(dllexport) bool triangleInsideSphere(
const float* point, float radiusSq, const float* v1, const float* v2, const float* v3
) {
return ::triangleInsideSphere(point, radiusSq, v1, v2, v3); // 调用静态库函数
}
extern "C" __declspec(dllexport) bool pointInsideTriangle(
const float* point, const float* v1, const float* v2, const float* v3
) {
return ::pointInsideTriangle(point, v1, v2, v3);
}
步骤 2:链接静态库
- 项目属性 → Linker → Input → Additional Dependencies → 添加
YourStaticLib.lib。 - 项目属性 → C/C++ → General → Additional Include Directories → 添加静态库头文件路径。
步骤 3:生成动态库
- 编译生成
Wrapper.dll,该 DLL 会嵌入静态库代码。
Python 调用动态库
import ctypes
import numpy as np
# 加载动态库
lib = ctypes.CDLL('./Wrapper.dll')
# 配置函数参数类型 (示例)
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
# 调用函数
point = np.array([0.5, 0.5, 0.0], dtype=np.float32)
v1 = np.array([1.0, 0.0, 0.0], dtype=np.float32)
v2 = np.array([0.0, 1.0, 0.0], dtype=np.float32)
v3 = np.array([0.0, 0.0, 1.0], dtype=np.float32)
radius_sq = 0.5
result = lib.triangleInsideSphere(point, radius_sq, v1, v2, v3)
print(result) # 输出: True
性能与开发成本对比
| 方法 | 性能 | 开发复杂度 | 适用场景 |
|---|---|---|---|
| 静态库 + 封装 DLL | 高 | 中 | 需复用现有静态库代码 |
| 直接编写动态库 | 高 | 低 | 新项目开发 |
| Numba JIT | 中 | 低 | 快速迭代 |
总结
• 直接调用静态库不可行:Python 无法绕过动态库机制。
• 最佳实践:将静态库封装为动态库后,通过 ctypes 调用,既保留静态库的代码复用性,又兼容 Python 的灵活性。