C# Win32API ShellExecuteExでWindows 10の「コンピューターの基本的な情報の表示」を表示する

# 本文
Windows 10ではデスクトップのPCアイコンのプロパティから「コンピューターの基本的な情報の表示」を表示できます。C#では`SHGetKnownFolderIDList`に`FOLDERID_ComputerFolder`を与えて`LPITEMIDLIST`を取得した後、`SEE_MASK_INVOKEIDLIST`を指定して`ShellExecuteExW`を呼び出すことでこれを表示できます。

```cs
using System;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var pidlComputer = default(IntPtr);
            try
            {
                pidlComputer = NativeMethods.SHGetKnownFolderIDList(
                    FOLDERID_ComputerFolder,
                    KF_FLAG_DEFAULT,
                    IntPtr.Zero);

                var sei = new SHELLEXECUTEINFOW
                {
                    cbSize = (uint)Marshal.SizeOf<SHELLEXECUTEINFOW>(),
                    fMask = SEE_MASK_INVOKEIDLIST,
                    lpVerb = "properties",
                    lpIDList = pidlComputer
                };
                NativeMethods.ShellExecuteExW(ref sei);
            }
            finally
            {
                Marshal.FreeCoTaskMem(pidlComputer);
            }
        }

        private static class NativeMethods
        {
            [DllImport("shell32.dll", ExactSpelling = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool ShellExecuteExW(
                ref SHELLEXECUTEINFOW pExecInfo);

            [DllImport("shell32.dll", ExactSpelling = true, PreserveSig = false)]
            public static extern IntPtr SHGetKnownFolderIDList(
                [MarshalAs(UnmanagedType.LPStruct)] Guid rfid,
                uint dwFlags,
                IntPtr hToken);
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct SHELLEXECUTEINFOW
        {
            public uint cbSize;
            public uint fMask;
            public IntPtr hwnd;
            public string lpVerb;
            public string lpFile;
            public string lpParameters;
            public string lpDirectory;
            public int nShow;
            public IntPtr hInstApp;
            public IntPtr lpIDList;
            public IntPtr lpClass;
            public IntPtr hkeyClass;
            public uint dwHotKey;
            public IntPtr hIconOrMonitor;
            public IntPtr hProcess;
        }

        private static readonly Guid FOLDERID_ComputerFolder = Guid.Parse(
            "{0AC0837C-BBF8-452A-850D-79D08E667CA7}");

        private const uint KF_FLAG_DEFAULT = 0;

        private const uint SEE_MASK_INVOKEIDLIST = 0x0000000C;
    }
}
```

Windows 10以外ではおそらく普通にPCのプロパティが表示されます。

# アンマネージリソースと`try...finally`

```cs
var pidlComputer = default(IntPtr);
try
{
    pidlComputer = NativeMethods.SHGetKnownFolderIDList(...);
    ...
}
finally
{
    Marshal.FreeCoTaskMem(pidlComputer);
}
```

`SHGetKnownFolderIDList`の戻り値である`pidlComputer`はアンマネージリソースのポインタであり、`Marshal.FreeCoTaskMem`で解放する必要があります。`try`の中で`var pidlComputer = ...`とすると`finally`のスコープからは`pidlComputer`を参照できないので、`try`の外側で`pidlComputer`を宣言しています。

この冗長さを回避するためには`SafeHandle`の派生クラスを作成して`using`と組み合わせます。

# オブジェクト初期化子による構造体の初期化

```cs
var sei = new SHELLEXECUTEINFOW
{
    cbSize = ...
};
```

構造体は`new 型名 {field1 = ..., field2 = ..., ...}`の形式で初期化できます。

# 構造体中の`string`のP/Invoke

```cs
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct SHELLEXECUTEINFOW
{
    ...
    public string lpVerb;
    ...
]
```

P/Invoke時（`DllImport`属性を持つ関数の呼び出し時）、構造体中の文字列は自動的に`IntPtr`型へ変換・復元されます。`IntPtr`を指定して毎回`Marshal.PtrToStringAnsi/Uni`を呼び出すこともできますが、文字列を再利用しなければこちらの方が簡潔です。変換時のANSI/UNICODEは`StructLayout`属性の`CharSet`で指定します。

# `UnmanagedType.LPStruct`

C#側で値渡しした構造体（`Guid`等）をP/Invoke時にそのポインタとして渡します。C++の`REFCLSID`や`REFIID`にC#の`type(...).GUID`等を渡すとき、`ref Guid ...`とすると必要になる一時的な変数に省略に使います。