代码之家  ›  专栏  ›  技术社区  ›  c00000fd

如何静态链接到由序号导出的DLL函数?

  •  3
  • c00000fd  · 技术社区  · 7 年前

    例如,如果DLL具有函数:

    int TestFunc01(int v)
    {
        WCHAR buff[256];
        ::StringCchPrintf(buff, _countof(buff), L"You passed a value of %d", v);
        return ::MessageBox(NULL, buff, L"Test Dll Message", MB_OK | MB_ICONINFORMATION);
    }
    

    仅按其序号值导出的(以下是 .def 文件):

    LIBRARY   DllName
    EXPORTS
       TestFunc01   @1 NONAME
    

    现在,当我想从另一个模块静态链接到该函数时,如果该函数是按其名称导出的,我会执行以下操作:

    extern "C" __declspec(dllimport) int TestFunc01(int v);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestFunc01(123);
    }
    

    但我如何仅通过它的序数值静态链接到它?

    另外,我正在使用Visual Studio C++编译器&连接器

    3 回复  |  直到 7 年前
        1
  •  3
  •   RbMm    7 年前

    但我如何仅通过它的序数值静态链接到它?

    绝对相同的方式,当函数按名称导出时-没有任何区别。在这两种情况下,您都需要两件事-正确声明函数:

    extern "C" __declspec(dllimport) int /*calling convention*/ TestFunc01(int v);
    

    和lib文件,包括在链接器输入中。

    当您包括 somename。def公司 文件到visual studio项目,它会自动添加 /def:"somename.def" 链接器选项(否则需要手动添加此选项)。生成的lib文件将包含 __imp_*TestFunc01* 符号-就地*将根据 c c类++ 符号和呼叫约定 x86 .

    从另一方面来说,当调用函数时 __declspec(dllimport) 属性编译器( )将生成 call [__imp_*TestFunc01*] -so参考 __imp\u*TestFunc01* 符号(再次*就地实际装饰)。将搜索链接器 __imp\u*TestFunc01* 符号,并在lib文件中找到它。

    这个 NONAME 选项对该过程不重要-这只影响其形成方式 IAT/内景 中此函数的条目 体育课 (将按名称或序号导入)

    请注意,如果我们仅将generate lib file与def file分开,则 link.exe /lib /def:somename.def -链接器将没有正确的导出函数声明(def文件只包含名称,不调用约定和c或c++名称),因此它始终被视为符号 extern "C" __cdecl


    在具体情况下,在dll函数中实现为 int TestFunc01(int v) -所以没有 外部“C” -因此,lib文件中的符号类似 __imp_?TestFunc01@@YAHH@Z (我想 __cdecl公司 x86 ),但在与一起使用的另一个模块函数中 外部“C” -所以链接器将被搜索 __imp__TestFunc01 当然没有找到它,因为它不存在于lib文件中。因此,当我们导出/导入一些符号时,它必须是 相同的 为两个模块声明。最好单独申报 .h类 具有显式调用约定的文件

        2
  •  3
  •   SoronelHaetir    7 年前

    您可以使用lib。exe工具创建。来自的lib文件。您编写的DEF文件,实际上不必提供任何对象文件。

    你会写一个。与要导出的序号匹配的def,但也提供名称,然后使用lib。exe创建。lib。

    然后在代码中声明为: 外部“C”\u declspec(dllimport)ret\u type functname(arg\u type);

    调用约定必须与函数实际使用的内容相匹配,但名称必须与创建的内容相匹配。即使该名称不遵循该调用类型的常规修饰,lib也会使用。

        3
  •  0
  •   c00000fd    7 年前

    这不是为了与 actual answer . 大卫·赫弗南 在评论中提出了一个很好的观点,关于 dllimport . 所以我想做几个测试,看看当我使用 __declspec(dllimport) 如果我没有:

    1、x86版本

    DLL中的函数声明:

    extern "C" int __cdecl TestFunc01(int v)
    {
        WCHAR buff[256];
        ::StringCchPrintf(buff, _countof(buff), L"You passed a value of %d", v);
        return ::MessageBox(NULL, buff, L"Test Dll Message", MB_OK | MB_ICONINFORMATION);
    }
    

    然后从另一个模块导入并调用它:

    具有 __declspec(dllimport)

    extern "C" __declspec(dllimport) int __cdecl TestFunc01(int v);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestFunc01(123);
    }
    

    已编译机器代码:

    enter image description here

    这个 call 指令从 通道地址表 (IAT):

    enter image description here

    这为它提供了导入函数的位置:

    enter image description here


    没有 __declspec(dllimport)

    extern "C" int __cdecl TestFunc01(int v);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestFunc01(123);
    }
    

    已编译机器代码:

    enter image description here

    在这种情况下,它是相对的 呼叫 到单个 jmp 说明:

    enter image description here

    进而从IAT读取函数地址:

    enter image description here

    然后跳到它:

    enter image description here


    2、x64发布版本

    对于64位,我们必须将调用约定更改为 __fastcall . 其余保持不变:

    具有 __declspec(dllimport)

    extern "C" __declspec(dllimport) int __fastcall TestFunc01(int v);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestFunc01(123);
    }
    

    已编译机器代码:

    enter image description here

    再一次 呼叫 指令从IAT读取函数地址(如果是x64,则使用相对地址):

    enter image description here

    这为其提供了函数地址:

    enter image description here


    没有 __declspec(dllimport)

    extern "C" int __fastcall TestFunc01(int v);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestFunc01(123);
    }
    

    已编译机器代码:

    enter image description here

    又是一个亲戚 呼叫 到单个 jmp公司 :

    enter image description here

    然后从IAT读取函数地址:

    enter image description here

    然后跳到它:

    enter image description here

    结论

    如你所见,不使用 dllimport公司 从技术上讲,你会招致额外的 jmp公司 . 我不确定跳转的目的是什么,但它肯定不会让代码运行得更快。也许这是一种代码维护,也许这是一种方法 热修补 对于更新,可能是调试功能。因此,如果有人能说明这次跳跃的目的,我将很高兴听到。