Component Object Model - Microsoftが提唱する(していた)、ソフトウェア資産再利用のための技術。美しく厳格なオブジェクト指向の世界を追及していたようであるが、そのためにややこしくなって凡人はついていけなくなり、フェードアウトしつつある・・・という認識でいいのだろうか?

時代遅れなのかもしれないが、オートメーションはなんだかんだで便利だし、DirectXも大部分はCOMで実装されているみたいだし、後継の.NETもCOMベースの技術だそうなので、ちょっとやってみることにした。ヘルプや解説Webページを見るもわけがわからず、ムカつく日々を過ごしていたが、腰を据えて勉強してみると意味や意義がおぼろげに捉えられてきた。開発などはVC6で行う。

全体的なこと *

link *

CodeGuruに色々記事がある。

メモ *

virtual *

C++における class と virtual が理解のために重要な概念・・・なのだろう。

レジストリ *

クラスやインターフェース等の登録はレジストリに記録される(なんでCOM専用のデータベースが作られなかったのだろう?)。コンポーネントは登録や削除用の関数を備えなくてはならないのだそうだ。ローカルサーバ(実行ファイル)はコマンドラインオプションによって、プロセス内サーバ(DLL)の場合はregsvr32.exeによって、これらがコールされる。

プロセス内サーバ *

実際にAppWizardからATLでプロセス内サーバを作ってやってみた。ビルドすると

HKCR\CLSID\(CLSID)
    \Interface\(interfaceのGUID)
    \(ProgID バージョンあるのとないの)

HKLM\SOFTWARE\Classes\CLSID\(CLSID)
                     \Interface\(interfaceのGUID)
                     \(ProgID バージョンあるのとないの)

に登録されていた。regsvr32/u して登録解除しても、CLSIDを含むエントリは残っていた。←改めて試したら、きちんとhousekeepingできているようだった。何かしら使用中のプロセスがあったのかなあ?

ローカルサーバ *

今度は実行可能サーバを作ってみた。で、ダイアログだけ作ってビルドしてみた。すると

HKCR\AppID\(AppID)
          \(実行ファイル名)
    \TypeLib\(TypeLibID)

HKLM\SOFTWARE\Classes\AppID\(AppID)
                           \(実行ファイル名)
                     \TypeLib\(TypeLibID)

なるエントリができていた。これらは-RegServerオプションによって登録されたのだが、-UnregServerオプションで実行してやるとこれらのエントリは全て消えた。

ATL *

COM開発に役立つフレームワーク

FinalConstructとFinalRelease *

FinalConstructでS_OK以外を返すと、呼出し側でオブジェクト作成の際にエラーが出る。

外部シンボル "_main" は未解決です *

リリースビルド時にこんなエラーが出るのはなぜか?perfectな記述がすぐに見つかった。

cannot open input file oaidl.idl *

ビルド時、いかにも必須っぽいのにファイルが無いと怒られてしまった。MFCでも似たようなことがあったのだが、これはオプションメニューからインクルード、ライブラリのディレクトリをちゃんと指定(ATL、MFCのも)してやると消えた。

IDL *

インターフェースを宣言するファイル。

引数の省略 *

COMプログラミングというページの記述通りなのだが一応メモ。

引数の宣言にoptionalを入れると、その引数は省略可能となった。私はDWORD値にoptionalを加えてみたが、省略するとゼロが入るようであり、上記ページと同様であった。

引数の変更 *

シンプルオブジェクトをウィザードから追加し、またそのメソッドもウィザードで追加した状況を考える。その後に引数の数や種類を変えたいと思った時にはどうすればよいか?

IDLの記述を変更し、当該オブジェクトのヘッダとソースの記述を変えればそれで良いようである。削除も同様にすればOK?

引数の種類 *

[out, retval] *

[out, retval]とすると、これがメソッドやプロパティの戻り値となる。これを引数リストのどこに持ってゆけばよいんだろう?

http://www.microsoft.com/japan/msdn/thisweek/combasics/combasics3.asp に、最後に記述している例があった。実際に最後でやってみたらうまくいった。

文字列の戻り値は

[out, retval] BSTR* ret_str

などとする。

[out] *

値をたくさん戻したい場合、戻り値ではなくポインタや参照渡しの引数を使うのが、まあ一般的なプログラミングテクニックである。 こんな時には引数の属性に[out]を用いるわけだが、VBScriptからの使用を考えた場合、VBScriptにはVARIANTしか存在しないため、引数はVARIANTでなくてはいけないそうだ (JScriptはそもそも参照渡しできないみたい?よくわからん)。

また、その場合には空のVARIANTを渡さねばメモリリークするそうだ。[in,out]だと受け取った側(COMコンポーネント)が元のVARIANTの領域を解放するが、[out]だけだったら入ってきたVARIANTに対しては何もしないからというのが理由みたい。

スクリプティング *

IDispatchを備えるオブジェクトはスクリプトから簡単に操作することができる。特に、Windows2000以降はWSH(Windows Scripting Host)が標準で備わっているため、特別な準備をしなくともVBScript、JScriptが使える。

Windows Script File *

XMLで強烈に柔軟性を持たせたもの。他ファイルのインクルードはおろか、複数のスクリプトエンジンの同居もできてしまう。「Windows スクリプト ファイル (.wsf) を使用する」からの抜粋だが

<job id="IncludeExample">
   <script language="JScript" src="FSO.JS"/>
   <script language="VBScript">
      ' C ドライブの空き領域を取得します。
      s = GetFreeSpace("c:")
      WScript.Echo s
   </Script>
</job>

などとできるわけである。凝りすぎ?

VBScript *

利点 *

link *

memo *

Wscript.sleep(1000) ' 1000[ms] 待つ
WScript.Quit ' やめる
msgbox("hello" & "world") ' メッセージボックスを表示
inputbox("type the param") ' パラメータ入力可能なダイアログ

' ループ
Dim i
For i = 1 To 10
  ' any
Next

 

msgbox "this is ""quoted"" string" ' 「"」のエスケープはバックスラッシュでなく連打で行う

JScript *

利点 *

link *

変数の型 *

BSTR、VARIANT、SAFEARRAYあたりが聞き慣れないがCOMによく登場する型

link *

VARIANT_BOOL *

互換性の観点から、booleanの型はVARIANT_BOOL型を使うべきなのだそうだ。定数 VARIANT_TRUE と VARIANT_FALSE があらかじめ定義されているが、VC6+ATLで開発中にVARIANT_TRUEを使ったら、警告レベル4でwarningをくらった。

warning C4310: キャストによって定数値が切り捨てられました。

これはMicrosoft側に非があるようだ。回避するためには

#undef VARIANT_TRUE
#undef VARIANT_FALSE
const short VARIANT_TRUE = -1;
const short VARIANT_FALSE = 0;

などとすれば良いようだ。

SAFEARRAY *

ATLにSAFEARRAYのラッパーは無い(MFCならCOleSafeArrayがある)。なんで、APIを直接使う。

_variant_t var = integer_values_are_here;
SAFEARRAY* psa = var.parray;
long lb, ub;
SafeArrayGetLBound(psa, 1, &lb);
SafeArrayGetUBound(psa, 1, &ub);

long* val;
SafeArrayAccessData(psa, (void**)&val);
for(long i = lb; i <= ub; i++){
  // val[i] holds the value
}
SafeArrayUnaccessData(psa);

などとするらしい。

エラー処理 *

HRESULT *

IErrorInfo *

HRESULT以上の情報が必要な場合に用いられるインターフェース。同時にISupportErrorInfoも持ってなくてはいけないなど実装するのは面倒だがATLを使うと簡潔に記述できる。

そのためには、ATLオブジェクトの挿入の際に「ISupportErrorInfo サポート」にチェックするだけでOK。これが本当に正しいかは知らないが、既存のオブジェクトでも

STDMETHODIMP CMyclass::InterfaceSupportsErrorInfo(REFIID riid)
{
  static const IID* arr[] = 
  {
    &IID_IMyclass
  };
  for (int i=0; i< sizeof(arr) / sizeof(arr[0]); i++)
  {
    if (InlineIsEqualGUID(*arr[i],riid))
      return S_OK;
  }
  return S_FALSE;
}

を手作業で行ったらば、まあ問題なく動いているようである。ここで、Myclassというのが既存のATLオブジェクトである。他のインターフェースのメソッドでもエラー情報扱う際には、arr[]には適宜インターフェースを追加する必要がある。

エラーはCComCoClass::Errorをコールするようだ。色々種類があるが、

return Error(TEXT("error!!!"));

などとすれば、WSHクライアントからエラーメッセージが見られることを確認した。