创建DLL时导出所有符号


71

使用VS2005,我想创建一个DLL并自动导出所有符号,而无需在各处添加__declspec(dllexport)且无需手动创建.def文件。请问这是一种方法吗?

Answers:


45

可以办到...

我们在这里做的方法是使用链接器的/ DEF选项来传递一个包含我们的导出列表的“模块定义文件”。我从您的问题中了解到您了解这些文件。但是,我们不是手工完成的。导出列表本身是由dumpbin / LINKERMEMBER命令创建的,并通过简单的脚本将输出操纵为模块定义文件的格式。

设置需要很多工作,但是它允许我们编译在Windows上没有Unix的dllexport声明的情况下创建的代码。


11
通常最好添加导出宏,该宏__declspec(dllexport)在Windows__attribute__ ((dllexport))上,gcc上扩展为,在其他编译器上为空。然后传递-fvisibility=hiddengcc。您将获得一个更小,更整洁的符号表,并且会发现在Linux上进行测试时会破坏Windows构建的错误。
Craig Ringer 2014年

17
OP不想在__declspec(dllexport)任何地方编写。在各处添加其他一些导出宏也同样困难。
Andrew Stein 2014年

39

简短答案

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

当前在dev分支中。稍后,该功能将添加到cmake-3.4的发行版中。

链接到cmake dev:

cmake_dev

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

在Windows上使用新的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导出

如何:从DLL导出C ++类

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

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

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


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

有关更多信息,请通过链接提供:

使用__declspec(dllexport)从DLL导出

用法示例(用真实的项目名称替换“ Project”):

// 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文件。

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

用法示例:

DUMPBIN /SYMBOLS example.obj > log.txt

有关更多信息,请通过链接提供:

/符号

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

我认为我会使用呼叫对流,例如“ __cdecl / __ fastcall”,“ SECTx / UNDEF”符号字段(第三列),“外部/静态”符号字段(第五列),“ ??”,“?”。 ” 解析.obj文件的信息。

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

链接到CMake项目:

CMake_github

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

6)使用.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使用。Hovewer,我必须承认CMake是非常有用的程序,尤其是对于跨平台开发。


这是实打实的!非常感谢你。
fotinsky

这是很棒的信息。我只想补充一点,选项1是具体是什么OP也没有想这样做。选项2回答了他的问题。具体而言,2.3是有关接受的anwser和@ user72260的新信息。
安德鲁·斯坦

在Windows上,从Visual Studio 2015 Update 2开始,链接器具有/ WHOLEARCHIVE选项。见docs.microsoft.com/en-us/cpp/build/reference/...
羊咩咩

刚尝试玩Pre-Link事件。似乎工作良好。谢谢!以下是我对liblmdb的实验结果。`dumpbin / SYMBOLS $(平台)\ $(配置)\ mdb.obj | findstr / R“()。* External。* mdb _。*”> $(Platform)\ $(Configuration)\ mdb_symbols&(echo EXPORTS&for / F“ usebackq tokens = 2 delims == |” %% E在(type $(Platform)\$(Configuration)\mdb_symbols)做@echo %% E)> $(平台)\ $(配置)\ lmdb.def
Sergey

对于mingw用户,变量CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS的名称含糊不清,仅适用于msvc。
javs

8

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

在DLL上执行此操作的问题在于,必须先链接没有导出定义的DLL才能创建.lib文件,然后生成.def,这意味着您现在必须再次将DLL与.def文件重新链接为实际导出参考。

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

不幸的是,我的公司不允许我向您显示来源。所涉及的工作是识别def文件中转储输出中不需要哪些“公共符号”。您必须丢弃很多这些引用,NULL_IMPORT_DESCRIPTOR,NULL_THUNK_DATA,__imp *等。


您如何处理在cpp文件上具有成员的模板?
rxantos

@rxantos-使用显式实例化来强制实例化。或者,确保它是仅标头的实现(听起来好像没有)。
jww

@ user72260-使用相同的对象构建静态库而不是DLL。dumpbin.exe在静态库上运行。你不会有NULL_IMPORT_DESCRIPTORNULL_THUNK_DATA__imp*,等。然后,创建具有相同对象和新的DEF文件中的DLL。
jww

7

我想创建一个DLL并自动导出所有符号,而无需在各处添加__declspec(dllexport)且无需手动创建.def文件。请问这是一种方法吗?

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

最后,下面的步骤假定您正在使用Visual Studio Developer Promptvcvarsall.bat已在其中运行Windows终端)进行工作。您需要确保构建工具cl.exelib.exe,,link.exe和)nmake.exe在运行中。

有关更多信息,请通过链接提供:

使用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

第三,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 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;
}

3

感谢@Maks提供详细答案

以下是我在Pre-Link事件中用于从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_ *函数。然后解析输出,以保留名称,只考虑缩进的空间量(一个在拆分为令牌后,另一个在回显中。尽管如此,我也不知道)。

现实世界中的脚本可能会更加复杂。


0

也许有人发现我有用的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()

使用此脚本,您可以通过两步来获取.lib的.def文件:

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

-3

不,您将需要一个宏来解析__declspec(dllexport)何时实现导出功能的.cpp文件包含该宏,否则解析为宏__declspec(dllimport)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.