16

I have a C++ dll that I want to use in Unity by exporting the functions to C#. The Unity project runs on Android devices and the C++ code makes use of java. To initialize the C++ I need to call the following function first:

void api_initialize(JNIEnv* env, jobject* app_context, jobject* class_loader) {

    JavaVM* vm = nullptr;
    env->GetJavaVM(&vm);
    if (!vm) {
      return;
    }

    //Do other proprietary things
}

In Unity I have the following exported Dll function

    [DllImport (dllName)]
    private static extern void api_initialize (IntPtr java_env, IntPtr app_context, IntPtr class_loader);

My question is How do I get a JNIEnv pointer in my C# class to then pass as a parameter into this function?

I am not the creator of this API and don't have the access to modify it, so I need to get the JavaVM from the JNIEnv, not the other way around.

user1782677
  • 1,963
  • 5
  • 26
  • 48
  • I believe that you could find an answer here-> http://stackoverflow.com/questions/21951711/how-to-pass-a-jni-c-sharp-class-into-java-or-handle-this- or create your custom java call using the Oracle documentation -> situationhttp://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp715 Anyway is a very good question. – Cabrra Aug 12 '16 at 17:13
  • could you use JNI_GetCreatedJavaVMs for this? – fuzzyTew Feb 12 '21 at 15:40

3 Answers3

5

I guess there is no way you can do this (might be there but haven't seen it yet) because this type of NDK calls required java wrapping around so i would like to purpose an another solution that use Java as an intermediate for your C# calls and then you can redirect this call to NDK code from java.

using UnityEngine;
using System.Collections;
using System.IO;

#if UNITY_ANDROID
public class JavaCallPlugin {

  // Avoid invalid calls
  public static void initiateNDK() {
    if (Application.platform != RuntimePlatform.Android)
          return;

    var pluginClass = 
        new AndroidJavaClass("com.package.class.UnityJavaDelegate");
    AndroidJavaObject plugin = 
        pluginClass.CallStatic<AndroidJavaObject>("obj");
    return plugin.Call("javaCallToNDK");
  } 
}
#endif

And in your java file do this

package com.package.class;

import android.content.ContentValues;
import android.content.Intent;
import android.os.Environment;

public class UnityJavaDelegate{
  private static UnityJavaDelegate obj;

  // define function call to your NDK method with native keyword
  private native void api_initialize(); 

  static {
    System.loadLibrary("yourlibraryname"); // setup your ndk lib to use 
  }

  public static UnityJavaDelegate getObj() {
    if(m_instance == null)
       obj= new UnityJavaDelegate();
    return obj;
  }

  private UnityJavaDelegate(){
  }

  public void javaCallToNDK(){
    // this call may not work because i haven't passed class 
    // loader TO NDK before, if not then you should try links 
    // at the bottom of the post to know how to pass customize 
    // as parameter to NDK.

    // put your call to NDK function here 
    api_initialize(this.getClass().getClassLoader()); 
  }
}

When you declare native method the javah automatically generate a ndk call definition with javaEnv and jobject as parameter so i guess you only need to pass class loader here .You can try this link and this links in case for more clarification.

Good Luck.Hope This will help.

Community
  • 1
  • 1
Pavneet_Singh
  • 36,884
  • 5
  • 53
  • 68
4

I think you can create another native plugin which will give you JNIEnv.

Plugins for Android

Summarizing this article you should try something like this (untested pseudo code):

JavaVM* g_JVM; // JavaVM is valid for all threads, so just save it globally 

extern "C" {
    JNIEnv* GetJniEnv();
}

// The VM calls JNI_OnLoad when the native library is loaded
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    g_JVM = vm;
    return JNI_VERSION_1_6;
}

// The JNI interface pointer (JNIEnv) is valid only in the current thread.
JNIEnv* GetJniEnv() {
    JNIEnv* jni_env = 0;
    g_JVM->AttachCurrentThread(&jni_env, 0);
    return jni_env;
}

Then in C#

[DllImport ("PluginName")]
private static extern IntPtr GetJniEnv();
  • 1
    I think JNI_OnLoad is only invoked if a Java module explicitly requests loading the library (System.loadLibrary). – Maestro Oct 18 '17 at 19:27
  • Using `GetJniEnv` as the function name is confusing here as the pointer is needed in the C++ world. However I have just tested this solution from a Unity3d C# script `[DllImport("mynative")] private static extern float helloWorld();` and it works perfectly as of Unity 2021.1.25f. – Crog Feb 14 '22 at 16:34
3

Expanding on Vyacheslav Gerasimov's answer, This code is tested and drop in without any additional work required:

JNIEnv* jni_env = NULL;
JavaVM* JVM;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JVM = vm;
    if (vm->GetEnv((void**)&jni_env, JNI_VERSION_1_6) != JNI_OK)
        __android_log_print(ANDROID_LOG_ERROR, "Unity", "ERROR: GetEnv failed")

    return JNI_VERSION_1_6;
}

JNI_OnLoad is called automatically by Unity/Java

No C# is required as you can pass your jni_env or JVM variable in c++.

Mark Bamford
  • 113
  • 1
  • 5