COMサーバーにJScriptのイベントハンドラを登録する

VC++ 2008EE で COMサーバーをカンタンに作成」のつづきです。

C++属性を使ってC++で簡単なCOMサーバーを作成はできました。次に、ブラウザ上でやってるような、イベントを発生させて、それをjscriptの関数、たとえば、

function onExec()
{
  WScript.Echo("メソッドが実行されました");
}

というようなイベントハンドラを登録して、COMサーバーのあるメソッドを実行したときに、このonExec という関数を実行させるようにしたい! と思います。

要は、HTML+Javascriptでやってるような、window.onload = function(){…} というようなことを自分が作成したCOMサーバーにやらせよう、というわけです。実行環境は、Windows Scripting Hostです。

はじめはどうやればさっぱり分かんなかったんですが、ネットで検索していろいろ調べた結果の備忘録となります。

COMサーバーから、各種イベント通知を受け取るには、C++だとIConnectionPointとシンクオブジェクトを経由したものになるんですが、JScriptにはそういった接続ポイントをゴニョゴニョして・・・というやり方はできないようです。つまりCOMでシンクを経由したイベントを受け取る機構は通用しないということです。

ここから補足 (2012/2/10)

正確には、動的にインスタンスを生成した(new ActiveXObject()とかで作成した)オブジェクトでシンクを経由したイベントを受け取る機構がないのです。たとえばHTAとかで

<object id=’xxxx’ classid=”clsid:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee”></object>

という風にOBJECTタグをHTMLに埋め込んで、function xxxx::関数名(引数){…} というイベントハンドラを定義すれば、可能になります。たぶん。

JScriptをホストしているのがWSHならば、以下のようにイベントハンドラを定義して、シンクを経由した通常のCOMイベントを受け取れます。

var obj = WScript.CreateObject("MyServer.Utility","myserver_");
function myserver_onXXXX()
{
   WScript.Echo("Fire!");
}
obj.XXXX();

しかし、Webブラウザ上で、たとえば・・・

var button = document.getElementById('button');
button.onclick = function()
{
  window.alert("クリック!") ;
}

というようにもしたいわけです。

var myserver = WScript.CreateObject('MyServer.Utility');
myserver.onExec = function()
{
  WScript.Echo("実行");
}

とこんな感じにもできたらなぁ・・・と思ったのが今回の動機です。

単純に考えれば、COMサーバー側にonExecというプロパティを設置して、COMサーバーのあるメソッドが実行されたら、このonExecを実行させる(イベントを発生)させればいいだけかな、と思いました。function(){}の関数オブジェクトの部分。関数オブジェクトはC++で、IDispatch*で受け取ればいいですね。

ということで、いろいろネットで調べた結果、次のような単純なCOMサーバーを作成しました。

  • CurrentDirectory() というメソッドを一つ定義。
  • 関数オブジェクトの保持用にプロパティを一つ定義
  • CurrentDirectory()がコールされたら、プロパティに格納している関数オブジェクトを実行する。
    (つまりイベントを発生させる)

具体的なコードは・・・

/*
 コンパイル・コマンドライン :
   cl /LD MyServer.cpp /link /TLBOUT:MyServer.tlb
*/

#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_ATTRIBUTES
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlcom.h>
#include <atlwin.h>
#include <atltypes.h>
#include <atlctl.h>
#include <atlhost.h>
using namespace ATL;

//マクロ定義
#define PPVOID(X) (reinterpret_cast<LPVOID*>(X))
#define RELEASE(X) (X ? X->Release() : 0)

// DllMain,DllCanUnloadNow,DllRegisterServer and DllUnregisterServer
[ module(dll, name = "MyServer", helpstring = "MyServer 1.0 Type Library") ];
[ emitidl ];

/////////////////////////////////////////////////////////////////////////////
// インターフェイス定義  :   IMyServer 
[
   object,
   dual,
   oleautomation,
   helpstring("IMyServer Interface"),
   pointer_default(unique)
]
__interface IMyServer : IDispatch
{
  [id(1)] HRESULT CurrentDirectory([out,retval]BSTR *sPath);

  [id(2),propput] HRESULT onCurrent([in] LPDISPATCH pDisp);
  [id(2),propget] HRESULT onCurrent([out,retval] LPDISPATCH *ppDisp);
};

/////////////////////////////////////////////////////////////////////////////
// イベントインターフェイス定義 : IMyServerEvent
[
  dispinterface,
  hidden,
  helpstring("IMyServerEvents Interface")
]
__interface IMyServerEvent
{
  [id(1)] HRESULT onCurrent();
};

/////////////////////////////////////////////////////////////////////////////
// インターフェイス実装クラス  : CMyServer
[
   coclass,
   threading(apartment),
   source(IMyServerEvent),
   event_source(com),
   vi_progid("MyServer.Utility"),
   progid("MyServer.Utility.1"),
   version(1.0),
   helpstring("MyServer Class")
]
class ATL_NO_VTABLE CMyServer :
   public IMyServer
{
public:
   CMyServer()
     {

     }

  //COMイベントのためのおまじない。
  __event __interface IMyServerEvent;

  DECLARE_PROTECT_FINAL_CONSTRUCT()
  HRESULT FinalConstruct()
    {
      //プライベート・メンバ初期化
      m_pDispatch = NULL;
      return S_OK;
    }

  void FinalRelease()
    {
      //後処理
      RELEASE(m_pDispatch);
      return;
    }

  //単にカレントディレクトリを返す。
  HRESULT CurrentDirectory(BSTR *sPath)
    {
      WCHAR pBuffer[MAX_PATH+1] = {0};
      ::GetCurrentDirectoryW(MAX_PATH+1,pBuffer);

      *sPath = ::SysAllocString(pBuffer);

      Fire();
      return S_OK;
    }

  // IDispatch*(スクリプトから渡されるオブジェクト)の設定と取得
  // スクリプトでいう、obj.onCurrent = function(){...} の部分
  HRESULT put_onCurrent(LPDISPATCH pDispatch)
    {
      LPDISPATCH pDispatchNew = NULL;
      HRESULT hRes = S_OK;

      if(SUCCEEDED(hRes =
           pDispatch->QueryInterface(IID_IDispatch,PPVOID(&pDispatchNew))))
        {
          RELEASE(m_pDispatch);
          m_pDispatch = pDispatchNew;
        }

      return hRes;
    }

  //関数オブジェクトの取得
  HRESULT get_onCurrent(LPDISPATCH *ppDisp)
    {
      HRESULT hRes = E_NOTIMPL;

      if(m_pDispatch)
        hRes = m_pDispatch->QueryInterface(IID_IDispatch,PPVOID(ppDisp));

      return hRes;
    }

private:

  LPDISPATCH m_pDispatch;

  void Fire()
    {
      DISPPARAMS dispparams = {NULL,NULL,0,0};

      //COMイベント発火
      IMyServerEvent_onCurrent();

      //設定された関数オブジェクトを実行,デフォルトDISPID(0)
      m_pDispatch->Invoke((DISPID)DISPID_VALUE,
                          IID_NULL,
                          LOCALE_USER_DEFAULT,
                          DISPATCH_METHOD,
                          &dispparams,
                          NULL,
                          NULL,
                          NULL);
    }
};

一応、シンクオブジェクト経由でもイベントを受け取れるようにもしました。仕組みが分かれば意外と簡単です。以前にも書きましたが、ほとんどサンプルの流用で、ATLを使った部分や属性部分はサンプルそのまま。

ただ、このテストサンプルは複数のイベントハンドラを登録できません。複数のイベントハンドラを登録できるようにするには、複数のIDispatch*ポインタを管理しなければなりません。内部でstd::vectorなどのポインタ配列用のコンテナ(COMだったらsafe arrayあたり?)を用意して・・・というような処理が必要になってくるでしょう。

できたMyServer.dllを regsvr32 で登録して、以下のJScriptで動作確認。

//シンクを経由した、イベントハンドラ
//WSHホストで動作可能です。
function myserver_onCurrent()
{
  WScript.Echo("Sync Event Occured!");
}

try
{
  //IEがホストなら・・・
  // var obj = new ActiveXObject("MyServer.Utility");
  // WScript.Echo は window.alert になります。
  var obj = WScript.CreateObject("MyServer.Utility","myserver_");

  obj.onCurrent = function()
    {
      WScript.Echo("Fire Current!");
    }

  WScript.Echo(obj.CurrentDirectory());
}
catch(e)
{
	WScript.Echo(e.message);
}

というわけで、備忘録終了。

七夕いろいろ

曇り空。せっかくの七夕の日なのに天の川は見えなくて残念。

Windows7の発売日決まりましたね。10月22日、世界同時リリース。僕はこないだの半額キャンペーンでProfessional版を予約したんですが・・・失敗しました。2本(2ライセンス)買っておくべきでした。ノート用とデスクトップ用。VAIO君の方はRC版の使用期限ギリギリまで使ってライフサイクルを終了させ、ノートパソコン自体を買い替え・・・というのが賢そうです(^^;;;

それにしても、予想外にリリース日が早かった。β2が出たときは2010年初頭という噂だったんですが・・・それだけマイクロソフトも自信があるんでしょうねぇ~。

で、Windows7、僕が最も注目しているのがXPモード(仮想環境)なんですが、それともう一つ、面白そうな新機能の一つとして、VHD(仮想ハードディスク)形式のファイルを標準でマウントできる、という点です。VHDファイルは、マイクロソフトがフリーとして提供しているVirtual PCで使用されるファイル形式で、そのものずばりバーチャルマシンで使用するHDDです。

で、これをHDDドライブとしてマウントしでき、結果VHDファイルの中身にアクセスできるわけです。けど・・・それだけなら、新機能と呼ぶほどでもないのですが、驚くべき事に、VHDファイルをHDDドライブとしてマウントし、さらに、なんとブート可能なドライブとすることが可能なんですね・・・。これは仮想マシンとしてブートするわけでなく、いわばホンモノの?ハードウェアを使用した物理マシンとしてブートできる、とのこと。

はじめネット上の記事を読んだとき、よく分かんなかったけど、要するに、デュアルブートが簡単に構築できるようなイメージでしょうか。ブートするときに、ホンモノの?ドライブにあるOSを読み込むのではなく、VHDファイルに構築されているOSを読みに行くようです。おそらくディスクアクセスにオーバーヘッドが生じるのでファイルへのREAD/WRITEなどのI/Oアクセスはちょっと遅くなるんでしょうけど・・・。ということは、VHDファイルを複製すればテスト環境の構築・運用がすっげぇラクになる、ってことでしょうか。

で、誰しも考えることでしょうが、XPやらVistaからLinux/FreeBSDやらのフリーなOSなんかもVHDファイル上に構築しておけば・・・マルチブートがーーーー!と思ったんだけど・・・残念ながらそれはできません(^^;;; VHDファイルからブートできるのは、Windows Server 2008R2やWindows7などの新しいOSのみ。なぁーーーーんだ、残念。。。

もう一つ。E-P1。
E-P1は設定できる項目が多すぎて下手にカスタマイズすると何がなんだか分からなくなりそうです。ボタンの入れ替えは言うに及ばず、ピントリングの回す方向も変えられたり、ダイヤル方向も変えれたり・・・。

E-P1背面

E-P1背面

AFモードを、S-AF+MF(シングルAFとMF)、α900で言えばDMFな感じにしています。半押しでAF合焦させてから、ピントリングを回すとライブビュー画面で7倍に拡大表示されますが、このままで元の表示(1倍)に戻す方法がありません・・・。元に戻すにはまたシャッターボタンの半押ししかないので非常に不便。半押ししてしまうと、せっかくMFでピント合わしたのにまたAFしなおしです。

で、AEL/AFLボタンでAF、シャッターボタン半押しをAEL、に入れ替えて親指AFにすることでなかなか快適になりました。

予約しました。

マイケル・ジャクソンの突然の死。ビックリしました。何かと話題のあるスターでしたね~。スリラーは中学の時?MTVとかでよく見ました。ファンというわけではありませんが、最初のころは良かったなぁ。マイケルジャクソンではないですが、彼が作曲したwe are the world. は僕の好きな曲です。ムーンウォークをよく学校で友達と真似したのを思いだします(^^;

それはさておき・・・、

Windows7の参考価格が発表され、今日から10日間マイクロソフトのストアで予約すると、アップグレード価格が半額になる、という大盤振る舞い企画に、僕はコロッと逝ってしまい(笑)、今日の昼休みにWindows7 Professionalを予約してしまいました(^^

Windows7

Windows7

アメリカでは10月発売みたいですが、日本版は11月ぐらい? それとも世界同時になるのか?OSがリリースするのにソワソワするのは、Windows95が出たとき以来、ひさびさ。フィルムスキャナのドライバの関係でVistaにアップグレードするのをためらっていましたが、7ではXPモードがあるので安心してバージョンアップできます。

追記:
既にHome Premiumが在庫切れになっているみたいです(笑)

VC++ 2008EE で COMサーバーをカンタンに作成

【追記】
ご承知のように、Visual Studio Commuity Edition がリリースされました。ライセンス的にやや難がありますが、商用/非商用アプリケーションの個人開発者にはVisual Studio の Professional 版相当のフルセットが無料で提供されるようになりましたね、バンザーイ!
ってことで、このページの内容は、意味をなさないので、とっととCommunity Editionをインストールしてラクをしてください。


備忘録のエントリです。

フリーの開発環境でラクにCOMサーバーを作ろう、という主旨です(^^;;;

キーワードは「C++属性」です。この属性を利用することで手作業であっても退屈なメンドクサイ、コードの大部分(ほとんど)が自動的に作成されます。コーディングするのは、実装するインターフェイスのメソッドの中身だけ。簡単です。ただ・・・ATLだけはどうしても必要です。

しかし・・・Visual Studio 2008 Express EditionにはATLは付属していません。

ATLのヘッダーファイルとライブラリファイルをどこぞから調達しなくてはいけませんが、幸いなことにマイクロソフトがこれまた無料で配布しているWDK(Windows Driver Kit)をインストールすることにより利用することが可能です。WDKはかつては DDK(Driver Developper Kit)と呼ばれていたモノです。WDKにはVC++2008と同じコンパイラ・リンカなどの開発ツール、ATL/MFCやWindowsでのカーネルモードドライバを作成するために必要なすべてのファイル・ドキュメントが同梱されています。

無料で手に入れることができるのですが・・・一点、不安なことがあります。

それは・・・Visual Studio 2008 Express Editionと、WDK内の構成ファイル(ATLヘッダーファイルとライブラリファイル)を混ぜてコードをビルドするのがライセンス的に良いのか駄目なのか・・・等は、調査不足で分かりません。Visual Studio 2008 Express Editionで作成したアプリケーションは商用・非営利を問わず配布できたかと思いますが、WDKはそのあたり分かりません。まぁ非営利・個人的な利用なら問題はないかと思いますが・・・。

(WDKのダウンロードにはWindows Liveやマイクロソフト・パスポートのアカウントが必要です)

MSのダウンロードセンターからダウンロードできるようになってました。( 2012/1/11)

で、話を本題に戻します。 このWDKをインストール後、VC++ 2008 Express Editionのディレクトリ設定にこれらのパスを追加登録してあげればATLを利用したアプリケーション開発が可能となります。ただし、ATL関連ファイルがあるというだけで、便利なプロジェクト・ウィザードはないので全部手作業です(^^;;; やっぱりラクはできません(笑)。

さて、COMサーバーの実際の手順なんですが・・・これは日本語ドキュメントが既にあります(^^ MSDNライブラリの「COM属性によるCOM DLLの開発 -> チュートリアル : テキスト エディタを使った COM サーバーの作成」です。

サンプルがありますのでこれを自分用に修正していけばいいだけ。僕は以下のようにMyServerというCOMオブジェクトをテストとして作成することにしました。見れば分かりますが・・・[ ] でくくっているところが属性と呼ばれるマイクロソフトの独自の拡張です。かなり違和感のあるコーディングです。が、これをコンパイルすることで必要なファイル、足りないコードを自動生成してくれます。理屈は分かりません(笑)  ある程度理解するにはやはりCOM/ATLの知識が必要です。魔法(ブラックボックス)として扱うのがよさそうです。

IMyServerインターフェイスを定義して、CMyServerクラスで実装。インターフェイスメソッド、CurrentDirectory()を定義。これは現在のカレントディレクトリを文字列で返すメソッドをサンプルとして定義しました。必要に応じてインターフェイスやメソッドを増やしたい場合もチョチョッと手を加えるだけで済みます。

んで、これだけのコードで完璧なオートメーションサーバーができてしまいます。属性の詳細は・・・あまり知る必要はないかと。詳細はかなり難解です。何度も書きますが、理解するにはCOMとかATLに関する中級~上級レベルの知識が必須かと。ただ公開するものの実装さえしてやればいいので、あとのCOMに関連するおきまりのコードはコンパイラとリンカが勝手にやってくれますのでかなりラクです。従来ならIDLファイルを書いて、IDispatch,IUnknowを実装して、レジストリへの登録処理を書いて・・・などが必要でしたが・・・。

このファイルをたとえば  cl /LD MyServer.cpp /link /TLBOUT:MyServer.tlb  みたいにコンパイルしてやればインプロセスサーバー(DLL)、タイプライブラリが生成されます。

あとはRegsvr32.exe でDLLファイルを登録し、以下のようなテストコードで動作確認。

パフォーマンスにシビアなコードはC++で実装して、あとはC#やJScriptでお手軽に・・・というような用途にいいかもです。

かなりエエかんじ

うちのVaio TZ 君にWindows7 RC版 、もうちょっとで各種ソフトのインストールが終わりますが、あともうちょっと。一気にやると2~3日かかるのでチョビチョビやってます。

VAIO TZ with Windows7RC

VAIO TZ with Windows7RC

TZとの組み合わせでβ2の時にあった不具合がほとんど修正されています(^^)

Fnキーとの連携、AVボタン、ワンセグチューナ、正常動作しています。

たしかにXPよりメモリ消費は激しい(スタートアップ後アイドル状態で約700MBぐらい)けど、Vistaに比べると激減。Vista SP1のときは常に1GBとか消費されていたので・・・。デスクトップのAero描画も若干機敏になっているような気が・・・。

Vista/7 β2から変わったちょっと残念なのが2点。どうでもいいっちゃ~どうでもいいんですが・・・。

タスクバー オプション

タスクバー オプション

タスクバーのプロパティーで、”タスクバーの手前にウィンドウを表示する”の項目が無くなっており、スクリーン全体を使用できない。これは、この設定が他のところに移動したのか、それともスクリーン全体を使用したいときは”タスクバーを自動的に隠す”にすればいい、というように仕様自体が変更になったのか? どうなんでしょ。

フォルダーオプション

フォルダーオプション

それともう一つ。フォルダーオプションの中の、「ログイン時に以前のフォルダーウィンドウを表示する」が無くなっていた。これについては、新しいタスクバーボタンを利用しろ、ということだろうか。

あとは・・・エクスプローラシェルが落ちる頻度がちょっとだけ小さくなった感じでしょうか。

Windows7はVistaを継承しているので、WindowsXPラブな人なかなか受け入れにくい感じがするのもあります。基本的な操作(エクスプローラシェル)はあまり変わらないものの、細かい部分でかなり変わっているので最初は戸惑うと思います。Vistaからのアップグレードな人は、起動・シャットダウンの速さ、そのUIの軽快さに満足すると思います。実際僕は相当満足しています。

おそらく一般コンシューマ向けWindowsの中でも最長寿命のXPもやっとその役割を終えるんではないかと思います。

ソフトウェアを開発する側にとっても早くXPサポートを切りたいはずです(^^;;;