💬

ショートカットファイル(.lnk)からリンク先を取得する(CsWin32編)

2022/12/31に公開

よくあるコードを Microsoft.Windows.CsWin32 を使って書いてみたサンプルコードです。

準備

プロジェクトファイルおよび使用しているバージョンは下記です。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0-windows</TargetFramework>
    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
    <LangVersion>latest</LangVersion>

	<ItemGroup>
        <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.164-beta">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.Windows.SDK.Win32Metadata" Version="40.0.14-preview" />
        <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
        <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
    </ItemGroup>
</Project>

使用する関数や定義は NativeMethods.txt に書くので、下記の内容で用意します。

IShellLinkW
IPersistFile
CoCreateInstance
ShellLink
CLSCTX

COMオブジェクトの作成

コードは少し冗長になりますが、わかりやすい様に名前空間を書いてます。

Windows.Win32.Foundation.HRESULT hr = Windows.Win32.PInvoke.CoCreateInstance(typeof(Windows.Win32.UI.Shell.ShellLink).GUID, null, Windows.Win32.System.Com.CLSCTX.CLSCTX_INPROC_SERVER, out Windows.Win32.UI.Shell.IShellLinkW psl);
  • C言語と同じように CoCreateInstance 関数でCOMオブジェクトを作成しますが、
    CLSID_ShellLink のような CLSID_* の定義はCsWin32では生成されないので、 typeof(Windows.Win32.UI.Shell.ShellLink).GUID を使います。
  • オブジェクトのインターフェースは C言語ではIID_PPV_ARGS を使ってましたが、代わりに out でローカル変数の宣言と一緒に受け取れます。
  • -W-A があるインターフェースは明示的に指定する必要があります。
  • CsWin32で作成されるメソッドでは、デフォルトでは関数の戻り値の HRESULT は自動的に例外を送出するようになっていますが、 CoCreateInstance メソッドは例外が送出されません。
    手動で戻り値の Windows.Win32.Foundation.HRESULT 構造体の ThrowOnFailure() を呼び出しましょう。

文字列処理

Span<char> path = stackalloc char[260];
fixed (char* p = path)
{
    psl.GetPath(p, path.Length, null, 0);
}
var target = path[..path.IndexOf('\0')].ToString();
  • LPWSTRなどの書き換えが起きる文字列はCsWin32では PWSTR 構造体となっていますが、中身は char* です。
    new char[] で用意してもいいですが、一時的にしか使用しないなら stackalloc char[] を使いましょう。
  • C# の string にする場合は、NUL文字より前までを切り出します。

COMオブジェクトの解放

CsWin32のデフォルトではマーシャリングが有効です。
手動で開放する場合はMarshal.ReleaseComObject(Object) メソッド を呼びます。

今回はラインタイムのGCに任せるため、明示的には呼び出していません。

コード全文

折りたたんでます
var target = GetTargetOfShortcutFile(@"C:\Users\User\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\System Tools\Command Prompt.lnk");
Console.WriteLine(target); // C:\WINDOWS\system32\cmd.exe

static unsafe string GetTargetOfShortcutFile(string shortcut)
{
    Windows.Win32.Foundation.HRESULT hr = Windows.Win32.PInvoke.CoCreateInstance(typeof(Windows.Win32.UI.Shell.ShellLink).GUID, null, Windows.Win32.System.Com.CLSCTX.CLSCTX_INPROC_SERVER, out Windows.Win32.UI.Shell.IShellLinkW psl);
    hr.ThrowOnFailure();

    ((Windows.Win32.System.Com.IPersistFile)psl).Load(shortcut, Windows.Win32.System.Com.STGM.STGM_READ);
    Span<char> path = stackalloc char[260];
    fixed (char* p = path)
    {
        psl.GetPath(p, path.Length, null, 0);
    }
    return path[..path.IndexOf('\0')].ToString();
}

Discussion