Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

karmakrafts/COMInterop

Repository files navigation

COMInterop

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.

How to use it

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>")
 }
 }
}

Initialization

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()
}

Binding a COM interface

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()
}

Binding a COM class

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 NetworkListManager needs to be instantiated with the CLSCTX_ALL context flag.

AltStyle によって変換されたページ (->オリジナル) /