web-dev-qa-db-ja.com

DLLを作成するときにすべてのシンボルをエクスポートします

VS2005では、DLLを作成し、__ declspec(dllexport)をどこにでも追加せずに、.defファイルを手動で作成することなく、すべてのシンボルを自動的にエクスポートします。これを行う方法はありますか?

62
Jazz

できます...

ここで行う方法は、リンカの/ DEFオプションを使用して、エクスポートのリストを含む "モジュール定義ファイル" を渡すことです。あなたの質問から、これらのファイルについて知っていることがわかります。ただし、手作業では行いません。エクスポートのリスト自体は dumpbin /LINKERMEMBERコマンドによって作成され、出力を単純なスクリプトを介してモジュール定義ファイルの形式に操作します。

セットアップには多くの作業が必要ですが、Windows上のUnixでdllexport宣言なしで作成されたコードをコンパイルできます。

37
Andrew Stein

短い答え

CMakeの新しいバージョン(任意のバージョンのcmake-3.3.20150721-g9cd2f-win32-x86.exe以降)を使用して実行できます。

現在、devブランチにあります。後で、この機能はcmake-3.4のリリースバージョンに追加されます。

Cmake devへのリンク:

cmake_dev

テクニックを説明する記事へのリンク:

新しいCMakeエクスポートのすべての機能を使用して、declspec()なしでWindows上にdllを作成します

サンプルプロジェクトへのリンク:

cmake_windows_export_all_symbols


長い答え

注意:以下のすべての情報は、MSVCコンパイラまたはVisual Studioに関連しています。

LinuxのgccやWindowsのMinGW gccコンパイラーなどの他のコンパイラーを使用する場合、GSVCコンパイラーはデフォルトでMSVCまたはIntel Windowsコンパイラーの代わりに動的ライブラリー(dll)のすべてのシンボルをエクスポートするため、リンクエラーはありません。

Windowsでは、dllからシンボルを明示的にエクスポートする必要があります。

これに関する詳細情報はリンクで提供されます:

DLLからのエクスポート

HowTo:DLLからC++クラスをエクスポートする

したがって、MSVC(Visual Studioコンパイラ)を使用してdllからすべてのシンボルをエクスポートする場合、2つのオプションがあります。

  • クラス/関数の定義でキーワード__declspec(dllexport)を使用します。
  • モジュール定義(.def)ファイルを作成し、DLLをビルドするときに.defファイルを使用します。

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

次に、すべてのクラスに「PROJECTAPI」を追加します。 「USEPROJECTLIBRARY」は、dllからシンボルをエクスポート/インポートする場合にのみ定義します。 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ファイルを使用します

これに関する詳細情報は、リンクで提供されます。

DLL DEFファイルの使用)からのエクスポート

さらに、.defファイルの作成方法に関する3つのアプローチについて説明します。


2.1。 C関数のエクスポート

この場合、.defファイルに関数宣言を手動で簡単に追加できます。

使用例:

extern "C" void HelloWorld();

.defファイルの例(__cdecl命名規則):

EXPORTS 
_HelloWorld

2.2。静的ライブラリからシンボルをエクスポートします

「user72260」で提案されているアプローチを試しました。

彼は言った:

  • まず、静的ライブラリを作成できます。
  • 次に、「dumpbin/LINKERMEMBER」を使用して、静的ライブラリからすべてのシンボルをエクスポートします。
  • 出力を解析します。
  • すべての結果を.defファイルに入れます。
  • .defファイルを使用してdllを作成します。

私はこのアプローチを使用しましたが、常に2つのビルド(静的ライブラリと動的ライブラリ)を作成することはあまり便利ではありません。しかし、私は認めなければならない、このアプローチは本当に機能します。


2.3。 .objファイルから、またはCMakeを使用してシンボルをエクスポートします


2.3.1。 CMake使用時

重要なお知らせ:クラスまたは関数へのエクスポートマクロは必要ありません!

重要なお知らせ:このアプローチを使用する場合、/ GL( Whole Program Optimization )は使用できません!

  • 「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」ファイルを作成し、.objファイルの情報をDLLで使用します。

2)dllをコンパイルします。つまり、.objファイルを作成します。

3)「objects.txt」ファイル情報に基づいて、.objファイルからすべてのシンボルを抽出します。

使用例:

DUMPBIN /SYMBOLS example.obj > log.txt

これに関する詳細情報は、リンクで提供されています。

/ SYMBOLS

4).objファイル情報から抽出された解析。

私の意見では、「__ cdecl/__ fastcall」、「SECTx/UNDEF」シンボルフィールド(3列目)、「外部/静的」シンボルフィールド(5列目)、「??」、「?」などの呼び出し対流を使用します。 」 .objファイルを解析するための情報。

CMakeが.objファイルを正確に解析する方法がわかりません。ただし、CMakeはオープンソースなので、興味があるかどうかを確認できます。

CMakeプロジェクトへのリンク:

CMake_github

5)エクスポートされたすべてのシンボルを.defファイルに入れます。

6).defで作成されたファイルの使用とdllをリンクします。

手順4)〜5)。これは、.objファイルを解析し、.defファイルを作成してからリンクし、.defファイルを使用します。CMakeは、「リンク前イベント」の助けを借りて行います。 「リンク前イベント」が発生している間は、必要なプログラムを呼び出すことができます。したがって、「CMakeの使用」「プリリンクイベント」の場合、.defファイルの配置場所と「objects.txt」ファイルの場所に関する次の情報と引数「-E __create_def」を使用してCMakeを呼び出します。この情報を確認するには、「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を使用しない場合

CMakeを使用せずに、自分で.objファイルを解析するための小さなプログラムを簡単に作成できます。 Hovewer、私はCMakeが特にクロスプラットフォーム開発に非常に役立つプログラムであることを認めざるを得ません。

29
Maks

.libファイルの「dumpbin/linkermember」の出力を解析する小さなプログラムを作成しました。 1つのDLLからエクスポートする8,000以上の関数参照があります。

DLLでそれを行う際の問題は、.libファイルを作成するためにエクスポートされた定義なしでDLLを一度リンクしてから。 defつまり、DLLを再び.defファイルに再リンクして、実際に参照をエクスポートする必要があります。

静的ライブラリの操作は簡単です。すべてのソースを静的ライブラリにコンパイルし、dumbinを実行し、小さなプログラムで.defを生成し、エクスポート名が利用できるようになったライブラリをDLLにリンクします。

残念ながら、私の会社はあなたにソースを見せることを許可しません。関連する作業は、ダンプ出力のどの「パブリックシンボル」がdefファイルで不要かを認識することです。これらの参照、NULL_IMPORT_DESCRIPTOR、NULL_THUNK_DATA、__ imp *などの多くを破棄する必要があります。

8
user72260

詳細な回答 について@Maksに感謝します。

以下は、objからdefファイルを生成するためにPre-Linkイベントで使用したものの例です。それが誰かに役立つことを願っています。

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)の1つを取得し、mdb_ *関数をgrepしました。次に、インデント用のスペースの量を考慮して名前だけを保持するために出力を解析しました(1つはトークンに分割し、もう1つはエコーします。それが問題かどうかはわかりません)。

ただし、実世界のスクリプトはおそらくもっと複雑になります。

2
Sergey

DLLを作成し、あらゆる場所に__declspec(dllexport)を追加せずに、.defファイルを手動で作成することなく、すべてのシンボルを自動的にエクスポートします。これを行う方法はありますか?

これは遅い回答ですが、セクション(2)のMaksの回答の詳細を提供します。また、スクリプトを回避し、_dump2def_というC++プログラムを使用します。 _dump2def_のソースコードは次のとおりです。

最後に、以下の手順は、Visual Studio 開発者プロンプト から作業していることを前提としています。これは、_vcvarsall.bat_が実行されたWindowsターミナルです。 _cl.exe_、_lib.exe_、_link.exe_、_nmake.exe_などのビルドツールがパス上にあることを確認する必要があります。

これに関する詳細情報は、リンクで提供されています。

DLL DEFファイルの使用)からのエクスポート
...

以下の指示は以下を使用します。

  • _static.lib_-静的ライブラリアーカイブ(* .a Linux上のファイル)
  • _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
_

3番目に、_dump2def.exe_ファイルで_*.dump_を実行して_*.def_ファイルを生成します。 _dump2def.exe_のソースコードは次のとおりです。

_dynamic.def: static.lib dynamic.dump
    dump2def.exe dynamic.dump dynamic.def
_

4番目に、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;
}
_
1
jww