跨平台项目开发:Windows与Linux的窗口创建

在现代软件开发中,跨平台兼容性是一个重要的考量。本文将介绍如何使用Kotlin Native创建一个能够在Windows和Linux平台上工作的项目。项目的核心功能是检测当前运行的平台,并显示相应的消息。此外,还将探讨如何在这两个平台上创建原生窗口,并添加切换到全屏模式的功能。需要注意的是,全屏模式的实现在Windows平台上更为简单,而在Linux平台上,将展示如何最大化窗口并去除装饰,后续会添加真正的全屏切换功能。

Windows平台的实现

首先,从Windows平台开始,因为它的实现相对简单。将添加两个不同的线程:第一个用于系统消息循环,第二个用于Vulkan渲染。为了在线程之间传递数据,首先需要定义共享数据。在之前的部分中,已经在原生互操作中添加了global.def文件。现在,将定义所需的数据:

internal class CommonData( val semaphore: sem_tVar, var hInstance: HINSTANCE? = null, var hwnd: HWND? = null, var showWindowCentered: Boolean = true, var showWindowFullscreen: Boolean = false, var onTheRun: Boolean = true, var windowsSurface: WindowsSurface? = null )

在这里,添加了所有需要传递给系统消息循环的数据,最重要的是:用于线程同步的信号量、用于停止所有处理的"onTheRun"变量,以及对新窗口类的引用。还添加了获取指向共享数据的指针的类。

接下来,需要对公共代码进行一些更改:

internal expect class Platform { fun Initialize() companion object { val type: PlatformEnum val VK_KHR_PLATFORM_SURFACE_EXTENSION_NAME: String } }

期望每个原生平台都有一个"Initialize"方法,它将创建窗口并开始渲染,并添加了一个常量来定义表面扩展的名称。现在,主函数将如下所示:

@ExperimentalUnsignedTypes fun main(args: Array) { val platform = Platform() platform.Initialize() }

现在,是时候创建窗口了。这与在C++中的做法大致相同——使用标准的WinAPI方法,并将类引用传递给WndProc以调用类方法。首先,让在类初始化中获取共享数据:

init { val kotlinObject = DetachedObjectGraph(sharedData.kotlinObject).attach() val sharedData = kotlinObject.userdata!!.asStableRef().get() sharedData.windowsSurface = this }

然后,让创建原生窗口本身并运行它:

fun initialize() { memScoped { val hInstance = GetModuleHandleW(null) val hBrush = CreateSolidBrush(0x00000000) val wc: WNDCLASSEXW = alloc() wc.cbSize = sizeOf().convert() wc.style = (CS_HREDRAW or CS_VREDRAW or CS_OWNDC).convert() wc.lpfnWndProc = staticCFunction { hwnd, msg, wParam, lParam -> when (msg.toInt()) { WM_CLOSE -> { val kotlinObject = DetachedObjectGraph(sharedData.kotlinObject).attach() val sharedData = kotlinObject.userdata!!.asStableRef().get() sharedData.onTheRun = false DestroyWindow(hwnd) } WM_DESTROY -> { PostQuitMessage(0) } // ... WM_KEYDOWN -> { if (GetAsyncKeyState(VK_ESCAPE) != 0.toShort()) { val kotlinObject = DetachedObjectGraph(sharedData.kotlinObject).attach() val sharedData = kotlinObject.userdata!!.asStableRef().get() if (sharedData.showWindowFullscreen) { sharedData.windowsSurface?.changeFullscreen(false) } PostMessageW(hwnd, WM_CLOSE, 0, 0) } } WM_SYSKEYDOWN -> { if (wParam == VK_F4.toULong()) { return@staticCFunction 1 } } else -> { return@staticCFunction DefWindowProcW(hwnd, msg, wParam, lParam) } } } wc.hInstance = hInstance // ... val failure: ATOM = 0u if (RegisterClassExW(wc.ptr) == failure) { throw RuntimeException("Failed to create native window!") } hwnd = CreateWindowExW( WS_EX_CLIENTEDGE, "kvarc", "kvarc", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, _width, _height, null, null, hInstance, null ) if (hwnd == null) { MessageBoxW( null, "Failed to create native window!", "kvarc", (MB_OK).convert() ) throw RuntimeException("Failed to create native window!") } // ... ShowWindow(hwnd, SW_SHOWNORMAL) UpdateWindow(hwnd) } }

窗口消息循环:

fun messageLoop() { memScoped { val msg: MSG = alloc() while (GetMessageW(msg.ptr, null, 0u, 0u) > 0) { TranslateMessage(msg.ptr) DispatchMessageW(msg.ptr) } } }

由于已经创建了窗口,是时候为窗口和渲染添加线程了:

@ExperimentalUnsignedTypes internal actual class Platform { actual fun Initialize() { try { val arena = Arena() val semaphore = arena.alloc() sem_init(semaphore.ptr, 0, 0) // ... memScoped { val winThread = alloc() pthread_create(winThread.ptr, null, staticCFunction { _: COpaquePointer? -> initRuntimeIfNeeded() val kotlinObject = DetachedObjectGraph(sharedData.kotlinObject).attach() val data = kotlinObject.userdata!!.asStableRef().get() val win = WindowsSurface(data.showWindowFullscreen) win.initialize() // ... @Suppress("UNCHECKED_CAST") data.hwnd = win.hwnd!! sem_post(data.semaphore.ptr) win.messageLoop() win.dispose() null as COpaquePointer? // keep it lies not needed }, null).ensureUnixCallResult("pthread_create") val vkThread = alloc() pthread_create(vkThread.ptr, null, staticCFunction { _: COpaquePointer? -> initRuntimeIfNeeded() val kotlinObject = DetachedObjectGraph(sharedData.kotlinObject).attach() val data = kotlinObject.userdata!!.asStableRef().get() sem_wait(data.semaphore.ptr) // TODO Vulkan loop null as COpaquePointer? // keep it lies not needed }, null).ensureUnixCallResult("pthread_create") pthread_join(vkThread.value, null).ensureUnixCallResult("pthread_join") pthread_join(winThread.value, null).ensureUnixCallResult("pthread_join") sem_destroy(semaphore.ptr) commonDataStableRef.dispose() arena.clear() } } catch (ex: Exception) { logError("Failed to start with exception: ${ex.message}") } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485