Kotlin/Native - mingwでWin32 API メモ

(自分用メモ)
- KotlinでWindows向けネイティブプログラム
- Win32APIの呼び出し

※「Kotlin/jvm - JNAでWin32 API」はまたそのうち。

環境
---
OS: Windows 11
IntelliJ: 2022.2
Kotlin: 1.7

テストコード
---
- [サンプルプロジェクト](https://github.com/kyoya-p/samples/tree/a82c6ce33af0d904e0a1d3f5991230812da9c94d/2022/KtMp/KtNativeWin)
- [Win32 APIコールの例 - Windowsサービス管理](https://github.com/kyoya-p/samples/blob/a82c6ce33af0d904e0a1d3f5991230812da9c94d/2022/KtMp/KtNativeWin/src/nativeMain/kotlin/sample/InstallService.kt)

ネイティブヒープの確保/解放
---
```kotlin:例1.kt
    val dw = nativeHeap.alloc<DWORDVar>()
    //..使用..
    nativeHeap.free(dw) //解放
```

```kotlin:例2.kt
fun enumService() = memScoped {
    val dw = alloc<DWORDVar>()
    val buffer = allocArray<ByteVar>(1024)
    //..使用..
} //スコープを外れると中でallocされたヒープは解放される

```

Win32APIコール - 入力パラメータの例
---
```c: API(C言語関数)の定義例: interop.def
void func_in_params(DWORD dw,CHAR c,WCHAR wc,LPCSTR lpCStr, LPCWSTR lpCWStr) {}
```
```kotlin:Kotlinでの呼び出し例: Test.kt
    fun test_in_params() = memScoped {
        val dw: DWORD = 0x12345678.toUInt() // DWORD = UInt
        val c: CHAR = 'c'.code.toByte() // CHAR = Byte
        val wc: WCHAR = 'w'.code.toUShort() //WCHAR = UShort
        val lpCStr: String = "const string" //  @CCall.CString LPCSTR型の仮引数にはkotlin.String?型を渡す
        val lpCWStr: String = "const wide string" //  @CCall.WCString LPCWSTR型の仮引数にはkotlin.String?型を渡す

        func_in_params(
            dw = dw,
            c = c,
            wc = wc,
            lpCStr = lpCStr,
            lpCWStr = lpCWStr
        )
    }
```

Win32APIコール - 出力パラメータの例
---
```c: API(C言語関数)の定義例: interop.def
void func_out_params(LPDWORD lpDW_Out,LPSTR lpStr_Out,LPWSTR lpWStr_Out ) {}
```
```kotlin:Kotlinでの呼び出し例: Test.kt
    fun test_out_params() = memScoped {
        val lpDw_Out: LPDWORD = alloc<DWORDVar>().ptr //  LPDWORD = CPointer<DWORDVar>
        val lpStr_Out: LPSTR = allocArray<CHARVar>(256) //  LPSTR = CPointer<CHARVar>
        val lpWStr_Out: LPWSTR = allocArray<WCHARVar>(256) // LPWSTR = CPointer<WCHARVar>

        func_out_params(
            lpDW_Out = lpDw_Out,
            lpStr_Out = lpStr_Out,
            lpWStr_Out = lpWStr_Out
        )

        //出力を参照
        val resDw: UInt = lpDw_Out.pointed.value
        val resStr: String = lpStr_Out.toKString()
        val resWStr: String = lpWStr_Out.toKString()
    }
```

cinteropコマンドを使用し、宣言をc言語からKotlinへ変換
---
※今回は勉強のために使用

```src/nativeInterop/cinterop/interop.def


void ints(char c, short d, int e, long f) { }
void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f) { }
void doubles(float a, double b) { }
```
`gradlew cinteropInteropNative`

生成ファイル: `build/classes/kotlin/native/main/cinterop/KtNativeWin-cinterop-interop.klib`
に含まれる.knmファイルをIntelliJで開くと、Kotlinでの表現が確認できる
```default/linkdata/package_interop/0_interop.knm
package interop
@kotlinx.cinterop.internal.CCall public external fun doubles(a: kotlin.Float, b: kotlin.Double): kotlin.Unit { /* compiled code */ }
@kotlinx.cinterop.internal.CCall public external fun ints(c: kotlin.Byte, d: kotlin.Short, e: kotlin.Int, f: kotlin.Int): kotlin.Unit { /* compiled code */ }
@kotlinx.cinterop.internal.CCall public external fun uints(c: kotlin.UByte, d: kotlin.UShort, e: kotlin.UInt, f: kotlin.UInt): kotlin.Unit { /* compiled code */ }
```

メモリ確保/ポインタの参照/ポインタのキャスト
---
メモリを指定サイズ確保し、それを指定の型のポインタに変更。
```kotlin:Kotlin
    fun reinterpretCastTest() = memScoped {
        val bufferSize = sizeOf<DWORDVar>() * 10 // DWORD 10個分のメモリサイズ取得(単位:バイト)
        val buffer = allocArray<ByteVar>(bufferSize) // Byteの配列として確保
        fun printBuffer() = println((0 until bufferSize).map { buffer[it] }.joinToString { it.toString(0x10) }) // デバック用

        val pDw = buffer.reinterpret<DWORDVar>() // Byteの配列の先頭ポインタをDWORDのポインタにキャスト
        pDw.pointed.value = 0x01020304u // ポインタが指すDWORD型変数に書き込み
        pDw[2] = 0x02020202u //ポインタを配列として扱い3番目のDWORD要素に書き込み

        val v: DWORD = pDw.pointed.value // ポインタが指すDWORD変数の値を参照
        val v2: DWORD = pDw[2] // // ポインタを配列として扱い、3番目の要素を参照

        assert(v == 0x01020304u)
        assert(v2 == 0x02020202u)
        println("v:DWORD=0x${v.toString(0x10)}")
        println("v2:DWORD=0x${v2.toString(0x10)}")
        printBuffer()
    }
```

構造体と共用体の使用 / struct & union
---
```c:interop.def
---

typedef struct {
  unsigned char uc0;
  unsigned char uc1;
  unsigned char uc2;
  unsigned char uc3;
} Struct1;

typedef union {
  int i;
  Struct1 m;
  unsigned char uc[4];
} Union1;
```
```Kotlin:Kotlin.kt
fun unionSample() {
    val cUnion = cValue<Union1>()
    cUnion.useContents {
        i = 0x01020304
        println("cUnion.i=${i.toString(0x10)}")
        with(m) { listOf(uc3, uc2, uc1, uc0) }.joinToString { it.toString(0x10) }.let { println("cUnion.m=$it") }
        m.uc2 = 0xffu
        with(m) { listOf(uc3, uc2, uc1, uc0) }.joinToString { it.toString(0x10) }.let { println("cUnion.m=$it") }
        println("cUnion.i=${i.toString(0x10)}")
    }
}
```

```text:実行結果
cUnion.i=1020304
cUnion.m=1, 2, 3, 4
cUnion.m=1, ff, 3, 4
cUnion.i=1ff0304
```

その他WindowsOS一般
---
- マニフェストファイル Manifest
実行に権限が必要なAPIを使用するexeファイルについて宣言する必要あり。
実行ファイルが、`Sample.exe`の場合、同じフォルダに`Sample.exe.manifest` ファイルを置く。
https://docs.microsoft.com/ja-jp/windows/win32/sbscs/application-manifests

プロジェクト履歴
---
- IntelliJ > 新規 > プロジェクト > Kotlin マルチプラットフォーム > Native Application

- cinteropコマンドを使用する設定(習熟目的)
```build.gradle.kts
//..中略..
kotlin {
  mingwX64("native") {
    val main by compilations.getting // 追加
    val interop by main.cinterops.creating // 追加
   // ..中略..
  }
}
```


Javaはgradleに添えるだけ

参考
---
- [Kotlin公式](https://kotlinlang.org/docs/native-c-interop.html)
- [Win32公式 - サービスの状態遷移](https://docs.microsoft.com/ja-jp/windows/win32/services/service-status-transitions)