说,如果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 ++编译器&接头
答案 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)
时我做了什么,当我不使用时:
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);
}
编译机器代码:
call
指令从导入地址表(IAT)中读取函数地址:
给它导入函数的位置:
__declspec(dllimport)
extern "C" int __cdecl TestFunc01(int v);
int _tmain(int argc, _TCHAR* argv[])
{
TestFunc01(123);
}
编译机器代码:
在这种情况下,它是单call
条指令的相对jmp
:
反过来从IAT读取函数地址:
然后跳到它:
对于64位,我们必须将调用约定更改为__fastcall
。其余的保持不变:
__declspec(dllimport)
extern "C" __declspec(dllimport) int __fastcall TestFunc01(int v);
int _tmain(int argc, _TCHAR* argv[])
{
TestFunc01(123);
}
编译机器代码:
call
指令再次从IAT读取函数地址(如果x64使用相对地址):
给它函数地址:
__declspec(dllimport)
extern "C" int __fastcall TestFunc01(int v);
int _tmain(int argc, _TCHAR* argv[])
{
TestFunc01(123);
}
编译机器代码:
再一次call
到一个jmp
:
反过来从IAT读取函数地址:
然后跳到它:
如您所见,不使用dllimport
,您在技术上会产生额外的jmp
。我不确定跳转的目的是什么,但肯定不会让你的代码运行得更快。也许这是一个代码维护的事情,也许这是一种方法来进行 hot-patching 更新,也许是一个调试功能。因此,如果有人可以了解跳跃的目的,我会很高兴听到它。