A basic COM runtime for Kotlin/Native based on CInterop.
This can be used to interop with WinRT/Windows SDK functionality otherwise only available through Microsoft compiler
extensions and C++ code.
First, add the official Maven Central repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}Then add a dependency on the library in your buildscript:
kotlin {
mingwMain {
dependencies {
implementation("dev.karmakrafts.cominterop:cominterop-core:<version>")
}
}
}Before using any COM APIs, you need to call ComRuntime.init() to ensure the runtime
is properly initialized. This will invoke CoInitializeEx and RoInitialize allowing multi-threaded use.
When you are done using the COM runtime, you also should ensure proper resource cleanup
using the provided ComRuntime.uninit() function.
fun main() { ComRuntime.init() // Your code here ComRuntime.uninit() }
The primary use of this library is to bind COM interface into the Kotlin world, by providing wrapper classes
which can be constructed from arbitrary CInterop pointers.
Much like in C/C++ on Windows, interfaces require an IID which uniquely identifies them. This IID is primarily
used to query one interface from another.
Note: Interface IIDs can be obtained from the IDL files shipped with Windows and the Windows SDK.
Just like its OOP counterpart, a COM interface also contains/uses a v-table to invoke its implementation functions.
The v-table is built by this library at runtime and cached throughout the lifetime of the program.
Let's assume we want to bind the IShellLinkW interface:
class IShellLinkW : ComInterface<IShellLinkW.Companion>(Companion) { // Define the type of the function we want to invoke private typealias _GetPath = ( self: COpaquePointer, pszFile: LPWSTR, cch: Int, pfd: CPointer<WIN32_FIND_DATAW>, fFlags: DWORD ) -> HRESULT // Define the interface type through its companion object companion object : ComInterfaceType { // Construct a v-table function list based on the IDL definitions override val functions: List<String> = VTableFunctionList.build { add("GetPath") addStubs(17) // Stub all functions we don't need } // Populate the IID pointed to by the given pointer with the right interface IID override fun getIID(iid: CPointer<IID>, iface: ComInterface<*>) { ComRuntime.iidFromString("{000214EE-0000-0000-C000-000000000046}", iid) } // Create a default instance of the interface override fun create(): ComInterface<*> = IShellLinkW() } // Bind against the GetPath function defined in the v-table; calculates address of the function private val GetPath: CPointer<CFunction<_GetPath>> by vTable // Expose the functionality as a high-level Kotlin property val path: String get() = memScoped { } }
This will let us take a given COpaquePointer, and turn it into an instance of the IShellLinkW interface:
fun main() = memScoped { ComRuntime.init() val myPointer = someFunction() val shellLink = myPointer.asCom<IShellLinkW, _>(IShellLinkW) val path = shellLink.path // Internally calls GetPath on the COM interface shellLink.release() ComRuntime.uninit() }
COM interfaces become especially useful, if you can also construct them by one of their
implementations directly from within the Kotlin code:
class INetworkListManager : ComInterface<INetworkListManager.Companion>(Companion) { // ... } // We can define COM classes as simple singleton objects object NetworkListManager : ComClass<INetworkListManager.Companion> { override fun getCLSID(clsid: CPointer<CLSID>) { ComRuntime.iidFromString("{DCB00C01-570F-4A9B-8D69-199FDBA5723B}", clsid) } // The first interface in the inheritance list provided by the IDL definition override val defaultInterface: INetworkListManager.Companion = INetworkListManager }
This will allow us to obtain a new INetworkListManager instance as follows:
fun main() { ComRuntime.init() val manager = NetworkListManager.new<INetworkListManager, _, _>() // Do something with the network manager instance manager.release() ComRuntime.uninit() }
Note: this is purely meant as example code, in reality
NetworkListManagerneeds to be instantiated with theCLSCTX_ALLcontext flag.