用__declspec(dllexport)在头文件中导出函数并加extern "C"防止名字修饰,静态链接需配置.lib和头文件路径,动态加载用LoadLibrary+GetProcAddress;跨语言调用须统一调用约定(如__cdecl)和数据类型,用dumpbin /exports和Process Monitor排查符号与路径问题。

怎么用 Visual Studio 创建一个能被其他程序调用的 C++ DLL
关键不是“能不能编译通过”,而是导出符号是否真正可见、调用方能否解析。默认新建的 DLL 项目不导出任何函数,直接调用会报 LNK2019: unresolved external symbol。
- 在头文件里用
__declspec(dllexport)标记要导出的函数,比如:extern "C" __declspec(dllexport) int add(int a, int b) { return a + b; }加extern "C"是为了防止 C++ 名字修饰(name mangling),否则 C# 或 Python 调用时找不到符号 - 不要依赖项目属性里的“导出符号文件(.def)”自动生成——它只处理全局函数,对类成员、模板、重载函数基本无效;手动写 .def 文件又容易漏项,优先用
__declspec(dllexport) - 生成时注意平台匹配:DLL 是
x64,调用方也必须是x64;混用Win32和x64会导致ERROR_BAD_EXE_FORMAT
怎么在另一个 C++ 程序里安全加载并调用这个 DLL
静态链接(.lib + #include 头文件)最方便,但要求编译时就有 DLL 的头和 .lib;运行时动态加载(LoadLibrary + GetProcAddress)更灵活,适合插件机制或规避启动时依赖。
- 静态调用:把 DLL 对应的
.lib加到调用方的“附加依赖项”,头文件路径加到“附加包含目录”,然后直接调用函数名——但要注意 DLL 必须在运行时能找到(PATH 环境变量、exe 同目录、或SetDllDirectory指定) - 动态调用:用
LoadLibrary(L"mylib.dll")返回HMODULE,再用GetProcAddress(hmod, "add")取函数指针;必须用typedef int (*add_func)(int, int)强制转换,否则调用会崩溃 - 常见错误:
GetProcAddress返回NULL,通常是因为函数名不匹配(C++ 修饰后名字变了)、DLL 没加载成功、或函数没加__declspec(dllexport)
为什么 C# 或 Python 调用 C++ DLL 总是失败
不是语言不兼容,是调用约定和内存模型没对齐。C++ 默认用 __cdecl,而 C# [DllImport] 默认按 StdCall 查找,名字根本对不上。
- C++ 导出函数显式声明调用约定:
extern "C" __declspec(dllexport) int __cdecl add(int a, int b)
或者用__stdcall,但必须和 C# 端CallingConvention = CallingConvention.Cdecl一致 - C# 中
[DllImport("mylib.dll", CallingConvention = CallingConvention.Cdecl)]必须写全,漏掉CallingConvention就是静默失败 - Python 用
ctypes.CDLL("mylib.dll")加载后,要设restype和argtypes,比如:lib.add.argtypes = [ctypes.c_int, ctypes.c_int]; lib.add.restype = ctypes.c_int
不设就可能返回垃圾值
调试时 DLL 找不到、函数地址为空、或一调用就崩
绝大多数问题出在模块加载路径、符号可见性、或数据类型跨语言失真,而不是代码逻辑本身。
立即学习“C++免费学习笔记(深入)”;
- 用
Process Monitor过滤进程名 + “PATH”和“.dll”,看系统到底去哪找了哪些路径,确认 DLL 是否真被加载 - 用
DumpBin /exports mylib.dll检查导出表,确认函数名是否如你所想(extern "C"后应该是纯名字,没有?add@@YAHHH@Z这种修饰) - 结构体传参最容易出错:C++ 里
std::string、std::vector不能跨 DLL 边界传递;必须用 C 风格的const char*、int*、struct,且双方结构体字段顺序、对齐(#pragma pack(1))、大小必须完全一致
真正麻烦的从来不是“怎么写完”,而是“怎么让两边对上眼”。名字、调用约定、内存布局、加载路径——四个地方只要一个没咬合,就卡住。动手前先用 DumpBin 和 Process Monitor 看一眼实际发生了什么,比改十遍代码更快。











