2006 年 6 月 30 日 20 時 57 分

IShellLinkW インタフェース


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


まずは、Shortcut クラスを定義する。

実は外部の COM クラスを定義するのは非常に簡単だ。
クラスの定義に、ComImportAttribute 属性と、
GuidAttribute 属性を定義するだけでいいのだ。

では、Interop フォルダに ShortcutClass.cs を作る。

========== ShortcutClass.cs ==========

using System;
using System.Runtime.InteropServices;

namespace LoaferShellEx.Interop {

    // シェルの提供する Shortcut COM クラス
    [ComImport]
    [ComVisible(false)]
    [Guid("00021401-0000-0000-C000-000000000046")]
    public class ShortcutClass {
        // 実装やインタフェース継承は省略できる
    }

}

========== end of ShortcutClass.cs ==========

Shortcut とせずに、ShortcutClass としたのは、
.NET のタイプライブラリインポータの流儀に従っただけだ。
実際は、GUID さえ正しければどんな名前でもかまわない。

また、定義が驚くほど単純である。

COM では、オブジェクトは CLSID を元に作成され、
必ずインターフェース経由で操作される。
C# 風に書くと以下のような感じになる。

    Shortcut obj = new Shortcut()
    IShellLinkW link = (IShellLinkW)obj;
    link.~();

つまり、Shortcut クラス型の変数に対して、
直接メソッドを呼び出すことがないので、
クラスの実装は定義する必要がない。

また、上記で示す、IShellLinkW へのキャストも、
ComImportAttribute 属性が定義されている場合、
つまり、外部にある COM のクラスの場合は、
インタフェースの実装チェックが自動的に行われる。
そのため、インタフェースを明記する必要もない。

つまり、外部で提供されている COM クラスの定義は、
実装や継承インタフェースまで省略することができる。

また、クラスに ComVisibleAttribute を指定しているが、
ComVisible は、COM 相互運用機能が有効な場合、
.NET クラスを COM オブジェクトとして使えるように、
システムに登録するかどうかを指定する。
ComVisible は既定で true である。

ShortcutClass はシェルが保有するクラスだ。
ComVisible(false) を指定しなかった場合、
COM 相互運用機能が働き、ShortcutClass が
LoaferShellEx.dll の公開クラスとして登録されてしまう。
そこで ComVisible(false) を指定して抑制する。

実は、以前作った ComFalseException なども、
ComVisible を指定していないので、
COM クラスとして登録されてしまっている。

こっちは特に害はないが、
システムへの登録は必要最小限にしたいので、
ColumnProvider クラス以外に対して、
ComVisible(false) を明示するか、
既定の ComVisible を false に設定した上で、
ColumnProvider にのみ ComVisible(true) を明示すべきだ。

ちなみに、既定の ComVisible を false にするには、
アセンブリ(プロジェクト単位)に対して属性を定義する。
アセンブリに対する属性は、プロジェクト直下にある、
AssemblyInfo.cs に集約されているので、
これを開き、以下の行を適当な場所に追加する。
InteropServices 名前空間への参照も忘れずに。

    using System.Runtime.InteropServices;

    // 何でもかんでも COM で公開しないように、
    // 既定で公開をオフにする
    [assembly: ComVisible(false)]

属性の大括弧の後に assembly: と書くと、
その属性はアセンブリに対して適用されるのだ。


では、続けて、IShellLinkW インタフェースだ。
IShellLinkA というのもありメソッドなどは同じなのだが、
これらは、ANSI と Unicode の違いだけである。
まあ、Unicode に対応している IShellLinkW にしておこう。
(Windows Me よ、さようなら~)

Interop フォルダに IShellLinkW.cs を作り、
IShellLinkW の定義を参照して、
前と同じようにインタフェースの定義をする。
全て書くと面倒なので、今回は「使うものだけ」定義しよう。

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/ifaces/ishelllink/ishelllink.asp

今日は一気に書く。

========== IShellLinkW.cs ==========

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

namespace LoaferShellEx.Interop {

    // SLGP_FLAGS
    [Flags]
    public enum SLGP : uint {
        SHORTPATH = 0x0001,
        UNCPRIORITY = 0x0002,
        RAWPATH = 0x0004,
    };

    [ComImport]
    [Guid("000214F9-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IShellLinkW {

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

        void GetPath(
            [Out][MarshalAs(UnmanagedType.LPWStr)]
            StringBuilder pszFile,
            int cch,
            IntPtr pfd,
            SLGP fFlags);

        void _1(); // GetIDList
        void _2(); // SetIDList
        void _3(); // GetDescription
        void _4(); // SetDescription
        void _5(); // GetWorkingDirectory
        void _6(); // SetWorkingDirectory
       
        void GetArguments(
            [Out][MarshalAs(UnmanagedType.LPWStr)]
            StringBuilder pszArgs,
            int cch);

        void _8(); // SetArguments
        void _9(); // GetHotkey
        void _10(); // SetHotkey
        void _11(); // GetShowCmd
        void _12(); // SetShowCmd
        void _13(); // GetIconLocation
        void _14(); // SetIconLocation
        void _15(); // SetRelativePath
        void _16(); // Resolve
        void _17(); // SetPath

    }
}

========== end of IShellLinkW.cs ==========

さて、一気に書いた。特徴的なのが、GetPath メソッドだ。

GetPath メソッドの引数である pszFile は、
今回はメソッドの直接引数である。
呼び出し側がバッファを渡し、
メソッド側がそこに書き込むタイプだ。

今まで出てきたのは構造体だったので、
.NET ランタイムにより構造体ごとマーシャリングされたが、
直接引数の場合、注意する必要がある。

.NET の String 型は不変型である。
そのため、それをメソッドの引数に渡しても、
文字列の中身を変更することができない。

メソッド側で文字列の変更が必要な場合、
System.Text.StringBuilder クラスを使う必要がある。
StringBuilder にはほかにも注意事項があるが、
それは、GetPath を使うときにまた説明しよう。

OutAttribute や MarshalAsAttribute は、
構造体のメンバの時と同じだ。
IShellLinkW は Unicode なので、LPWStr となる。

今回、out キーワードを指定してはいけない。
StringBuilder は参照型であるので、
普通に StringBuilder を渡すだけで、
ポインタとしてマーシャリングされる。

もし、out をつけて、out StringBuilder pszFile とすると、
ポインタへのポインタになってしまうので要注意。
out には、引数が書き込み専用という意味だけでなく、
引数を参照(ポインタ)として引き渡すという機能がある。

それに対して、OutAttribute は、
プラットフォーム呼び出しや COM 相互運用の際に、
引数が書き込み専用という意味を示すためにあり、
それによって引数の渡され方が変わることはない。

pszFile は書き込み専用として渡されるので、
OutAttribute を使って明示しておくのだ。

そして、pfd 引数は WIN32_FIND_DATA 構造体のポインタだが、
情報が必要ない場合、NULL を渡すこともできる。
今回は使わないので、WIN32_FIND_DATA 自体定義せず、
単純に NULL を渡せるように、IntPtr 型で宣言した。

IntPtr は値型であり、IntPtr.Zero が NULL を表す。
C 言語におけるポインタやハンドルの類は、
.NET では、IntPtr を使えば取り扱うことができる。

GetArguments メソッドには特に目新しいことはない。

そして、一番大きな点が、メソッドの宣言を端折ってる事だ。

IUnknown ベースのオブジェクトは、
メソッドをクラス内の位置で特定するため、
定義順さえ合っていれば呼び出すことができる。

そのため、使わないメソッドに関しては、
その場所に何らかのメソッドがあることだけを示しておき、
適当な宣言を行っても問題ないというわけだ。



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