The Backyard - HowtoInteractWin32API Diff
- Added parts are displayed like this.
- Deleted parts are displayed
like this.
この記事は、「WEB+DB PRESS Vol.14」(技術評論社)に掲載していただいた記事の元原稿(必要に応じて加筆/修正/削除をしています)を公開するものです。
!Win32APIを呼び出してアイコンを取得
NetLauncherは、表示にアプリケーションのアイコンを使用します。この方法の利点は、あらかじめ実行プログラム内にエクスプローラで表示可能なアイコンが埋め込まれているため、サーバー上に余分なファイルの配備が不要な点です。
しかし、次の点が問題となります。
*.NET Frameworkではアプリケーションのアイコンを取得する手段が提供されていない
アプリケーションのメインアイコンは他のWin32アプリケーションと同様にWin32APIからアクセス可能なリソースとして埋め込まれているのですが、逆にそのため、.NET FrameworkのリソースAPIを使用して取得することが事実上、不可能です。
このような場合、.NETでは、P/Invokeと呼ばれる方法でWin32APIを呼び出すことで対応できます(リスト:IconLoader.cs 抜粋)。
リスト:IconLoader.cs 抜粋
public static Icon Load(string pathname)
{
SHFILEINFO shfi = new SHFILEINFO();
uint flags = SHGFI_ICON | SHGFI_LARGEICON;
SHGetFileInfo(pathname, 0, ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi), flags);
Icon icon = (Icon)Icon.FromHandle(shfi.hIcon);
return icon;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct SHFILEINFO
{
public IntPtr hIcon;
...省略...
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=NAMESIZE)]
public string szTypeName;
}
[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SHGetFileInfo(
string pszPath,
...省略...
uint uFlags
);
P/Invokeを実行するコードは、リソースの取り扱いや構造体のレイアウトなどWin32APIに特化したコードが紛れ込むため、一般論としてユーティリティクラスに隔離すべきです。特にリソースの取り扱いについては、Win32APIの知識と.NET Frameworkの知識の両方が求められるため、注意深い設計が必要となります。
たとえば、SHGetFileInfoで取得したアイコンハンドルはDeleteIconを使用して削除しなければなりません。一方、IconクラスのFromHandleファクトリメソッドは、引数で指定したアイコンハンドルをラップしたIconオブジェクトを作成します。この時、元のアイコンハンドルがそのまま利用されます(注)。これらの仕様を知らなければ、元のアイコンハンドルをどう扱うか正しく判断できません。たとえば、SHGetFileInfoの仕様を知っていてもIconクラスのFromHandleメソッドの仕様を知らずに、生成直後にWin32APIのDestroyIcon関数を呼び出したらどうなるでしょうか。Iconオブジェクトは元のアイコンハンドルが削除されたことがわからないため、使用時に初めて例外となります。逆に、ファクトリメソッド内でリソースを複製するように設計されたクラスであれば、オブジェクト作成後に元のリソースを解放しなければリソースリークとなります。
(注)MSDNのIcon#FromHandleの説明からはわかりません。Handleプロパティの説明から想像するか、デバッガでIconオブジェクトの内容を確認するか、または実際にDeleteIconを呼び出して動作を確認する必要があります。
P/Invokeを使用する業務アプリケーションの場合には他にも注意点があります。それは不正確なソースを作成してはならないということです。たとえば、IconLoader.csで使用しているSHGetFileInfoの機能は、メインアイコンの取得だけです。このことは、SHFILEINFO構造体のszDisplayNameやszTypeNameメンバが使用されないことを意味します。したがって、[MarshalAs(UnmanagedType.ByValTStr, SizeConst=NAMESIZE)]といった属性を省略しても正しく動作します。記述しなければ、それぞれ32ビットポインタ2つ分の8バイトの領域が確保されるだけですが、アイコンハンドルの返送には問題ないからです。
しかし、問題ないからと言って、属性の記述を省略した場合、誰かがそのソースを元にしてSHGetFileInfoを呼び出して表示名を取得する(この場合、szDisplayNameが使用されます)アプリケーションを作成すると奇妙な現象に出会うことになります。定義上はstringの参照なのにも関わらず、直接文字が埋め込まれてしまうからです。この場合、心理的に、既に正しく動作しているソースを元にして作成したという点から、見当違いな対処方法を求めかねません。
業務アプリケーションの場合、ソースファイルは企業の財産となります。したがって常に自分以外の人間がソースを再利用する可能性を考慮しなければなりません。特にメモリーレイアウトのようなクリティカルな属性を含み、しかもそれほど参照資料が出回っていないP/Invokeでは注意が必要です。
!Win32APIを呼び出してアイコンを取得
NetLauncherは、表示にアプリケーションのアイコンを使用します。この方法の利点は、あらかじめ実行プログラム内にエクスプローラで表示可能なアイコンが埋め込まれているため、サーバー上に余分なファイルの配備が不要な点です。
しかし、次の点が問題となります。
*.NET Frameworkではアプリケーションのアイコンを取得する手段が提供されていない
アプリケーションのメインアイコンは他のWin32アプリケーションと同様にWin32APIからアクセス可能なリソースとして埋め込まれているのですが、逆にそのため、.NET FrameworkのリソースAPIを使用して取得することが事実上、不可能です。
このような場合、.NETでは、P/Invokeと呼ばれる方法でWin32APIを呼び出すことで対応できます(リスト:IconLoader.cs 抜粋)。
リスト:IconLoader.cs 抜粋
public static Icon Load(string pathname)
{
SHFILEINFO shfi = new SHFILEINFO();
uint flags = SHGFI_ICON | SHGFI_LARGEICON;
SHGetFileInfo(pathname, 0, ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi), flags);
Icon icon = (Icon)Icon.FromHandle(shfi.hIcon);
return icon;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct SHFILEINFO
{
public IntPtr hIcon;
...省略...
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=NAMESIZE)]
public string szTypeName;
}
[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SHGetFileInfo(
string pszPath,
...省略...
uint uFlags
);
P/Invokeを実行するコードは、リソースの取り扱いや構造体のレイアウトなどWin32APIに特化したコードが紛れ込むため、一般論としてユーティリティクラスに隔離すべきです。特にリソースの取り扱いについては、Win32APIの知識と.NET Frameworkの知識の両方が求められるため、注意深い設計が必要となります。
たとえば、SHGetFileInfoで取得したアイコンハンドルはDeleteIconを使用して削除しなければなりません。一方、IconクラスのFromHandleファクトリメソッドは、引数で指定したアイコンハンドルをラップしたIconオブジェクトを作成します。この時、元のアイコンハンドルがそのまま利用されます(注)。これらの仕様を知らなければ、元のアイコンハンドルをどう扱うか正しく判断できません。たとえば、SHGetFileInfoの仕様を知っていてもIconクラスのFromHandleメソッドの仕様を知らずに、生成直後にWin32APIのDestroyIcon関数を呼び出したらどうなるでしょうか。Iconオブジェクトは元のアイコンハンドルが削除されたことがわからないため、使用時に初めて例外となります。逆に、ファクトリメソッド内でリソースを複製するように設計されたクラスであれば、オブジェクト作成後に元のリソースを解放しなければリソースリークとなります。
(注)MSDNのIcon#FromHandleの説明からはわかりません。Handleプロパティの説明から想像するか、デバッガでIconオブジェクトの内容を確認するか、または実際にDeleteIconを呼び出して動作を確認する必要があります。
P/Invokeを使用する業務アプリケーションの場合には他にも注意点があります。それは不正確なソースを作成してはならないということです。たとえば、IconLoader.csで使用しているSHGetFileInfoの機能は、メインアイコンの取得だけです。このことは、SHFILEINFO構造体のszDisplayNameやszTypeNameメンバが使用されないことを意味します。したがって、[MarshalAs(UnmanagedType.ByValTStr, SizeConst=NAMESIZE)]といった属性を省略しても正しく動作します。記述しなければ、それぞれ32ビットポインタ2つ分の8バイトの領域が確保されるだけですが、アイコンハンドルの返送には問題ないからです。
しかし、問題ないからと言って、属性の記述を省略した場合、誰かがそのソースを元にしてSHGetFileInfoを呼び出して表示名を取得する(この場合、szDisplayNameが使用されます)アプリケーションを作成すると奇妙な現象に出会うことになります。定義上はstringの参照なのにも関わらず、直接文字が埋め込まれてしまうからです。この場合、心理的に、既に正しく動作しているソースを元にして作成したという点から、見当違いな対処方法を求めかねません。
業務アプリケーションの場合、ソースファイルは企業の財産となります。したがって常に自分以外の人間がソースを再利用する可能性を考慮しなければなりません。特にメモリーレイアウトのようなクリティカルな属性を含み、しかもそれほど参照資料が出回っていないP/Invokeでは注意が必要です。