potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# 9.0 Windows 10の拡張子の分類情報等を取得する

Windows 10で拡張子の分類情報など(Perceived Type等)を取得するにはWin32APIのAssocGetPerceivedType関数を使用します。以下はそのサンプルコードです。recordnew (...)を使用しているのでC# 9.0以降対応です。

using System;
using System.Runtime.InteropServices;

static class Program
{
    private record PerceivedInfo(
        PERCEIVED Perceived,
        PERCEIVEDFLAG PerceivedFlag,
        string TypeString);

    static void Main()
    {
        var exePerceivedTypeInfo = GetPerceivedType(".exe");
        // {PerceivedInfo { Perceived = PERCEIVED_TYPE_APPLICATION, PerceivedFlag = PERCEIVEDFLAG_HARDCODED, TypeString = application }}
        var bmpPerceivedTypeInfo = GetPerceivedType(".bmp");
        // {PerceivedInfo { Perceived = PERCEIVED_TYPE_IMAGE, PerceivedFlag = PERCEIVEDFLAG_HARDCODED, PERCEIVEDFLAG_NATIVESUPPORT, PERCEIVEDFLAG_GDIPLUS, TypeString = image }}
        var txtPerceivedTypeInfo = GetPerceivedType(".txt");
        // {PerceivedInfo { Perceived = PERCEIVED_TYPE_TEXT, PerceivedFlag = PERCEIVEDFLAG_SOFTCODED, PERCEIVEDFLAG_NATIVESUPPORT, TypeString = text }}
    }

    static PerceivedInfo GetPerceivedType(string extenionNameWithDot)
    {
        var str = NativeMethods.AssocGetPerceivedType(
            extenionNameWithDot, out var type, out var flag);
        return new (type, flag, str);
    }

    static class NativeMethods
    {
        // https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-assocgetperceivedtype
        [DllImport("shlwapi.dll", ExactSpelling = true, PreserveSig = false, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CoTaskMemToStringUniMarshaler))]
        public static extern string AssocGetPerceivedType(
            [In] string pszExt,
            out PERCEIVED ptype,
            out PERCEIVEDFLAG pflag);
    }

    enum PERCEIVED : int
    {
        PERCEIVED_TYPE_FIRST = -3,
        PERCEIVED_TYPE_CUSTOM = -3,
        PERCEIVED_TYPE_UNSPECIFIED = -2,
        PERCEIVED_TYPE_FOLDER = -1,
        PERCEIVED_TYPE_UNKNOWN = 0,
        PERCEIVED_TYPE_TEXT = 1,
        PERCEIVED_TYPE_IMAGE = 2,
        PERCEIVED_TYPE_AUDIO = 3,
        PERCEIVED_TYPE_VIDEO = 4,
        PERCEIVED_TYPE_COMPRESSED = 5,
        PERCEIVED_TYPE_DOCUMENT = 6,
        PERCEIVED_TYPE_SYSTEM = 7,
        PERCEIVED_TYPE_APPLICATION = 8,
        PERCEIVED_TYPE_GAMEMEDIA = 9,
        PERCEIVED_TYPE_CONTACTS = 10,
        PERCEIVED_TYPE_LAST = 10,
    }


    [Flags]
    enum PERCEIVEDFLAG : ushort
    {
        PERCEIVEDFLAG_UNDEFINED = 0x0000,
        PERCEIVEDFLAG_SOFTCODED = 0x0001,
        PERCEIVEDFLAG_HARDCODED = 0x0002,
        PERCEIVEDFLAG_NATIVESUPPORT = 0x0004,
        PERCEIVEDFLAG_GDIPLUS = 0x0010,
        PERCEIVEDFLAG_WMSDK = 0x0020,
        PERCEIVEDFLAG_ZIPFOLDER = 0x0040,
    }
}

// https://potisan-programming-memo.hatenablog.jp/entry/2020/11/26/214836
internal sealed class CoTaskMemToStringUniMarshaler : ICustomMarshaler
{
    private static CoTaskMemToStringUniMarshaler instance;

    internal static ICustomMarshaler GetInstance(string cookie)
    {
        return instance ??= new CoTaskMemToStringUniMarshaler();
    }

    public void CleanUpManagedData(object ManagedObj) => throw new NotImplementedException();
    public IntPtr MarshalManagedToNative(object ManagedObj) => throw new NotImplementedException();
    public object MarshalNativeToManaged(IntPtr pNativeData) => Marshal.PtrToStringUni(pNativeData);
    public void CleanUpNativeData(IntPtr pNativeData) => Marshal.FreeCoTaskMem(pNativeData);
    public int GetNativeDataSize() => -1; // 値型ではない。
}

蛇足

  • AssocGetPerceivedType関数は複数の出力引数を返すため、ラッパー関数でrecordに変換しています。C# 8.0以前では他の複合型やタプル(TupleValueTuple)で代替します。
  • AssocGetPerceivedType関数は戻り値HRESULT型でサフィックスA/W)なくユニコード文字列を受け取る関数です。これらを示すためにDllImport属性にExactSpelling = true(文字列型を扱うがサフィックスは自動付加しない)、PreserveSig = falseHRESULT型戻り値を隠して失敗時は例外を発生させ、最後の引数を戻り値にする)、CharSet = CharSet.Unicode(文字列はユニコード)を指定しています。
  • record PerceivedInfo(...);...で与えられた引数をフィールドに持ち、作成時にそれらを指定する必要のあるレコードの実装です。;で終わらず{...}を続けることで引数以外のメンバー変数や関数なども追加できます。