我需要将一组符号从一个DLL转发到另一个DLL(如果你想知道的话,支持一些版本控制方案PEP 384)。它适用于各种功能;我写了一个模块定义文件,说
LIBRARY "python3"
EXPORTS
PyArg_Parse=python32.PyArg_Parse
PyArg_ParseTuple=python32.PyArg_ParseTuple
PyArg_ParseTupleAndKeywords=python32.PyArg_ParseTupleAndKeywords
[...]
然而,数据失败了。如果我说
PyBaseObject_Type=python32.PyBaseObject_Type
然后链接器抱怨PyBaseObject_Type是一个未解析的符号,即使它实际上是从python32.dll导出的。看一下导入库,我注意到,对于数据,只有_imp__
符号,所以我试过
PyBaseObject_Type=python32._imp__PyBaseObject_Type
链接器现在实际上创建了一个DLL,但是,在此DLL中,转发转到_imp__
符号,然后在运行时无法解析。我也尝试将DATA放入行中(有或没有_imp__
);这没有什么区别。
IIUC,转发数据应该可以正常工作,因为对于DLL的任何导入器,数据都被声明为__declspec(dllimport)
,因此编译器应该正确解释引用。
那么:我如何生成一个执行数据转发的DLL?
答案 0 :(得分:6)
在我看来,在从主DLL(保存数据的DLL)导出数据时,解决方案不时使用DATA。
要重现我的意思,您可以使用DllDataForward.c创建一个项目:
#include <Windows.h>
EXTERN_C __declspec(dllexport) int myData = 5;
#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) BOOL WINAPI MyFunc()
{
return TRUE;
}
和DllDataForward.def:
LIBRARY "DllDataForward"
EXPORTS
myData
MyFunc
通常会使用“myData DATA”而不是“myData”。
然后你可以创建一个ForwardingDll.c:
#include <Windows.h>
#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);
}
使用ForwardingDll.def:
LIBRARY "ForwardingDll"
EXPORTS
myNewData=DllDataForward.myData DATA
MyNewFunc=DllDataForward.MyFunc
在构建ForwardingDll.dll期间,应该包含在编译DllDataForward期间创建的导入库DllDataForward.lib作为链接器的输入。这样的导入库可以成功使用,您将收到ForwardingDll.dll。
dumpbin.exe ForwardingDll.dll /EXPORTS
产生输出
...
ordinal hint RVA name
1 0 MyNewFunc (forwarded to DllDataForward.MyFunc)
2 1 myNewData (forwarded to DllDataForward.myData)
...
使用DllDataForward.lib构建一个简单的测试应用程序,只有源test.c:
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
EXTERN_C __declspec(dllimport) int myNewData;
EXTERN_C __declspec(dllimport) BOOL WINAPI MyNewFunc();
int main()
{
BOOL isSuccess = MyNewFunc();
int i=myNewData;
_tprintf (TEXT("i=%d\nisSuccess=%s\n"),
i, isSuccess? TEXT("TRUE"): TEXT("FALSE"));
}
产生输出
i=5
isSuccess=TRUE
更新:我想在DEF文件中添加更多信息为什么使用“myData DATA”而不是“myData”做一招并如何使用我建议的现有DLL 的技巧,如python32.dll ,没有python32.dll的任何更改,也没有重新编译。我将展示原始python32.lib错过了所有数据变量的导出,如PyBaseObject_Type
。我将展示如何创建一个额外的python32.lib ,它具有我们需要的数据符号。
首先,我希望在DEF文件中从“myData DATA”更改为“myData”后,清除导入库中的所需更改。首先让我们使用带有“myData DATA”的DEF文件编译DllDataForward.dll,然后查看导入库DllDataForward.LIB的内部:
dumpbin.exe DllDataForward.lib /all >%TEMP%\DllDataForward-lib.txt
notepad %TEMP%\DllDataForward-lib.txt
我们将看到lib具有 6 公共符号:
224 __IMPORT_DESCRIPTOR_DllDataForward
46A __NULL_IMPORT_DESCRIPTOR
5A8 DllDataForward_NULL_THUNK_DATA
776 __imp__myData
708 _MyFunc@0
708 __imp__MyFunc@0
接下来将DEF文件从“myData DATA”更改为“myData”,创建dll和导入库并再次查看它。我们将看到导入库现在有 7 (!!!)而不是6个公共符号:
23A __IMPORT_DESCRIPTOR_DllDataForward
480 __NULL_IMPORT_DESCRIPTOR
5BE DllDataForward_NULL_THUNK_DATA
78C __imp__myData
78C _myData
71E _MyFunc@0
71E __imp__MyFunc@0
因此,我们在使用具有“myData DATA”的DEF文件时遇到问题,因为创建的导入库不包含公共符号_myData
。
我们可以继续使用具有“myData DATA”的正确的 DLL,并创建另外的第二个导入库,手动导出_myData
。我们不会对DllDataForward.dll进行任何更改,只需手动创建和使用其他库。
为此,我们转储DllDataForward.dll的导出dumpbin.exe DllDataForward.dll /exports
。我们将看到:
...
ordinal hint RVA name
1 0 00001020 MyFunc = _MyFunc@0
2 1 00003000 myData = _myData
...
现在我们在另一个目录中创建新的DllDataForward.def 文件,仅基于dumpbin.exe DllDataForward.dll /exports
的输出:
LIBRARY "DllDataForward"
EXPORTS
myData = _myData
接下来使用命令
lib.exe /DEF:DllDataForward.def /OUT:DllDataForward.lib /MACHINE:X86
我们创建第二个DllDataForward.lib
(在另一个目录中创建原始DllDataForward.lib
)。现在我们可以使用两个 DllDataForward.lib
编译ForwardingDll.dll 并接收我们需要的DLL。 Test.exe将显示批准工作。
正如我们从当前版本3.2a3中检查python32.lib一样:
dumpbin.exe "C:\Program Files\Python32\libs\python32.lib" /all >python32-lib.txt
notepad python32-lib.txt
我们将找到以下行(约在文件的开头)
1957 public symbols
…
1BCCC _PyArg_Parse
1BCCC __imp__PyArg_Parse
…
1BFF6 __imp__PyBaseObject_Type
…
我们也可以用
验证dumpbin C:\Windows\system32\python32.dll /exports >%TEMP%\python32-exports.txt
notepad %TEMP%\python32-exports.txt
符号PyBaseObject_Type
将导出为
14 D 001DD5D0 PyBaseObject_Type
因此我们可以从python32.def文件
创建额外的python32.libLIBRARY "python32"
EXPORTS
PyBaseObject_Type
使用
lib /DEF:python32.def /OUT:python32.lib /MACHINE:X86
现在您可以定义dll的DEF
LIBRARY "python3"
EXPORTS
PyArg_Parse=python32.PyArg_Parse
PyArg_ParseTuple=python32.PyArg_ParseTuple
PyArg_ParseTupleAndKeywords=python32.PyArg_ParseTupleAndKeywords
PyBaseObject_Type=python32.PyBaseObject_Type DATA
与您最初想要的完全相同,但我们将使用 “C:\ Program Files \ Python32 \ libs \ python32.lib”以及我们在链接期间创建的第二个小python32.lib。 / p>
我自己不使用python而且不知道PyBaseObject_Type
的大小,但如果我将其声明为int
EXTERN_C __declspec(dllimport) int PyBaseObject_Type;
我可以验证PyBaseObject_Type
的第一部分是1.它有效!
对于所有读到我所有答案直到这个地方的人来说,感谢很长时间的回答。
答案 1 :(得分:2)
我玩了一下这个,但无法找到与.def文件一起使用的正确组合。但是,我能够通过转发DLL源中的以下编译指示转发函数和数据:
#pragma comment(linker,"/export:_data=org.data,DATA")
#pragma comment(linker,"/export:_func=org.func")
注意我必须使用修饰的数据和函数名称。
以下是完整示例的文件:
int data = 5;
int func(int a)
{
return a * 2;
}
EXPORTS
data DATA
func
#pragma comment(linker,"/export:_data=org.data,DATA")
#pragma comment(linker,"/export:_func=org.func")
int func2(int a)
{
return a + 2;
}
EXPORTS
func2
#include <stdio.h>
__declspec(dllimport) int data;
__declspec(dllimport) int func(int a);
__declspec(dllimport) int func2(int a);
int main()
{
printf("data=%d func(5)=%d func2(5)=%d\n",data,func(5),func2(5));
return 0;
}
all: example.exe org.dll
example.exe: example.c fwd.dll
cl /W4 example.c /link fwd.lib
org.dll: org.c
cl /LD /W4 org.c org.def
fwd.dll: fwd.c
cl /LD /W4 fwd.c fwd.def
clean:
del *.exe *.dll *.obj *.exp *.lib
答案 2 :(得分:0)
这个答案略有不同,没有.def文件和转发的__stdcall函数,这与情况更相似。请注意,要导出stdcall名称,=
中/export
的两侧都需要装饰名称。它还有一个发烧友#include "fwd.h"
,因此可以在DLL和客户端EXE中重复使用。
#define ORGAPI __declspec(dllexport)
ORGAPI int data = 5;
ORGAPI int __stdcall func(int a)
{
return a * 2;
}
#define FWDEXPORTS
#include "fwd.h"
int func2(int a)
{
return a + 2;
}
#pragma once
#ifdef FWDEXPORTS
#define FWDAPI __declspec(dllexport)
// forwarded APIs exported here
#pragma comment(linker,"/export:_func@4=org._func@4")
#pragma comment(linker,"/export:_data=org.data")
#else
#define FWDAPI __declspec(dllimport)
// forwarded APIs imported here
FWDAPI int __stdcall func(int a);
FWDAPI int data;
#endif
// APIs unique to forwarding DLL here
FWDAPI int func2(int a);
#include <stdio.h>
#include "fwd.h"
int main()
{
printf("data=%d func(5)=%d func2(5)=%d\n",data,func(5),func2(5));
return 0;
}
all: example.exe org.dll
example.exe: example.c fwd.h fwd.dll
cl /nologo /W4 example.c /link /nologo fwd.lib
org.dll: org.c
cl /nologo /LD /W4 org.c /link /nologo
fwd.dll: fwd.c fwd.h
cl /nologo /LD /W4 fwd.c /link /nologo
clean:
del *.exe *.dll *.obj *.exp *.lib
答案 3 :(得分:0)
这实际上是MS链接器中的一个错误。但有2种解决方法。
背景:dll的导入库包含小块数据,这些数据包含 imp__FooBar等符号,其中FooBar是导出的数据函数的名称。当导入模块已宣布数据或函数中使用__declspec(dllimport的),编译器会自动引用这些__imp 强> *符号和创建间接函数调用(呼叫DWORD PTR [<强> imp__FooBar])或存储器的引用。如果未使用dllimport声明函数,它仍然有效,因为对于函数导出,库包含不带_ imp 前缀的附加符号。这些符号属于小代码存根,由间接跳转指令组成(“FooBar:jmp dword ptr [ _imp__FooBar]”)。在x86上,符号将具有前导下划线前缀和可能的stdcall装饰,但这并不重要。
现在到链接器错误:链接器要求您要转发的符号可用。这是有道理的,因为在构建转发dll时需要链接目标dll的导入库,它可以防止生成错误的导出。但实际上链接器采用快捷方式并且不检查目标符号是否在右侧dll中,但它仅检查符号是否可用。该符号未链接到dll。现在真正的错误是链接器检查FooBar而不是_ imp _FooBar,它应该是,因为后者是实际的导入符号,前者只是函数导出的便利“插件”。
现在可能的2个解决方案。已经提到过一个:创建一个假装导出函数而不是数据的新导入库。唯一的区别是生成的导入库创建了一个额外的间接jmp指令存根,它跳转到导出的数据。除了提供链接器所需的符号之外,该指令没有任何用途。如前所述,此存根不会链接到dll。
第二种解决方案甚至不需要创建导入库,在某些情况下可能更简单。您只需将符号添加到转发dll即可。它可以是任何一种符号。 “char FooBar;”够好了。当您的导出文件包含“FooBar = otherdll.FooBar DATA”时,dll将有一个导出FooBar,它会重定向到otherdll.dll,不会使用forwardng dll中的符号。