Marshal const void* buffers, pass JNI includes, cross-compile ABI builds - #3
Open
lemcoder wants to merge 3 commits into
Open
Marshal const void* buffers, pass JNI includes, cross-compile ABI builds#3lemcoder wants to merge 3 commits into
lemcoder wants to merge 3 commits into
Conversation
… builds
Three gaps found binding pdfium — a C++ library shipped as a prebuilt binary — for
JVM, Android and Kotlin/Native from one .def.
cinterop writes a const void* parameter as CValuesRef<*>, which fell through the
element-type match and stayed a raw address, so a JVM caller had no way to hand
FPDF_LoadMemDocument a ByteArray without finding off-heap memory. Const says the
callee only reads, so it is data; a non-const void* is a COpaquePointer and still
crosses as an address.
The generated stub includes jni.h and the documented CMakeLists had no way to find
it, so that example fails for anyone whose compiler lacks the JDK headers on its
default path. The plugin already picks a JDK for the bindings and now passes its
include roots as KONAN_JNI_INCLUDE_DIRS; find_package(JNI) would be free to choose
a different one.
An ABI build used the host compiler. abi() means Android, which means
cross-compiling, so the NDK toolchain is now supplied — found through
ANDROID_NDK_HOME, ndk.dir or sdk.dir in local.properties, or ANDROID_HOME, and
skipped when the build names a toolchain itself. Without it the failure is a linker
reporting an unknown file type, which says nothing about Android. abi {
platform.set(26) } chooses the minimum API.
Version bumped to 1.2.0-alpha06.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cinterop numbers them, so a binding read `kniBridge54(handle)` where the C API says `FPDFText_CountChars(text_page)`. The number is an artefact of the generator and carries nothing: it makes the hand-written expect/actual on top unreadable, and a linker error or stack frame names something you cannot search for. The name was already there. cinterop generates a friendly wrapper beside each bridge, and the formatter was reading it to build the doc comment; `parseBridgeNames` now takes the name from the same place and both `stripCinterop` and `marshalStub` rename their side together. JNI escapes `_` in a method name as `_1`, because `_` is the separator in `Java_<package>_<class>_<method>`. Without that, `FPDFText_CountChars` resolves as method `CountChars` on class `FPDFText`, and it compiles, links and dies on the first call — the same failure a missing `@JvmName` gives. Almost every C API worth binding has underscores in it, so the mangling is covered by a test rather than left to a future reader. A bridge keeps its number wherever the rename would be unsafe: two bridges that would share a name, one bridge reached from wrappers that disagree, a name the generated file already uses, or anything that is not a plain identifier. Passing no names reproduces the old output exactly. Verified on the jvm example, which now resolves `my_add`/`my_scale` and prints 5 and 40.0, and against pdfium in MikroMarkdown, whose 176 bridges rename with no numbers left and whose tests pass with real symbols. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`java.library.path` named `jvmInterop/mymath/jniLibs/<abi>`, but a host build writes to `jvmInterop/mymath/lib/<abi>` — only ABI-named directories may sit under jniLibs, which is AGP's contract and the reason for the split. A clean build therefore failed with "no examplestubs in java.library.path", and the example only appeared to work because an older plugin version had left a stub in jniLibs that later runs kept loading. That stale copy is also why the rename looked broken at first: the run picked up a dylib still exporting kniBridge0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three gaps found while binding pdfium — a C++ library shipped as a prebuilt binary — for JVM, Android and Kotlin/Native from a single
.def. Each one cost real debugging time, and each failed in a way that pointed somewhere other than the cause.const void*stayed a raw addresspdfium's loader is
FPDF_LoadMemDocument(const void* data, int size, const char* password). cinterop writes that parameter asCValuesRef<*>, which fell past the element-type match inclassifyand became aLong:A JVM caller had no way to hand it a
ByteArraywithout finding off-heap memory — the thing these bindings exist to avoid.constsays the callee only reads, so it is data; a non-constvoid*is aCOpaquePointerand still crosses as an address, which is right for a handle. The C stub needed nothing:jbyte*converts toconst void*implicitly.The stub could not find
jni.hThe generated stub includes it, and the CMakeLists in the README supplies no path — so that example fails for anyone whose compiler does not have the JDK headers on its default path, which is most people using an IDE-bundled JBR. The plugin already chooses a JDK for the bindings, so it now passes that JDK's include roots as
KONAN_JNI_INCLUDE_DIRS.find_package(JNI)would also work but is free to pick a different JDK than the one the bindings were generated against.An ABI build used the host compiler
abi()means Android, and Android means cross-compiling, but no toolchain was passed. CMake used the host clang, compiled the stub for macOS, and then:which says nothing about Android at all. The NDK is now found through
ANDROID_NDK_HOME, thenndk.dirorsdk.dirinlocal.properties, thenANDROID_HOME, taking the newest when several are installed — and skipped entirely when the build names-DCMAKE_TOOLCHAIN_FILEitself.abi("arm64-v8a") { platform.set(26) }chooses the minimum API; it isandroid-21otherwise.What I tried and removed
I also added
jvmInterops(sourceSet), to put bindings in a source set shared by a JVM and an Android target. You had already removed exactly that API in 83dcfc9, for exactly the reason it was wrong. I should have read the history before adding it. It is not in this branch — only the net change is.Verification
Unit tests pass, including a new case for the void-buffer mapping. Beyond the tests, all three are exercised end to end in MikroMarkdown: the JVM leg converts a real PDF through the generated bridges, and the Android leg produces
arm64-v8aandx86_64stubs that are genuine ELF objects naminglibpdfium.so.Version bumped to
1.2.0-alpha06.🤖 Generated with Claude Code