Adam S
04/27/2023, 6:07 PMstaticCFunction()
to pass in a custom callback, but the callback requires some boilerplate to handle the C types. Is it possible to ‘wrap’ this callback with one of my own, without breaking the ‘no references’ rule for staticCFunction()
?
What I want is something like this, but this fails because “kotlinx.cinterop.staticCFunction must take an unbound, non-capturing function or lambda, but captures at (…) callback”
fun main() {
attachAudioMixedProcessor { samples, frames ->
for (frame in 0 until frames.convert()) {
val left = samples[frame * 2 + 0]
val right = samples[frame * 2 + 1]
// ...
}
}
}
fun attachAudioMixedProcessor(
callback: (samples: CPointer<FloatVar>, frames: Int) -> Unit
) {
// call the c-interop function
AttachAudioMixedProcessor(staticCFunction { buffer: COpaquePointer?, frames: UInt ->
val samples: CPointer<FloatVar> = buffer?.reinterpret() ?: return@staticCFunction
callback(samples, frames.toInt())
})
}
// generated C interop function
external fun AttachAudioMixedProcessor(processor: CPointer<CFunction<(COpaquePointer?, UInt) -> Unit>>?)
fun main() {
// cinterop function
AttachAudioMixedProcessor(staticCFunction(::processAudio))
}
/** Audio processing function */
private fun processAudio(buffer: COpaquePointer?, frames: UInt) {
val samples: CPointer<FloatVar> = buffer?.reinterpret() ?: return
for (frame in 0 until frames.convert()) {
val left = samples[frame * 2 + 0]
val right = samples[frame * 2 + 1]
// ...
}
}
vbsteven
04/27/2023, 6:37 PMuser_data
pointer when calling or configuring the callback, and then when calling the callback they will pass the original user_data
as one of the arguments.
This way when the callback function is called, you have a way of getting the context back from inside the "standalone" callback.Adam S
04/27/2023, 6:38 PMvoid AttachAudioMixedProcessor(AudioCallback processor);
typedef void (*AudioCallback)(void *bufferData, unsigned int frames);
vbsteven
04/27/2023, 6:47 PMuser_data
I think your only option is to have some other outside Object
or fun
that you can access from inside the callback function.Adam S
04/27/2023, 6:49 PMvbsteven
04/27/2023, 6:50 PMAdam S
04/27/2023, 6:51 PMvbsteven
04/27/2023, 6:52 PMcallback
value inside an object
, and in the staticCFunction, get it back from the object
Adam S
04/27/2023, 6:54 PMvbsteven
04/27/2023, 6:56 PMuser_data
pointer, so you have something to distinguish for who the callback was intendedAdam S
04/27/2023, 7:15 PMvbsteven
04/27/2023, 7:16 PMStableRef
to the Kotlin lambda when registering the callback. And in the staticCFunction I can extract the original Kotlin callback again from the StableRef
using the user_data pointer, and then I can invoke the Kotlin callback from within staticCFunction
Adam S
04/28/2023, 11:15 AMvbsteven
04/28/2023, 11:15 AMAdam S
04/28/2023, 11:15 AMAudioProcessor
is a fun interface
that accepts the converted argumentsattachAudioMixedProcessor(::processAudio)
vbsteven
04/28/2023, 11:22 AMstaticCFunction
, I'm not sure if both of these invocations will resolve to the same pointer, so it could be that you are detaching a different function than the one you are attachingstaticCFunction
I usually define it once as a val
in the file, and then reference that.
private val myCFunc = staticCFunction {
// do the processing
}
Adam S
04/28/2023, 11:24 AMstaticCFunction {}
when all the processors were added, and the function was ‘ready’vbsteven
04/28/2023, 11:27 AMstaticCFunction
will make sure there is a C function defined that can be called from C code. It might be smart enough to resolve to the same pointer if the same kotlin function is passed in, but I'm not sureAdam S
04/28/2023, 11:29 AM// must be a static singleton, required by `staticCFunction {}`
internal object AudioDeviceInitializerImpl : AudioDeviceInitializer {
override fun attachAudioMixedProcessor(processor: AudioProcessor) {
mixedAudioProcessors += processor
}
internal fun attach(): Unit = AttachAudioMixedProcessor(processAllStatic)
internal fun detach(): Unit = DetachAudioMixedProcessor(processAllStatic)
}
// must be a package-level function, required by `staticCFunction {}`
private val mixedAudioProcessors = mutableListOf<AudioProcessor>()
// must be a package-level function, required by `staticCFunction {}`
private fun processAll(buffer: COpaquePointer?, frames: UInt) { for (
processor in mixedAudioProcessors) {
// ...
}
}
/** static reference to [processAll] */
private val processAllStatic = staticCFunction(::processAll)