2021年8月1日日曜日

22: Visual C++で、DLLからシンボル群をエクスポートする

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

そうしなければ、シンボル群は、DLLの外からは不可視になります。いかに、'__declspec (dllexport)'および'__declspec (dllimport)'をコントロールするか。

話題


About: C++
About: Visual C++

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、Visual C++は、シンボル群がDLLの外部から可視であるためには、それらはDLLから明示的にエクスポートされるよう求めること、およびそれを実施する方法を知る。

オリエンテーション


'inline'仕様のおぞましさを検討する記事が掲載される予定です。.

Visual C++の奇癖について、他にいくつか記事(「unable to match function definition to an existing declaration」エラー)があります。


本体

ト書き
Hypothesizer 7が独白する。


1: Dll由来のシンボル群が解決されない? . . . なぜVisual C++でだけ?


Hypothesizer 7
私は、通常、C++プログラミングをGCCで行なうので、GCCで問題なくビルドされるある一群のプロジェクト(1つのメインプロジェクトといくつかのライブラリプロジェクト)を持っており、今、それらをVisual C++でビルドしなければならない。

いくつの非互換性(その内のいくつかには異議がある)が矯正された後、分割コンパイルがようやく成功したが、リンクが多くのエラーで失敗する。

. . . えーと、どのDLL内のシンボルも全て、DLLの外部から認識されないようだ。

結局判明したのは、Visual C++は、シンボルが明示的にエクスポートされるよう要求するということだ、GCCはそのようなことを何も要求しないが。

そして「明示的にエクスポートされる」というのは、単なるコンパイルスイッチを意味するのではなく、いくつかの結構面倒な処置を意味する(なぜVisual C++はGCCと同程度に容易でありえないのかを私は理解しない)。


2: 基本事項と複雑な事情、そして私の解決策


Hypothesizer 7
シンボル群は、何らのソースファイル(ヘッダファイルまたは'cpp'ファイル)も調整することなく、いわゆる「モジュール定義ファイル」を作成してそれをリンカーに指定することでエクスポートできるが、私は、ソースファイルを調整する処置を取ることにする、なぜなら、「モジュール定義ファイル」を作成する方がより面倒に思えるから。

任意のシンボルをエクスポートする基本は、特定の箇所に、'__declspec (dllexport)'という修飾を付けることだ。

「特定の箇所」? . . . 例えば、以下は、あるクラスのメンバー群(このケースでは単一のメンバーしかないが)をエクスポートする。

'theBiasPlanet/coreUtilities/constantsGroups/FileNameSuffixesConstantsGroup.hpp'

@C++ ソースコード
#ifndef __theBiasPlanet_coreUtilities_constantsGroups_FileNameSuffixesConstantsGroup_hpp__
	#define __theBiasPlanet_coreUtilities_constantsGroups_FileNameSuffixesConstantsGroup_hpp__
	
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilities {
			namespace constantsGroups {
				class __declspec (dllexport) FileNameSuffixesConstantsGroup {
					public:
						static string const c_xmlFileNameSuffix;
				};
			}
		}
	}
#endif

注意すべきだが、それは、クラス自体をエクスポートするのではなく、そのメンバー群をエクスポートする。 . . . クラス自体はどうなるのか?クラス自体は何らのオブジェクト的オブジェクトではなく、青写真であり、したがって、クラス自体はエクスポートされる必要がない、なぜなら、クラスは、オブジェクトファイル内にないから

他方で、私は先程、ヘッダファイルのみを示したが、'c_xmlFileNameSuffix'変数を定義した'cpp'ファイルもあり、その変数は、オブジェクト的オブジェクトであり、エクスポートされる必要がある。

それはともかく、複雑な事情は、当該ヘッダファイルは外部生成物にもインクルードされるように想定されているので、その「__declspec (dllexport)」という修飾をそのままそこに書くことはできないということだ、外部生成物はそのクラスメンバー群をエクスポートしないので。

実のところ、私は、その修飾が、そのヘッダファイルが当該DLLにインクルードされる時のみ'__declspec (dllexport)'となり、その他の時には'__declspec (dllimport)'となるようにコントロールしなければならない。

「当該DLLにインクルードされる」と言ったが、「当該DLL」は、シンボル群を定義したまさにそのDLLでなければならず、別のDLLであってはいけない、なぜなら、その別DLLは、それらシンボル群のインポーターだからだ。

したがって、以下の、どのDLLにも'DLL'定義を指定するというのはうまくいかない、当然。

'theBiasPlanet/coreUtilities/constantsGroups/FileNameSuffixesConstantsGroup.hpp'

@C++ ソースコード
#ifndef __theBiasPlanet_coreUtilities_constantsGroups_FileNameSuffixesConstantsGroup_hpp__
	#define __theBiasPlanet_coreUtilities_constantsGroups_FileNameSuffixesConstantsGroup_hpp__
	#ifdef DLL
		#define __symbolExportingOrImportingForVisualCplusplus__ __declspec (dllexport)
	#else
		#define __symbolExportingOrImportingForVisualCplusplus__ __declspec (dllimport)
	#endif
	
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilities {
			namespace constantsGroups {
				class __symbolExportingOrImportingForVisualCplusplus__ FileNameSuffixesConstantsGroup {
					public:
						static string const c_xmlFileNameSuffix;
				};
			}
		}
	}
#endif

. . . したがって、ビルドされる生成物がDLLか否かが問題なのではなく、そのシンボルが、ビルドされる生成物によって所有されているか否かが問題なのだ。

したがって、以下のように、定義名はそれぞれ、生成物固有でなければならない('__theBiasPlanet_coreUtilities__'定義は、コンパイル時に指定される)。

'theBiasPlanet/coreUtilities/constantsGroups/FileNameSuffixesConstantsGroup.hpp'

@C++ ソースコード
#ifndef __theBiasPlanet_coreUtilities_constantsGroups_FileNameSuffixesConstantsGroup_hpp__
	#define __theBiasPlanet_coreUtilities_constantsGroups_FileNameSuffixesConstantsGroup_hpp__
	#ifdef __theBiasPlanet_coreUtilities__
		#define __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ __declspec (dllexport)
	#else
		#define __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ __declspec (dllimport)
	#endif
	
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilities {
			namespace constantsGroups {
				class __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ FileNameSuffixesConstantsGroup {
					public:
						static string const c_xmlFileNameSuffix;
				};
			}
		}
	}
#endif

それで全て? . . . えーと、私のヘッダファイルはGCCにも通用しなければならないから、以下のように、当該修飾がGCCでは消滅するようにコントロールしなければならない、コンパイル時に'GCC'定義を指定したりしなかったりして。

@C++ ソースコード
#ifdef GCC
	#define __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__
#else
	#ifdef __theBiasPlanet_coreUtilities__
		#define __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ __declspec (dllexport)
	#else
		#define __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ __declspec (dllimport)
	#endif
#endif

勿論、その同じものを複数のファイルに書く必要はなく、それを、ある1つのヘッダファイル(ここでは、'theBiasPlanet/coreUtilities/visualCplusplusSpecificHeaders/VisualCplusplusSpecificDefinitions.hpp'としよう)へ入れて、複数のファイルにそれをインクルードさせることができる、しかし、いずれにせよ、あるDLLからシンボルをエクスポートするというような罪のないことを行なうだけのために、そのような面倒な事をしなければならないというのは、愚かしく思われる。


3: テンプレートインスタンスをエクスポートすることにはさらに複雑な事情がある


Hypothesizer 7
テンプレートインスタンスをエクスポートすることについては、さらに複雑な事情がある。

はあ?単に以下のようにするだけではないのか(NavigableLinkedMap'は、ある以前の記事で紹介された、スタンダードmap互換の要素群順序維持マップテンプレートだ)?

theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.hpp

@C++ ソースコード
#ifndef __theBiasPlanet_coreUtilities_collections_NavigableLinkedMap_hpp__
	#define __theBiasPlanet_coreUtilities_collections_NavigableLinkedMap_hpp__
	
	~
	
	namespace theBiasPlanet {
		namespace coreUtilities {
			namespace collections {
				template <typename T, typename U, typename W = less <T>> class __declspec (dllexport) NavigableLinkedMap {
					~
				}
			}
		}
	}
}

いいや。それはうまくいかない。

いくつかのことを明確にしよう。

第1に、テンプレート自体(それが、クラステンプレートであろうがファンクションテンプレートであろうが)は、オブジェクト的オブジェクトではなく、青写真であり、したがって、テンプレートをエクスポートするということは決してない、テンプレートインスタンスをエクスポートすることはあるかもしないが。

第2に、クラステンプレートの特殊化はクラスであり、それはやはり青写真であり、決してエクスポートされることはない。

第3に、ファンクションテンプレートがインラインされる時は、ファンクションオブジェクトは生成されない、したがって、エクスポートされるべきものは何もない。

第4に、もしも、他の生成物にファンクションテンプレートをインスタンス化させるのであれば、当該DLL内には何のオブジェクトも生成されず、したがって、エクスポートを行なう必要はない。

したがって、当該DLLがファンクションテンプレートをインスタンス化するケースのみのことを考えればよい。

インスタンス化は明示的にも暗黙的にも行なえるが、インスタンスをエクスポートするためには、以下のように、それを明示的に行わなければならない、私が知る限り。

'theBiasPlanet/coreUtilities/templatesInstantiator/TemplatesInstantiator.cpp'

@C++ ソースコード
#include "theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.tpp"
#include "theBiasPlanet/coreUtilities/visualCplusplusSpecificHeaders/VisualCplusplusSpecificDefinitions.hpp"

using namespace ::theBiasPlanet::coreUtilities::collections;

template class __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ NavigableLinkedMap <string, int>;
template __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ pair <NavigableLinkedMap <string, int>::iterator, bool> NavigableLinkedMap <string, int>::emplace <string, int> (string && a_argument0, int && a_argument1);

注意すべきだが、'template class __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ NavigableLinkedMap <string, int>;'は、クラステンプレートをインスタンス化しているかのように見えるが、違う、それは、本当には、そのクラステンプレートのいくつかのメンバーをインスタンス化している。

それはともかく、それは、当該DLLからシンボル群をエクスポートする場合だけのものである。外部生成物は、それらのシンボルをインポートするために、何をすればよいのか?

実のところ、外部生成物は、以下のコードを持つべきである。

@C++ ソースコード
#include "theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.hpp"
#include "theBiasPlanet/coreUtilities/visualCplusplusSpecificHeaders/VisualCplusplusSpecificDefinitions.hpp"

using namespace ::theBiasPlanet::coreUtilities::collections;

template class __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ NavigableLinkedMap <string, int>;
template __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ pair <NavigableLinkedMap <string, int>::iterator, bool> NavigableLinkedMap <string, int>::emplace <string, int> (string && a_argument0, int && a_argument1);

注意すべきだが、'theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.tpp'をインクルードすることはできず、'theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.hpp'をインクルードするのだ: それが、'__declspec (dllimport)'修飾を使用する際のルールであり、そもそも、当該DLLからテンプレートインスタンスをエクスポートすることの要点は、'tpp'ファイルは、当該DLLのエンドユーザーには公開されないということである。

外部生成物内に、上記コードを直接に含む'cpp'ファイルを新たに作成することはできるが、私は、そのソースファイルを当該DLL内に以下のように作成して、外部生成物にそれをインクルードさせる。

'theBiasPlanet/coreUtilities/templatesInstantiator/TemplatesInstantiator.cpp'

@C++ ソースコード
#ifdef __theBiasPlanet_coreUtilities__
	#include "theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.tpp"
#else
	#include "theBiasPlanet/coreUtilities/collections/NavigableLinkedMap.hpp"
#endif
#include "theBiasPlanet/coreUtilities/visualCplusplusSpecificHeaders/VisualCplusplusSpecificDefinitions.hpp"

using namespace ::theBiasPlanet::coreUtilities::collections;

template class __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ NavigableLinkedMap <string, int>;
template __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ pair <NavigableLinkedMap <string, int>::iterator, bool> NavigableLinkedMap <string, int>::emplace <string, int> (string && a_argument0, int && a_argument1);

そして、以下のようなソースファイルを外部生成物内に作成する。

'theBiasPlanet/unoUtilities/anotherProjectTemplatesImporters/CoreUtilitiesTemplatesImporter.cpp'

@C++ ソースコード
#include "theBiasPlanet/coreUtilities/templatesInstantiator/TemplatesInstantiator.cpp"

ふーむ、'cpp'ファイルをインクルードするというのには若干、抵抗を覚えるが、そこには妥協をした。

ひゅー、Visual C++の、シンボルが明示的にエクスポートされるように要求するという仕様は、結構面倒な処置をともなう . . .


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>