Question

tarc182 on Wed, 17 Dec 2014 07:51:29


C#からC++クラスライブラリを呼びたい、この手の投稿はたくさんあるのですが、

今回は、その逆、MFC(アンマネージ)からC#のクラスライブラリを呼ぶ方法です。

知りたいのは、後述のWrapperとしてのMFCクラスライブラリ(DLL;アンマネージ)の作り方、実装内容です。

環境:VisualStudio2012Pro

作りたい最終の姿:MFCアプリ(アンマネージ)⇔MFCクラスライブラリ(DLL;アンマネージ)⇔C#クラスライブラリ(DLL;マネージ)

→MFCアプリからMFCクラスライブラリDLL(Wrapper)を呼び、そこから、C#クラスライブラリの関数を呼ぶ。

COM経由でない方法を取りたいです。

COMは、タイプライブラリをシステムに登録するひと手間が必要なようなので、この環境を別のPCにインストールする時に

その登録が必要(この認識に間違えあればご指摘ください)なのが、許容できないためです。

MFCの経験低なので。。。できれば、一通りサンプルでもご提示していただけると幸いです。よろしくお願いいたします。


Sponsored



Replies

仲澤@失業者 on Wed, 17 Dec 2014 08:38:52


あんまり詳しくありませんが、
たしかに情報は少ないですね。
以下などは、参考になりますでしょうか。

http://www.codeproject.com/Tips/695387/Calling-Csharp-NET-methods-from-unmanaged-C-Cplusp

tarc182 on Wed, 17 Dec 2014 09:11:48


仲澤さん、早速の情報ありがとうございます。イメージはだいぶ湧いてきました。

今困っているのはまず、C#DLLで使用しているnamespaceをWrapperDLL側で

using namespace aaa::bbb:ccc;

BUILDエラー(error C2653:'aaa':識別子がクラス名でも名前空間でもありません。

                   error CC2871:'ccc':この名前を指定された名前空間が存在しません。

と怒られ、ソリューションのプロパティで、参照の追加をC#のDLLを参照追加しようとすると、

「.NETアセンブリまたは登録されたActiveXコントロールでないため、ファイル・・・.dllに

参照を追加できませんでした」と行き詰ってしまいました。根本的にやり方が違うでしょうか。

情報あったら、お願いします。

佐祐理 on Wed, 17 Dec 2014 10:53:06


MFCとは無関係です。C++からC#を含む.NET DLLを呼び出す方法は次の2種類です。

  • C++側もC++/CLIとしてマネージコードにする
  • .NET DLLをCOM登録して、C++側からはCOM呼び出しをする

一応、登録を必要としない COM 相互運用機能も用意されていますが、そもそもC++からCOM呼び出しには慣れていますでしょうか?

tarc182 on Wed, 17 Dec 2014 11:16:26


佐祐理さん、ありがとうございます。

仕事で製品になるような環境でCOMは試したことはないです(慣れていないです)。

おっしゃる「登録を必要としないCOM相互運用機能」の内容を確認してみます。

(たぶん理解に苦労するので、また質問させてください。。。)

sygh on Wed, 17 Dec 2014 11:38:21


C#のDLLで具体的に何をしようとしているのでしょうか?

WindowsフォームのGUIコントロールライブラリですか?

WPFのGUIコントロールライブラリですか?

それともGUI要素を一切含まないロジックのみのマネージクラスライブラリですか?

もしGUIコントロールだとすれば、それはMFC側のネイティブコントロールと並列して存在し続けるようなモードレスウィンドウ/モードレスコントロールですか? それとも特定のトランザクション期間のみで表示するだけのモーダルウィンドウですか?

それらの条件によって、提示できるコード例(C++/CLIによるMFC向けラッパークラス)も変わってきます。

なお、「ソリューションのプロパティで、参照の追加……」とありますが、「プロジェクトのプロパティで、[新しい参照の追加]ボタンによって」の間違いではないですか?

また、MFCクラスライブラリは「アンマネージ」を想定しているようですが、COM公開しない方法であればアンマネージとすることはできません。C++/CLIを使って中継用の混合コードを書く必要があります。その代わり、MFCのEXE側に公開するインターフェイス(グローバル関数、列挙体、構造体、クラス)をネイティブに絞ってラップすることで、EXE側はネイティブのままとすることができます。まず、中継に入るMFCのDLLを「MFC拡張DLL」として作成し、MFC拡張DLLプロジェクトのプロパティにおいて「構成プロパティ」→「全般」→「共通言語ランタイム サポート」の欄を、「共通言語ランタイム サポート (/clr)」に設定することで、C++/CLIを使って混合コードを書けるようになります。ラッパーの書き方は前述のとおり目的に依ります。

それと、C#クラスライブラリのプロジェクトは、同一ソリューション内にありますか? それとも別のビルド済みアセンブリとして提供されているものですか? さらに、そのクラスライブラリの.NET Frameworkバージョンはいくつですか?

個人的には、COM公開よりも開発効率の高い/clrを推奨しますが、C#、C++/CLI、およびネイティブC++の違いの理解と使い分けができていないと厳しいです。また、MFCのGUIアプリケーションからWindows FormsやWPFのGUIコンポーネントを使う場合、どういう手段を取ろうとも、MFCにおける相互運用特有の知識(特にメッセージループ共有やスレッドに関するもの)が必要になるので、MFC初心者にはかなりハードルの高い内容になると思われます(意図しない動作をした場合、半端な知識ではデバッグできない)。どうしてもEXE側をMFCで作る必要があるのであれば別に止めはしませんが、できるかぎりEXE側もC#で作ってしまったほうがかえって楽だと思います。

tarc182 on Wed, 17 Dec 2014 12:57:38


syghさん、ありがとうございます。

すみません、終業のため改めて返信させていただきます。

なお、EXE側はMFC必須、C#側ではADO.NETでDBアクセスをします。

ちちびんたリカ on Wed, 17 Dec 2014 14:46:59


こことか。

[連載! とことん VC++] 第 8 回 C++/CLI を利用した相互運用 ~ネイティブ C++ から .NET の利用~ 言語: C++

https://code.msdn.microsoft.com/VisualC-ee06b200

ついでに逆方向も貼っておきます。

[連載! とことん VC++] 第 9 回 C++/CLI を利用した相互運用 ~.NET からのネイティブ C++ 資産の再利用~ 言語: C#, C++

https://code.msdn.microsoft.com/VisualC-a1dc1f1d

[C++] C++/CLI を用いて、.NET 対応アプリケーションから MFC 対応クラスを使用する 言語: C++

https://code.msdn.microsoft.com/windowsdesktop/VisualC-howto-76f9cd9e

佐祐理 on Wed, 17 Dec 2014 23:03:08


COM経由の場合は任意のC#クラスが呼び出せるわけではありません。COMの制限を受けます。例えば静的メソッドや引数付きのコンストラクタなどはCOM経由では呼び出せなかったと思います。ですので、そういった制限を踏まえてC#側を設計する必要があります。

C++/CLIについてもC++を知っているからといってすぐに使えるものではありません。ポインターひとつとってもpin_ptrなどC++にはない概念が出てきます。トラッキングハンドルとかも。そして質問者さん自身も認識されていると思いますが情報がほとんど少なく、少ない中でも間違ったものもあります。例えばちちびんたリカさんの挙げられたサイト、9. ネイティブ コード用の文字列と .NET 版の文字列との相互変換なんかも適切とは言えず、方法: System::String の文字にアクセスするの方が適切です。

DBにアクセスする程度であれば、ネイティブコードで実装した方が楽だとは思います。

tarc182 on Sat, 20 Dec 2014 08:51:55


しばらく音信不通で申し訳ありません。しばらく別件で手を付けられず、やっと戻ってきた。
>作りたい最終の姿:MFCアプリ(アンマネージ)⇔MFCクラスライブラリ(DLL;アンマネージ)⇔C#クラスライブラリ(DLL;マネージ)
でなく、MFCアプリ(アンマネージ)⇔CLRクラスライブラリ⇔C#クラスライブラリ(DLL;マネージ)で行うことになりました。
で、最初の仲澤@失業者さんが提示してくれたものを参考に。これは少し手直しして、超簡易なものとしては連携を確認出来ました。
つぎに、本番環境のMFCクラスライブラリ(DLL;アンマネージ)とC#クラスライブラリ(DLL;マネージ)を用意して、最後にMFCアプリ(アンマネージ)のプロジェクトに対し、「参照の追加」をC#DLLとCLRDLLに対し行ったところ、またまた、「.NETアセンブリまたは登録されたActiveXコントロールでないため、ファイル・・・.dllに参照を追加できませんでした」が出てしまいました。超簡易環境(上述)では、でません。
MFCアプリへの設定は、プロパティ-全般で、
 共有DLLでMFCを使う
 共通言語ランタイムサポート(/clr)
を設定。もちろん、ビルドは通っておりますが、DLLの参照追加が出来ていないので、実行してもDLLが見つからないので動かない。
MFCアプリの最終的な機能は、データベースアクセスをC#DAOライブラリ(DLL)→CLRラッパー→MFCアプリで結果をみるものです。
超簡易環境と同じ進め方なのですが、なにがいけないのかわかりません。どなたかお助けを。

tarc182 on Sat, 20 Dec 2014 08:54:37


すみません、間違えあり。

誤:本番環境のMFCクラスライブラリ(DLL;アンマネージ)とC#クラスライブラリ(DLL;マネージ)を用意して

正:本番環境のCLRクラスライブラリ(DLL;アンマネージ)とC#クラスライブラリ(DLL;マネージ)を用意して

要は、最後の方に書いた通り、

C#DAOライブラリ(DLL)→CLRラッパー(DLL)→MFCアプリ

です。

Azulean on Sat, 20 Dec 2014 10:10:22


「C# の DLL を C++ 関連のプロジェクトで扱うためには /clr を有効にし、C++/CLI にしなければならない」という制約がありますが、間に CLR クラスライブラリを作るのであれば、MFC アプリに /clr オプションをつける必要はありません。
(/clr オプションをつけるなら、間の CLR クラスライブラリがいらない…)

ところで、その MFC アプリ は過去の Visual Studio から変換して開発されてきたものなのでしょうか?
VS2012 で作ったものなのでしょうか?

tarc182 on Sat, 20 Dec 2014 10:34:47


Azuleanさん、ありがとうございます。

MFCアプリはVS2012でテスト用に新規に作成したものです。

で、おっしゃる、MFCアプリの/clrを取るとビルドはもちろん通りますが、「コンピュータにxxxx.dll(CLRクラスライブラりのDLLです)が

ないため、プログラムを開始できません。・・・」という実行時のシステムエラーが出ます。なお、リンクの入力には対応するlibファイルを

設定してビルドしています。上述のエラーがでないがために、「参照の追加」をしようとしています(/clrをとっちゃうと、参照追加ができない)。

なにか根本的にやり方が違うでしょうか?

Azulean on Sat, 20 Dec 2014 11:21:58


C# を呼び出す CLR DLL が同じソリューションにあるのであれば、/clr オプションをつけなくても参照追加が可能なはずです。

ソリューションが分離されているのであれば、追加のライブラリファイルとして lib ファイルを指定し、DLL ファイルはビルドイベントでコピーして、exe と同じフォルダーに配置されるように整備する必要があります。

佐祐理 on Sat, 20 Dec 2014 11:24:27


登場人物がよくわかりません。C++ DLLであっても /clr オプションを付けたものであれば C# DLLを呼び出せます。その上で、質問者さんの書かれる「CLRクラスライブラリ」の必要性がよくわかりません。

各DLLは同じディレクトリに配置しているのでしょうか? そうでない場合は、各ロード方法での検索対象のディレクトリに配置する必要があります。(もしくは検索対象のディレクトリを設定する必要があります。)

tarc182 on Mon, 22 Dec 2014 04:53:28


Azuleanさん、佐祐理さん、失礼しております。
だいぶ長くなって、再度整理します(次のステップに行きたい)。
 MFCアプリ(/clrオプション付)⇔C++クラスライブラリDLL(/clrオプション付)⇔C#クラスライブラリDLL
現状、やっとこのつながりで関数が呼べるようになりました。
佐祐理さんおっしゃるようにCLRクラスライブラリにしなくてもよいですね。
また、MFCアプリで「参照の追加」ができないと騒いでいましたが、そもそも
そんな必要なくて、そのアプリから単にC++DLLの関数を呼べばよかっただけでした。
(なにか変なものに固執してたようです。)

ちょっと細かい話、もう少しお助け下さい。
登場人物の呼び方が長いので、MFCアプリ⇔C++WrapDll⇔C#DLL  とします。
例として
 C#DLLの中でClass AAが、またClass BBがあり、BBはメソッドfuncがあるとします。
 C++WrapDLLのラップする中身は、
  AA ^aa = gcnew AA();
 とか、
  BB ^bb = gcnew BB();
  bb->func();
のイメージです。
知りたいのは、そのC++WrapDLLのラップする中身について、aaやbbなるオブジェクトは
 C++WrapDLL
 MFCアプリ
どちらで管理されるべきでしょうか? もうちょっと具体的に申し上げると、
 <MFCアプリでオブジェクトの実体(単なるポインタ?)を管理するイメージ>
  aaやbbのオブジェクトは、MFCアプリが呼び出すC++WrapDLLの関数の引数なりで
  MFCアプリに返却し、MFCアプリでオブジェクトを管理する。
  MFCアプリがBBのfunc()を使いたいときは、bbが引数にあるC++WrapDLLの関数をつくり、
  その中で、func()を呼ぶ。
 <C++WrapDLLの中で管理する>
  すみません、こちらの管理イメージが湧いていないです。

C++WrapDLLでC#DLLをラップする設計イメージがよくわかっていません。
普通はこうするんだよ、っていうやり方をご教示ください。
ご面倒ですみませんが、よろしくお願いいたします。

kekyo on Sun, 18 Jan 2015 12:28:08


こんにちは。

どちらの方法がより適切か、という例が分かりませんでしたが、私が実装した場合の方法の例を挙げます。
中間のWrapperDLLはC++/CLIで実装しますが、定義するのは通常のC++クラスです。

ここで、そのクラスのメンバーにgc_root<T>を使って、C#側のクラス(AAやBB)の参照を保持します。
例えば:

class AAWrapper
{
private:
  gcroot<AA^> aaPtr_;
public:
  AAWrapper()
    : aaPtr_(gcnew AA())
  {
  }
  ~AAWrapper() throw()
  {
  }
  int func(const wchar_t *pArgument)
  {
    return aaPtr_->func(gcnew System::String(pArgument));
  }
};


gc_rootで参照が保持されると、そのインスタンスはGCが回収しなくなります。クラスが破棄される時、gc_rootも破棄されるので、そこでAAのインスタンスは宙に浮き、最終的にはGCが回収します。

このように実装して、MFC側からはこのC++クラスを使えばよいでしょう。クラス全体をdllexportするか、MFC側でvcclr.hをインクルードしたくないのであれば、純粋インターフェイス定義をヘッダに定義して、COMっぽく分離すれば、そのような依存を排除出来ます。