そうしなければ、シンボル群は、DLLの外からは不可視になります。いかに、'__declspec (dllexport)'および'__declspec (dllimport)'をコントロールするか。
話題
About: C++
About: Visual C++
この記事の目次
- 開始コンテキスト
- ターゲットコンテキスト
- オリエンテーション
- 本体
- 1: Dll由来のシンボル群が解決されない? . . . なぜVisual C++でだけ?
- 2: 基本事項と複雑な事情、そして私の解決策
- 3: テンプレートインスタンスをエクスポートすることにはさらに複雑な事情がある
開始コンテキスト
- 読者は、C++の基本的知識を持っている。
- 読者は、C++テンプレートとは何であるか、およびテンプレートインスタンス化(明示的または暗黙的)とインライン化の区別を理解している。
- 読者は、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++の、シンボルが明示的にエクスポートされるように要求するという仕様は、結構面倒な処置をともなう . . .