创建 DLL 时导出所有符号

新手上路,请多包涵

使用 VS2005,我想创建一个 DLL 并自动导出所有符号,而无需在任何地方添加 __declspec(dllexport) 并且无需手动创建 .def 文件。有没有办法做到这一点?

原文由 Jazz 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2.4k
2 个回答

简短的回答

您可以借助新版本的 CMake(任何版本的 cmake-3.3.20150721-g9cd2f-win32-x86.exe 或更高版本)来完成此操作。

目前它在 dev 分支中。稍后,该功能将在 cmake-3.4 的发布版本中添加。

链接到 cmake 开发:

cmake_dev

链接到描述该技术的文章:

使用新的 CMake 导出所有功能在没有 declspec() 的情况下在 Windows 上创建 dll

链接到示例项目:

cmake_windows_export_all_symbols


长答案

注意: 以下所有信息都与 MSVC 编译器或 Visual Studio 有关。

如果您在 Linux 上使用 gcc 或在 Windows 上使用 MinGW gcc 编译器等其他编译器,则不会由于未导出符号而出现链接错误,因为 gcc 编译器默认导出动态库 (dll) 中的所有符号,而不是 MSVC 或 Intel Windows 编译器.

在 Windows 中,您必须从 dll 中显式导出符号。

链接提供了有关此的更多信息:

从 DLL 导出

HowTo:从 DLL 导出 C++ 类

因此,如果您想使用 MSVC(Visual Studio 编译器)从 dll 中导出所有符号,您有两种选择:

  • 在类/函数的定义中使用关键字 __declspec(dllexport)。
  • 创建模块定义 (.def) 文件并在构建 DLL 时使用 .def 文件。

_1.在类/函数的定义中使用关键字_declspec(dllexport)


_1.1。将“__declspec(dllexport) / _declspec(dllimport)”宏添加到要使用的类或方法中。因此,如果要导出所有类,则应将此宏添加到所有类

链接提供了有关此的更多信息:

使用 __declspec(dllexport) 从 DLL 导出

使用示例(将“项目”替换为真实项目名称):

 // 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”添加到所有类。仅当您希望从 dll 导出/导入符号时才定义“USEPROJECTLIBRARY”。为 dll 定义“PROJECTLIBRARY_EXPORTS”。

类导出示例:

 #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)文件,构建DLL时使用.def文件

链接提供了有关此的更多信息:

使用 DEF 文件从 DLL 导出

此外,我描述了三种关于如何创建 .def 文件的方法。


2.1。导出 C 函数

在这种情况下,您可以简单地手动在 .def 文件中添加函数声明。

使用示例:

 extern "C" void HelloWorld();

.def 文件示例(__cdecl 命名约定):

 EXPORTS
_HelloWorld


2.2.从静态库中导出符号

我尝试了“user72260”建议的方法。

他说:

  • 首先,您可以创建静态库。
  • 然后使用“dumpbin /LINKERMEMBER”从静态库中导出所有符号。
  • 解析输出。
  • 将所有结果放入 .def 文件中。
  • 使用 .def 文件创建 dll。

我使用了这种方法,但总是创建两个构建(一个作为静态库,另一个作为动态库)并不是很方便。但是,我不得不承认,这种方法确实有效。


2.3.从 .obj 文件或借助 CMake 导出符号


2.3.1。使用 CMake

重要通知: 您不需要任何导出宏到类或函数!

重要提示: 使用此方法时不能使用 /GL( 全程序优化)!

  • 根据“CMakeLists.txt”文件创建 CMake 项目。
  • 将以下行添加到“CMakeLists.txt”文件中: set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
  • 然后在“CMake (cmake-gui)”的帮助下创建 Visual Studio 项目。
  • 编译项目。

使用示例:

根文件夹

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”文件,其中包含在dll中使用的.obj文件的信息。

2)编译dll,即创建.obj文件。

  1. 根据“objects.txt”文件信息从.obj 文件中提取所有符号。

使用示例:

 DUMPBIN /SYMBOLS example.obj > log.txt

链接提供了有关此的更多信息:

/符号

  1. 解析从.obj 文件信息中提取的信息。

在我看来,我会使用调用对流,例如“cdecl/fastcall”、“SECTx/UNDEF”符号字段(第三列)、“外部/静态”符号字段(第五列)、“??”、“? “解析 .obj 文件的信息。

我不知道 CMake 究竟是如何解析 .obj 文件的。但是,CMake 是开源的,因此您可以了解它是否对您感兴趣。

链接到 CMake 项目:

CMake_github

  1. 将所有导出的符号放入 .def 文件中。

  2. 使用 .def 创建的文件链接 dll。

步骤 4)-5),即在链接和使用 .def 文件之前解析 .obj 文件并创建一个 .def 文件 CMake 在“预链接事件”的帮助下执行。当“预链接事件”触发时,您可以调用任何您想要的程序。因此,在“CMake 使用”“预链接事件”的情况下,使用以下信息调用 CMake,其中包含有关将 .def 文件放置在何处以及“objects.txt”文件在何处以及参数“-E __create_def”的信息。您可以通过使用“set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)”创建 CMake Visusal Studio 项目来检查此信息,然后检查 dll 的“.vcxproj”项目文件。

如果您尝试在没有“set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)”或“set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)”的情况下编译项目,则会出现链接错误,因为符号不是从 dll 导出的。

链接提供了有关此的更多信息:

了解自定义构建步骤和构建事件


2.3.2.不使用 CMake

您可以简单地创建一个小程序来自己解析 .obj 文件,而无需使用 CMake。但是,我不得不承认 CMake 是非常有用的程序,尤其是对于跨平台开发。

原文由 Maks 发布,翻译遵循 CC BY-SA 3.0 许可协议

我想创建一个 DLL 并自动导出所有符号,而无需在任何地方添加 __declspec(dllexport) 并且无需手动创建 .def 文件。有没有办法做到这一点?

这是一个较晚的答案,但它提供了 Maks 在第 (2) 节中的答案的详细信息。它还避免使用脚本并使用名为 dump2def 的 C++ 程序。 dump2def 的源代码如下。

最后,以下步骤假设您正在使用 Visual Studio Developer Prompt 工作,这是一个运行 vcvarsall.bat 的 Windows 终端。您需要确保构建工具,如 cl.exelib.exelink.exenmake.exe -path6681b68。

链接提供了有关此的更多信息:

使用 DEF 文件从 DLL 导出

下面的指令使用:

  • static.lib - 静态库存档(Linux 上的 *.a 文件)
  • dynamic.dll - 动态库(Linux上的*.so文件)
  • 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

三、在 *.dump 文件上运行 dump2def.exe 产生 *.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 makefile 的样子。它是 真实 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;
}

原文由 jww 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏