我的一个朋友在创建DLL时会遇到一堆错误。 Visual Studio抱怨未解析的外部符号。我主要是一个Unix用户,所以我可能会在那里弄错。在Unix上,当您创建静态库(存档)时,它不会将不同的目标文件连接到存档文件中。我希望以相同的方式创建动态对象,但显然会发生额外的链接阶段。
第一个问题:为什么dll会有链接阶段?
在这种情况下,DLL indeeds包含未定义的符号,因为我们希望DLL在EXE文件中找到这些符号。这与典型的DLL行为完全相反,其中EXE使用DLL中定义的符号。为了清楚起见,我希望在DLL加载到内存中时能够找到这些符号。
第二个问题:如何让DLL使用EXE文件中定义的符号?
修改 我重新提出了这个问题,因为我认为我没有明确说明问题。
答案 0 :(得分:11)
您将问题的根源描述为:“我想让DLL从exe文件中导入一些符号”在评论中给Luchian Grigore的答案。您在问题的文本中另外写了“DLL要在EXE文件中找到这些符号。我们希望DLL在EXE文件中找到这些符号。”
主要是设计问题是否从exe中导出函数或数据。通常只能从DLL创建导出。如果EXE需要向DLL提供一些信息,则它通过参数提供信息。例如,您在EXE中调用了一些在DLL中实现和导出的函数MyFunc
。作为MyFunc
的附加参数,您可以获得context
指针,该指针可以直接或间接获取所需的EXE所有信息。
在某些情况下,您可以从EXE导出数据或函数。例如,您使用DumpBin.exe
实用程序(只需启动“Visual Studio命令提示符(2010)”即可使用它)来验证Outlook.exe导出
DumpBin.exe /exports "C:\Program Files\Microsoft Office\Office14\OUTLOOK.EXE"
File Type: EXECUTABLE IMAGE
Section contains the following exports for outlook.exe
00000000 characteristics
4E79B6C8 time date stamp Wed Sep 21 12:04:56 2011
0.00 version
1 ordinal base
66 number of functions
66 number of names
ordinal hint RVA name
1 0 00B58A88 CleanupAddressComponents
2 1 00B58A88 CleanupNameComponents
3 2 00228DC4 DllCanUnloadNow
4 3 004848F8 DllGetClassObject
...
65 40 0038EF30 UpdateContactTracker
66 41 00902788 dwIsLoggingEnabled
我可以解释如何在不进行长时间讨论的情况下实现方案,以及是否真的应该这样做。
首先,LIB文件包含OBJ文件,其格式为Program Executable (PE)。在编译期间,不同的公共部分将放在OBJ文件中。非常重要的是,程序可执行文件(EXE或DLL)不仅包含代码,还包含PE头部的许多附加信息。最重要的是
您可以使用DumpBin.exe
实用程序(只需启动“Visual Studio命令提示符(2010)”即可轻松使用它)。要查看有关标题的信息,您可以使用DumpBin.exe /headers my.exe
。要查看导出目录的包含,您可以使用DumpBin.exe /exports my.exe
等等。
如果编译导出某些函数或数据的DLL,则将另外创建LIB文件。它名为导入库。如果在EXE项目中使用LIB,它使用DLL中的某些函数或数据,链接器将解析外部引用和EXE导入目录中的位置有关应在加载时解析的函数的信息时间。
因此,导入库仅包含用于在EXE中填充“导入目录”和“导入地址表目录”的模板。
通常,可以以相同的方式从EXE导出一些函数数据,创建LIB,在DLL项目中使用LIB,以及实现从EXE导入DLL中的一些信息的方式。
我做了the demo project,证明了这一点。 如果您想从项目中删除所有LIB并自行创建,请仔细阅读我的答案末尾的编译说明。代码ExportFromExe.c
(EXE):
//#define CREATE_IMPORT_LIBRARY_ONLY
#include <Windows.h>
EXTERN_C __declspec(dllexport) int someData = 0;
EXTERN_C __declspec(dllexport) int __stdcall myFunc (int x);
EXTERN_C __declspec(dllexport) int __stdcall MyFunc();
int __stdcall myFunc (int x)
{
return x + 10;
}
#ifndef _DEBUG
int mainCRTStartup()
#else
int main()
#endif
{
someData = 5;
#ifndef CREATE_IMPORT_LIBRARY_ONLY
return MyFunc();
#endif
}
MyDll.c
的代码(DLL):
#include <Windows.h>
EXTERN_C __declspec(dllexport) int myData = 3;
EXTERN_C __declspec(dllimport) int someData;
EXTERN_C __declspec(dllimport) int __stdcall myFunc (int x);
#ifndef _DEBUG
EXTERN_C BOOL WINAPI _DllMainCRTStartup (HINSTANCE hinstDLL, DWORD fdwReason,
LPVOID lpvReserved)
#else
BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
#endif
{
if (fdwReason == DLL_PROCESS_ATTACH)
DisableThreadLibraryCalls(hinstDLL);
return TRUE;
UNREFERENCED_PARAMETER (lpvReserved);
}
EXTERN_C __declspec(dllexport) int WINAPI MyFunc()
{
return someData + myFunc(myData);
}
为了能够在第一时间成功创建项目我们必须解决问题:“谁是第一个:鸡还是鸡蛋?”因为EXE项目取决于MyDll.lib
,DLL项目取决于ExportFromExe.lib
。对于EXE的第一次编译,我们可以从EXE项目的链接器设置中删除$(OutDir)MyDll.lib
并定义CREATE_IMPORT_LIBRARY_ONLY
。结果我们将创建ExportFromExe.exe
和ExportFromExe.lib
。在更大的项目中,可以使用链接器的Undefined Symbol Only (/FORCE:UNRESOLVED)
选项代替。然后,我们可以构建MyDll
项目,该项目会创建MyDll.dll
和MyDll.lib
。现在,您可以从EXE中删除CREATE_IMPORT_LIBRARY_ONLY
并将$(OutDir)MyDll.lib
包含为链接器设置(“输入”部分设置中的“附加深度”)。 EXE项目的下一个版本将产生最终解决方案。
我使用了一些小技巧来删除C-Runtime并将EXE和DLL的大小减小到2,5或3 KB。因此,您可以使用/all
切换DumpBin.exe
来检查EXE和DLL包含RAW二进制数据的完整信息。
因为EXE返回结果为ERRORLEVEL,您可以在推荐提示中测试应用程序:
echo %ERRORLEVEL%
0
ExportFromExe.exe
echo %ERRORLEVEL%
18
答案 1 :(得分:2)
第一个问题:为什么dll存在链接阶段?
因为它是这样的。每次要创建二进制文件时都有一个链接阶段。这些符号需要以某种方式解决,对吧?
第二个问题:我该怎么做?
您将lib
旁边生成的dll
文件添加到项目中的Additional Dependencies
- 属性 - &gt;配置属性 - &gt;链接器 - &gt;输入强>
如果您还没有这样做,为了导出到lib
,必须使用_declspec(dllexport)
声明符号。当您包含标题时,您告诉编译器这些符号将使用_declspec(dllimport)
导入。