关于动态链接库编译顺序导致 " _DllMain@12 已经在 LIBCMTD.lib(dllmain.obj) 中定义 " 问题的解决方法
1. 我用 VC 2003 编译一个关于网络发包的动态链接库, 其中包含文件如下:
- 源文件
- base64.cpp
- CWSTAPI.cpp
- IniFile.cpp
- md5.cpp
- 头文件
- base64.h
- CWSTAPI.h
- IniFile.h
- md5.h
其中 CWSTAPI.h 和 CWSTAPI.cpp 文件中包含动态链接库导出函数的声明和定义.
2. 直接生成解决方案会产生如下错误 :
LINK : LNK6004: 没有找到 ..\..\bin/CWSTAPI.dll 或上一个增量链接没有生成它;正在执行完全链接
nafxcwd.lib(dllmodul.obj) : error LNK2005: _DllMain@12 已经在 LIBCMTD.lib(dllmain.obj) 中定义
nafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) 已经在 libcpmtd.lib(newop.obj) 中定义
nafxcwd.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) 已经在 LIBCMTD.lib(dbgdel.obj) 中定义
nafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new[](unsigned int)" (??_U@YAPAXI@Z) 已经在 libcpmtd.lib(newaop.obj) 中定义
nafxcwd.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete[](void *)" (??_V@YAXPAX@Z) 已经在 LIBCMTD.lib(delete2.obj) 中定义
nafxcwd.lib(dllmodul.obj) : warning LNK4006: _DllMain@12 已在 LIBCMTD.lib(dllmain.obj) 中定义;已忽略第二个定义
nafxcwd.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) 已在 libcpmtd.lib(newop.obj) 中定义;已忽略第二个定义
nafxcwd.lib(afxmem.obj) : warning LNK4006: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) 已在 LIBCMTD.lib(dbgdel.obj) 中定义;已忽略第二个定义
nafxcwd.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new[](unsigned int)" (??_U@YAPAXI@Z) 已在 libcpmtd.lib(newaop.obj) 中定义;已忽略第二个定义
nafxcwd.lib(afxmem.obj) : warning LNK4006: "void __cdecl operator delete[](void *)" (??_V@YAXPAX@Z) 已在 LIBCMTD.lib(delete2.obj) 中定义;已忽略第二个定义
正在创建库 ..\..\bin/CWSTAPI.lib 和对象 ..\..\bin/CWSTAPI.exp
..\..\bin/CWSTAPI.dll : fatal error LNK1169: 找到一个或多个多重定义的符号
后来发现 .cpp 文件的编译顺序如下:
正在编译...
md5.cpp
IniFile.cpp
CWSTAPI.cpp
base64.cpp
3. 以前我没有添加 base64.h 和 base64.cpp 文件, 可以正常编译, 难道是和这个有关, 于是我想办法让 base64.cpp 在 CWSTAPI.cpp 之前编译, 我共想了三种方法 :
(1) 先将 base64.cpp 文件从项目中移除, 接着又加载进来, 那么 "解决方案资源管理器" 中文件的顺序为 :
- 源文件
- CWSTAPI.cpp
- IniFile.cpp
- md5.cpp
- base64.cpp
这样我点击菜单上 生成 -> 重新生成解决方案, 果然编译顺序为 :
正在编译...
base64.cpp
md5.cpp
IniFile.cpp
CWSTAPI.cpp
注意不能使用生成解决方案, 那样可能 .cpp 文件不会重新编译.
但是这样会发生问题, 因为你关掉这个解决方案, 重新打开这个解决方案时, 文件又按字母顺序在 "解决方案资源管理器" 中显示, 这就以为着生成解决方案编译 .cpp 文件时是按照 .cpp 文件的字母顺序来编译的.
(2) 重命名 .cpp 文件的名称
既然 .cpp 文件是根据字母顺序排序的, 编译时也遵循这个顺序的反序, 那么只要保证 CWSTAPI.cpp 文件最后编译就可以了, 而 base64.cpp 文件排在 CWSTAPI.cpp 文件的前面, 那么将 base64.cpp 文件重命名为 ZBase64.cpp, 并相应地将 base64.h 重命名为 ZBase64.h, 这样就改变了编译的顺序. 这样做有个不好的地方就是为了达到调整编译顺序的目的而去改变 .cpp 文件的名称, 这样的命名往往是违心的.
(3) 将 .cpp 移到其他文件夹中
VC 默认将 .cpp 文件放在 源文件中, 将 .h 文件放在 头文件中.
建立一个文件夹 Include, 将除了 CWSTAPI.h, CWSTAPI.cpp 文件的所有 .h 和 .cpp 文件都放在这个文件夹中, 即
- 源文件
- CWSTAPI.cpp
- 头文件
- CWSTAPI.h
- 资源文件
- Include
- base64.cpp
- base64.h
- IniFile.cpp
- IniFile.h
- md5.cpp
- md5.h
这个顺序需要重新打开解决方案才能看到, 这样编译顺序为 :
正在编译...
md5.cpp
IniFile.cpp
base64.cpp
CWSTAPI.cpp
4. 这个问题足足困扰了我大半天, 最后终于找到这几种解决方法, 并且分享给大家, 最终我采用了最后一种方法. 这些方法肯定不是最好的, 所以我想征集一下大家的方法.