libclangでWindowsKitsをDllImportするライブラリを作った(ている)

WindowsKitsのヘッダ `C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0` を libclang でパースして大量の DllImport を生成しました。

* https://github.com/ousttrue/NWindowsKits
  * [user32.dll](https://github.com/ousttrue/NWindowsKits/blob/master/NWindowsKits/NWindowsKits/winuser.cs)
* https://www.nuget.org/packages/NWindowsKits/

# 使い方

```
> mkdir Hello
> cd Hello
> dotnet new console
> dotnet add package NWindowsKits
```

```cs:Program.cs
using System;
using NWindowsKits;

namespace HelloWorld
{
    static class Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            var hInstance = default(HINSTANCE);
            var nCmdShow = C.SW_SHOW;

            // Register the window class.
            var CLASS_NAME = "Sample Window Class";

            var wc = new WNDCLASSW { };

            wc.lpfnWndProc = WindowProc;
            wc.hInstance = hInstance;
            wc.lpszClassName = CLASS_NAME;

            user32.RegisterClassW(ref wc);

            // Create the window.

            var hwnd = user32.CreateWindowExW(
                0,                              // Optional window styles.
                CLASS_NAME,                     // Window class
                "Learn to Program Windows",    // Window text
                C.WS_OVERLAPPEDWINDOW,            // Window style

                // Size and position
                C.CW_USEDEFAULT, C.CW_USEDEFAULT, C.CW_USEDEFAULT, C.CW_USEDEFAULT,

                default,       // Parent window    
                default,       // Menu
                hInstance,  // Instance handle
                default        // Additional application data
                );

            if (hwnd.ptr == IntPtr.Zero)
            {
                return;
            }

            user32.ShowWindow(hwnd, nCmdShow);

            // Run the message loop.
            var msg = new MSG { };
            while (user32.GetMessageW(ref msg, default, 0, 0) != 0)
            {
                user32.TranslateMessage(ref msg);
                user32.DispatchMessageW(ref msg);
            }
        }

        static long WindowProc(HWND hwnd, uint uMsg, ulong wParam, long lParam)
        {
            switch (uMsg)
            {
                case C.WM_DESTROY:
                    user32.PostQuitMessage(0);
                    return 0;

                case C.WM_PAINT:
                    {
                        PAINTSTRUCT ps = default;
                        var hdc = user32.BeginPaint(hwnd, ref ps);

                        // All painting occurs here, between BeginPaint and EndPaint.
                        var hbrush = new HBRUSH
                        {
                            ptr = new IntPtr(C.COLOR_WINDOW + 1),
                        };
                        user32.FillRect(hdc, ref ps.rcPaint, hbrush);
                        user32.EndPaint(hwnd, ref ps);
                    }
                    return 0;
            }

            return user32.DefWindowProcW(hwnd, uMsg, wParam, lParam);
        }
    }
}
```

上記のコードは、 

https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/begin/LearnWin32/HelloWorld/cpp/main.cpp

をベタに移植したものです。
C++ の知識があれば、 IntPtr の扱いとか `CreateWindowExW` になっているところとかなんとなくわかると思う。
関数は、dllと同じ名前の static class に、マクロ定数は `static class C` に押し込んだ。namespace に直接定義できないのでやむを得ない。

# d3d11あるよ

https://github.com/ousttrue/NWindowsKits/blob/master/NWindowsKits/Samples/D3D11ClearSwapchain/Program.cs

という感じになった。Comのベースクラスを、C++ ComPtr を模して作った。

https://github.com/ousttrue/NWindowsKits/blob/master/NWindowsKits/NWindowsKits/__ComPtr__.cs


```c#
using var device = new ID3D11Device();
var hr = CreateCom(ref device.NewPtr); // C++ の void** ppVoid に相当する
if(hr!=0)
{
    throw new COMException("", hr);
}
```

のように使うクラスで、

```c++
ComPtr<ID3D11Device>::operator& // ref IntPtr device.NewPtr が相当。void** ppVoid
ComPtr<ID3D11Device>::Get() // IntPtr device.Ptr が相当。T*
```

となっている。

C# は、 Com をサポートしているのになんでそんなことするのかというと、いろいろとうまく行かなかったからです。

```c#
// こういうやつ。記述が間違っているとエラーになりますが、どう間違っているかわからないので対処できません。
// だいたい仮想関数のindexがずれているのだけど Marshal.GetStartComSlot 等を駆使してデバッグするのがつらいっていうかなおせなかった。
// Marshal の細かいところをエスパーする必要がありつらい。
[ComImport, Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ISomeCOMInterface
{
   // Define interface methods here, using PInvoke conversion between types
}
```

あと、ComPtr のようなスマートポインタで Release したい。
なので、IDisposable にした。

# コード生成はLLVMのlibclangで作った

https://github.com/ousttrue/ClangCaster

去年から何回か作り直していて、 4代目か5代目くらい。
tag名, typedef, macro, 前方宣言, struct(union)の無名フィールドといった、 C言語 特有の問題に対処する必要がある。
だいたい、 libclang の API で何とかなるが、マクロは本当につらい。int 定数の定義だけ雑に対応している。
あと、const。まじめに対応すると複雑になるのだが、C# のコード生成なので捨てちゃった。