代码之家  ›  专栏  ›  技术社区  ›  Jazz

创建dll时导出所有符号

  •  62
  • Jazz  · 技术社区  · 17 年前

    使用VS2005,我希望创建一个DLL并自动导出所有符号,而不需要在任何地方添加uu declspec(dll export),也不需要手动创建.def文件。三人行吗?

    6 回复  |  直到 7 年前
        1
  •  36
  •   Ross    9 年前

    可以做到…

    我们在这里的方法是使用链接器的/def选项传递 "module definition file" 包含我们的出口清单。我从你的问题中看出你知道这些文件。但是,我们不是用手做的。导出列表本身由 dumpbin /linkermember命令,并通过一个简单的脚本将输出操作为模块定义文件的格式。

    安装是一项非常繁重的工作,但它允许我们编译在Windows上不使用dllexport声明为UNIX创建的代码。

        2
  •  29
  •   Humam Helfawi    8 年前

    简短回答

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

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

    链接到cmake dev:

    cmake_dev

    链接到描述技术的文章:

    Create dlls on Windows without declspec() using new CMake export all feature

    链接到示例项目:

    cmake_windows_export_all_symbols


    长回答

    警告: 下面的所有信息都与MSVC编译器或Visual Studio有关。

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

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

    有关此问题的详细信息由以下链接提供:

    Exporting from a DLL

    HowTo: Export C++ classes from a DLL

    因此,如果要使用msvc(Visual Studio编译器)从dll中导出所有符号,则有两个选项:

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

    1。在类/函数的定义中使用关键字declspec(dllexport)


    1.1。向要使用的类或方法中添加“u declspec(dllexport)/”declspec(dllimport)”宏。因此,如果要导出所有类,则应将此宏添加到所有类中

    链接提供了有关此的详细信息:

    Exporting from a DLL Using __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”添加到所有类中。 仅当需要从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代码,你可以在函数前面加上一个“C”来消除名字的错误。

    有关C++名称的更多信息由链接提供:

    Name Decoration

    使用示例:

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

    链接提供了有关此的详细信息:

    Exporting C++ Functions for Use in C-Language Executables


    2。创建模块定义(.def)文件,并在生成dll时使用.def文件

    链接提供了有关此的详细信息:

    Exporting from a DLL Using DEF Files

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


    2.1。导出C函数

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

    使用示例:

    extern "C" void HelloWorld();
    

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

    EXPORTS 
    _HelloWorld
    

    2.2。从静态库导出符号

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

    他说:

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

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


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


    2.3.1使用cmake

    重要提示: 您不需要将宏导出到类或函数!

    重要提示: 您不能使用/gl( Whole Program Optimization )使用此方法时!

    • 基于“cmakelists.txt”文件创建cmake项目。
    • 将以下行添加到“cmakelists.txt”文件中: 设置(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_导出所有符号

    cmake使用与“2.2”不同的。从静态库中导出符号“方法。

    它执行以下操作:

    1)在build目录中创建包含.obj文件信息的“objects.txt”文件,该文件在dll中使用。

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

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

    使用示例:

    DUMPBIN /SYMBOLS example.obj > log.txt
    

    链接提供了有关此的详细信息:

    /SYMBOLS

    4)解析从.obj文件信息中提取的内容。

    在我看来,我将使用调用对流,例如“uu cdecl/uuu fastcall”、“sectx/undef”符号字段(第三列)、“external/static”符号字段(第五列)、“?“?”用于分析.obj文件的信息。

    我不知道cmake如何准确地解析.obj文件。 但是,cmake是开放源码的,因此您可以了解它是否对您感兴趣。

    链接到cmake项目:

    CMake_github

    5)将所有导出的符号放在.def文件中。

    6)使用.def创建的文件链接DLL。

    步骤4)-5),即在链接和使用.def文件之前,在“预链接事件”的帮助下解析.obj文件并创建.def文件。 当“预链接事件”触发时,您可以调用任何您想要的程序。 因此,在“cmake usage”“pre-link event”的情况下,使用以下信息调用cmake,这些信息包括放置.def文件的位置、“objects.txt”文件的位置以及参数“-e_uu create_def”。 您可以通过使用“set(cmake-windows-export-all-u-symbols on)”创建cmake-visusal studio项目来检查此信息,然后检查dll的“.vcxproj”项目文件。

    如果您试图编译一个没有“set(cmake_windows_export_all_symbols on)”或“set(cmake_windows_export_all_symbols off)”的项目,您将得到链接错误,因为符号不是从dll导出的。

    链接提供了有关此的详细信息:

    Understanding Custom Build Steps and Build Events


    2.3.2.不使用cmake

    您可以创建一个小程序来解析.obj文件,而不需要使用cmake。霍维尔,我不得不承认,cmake是非常有用的程序,特别是对于跨平台开发。

        3
  •  8
  •   user72260    17 年前

    我编写了一个小程序来解析.lib文件上“dumpbin/linkermember”的输出。我有超过8000个函数引用要从一个DLL导出。

    在dll上执行此操作的问题是,必须在不使用导出定义的情况下链接一次dll才能创建.lib文件,然后生成.def,这意味着您现在必须再次将dll与.def文件重新链接,以实际导出引用。

    使用静态库更容易。将所有源编译成静态libs,运行dumbin,用您的小程序生成一个.def,然后在导出名称可用的情况下将libs链接到一个dll中。

    不幸的是,我的公司不允许我告诉你消息来源。所涉及的工作是识别在您的def文件中不需要转储输出中的哪些“公共符号”。您必须丢弃大量的引用、空的导入描述符、空的thunk数据、空的imp*等。

        4
  •  2
  •   Sergey    8 年前

    感谢@maks detailed answer .

    下面是我在预链接事件中用来从obj生成def文件的示例。我希望它能对某人有所帮助。

    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_x函数。然后对输出进行解析,以便只保留名称,同时考虑缩进的空间量(一个在拆分成标记后,另一个在Echo中)。但我不知道这是否重要)。

    不过,现实世界的剧本可能会更复杂一些。

        5
  •  0
  •   jww avp    7 年前

    我想创建一个DLL并自动导出所有符号,而不需要在任何地方添加uu declspec(dll export),也不需要手动创建.def文件。三人行吗?

    这是一个迟交的答案,但它在第(2)节中提供了Maks答案的详细信息。它还避免脚本,并使用C++程序调用 dump2def . 的源代码 Dimp2DEF 在下面。

    最后,下面的步骤假设您在Visual Studio中工作 Developer Prompt ,这是一个Windows终端, vcvarsall.bat 已经运行。您需要确保构建工具 cl.exe , lib.exe , link.exe 和 nmake.exe 在路上。

    链接提供了有关此的详细信息:

    Exporting from a DLL Using DEF Files
    …

    以下使用说明:

    • static.lib -静态库存档文件(*.a linux上的文件)
    • dynamic.dll -动态库(*.so file on 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 上 *转储 文件以生成 *.def 文件。的源代码 DUM2DEF.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
    

    当 动态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的样子。它是A的一部分 real Nmake file :

    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
    

    这是的源代码 DUM2DEF.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
  •  -3
  •   angelsl    11 年前

    不,您需要一个宏来解析为 __declspec(dllexport) 当它包含在实现导出函数的.cpp文件中,并解析为 __declspec(dllimport) 否则。