创建DLL时未解析的外部符号

时间:2012-03-30 21:23:46

标签: c++ visual-studio dll linker

我的一个朋友在创建DLL时会遇到一堆错误。 Visual Studio抱怨未解析的外部符号。我主要是一个Unix用户,所以我可能会在那里弄错。在Unix上,当您创建静态库(存档)时,它不会将不同的目标文件连接到存档文件中。我希望以相同的方式创建动态对象,但显然会发生额外的链接阶段。

第一个问题:为什么dll会有链接阶段?

在这种情况下,DLL indeeds包含未定义的符号,因为我们希望DLL在EXE文件中找到这些符号。这与典型的DLL行为完全相反,其中EXE使用DLL中定义的符号。为了清楚起见,我希望在DLL加载到内存中时能够找到这些符号。

第二个问题:如何让DLL使用EXE文件中定义的符号?

修改 我重新提出了这个问题,因为我认为我没有明确说明问题。

2 个答案:

答案 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.exeExportFromExe.lib。在更大的项目中,可以使用链接器的Undefined Symbol Only (/FORCE:UNRESOLVED)选项代替。然后,我们可以构建MyDll项目,该项目会创建MyDll.dllMyDll.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)导入。