资讯动态

MFC DLL 类型选型、接口导出与模块状态避坑指南

发布时间:2026/10/26 10:34:35 来源:尧图企业网站定制
简介面向具备一定 C 基础、希望掌握 MFC 动态链接库开发的初学者与工程人员这份示例围绕扩展 MFC DLL 的创建与调用展开解决从零搭建 DLL 工程、正确导出类与函数以及在对话框程序中链接使用的问题可作为课程实验或入门练手的参考代码。压缩包共 61 个文件约 4.79MB以 h 头文件、cpp 源文件为主配合 dsp/dsw 工程配置、rc 与 res 资源脚本、def 导出定义、ico 图标以及编译产物 lib、dll、pdb 等完整保留了一份可直接对照的工程现场。目录中并列放置 DLL 工程与调用方对话框工程示例以 Date 类的封装导出演示接口设计ReadMe 与构建日志保留了当时的编译记录。已有 407 人学习适合按工程结构逐个文件比对理解导出声明、库引用与运行时依赖的排错思路。1. 从一个插件化需求说起为什么把功能拆进 MFC DLL一个典型的 MFC 上位机项目界面、串口通信、数据处理全堆在同一个工程里编译一次要等几分钟两个人改同一份代码冲突比功能还多。后来把通信和算法拆成独立的动态链接库主程序只留框架和调度问题才慢慢收敛。这个过程中真正耗时间的不是写代码而是搞清楚 MFC DLL 到底有几种工程类型、导出接口怎么定、模块状态什么时候切换。这套方案适合正在做 MFC 课设或毕设、需要把功能模块拆开交付的人也适合维护老 MFC 程序、想给现有系统加插件机制的开发者。需要导出 C 类给 MFC 客户端复用走扩展 MFC DLL只需要暴露一组函数常规 DLL 就够。类型选错后面全是返工。2. 三种 MFC DLL 的选型共享 MFC、静态链接与扩展 DLL 的工程骨架动手新建工程之前先把「用哪种 DLL」这件事定下来。选型错了代码写到一半才发现导出不了类或者客户端根本加载不了返工成本比重新建一个工程还高。这一章把三种工程类型的边界划清楚再落到目录结构和导出方式上保证后面几章的接口设计有地方放。2.1 三种工程类型的差异与适用边界MFC 的 DLL 工程向导里通常给三个选项名字很像用途差得远。工程类型MFC 链接方式能否导出 C 类客户端限制典型用途常规 DLL共享 MFC动态链接 MFC技术上可以不推荐无功能函数、界面组件、带资源的对话框常规 DLL静态链接 MFC静态链接 MFC不建议无独立工具函数部署不想带 MFC 运行库MFC 扩展 DLL必须动态链接 MFC推荐客户端必须是 MFC 程序复用 C 类、扩展文档模板、组件化判断顺序可以按三个问题走客户端是不是 MFC 程序要导出的东西里有没有 C 类或继承自 MFC 的类型部署环境能不能保证有对应版本的 MFC 运行库客户端不是 MFC 程序直接排除扩展 DLL因为扩展 DLL 依赖 MFC 的模块状态和CDynLinkLibrary链纯 Win32 程序加载它会在初始化阶段就出问题。要导出CString、CWnd派生类这类东西只能走扩展 DLL常规 DLL 导出 C 类会遇到名字修饰和对象布局的双重麻烦调试成本极高。部署环境不确定、又只导出纯函数静态链接 MFC 的常规 DLL 最省事代价是每个 DLL 都带一份 MFC 代码体积上去而且模块之间不能传递 MFC 对象。还有一个容易忽略的点静态链接 MFC 的常规 DLL 不能再去创建扩展 DLL两边的 MFC 状态是各自独立的混用会在资源查找上翻车。项目里只要有一处需要扩展 DLL整个方案就得统一到共享 MFC 的路线。2.2 新建工程后的目录与预编译头处理工程建好之后先把目录结构定下来不然后面接口头文件和实现文件混在一起客户端引用时会带出一堆不该暴露的 MFC 头。PluginDemo/ include/ PluginApi.h // 只放导出声明、结构体、常量 src/ PluginDemo.cpp // 导出函数实现 PluginImpl.h // 内部实现不对外 pch.h / pch.cpp // 预编译头 res/ PluginDemo.rc // DLL 自己的对话框、图标、字符串 PluginDemo.def // 导出符号列表接口目录和实现目录分开的好处很直接客户端工程只需要把include加进附加包含目录不会顺带把pch.h、afxwin.h这些依赖拉进来。接口头文件里只放extern C的函数声明、POD 结构体和常量不放任何 MFC 类型这样即使客户端不是 MFC 程序包含这个头也不会编译失败。预编译头在 DLL 工程里同样要开。MFC 头文件的展开量很大关掉预编译头之后单次编译时间会明显变长改一行代码等半分钟调试节奏全乱。pch.h里通常放afxwin.h、afxext.h如果用到数据库或网络再补对应的头。工程属性里把「使用预编译头」设成「使用(/Yu)」pch.cpp单独设成「创建(/Yc)」这是标准做法。还有两个编译配置必须在同一批里对齐字符集和运行库。DLL 工程用 Unicode客户端也得是 UnicodeDLL 用「多线程调试 DLL(/MDd)」客户端不能用「多线程调试(/MTd)」。这两项不一致症状往往不是编译报错而是运行时堆被踩坏排查起来非常费劲。2.3 用 .def 与 __declspec 两种方式导出函数导出函数有两套写法选一套坚持到底不要在一个工程里混着用。第一种是导出宏加extern C写起来直观// include/PluginApi.h —— 导出宏与接口声明 #pragma once #ifdef PLUGIN_EXPORTS #define PLUGIN_API __declspec(dllexport) #else #define PLUGIN_API __declspec(dllimport) #endif // extern C 关掉 C 名字修饰显式加载时按名字查找才不会失败 extern C { PLUGIN_API int PluginInit(const char* configPath); PLUGIN_API int PluginRun(int taskId); PLUGIN_API void PluginRelease(); }PLUGIN_EXPORTS这个宏要在 DLL 工程的「预处理器定义」里加上客户端工程不加这样同一个头文件在两边展开成不同的修饰符。注意这里用的是默认的__cdecl调用约定没有写__stdcall原因在下面讲。第二种是.def文件把导出符号显式列出来; PluginDemo.def —— 显式列出导出符号 LIBRARY PluginDemo EXPORTS PluginInit PluginRun PluginRelease.def的价值在于导出名完全可控不受编译器修饰规则影响。但这里有个血泪经验如果函数声明成__stdcallx86 下实际符号名会变成_PluginInit4这种形式.def里写PluginInit链接器会找不到符号直接报 LNK2001。两种解决办法要么在.def里写完整的修饰名要么统一用__cdecl让链接器自动做下划线匹配。x64 下没有这个修饰问题但工程如果还要出 32 位版本这个坑迟早会踩。我的习惯是导出函数统一用默认调用约定.def里写干净的名字同时在头文件里保留extern C。这样隐式链接和显式加载两条路都走得通后面换调用方式不用改 DLL。3. 导出接口怎么设计函数、类与模块状态切换的落地写法接口设计决定了这个 DLL 好不好用、能用多久。把内部类直接导出去客户端就得跟着 DLL 的编译器和 MFC 版本走升级一次全员重编只导出函数和句柄边界清楚但设计时得多想一层。这一章给两套写法句柄式 C 接口和扩展 DLL 的类导出再把模块状态这个绕不开的机制讲透。3.1 导出 C 风格函数与句柄式接口最稳的接口形态是「句柄 一组函数」客户端拿到的是一个不透明指针内部结构完全不暴露。// src/PluginDemo.cpp —— 句柄式接口实现 #include pch.h #include PluginApi.h namespace { // 内部上下文客户端看不到具体布局 struct PluginContext { HINSTANCE hModule; CString configPath; bool running; }; PluginContext* g_ctx nullptr; } extern C PLUGIN_API int PluginInit(const char* configPath) { if (g_ctx ! nullptr) { return 0; // 已初始化拒绝重复创建 } g_ctx new PluginContext(); g_ctx-hModule AfxGetInstanceHandle(); g_ctx-configPath CA2T(configPath); // 按当前字符集转换入参 g_ctx-running false; return 1; } extern C PLUGIN_API void PluginRelease() { delete g_ctx; // 谁分配谁释放全部留在 DLL 内部 g_ctx nullptr; }这段代码有三个设计点值得说明。第一入参用const char*而不是CString避免把 MFC 对象跨模块传递如果工程是 Unicode用CA2T在边界上做一次转换转换后的CString只活在 DLL 内部。第二所有new和delete都封在 DLL 里客户端拿不到指针也就不存在「在 EXE 里 delete 由 DLL 分配的内存」这种跨堆问题。第三PluginInit做了重入保护重复初始化直接返回失败比让调用方自己去记状态可靠。句柄式接口的代价是函数数量会变多每个操作都要一个导出函数。功能超过二十个的时候可以按领域拆成几个 DLL而不是往一个 DLL 里继续塞。拆分粒度按「会不会被独立替换」来判断通信、算法、报表各成一个主程序只负责编排。3.2 扩展 MFC DLL 导出 C 类确实需要把类暴露给客户端就走扩展 DLL。类的声明加AFX_EXT_CLASS宏这个宏在 DLL 编译时展开成__declspec(dllexport)在客户端展开成__declspec(dllimport)由 MFC 的头文件自动处理不需要自己写导出宏。// include/DeviceDriver.h —— 扩展 DLL 导出的类 #pragma once #include afxwin.h class AFX_EXT_CLASS CDeviceDriver : public CObject { DECLARE_DYNAMIC(CDeviceDriver) public: CDeviceDriver(); virtual ~CDeviceDriver(); BOOL Open(LPCTSTR portName, int baudRate); void Close(); int ReadBuffer(BYTE* buffer, int size); private: HANDLE m_hPort; };DECLARE_DYNAMIC让类支持运行时类型识别客户端可以用DYNAMIC_DOWNCAST做安全转换。对应的实现文件里要写IMPLEMENT_DYNAMIC(CDeviceDriver, CObject)少写这一句链接期不一定报错运行期一转换就崩。扩展 DLL 还需要一个DllMain来做 MFC 扩展模块的初始化这段骨架不能省// src/ExtensionDll.cpp —— 扩展 DLL 的初始化骨架 #include pch.h static AFX_EXTENSION_MODULE g_extensionModule { NULL, NULL }; extern C int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { UNREFERENCED_PARAMETER(lpReserved); if (dwReason DLL_PROCESS_ATTACH) { // 初始化 MFC 扩展模块状态 if (!AfxInitExtensionModule(g_extensionModule, hInstance)) { return 0; } // 挂到 MFC 的模块链表上客户端才能共享资源 new CDynLinkLibrary(g_extensionModule); } else if (dwReason DLL_PROCESS_DETACH) { AfxTermExtensionModule(g_extensionModule); } return 1; }DllMain里只做模块初始化和清理不要在这里创建窗口、起线程、读大文件。DLL_PROCESS_ATTACH阶段加载器锁还持有任何触发其他 DLL 加载的操作都可能死锁这类问题在现场表现为「程序启动卡住」用调试器附加都很难抓到。导出类还有一个兼容性约束客户端和 DLL 必须用同一套运行库、同样的 Debug/Release 配置、同样的位数。C 类跨模块传递时对象布局、虚表、异常处理模型都得一致任何一项不匹配症状都是随机的内存错误而不是明确的链接错误。3.3 模块状态与资源句柄AFX_MANAGE_STATE 的正确位置MFC 把资源句柄、当前模块实例这些状态按模块隔离存放。DLL 里的导出函数被调用时MFC 的「当前模块」还停在 EXE 上这时候去LoadString、DoModal找的全是 EXE 的资源。ID 对不上就加载失败对话框弹出来是空白的或者直接断言。解决方式是在导出函数入口切换模块状态// src/PluginDemo.cpp —— 使用 DLL 自身资源的导出函数 extern C PLUGIN_API BOOL ShowPluginAbout(HWND hParent) { // 进入函数时切换到本 DLL 的模块状态函数返回时自动恢复 AFX_MANAGE_STATE(AfxGetStaticModuleState()); CDialog dlg(IDD_PLUGIN_ABOUT, CWnd::FromHandle(hParent)); return dlg.DoModal() IDOK; }AFX_MANAGE_STATE是一个作用域宏在构造时保存当前状态并切到传入的状态析构时恢复。所以它必须写在函数体的最前面不能写在某个if分支里也不能指望它在函数中间生效。有几个位置特别容易漏。一是回调函数DLL 把函数指针传给系统 API回调触发时模块状态又回到 EXE 了回调内部要用资源就得再切一次。二是线程入口DLL 里起的线程默认没有正确的模块状态线程函数开头要单独处理。三是InitInstance内部这里 MFC 已经切好了状态不要再套一层。常规 DLL 如果静态链接 MFC模块状态是独立的一般不需要这个宏扩展 DLL 和共享 MFC 的常规 DLL 则必须加。判断方法很简单导出函数里只要出现了资源 ID 或 MFC 界面类就先加一行加了不会有副作用漏了就是玄学问题。3.4 常规 DLL 里的 CWinApp 与 InitInstance共享 MFC 的常规 DLL 会生成一个CWinApp派生类MFC 在DLL_PROCESS_ATTACH时调用它的InitInstance在卸载时调用ExitInstance。这个类不是摆设DLL 级的初始化逻辑放这里比放DllMain安全。// src/PluginApp.cpp —— 常规 DLL 的应用对象 #include pch.h class CPluginApp : public CWinApp { public: virtual BOOL InitInstance(); virtual int ExitInstance(); }; CPluginApp theApp; // 全局唯一实例MFC 靠它挂接初始化流程 BOOL CPluginApp::InitInstance() { CWinApp::InitInstance(); // 适合放日志系统初始化、配置读取、全局缓存预热 return TRUE; } int CPluginApp::ExitInstance() { // 适合放释放全局资源、关闭日志句柄 return CWinApp::ExitInstance(); }theApp这个全局对象不能省也不能改成局部变量或者指针MFC 的启动流程依赖它在模块加载阶段完成构造。InitInstance里不要创建窗口、不要起依赖消息循环的线程这个阶段消息泵还没跑起来创建出来的窗口收不到消息表现是界面卡死。还有一个顺序问题InitInstance执行时DLL 的导出函数还没被调用过如果客户端在LoadLibrary之后立刻调用导出函数这两件事的先后是确定的但如果是隐式链接EXE 启动阶段就会加载 DLL 并执行InitInstance这时候 EXE 自己的CWinApp可能还没初始化完。所以InitInstance里不要反向调用 EXE 的函数依赖方向只能是 EXE 依赖 DLL。4. 调用方怎么接隐式链接与显式加载的两套接入流程DLL 写完了客户端怎么调是另一半工作量。隐式链接写起来像普通函数调用但部署时少一个文件程序就直接起不来显式加载多写十几行换来的是可控的失败处理。两条路都要会按模块是否可选来决定用哪条。4.1 隐式链接头文件、导入库与运行目录隐式链接的写法最省事包含头文件、链上导入库之后直接调函数。// client/main.cpp —— 隐式链接调用 #include PluginApi.h #pragma comment(lib, PluginDemo.lib) int main() { if (PluginInit(config.ini) ! 1) { return -1; } PluginRun(1); PluginRelease(); return 0; }工程配置要加三项附加包含目录指向include附加库目录指向.lib所在目录附加依赖项里写PluginDemo.lib。这个.lib是导入库不是静态库里面只有符号跳转信息真正的代码在.dll里别把两者搞混。运行时 DLL 的搜索顺序是固定的EXE 所在目录、系统目录、PATH 里的目录。开发机上经常是 IDE 把输出目录加进了 PATH 所以能跑拷到别的机器就报「找不到指定的模块」。稳妥的做法是把 DLL 拷到 EXE 同目录或者统一放一个bin目录再把它加进搜索路径。隐式链接的失败表现很难看程序启动阶段加载器就失败了弹一个系统错误框代码里一行都没执行日志也没有。所以它适合那些「缺了程序就没意义」的核心模块比如主通信库。4.2 显式加载LoadLibrary 到 FreeLibrary 的完整流程可选模块、插件、需要热替换的功能用显式加载。整条链路要写完整包括每一步的失败处理。// client/plugin_loader.cpp —— 显式加载与调用 #include windows.h #include tchar.h typedef int (*PFN_PluginInit)(const char*); typedef int (*PFN_PluginRun)(int); typedef void (*PFN_PluginRelease)(); bool RunPlugin(const char* configPath) { HMODULE hDll LoadLibrary(_T(PluginDemo.dll)); if (hDll nullptr) { // 126找不到模块193位数不匹配14001运行库缺失 DWORD err GetLastError(); return false; } auto pfnInit (PFN_PluginInit)GetProcAddress(hDll, PluginInit); auto pfnRun (PFN_PluginRun)GetProcAddress(hDll, PluginRun); auto pfnRelease (PFN_PluginRelease)GetProcAddress(hDll, PluginRelease); if (pfnInit nullptr || pfnRun nullptr || pfnRelease nullptr) { // 127找不到导出函数通常是名字修饰或导出名不一致 FreeLibrary(hDll); return false; } bool ok (pfnInit(configPath) 1); if (ok) { pfnRun(1); pfnRelease(); } FreeLibrary(hDll); // 确认没有后台线程还在回调再卸载 return ok; }几个细节值得抠。GetProcAddress传的名字必须和导出表里的名字逐字符一致用了extern C加.def之后名字是干净的但如果哪天有人把extern C去掉这里立刻返回空指针。函数指针的类型要和 DLL 里的声明完全一致调用约定、参数类型、返回值任何一项不同编译期不报错运行期栈就乱了。FreeLibrary之前一定要确认 DLL 内部没有还在跑的线程、没有注册到系统的回调。卸载之后这些线程的代码段已经无效下一次回调就是访问冲突而且崩溃位置看起来和 DLL 毫无关系属于典型的黑匣子问题。4.3 两套方式的取舍与部署清单维度隐式链接显式加载调用写法直接函数调用函数指针缺失时的表现程序无法启动可在代码里降级处理加载时机进程启动调用到时才加载卸载能力进程结束才卸可主动FreeLibrary适用场景核心依赖模块插件、可选功能、热替换选型之外部署清单要提前列好不然后面每次发版都要重新查一遍。检查项要求位数DLL 与 EXE 同为 x86 或同为 x64字符集双方一致Unicode 对 Unicode运行库Debug/Release 不混用/MD 与 /MDd 不混用MFC 运行库共享 MFC 时目标机需具备对应运行库依赖文件DLL 及其依赖项全部随包发布导出名与GetProcAddress中的字符串一致4.4 依赖检查dumpbin 与运行时定位交付前用工具把依赖关系过一遍比在客户现场猜要快得多。# 查看 DLL 依赖了哪些运行库和其他 DLL dumpbin /dependents PluginDemo.dll # 查看导出表确认函数名有没有被修饰 dumpbin /exports PluginDemo.dll/dependents的输出里如果出现 MFC 和 CRT 的运行库名说明目标机必须装对应的可再发行组件如果出现一个意料之外的第三方 DLL那就是隐式依赖打包时容易漏。/exports用来核对导出名看到?PluginInitYAHPEBDZ这种形式说明extern C没生效显式加载按PluginInit查找必然失败。运行时定位还有两个辅助手段。一是用进程监视工具看加载了哪些模块确认 DLL 是从预期路径加载的而不是从系统目录里翻出来一个旧版本。二是在LoadLibrary失败后立刻取GetLastError并记录把错误码和路径一起写进日志现场排查时这两个信息能省掉大量来回沟通。5. 避坑与排查MFC DLL 最容易翻车的五个现场DLL 的问题有个共同特点编译链接全都过错误在运行期才出现而且报错位置经常离真正的原因很远。这一章列的五个现场是这类项目里出现频率最高的每条按现象、原因、解决三段拆开。5.1 对话框弹出来是空白图标和字符串也找不到现象DLL 里定义的对话框能弹出来但控件全是空的标题是默认值图标是系统默认图标。调试时单步跟进DoModal也没报错。原因模块状态没切换。MFC 按模块隔离资源句柄导出函数被调用时当前模块还是 EXEDoModal(IDD_XXX)拿这个 ID 去 EXE 的资源段里找ID 撞上了就显示成别的对话框撞不上就创建一个空壳。解决在导出函数第一行加AFX_MANAGE_STATE(AfxGetStaticModuleState());回调函数和 DLL 内部线程入口同样要加。加完之后如果还有个别资源找不到检查.rc文件有没有被正确编译进 DLL以及资源 ID 在两边是不是重复定义。5.2 编译链接都通过运行时提示找不到 DLL 或找不到入口点现象开发机上跑得好好的拷到测试机弹出「找不到 PluginDemo.dll」或者「无法定位程序输入点 xxx 于动态链接库上」。原因三种可能。DLL 不在搜索路径里两边位数不一致x64 进程加载不了 x86 的 DLL导出名被 C 修饰过导入库里的符号和运行时查找的名字对不上。解决先把 DLL 放到 EXE 同目录排除路径问题再用dumpbin /headers看机器的类型确认位数最后用dumpbin /exports核对导出名。如果发现名字被修饰回头检查extern C和.def是否生效同时确认客户端和 DLL 用的是同一份接口头文件。5.3 Debug 下正常Release 下崩溃或者数据错乱现象调试版跑一整天都没事换成 Release 版本运行几分钟就崩崩溃位置飘忽不定有时候在字符串处理有时候在对象析构。原因DLL 和 EXE 的配置不一致。常见组合是 DLL 用/MDd、EXE 用/MD或者一边 Debug 一边 Release。两套 CRT 各自管理自己的堆跨越模块边界new出来、另一边delete堆头对不上行为就是随机的。解决把整个解决方案的配置对齐Debug 全 DebugRelease 全 Release运行库选项统一。接口设计上尽量避免跨模块传递需要释放的指针改成「谁分配谁释放」或者提供成对的分配/释放函数。这类问题用调试器很难抓因为崩溃点离现场太远提前对齐配置比事后排查划算。5.4 扩展 DLL 被非 MFC 程序加载初始化直接失败现象写了一个扩展 DLL用 MFC 客户端调用正常换成一个纯 Win32 的小工具去LoadLibrary返回成功但调用导出函数就崩或者加载阶段直接失败。原因扩展 DLL 的DllMain里调用了AfxInitExtensionModule和new CDynLinkLibrary这两步依赖 MFC 的模块状态体系非 MFC 进程里没有这套环境。这跟 DLL 写得对不对无关是工程类型本身的约束。解决有两种走法。一是把扩展 DLL 改成常规 DLL导出 C 风格函数内部再用 MFC这样任何程序都能加载二是保留扩展 DLL但明确要求客户端必须是 MFC 程序并在文档里写清楚。选哪种取决于复用范围如果模块将来要给别的语言或非 MFC 程序用一开始就不该选扩展 DLL。5.5 卸载 DLL 的时候崩溃或者进程退出时报访问冲突现象调用FreeLibrary的瞬间崩溃或者主程序退出时弹一个访问冲突调用栈停在一个已经卸载的模块地址上。原因DLL 里起的线程还在跑注册的回调还没注销或者有窗口、定时器挂在 DLL 的代码段上。FreeLibrary只是减引用计数代码段一卸载这些回调的下一次触发就是野指针。解决给 DLL 加一个显式的Shutdown导出函数在里面停线程、等线程退出、注销回调、销毁窗口全部做完再让客户端调FreeLibrary。线程退出要用可等待的方式WaitForSingleObject等到线程句柄有信号为止不要用TerminateThread那样资源根本没释放。进程退出阶段顺序更难控制能在ExitInstance里做的清理就别拖到DllMain的PROCESS_DETACH里做。6. 验收与答辩用一份自测清单把 DLL 方案讲清楚DLL 这种东西功能跑通只是及格线。验收要看的是「换台机器还能不能跑」「缺文件的时候表现是否可控」「卸载是否干净」。我一般按下面这份清单过一遍每条都能给出证据再交付。验收项通过标准功能调用隐式链接与显式加载两条路都能调通全部导出函数资源显示DLL 内的对话框、图标、字符串全部正常显示依赖完整dumpbin /dependents输出中的依赖项全部随包提供位数匹配x86 与 x64 各构建一次对应客户端均能加载配置一致Debug 与 Release 各自成对测试无混用异常处理删除 DLL 后显式加载返回失败而非崩溃卸载干净调用停止接口后FreeLibrary进程正常退出无报错干净环境在未装开发工具的机器上完整跑一遍演示环节控制在三步。第一步跑正常流程展示主界面调用 DLL 完成一次完整业务第二步制造异常把 DLL 改名或删掉展示显式加载的失败提示这一步最能体现接口设计是否考虑了失败分支第三步打开依赖查看工具现场展示导出表和依赖列表说明每个依赖项的来源。答辩时老师最爱问的三个问题提前准备好答案。为什么选这种 DLL 类型回答要落到客户端是不是 MFC、要不要导出类、部署环境有没有 MFC 运行库这三点上而不是「向导默认选的」。模块状态是干什么的能用一句话说清楚MFC 的资源按模块隔离导出函数里不切换就会去 EXE 里找资源。跨模块传对象有什么风险答运行库和堆的归属再补一句接口设计上是怎么规避的。从那以后我每次交付 DLL 之前都会在一台干净环境里用dumpbin过一遍依赖再手动把显式加载的失败分支跑一次确认删掉文件之后程序是提示而不是崩溃。这套动作花不了十分钟省下的现场排查时间远不止这些。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑