VC++ COMの呼び出し

Visual C++ Express 2010で、ATLを使わずに、COMクライアントを作成してみた。

今回は、iTunesから現在再生中の曲のタイトルを取得するクライアントを例に説明する。

最初に、#importを使ってiTunesのインターフェスの宣言を取り込む

書き方は以下のとおり。

#import "c:\\program files\\itunes\\iTunes.exe" named_guids raw_interface_only

続いて、COMの初期化と終了処理。ここは、お決まりのCoInitialize() CoUninitialize()を書く

int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(0);
// ここにCOM呼び出しの処理を書く
::CoUninitialize();
}

最初は、ラッパークラスなどを使わない方法のコードを示す。

void iTunes1(){
HRESULT hResult;
iTunesLib::IiTunes *pItunes = NULL;
iTunesLib::IITTrack *pTrack = NULL;
BSTR name = NULL;
char buf[256];

//インスタンスの作成
hResult = ::CoCreateInstance(iTunesLib::CLSID_iTunesApp, NULL, CLSCTX_LOCAL_SERVER,
iTunesLib::IID_IiTunes, (LPVOID *)&pItunes);

if( ! SUCCEEDED(hResult) ) {
printf("ERROR occured = %08x\n", hResult);
goto end;
}

//トラック取得
hResult = pItunes->get_CurrentTrack(&pTrack);
if( ! SUCCEEDED(hResult) ) {
printf("ERROR occured = %08x\n", hResult);
goto end;
}

if (pTrack == NULL){
printf("no track\n");
goto end;
}

//トラックの名前
hResult = pTrack->get_Name(&name);
if( ! SUCCEEDED(hResult) ) {
printf("ERROR occured = %08x\n", hResult);
goto end;
}

// UNICODEANSI変換
::WideCharToMultiByte(CP_ACP,0,(LPCWSTR)name,-1,buf,sizeof(buf),NULL,NULL);

//コンソールに表示
printf("%s\n", buf);

end:
// BSTR開放
if (name != NULL) ::SysFreeString(name);

// track開放
if (pTrack != NULL) pTrack->Release();

// pItues開放
if (pItunes != NULL) pItunes->Release();
}

この方法では、いかにもCOMぽいのだが、以下の点は、より簡単に書くことができる。


  1. リソースの解放
    不要になったオブジェクトは Release()関数の呼び出して解放が必要。COMのお作法だが、いかにも忘れやすい。
  2. BSTRの扱い
    文字列を扱うBSTRは、UNICODEで文字列が格納されているため、UNICODEANSI変換が必要になる。もちろん、プログラム内で文字列をすべてUNICODEで扱っていれば問題ない。
    そして、使い終わった後はSysFreeString()関数でリソースを解放してやる必要がある。
  3. 関数の呼び出しが面倒
    get_XXX()の引数にout変数を渡している。これでも良いが、リターン値で扱いたい。
  4. エラー処理
    毎回、関数の戻り値(HRESULT)をチェックして、成功失敗を判断している

これらを行うには、COMのラッパークラスとCOMの便利なクラス(_bstr_t, _com_error)を使う。

まず、ラッパークラスは、最初の#import宣言のところのraw_interface_only を外すと、iTunesのCOMインターフェスのラッパークラスを生成してくれる。

#import "c:\\program files\\itunes\\iTunes.exe" named_guids

ただし、iTunesの場合、このままだと、GetFreeSpaceという関数が、Windows.hのマクロとぶつかってしまうので、以下のようにする。

#undef GetFreeSpace //Windows.h のマクロとぶつかる
#import "c:\\program files\\itunes\\iTunes.exe" named_guids

こうすると、IiTunesのスマートポインタ版のIiTunesPtrが作成される最初の変数宣言は以下のとおり。

iTunesLib::IiTunesPtr iTunesObj;

CoCreateInstanceをラップしたCreateInstance関数が使える。こちらのほうが引数が少なくて便利。

また、get_CurrentTrackはCurrentTrackにより参照できる。

ラッパークラスを使うことで以下のようにコードを書くことができる。

iTunesLib::IiTunesPtr iTunesObj; //スマートポインタなのでRelease不要
iTunesObj.CreateInstance(iTunesLib::CLSID_iTunesApp);

iTunesLib::IITTrackPtr track; //スマートポインタなのでRelease不要

if (track == iTunesObj->CurrentTrack) {
printf("no track\n");
return;
}


続きてBSTRは、ラッパークラスの_bstr_tを使うことで以下のように扱える

_bstr_t bstrName;	// _bstr_t はBSTR型のスマートポインタ版
track->get_Name(bstrName.GetAddress());

// _bstr_tは (char*)にキャストするとUNICODEからANSIに変換する
printf("%s\n", (char*)bstrName);


エラー処理は _com_errorクラスを使うと、try〜catchで書くことができる。

try {
//COMの処理
}catch (_com_error& e) {
// 例外ハンドラ
printf("Error: %s\n", e.ErrorMessage());
}


以上の記述方法を使うと、最初に示したコードは以下のようになる

// SimpleComClient.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <windows.h>

#include <stdio.h>
#include <tchar.h>
#include <string>

#undef GetFreeSpace //Windows.h のマクロとぶつかる
#import "c:\\program files\\itunes\\iTunes.exe" named_guids

// 便利ラッパーを使った例
void iTunes(){
iTunesLib::IiTunesPtr iTunesObj; //スマートポインタなのでRelease不要

try {
iTunesObj.CreateInstance(iTunesLib::CLSID_iTunesApp);

iTunesLib::IITTrackPtr track; //スマートポインタなのでRelease不要

if (track == iTunesObj->CurrentTrack) {
printf("no track\n");
return;
}

// _bstr_tは (char*)にキャストするとUNICODEからANSIに変換する
printf("%s\n", (char*)iTunesObj->CurrentTrack->Name);
}catch (_com_error& e) {
// 例外ハンドラ
printf("Error: %s\n", e.ErrorMessage());
}
}

// メイン処理
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(0);

iTunes();

::CoUninitialize();
}

以上のように、ATLを使わなくても簡単にCOMクライアントを書くことができた。

この記事意外にもVC++のCOM向けマイクロソフト拡張は以下のものがある

COMサポート関数


_com_raise_error


Throws a _com_error in response to a failure.


ConvertBSTRToString


Converts a BSTR value to a char *.


ConvertStringToBSTR


Converts a char * value to a BSTR.

 

COMサポートクラス


_bstr_t


Wraps the BSTR type to provide useful operators and methods.


_com_error


Defines the error object thrown by _com_raise_error in most failures.


_com_ptr_t


Encapsulates COM interface pointers, and automates the required calls to AddRef, Release, and QueryInterface.

_variant_t

Wraps the VARIANT type to provide useful operators and methods.

Windows7のフォールトトレラントヒープ

Fault Tolerant Heap(FTH)はWindows7に搭載されているメモリ破壊によるプロセスの停止を回避する機能です。http://technet.microsoft.com/ja-jp/windows/dd723549.aspx

調べたところ、以下のDieHardアルゴリズムを使っている模様。
http://www.cs.umass.edu/~emery/pubs/diehard-journal-07.pdf

これによると、Randomized memory managerをコアとしたもので、Dangling pointers、Buffer overflows、Uninitialized readsなどのエラーを確率的に低減させる働きをします。
通常のMemory managerはメモリを連続したアドレスに順番に確保していきます。
たとえば、8バイトのメモリを3回確保した場合、そのアドレス(M1,M2,M3)は

M1 = 0x1000
M2 = 0x1008
M3 = 0x1010

のようになります。
これが、Randomized memory managerを使うと、ランダムな位置にメモリが確保されます。

M1 = 0x1020 
M2 = 0x1108
M3 = 0x1050

こうすると何がいいかというと、通常のmemory managerでは、M1に対して8バイト以上書きこむとM2のメモリが破壊されてしまいますが、Randomized memory managerではM1の後ろが空いているので壊れても影響が出ません。
つまり、十分なメモリ領域があり、均等にランダムな位置にメモリが確保されていれば、メモリ破壊が起こってもエラーを出さずにプログラムを続行できるというものです。論文によると、ヒープ全体の容量に対して使用量が1/2程度のときに、約1/2の確率でエラーを回避できるとあります。*1
ランダムに確保するので、プログラムを起動するたびに、破壊されたり破壊されなかったりするのですが、これを原因の特定に使うこともできます。メモリ破壊が起きた時は破壊された領域より、必ず前の領域に犯人がいいるので、何回か試行すれば捕まえられるという具合です。

Windows7では、原因の特定まで行い、原因を回避する配置まで行っているようです。

*1:ランダムに配置するとフラグメント化しやすいとか、CPUキャッシュのヒット率が悪いとかありそうですがその辺は論文を読んでくださいね(^-^;

ARPについて

TCP/IPARP (=Address resolusion protocol)について、調査が必要になったのでメモ。

JNIのデバッグ

Javaのネイティブライブラリのデバッグっていままで面倒かも?と思っていたけど、実は簡単だった。

  1. dllをデバッグ版で作成
  2. Javaのアプリケーションを起動
  3. デバッガをJavaのプロセス(java.exe)にアタッチ
  4. デバッガでブレークポイントを張る
  5. ブレークポイントにかかったらデバッグ開始

普通のdllと変わらないですね。JNIだからって特別な話じゃなかった。環境を整えるのは面倒かな。EclipseとVisualStudioを切り替えながらなのでへぼいマシンだとメモリ不足でちょっと厳しい〜

gccのインラインアセンブラ

最適化や特権モード命令を使用するときは、アセンブラでコードを書きたくなる。でも、分岐とかループが面倒で...というときのインラインアセンブラgccについては以下のページが入門として参考になりました。

http://caspar.hazymoon.jp/OpenBSD/annex/gcc_inline_asm.html

最近、某組み込みOSのコードを調べてたら、スケジューラが全部アセンブラで書いてあってビビったwこの世界はこれが普通なのね〜

Makefileの構文

Makefileの文法を忘れないようにまとめ。主な情報源はhttp://www.ecoop.net/coop/translated/GNUMake3.77/make_toc.jp.html

ルール構文

ターゲット名:依存関係 
<TAB>コマンド 

変数構文

マクロとも言われる

  • 代入
変数名=値
  • 参照
$(変数名)

条件分岐構文

変数の内容を判断して動作を分岐させる。ifdef〜endif 以外にもifeq〜endifなどあり。処理の部分をインデントして、誤ってタブを使うとコマンドになるので注意。インデントしない書き方も多い。

ifdef 変数名
  処理
endif

コマンド構文

TABで始まるものはコマンドとして扱われ、シェルで実行される

<TAB>コマンド

関数呼び出し構文

makeにあらかじめ用意されている文字列操作などの関数を呼び出す。結果は変数に代入するか、別の関数の引数として入れ子にすることもできる。

変数名=$(関数名 引数1,引数2,..,引数n)

例 $(VPATH)のコロンをスペースに置換

pathlist=$(subst :, ,$(VPATH))

コメント構文

#から始まる

#コメント

ディレククティブ

makeに出す指令。C言語のように頭に"#"がつくというようなルールが無いので、知らないとディレクティブかどうかがわからないことが多い。条件分岐のifdefなどもディレクティブの一種。

  • include

他のMakefileを読み込む

include ファイル名
  • define

普通の変数定義と違って値を複数行にできる

define 変数名
値
endef
  • export / unexport
  • override