2.3 拡張シェルオブジェクト例題

こでは、前章までに解説した知識を用いて、実用的な拡張シェルオブジェクト「簡単ファイル暗号化ユーティリティ EasySecret」(以下、EZSECRET)を具体的に作成してみることにします。
ァイル暗号化ツールは、単独で動作するアプリケーションとしても勿論有効ですが、EZSECRETは拡張シェルの勉強のための教材ですので、シェルに統合された拡張シェルオブジェクトとして作成します。(実際問題、勉強という目的以外にも、これによりエクスプローラやフォルダから右クリックでファイルの暗号化・復号化が出来るようになるので、便利でもあると思います。)

こで、以下ではまず EZSECRETの仕様を決め、次にその仕様の実現のために必要なレジストリの設定を確認し、最後にインターフェイスを実装する、という順序で解説を行うことにします。
だし、内容は前章までの知識と COMに関する基本的な知識を前提としていますので、不明な点が出てきましたら、前章に立ち戻るなり関連書籍を参照するなりして下さい。また、お手持ちの開発環境に付属しているサンプルプログラムの中にも関連するものが存在している可能性が高いのでそちらを参考にするのも良いと思います。

お、開発環境としては「Delphi」を採用していますが、内容は Delphi使い以外の方でも参考になるものと思いますので拡張シェルを作成してみたいとお思いの方は一度目を通していただければ幸いです。

2.3.1 EasySecret仕様

はじめに の節では EZSECRETの仕様を決定しますが、ここでいう"仕様"とは拡張シェルに関する部分の仕様のみを指すものとします。つまり、暗号化に関する仕様(暗号の種類、復号の方法等)については触れないということです。
れは、解説のボリュームが増えすぎるとかえって本来の主題がぼやけてしまうためで、今回はあくまでも拡張シェルオブジェクトの作成方法を学習するという点に的を絞ります。以下の節でもこの方針を通すことにします。(暗号化に関するコードは掲載しません。)
お、ここで作成・使用したユーティリティ・コンポーネントは、後日、也工房の各コーナーに展示する予定としていますので、必要な方はそこからダウンロードして使用して下さい。

暗号化、復号化のタイミング 号化、復号化はそれぞれ、エクスプローラやフォルダでの右クリックで現れるコンテキストメニューから「暗号化」「復号化」が選択された時に実行されるものとします。
たがって、追加するコンテキストメニュー項目は「暗号化」「復号化」の二つとなります。ただし、「復号化」は拡張子が「.sct」であるファイルが選択されている時にのみ追加されるものとします。(暗号化されたファイルは「.sct」の拡張子を持つよう実装します。)

の機能を実現するにはコンテキストメニューハンドラを作成する必要があります。
コンテキストメニューハンドラ内で暗号化、復号化の全ての処理を行うことも可能ですが、複数回に分けてファイルが選択された状態でも「暗号化」「復号化」がまとめて処理できるよう、拡張シェルオブジェクトと暗号化/復号化を行うモジュールとを分離することにします。

暗号化時情報の表示 ーザがパスワードを忘れた際の助けとなるように、拡張子が「.sct」となるファイルに対しては「暗号化時情報」のコンテキストメニューを追加して、暗号化した際のパスワード以外の各種情報(元ファイル名、暗号化オプション、暗号化場所)を表示するようにします。

の機能を実現するには上記のコンテキストメニューハンドラにこの機能を追加する必要があります。

下に、上記仕様をまとめます。

実現させる機能 実装するハンドラ
 エクスプローラ等のコンテキストメニューからファイルの「暗号化/復号化」を行う。
 また、暗号化ファイルの「暗号化時情報」の表示も行えるようにする。
 ただし、"復号化"及び"暗号化時情報"のメニューへは拡張子が「.sct」のファイルのみがアクセスできるようにする。
コンテキストメニューハンドラ
[ 補足 ]
 複数回ファイルを選択しまとめて処理できるよう、拡張シェルオブジェクトから暗号化/復号化のためのモジュールを分離する。
 したがって、拡張シェルオブジェクトは選択されたファイルを暗号化/復号化モジュールにディスパッチするだけで、それ自身では暗号化/復号化処理を行わない。
 以下ではこの拡張シェルオブジェクトの方を「EZSHLEXT.DLL」、暗号化/復号化のためのモジュールの方を「EZSECRET.EXE」と呼ぶこととする。

2.3.2 EasySecretに必要なレジストリ設定

はじめに の節では 上記EZSECRETの仕様を満たすために必要なレジストリの設定を検討します。
だし注意しなければならないのは、当然のことながら個別のアプリケーションに依存するレジストリの設定はそのアプリケーションがアンインストールされるときには削除されなくてはならない、という点です。
要するにレジストリに何かしらの設定をする必要があるアプリケーションにはしっかりしたアンインストーラが必要になるということです。
の問題に対処する一番お手軽な方法は商用/非商用で出回っているインストーラ/アンインストーラ作成ユーティリティ(InstallShieldなど)を使用することです。個人的にはこの方法をお勧めしますし EZSECRETでもその手のツールの使用を前提とすることとします。
したがって、ここでは設定する必要があるレジストリのみを簡単のため「REGEDIT4」の形式で考えることにします。
インストール時/アンインストール時はどうする等というのはツールに任せることにします。
はいえ実際の場面ではそれらユーティリティではかえって面倒になってしまう場合もあったりしますので、その場合はインストーラ/アンインストーラを自作して下さい。
カッコ良くコントロールパネルの「アプリケーションの追加と削除」にアプリケーションを登録させるためにはアンインストーラを Windowsに登録する必要がありますのでインストーラの作業が若干増えることになります。(以下に詳細)

ミニ解説: アンインストーラの Windowsへの登録

ンインストーラの Windowsへの登録はレジストリの「HKEY_LOCAL_MACHINE¥SOFTWARE¥Microsoft¥Windows¥CurrentVersion¥Uninstall」に対して行います。
まず、このキーに適当なサブキー(大抵はアプリケーション名)を作成します。作成したキーに"DisplayName"、"UninstallString"のエントリを作成すれば登録完了です。
以下に各エントリの意味を解説します。

DisplayName.....「アプリケーションの追加と削除」に表示されるアプリケーションを示す文字列を指定
UninstallString.....Windowsがアンインストーラを起動するときに使用するコマンドラインを指定

他のアプリのアンインストーラのキーを参考にすると感じがつかめると思います。

拡張子".sct"の登録 の仕様のところでも触れたとおり、暗号化ファイルの拡張子は".sct"とします。
独自のアイコンなどを表示させるためにはこの拡張子を Windowsに登録する必要があります。
前章までに述べたとおり、これは以下のレジストリの登録でOKです。
以下の例では拡張子".sct"に"EasySecret.Document.1"というAppIDが割当てられています。
[HKEY_CLASSES_ROOT\.sct]
@= "EasySecret.Document.1"

[HKEY_CLASSES_ROOT\EasySecret.Document.1]
@= "EasySecret Document"


拡張子".sct"ファイルのデフォルトアイコンの登録 はり独自の拡張子には独自のアイコンを割当てた方がシェルの外見がすっきりします。
前章までに述べたとおり、これは拡張子に割当てられた AppIDのレジストリエントリに"Defaulticon"キーを加えてアイコンを設定すればOKです。
以下の例では、EZSECRET.EXEの2番目のアイコンインデックスのものをデフォルトアイコンに設定しています。
[HKEY_CLASSES_ROOT\EasySecret.Document.1\DefaultIcon]
@= "EZSECRET.EXE,2"


拡張シェルオブジェクト(EZSHLEXT.DLL)の登録 張シェルオブジェクトはコンテキストメニューハンドラとして Windowsに登録されなくてはならないので、まず拡張シェルオブジェクト自身を登録します。
前章までに述べたとおり、これは拡張シェルオブジェクトに割当てられたCLSIDを「HKEY_CLASSES_ROOT¥CLSID」キーにサブキーとして登録し、ファイル本体の位置を更にその"InProcServer32"サブキーに登録することでOKです。
[HKEY_CLASSES_ROOT¥CLSID¥{D91A29E0-57EA-11D2-A96E-444553540000}]
@= "EasySecret Shell Extension"
[HKEY_CLASSES_ROOT¥CLSID¥{D91A29E0-57EA-11D2-A96E-444553540000}¥InProcServer32]
@= "EZSHLEXT.DLL"
"ThreadingModel" = "Apartment"


コンテキストメニューハンドラの登録 張シェルオブジェクト自身の登録は済んだので、次はコンテキストメニューハンドラを追加することを Windowsに教えてやる必要があります。
前章までに述べたとおり、これは追加されるコンテキストメニューハンドラの本体がどこにあるかをCLSIDをとおして教えてやればOKです。
[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\EZSecret]
@= "{D91A29E0-57EA-11D2-A96E-444553540000}"


上で、先の仕様のもと EasySecretに必要な最低限のレジストリ設定は完成です。
後はこれらをインストーラ/アンインストーラに反映させればOKです。

2.3.3 インターフェイスの実装

はじめに 初に予告しましたとおり、インターフェイスの実装は Delphiにて行います。しかし、インターフェイスの実装はどの言語を使用しても似たような形になるはずですので別の言語で開発しておられる方にもそれなりに参考になるものと思います。

番としましては、まずこの手のインプロセスActiveXサーバーではお決まりの形となっていますライブラリソースを解説し、次にコンテキストメニューハンドラの核であるIContextMenuインターフェイスの実装を解説します。
お、コードに関する注釈は最低限のものとなっておりますので、不明な点は前章の各インターフェイスの解説を参照するなり関連書籍・資料を参照するなりして下さい。
2.3.3.1 ライブラリソース

イブラリソースはきわめて単純なものです。インプロセスActiveXサーバーを開発する場合は常にいわゆる「お決まりの型」にするだけでよいです。
意しなければならないことは、下のコード中にも見られるようにインプロセスActiveXサーバーは必ず
1. DllGetClassObject
2. DllCanUnloadNow
3. DllRegisterServer
4. DllUnregisterServer
の4つの関数をエクスポートしなくてはならない、という点だけです。

下にライブラリソースを示します。
library EZSHLEXT;

uses
  SysUtils,
  Classes,
  ComServ,
  EZShlObj in 'EZShlObj.pas';

exports
  {Inprocess ActiveX Server がエクスポートしなければならないお約束の関数}
  DllGetClassObject,
  DllCanUnloadNow,
  DllRegisterServer,
  DllUnregisterServer;

begin
end.


2.3.3.2 IContextMenuインターフェイスの実装

ンテキストメニューハンドラはIContextMenuインターフェイスを実装することで実現されます。
ここで、そのIContextMenuを実装するクラスはTComObjectから派生させたクラスで、クラス名をTEZShellObjectクラスとすることとします。
回の例に限らず、拡張シェルオブジェクトはTComObjectクラスから派生させるのが便利です。TComObjectはIUnknownインターフェイス、ISupportErrorInfoインターフェイスを既に実装しており、クラスファクトリによるインスタンス化・アグリゲーション・OLE例外処理・デュアルインターフェースで使うsafecall呼び出し規約、をサポートしています。

章までで述べたように、コンテキストメニューハンドラはその初期化をIContextMenuを通じては行いません。初期化はIShellExtInitインターフェイスを通じて行われます。
したがって、最終的にはTEZShellObjectクラスは「TComObject、IContextMenu、IShellExtInit」から継承しなくてはならない、ということが分かります。(ObjectPASCALはクラスの多重継承を認めてはいませんが、インターフェイスの多重継承は認めている点に注意)

ずは最初にTEZShellObjectクラスの宣言部を示し、追加されたプライベートフィールドに関する解説をすることにします。
TEZShellObject = class(TComObject,IShellExtInit,IContextMenu)
private
  Files: TStrings; //選択されたファイルを格納する
  DdeClientConv: TDdeClientConv; //DDE通信用

  procedure InitFields; //各内部フィールドを初期化する
  procedure SendFileNames(WindowClass, CommandParams, DdeServerName: string); //EZSECRETを起動し、ファイル名を送る

public
  destructor Destroy; override; //各内部フィールドを掃除する

  //IShellExtInit
  function Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
    hKeyProgID: HKEY): HResult; stdcall;

  //IContextMenu
  function QueryContextMenu(Menu: HMENU;
    indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall;
  function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
  function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
    pszName: LPSTR; cchMax: UINT): HResult; stdcall;
end;

内部フィールド用  途
Filesユーザーによって選択されたファイル名を格納するTStringsオブジェクト
DdeClientConvEZSECRET.EXEにファイル名を渡すのに使用されるTDdeClientConvオブジェクト
注意:ここではDDE通信を用いてファイル名を渡すことにしたが、別にDDE通信でなくても要はファイル名をEZSECRETに渡せれば他の手段でも何ら問題は無い。


に、追加されたプライベートメソッドについて、実装部とその解説を示します。
念のためここで断っておきますが、別に拡張シェルオブジェクトを作成する時には常に上のTEZShellObjectのようなプライベートメソッドなりプライベートフィールドなりを追加する必要があるというわけではありません。
だし、IContextMenuインターフェイスやIShellPropSheetExインターフェイスのような別のインターフェイスを通じて初期化されるタイプの拡張シェルオブジェクトは、上でのFilesフィールドのような初期化用のフィールドを持つ必要が結局は出てくることになりますが...
{各内部フィールドを初期化する
~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

procedure TEZShellObject.InitFields;
var
  i: integer;
  rt: TRect;
  tmpBmp: TBitmap;
begin
  Files := TStringList.Create;
  DdeClientConv := TDdeClientConv.Create( Nil);
  with DdeClientConv do
  begin
    ConnectMode := ddeAutomatic;
    FormatChars := FALSE;
  end;
end;



{EZSECRETを起動し、ファイル名を送る
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[引数]
WindowClass:ファイル名の送り先ウィンドウクラス(例:TEncodeForm)
CommandParams:EZSECRETを起動する際に使用するコマンドパラメータ(例:/e)
DdeServerName:ファイル名を送る際のリンク先DDEサーバー名(例:DdeEncdServer)
}

procedure TEZShellObject.SendFileNames(WindowClass, CommandParams, DdeServerName: string);
var
  hEXE: UINT;
begin
  if FindWindow( PChar(WindowClass), Nil) = 0 then
  begin
    hEXE := WinExec( PChar( 'EZSECRET.EXE ' + CommandParams), SW_SHOW);
    if hEXE < 32 then
    begin
      MessageDlg( 'Easy Secret を起動できませんでした', mtWarning, [mbYES], 0);
      Exit;
    end;
  end;

  while FindWindow( PChar(WindowClass), Nil) = 0 do Sleep(50); //Windowが生成されるまで待つ

  if not DdeClientConv.SetLink('EZSECRET', DdeServerName) then Exit;
  if not DdeClientConv.ExecuteMacroLines( Files, False) then Exit;
end;

メソッド解  説
InitFields  Files、DdeClientConvオブジェクトを生成する。
 このメソッドをどこで呼出せばよいかが重要である。Createをオーバーライドしてその中で呼出しても意味がない。なぜなら、クラスファクトリから生成された場合、Createは経由されないからである。
 したがって、ここではこのメソッドをIShellExtInit.Initialize中で呼出すことにした。そうすればこのメソッドはコンテキストメニューハンドラが初期化される際必ず呼出されるからである。
SendFileNames  EZSECRET.EXEにFilesに格納されたファイル名を渡す。
 ここではDDE通信を使用しているが、上でも述べたように、別の手段を用いても何ら問題は無い。例えば、ウィンドウメッセージを送りメモリマップドファイルを経由してファイル名を送るのも良いだろう。
 各引数についてはコード中のコメント参照のこと。


下に、IShellExtInitインターフェイス、IContextMenuインターフェイスに関して、各メソッドの実装部とその解説をまとめて示します。
これでTEZShellObjectは完成です。後は、ユニットのInitialization部とFinalization部のみとなります。最初に断りましたとおり、EZSECRET.EXEの方は具体的なファイルの暗号化・復号化をうけもっているだけなのでここでは割愛します。
お、Destroyメソッドの実装部を載せてませんが、これは単にInitFieldsメソッドで生成したオブジェクトを破棄しているだけなので特に掲載する必要もないものと思いあえてそうしています。万が一わからないという方がおられましたらメールでも下さい。
//IShellExtInit

{拡張シェルの初期化
~~~~~~~~~~~~~~~~~~
選択されたファイルをFilesに格納する。

[引数]
pidlFolder:拡張コンテキストメニュー、拡張プロパティシートに対しては親フォルダ、
      非デフォルトドラッグアンドドロップに対してはターゲットフォルダを示
      すTItemIDList構造体へのポインタ
lpdobj:1つ以上の選択された(ドロップされた)項目を返すIDataObjectオブジェクト
      へのポインタ
hKeyProgID:拡張コンテキストメニュー、拡張プロパティシートに対してはフォーカス
      のあるファイル項目のレジストリキー(ファイルクラス)、非デフォルト
      ドラッグアンドドロップに対してはターゲットフォルダのレジストリキー
      (ファイルクラス)を示す

[返値]
関数が成功すれば、NOERRORを返します。失敗した場合はOLEで定義されたエラーコード
を返します。
}

function TEZShellObject.Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
  hKeyProgID: HKEY): HResult;
var
  StgMedium: TStgMedium;
  FormatEtc: TFormatEtc;
  i, n: integer;
  Buffer: array[0..1024] of char;
begin
  Result := E_FAIL;
  if lpdObj = Nil then Exit; //引数チェック

  InitFields; //内部フィールドの初期化

  {データ形式の指定}
  with FormatEtc do
  begin
    cfFormat := CF_HDROP; //ファイルリスト
    ptd := Nil;
    dwAspect := DVASPECT_CONTENT; //データはコンテナ内部への埋め込み
    lindex := -1; //全てのデータを対象
    tymed := TYMED_HGLOBAL; //媒体にグローバルメモリハンドル
  end;

  {選択されたファイル名の取得}
  Result := lpdObj.GetData( FormatEtc, StgMedium);
  if Failed( Result) then Exit;

  n := DragQueryFile( StgMedium.hGlobal, $FFFFFFFF, Nil, 0); //ファイル数の取得
  for i := 0 to n - 1 do
  begin
    ZeroMemory( @Buffer, SizeOf(Buffer));
    DragQueryFile( StgMedium.hGlobal, i, Buffer, SizeOf(Buffer));
    if FileExists( string( Buffer)) then Files.Add( string( Buffer));
  end;

  {Resultの設定 & StgMediumの開放}
  if n > 0 then
    Result := NOERROR;
  ReleaseStgMedium( StgMedium);
end;



//IContextMenu

{コンテキストメニューを追加する
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[引数]
Menu:メニューハンドル
indexMenu:最初のメニュー項目の挿入位置(0ベース)
idCmdFirst:挿入したメニュー項目IDの下限
idCmdLast:挿入したメニュー項目IDの上限
uFlags:起動された状況を示すフラグ(詳細はSDK参照)

[返値]
関数が成功すれば、HRESULTを返します。ただし、そのCODEメンバ(low word)は追加
したメニュー項目のIDの最大値(idCmdFirst - 1)からのオフセットを表しています。
}

function TEZShellObject.QueryContextMenu(Menu: HMENU;
  indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult;
var
  i: integer;
  idEncd, idDecd, idProp: UINT;
begin
  Result := 0;
  if (uFlags and CMF_NORMAL) <> CMF_NORMAL then Exit;
  idEncd := idCmdFirst;
  idDecd := idCmdFirst + 1;
  idProp := idCmdFirst + 2;

  InsertMenu( Menu, indexMenu, MF_SEPARATOR or MF_BYPOSITION, 0, Nil);
  InsertMenu( Menu, indexMenu + 1, MF_STRING or MF_BYPOSITION, idEncd, 'ファイルの暗号化...');

  Result := Result + 1; //increment Result

  for i := 0 to Files.Count - 1 do
    if LowerCase( ExtractFileExt( Files.Strings[i])) <> '.sct' then Exit;

  InsertMenu( Menu, indexMenu + 2, MF_STRING or MF_BYPOSITION, idDecd, 'ファイルの復号化...');
  InsertMenu( Menu, indexMenu + 3, MF_STRING or MF_BYPOSITION, idProp, '暗号化時情報...');

  Result := Result + 2; //increment Result
end;



{追加したメニューを実行する
~~~~~~~~~~~~~~~~~~~~~~~~~~
QueryContextMenuで追加されたメニューがクリックされた時に呼出されるので、メニ
ューを実行する。

[引数]
lpici:選択されたコマンドに関する情報を格納しているTCMInvokeCommandInfo構造体

[返値]
関数が成功すれば、NOERRORを返します。失敗した場合はOLEで定義されたエラーコー
ドを返します
}

function TEZShellObject.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
begin
  Result := E_FAIL;

  {アプリケーション呼出しの場合はEXit}
  if HiWord( Integer( lpici.lpVerb)) <> 0 then Exit;

  {メニューIDのオフセット値は 0(暗号化)、1(復元)、2(暗号化時情報)のみ}
  if LoWord( Integer( lpici.lpVerb)) > 2 then
  begin
    Result := E_INVALIDARG;
    Exit;
  end;

  if LoWord( Integer( lpici.lpVerb)) = 0 then
    SendFileNames( 'TEncodeForm', '/e', 'DdeEncdServer') //暗号化
  else if LoWord( Integer( lpici.lpVerb)) = 1 then
    SendFileNames( 'TDecodeForm', '/d', 'DdeDecdServer') //復元
  else
    SendFileNames( 'TProptyForm', '/p', 'DdePropServer'); //暗号化時情報

  Result := NOERROR;
end;



{Explorer のステータスバーに表示する文字列を指定する
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[引数]
idCmd:選択されているメニュー項目IDのidCmdFirstからのオフセット
uType:返値の種類を指定するパラメータ(詳細は SDK参照)
pwReserved:予約済
pszName:返値を格納するバッファーのアドレス
cchMax:上記バッファのサイズ

[返値]
関数が成功すれば、NOERRORを返します。失敗した場合はOLEで定義されたエラーコー
ドを返します。
}

function TEZShellObject.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
  pszName: LPSTR; cchMax: UINT): HResult;
begin
  Result := E_INVALIDARG;
  if idCmd > 1 then Exit;

  if idCmd = 0 then
  begin
    {暗号化}
    StrLCopy( pszName, 'ファイルを暗号化します', cchMax - 1);
  end
  else if
idCmd = 1 then
  begin

    {復元}
    StrLCopy( pszName, '暗号化ファイルを復元します', cchMax - 1);
  end
  else
  begin

    {暗号化時情報}
    StrLCopy( pszName, '暗号化ファイルの情報を表示します', cchMax - 1);
  end;

  Result := NOERROR;
end;

メソッド解  説
Initialize  ここでコンテキストメニューハンドラの初期化を行う。
 最も重要な作業は、ユーザーに選択されたファイルをFilesオブジェクトに記録することである。
 シェルから渡されたIDataObjectオブジェクトの取り扱いがややこしいが、選択ファイル名を取得したいだけであれば常に上のコードに掲載の手順でファイル名を取得すればよい。
 IDataObjectオブジェクトから得たTStgMedium構造体の開放タイミングは拡張シェルオブジェクトにしかわからないので、拡張シェルオブジェクトの実装部内で必ずその開放をしなくてはならない点に注意。勿論、そういう意味でInitializeメソッド内で開放しなければならないわけではない。
 引数に関しては上記コード中のコメントを参照のこと。
QueryContextMenu  登録した拡張子のファイルをユーザーが選択した上で右クリックしたとき、つまり選択されたファイルに関するコンテキストメニューをシェルが表示しなくてはならないとき、シェルがこのメソッドを呼出し何か追加するべきメニューがあるかどうかを問合せてくる。
 メニューを追加する必要がある場合は上のコードのようにInsertMenuしてやればよい。ただし、どこに追加してもよいというわけではない。引数のindexMenu、idCmdFirstにしたがったものにする必要がある。
 引数に関しては上記コード中のコメントを参照のこと。
InvokeCommand  QueryContextMenuで追加したメニューをユーザーが選択したときに呼出される。
 上のコードのように選択されたメニューのメニューIDオフセット値を調べてその後の処理を行うとよい。
 引数に関しては上記コード中のコメント及び前章での解説を参照のこと。
GetCommandString  ユーザーがファイルを右クリックしてでたコンテキストメニューの上をマウスカーソルが移動している最中、ビューの左下にそのマウスカーソルの下にあるメニューの動作概要が表示されるが、QueryContextMenuで追加したメニューに関するその文字列をシェルが問合せてくる際に呼出される。
 これもメニューIDオフセット値を調べて処理を行うとよい。
 引数に関しては上記コード中のコメントを参照のこと。


後にTEZShellObjectを宣言・定義しているユニットのInitialization部及びFinalization部を掲載します。
Initialization部では、COMライブラリの初期化とクラスファクトリの生成を行っています。
このように拡張シェルオブジェクトは「お決まりの型」としてInitialization部でクラスファクトリを生成しておくのがよいでしょう。
クラスファクトリをサーバーのロード時に自動的に使用可能にするためには、クラスファクトリはその関連付けられたサーバーを含むユニットの initializationセクションでインスタンス化しておかなければならないためです。
Finalization部では、COMライブラリの後始末をしています。

際にEZSHLEXT.DLLをコンテキストメニューハンドラに登録して使用してみると、シェルの拡張はユーザーにアピールするところが意外に大きいことに気付きます。研究すればいろいろと面白いことが出来るようになると思います。
直言うと僕自身まだシェルをいじった経験はほとんどありません。(コンテキストメニューハンドラとコピーフックハンドラとプロパティシートハンドラのみ経験...)
皆さんの開発事例等から何か面白い応用例等がありましたら教えて下さい。(最後はお願いになってしまいましたね ^-^;)
initialization
  CoInitialize( Nil);
  TComObjectFactory.Create( ComServer
                ,TEZShellObject
                ,CLSID_EZShellObject
                ,''
                ,'Easy Secret Ver. 1.00'
                ,ciMultiInstance);

finalization
  CoUninitialize;

end.

進め!中級プログラマー 第2号
次号予告
号に引き続きお送りしました、「シェルと仲良くする part2」はいかがでしたでしょうか。
ボリュームが相当に膨らんでしまいましたので読込むのにかなり苦労されたかもしれませんが、ファイルを細かく分けすぎてしまいますと後々のバックナンバー管理が大変ですので、計3ファイル(3ページ)とさせていただきました。ご了承下さい。

回で一応「シェルと仲良くする」は終了なのですが、実は、シェルの特集を組んでおきながら、シェルの大事な機能である「ドラッグ&ドロップ」及び「タスクトレイ」に関しましてあえて触れていませんでした。
これは、両機能がそれぞれ、別々に特集を組む必要があるほどの題材であると判断したためです。
というわけですので、次号からはこの両機能「ドラッグ&ドロップ」「タスクトレイ」を続けて特集する予定としています。

、とりあえず次号は「タスクトレイを利用する」と題しましてタスクトレイに関します概念、テクニックをお送りする予定ですのでよろしければまたお越しください。

だし、例のごとく次号発刊日は未定です。前号に続き今回のでもかなり疲れてしまいましたのと、いつまでも失業中といっているわけにはいかないので、ここは再就職活動を優先させます。というわけですので次号に取り掛かるのはいつになるか未定です。ただ、皆さんからの強力なプッシュがあれば多少早くなるかもしれません。どちらにしましても、今度もあまり期待しないで適当に待っていて下さい。(何とか年内には次号を出したいのですけど...)

前々ページ( 2.1 拡張シェル登録に係るレジストリ操作 )に移動
前ページ( 2.2 拡張シェルオブジェクト用インターフェイス )に移動
トップページに戻る

メール作成 ご意見・ご要望等は左のポストをクリックして杉浦(gv4j-sgur@asahi-net.or.jp)までお寄せ下さい
なお、当サイトは「Netscape Communicator 4.5 for MS Windows」でのみ表示・動作が調整されております
Internet Explorer の場合、正常に表示・動作しない可能性がありますのでご了承下さい。
Copyright © 1998 Jun-ya Sugiura and Hanayo Sugiura. All rights reserved. Updated - 16 May 1999