1. ホーム
  2. c++

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

2023-09-18 03:29:23

質問

VS2005で、DLLを作成し、すべてのシンボルを自動的にエクスポートしたいのですが、その際に __declspec(dllexport) を至る所に追加することなく、また、手作業で .def ファイルを手作業で作成する必要はありません。これを行う方法はあるのでしょうか?

どのように解決するのですか?

短い答え

あなたは、CMakeの新しいバージョン(任意のバージョンcmake-3.3.20150721-g9cd2f-win32-x86.exe以上)の助けを借りて、それを行うことができます。

現在、devブランチにあります。 その後、cmake-3.4のリリース版でこの機能が追加される予定です。

cmakeのdevへのリンクです。

cmake_dev

技術を説明した記事へのリンク。

CMake の新しい export all 機能を使用して declspec() なしで Windows 上で dll を作成する。

サンプルプロジェクトへのリンクです。

cmake_windows_export_all_symbols


長い回答

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

Linux の gcc や Windows の MinGW gcc コンパイラのような他のコンパイラを使用する場合、エクスポートされないシンボルによるリンクエラーは発生しません。

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)を使ってエクスポートする

使用例("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++のname manglingについての詳細は、リンク先で提供されています。

名前の装飾

使用例です。

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

これについての詳細は、リンクで提供されています。

C 言語の実行ファイルで使用するために C++ 関数をエクスポートする


2. モジュール定義(.def)ファイルを作成し、DLLをビルドする際に.defファイルを使用する。

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

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

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


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

この場合、.defファイルに関数宣言を手作業で追加するだけでよいでしょう。

使用例です。

extern "C" void HelloWorld();

.defファイルの例(__cdecl命名規則)です。

EXPORTS 
_HelloWorld


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

user72260" さんの提案された方法を試してみました。

彼はこう言いました。

  • まず、静的なライブラリを作成することができます。
  • 次に、"dumpbin /LINKERMEMBER" を使用して、静的ライブラリからすべてのシンボルをエクスポートします。
  • 出力をパースします。
  • すべての結果を .def ファイルに格納します。
  • .defファイルを使ってDLLを作成します。

私はこの方法を使いましたが、常に2つのビルド(1つは静的ライブラリとして、もう1つは動的ライブラリとして)を作成するのはあまり納得のいくものではありません。しかし、私は、このアプローチが本当にうまくいくことを認めざるを得ません。


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.Symbol from static library"と異なるアプローチを採用しています。Export symbols from static library"のアプローチと異なります。

それは次のようなものです。

1) .objファイルがDLLで使用される情報を含む "objects.txt"ファイルをビルドディレクトリに作成します。

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

3) "objects.txt"ファイルの情報を元に、.objファイルから全てのシンボルを抽出します。

使用例です。

DUMPBIN /SYMBOLS example.obj > log.txt

これについての詳細は、リンクで提供されています。

/SYMBOLS

4) .objファイル情報から抽出したものをパースする。

私の意見では、.obj ファイルを解析するために、例えば "__cdecl/__fastcall", "SECTx/UNDEF" シンボル フィールド (3 列目 ), "External/static" シンボル フィールド (5 列目 ), "?", "?" 情報などの呼び出しコンベックスを使います。

CMake が .obj ファイルを正確にどのようにパースするかはわかりません。 しかし、CMake はオープンソースなので、興味があれば調べることができます。

CMake プロジェクトへのリンクです。

CMake_github

5) エクスポートしたシンボルを全て.defファイルにまとめます。

6) .defで作成したファイルを使用してDLLをリンクする。

4)~5)の手順、つまり.objファイルを解析して.defファイルを作成してからリンクし、.defファイルを使用することは、CMakeが"Pre-Link event"の助けを借りて行います。 Pre-Link event"が発生している間、あなたは好きなプログラムを呼び出すことができます。 つまり、"CMakeの使い方"Pre-Link event"の場合、.defファイルをどこに置くか、"obs.txt"ファイルをどこに置くかという情報と引数 "-E __create_def" でCMakeを呼び出すことになるわけですね。 この情報は、CMake Visusal Studioプロジェクトを "set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" で作成し、そのプロジェクトファイル ".vcxproj" でDLを確認すれば、確認することができます。

もし "set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" または "set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)" なしでプロジェクトをコンパイルしようとしたら、シンボルが dll からエクスポートされないことが原因でリンク エラーが発生します。

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

カスタム ビルド ステップとビルド イベントを理解する


2.3.2. CMakeを使用しない場合

CMakeを使用しなくても、.objファイルをパースする小さなプログラムを自分で作成することができます。CMakeは、特にクロスプラットフォーム開発において、非常に有用なプログラムであることを認めざるを得ません。