转发DLL中的数据

时间:2010-10-30 18:42:54

标签: dll linker

我需要将一组符号从一个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?

4 个答案:

答案 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.lib
LIBRARY "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")

注意我必须使用修饰的数据和函数名称。

以下是完整示例的文件:

org.c

int data = 5;
int func(int a)
{
    return a * 2;
}

org.def

EXPORTS
    data DATA
    func

fwd.c

#pragma comment(linker,"/export:_data=org.data,DATA")
#pragma comment(linker,"/export:_func=org.func")

int func2(int a)
{
    return a + 2;
}

fwd.def

EXPORTS
    func2

example.c

#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中重复使用。

org.c

#define ORGAPI __declspec(dllexport)

ORGAPI int data = 5;
ORGAPI int __stdcall func(int a)
{
    return a * 2;
}

fwd.c

#define FWDEXPORTS
#include "fwd.h"

int func2(int a)
{
    return a + 2;
}

fwd.h

#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);

example.c

#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中的符号。