CsWin32 を使って F# Interactive から Win32 API を呼び出す

関連リンク

何がうれしいのか
何がうれしいのか書く

前提
.NET SDK

CsWin32 の準備
以下ではコマンドライン (PowerShell) からの手順を説明する。
# 前準備
mkdir FsWin32
cd FsWin32
dotnet new classlib
rm 'Class1.cs'
dotnet add package 'Microsoft.Windows.CsWin32'
echo 'GetForegroundWindow' > 'NativeMethods.txt'
echo @'
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": true
}
'@ > NativeMethods.json
dotnet build
と進めていくと、アセンブリ FsWin32.dll
が bin\Debug\<TFM>
[1] に得られる。

NativeMethods.txt
ポイント 1: Create a
NativeMethods.txt
file in your project directory that lists the APIs to generate code for.
Each line may consist of one of the following:
- Exported method name (e.g.
CreateFile
). This may include theA
orW
suffix, where applicable. This may be qualified with a namespace but is only recommended in cases of ambiguity, which CsWin32 will prompt where appropriate.- A macro name (e.g.
HRESULT_FROM_WIN32
). These are generated into the same class with extern methods. Macros must be hand-authored into CsWin32, so let us know if you want to see a macro added.- A namespace to generate all APIs from (e.g.
Windows.Win32.Storage.FileSystem
would search the metadata for all APIs within that namespace and generate them).- Module name followed by
.*
to generate all methods exported from that module (e.g.Kernel32.*
).- The name of a struct, enum, constant or interface to generate. This may be qualified with a namespace but is only recommended in cases of ambiguity, which CsWin32 will prompt where appropriate.
- A prefix shared by many constants, followed by
*
, to generate all constants that share that prefix (e.g.ALG_SID_MD*
).- A comment (i.e. any line starting with
//
) or white space line, which will be ignored.

NativeMethods.json
ポイント 2: public
プロパティは true
にしておく。
A value indicating whether to expose the generated APIs publicly (as opposed to internally).
(via https://aka.ms/CsWin32.schema.json)

F# Interactive からの参照
先ほど得られたアセンブリ bin\Debug\<TFM>\FsWin32.dll
を F# Interactive で参照することで Win32 API を呼び出せるようになる。
参照のやり方は以下の二通りある:
コマンドラインから起動時に参照する
dotnet fsi -r 'bin\Debug\<TFM>\FsWin32.dll'
# 同じ
#dotnet fsi --reference:'bin\Debug\<TFM>\FsWin32.dll'
F# Interactive のディレクティブで参照する
> #r @"bin\Debug\<TFM>\FsWin32.dll"
--> Referenced 'path\to\bin\Debug\<TFM>\FsWin32.dll' (file may be locked by F# Interactive process)

参考:F# Interactive のディレクティブ
> #help;;
F# Interactive directives:
#r "file.dll";; // Reference (dynamically load) the given DLL
#i "package source uri";; // Include package source uri when searching for packages
#I "path";; // Add the given search path for referenced DLLs
#load "file.fs" ...;; // Load the given file(s) as if compiled and referenced
#time ["on"|"off"];; // Toggle timing on/off
#help;; // Display help
#help "idn";; // Display documentation for an identifier, e.g. #help "List.map";;
#r "nuget:FSharp.Data, 3.1.2";; // Load Nuget Package 'FSharp.Data' version '3.1.2'
#r "nuget:FSharp.Data";; // Load Nuget Package 'FSharp.Data' with the highest version
#clear;; // Clear screen
#quit;; // Exit
F# Interactive command line options:
See 'dotnet fsi --help' for options

参照時の注意
ディレクティブ実行された後に、F# Interactive が “file may be locked by F# Interactive process” と報告している通り。
ファイルのロックが原因でどこかで不具合が生じる可能性がある。そういうときには慌てずに F# Interactive を終了してみよう。いちど参照した DLL アセンブリをセッション中にアンロードする方法(ディレクティブ)はない。.NET API を使えばできるかも?

F# Interactive からの呼び出し
はじめに NativeMethods.txt
ファイルで宣言した関数は、Windows.Win32
名前空間の PInvoke
クラスの静的メソッドとして呼び出しできる。
> open Windows.Win32;;
> PInvoke.GetForegroundWindow;;
val it: unit -> Foundation.HWND
> PInvoke.GetForegroundWindow ();;
val it: Foundation.HWND = 0x9080a {IsNull = false;
Value = System.Reflection.Pointer;}
タブ補完も問題なく効く。
> PI[TAB].G[TAB] ⟶ PInvoke.GetForegroundWindow
Windows.Win32.Foundation
名前空間には、関連するデータ型が自動で作られる。
> typedefof<PInvoke>.Assembly.GetTypes()
- |> Seq.filter (fun x -> x.Namespace = "Windows.Win32.Foundation")
- |> Seq.map (fun x -> string x);;
val it: string seq =
seq
["Windows.Win32.Foundation.BOOL"; "Windows.Win32.Foundation.HANDLE";
"Windows.Win32.Foundation.HWND"]

CsWin32 の疑問点
- ソースジェネレーターによって生成されたファイルの実体はどういう規則に従ってどこへ保存されているのだろうか?
- 関数に対応する名前空間はどこで分かるのか?(“A namespace to generate all APIs from (e.g.
Windows.Win32.Storage.FileSystem
would search the metadata for all APIs within that namespace and generate them)” の部分)