С VS2005 я хочу создать DLL и автоматически экспортировать все символы без добавления __declspec (dllexport) во всем мире и без создания файлов .def вручную. Это лучший способ сделать это?
Экспорт всех символов при создании DLL
Ответ 1
Это можно сделать...
Для этого мы используем параметр компоновщика /DEF для передачи "файла определения модуля", содержащего список наших экспортов. По вашему вопросу я вижу, что вы знаете об этих файлах. Однако мы не делаем это вручную. Сам список экспорта создается командой dumpbin/LINKERMEMBER и управляет выводом с помощью простого сценария в формате файла определения модуля.
Настройка очень большая, но она позволяет нам компилировать код, созданный без объявлений dllexport для Unix в Windows.
Ответ 2
Короткий ответ
Вы можете сделать это с помощью новой версии CMake (любая версия cmake-3.3.20150721-g9cd2f-win32-x86.exe или выше).
В настоящее время это в ветке разработчика. Позже эта функция будет добавлена в релизную версию cmake-3.4.
Ссылка на cmake dev:
Ссылка на статью, которая описывает технику:
Создавайте dll в Windows без declspec(), используя новую функцию экспорта всех CMake
Ссылка на пример проекта:
cmake_windows_export_all_symbols
Длинный ответ
Осторожно! Вся информация ниже относится к компилятору MSVC или Visual Studio.
Если вы используете другие компиляторы, такие как gcc в Linux или MinGW gcc-компилятор в Windows, у вас не будет ошибок компоновки из-за неэкспортированных символов, поскольку компилятор gcc по умолчанию экспортирует все символы в динамическую библиотеку (dll) вместо компиляторов MSVC или Intel Windows.,
В окнах вы должны явно экспортировать символ из 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;
}
Ссылка на пример проекта снова:
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:
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 - очень полезная программа, особенно для кроссплатформенной разработки.
Ответ 3
Я написал небольшую программу для анализа вывода "dumpbin/linkmember" в .lib файле. У меня есть 8000 ссылок на функции для экспорта из одной DLL.
Проблема с выполнением этого в DLL заключается в том, что вам нужно связать DLL без экспортированных определений один раз, чтобы создать .lib файл, а затем сгенерировать .def, что означает, что теперь вам нужно повторно связать DLL с помощью .def файл, чтобы фактически экспортировать ссылки.
Работа со статическими библиотеками проще. Скомпилируйте все свои источники в статические библиотеки, запустите dumbin, сгенерируйте .def с помощью своей маленькой программы, а затем соедините библиотеки вместе в DLL, когда доступны имена экспорта.
К сожалению, моя компания не позволит мне показать вам источник. В этой работе признается, какие "публичные символы" в выводе дампа не нужны в вашем файле def. Вы должны отбросить много этих ссылок, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp * и т.д.
Ответ 4
Спасибо @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_ *. Затем проанализировал вывод, чтобы сохранить только имена с учетом количества пробелов для отступа (один после разбиения на токены, а другой в эхо. Хотя я не знаю, имеет ли это значение).
Реальный сценарий, вероятно, будет немного сложнее.
Ответ 5
Я хочу создать DLL и автоматически экспортировать все символы без добавления __declspec (dllexport) везде и без создания файлов .def вручную. Есть ли способ сделать это?
Это поздний ответ, но он предоставляет подробности ответа Макса в Разделе (2). Он также избегает скриптов и использует программу C++ с именем dump2def
. Исходный код для dump2def
ниже.
Наконец, нижеприведенные шаги предполагают, что вы работаете из подсказки разработчика Visual Studio, которая является терминалом Windows, в котором был запущен vcvarsall.bat
. Вы должны убедиться, что инструменты сборки, такие как cl.exe
, lib.exe
, link.exe
и nmake.exe
находятся в пути.
Более подробная информация об этом предоставляется по ссылке:
Инструкция ниже использовать:
-
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:[email protected]
Во-вторых, запустите 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:[email protected]
/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:[email protected]
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:[email protected]
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:[email protected]
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;
}
Ответ 6
Нет, вам понадобится макрос, который разрешает __declspec(dllexport)
, когда он включен в .cpp файл, который реализует экспортированные функции, и решает в __declspec(dllimport)
в противном случае.