もりゃきに後は無い

SHGetFileInfoをDllImportからLibraryImportに書き換え

あの、Windowsのファイルアイコンやら色々な情報を獲得する SHGetFileInfo の宣言を、DllImportからLibraryImportに書き換えて、憎きSYSLIB1054を一つ撲滅する方法をお伝えします。

まず、DllImportのパターンは、よく見るのはこういう宣言ですね。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHFILEINFO
{
    public IntPtr hIcon;
    public int iIcon;
    public uint dwAttributes;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string szDisplayName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
    public string szTypeName;
};

[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttribs, ref SHFILEINFO psfi, uint cbFileInfo, SHGFI uFlags);

そして使い方はこんな感じですよね?

SHFILEINFO shinfo = new();
IntPtr shFileInfoResult;

// ここ
shFileInfoResult = SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI.SHGFI_ICON | SHGFI.SHGFI_SMALLICON | SHGFI.SHGFI_TYPENAME);

if (shFileInfoResult == IntPtr.Zero || shinfo.hIcon == IntPtr.Zero)
{
    int lastError = Marshal.GetLastWin32Error();
    throw new Exception($"SHGetFileInfo Failed with error code {lastError}");
}


BitmapSource icon = Imaging.CreateBitmapSourceFromHIcon(shinfo.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
DestroyIcon(shinfo.hIcon);

// あとはiconとshinfo.szTypeNameでアイコンとファイル種類が手に入る

で、今となってはSHGetFileInfoに警告SYSLIB1054が出てくる、と。

そしてVisual Studioの提案のままに、DllImportからLibraryImportに書き換えるとこうなります。

/*
// これが
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttribs, ref SHFILEINFO psfi, uint cbFileInfo, SHGFI uFlags);
*/
// ↓こう
[LibraryImport("shell32.dll", EntryPoint = "SHGetFileInfoW", StringMarshalling = StringMarshalling.Utf16)]
private static partial IntPtr SHGetFileInfo(string pszPath, uint dwFileAttribs, ref SHFILEINFO psfi, uint cbFileInfo, SHGFI uFlags);

そして、悲惨なことに第三引数psfiでエラーになってしまうんです。

その回避方法となりますが、エラーになったSHFILEINFOの書き換えが必要になります。できあがりがこちら。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public unsafe struct SHFILEINFO
{
    public IntPtr hIcon;
    public int iIcon;
    public uint dwAttributes;
    public fixed ushort szDisplayName[260];
    public fixed ushort szTypeName[80];
};

これで、エラー自体は抑制できますが、今度は別の場所が問題になります。szDisplayNameszTypeNamestring 型から ushort 配列になったのですから当然ですね。

szDisplayNameszTypeNamestring型に変換する必要があります。ここではszTypeNameを対象にします。

string typeName = String.Empty;
unsafe
{
    typeName = Utf16StringMarshaller.ConvertToManaged(shinfo.szTypeName) ?? String.Empty;
}

unsafeで括ってUtf16StringMarshaller.ConvertToManagedを使えばいいです。 以上。

追記:Prism調べるの止めました。関連記事も近日中に削除します。