template 関数をDLL にしたいのですが

[戻る]
Sum 2011/09/03(土) 12:04:47
よろしくおねがいします。
環境 Windows 7 Visual Studio 2010 pro

DLL からtemplate 関数を導出するのに次のテストコードを試しているのですが。
下記エラーが発生します、ご教示お願いします。

main.obj : error LNK2019: 未解決の外部シンボル "public: void __thiscall 
NCPrint::CPrint::operator()<class std::basic_string<char,struct 
std::char_traits<char>,class std::allocator<char> > >(class 
std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> 
> const &)" (??$?RV?$basic_string@DU?$char_traits@D@std@@V?
$allocator@D@2@@std@@@CPrint@NCPrint@@QAEXABV?$basic_string@DU?
$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) が関数 "class NCPrint::CPrint 
__cdecl std::_For_each<class std::basic_string<char,struct 
std::char_traits<char>,class std::allocator<char> > *,class NCPrint::CPrint>
(class std::basic_string<char,struct std::char_traits<char>,class 
std::allocator<char> > *,class std::basic_string<char,struct 
std::char_traits<char>,class std::allocator<char> > *,class NCPrint::CPrint)" 
(??$_For_each@PAV?$basic_string@DU?$char_traits@D@std@@V?
$allocator@D@2@@std@@VCPrint@NCPrint@@@std@@YA?AVCPrint@NCPrint@@PAV?
$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@0V12@@Z) で参照されまし
た。

// DLL ファイル PrintDll.h
#ifndef CPRINT_H_
#define CPRINT_H_

#include <ostream>

namespace NCPrint {
    class __declspec(dllexport) CPrint
    {
    public:
        /** @brief Constructor */
        explicit CPrint(std::ostream& lhs);

        /** @brief Operator overload */
        template <typename T> inline void operator()(const T& lhs);

    private:
        std::ostream& os;
    };
}
#endif    // end of CPRINT_H_

// DLL ファイル PrintDll.cpp
#include "PrintDll.h"

namespace NCPrint
{
    CPrint::CPrint(std::ostream& lhs) : os(lhs) {}

    template <typename T> inline void CPrint::operator()(const T& lhs)
    {
        os << lhs << endl;
    }
}


// EXE ファイル main.cpp
#include <iostream>
#include <algorithm>
#include <vector>
#include "PrintDll.h"

using namespace std;

int main()
{
    vector<string> Day;
    Day.push_back("Monday");
    Day.push_back("Tuseday");
    Day.push_back("Wednesday");
    Day.push_back("Thurday");
    Day.push_back("Friday");
    Day.push_back("Saturday");
    Day.push_back("Sunday");

    for_each(Day.begin(), Day.end(), NCPrint::CPrint(cout) );
    
    return 0;
}

tetrapod 2011/09/03(土) 16:44:19
×template 関数
○関数 template
関数の鋳型 (function template) は「関数ではない」ことは理解できているかな?

PrintDll.cpp 中の operator() は「まだ関数ぢゃない」ため公開不可能。

そもそも inline にしたいんだったら DLL 化することを考えちゃだめだし、
どう書いたらよいかは、どうしたいか次第。

Sum 2011/09/03(土) 22:26:35
tetrapod さん。お世話になります。

鋳型をDLLにはできません、当然ですね、メモリを確保した状態で中身はないのですからね、勉強不足でした。
inline はごめんなさい消し忘れです。すみませんでした・・・orz

それから、DLLでoperatorのオーバーロードは無理なんですか・・・ということはfor_eachとかtransformとか、アルゴリズム系の
実装はできないということですね?・・・(´・ω・`) ショボーン.です。

想像するに、仮想関数もDLLにするのは面倒臭い感じかな・・・・?
オブジェクト指向型言語とDLLは相性悪いね、結論ですが
そもそも、ダイナミック リンク ライブラリィはオブジェクト指向だとか
templateなどは考えず、手続き型言語で導出できる関数を扱えば間違いない?
この認識でいいですか?

tetrapod 2011/09/04(日) 07:50:15
何か誤解がありそう・・

> メモリを確保した状態で中身はないのですからね
メモリを確保? 誰がどこに、という解説がないので意味不明な文書になっている。
C++ の function template はその仕様上
「コンパイル時にコンパイラが関数鋳型の内容をコンパイラの作業メモリに」覚える
「template parameter に具体的型や数値が指定されたら」そこで関数になる
わけだ。

提示ソースコードをコンパイルした状態では、後者の指示がない。
つまり printdll.obj 中に、
template<T> NCPrint::CPrint::operator() という鋳型が生成されることはない。
DLL のロードの際にコンパイルされなおすこともない(ので提示結果のとおり)

printdll.cpp 中で <char> とか <std::string> とか具体的に与えられると
NCPrint::CPrint::operator()<char>
NCPrint::CPrint::operator()<std::string>
のように「関数」に具現化されて、これらは .obj や .dll に入れることができる。

> DLLでoperatorのオーバーロードは無理なんですか・・・
できるよ。だれができないって言ったの?
function template を DLL 化することはできないけど(提示例)
ふつーの operator のように単純な関数であったり
具現化済み function template (は既に関数なので) DLL 化できる。

DLL と EXE でコンパイラのメーカー違いとかバージョン違いとかあると
非互換になる可能性もあるし、俺なら DLL 化するのは慎重に行うな。

Sum 2011/09/04(日) 12:57:48
お世話になります。
勘違いがあったようです、ご指摘ありがとうございます。

>printdll.cpp 中で <char> とか <std::string> とか具体的に与えられると
>NCPrint::CPrint::operator()<char>
>NCPrint::CPrint::operator()<std::string>
>のように「関数」に具現化されて、これらは .obj や .dll に入れることができる。
具体的な書き方を教えて頂けないでしょうか。

tetrapod 2011/09/04(日) 14:53:18
step1:
非 template つまり、通常のメンバ関数なら DLL export/import できる?
できるのなら、

step2:
(step1+)明示的テンプレート具現化すればよいだけだよ。

いきなりソース書くのは回避しておこう。
まずは自分で試してみておくんなまし。

仲澤@失業者 2011/09/05(月) 11:02:36
ヒント:
テンプレートを具現(クラス、関数)化する一つの方法としてtypedefが使えます。
MFCのCArrayの例でやると、
   typedef CArray< CString>  STR_Ary;
   STR_Ary  StrAry;
   StrAry.Add( CString(_T("一番目")));
とか。

tetrapod 2011/09/05(月) 12:27:42
それは単純に「普通に使ったから具現化された」例であって明示具現化ではない

template<typename T> void testfunc(T t) { ... } // testfunc<T>
があるとき、強制的に testfunc<int> をコンパイル時生成させるには
template void testfunc(int t);
と書けばいい。 ISO/IEC 14882:1998 14.7.2 Explicit instantiation
# template void testfunc<int>(int t); でもいい。が、冗長。

ここで <> が無いことに注意。
template<> void testfunc(long t); は specialization になるので意味が違う。
同 14.7.3 Explicit specialization

tetrapod 2011/09/05(月) 12:31:56
そもそも論をいうなら
・ template はコンパイル時に解決されるものである
・ DLL はコンパイル済みバイナリである
という点で DLL に template の具現化実体を置こうというのが仕様矛盾している。
俺ならこういう設計は最初から選択肢に入らない。
それでもあえて DLL に template の具現化実体を置くにはどうすればいいか?
という純粋技術的興味の問題として扱うものとして。

あとは DLL の export 側と import 側の問題でしかないので
----test.h----
#if !defined(TEST_T_CALLING_CONVENTION)
#define TEST_T_CALLING_CONVENTION __declspec(dllimport) __stdcall
#endif

struct test_t {
 template<typename T>
 void TEST_T_CALLING_CONVENTION operator() (const T& t);
};
----testdll.cpp----
#define TEST_T_CALLING_CONVENTION __declspec(dllexport) __stdcall
#include "test.h"
template<typename T> void test_t::operator ()(const T &t) {
 std::cout << t;
}
template void test_t::operator ()(const int&);
----testexe.cpp----
#include "test.h"
int main() {
 test_t t;
 t(1); // はうまく動く
 // t(1.0); // はコンパイル通るがリンクエラー
}
----
__stdcall をつけてみたものの実際には __thiscall になるような気が・・・

仲澤@失業者 2011/09/05(月) 17:25:57
>それは単純に「普通に使ったから具現化された」例であって明示具現化ではない

ややっ、これは失礼m(__)m。
テンプレートの明示的具現化をDLL内で行う方法としてあげたわけではありません。
DLL内では具現化しなければならないのは自明だと考えてました。

> t(1); // はうまく動く

は、ちょっと驚きです。
やってみたんですが、何が悪いのか自分とこのVS2008ではリンクエラー(しくしく)。

tetrapod 2011/09/05(月) 17:39:41
いやもちろん testdll.lib を追加しなきゃ・・・

Sum 2011/09/05(月) 21:29:24
お世話になります。
もう少し考えさせてください。

Sum 2011/09/05(月) 22:57:41
>step1:
>非 template つまり、通常のメンバ関数なら DLL export/import できる?
>できるのなら

step 1 ですがこのように実装しましたがどうでしょうか?
駄目があれば指摘してください。

// Proejct name templateTestDll
// Filename        PrintDll.h

#ifndef CPRINT_H_
#define CPRINT_H_
#include <iostream>

    class __declspec(dllexport) CPrint
    {

    public:
        explicit CPrint(std::ostream& os_) : os(os_){};
        void CPrint::operator()(std::string& os);

    private:
        std::ostream& os;
    };
#endif    // end of CPRINT_H_

// Proejct name templateTestDll
// Filename        PrintDll.cpp

#include "PrintDll.h"

using namespace std;

void CPrint::operator()(std::string& os)
{
    cout << os.c_str() << endl;
}

// Proejct name templateTestExecDll
// Filename        main.cpp

#include <algorithm>
#include <vector>
#include "PrintDll.h"
#pragma comment(lib, "templateTestDll.lib")

using namespace std;

int main()
{
    vector<string > Day;

    Day.push_back("Monday");
    Day.push_back("Tuseday");
    Day.push_back("Wednesday");
    Day.push_back("Thurday");
    Day.push_back("Friday");
    Day.push_back("Saturday");
    Day.push_back("Sunday");

    for_each(Day.begin(), Day.end(), CPrint(cout) );

    return 0;
}

tetrapod 2011/09/06(火) 08:53:44
class に __declspec(dllimport) をつけるということは、
全メンバ関数を DLL に import/export するということ(とほぼ同等)だ。
# function template はその原理上 import/export されないし、できない
っていうか 常に dllexport だと DLL 化できていないんだが?

当初議題 09/03 はメンバ関数1個だけを import/export と、俺は解釈したので、
09/05 の実装だと全メンバ関数になり、若干ずれてきている気のせいがするが、
まあそこへんは俺の勝手な解釈なので単に気のせいかも。
全メンバ関数を import/export したいのであれば 09/05 の実装で適切だよ。
(dllimport/dllexport を適切に使い分ける前提で)
(template 化しようとすると話は変わってくる)

# 重箱の隅ツッコミがほしいのなら
# wcout に出力したく (wchar_t ベースに書き換えたく) なったり
# string / basic_ostream の allocator や traits を変えたい要望に対応するには
# CPrint 自体を template ベースにする= DLL 化は無し
# という選択をするしかないだろう

というわけで「結局何がやりたいのか」が読者に伝わっていないのが伸びてる原因。
技術的興味を満たすにはスレッドが長くてもかまわないんだけど。

Sum 2011/09/07(水) 19:33:11
[[解決]]
ありがとう。

古いスレッドには追加発言できません。