(自分用メモ)
- KotlinでWindows向けネイティブプログラム
- Win32APIの呼び出し
※「Kotlin/jvm - JNAでWin32 API」はまたそのうち。
環境
OS: Windows 11
IntelliJ: 2022.2
Kotlin: 1.7
テストコード
ネイティブヒープの確保/解放
例1.kt
val dw = nativeHeap.alloc<DWORDVar>()
//..使用..
nativeHeap.free(dw) //解放
例2.kt
fun enumService() = memScoped {
val dw = alloc<DWORDVar>()
val buffer = allocArray<ByteVar>(1024)
//..使用..
} //スコープを外れると中でallocされたヒープは解放される
Win32APIコール - 入力パラメータの例
API(C言語関数)の定義例: interop.def
void func_in_params(DWORD dw,CHAR c,WCHAR wc,LPCSTR lpCStr, LPCWSTR lpCWStr) {}
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コール - 出力パラメータの例
API(C言語関数)の定義例: interop.def
void func_out_params(LPDWORD lpDW_Out,LPSTR lpStr_Out,LPWSTR lpWStr_Out ) {}
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
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
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.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)}")
}
}
実行結果
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に添えるだけ