💬
ショートカットファイル(.lnk)からリンク先を取得する(CsWin32編)
よくあるコードを 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