とまと あんらいぷ…

エンジニアの活動記録とかつぶやきとか

GitHub
スポンサードリンク

C#からC++DLLを呼び出してマーシャリングの動きを確認した(前編)

C#からC++のDLLを呼び出す前に必要な理解


C#からC++で作成されたDLL(アンマネージDLL)の呼び出し時における
マーシャリングに関する考え方、記載方法をまとめてみました。

大前提として、.NETとは無縁のC++で作成されたDLLを呼び出す方法を記載しています。
また、開発環境はMicrosoft Visual Studio 2010としています。

そもそもC#という言語は.NET Frameworkというフレームワーク上で動いていて
利用したメモリの開放や、細かなメモリ操作は.NETに丸投げすることで
プログラマは利用資源を気にすること無く「ユースケースとしてやりたいことを素直に書く事ができる」
言語だと認識しています。

一昔前であれば、ただのテキストファイルを読み込むにあたって

1.まずは読み込みに必要なメモリ領域を確保
2.テキストファイルを開く
3.テキストファイルを読み込む(文字コードの指定等も含まれる)
4.テキストファイルを閉じる
5.確保したメモリ領域を開放する

このような処理をプログラマが自分ですべて書かなくてはいけませんでした。
ユースケース的には2.3.4でよいのに、1と5が付随してくるのですね。
.NET上で動く言語であれば、C#にしろVBにしろファイルを読み込みするクラスを探して
new ってやれば、メモリ確保と開放を自動的に行なってくれます。

この自動的に良きにはからってくれる.NETで作ったプログラムコードの事を
マネージドコードといい、マネージドコードで作られたDLLの事をマネージドDLLと呼びます。

逆にC++等で作られた、メモリの管理はプログラマの責任に於いて行なうプログラムコードの事を
アンマネージドコードといい、アンマネージドコードで作られたDLLの事をアンマネージドDLLと呼んでいます。
ネイティブコードとも呼ばれたりしていますが、
.NETを語る時アンマネージド・マネージドは.NETから見た呼び方ですが
ネイティブコードは、何にとってネイティブなのか定義が定かじゃないので
このブログではアンマネージドと記載します。
(ネット上ではマネージドの「ド」があったりなかったりです)

また、Microsoft社が提供するアプリケーション開発に使われてきた既存のWindowsテクノロジ
――つまりは後で出てくる"user32.dll"のようにWindows APIと呼ばれるもの――
と、.NETとのやり取りを「COM相互運用」という呼び方をしています。

では早速ですが、DLLの呼び出し方法を見て行きましょう。

アンマネージドDLLの呼び出し


C#でプロジェクトを作る時、同じ.NET上で動くDLLであれば「参照設定」を行なうことで
DLL内部にあるクラスやメソッドは、あたかも同一ソリューション内のメンバとして扱うことができます。

ところがアンマネージドDLLはそもそも構造が違うので
C#で使う時は、アンマネージドDLLをマネージドDLLとして扱う事を宣言して呼び出す必要があります。

例えばこんなコードです

これは、ウインドウハンドルを返すWindowsAPIを呼び出すサンプルコードです。

2行目から見て行きましょう。
まずDllImport部分で、"user32.dll"という外部DLLの呼び出しを行なうことを宣言します。
EntryPoint = "FindWindow" というのは、"user32.dll"に存在する関数名です。
3行目はC#で使うメソッド名を定義します。EntryPoint部分で、本当の関数名を指定してるので
ここではC#で使うメソッド名を自由につけてOKです。
C#側では外部DLLのメソッド呼び出しは stati メソッドとして行います。

引数について、string 型で宣言していますが、C言語、C++言語には基本的にstring型なんてありません。
ではどうしてC#でstring型を宣言するのか?

FindWindow関数の説明が行われているサイトを見ると
FindWindow関数
引数はLPCTSTR 型と書いています。

最初にも記載しましたが、そもそもC#とC++は別言語の世界のお二方で、
メモリの扱いも基本的な型も全く違います。

だから、引数のやり取りや戻り値をするときには、
お互いが分かり合えるように、型を翻訳してあげる必要があります。
これを「マーシャリング」と言います。

マーシャリング


相互運用マーシャリングの概要

にとても詳しく書いてるのですが
概念だけを抽出するとC#とC++は、全然違う言語なのですが
マーシャラというものが2つ言語間を取り持ってくれるわけです。
そのおかげで全然違うプログラムのDLLを呼び出すことができます。

ただ、何でもかんでも自動やってくれるわけではなくて
プログラム上でマーシャラに対して指示が必要なんですね。
さっきの例で言うと、EntryPointの指定とか、引数の指定とか。
マーシャラは指示された通りに言語間の仲介を行なうわけですから
誤った指示を出せば当然エラーを引き落とします。

為替みたいなもんでしょうか?
1ドル100円のレートの時に
100円必要なAさん(C++)、1$を持っているBさん(C#)。
でもお互い言葉は通じないのでMさん(マーシャラ)にお願いする。
Mさんは言われたとおりに動きます。
Bさんが、マーシャラに50セントしか渡さずに、「Aさんと100円交換してきて」
なんて言おうものなら、Aさんは怒って両替してくれないわけです。
同じ事がDLL呼び出しで発生します。

上記コードでは
LPCTSTR型に対してstringを渡しています。
LPCTSTRを検索すると、コンパイルの方法によって
const WCHAR* だったりconst char*だったり変化する型だということが分かります。

TCHARとかLPCTSTR、LPTSTRって何???

DllImportの説明サイトを見ると
"user32.dll"はWCHAR*を利用できる事が判断できます。

DllImport 属性の使用

[DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)]
この「CharSet = Unicode」のところですね。Unicodeを使うという指定があるので
"user32.dll"の中のLPCTSTRは WCHAR*であることが分かります。

次に、お互いの型のマッピング(マーシャリング)情報を探します。

非 blittable 型とマーシャリングのサポート

このサイト内に、C#のstringは
アンマネージコード(C++)のWCHAR *であることがわかります。

上記の事から、C#側から呼び出すFindWindow関数の引数をstringとして定義しているわけです。

C#からアンマネージドDLL(ネイティブDLL)を呼び出す時は
このようにアンマネージ側の関数の実装を調べて、
それに見合う型をC#側で宣言して利用していくという手順を踏むことになります。

次の記事では、実際にC++でDLLを作って、その呼び出し方法を確認していきます。
▼この記事を読んだ方は、こんな記事も読んでいます。▼

スポンサードリンク

テーマ:プログラミング - ジャンル:コンピュータ

コメント

ネイティブコード

>ネイティブコードは、何にとってネイティブなのか
たぶん、マシン・アーキテクチャに対してだと思います。推測ですがw。生成されるコードがマシンのアーキテクチャに互換性を持つ=ネイティブ、ということかと。ただ、それだと.NETやJavaはalien code/foreign codeという呼び名になりそうです(nativeの対義語はalien/foreignだそうで…一応調べました)。はじめはなんかイマイチだと思ったのですが、alien codeってなんかカッコいいかも。なんか、davinchi code的な・・・解いてはいけない、みたいな。

davinchi codeでクスッ

コメントありがとうございます。
なるほど。アーキテクチャに対して互換性を持つ。ですか。
確かにネイティブと対にしている割には、マネージコードとはしっくりこないですね。
だからいつの間にか、アンマネージ/マネージという呼び名になったのかも?

alien code の方が。かっこいい響きですね。うん。
ところで alien code の発音が分からなくてGoogle先生に問い合わせてみたところ、
発音はとてもかっこいいのに
「外国人コード」なんて翻訳するものですから途端にイマイチ感が復活してきました・・・

  • 2014/07/18(金) 20:51:34 |
  • URL |
  • 管理人 #z9jllgzo
  • [ 編集 ]

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://dalmore.blog7.fc2.com/tb.php/61-21c7f3f1
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad