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

问题描述 投票:3回答:3

比方说,如果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 ++编译器和链接器。

c++ visual-studio winapi dll dllimport
3个回答
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将生成的lib文件与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文件中声明它


3
投票

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

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

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

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


-1
投票

这不是为了与actual answer竞争。 David Heffernan在那里的评论中提出了一个很好的观点,关于使用dllimport。所以我想做几次测试,看看当我使用__declspec(dllimport)和不使用时它会有什么不同:

1. x86 release build

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 release build

对于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

Conclusion

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

© www.soinside.com 2019 - 2024. All rights reserved.