2006 年 7 月 4 日 21 時 25 分

IUniformResourceLocatorW インタフェース


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


さて、では次の手を考えてみよう。

InternetShortcutClass が実装しているインタフェースは
ISellLinkW 以外にもまだまだ数多くある。
その中に、IUniformResourceLocatorW というのがある。
URL そのものを表すらしい、変な名前のインタフェースだ。

MSDN を探る。みっけ。

http://msdn.microsoft.com/library/default.asp?url=/workshop/misc/shortcuts/reference/iuniformresourcelocator.asp

そのものズバリの GetURL ってメソッドがある。
これは使えそうだ。

では、これも一気に定義を書いてみよう。
場所は Interop フォルダ内に入れる。
これも、使うメソッドのみ定義することにする。

========== IUniformResourceLocatorW.cs ==========

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace LoaferShellEx.Interop {

    [ComImport]
    [Guid("CABB0DA0-DA57-11CF-9974-0020AFD79762")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IUniformResourceLocatorW {

        // 今回使うものだけちゃんと定義し、それ以外端折る
        // 端折ったやつは絶対に呼び出さないように。

        void _0(); // SetURL

        void GetURL(
                [MarshalAs(UnmanagedType.LPWStr)]
                out string ppszURL);

        void _2(); // InvokeCommand

    }
}

========== end of IUniformResourceLocatorW.cs ==========

定義するのは GetURL メソッドだけなので簡単に見えるが、
これまた文字列の扱いを考えてないと罠にはまる。

GetURL の元のシグネチャを引用しよう。
ばお、このシグネチャは Unicode 版(W)のものだ。

    HRESULT GetURL(
            LPWSTR *ppszURL
    );

非常に見落とし易いが、引数である ppszURL は、
「LPWSTR *ppszURL」であって「LPWSTR ppszURL」ではない。

C 言語で言うと、文字配列を確保して渡すのではなく、
メソッド側が文字列領域を確保して、
そのポインタを引数に返すのである。

IShellLinkW の時は、バッファを割り当てて、
メソッドがそこに書き込む処理であったが、
これは、バッファの割り当てはメソッド側が行うのだ。

これは少し厄介だ。

.NET の管理下でないメモリが割り当てられて返却されるので、
呼び出し側で解放しなければならないということになる。

ppszURL パラメータの説明を読むと、このように書いてある。

Address of an LPSTR that will be filled with a pointer to the object's URL. Because this method allocates memory for the string, you must create and instance of an IMalloc interface and free the memory using IMalloc::Free when it is no longer needed.

メソッドの確保したメモリは、
IMalloc::Free を呼び出して解放しろとある。

これは、シェルのメモリアロケータが持つ、
IMalloc インタフェースを、CoGetMalloc API で取得し、
その Free を呼び出して解放するという手順を踏むか、
一連の手順と同じ役割を持つ API である、
CoTaskMemFree を呼び出して解放しろということだ。

なお、.NET には、Marshal.FreeCoTaskMem という名前で、
CoTaskMemFree API が定義されているため、
このメソッドを使えば解放することは可能である。

以上に基づいて C# で定義する場合、
メソッド定義は、以下のようになると考えられる。

    void GetURL(out IntPtr ppszURL);

IntPtr は、汎用ポインタ型である。
ポインタへの参照を渡すことで、
メソッド側が確保したメモリのアドレスが返却される。

これを呼び出す場合、以下のような手順となるはずだ。

    IntPtr ptr;
    ~.GetURL(out ptr);
    string url = Marshal.PtrToStringUni(ptr);
    Marshal.FreeCoTaskMem(ptr);

GetPath が返すポインタは、ptr に格納される。
ptr が指すのは NULL 終端 Unicode 文字列なので、
Marshal.PtrToStringUni で String にコピーを取る。
そして、Marshal.FreeCoTaskMem で ptr を解放する。

これは、少し面倒ではないだろうか。

GetPath メソッドの機能を呼び出し側からの視点で見ると、
文字列(String)への参照を渡せば、
メソッド側が新しい String インスタンスを作成して、
その参照を引数に返却するということになる。

上記のコードは、IntPtr を使ってアドレスを扱っており、
IntPtr の解放忘れによるメモリリークの危険があるのだ。
そうなると、GetURL の後に例外処理コードが必要になり、
さらに見にくいコードになってしまう。

実は .NET は上のようなシナリオを考慮しており、
COM や API 側が文字列を内部で割り当てて返す場合、
自動的に Marshal.FreeCoTaskMem で解放する機能がある。
(BSTR の場合は Marshal.FreeBSTR)

この機能を利用すると、上に書いた自然な考え方で
GetPath を宣言することが可能となるのだ。

    void GetURL(
            [MarshalAs(UnmanagedType.LPWStr)]
            out string ppszURL);

ppszURL は String を受け取るということなので、
C# では out string となる。
これによって、メソッドが文字列を返すという意味になる。

.NET は、String を out で受け取る場合、
COM や API 側で文字列が割り当てられていると判断し、
戻り値を .NET の String にコピーした後で
適切なメソッドを呼んで解放しようとする。

MarshalAs は、その解放の方法を指定する役割もある。
指定しないと String は BSTR としてマーシャリングされる。
GetPath は NULL 終端 Unicode 文字列を扱うので、
MarshalAs で LPWStr を指定することで、
解放する際に Marshal.FreeCoTaskMem が呼ばれるのだ。

上記のように宣言した場合、
GetPath を呼び出す手順は以下のようになる。

    string url;
    ~.GetURL(out url);

どうだろう、非常にすっきりとして見やすくなった。

url を out として渡す際に、.NET によって
自動的に String の変換とメモリの解放が行われるのだ。


このように COM や API の相互運用機能をうまく利用すると、
それらを .NET のクラスの一部のように定義し、
低水準の型を意識せず、自然に使用することができる。

確かに宣言を書くのが少し面倒なのは否定できないが、
一度正しく宣言さえしておけば、
過去の資産を利用しつつ、効率が良い開発が可能となるのだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.