Пересылка данных в DLL - PullRequest
5 голосов
/ 30 октября 2010

Мне нужно переслать набор символов из одной 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, пересылка данных должна работать нормально, поскольку данные объявлены как __declspec(dllimport) для любого импортера DLL, поэтому компилятор должен правильно интерпретировать ссылку.

Итак: Как я могу сгенерировать DLL, которая выполняет пересылку данных?

Ответы [ 4 ]

6 голосов
/ 02 ноября 2010

Мне кажется, что решением является , а не , чтобы использовать DATA при экспорте данных из основной DLL (DLL, в которой хранятся данные).

Чтобы воспроизвести то, что я имею в виду, вы можете создать проект, имеющий 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» будет использоваться «myData DATA».

Затем вы можете создать 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

Вы должны включить библиотеку импорта DllDataForward.lib, созданную во время компиляции DllDataForward, в качестве входных данных для компоновщика при сборке ForwardingDll.dll. Такая библиотека импорта может быть успешно использована, и вы получите 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

ОБНОВЛЕНО : я хочу добавить немного больше информации , почему использование "myData DATA" вместо "myData" в файле DEF помогает, и как использовать трюк, который я предлагаю с существующей DLL , такой как python32.dll без каких-либо изменений в python32.dll и без перекомпиляции . Я покажу, что исходный файл python32.lib пропускает экспорт всех переменных данных, таких как PyBaseObject_Type. Я покажу, как вы можете создать дополнительный python32.lib , в котором есть символы с данными, которые нам нужны.

Прежде всего я хочу отменить изменения, которые мы вносим в библиотеку импорта после перехода с «myData DATA» на «myData» в файле DEF. Сначала давайте скомпилируем DllDataForward.dll с файлом DEF, имеющим «myData DATA», и посмотрим внутрь библиотеки импорта DllDataForward.LIB:

dumpbin.exe DllDataForward.lib /all >%TEMP%\DllDataForward-lib.txt
notepad %TEMP%\DllDataForward-lib.txt

Мы увидим, что библиотека содержит 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

Таким образом, у нас возникла проблема с использованием файла DEF с «myData DATA», поскольку созданная библиотека импорта не содержит общедоступного символа _myData.

Мы можем остаться с правильной DLL, имеющей «myData DATA», и создать дополнительную вторую библиотеку импорта , которая экспортирует _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). Теперь мы можем скомпилировать ForwardingDll.dll , используя два DllDataForward.lib и получить DLL, которая нам нужна. Test.exe покажет, что подход работает.

Точно так же мы рассматриваем python32.lib из текущей версии 3.2a3:

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.lib из файла python32.def

LIBRARY "python32"
EXPORTS
    PyBaseObject_Type

с помощью

lib /DEF:python32.def /OUT:python32.lib /MACHINE:X86

Теперь вы можете определить DEF вашей DLL

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, который мы создали во время компоновки.

Я сам не использую python и не знаю размер PyBaseObject_Type, но если я объявлю его как int

EXTERN_C __declspec(dllimport) int PyBaseObject_Type;

Я могу убедиться, что первая часть PyBaseObject_Type равна 1. Это работает!

Извините за длинный ответ и спасибо всем, кто прочитал весь мой ответ до этого места.

2 голосов
/ 07 ноября 2010

Я немного поигрался с этим и не смог найти правильную комбинацию, которая работала бы с файлом .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;
}

makefile

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
0 голосов
/ 23 февраля 2013

На самом деле это ошибка в компоновщике MS. Но есть 2 обходных пути.

Справочная информация: библиотека импорта для DLL содержит небольшие порции данных, которые имеют такие символы, как imp__FooBar, где FooBar - это имя экспортируемой функции данных. Когда импортирующий модуль объявил данные или функцию, используя __declspec (dllimport), компилятор автоматически ссылается на эти символы __imp * и создает косвенные вызовы функций (call dword ptr [ imp__FooBar]) или ссылки на память. Если функция не была объявлена ​​с помощью dllimport, она все еще работает, поскольку для экспорта функций библиотека содержит все дополнительные символы без префикса _ imp . Эти символы принадлежат небольшим заглушкам кода, состоящим из команды косвенного перехода («FooBar: jmp dword ptr [ _imp__FooBar]»). На x86 символы будут иметь начальный префикс подчеркивания и, возможно, украшение stdcall, но это не важно.

Теперь об ошибке компоновщика: компоновщик требует наличия символа, который вы хотите переслать. Это имеет смысл, так как вам нужно связать библиотеку импорта целевой dll при построении dll пересылки, и это предотвращает создание неправильного экспорта. Но на самом деле компоновщик использует ярлык и не проверяет, находится ли целевой символ в правой dll, а только проверяет, доступен ли символ. Символ не попадает в DLL. Теперь настоящая ошибка заключается в том, что компоновщик проверяет FooBar, а не _ _ 1011 * imp _FooBar, что и должно быть, поскольку последний является действительным символом импорта, а первый - лишь вспомогательным «дополнением» для экспорта функций.

Теперь о возможных 2 решениях. Одно уже упоминалось: создайте новую библиотеку импорта, которая претендует на экспорт функции вместо данных. Единственное отличие состоит в том, что сгенерированная библиотека импорта создает дополнительную косвенную заглушку инструкции jmp, которая переходит к экспортируемым данным. Эта инструкция не имеет никакой цели, кроме предоставления символа, который необходим компоновщику. Как упоминалось ранее, эта заглушка не связана с DLL.

Второе решение даже не требует создания библиотеки импорта и может быть проще в некоторых случаях. Вы просто добавляете символ к вашей пересылке DLL. Это может быть любой символ. "char FooBar;" это достаточно хорошо. Когда ваш файл экспорта содержит «FooBar = otherdll.FooBar DATA», DLL будет иметь экспорт FooBar, который перенаправляет на otherdll.dll, символ в DLL forwardward не будет использоваться.

0 голосов
/ 08 ноября 2010

Этот ответ немного отличается, без файлов .def и перенаправленной функции __stdcall, которая больше похожа на ситуацию.Обратите внимание, что для экспорта имени stdcall, оформленное имя было необходимо по обе стороны от = в /export.У него также есть более приятный #include "fwd.h", поэтому он может быть повторно использован в DLL и клиентском EXE.* 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;
}

makefile

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
...