Экспортировать все символы при создании DLL

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

6 ответов

Решение

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

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

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

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

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

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

Ссылка на cmake dev:

cmake_dev

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

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

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

https://bitbucket.org/vlasovmaksim/cmake_windows_export_all_symbols/overview


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

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

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

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

Более подробная информация об этом предоставляется по ссылкам:

Экспорт из DLL

Как: Экспортировать классы 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

Затем добавьте "ПРОЕКТ" ко всем классам. Определите "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": set(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;
}

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

https://bitbucket.org/vlasovmaksim/cmake_windows_export_all_symbols/overview

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.

Более подробная информация об этом предоставляется по ссылке:

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


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

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

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

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

Наконец, нижеприведенные шаги предполагают, что вы работаете с приглашением разработчика Visual Studio, которое является терминалом 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;
}

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

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

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

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

Спасибо @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_*. Затем проанализировал вывод, чтобы сохранить только имена с учетом количества пробелов для отступа (один после разбиения на токены, а другой в эхо. Хотя я не знаю, имеет ли это значение).

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

Возможно, кому-нибудь пригодится мой скрипт Python для преобразования.dump в.def.

import sys, os
functions = []
startPoint = False
# Exclude standard API like sprintf to avoid multiple definition link error
excluded_functions = [ 'sprintf', 'snprintf', 'sscanf', 'fprintf' ]

if len(sys.argv) < 2:
    print('Usage: %s <Input .dump file> <Output .def file>.' % sys.argv[0])
    print('Example: %s myStaticLib.dump exports.def' % sys.argv[0])
    sys.exit(1)
print('%s: Processing %s to %s' % (sys.argv[0], sys.argv[1], sys.argv[2]))

fin = open(sys.argv[1], 'r')
lines = fin.readlines()
fin.close()

# Reading
for l in lines:
    l_str = l.strip()
    if (startPoint == True) and (l_str == 'Summary'): # end point
        break
    if (startPoint == False) and ("public symbols" in l_str):
        startPoint = True
        continue
    if (startPoint == True) and l_str is not '':
        funcName = l_str.split(' ')[-1]
        if funcName not in excluded_functions:
            functions.append("    " + funcName)
# Writing
fout = open(sys.argv[2], 'w')
fout.write('EXPORTS\n')
for f in functions:
    fout.write('%s\n' % f)
fout.close()

С помощью этого скрипта вы можете получить файл.def для своего.lib за два шага:

dumpbin /LINKERMEMBER:1 myStaticLib.lib > myExports.dump
python dump2def.py myExports.dump myExports.def

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

Другие вопросы по тегам