#pragma onceを忘れたとかそんな話ではない。
すべてCLIで、testclidll.dll内にクラスClass1を作成し、それをexe側に参照を追加してClass1を使おうとしたとき、
error C2011: 'testclidll::Class1' : 'class' 型の再定義
が発生する
再現手順 (Visual Studio 2017 community)
1.[ファイル] - [新規作成] - [プロジェクト] から、「CLRコンソールアプリケーション」を作成(名前をexe_and_dll_testとする)
2.[ファイル] - [新規作成] - [プロジェクト] から、「クラスライブラリ」を作成(名前をtestclidllとする)
3.testclidll.hを以下のようにする
// testclidll.h #pragma once using namespace System; namespace testclidll { public ref class Class1 { int c; public: void Set(int d); int Get(); }; }
4.testclidll.cppを以下のようにする
namespace testclidll { void Class1::Set(int d){ c = d; } int Class1::Get(){ return c; } }
5.exe側を以下のようにする
#include "stdafx.h" using namespace System; #include "../testclidll/testclidll.h" int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
6.DLLをコンパイルする
7.ソリューションエクスプローラから、exe側のプロジェクトを右クリックし、[参照...]を選択
プロパティページから、[共通プロパティ]-[Frameworkと参照]で「新しい参照の追加...」ボタンを押し、コンパイルしたdllを追加する
8.exe側をコンパイルする
2>e:\mycodes\exe_and_dll_test\exe_and_dll_test\../testclidll/testclidll.h(12): error C2011: 'testclidll::Class1' : 'class' 型の再定義
2> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
2>exe_and_dll_test.cpp(14): error C2027: 認識できない型 'testclidll::Class1' が使われています。
2> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
2>exe_and_dll_test.cpp(14): error C2227: '->Set' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
2>exe_and_dll_test.cpp(18): error C2027: 認識できない型 'testclidll::Class1' が使われています。
2> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
2>exe_and_dll_test.cpp(18): error C2227: '->Get' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
まず、C/C++ではdllをリンクするときにも必ずヘッダファイルをincludeしなければならないが、.NETではdllが情報を持っている(?)ので、そこへさらにインクルードをかけると定義が二重に行われる(のだと思う)。従って、
#include "stdafx.h" using namespace System; //#include "../testclidll/testclidll.h" //このincludeはしてはいけない int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
再現手順
1. testclidll.hを、以下のように修正する
// testclidll.h #pragma once using namespace System; namespace testclidll { enum ReturnT{ //型を追加 True,False, }; public ref class Class1 { int c; public: void Set(int d); int Get(); ReturnT isOdd(); //メンバが奇数かどうかを返す関数を追加。戻り値はユーザー定義型のReturnT }; }
2.testclidll.cppを以下のように修正する
// これは メイン DLL ファイルです。 #include "stdafx.h" #include "testclidll.h" namespace testclidll { void Class1::Set(int d){ c = d; } int Class1::Get(){ return c; } //メンバが奇数かどうかを返す関数の定義 ReturnT Class1::isOdd(){ if( c % 2 != 0 ) return True; return False; } }
3.exe側を以下のように修正する
#include "stdafx.h" using namespace System; //#include "../testclidll/testclidll.h" int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); testclidll::ReturnT t = c->isOdd();//奇数かどうかの判定を追加 Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
4.testclidllをコンパイルする
5.exe側をコンパイルする
コンバイルエラー
2>exe_and_dll_test.cpp(16): error C2039: 'ReturnT' : 'testclidll' のメンバーではありません。
2>exe_and_dll_test.cpp(16): error C2065: 'ReturnT' : 定義されていない識別子です。
2>exe_and_dll_test.cpp(16): error C2146: 構文エラー : ';' が、識別子 't' の前に必要です。
2>exe_and_dll_test.cpp(16): error C2065: 't' : 定義されていない識別子です。
これは、クラスの情報は参照したdllの中にあるが、enumのような定数の情報は入っていない(?)ので、定義がないと怒られる。そこで、testclidll.hのincludeを復活させてみる。
#include "stdafx.h" using namespace System; #include "../testclidll/testclidll.h" //やっぱりいるのか? int main(array<System::String ^> ^args) {
1>e:\mycodes\exe_and_dll_test\exe_and_dll_test\../testclidll/testclidll.h(14): error C2011: 'testclidll::Class1' : 'class' 型の再定義
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(14): error C2027: 認識できない型 'testclidll::Class1' が使われています。
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(14): error C2227: '->Set' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
1>exe_and_dll_test.cpp(16): error C2027: 認識できない型 'testclidll::Class1' が使われています。
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(16): error C2227: '->isOdd' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
1>exe_and_dll_test.cpp(18): error C2027: 認識できない型 'testclidll::Class1' が使われています。
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(18): error C2227: '->Get' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
当然のように最初のエラーが再発する。
enumの定義を別のヘッダファイルへ移動する
testclidllプロジェクト内、defvalheader.hを追加
namespace testclidll { enum ReturnT{ True,False, }; }
それに伴い、dll本体側であるtestclidll.hからenumの定義を消し、includeにする
// testclidll.h #pragma once #include "defvalheader.h" //ReturnTの定義を読み込む using namespace System; namespace testclidll { public ref class Class1 { int c; public: void Set(int d); int Get(); ReturnT isOdd(); }; }
exe側では、testclidll.hではなく、defvalheader.hを読み込む
#include "stdafx.h" using namespace System; #include "../testclidll/defvalheader.h" int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); testclidll::ReturnT t = c->isOdd(); Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }