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

时间:2018-01-01 06:04:42

标签: c++ visual-studio winapi dll dllimport

说,如果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);
}

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

PS。我正在使用Visual Studio C ++编译器&接头

3 个答案:

答案 0 :(得分:3)

您可以使用lib.exe工具从您编写的.DEF文件创建.lib文件,而不必实际提供任何目标文件。

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

在代码中,您将其声明为: extern“C”__ declspec(dllimport)ret_type funcName(arg_type);

调用约定必须与实际使用的函数匹配,但名称必须与您创建的.lib使用的名称相匹配,即使该名称不遵循该调用类型的装饰法线。

答案 1 :(得分:3)

  

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

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

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)属性。编译器( CL )将生成call [__imp_*TestFunc01*] - 因此引用__imp_*TestFunc01*符号(再次*实际装饰)。链接器将搜索__imp_*TestFunc01*符号并在lib文件中找到它。

NONAME选项对此过程无关紧要 - 这仅影响 PE 中此函数的 IAT / INT 条目的形成方式(将按名称或序数导入)

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

在具体情况下可见,在dll函数中实现为int TestFunc01(int v) - 所以没有extern "C" - 因为lib文件中的结果将是__imp_?TestFunc01@@YAHH@Z的符号(我假设 __ cdecl x86 ),但在与 extern "C" 一起使用的另一个模块函数中 - 所以链接器将搜索__imp__TestFunc01,当然找不到它,因为它不存在于lib文件中。因为这样,当我们导出/导入一些符号时 - 必须为两个模块声明相等。最佳声明在单独的 .h 文件中使用显式调用约定

答案 2 :(得分:-1)

这不是为了与actual answer竞争。 David Heffernan 在那里的评论中提出了一个关于使用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

在这种情况下,它是单call条指令的相对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

call指令再次从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

再一次call到一个jmp

enter image description here

反过来从IAT读取函数地址:

enter image description here

然后跳到它:

enter image description here

结论

如您所见,不使用dllimport,您在技术上会产生额外的jmp。我不确定跳转的目的是什么,但肯定不会让你的代码运行得更快。也许这是一个代码维护的事情,也许这是一种方法来进行 hot-patching 更新,也许是一个调试功能。因此,如果有人可以了解跳跃的目的,我会很高兴听到它。