Экспортировать все символы при создании DLL - PullRequest
62 голосов
/ 22 октября 2008

В VS2005 я хочу создать DLL и автоматически экспортировать все символы без добавления везде __declspec (dllexport) и без создания файлов .def вручную. Есть ли способ сделать это?

Ответы [ 6 ]

37 голосов
/ 22 октября 2008

Это можно сделать ...

Здесь мы используем параметр / DEF компоновщика для передачи «файла определения модуля» , содержащего список наших экспортов. По вашему вопросу я вижу, что вы знаете об этих файлах. Однако мы не делаем это вручную. Сам список экспорта создается командой dumpbin / LINKERMEMBER и управляет выводом с помощью простого сценария в формате файла определения модуля.

Настройка требует много работы, но она позволяет нам компилировать код, созданный без объявлений dllexport для Unix в Windows.

29 голосов
/ 29 августа 2015

Краткий ответ

Вы можете сделать это с помощью новой версии CMake (любая версия cmake-3.3.20150721-g9cd2f-win32-x86.exe или выше).

В настоящее время он находится в ветке dev. Позже эта функция будет добавлена ​​в релизную версию cmake-3.4.

Ссылка на cmake dev:

cmake_dev

Ссылка на статью, описывающую технику:

Создание библиотек Windows без declspec () с использованием новой функции экспорта всех CMake

Ссылка на пример проекта:

cmake_windows_export_all_symbols


Длинный ответ

Внимание: Вся информация ниже относится к компилятору MSVC или Visual Studio.

Если вы используете другие компиляторы, такие как gcc в Linux или MinGW gcc-компилятор в Windows, у вас не будет ошибок компоновки из-за неэкспортированных символов, поскольку компилятор gcc по умолчанию экспортирует все символы в динамическую библиотеку (dll) вместо MSVC или Windows-компиляторы Intel.

В Windows вы должны явно экспортировать символ из DLL.

Более подробную информацию об этом можно получить по ссылкам:

Экспорт из DLL

HowTo: экспорт классов C ++ из DLL

Итак, если вы хотите экспортировать все символы из dll с помощью MSVC (компилятор Visual Studio), у вас есть два варианта:

  • Используйте ключевое слово __declspec (dllexport) в определении класса / функции.
  • Создайте файл определения модуля (.def) и используйте файл .def при сборке DLL.

1. Используйте ключевое слово __declspec (dllexport) в определении класса / функции


1,1. Добавьте макросы «__declspec (dllexport) / __declspec (dllimport)» к классу или методу, который вы хотите использовать. Поэтому, если вы хотите экспортировать все классы, вы должны добавить эти макросы ко всем из них

Более подробную информацию об этом можно получить по ссылке:

Экспорт из DLL с использованием __declspec (dllexport)

Пример использования (заменить «Проект» на реальное название проекта):

// ProjectExport.h

#ifndef __PROJECT_EXPORT_H
#define __PROJECT_EXPORT_H

#ifdef USEPROJECTLIBRARY
#ifdef  PROJECTLIBRARY_EXPORTS 
#define PROJECTAPI __declspec(dllexport)
#else
#define PROJECTAPI __declspec(dllimport)
#endif
#else
#define PROJECTAPI
#endif

#endif

Затем добавьте "PROJECTAPI" ко всем классам. Определите «USEPROJECTLIBRARY» только если вы хотите экспортировать / импортировать символы из dll. Определите "PROJECTLIBRARY_EXPORTS" для DLL.

Пример экспорта класса:

#include "ProjectExport.h"

namespace hello {
    class PROJECTAPI Hello {}   
}

Пример экспорта функции:

#include "ProjectExport.h"

PROJECTAPI void HelloWorld();

Внимание: не забудьте включить файл "ProjectExport.h".


1,2. Экспорт в функции C. Если вы используете компилятор C ++ для написания кода компиляции на C, вы можете добавить extern "C" перед функцией, чтобы исключить искажение имени

Более подробную информацию об искажении имен в C ++ можно получить по ссылке:

Название Украшение

Пример использования:

extern "C" __declspec(dllexport) void HelloWorld();

Более подробную информацию об этом можно получить по ссылке:

Экспорт функций C ++ для использования в исполняемых файлах языка C


2. Создайте файл определения модуля (.def) и используйте файл .def при сборке DLL

Более подробную информацию об этом можно получить по ссылке:

Экспорт из DLL с использованием файлов DEF

Далее я опишу три подхода к созданию файла .def.


2,1. Функции экспорта C

В этом случае вы можете просто добавить объявления функций в файл .def вручную.

Пример использования:

extern "C" void HelloWorld();

Пример файла .def (соглашение об именах __cdecl):

EXPORTS 
_HelloWorld

2,2. Экспорт символов из статической библиотеки

Я попробовал подход, предложенный "user72260".

Он сказал:

  • Во-первых, вы можете создать статическую библиотеку.
  • Затем используйте «dumpbin / LINKERMEMBER» для экспорта всех символов из статической библиотеки.
  • Разобрать вывод.
  • Поместить все результаты в файл .def.
  • Создать dll с файлом .def.

Я использовал этот подход, но не всегда удобно создавать две сборки (одну как статическую, а другую как динамическую библиотеку). Однако я должен признать, что этот подход действительно работает.


2,3. Экспорт символов из файлов .obj или с помощью CMake


2.3.1. С использованием CMake

Важное замечание: Вам не нужны макросы экспорта в классы или функции!

Важное замечание: Вы не можете использовать / GL ( Оптимизация всей программы ) при использовании этого подхода!

  • Создать проект CMake на основе файла "CMakeLists.txt".
  • Добавьте следующую строку в файл «CMakeLists.txt»: установлено (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
  • Затем создайте проект Visual Studio с помощью «CMake (cmake-gui)».
  • Скомпилируйте проект.

Пример использования:

Корневая папка

CMakeLists.txt (корневая папка)

cmake_minimum_required(VERSION 2.6)
project(cmake_export_all)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

set(dir ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${dir}/bin")

set(SOURCE_EXE main.cpp)

include_directories(foo)

add_executable(main ${SOURCE_EXE})

add_subdirectory(foo)

target_link_libraries(main foo)

main.cpp (корневая папка)

#include "foo.h"

int main() {
    HelloWorld();

    return 0;
}

Папка Foo (корневая папка / папка Foo)

CMakeLists.txt (папка Foo)

project(foo)

set(SOURCE_LIB foo.cpp)

add_library(foo SHARED ${SOURCE_LIB})

foo.h (папка Foo)

void HelloWorld();

foo.cpp (папка Foo)

#include <iostream>

void HelloWorld() {
    std::cout << "Hello World!" << std::endl;
}

Снова ссылка на пример проекта:

cmake_windows_export_all_symbols

CMake использует подход, отличный от подхода «2.2. Экспорт символов из статической библиотеки».

Это делает следующее:

1) Создайте файл "objects.txt" в каталоге сборки с информацией о том, что файлы .obj используются в dll.

2) Скомпилируйте dll, то есть создайте .obj файлы.

3) На основе информации файла "objects.txt" извлечь все символы из файла .obj.

Пример использования:

DUMPBIN /SYMBOLS example.obj > log.txt

Подробнее об этом можно узнать по ссылке:

/ СИМВОЛЫ

4) Извлечь из файла .obj информацию.

По моему мнению, я бы использовал конвекцию вызова, например, поле «__cdecl / __ fastcall», «SECTx / UNDEF» (третий столбец), поле «External / Static» (пятый столбец), «??» "?" информация для анализа файлов .obj.

Я не знаю, как именно CMake анализирует файл .obj. Тем не менее, CMake является открытым исходным кодом, так что вы можете узнать, заинтересован ли он для вас.

Ссылка на проект CMake:

CMake_github

5) Поместить все экспортированные символы в файл .def.

6) Связать DLL с использованием файла .def.

Шаги 4) -5), то есть анализ файлов .obj и создание файла .def перед компоновкой и использованием файла .def, который CMake делает с помощью «события Pre-Link». Пока срабатывает «событие Pre-Link», вы можете вызывать любую программу, какую захотите. Поэтому в случае «использования CMake» «Событие Pre-Link» вызовите CMake со следующей информацией о том, куда поместить файл .def и где находится файл «objects.txt» и с аргументом «-E __create_def». Вы можете проверить эту информацию, создав проект CMake Visusal Studio с параметром «set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)», а затем проверьте файл проекта «.vcxproj» для dll.

Если вы попытаетесь скомпилировать проект без «set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)» или с «set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)», вы получите ошибки компоновки из-за того, что символы не экспортируются из dll. * 1252

Более подробную информацию об этом можно получить по ссылке:

Понимание пользовательских шагов сборки и событий сборки


2.3.2. Без использования CMake

Вы можете самостоятельно создать небольшую программу для анализа файла .obj без использования CMake. Однако я должен признать, что CMake - очень полезная программа, особенно для кроссплатформенной разработки.

8 голосов
/ 09 апреля 2009

Я написал небольшую программу для анализа вывода команды "dumpbin / linkermember" в файле .lib. У меня более 8000 ссылок на функции для экспорта из одной DLL.

Проблема с выполнением этого в DLL заключается в том, что вам нужно связать DLL без экспортированных определений один раз, чтобы создать файл .lib, а затем сгенерировать .def, что означает, что теперь вам нужно снова связать DLL с .def файл, чтобы фактически экспортировать ссылки.

Работать со статическими библиотеками стало проще. Скомпилируйте все ваши источники в статические библиотеки, запустите dumbin, сгенерируйте файл .def с помощью вашей маленькой программы, а затем свяжите библиотеки вместе в DLL, когда имена экспорта доступны.

К сожалению, моя компания не позволит мне показать вам источник. Работа заключается в распознавании того, какие «публичные символы» в выводе дампа не нужны в вашем файле def. Вы должны отбросить множество этих ссылок, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp * и т. Д.

2 голосов
/ 18 апреля 2018

Спасибо @Maks за подробный ответ .

Ниже приведен пример того, что я использовал в событии Pre-Link для генерации файла def из obj. Я надеюсь, что это будет полезно для кого-то.

dumpbin /SYMBOLS $(Platform)\$(Configuration)\mdb.obj | findstr /R "().*External.*mdb_.*" > $(Platform)\$(Configuration)\mdb_symbols
(echo EXPORTS & for /F "usebackq tokens=2 delims==|" %%E in (`type $(Platform)\$(Configuration)\mdb_symbols`) do @echo  %%E) > $(Platform)\$(Configuration)\lmdb.def

По сути, я просто взял один из объектов (mdb.obj) и grepped mdb_ * функции. Затем проанализировал вывод, чтобы сохранить только имена с учетом количества пробелов для отступа (один после разбиения на токены и другой в эхо. Хотя я не знаю, имеет ли это значение).

Реальный сценарий, вероятно, будет немного сложнее.

0 голосов
/ 07 января 2019

Я хочу создать DLL и автоматически экспортировать все символы без добавления везде __declspec (dllexport) и без создания файлов .def вручную. Есть ли способ сделать это?

Это поздний ответ, но он содержит подробности ответа Макса в Разделе (2). Он также избегает скриптов и использует программу на C ++ под названием dump2def. Исходный код dump2def ниже.

Наконец, нижеприведенные шаги предполагают, что вы работаете с Visual Studio Developer Prompt , который является терминалом Windows, в котором запущен vcvarsall.bat. Необходимо убедиться, что инструменты сборки, такие как cl.exe, lib.exe, link.exe и nmake.exe, находятся в пути.

Более подробную информацию об этом можно получить по ссылке:

Экспорт из DLL с использованием DEF Файлы
...

Инструкция ниже:

  • static.lib - статический архив библиотеки (* .a файл в Linux)
  • dynamic.dll - динамическая библиотека (* .so файл в Linux)
  • import.lib - динамическая библиотека (библиотека импорта в Windows)

Также обратите внимание, что, хотя вы экспортируете все из DLL, клиенты все равно должны использовать declspec(dllimport) для всех символов (классов, функций и данных), которые они используют. Также смотрите на MSDN.

Сначала возьмите свои объекты и создайте статический архив:

AR = lib.exe
ARFLAGS = /nologo

CXX_SRCS = a.cpp b.cpp c.cpp ...
LIB_OBJS = a.obj b.obj c.obj ...

static.lib: $(LIB_OBJS)
    $(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@

Во-вторых, запустите dumpbin.exe /LINKERMEMEBER в архиве, чтобы создать файл *.dump:

dynamic.dump:
    dumpbin /LINKERMEMBER static.lib > dynamic.dump

В-третьих, запустите dump2def.exe для файла *.dump, чтобы получить файл *.def. Исходный код dump2def.exe ниже.

dynamic.def: static.lib dynamic.dump
    dump2def.exe dynamic.dump dynamic.def

В-четвертых, соберите DLL:

LD = link.exe
LDFLAGS = /OPT:REF /MACHINE:X64
LDLIBS = kernel32.lib

dynamic.dll: $(LIB_OBJS) dynamic.def
    $(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@

/IGNORE:4102 используется, чтобы избежать этого предупреждения. Ожидается в этом случае:

dynamic.def : warning LNK4102: export of deleting destructor 'public: virtual v
oid * __ptr64 __cdecl std::exception::`scalar deleting destructor'(unsigned int)
 __ptr64'; image may not run correctly

При вызове рецепта dynamic.dll создается файл импорта dynamic.lib и файл dynamic.exp:

> cls && nmake /f test.nmake dynamic.dll
...
Creating library dynamic.lib and object dynamic.exp

И

 C:\Users\Test\testdll>dir *.lib *.dll *.def *.exp
 Volume in drive C is Windows
 Volume Serial Number is CC36-23BE

 Directory of C:\Users\Test\testdll

01/06/2019  08:33 PM        71,501,578 static.lib
01/06/2019  08:33 PM        11,532,052 dynamic.lib

 Directory of C:\Users\Test\testdll

01/06/2019  08:35 PM         5,143,552 dynamic.dll

 Directory of C:\Users\Test\testdll

01/06/2019  08:33 PM         1,923,070 dynamic.def

 Directory of C:\Users\Test\testdll

01/06/2019  08:35 PM         6,937,789 dynamic.exp
               5 File(s)     97,038,041 bytes
               0 Dir(s)  139,871,186,944 bytes free

Склейте его вместе вот так выглядит сборочный файл Nmake. Он является частью реального файла Nmake :

all: test.exe

test.exe: pch.pch static.lib $(TEST_OBJS)
    $(LD) $(LDFLAGS) $(TEST_OBJS) static.lib $(LDLIBS) /out:$@

static.lib: $(LIB_OBJS)
    $(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@

dynamic.map:
    $(LD) $(LDFLAGS) /DLL /MAP /MAPINFO:EXPORTS $(LIB_OBJS) $(LDLIBS) /out:dynamic.dll

dynamic.dump:
    dumpbin.exe /LINKERMEMBER static.lib /OUT:dynamic.dump

dynamic.def: static.lib dynamic.dump
    dump2def.exe dynamic.dump

dynamic.dll: $(LIB_OBJS) dynamic.def
    $(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@

clean:
    $(RM) /F /Q pch.pch $(LIB_OBJS) pch.obj static.lib $(TEST_OBJS) test.exe *.pdb

А вот исходный код для dump2def.exe:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <set>

typedef std::set<std::string> SymbolMap;

void PrintHelpAndExit(int code)
{
    std::cout << "dump2def - create a module definitions file from a dumpbin file" << std::endl;
    std::cout << "           Written and placed in public domain by Jeffrey Walton" << std::endl;
    std::cout << std::endl;

    std::cout << "Usage: " << std::endl;

    std::cout << "  dump2def <infile>" << std::endl;
    std::cout << "    - Create a def file from <infile> and write it to a file with" << std::endl;
    std::cout << "      the same name as <infile> but using the .def extension" << std::endl;

    std::cout << "  dump2def <infile> <outfile>" << std::endl;
    std::cout << "    - Create a def file from <infile> and write it to <outfile>" << std::endl;

    std::exit(code);
}

int main(int argc, char* argv[])
{
    // ******************** Handle Options ******************** //

    // Convenience item
    std::vector<std::string> opts;
    for (size_t i=0; i<argc; ++i)
        opts.push_back(argv[i]);

    // Look for help
    std::string opt = opts.size() < 3 ? "" : opts[1].substr(0,2);
    if (opt == "/h" || opt == "-h" || opt == "/?" || opt == "-?")
        PrintHelpAndExit(0);

    // Add <outfile> as needed
    if (opts.size() == 2)
    {
        std::string outfile = opts[1];
        std::string::size_type pos = outfile.length() < 5 ? std::string::npos : outfile.length() - 5;
        if (pos == std::string::npos || outfile.substr(pos) != ".dump")
            PrintHelpAndExit(1);

        outfile.replace(pos, 5, ".def");
        opts.push_back(outfile);
    }

    // Check or exit
    if (opts.size() != 3)
        PrintHelpAndExit(1);

    // ******************** Read MAP file ******************** //

    SymbolMap symbols;

    try
    {
        std::ifstream infile(opts[1].c_str());
        std::string::size_type pos;
        std::string line;

        // Find start of the symbol table
        while (std::getline(infile, line))
        {
            pos = line.find("public symbols");
            if (pos == std::string::npos) { continue; }        

            // Eat the whitespace after the table heading
            infile >> std::ws;
            break;
        }

        while (std::getline(infile, line))
        {
            // End of table
            if (line.empty()) { break; }

            std::istringstream iss(line);
            std::string address, symbol;
            iss >> address >> symbol;

            symbols.insert(symbol);
        }
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Unexpected exception:" << std::endl;
        std::cerr << ex.what() << std::endl;
        std::cerr << std::endl;

        PrintHelpAndExit(1);
    }

    // ******************** Write DEF file ******************** //

    try
    {
        std::ofstream outfile(opts[2].c_str());

        // Library name, cryptopp.dll
        std::string name = opts[2];
        std::string::size_type pos = name.find_last_of(".");

        if (pos != std::string::npos)
            name.erase(pos);

        outfile << "LIBRARY " << name << std::endl;
        outfile << "DESCRIPTION \"Crypto++ Library\"" << std::endl;        
        outfile << "EXPORTS" << std::endl;
        outfile << std::endl;

        outfile << "\t;; " << symbols.size() << " symbols" << std::endl;

        // Symbols from our object files
        SymbolMap::const_iterator it = symbols.begin();
        for ( ; it != symbols.end(); ++it)
            outfile << "\t" << *it << std::endl;
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Unexpected exception:" << std::endl;
        std::cerr << ex.what() << std::endl;
        std::cerr << std::endl;

        PrintHelpAndExit(1);
    }   

    return 0;
}
0 голосов
/ 22 октября 2008

Нет, вам понадобится макрос, который разрешается до __declspec(dllexport), когда он включен в файл .cpp, который реализует экспортируемые функции, и разрешается до __declspec(dllimport) в противном случае.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...