https://kotlinlang.org logo
#kotlin-native
Title
# kotlin-native
j

Jeff Lockhart

07/06/2022, 12:56 AM
Is there a way to add additional
external expect fun
definitions to a pre-defined ObjC interop? I'm using an ObjC cocoapods library and need to call a function that's not part of its public API for testing. I know the function signature from its source code. Basically, I just need a way to ObjC "message" the function in Kotlin. Or something akin to Java reflection to access it.
r

Rick Clephas

07/07/2022, 6:40 AM
I am not sure what you mean by “pre-defined ObjC interop”, but if you have any control over the used
.def
file you could add custom declarations: https://kotlinlang.org/docs/native-c-interop.html#add-custom-declarations
j

Jeff Lockhart

07/07/2022, 7:12 AM
By pre-defined, I mean that the cocoapods plugin handles generating the interop for the ObjC library. The only
.def
file in the project is build/cocoapods/defs/LibName.def. And the contents is:
Copy code
language = Objective-C
modules = LibName
I'm not aware of a way to modify this, since the cocoapods plugin handles generating the interop. Is it possible to define another
.def
file for the same library?
r

Rick Clephas

07/07/2022, 7:24 AM
If I am not mistaken you can’t use two cinterop files for the same library. Though I think you should be able to modify the
.def
file and/or use a custom
.def
file. The cinterops can be created/accessed in the
build.gradle.kts
file: https://github.com/rickclephas/NSErrorKt/blob/17f92767016c9401cff6f3fc371c627cc41e693b/nserror-kt/build.gradle.kts#L44 Haven’t looked at the source of the cocoapods plugin, but I assume they are using the same feature. In that case your cocoapods library should have a cinterop “task”.
j

Jeff Lockhart

07/07/2022, 7:25 AM
I did discover a way to define the most important method I need to access, using the
@ObjCMethod
annotation:
Copy code
@ObjCMethod(selector = "methodName", encoding = "@16@0:8")
private external fun ObjCClass.methodName(): Boolean
I mostly stumbled upon this solution with trial and error. I can't find its usage documented anywhere. The
encoding
parameter is the hard part. I'm assuming I can find this somewhere from the ObjC compiler output. But
@16@0:8
seems to be valid for methods that have no parameters and works at least for the
Boolean
return type of the method I needed. I based this off this code snippet and it just happened to work when I tried in my own code. I'll have to look more into how best to get an ObjC method's encoding.
r

Rick Clephas

07/07/2022, 10:44 AM
Nice! Didn't know about that.
Just checked the cocoapods plugin source and we can actually modify the generated
.def
file:
Copy code
tasks.named<org.jetbrains.kotlin.gradle.tasks.DefFileTask>("generateDef${pod.moduleName}") {
    doLast {
        outputFile.appendText("""
            
            ---

            static inline int getErrno() {
                return errno;
            }
        """.trimIndent())
    }
}
j

Jeff Lockhart

07/08/2022, 4:43 AM
Awesome. Do you know how a method as the one above would be defined? The example describes a usage of implementing a method or macro in C. But I really just need to describe an existing ObjC method signature, as a header file would. Since the class is already defined elsewhere in its own header, maybe I'd need to define a category.
r

Rick Clephas

07/08/2022, 5:19 AM
I think you need to define a class extension instead. Since you just need to expose an already existing method that's part of the original implementation.
j

Jeff Lockhart

07/08/2022, 8:47 PM
I've been testing this out, appending to the cocoapods .def file. I'm able to see my additions appended in the generated .def file. I'm also getting errors output when the cinterop task runs, if something's undefined, syntax error, etc. But once I resolve any errors and the task completes successfully, I'm not actually seeing any of my cinterop definitions accessible in Kotlin.
r

Rick Clephas

07/08/2022, 9:01 PM
Hmm is that also the case with a simple static function (that e.g. returns a fixed integer or something)?
j

Jeff Lockhart

07/08/2022, 9:20 PM
Yeah, I just tested with this (almost identical to the example):
Copy code
static inline int doesThisWork() {
    return 1;
}
I can't find a reference to the function in Kotlin.
🤔 1
Are you able to successfully reference the function doing this in your own code?
r

Rick Clephas

07/09/2022, 9:15 PM
I haven't tried this specific case yet. Though I have been able to reference a function when manually creating cinterop tasks. But I can do some testing tomorrow 🙂
Did some testing and the good news is that it is indeed possible to expose the non public API. To verify this I tried to expose the
firebaseUserAgent
property from
FIRApp
. With the following
.def
file:
Copy code
language = Objective-C
headers = FirebaseCore/FIRApp.h 
headerFilter = FirebaseCore/**

 ---

@interface FIRApp ()
    @property(class, readonly, nullable) NSString *firebaseUserAgent;
@end
I can print the user agent in Kotlin:
Copy code
import cocoapods.FirebaseCore.FIRApp
import cocoapods.FirebaseCore.firebaseUserAgent

println(FIRApp.firebaseUserAgent)
However the CocoaPods plugin generates the following
.def
file:
Copy code
language = Objective-C
modules = FirebaseCore
When
modules
is specified instead of
headers
and
headerFilter
the interop logic doesn’t include the
mainFile
that contains our custom declarations. @svyatoslav.scherbina any specific reason why the custom declarations aren’t included when
modules
is used?
s

svyatoslav.scherbina

07/11/2022, 9:26 AM
I did discover a way to define the most important method I need to access, using the
@ObjCMethod
annotation:
Please note that this annotation is not intended to be used manually, and provides no compatibility guarantees. Also, the encoding is platform-specific.
@svyatoslav.scherbina any specific reason why the custom declarations aren’t included when
modules
is used?
Likely a bug. As a workaround, one can patch the generated
.def
file to use
headers =
and
headerFilter =
instead of
modules =
.
👍🏻 1
j

Jeff Lockhart

07/12/2022, 7:22 AM
Thanks for the clarification. Should I create a YouTrack issue to report this bug?
s

svyatoslav.scherbina

07/12/2022, 12:44 PM
That would be helpful, thank you!
j

Jeff Lockhart

07/12/2022, 3:00 PM
Here's the YouTrack issue. @Rick Clephas thank you for all your help delving into this.
🙏 1
r

Rick Clephas

07/12/2022, 3:56 PM
NP! I guess it should be an easy fix (just adding
mainFile
to the list of headers), but I am not sure how to cover that with tests 🙂
s

svyatoslav.scherbina

07/12/2022, 5:09 PM
Creating an interop test like this should be enough. (But instead of passing a path to header in
createInterop
, an include path should be specified).
🙏 1
r

Rick Clephas

07/12/2022, 8:18 PM
❤️ 2
🙏 1
4 Views