简介这份资源是一份图文并茂的 Visual Studio 2019 DLL 动态库连接实例教程适合刚接触动态链接库开发、希望在 VS2019 中完成从生成到调用全过程的 C/C 学习者。内容按步骤演示了如何新建 mydll 动态库项目在 pch.h 中声明 extern C _declspec(dllexport) 导出函数、在 pch.cpp 中实现 Add/Sub 功能并给出编译动态库、新建控制台应用、配置附加包含目录与附加依赖项、放置 DLL 文件以及调用函数的完整操作流程。资源为单一 PDF 文档共 1 个文件大小约 446KB便于直接阅读与打印。已有 5000 余人学习下载说明该教程对初学者具有较高的参考价值。通过这份材料读者可以掌握 VS2019 动态库项目的结构、函数导出与导入声明方式、工程属性配置思路以及常见编译问题的应对要领能够独立完成一个简单的 DLL 连接实例。1. 动态库不是玄学Visual Studio 2019 DLL动态库连接实例到底解决了什么问题这份 Visual Studio 2019 DLL动态库连接实例是典型的「一看就懂、一配就错」的入门项目。它用 Add 和 Sub 两个加减法函数把动态库从建立、编译到被控制台程序调用的完整链路走了一遍。我拆完的最大感受是真正卡住新人的不是函数怎么写而是「编译 DLL 时提示无法启动程序要不要管」「头文件目录、库目录、附加依赖项三处路径到底怎么配」「dll 文件该放哪」。这篇笔记就是照着这三件事来组织的。适合第一次动手建动态库的 C/C 开发者也适合那些学过 DLL 概念但没完整跑通过一遍的人——它的价值不在代码量而在把每一步操作和背后的原理对齐。2. 建立动态库项目为什么必须用 extern C dllexport2.1 从模板选择看项目结构pch.h、pch.cpp、dllmain.cpp 各自干什么在 Visual Studio 2019 里新建项目选择「动态链接库(DLL)」模板项目名我习惯取 mydll。确认后生成的默认结构包含四个文件很多第一次接触的人会困惑为什么新建出来的项目没有 main 函数其实这四个文件各司其职它们之间的关系决定了你后面导出函数的写法pch.h预编译头文件同时也是你声明导出函数的地方。模板生成的 pch.h 默认只有框架代码你要在这后面追加自己的函数声明。pch.cpp预编译头的实现文件模板自动生成。你的函数实现也写在这里。dllmain.cppDLL 的入口点相当于 DLL 版本的 main。默认代码里有 DllMain 函数负责处理 DLL_PROCESS_ATTACH、DLL_PROCESS_DETACH 等事件新手不用改。framework.h一些默认的宏定义和系统头文件引用一般不用动。常见做法是在 pch.h 里加声明在 pch.cpp 里加实现。这两个文件是配套的声明和实现分开跟我平时写普通 C 模块的习惯一致只是多了一层导出标记。2.2 导出声明拆解extern C 和 _declspec(dllexport) 缺一个都不行打开 pch.h在末尾加入下面两行extern C _declspec(dllexport) int Add(int a, int b); extern C _declspec(dllexport) int Sub(int a, int b);这两行声明里有三个关键点缺一个都会在调用端出问题。extern C是告诉编译器这段函数按照 C 语言的规则生成符号名不要做 C 的名字修饰。C 编译器为了支持函数重载会把函数名和参数类型编码成一长串修饰名比如?AddYAHHHZ。如果 DLL 导出的是这种名字你在外部用Add去调用就会报「无法解析的外部符号」。加上extern C后导出表里的符号就是干净的Add后面用 LoadLibrary 或隐式链接都能直接对上。_declspec(dllexport)是导出标记让编译器把这个函数写进 DLL 的导出表。没有它函数编译进了 DLL 内部但外部程序找不到入口。注意这里不是__declspec模板项目里的宏定义有时会封装一层但直接写_declspec也能编译通过。函数原型写成int Add(int a, int b)和int Sub(int a, int b)返回值和参数都是 int这是入门示例的最佳选择——不涉及字符串编码、结构体对齐、内存所有权这些衍生问题能把注意力集中在链接本身。2.3 实现函数与编译提示「无法启动程序」其实是正常的在 pch.cpp 里实现这两个函数int Add(int a, int b) { return a b; } int Sub(int a, int b) { return a - b; }实现很简单就是两个 return。但这里有个细节值得注意实现代码不需要再写extern C和_declspec(dllexport)它们已经在头文件声明里出现过编译器在包含头文件时就已经拿到了导出信息。我见过有人把这两个修饰符在声明和实现处各写一遍编译也能过但属于重复劳动没必要。按 F7 编译这个动态库项目Visual Studio 2019 会弹出「无法启动程序」的提示框。很多新手在这里就慌了以为代码写错了。其实不是。原因很直接动态库不是一个可以独立运行的程序它没有 main 函数编译生成 .dll 后系统没有入口可以执行。这是 DLL 项目在 Visual Studio 里的固定行为不是你的代码问题。正确做法是选「否」不要直接运行然后去「输出」窗口看编译结果。只要输出里能看到mydll.dll和mydll.lib生成成功这一步就完成了。编译产物默认在解决方案文件夹下的 Debug 或 Release 目录里具体看你的活动配置。dll 和 lib 会生成在同一个目录后面要用的是这个 lib 文件的路径。2.4 编译产物清单dll 和 lib 分别给了谁编译完成后去 Debug 目录看你会看到两个核心文件mydll.dll运行时真正被加载的文件包含函数机器码。发布给用户的是这个。mydll.lib导入库文件不是静态库里面不包含函数代码只记录了 DLL 里导出的符号名和位置信息。链接器靠它知道「Add 在哪个 DLL 里、序号是多少」。这个区分很重要。很多人误以为 lib 是静态库、dll 是动态库二选一用就行。实际上是两个都要用的编译链接阶段靠 lib 确认符号运行阶段靠 dll 拿到实现。后面第三步配置路径时头文件目录、附加库目录、附加依赖项这三处也就是分别喂给编译器和链接器的不同信息。3. 连接动态库三处路径配置与 lib、dll、exe 的三角关系3.1 隐式链接原理编译期靠 lib运行期靠 dll调用动态库分为隐式链接和显式链接两种方式。本项目的做法是隐式链接也就是在编译期告诉链接器「我要用 mydll 里的 Add」链接器从 mydll.lib 里找到 Add 的符号信息生成一个跳转指令程序运行时操作系统根据这个信息去加载 mydll.dll找到真正的函数地址执行。这套机制决定了你必须配三样东西第一头文件路径。编译器在编译你的 main.cpp 时看到#include pch.h它得知道 pch.h 在哪。这个路径放在「附加包含目录」。第二lib 文件路径。链接器在处理#pragma comment (lib,mydll.lib)时得知道 mydll.lib 在哪。这个路径放在「附加库目录」。第三lib 文件的文件名。如果你在代码里写了#pragma comment (lib,mydll.lib)注意这里要写带 .lib 后缀的文件名而不是完整路径。具体名称以你项目编译输出的实际名字为准不要照抄网上的mydll_03.lib这种带下标的名字。最后运行期把 mydll.dll 放到 exe 旁边或者放到系统 PATH 能搜索到的目录。操作系统加载 DLL 时按既定顺序搜索exe 所在目录排在前面所以最常见也最稳妥的做法就是拷贝到 exe 同目录。3.2 图形界面配置附加包含目录、附加库目录、附加依赖项三步走新建一个控制台应用项目名字随意比如 ConsoleApp。然后在解决方案资源管理器里右键工程名进入「属性」。确保左上角配置选的是和 DLL 项目一致的「调试」和「x64」或「Win32」三处路径才能对齐。第一处配置属性 → C/C → 常规 → 附加包含目录。填入 mydll 项目里 pch.h 所在的目录。注意是目录不是文件路径。目录分隔符建议用反斜杠结尾例如D:\Projects\mydll\mydll第二处配置属性 → 链接器 → 常规 → 附加库目录。填入 mydll.lib 所在的目录也就是 DLL 项目编译输出目录。例如D:\Projects\mydll\Debug第三处配置属性 → 链接器 → 输入 → 附加依赖项。填入 lib 文件名这里只写文件名不带路径mydll.lib这三处配置分别对应编译器找头文件、链接器找库文件、链接器确认具体库名。顺序不看先后但要逻辑自洽路径必须真实存在配置的位数和调试/发布模式必须和实际编译环境一致。如果 DLL 项目是 Debug x64 编译的控制台项目也必须是 Debug x64混合了 x86 的 Debug 也行但 DLL 用的位数和调用端必须一样。3.3 另一种等效写法不用图形界面用代码声明连接图形界面配好三处路径后主函数里的写法则体现了另一种连接思路。看这段完整的调用代码#include pch.h #pragma comment (lib,mydll.lib) extern C _declspec(dllimport) int Add(int a, int b); extern C _declspec(dllimport) int Sub(int a, int b); int main() { int sum Add(10, 20); int diff Sub(30, 8); printf(Add(10,20) %d\n, sum); printf(Sub(30,8) %d\n, diff); return 0; }这段代码里有几个要点。#include pch.h引入的是 DLL 项目里的那份 pch.h里面包含前面加的导出声明。但因为这边是调用方用的是_declspec(dllimport)和 DLL 项目里的dllexport对应。dllimport 比 dllexport 多一层含义告诉编译器这个符号来自外部 DLL可以生成更高效的调用指令。#pragma comment (lib,mydll.lib)是编译期指令等效于在链接器输入里手动添加 mydll.lib。如果图形界面里已经填了附加依赖项这行可以省略如果没填这行就是唯一指定 lib 的地方。两种方式任选其一不要又填了图形界面又在代码里加重复指定同一个 lib 不会报错但属于无意义操作。3.4 运行期部署dll 该放哪、不该放哪编译完成后把 mydll.dll 复制到 ConsoleApp.exe 所在的输出目录。这一步漏掉的典型表现是编译通过了一运行就弹窗「由于找不到 mydll.dll无法继续执行代码」。这不是代码问题是运行库部署问题。我不推荐把 dll 放到 C:\Windows\System32确实能解决问题但污染系统目录。也不推荐用 PATH 环境变量去兜底项目一多路径冲突会让人崩溃。最干净的做法就是 exe 同目录这也是 Windows 加载 DLL 时第一个搜索的位置。如果说图形界面三处配置是「编译期告诉编译器去哪找东西」那么把 dll 放进 exe 目录就是「运行期告诉操作系统去哪找东西」。这一步做完直接运行控制台程序Add 和 Sub 的结果就能正常打印出来。4. 避坑指南动态库连接最常见的五个坑与排查顺序4.1 编译 DLL 弹出「无法启动程序」现象F7 编译 mydll 项目Visual Studio 弹出错误提示「无法启动程序由于没有与之关联的任务或用户输入」新手以为项目写坏了。原因DLL 不是可执行程序VS 试图以 exe 的方式运行它自然失败。这是 DLL 项目模板的默认行为不是编译错误。解决点「否」忽略弹窗去菜单栏「生成」→「重新生成解决方案」看输出窗口。只要显示mydll.dll生成成功就可以直接关掉这个弹窗。从那以后我每次编译 DLL 项目都会在输出窗口确认 dll 和 lib 的生成时间而不是盯着弹窗看。4.2 控制台工程里找不到 pch.h现象编译控制台项目时报错fatal error C1083: 无法打开包括文件: pch.h: No such file or directory。原因附加包含目录没有配或者配了错误的层级。pch.h 在 mydll 项目根目录不是 Debug 目录也不是解决方案目录。用相对路径时理解偏差更容易出这种问题。解决打开工程属性 → C/C → 常规 → 附加包含目录填入 mydll 项目根目录的绝对路径。不确定在哪的话在解决方案资源管理器里右键 pch.h → 打开所在文件夹直接复制路径栏内容。配置后如果还找不到检查配置属性左上角是不是当前激活的配置Debug 配到了 Release 里是常见的手滑操作。4.3 链接报错无法解析的外部符号现象编译过了链接时报LNK2019 unresolved external symbol _Add referenced in function _main或者LNK2001。原因三个来源。一是附加依赖项里没写 lib 文件名二是 lib 文件名写错比如把mydll.lib写成了mydll.dll三是导出声明没有用extern C导致 DLL 导出的是修饰后的符号名调用端却用 C 风格名字去找。解决先确认附加依赖项里 lib 文件名和 Debug 目录里的实际文件一致再打开 pch.h 确认声明里有extern C _declspec(dllexport)。如果是从旧项目复制过来的代码注意 dllimport 和 dllexport 是否搞反——在 DLL 项目里用 dllimport 会导致导出表为空链接端自然找不到符号。用 dumpbin 验证导出符号的方法见下一节。4.4 运行报错找不到 mydll.dll复制文件后仍报错现象exe 编译成功一运行弹窗「由于找不到 mydll.dll」把 dll 复制到 exe 目录后偶尔仍报同样的错。原因复制的位置不对。VS 的输出目录不只是 Debug还有可能是 x64\Debug。你看到的是 x64\Debug 里的 exe但把 dll 复制到了 Debug 里。项目和输出目录的嵌套结构很容易让人看走眼。解决在解决方案资源管理器里右键控制台项目 → 打开所在文件夹把 dll 复制到这个目录。如果项目用了自定义输出路径检查工程属性 → 常规 → 输出目录确认 exe 实际位置。另外确认 dll 的位数和 exe 一致x64 的 exe 配了 x86 的 dllWindows 会提示「不是有效的 Win32 应用程序」这个报错和找不到 dll 的提示不同但同样常见。4.5 换机器运行报缺 VCRUNTIME140.dll 或 MSVCP140.dll现象本机运行正常把 exe 和 mydll.dll 一起拷到别的 Windows 机器启动报错「缺少 VCRUNTIME140.dll」。原因程序依赖了 VC 运行库目标机器没有装。DLL 和 exe 都是动态链接到运行库的而这台机器上没有对应的运行库版本。解决两个方向。长期方案是在目标机器安装 Visual C Redistributable短期方案是改工程属性 → C/C → 代码生成 → 运行库选「多线程 (/MT)」把运行库静态链接进 exe。后者会让 exe 体积变大但目标机器不需要额外装环境。我一般给客户交付小工具时选 /MT内部项目则保持默认 /MD。5. 验证导出与显式加载让「装好了」这件事不再靠玄学说到「DLL 到底导出成功了没」很多人靠肉眼猜其实 Visual Studio 自带一个命令行工具可以直接查。打开「开发者命令提示符」切到 mydll.dll 所在目录执行dumpbin /exports mydll.dll输出里会列出 DLL 的导出表。如果能看到Add和Sub两个名字说明导出成功如果看不到任何函数回头看 pch.h 里的导出声明是否生效。这个命令比你在代码里反复调试快得多也解决了 4.3 里「到底有没有导出成功」的争议。验证隐式链接跑通后推荐再试一下显式加载的写法它绕开 lib 和头文件配置是排查 DLL 问题时最有效的手段#include windows.h #include cstdio typedef int (*AddFunc)(int, int); int main() { HMODULE hMod LoadLibraryA(mydll.dll); if (hMod NULL) { printf(LoadLibrary failed: %lu\n, GetLastError()); return 1; } AddFunc add (AddFunc)GetProcAddress(hMod, Add); if (add NULL) { printf(GetProcAddress failed\n); FreeLibrary(hMod); return 1; } printf(Add(3, 4) %d\n, add(3, 4)); FreeLibrary(hMod); return 0; }这段代码用 LoadLibraryA 在运行期加载 DLL用 GetProcAddress 按名字拿函数地址。它的好处是不需要配置头文件目录和 lib 路径只要 mydll.dll 在 exe 同目录就能跑。如果这段能跑通而之前的隐式链接代码不行问题就锁定在 lib 配置或声明写法上而不是 DLL 本身。两个补充验证手段。一是用dumpbin /dependents 你的exe.exe查看 exe 依赖的 DLL 列表能确认它是否引用了 mydll.dll二是在 LoadLibrary 失败时认真看 GetLastError 的返回码常见的有 126找不到模块和 193位数不匹配这两个码能直接排除掉最典型的两类部署错误。从那以后我每次做完动态库连接都强制走一遍 dumpbin 验证导出 exe 同目录部署 显式加载三件套确认没问题再往下写业务代码。希望帮到你。本文还有配套的精品资源点击获取
