エアロのガラス効果

Windows Vistaに全く興味がなかったわけでもないですが、メインのPCをWindows7にしたことだし・・・ということで、ほとんど何の知識もないWindows Aeroですが、ちょっと勉強がてらいじってみました。

Aeroの最大の特徴は、やはり、ウィンドウフレームにも適用されているガラス効果でしょう。各ウィンドウとガラス効果などのAero効果をデスクトップ上のウィンドウとして表示できるように合成・管理しているのが、DWM(Desktop Window Manager)ということだそうです。

クライアント向けのWindowsで、NT4からXPまでのウィンドウマネージャなどの大部分はカーネルモードで動作していたと思います。これは純粋にパフォーマンス向上のためと思いますが、Vista以降ではドライバモデルが変更され、iその一部がユーザーモードで動作するようになりました。本来のNTの思想に戻ったといえます。ビデオカードをはじめとするハードウェア自体の性能向上のおかげでしょうね・・・。

このDWM、タスクマネージャで確認するとVistaと7では、明らかにメモリ使用量が違っています。詳しくは知りませんが、なんらかの改良が施されているんだと思います。

で、DWMに関連するAPIでネットを検索しても、SDKプログラマの興味がないのか、関心がないのか、C/C++ WindowsSDKで利用できるまとまった情報(コード)が少ないです。使ってみました、という記事はいっぱい出るんですが、詳細な記事が見つからず。MSDNのAPI説明見てもいまいち分からないし・・・。

まぁ、だけど利用できるAPIは限られていて、DWMに関連するAPIで最も簡単に利用できるのが、DwmExtendFrameIntoClientAreaというAPIです。
これは、ウィンドウフレームに適用されているガラス効果をクライアントエリアに拡張するAPIなんですが、使い方は至って簡単です。ガラス効果をどれだけクライアント側に食い込ませるかをMARGINS構造体にセットして、それをウィンドウハンドルとともに渡せばOKです。

//{左,右,上,下}という風になります。
//下記例だと、上部20ピクセルが拡張されます。
MARGINS margins = {0,0,20,0};
DwmExtendFrameIntoClientArea(hWnd,&margins);

//ちなみに、以下のように-1をセットすると、クライアント領域すべてがガラス効果の影響受けます。
MARGINS margins = {-1};

※dwmapi.hのインクルードと、dwmapi.libのリンクが必要です。

MARGINS margins={-1};としたときのガラス効果

このようにAPI自体はカンタンに利用できる反面、C/C++とWindowsSDKのみで組んで行くには相当めんどくさい処理が必要になります。

  1. 単純にボタンやエディットなどのコントロールを配置すると、悲惨なことになる。
  2. ガラス効果上に黒い文字を描画すると、黒い文字が透明になっちゃう(^^;;;
  3. ガラス効果にしたクライアント領域部分は、別途常に黒く塗りつぶさないといけない。
    (要するに、拡張した部分の背景色の再描画処理を別途記述しなければならないかと・・・)
  4. DWMのデスクトップコンポジションが有効か無効かを判別してそれぞれに対する処理を行う必要がある。

などなど・・・

ガラス効果上に配置した黒く塗りつぶしたGDIアイテムは、全部透明になってしまいます(テキストとかメニューの文字とか)。要するに、色値が0x00000000(要するに黒色)の部分がガラス効果として透明なるっちゅーわけだから、当たり前と言えば当たり前ですが・・・。COLORREF型の先頭1バイトを255にすればいいのか?よくわかんないです。
これの解決方法としては、コントロールをオーナードローで描画するか、GDI+ライブラリを使用する、という選択肢がMSDNで紹介されていました。が、子コントロールの色を全部修正するなんて、メンドーだし、他の解決方法があるのかもしれません。
そのうちガラス効果関連処理をラップしたC/C++のクラスライブラリがそのうち出るかもしれないし、でないかもしれないし、すでに出ているのかも・・・。C#の情報は結構あるんですけどね・・・。

ダイアログをリソースファイルで定義してDialogBox() APIで表示させているようなプログラムの場合、最低限以下のようなウィンドウメッセージを捕まえて背景を別途処理しないといけない。

・WM_INITDIALOG
デスクトップコンポジションが有効なら、DwmExtendFrameIntoClientAreaを実行。

・WM_ERASEBKGND
ガラス効果を適用しても、ダイアログの背景用描画ブラシで上書きされてしまうので、ガラス効果を拡張した部分は黒色で、それ以外はダイアログの背景色で背景を塗りつぶす。

・WM_DWMCOMPOSITIONCHANGED
デスクトップコンポジションが有効になったり、無効になったりすると、飛んでくるので、これを捕まえて適切な処理を行う。
注意しなければならないのは、デスクトップコンポジションが有効になったらその都度、DwmExtendFrameIntoClientAreaをコールする必要があること。

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  //上部20ピクセルを拡張
  MARGINS margins = {0,0,20,0};
  RECT rect = {0};
  BOOL fDwmEnabled = FALSE;
  HBRUSH hBrush = NULL;
  LONG oldBottom = 0;

  switch(message)
  {
  case WM_INITDIALOG:
    DwmIsCompositionEnabled(&fDwmEnabled);
    if(fDwmEnabled)
      DwmExtendFrameIntoClientArea(hDlg,&margins);
    return (INT_PTR)TRUE;

  case WM_ERASEBKGND:
    DwmIsCompositionEnabled(&fDwmEnabled);
    GetClipBox((HDC)wParam,&rect);

    //デスクトップコンポジションの有効・無効によって背景に使用するブラシを決定
    hBrush = fDwmEnabled ? (HBRUSH)GetStockObject(BLACK_BRUSH) : GetSysColorBrush(COLOR_BTNFACE);

    //クライアント領域の上部20ピクセル(ガラス効果)の背景を描画
    oldBottom = rect.bottom;
    rect.bottom = rect.top + 20;
    FillRect((HDC)wParam,&rect,hBrush);
    
    //クライアント領域の残りの下部の背景を描画
    hBrush = GetSysColorBrush(COLOR_BTNFACE);
    rect.top += 20;
    rect.bottom = oldBottom;
    FillRect((HDC)wParam,&rect,hBrush);

    return (INT_PTR)TRUE;

  case WM_DWMCOMPOSITIONCHANGED:
    DwmIsCompositionEnabled(&fDwmEnabled);
    if(fDwmEnabled)
      DwmExtendFrameIntoClientArea(hDlg,&margins);
    return (INT_PTR)0;

  case WM_COMMAND:
      //ダイアログ上のコントロールへの応答処理など
      if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
          EndDialog(hDlg, LOWORD(wParam));
          return (INT_PTR)TRUE;
        }
      break;
  }
  return (INT_PTR)FALSE;
}

ということで、なんとかかんとか、思ったように表示できました・・・。

ガラス効果2

なんか、久しぶりにGUIなAPI触ると、何がどうだったか・・・忘却の彼方へ・・・(ーー;

64bit Project with Visual C++ 2008 Express Edition SP1

無事、Windows7 のインストールと環境移行も完全に済み、ようやく64bitデビューできました(^^;;;

64bit版だからといって、特に変わったこともなく、フツーに使えてます。ただ、メモリ食いますね・・・。32bit版だと、タスクマネージャで確認すると起動時で600~800MBぐらいのメモリ消費。64bit版だと、この倍の1.1~1.3GBぐらいになります。まぁ・・・64bitなんだからしょうがないね・・・と自分で自分を納得させています(笑)

で、次は、ツール類のビルド環境です。自作のツールがチョコチョコとあるので、これも一気に64bitネイティブバイナリにしたい!っていうのは当然の欲求じゃないでしょうか(^^;;;

しかしながら・・・無料バージョンのVisual C++ 2008 Express Edtionでは、64bitの開発はサポート外になっています。別途、Windows SDK (Windows7,Server2008,Server2003,XPに対応したv7バージョン)を追加インストールすると、コマンドラインからの64ビット開発はできるものの、やはりVisual StudioのIDEで楽をしたいというものです。が、実際、IDEからは64bitのプロジェクトは構成できません。これは、Standardエディションを買えと、いうことですかね・・・。まぁ、そりゃそうだ。

でも、実は・・・サポート外、というだけで、少しの修正で64bitのプロジェクトが作成できるようです。

Visual C++ 2008 Express Edition And 64-Bit Targets
http://jenshuebel.wordpress.com/2009/02/12/visual-c-2008-express-edition-and-64-bit-targets/

やはりすでにやられている方がいますね。上記アドレスから、VCE64BIT.zip をダウンロードして、バッチファイルを実行すると64bitを構成することが可能になります。ありがたい。。。
(WindowsSDKインストーラのバグに対応した、VCE64BIT_WIN7SDK.zip ってファイルに更新されていました。VCE64BIT.zipをダウンロードして実行しても適用されないようです。ご注意を。 追記:2010/3/27)

ただ、上記アドレスの内容を読むと、おそらくSP1以前の内容じゃないかと・・・。僕の環境とはちょっと違っていたので、ダウンロードしたファイルのバッチファイルを修正して使用させてもらいました。

具体的には、VCProjectAMD64Platform.dllとVCProjectIA64Platform.dllがC:Program Files (x86)Microsoft Visual Studio 9.0VCvcpackagesディレクトリにコピーされないようにしました。僕の環境では、すでに上記二つファイルは、32bit版のものがインストールされていたので・・・。

上記内容をよく読むと、VC++2008expressって、64bit Windows上でも64bitのコンパイラではなく、32bitのクロスコンパイラが使われるようですね・・・。

次期バージョンのVisual Studio 2010では、64bitに完全対応してくれるのかな???

ということで?、無事、IDEから64bit プロジェクトが構成されました(^^)

VC++ 2008 Express Edition

補足

新たにプロジェクトを作成しても、デフォルトではソリューションプラットフォームにx64(or Itanium)の選択肢は表示されません。
まぁ、当たり前だと思いますが・・・。プラットフォームにx64 or Itanium を追加するには、一手間かける必要があります。

構成マネージャのダイアログを表示させ、アクティブソリューションプラットフォームから”新規作成”を選択すると、Itaniumとx64が現れますので、x64を追加する必要があります。

新規作成

それと、当然ですが、Windows SDK v7をインストールする必要があります。言わずもがなですけど・・・。

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

COMサーバーにJScriptのイベントハンドラを登録する の「修正版」です。

前回の例には、致命的なエラーが起こる場合があります。onCurrent というプロパティーに関数オブジェクトを設定し、取得することができます。しかし、まだonCurrentプロパティに関数オブジェクトを設定しない、未設定の場合、onCurrent を参照すると、スクリプト側にエラー値を戻すとので、エラーがスローされ実行が止まってしまいます。

つまり、

var obj = new ActiveXObject("MyServer.Utility");
WScript.Echo(obj.onCurrent);

とすると、エラーで止まってしまいます。まだプロパティが未設定ならば、nullを返すようにすれば万事OKです(^^;

というわけで、修正です。

まず、onCurrentのプロパティの設定と取得でのデータ受け渡しを、IDispatch* ではなく、VARIANT にします。関数オブジェクトは、このVARIANTで、vtメンバをVT_DISPATCHにして受け渡すことになります。

ところどころ修正したものが次のとおり。

/*
 compiler command line :
   cl /LD MyServer.cpp /link /TLBOUT:MyServer.tlb
*/

#pragma comment(lib,"comsuppw.lib")

#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;

#include <comutil.h>

//マクロ定義
#define PPVOID(X) (reinterpret_cast<LPVOID*>(X))
#define QI(X,Y,Z) ((X)->QueryInterface((Y),(reinterpret_cast<LPVOID*>(Z))))
#define RELEASE(X) {if(X){(X)->Release(); X=NULL;}}

// 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] VARIANTARG var);
  [id(2),propget] HRESULT onCurrent([out,retval] VARIANTARG *pvar);

};

/////////////////////////////////////////////////////////////////////////////
// イベントインターフェイス定義 : 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()
     {

     }

  __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)
    {
      TCHAR pBuffer[MAX_PATH+1] = {0};
      ::GetCurrentDirectory(MAX_PATH+1,pBuffer);

      *sPath = _bstr_t(pBuffer).Detach();

      Fire();
      return S_OK;
    }

  // スクリプトから渡されるオブジェクトの設定と取得
  HRESULT put_onCurrent(VARIANTARG var)
    {
      HRESULT hRes = E_INVALIDARG;
      LPDISPATCH pDispatchNew = NULL;

      if(var.vt == VT_DISPATCH)
        {
          if(SUCCEEDED(hRes = QI(var.pdispVal,IID_IDispatch,&pDispatchNew)))
            {
              /*if(m_pDispatch)
                m_pDispatch->Release();*/
              RELEASE(m_pDispatch);

              m_pDispatch = pDispatchNew;
            }
        }
      else
        hRes = E_INVALIDARG;

      return hRes;
    }

  //関数オブジェクトの取得
  HRESULT get_onCurrent(VARIANTARG *pvar)
    {
      HRESULT hRes = S_OK;
      IDispatch *pDisp = NULL;
      if(m_pDispatch)
        {
          if(SUCCEEDED(hRes = QI(m_pDispatch,IID_IDispatch,&pDisp)))
            {
              VariantClear(pvar);
              pvar->vt = VT_DISPATCH;
              pvar->pdispVal = pDisp;
            }
        }
      else
        {
          //もし保持している関数オブジェクトがなければ空に。
          VariantClear(pvar);
        }

      return hRes;
    }

private:

  LPDISPATCH m_pDispatch;

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

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

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

VARIANT型とIDispatchインターフェイスは、C++とJScriptとでデータを受け渡しする上で必ず付いてまわるのですが、あまりこのあたりを詳しく解説してある本を見つけることができないのがネックです。

この件の備忘録終了。

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);
}

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

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でお手軽に・・・というような用途にいいかもです。