{"id":22054071,"url":"https://github.com/landerlyoung/jenny","last_synced_at":"2026-03-09T17:37:09.523Z","repository":{"id":31000320,"uuid":"34558714","full_name":"LanderlYoung/Jenny","owner":"LanderlYoung","description":"JNI glue code generator","archived":false,"fork":false,"pushed_at":"2024-08-29T06:34:37.000Z","size":4297,"stargazers_count":88,"open_issues_count":2,"forks_count":11,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-20T12:33:44.724Z","etag":null,"topics":["android","annotation-processor","cpp","java","jni"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LanderlYoung.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-04-25T06:51:25.000Z","updated_at":"2024-12-08T06:58:17.000Z","dependencies_parsed_at":"2024-12-24T14:12:32.897Z","dependency_job_id":"cfaf8826-fbd1-445c-a218-b7ac703f4086","html_url":"https://github.com/LanderlYoung/Jenny","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LanderlYoung%2FJenny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LanderlYoung%2FJenny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LanderlYoung%2FJenny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LanderlYoung%2FJenny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LanderlYoung","download_url":"https://codeload.github.com/LanderlYoung/Jenny/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240690851,"owners_count":19841956,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","annotation-processor","cpp","java","jni"],"created_at":"2024-11-30T15:19:28.602Z","updated_at":"2026-03-09T17:37:09.489Z","avatar_url":"https://github.com/LanderlYoung.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Jenny -- the JNI helper\n\n[![CI][CI_B]][CI]  [![Publish][PUB_B]][PUB] [![MavenCentral][MV_B]][MV] ![GitHub code size in bytes][CS_B] ![GitHub][LC_B]\n\n[CI_B]: https://github.com/LanderlYoung/Jenny/workflows/CI/badge.svg\n[CI]: https://github.com/LanderlYoung/Jenny/actions?workflow=CI\n[PUB_B]: https://github.com/LanderlYoung/Jenny/workflows/Publish/badge.svg\n[PUB]: https://github.com/LanderlYoung/Jenny/actions?workflow=Publish\n[MV_B]: https://img.shields.io/maven-central/v/io.github.landerlyoung/jenny-annotation\n[MV]: https://central.sonatype.com/artifact/io.github.landerlyoung/jenny-compiler\n[CS_B]: https://img.shields.io/github/languages/code-size/LanderlYoung/Jenny\n[LC_B]: https://img.shields.io/github/license/LanderlYoung/Jenny\n\n---\n\n\n## Intro\n\n**Jenny is a java annotation processor, which helps you generate C/C++ code for JNI calls according to your java native class.**\n\nJenny comes with two main part:\n1. Native**Glue**Generator: which generate skeleton C++ code for your native class/method.\n2. Native**Proxy**Generator: which generate helper C++ class for you to call java APIs through JNI interface, including create new instance, call method, get/set fields, define constants.\n\n**Glue** stands for c++ code to implement Java native method. (Glue java and C++.)\n\n**Proxy** stands for c++ class to provide calls to java from c++. (c++ side proxy for the java class.)\n\nAnd there is an extra bonus -- [jnihelper.h](cpp/jnihelper.h) that uses C++ RAII technology to simplify JNI APIs. When opt-in (with `'jenny.useJniHelper'=true`), the generated proxy class will also add methods using `jnihelper`, which makes life even happier!\n\n## Why Jenny?\n\nWhen writing JNI code, people usually come across APIs where java method/field/type signatures are required, some of them like `JNIEnv::RegisterNatives`, `JNIEnv::FindClass`, `JNIEnv::GetMethodID`, etc. It is very hard to hand-craft those signatures correctly and efficiently, so programmers often waste much time writing and debugging those boilerplate.\n\nJenny is now your JNI code maid, who takes care of all those boilerplate so you can be even more productive.\n\n## At a glance\n\nLet's see what the generated code is.\n\nYou can find full code in [sample-gen]().\n\n### Glue\n\nJava class.\n\n```java\n@NativeClass\npublic class NativeTest {\n    public static final int RUNTIME_TYPE_MAIN = 1;\n    public native int add(int a, int b);\n    public native void cpp_magic(String s, byte[] data);\n}\n```\n\nThe generated Glue code.\n\n```C++\n// NativeTest.h\n\nnamespace NativeTest {\nstatic constexpr auto FULL_CLASS_NAME = u8\"io/github/landerlyoung/jennysampleapp/NativeTest\";\nstatic constexpr jint RUNTIME_TYPE_MAIN = 1;\n\njint JNICALL add(JNIEnv* env, jobject thiz, jint a, jint b);\nvoid JNICALL cpp_magic(JNIEnv* env, jobject thiz, jstring s, jbyteArray data);\n\ninline bool registerNativeFunctions(JNIEnv* env) { ... }\n\n}\n\n// NativeTest.cpp\n\njint NativeTest::add(JNIEnv* env, jobject thiz, jint a, jint b) {\n    // TODO(jenny): generated method stub.\n    return 0;\n}\n\nvoid NativeTest::cpp_magic(JNIEnv* env, jobject thiz, jstring s, jbyteArray data) {\n    // TODO(jenny): generated method stub.\n}\n\n```\n\nJenny generate:\n\n1. constant defines\n2. JNI register function\n3. native method declare with the same name as java methods\n4. native method implementation stubs\n\nYou just need to fill the stubs with real code.\n\n### Proxy\n\nThe following [code][proxy_code] is a show case that C++ uses OkHttp to perfomr a HTTP get operation through JNI APIs.\n\n[proxy_code]: sample-android/src/main/cpp/ComputeIntensiveClass.cpp#L100\n\n```C++\njstring func(jstring _url) {\n    jenny::LocalRef\u003cjstring\u003e url(_url, false);\n\n    OkHttpClientProxy client = OkHttpClientProxy::newInstance();\n    BuilderProxy builder = BuilderProxy::newInstance().url(url);\n    RequestProxy request = builder.build();\n    CallProxy call = client.newCall(request.getThis());\n    ResponseProxy response = call.execute();\n    ResponseBodyProxy body = response.body();\n    return body.string().release();\n}\n```\nAnd here is the equivlent java code.\n\n```java\nString run(String url) throws IOException {\n    OkHttpClient client = new OkHttpClient();\n    Request request = new Request.Builder()\n        .url(url)\n        .build();\n\n    Response response = client.newCall(request).execute();\n    return response.body().string();\n}\n```\n\nIf you are femiliar with JNI, you'd be surprised! The C++ code using Jenny just as clean as the Java code. Without Jenny it would be a nightmare.\n\nAnd here is another real world comparesion using `URLConnection` api **with vs without jenny**. [🔗Follow the link to see the nightmare!](https://gist.github.com/LanderlYoung/1a203f519ba5f91b38c1d81534d63664)\n\n\nAnd also, here is another example without `jnihelper`.\n\n```C++\nvoid NativeDrawable::draw(JNIEnv *env, jobject thiz, jobject _canvas) {\n    auto bounds = GraphicsProxy::drawableGetBounds(env, thiz);\n\n    GraphicsProxy::setColor(env, state-\u003epaint, state-\u003ecolor());\n    GraphicsProxy::drawableCircle(\n        env, _canvas,\n        RectProxy::exactCenterX(env, bounds),\n        RectProxy::exactCenterY(env, bounds),\n        std::min(RectProxy::exactCenterX(env, bounds),\n                 RectProxy::exactCenterY(env, bounds)) * 0.7f,\n        state-\u003epaint\n    );\n}\n```\n\n## How to\n\n### Use in gradle\n\nJenny comes with two component\n1. the annotation library\n2. the annotation-processor\n\n[![Download][MV_B]][MV] 👈👈👈 click here for latest version on maven central.\n\n```groovy\n\ndependencies {\n    compileOnly 'io.github.landerlyoung:jenny-annotation:1.0.0'\n    kapt 'io.github.landerlyoung:jenny-compiler:1.0.0'\n    // for non-kotlin project use:\n    // annotationProcessor 'io.github.landerlyoung:jenny-compiler:1.0.0'\n}\n```\n\nFor kotlin project, you gonna need the `kotlin-kapt` plugin.\n\nThat's it!\n\nThe generated code directory depends on your compiler config, typically, for android project it's inside `build/generated/source/kapt/debug/jenny`, for java project it's `build/generated/sources/annotationProcessor/java/main/jenny`. Also, you can use your own directory with simple config, see below.\n\nYou can use the generated code as you like, copy-past manually, or use gradle to copy them automatically (see sample in `sample-android/guild.gradle`).\n\n### Use annotations\n\n#### Annotations for glue\n\nAdd `@NativeClass()` annotation to you native class in order to let Jenny spot you class, and then generate corresponding cpp source.\n\nThen Jenny would generate code for you. [sample-gen](sample-gen) contains samples for Jenny generated code.\n\n\u003e Note: There is a config field in `NativeClass.dynamicRegisterJniMethods`, when `true` (the default value) will generate code registering JNI function dynamically on the JNI_OnLoad callback by `JNIEnv::RegisterNatives`, instead of using JNI function name conversions (what javah/javac does).\n\n#### Annotations for proxy\n\nAdd `@NativeProxy` to your normal java/kotlin class, need to cooperate with `@NativeMethodProxy` and `@NativeFieldProxy`, please read the doc.\n\nAlso, you can tell Jenny to generate code for libray classes by using the `@NativeProxyForClasses` annotation. \u003csup\u003e(note)\u003c/sup\u003e\n\n\u003e (note): Use this feature with caution. When your compile-class-path and runtime-class-path have different version of the same class, it's easy to crash because the proxy can't find some method which appears in compile-time but not on runtime. For instance to generate proxy for [`java.net.http.HttpRequest`](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.html) compiled with java-11, ran with java-8, your code crashes because that class just don't exist before java-11.\n\u003e\n\u003e In this case, the recommanded way is to write your own helper class, and generate proxy for it.\n\n## Configurations\n\nJenny annotation processor arguments:\n\n| name | default value | meaning |\n| :-: | :-: | :- | \n| `jenny.threadSafe` | `true` | The proxy class supports lazy init, this flag controls if the lazy init is thread safe or not. |\n| `jenny.errorLoggerFunction` | `null` | When proxy failed to find some method/class/field use the given function to do log before abort. The function must be a C++ function on top namespace with signature as `void(JNIEnv* env, const char* error)` |\n| `jenny.outputDirectory` | `null` | By default, Jenny generate filed to apt dst dir, use this argument to control where the generated files are. |\n| `jenny.fusionProxyHeaderName` | `jenny_fusion_proxies.h` | The `fusionProxyHeader` is a header file that include all generated proxy files and gives you a `jenny::initAllProxies` function to init all proxies at once, this flag changes the file name. |\n| `jenny.headerOnlyProxy` | `true` | The generated proxy file use header only fusion or not. |\n| `jenny.useJniHelper` | `false` | Turn on/off jnihelper |\n\nAnd also, there are some config in Jenny's annotations, please read the doc.\n\n## FAQ\n\n#### 1. How to passing arguments to annotation processor\n\n1. For kotlin project, it simple\n\n```groovy\nkapt {\n    // pass configurations to jenny\n    arguments {\n        arg(\"jenny.threadSafe\", \"false\")\n        arg(\"jenny.errorLoggerFunction\", \"jennySampleErrorLog\")\n        arg(\"jenny.outputDirectory\", project.buildDir.absolutePath+\"/test\")\n        arg(\"jenny.headerOnlyProxy\", \"true\")\n        arg(\"jenny.useJniHelper\", \"true\")\n        arg(\"jenny.fusionProxyHeaderName\", \"JennyFisonProxy.h\")\n    }\n}\n```\n\n2. For Android, you can also [do this](https://developer.android.com/studio/build/dependencies#processor-arguments).\n\n3. For Java Project, do this:\n\n```groovy\ncompileJava {\n    options.compilerArgs += [\n            \"-Ajenny.threadSafe=false\",\n            \"-Ajenny.useJniHelper=false\",\n    ]\n}\n```\n\n\n#### 2. My JNI code crashes saying some class not found while the are really there?!\n\nWhen using JNI with multi-thread in C++, please be noticed the `pure` native thread (that is create in C++ then attached to jvm) has its class loader as the boot class loader, so on such thread you can only see java standard library classes. For more info, please refer to [here](https://developer.android.com/training/articles/perf-jni#native-libraries).\n\nTo solve this problem, please init proxy classes on the `JNI_OnLoad` callback, and there is a `jenny_fusion_proxies.h` may by helpful.\n\n## License\n\nOpen sourced under the Apache License Version 2.0.\n\nIf you like or are using this project, please start!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flanderlyoung%2Fjenny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flanderlyoung%2Fjenny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flanderlyoung%2Fjenny/lists"}