Native library integration

Hi all,

I have to use external native library which expect to get a Java object instance, then it calls some method at this instance.

Java method signature:
public static native int Msg_StartUp(String functionName, boolen, boolean, Object callBackObj);

My version of C# method:
[DllImport("foo.so")]
public static extern int Msg_StartUp(String functionName, bool, bool, Object callBackObj);

It is kind of callback registration, "functionName" should contains method which is called by native library.

Problem:

  • When I call this method I receives exceptions:
    System.Runtime.InteropServices.MarshalDirectiveException: Type Java.Lang.Object which is passed to unmanaged code must have a StructLayout attribute.

  • When I change String and Object parameter types to JValue and call with new JValue(JNIEnv.ToJniHandle(obj)), I get System.NullReferenceException exception.

I browsed manuals and forums but I didn't find similar case.

Probably I will be able to resolve this problem with extra Java code and then use JNI or Java Bindings Library but I'm looking for simpler solution.

Did anybody meet with similar case or know correct solution ?

Posts

  • JonathanPryorJonathanPryor USXamarin Team Xamurai

    Short version: for now, you cannot sanely use P/Invoke to invoke the native method. (Rationale below.) You must instead use JNI to invoke the Java-side native method.

    Long version: The first problem is that your Java and C# methods don't match:

    // Java method signature:
    public static native int Msg_StartUp(String functionName, boolen, boolean, Object callBackObj);
    
    // C# P/Invoke declaration
    [DllImport("foo.so")]
    public static extern int Msg_StartUp(String functionName, bool, bool, Object callBackObj);
    

    There are lots of reasons they don't match, but the fundamental reason is that C# P/Invoke declarations need to match the name and arguments of the native library function that will be executed.

    1. A Java native method is "name mangled" in the native library, and your C# method declaration needs to match the actual export in the native library. Your C# method should thus be declaring Java_<i>packageName_className</i>_Msg_StartUp, otherwise you'll get an EntryPointNotFoundException (which I'm surprised you're not getting).
    2. A Java native method has additional arguments prefixed to the method parameters, the JNIEnv* pointer and the instance pointer or class pointer.
    3. Which String is in scope for your C# method, System.String or Java.Lang.String? They're not the same, and they don't marshal the same. This is somewhat moot, though, as:
    4. Java instances should use IntPtr for marshaling.

    Your C# method declaration should thus be:

    [DllImport("foo")]
    public static extern int Java_InsertFullyQualifiedClassNameHere_Msg_StartUp(
            IntPtr env,
            IntPtr jniClass,
            IntPtr functionName,
            bool something,
            bool somethingElse,
            IntPtr callBackObj
    );
    

    Next, you'll need to properly load libfoo.so. You cannot rely on normal P/Invoke semantics, as normal P/Invoke semantics will not invoke JNI_OnLoad. Consequently, you'll need to call JavaSystem.LoadLibrary("foo") (or "somehow" -- JNI? -- load the Java class which should do the Java equivalent for you).

    Now we hit the reason why we cannot currently do this: the above C# method declaration requires that you pass a JNIEnv pointer, and there's currently no way to obtain one. (Mono.Android.dll has one, but it's not publicly exposed.) You could pass IntPtr.Zero, but this will probably break your native code unless it caches the JNIEnv* instance passed to JNI_OnLoad().

    So let's assume that the native method can support a null JNIEnv pointer. Next you need a proper jniClass, because this is a static method. You can use JNIEnv.FindClass to obtain one.

    Finally, you'll need your string value and callback value. Putting it all together:

    public static int Msg_Startup(
            string functionName, 
            bool something, 
            bool somethingElse, 
            IJavaObject callBackObj)
    {
        // FIXME! This won't work for most JNI native methods
        IntPtr env = IntPtr.Zero;
    
        IntPtr jniClass = JNIEnv.FindClass("InsertJavaClassNameHere");
        try {
            using (var s = new Java.Lang.String(functionName))
                return Java_InsertFullyQualifiedClassNameHere_Msg_StartUp(
                        env,
                        jniClass,
                        s.Handle,
                        something,
                        somethingElse,
                        callBackObj == null ? IntPtr.Zero : callBackObj.Handle,
                );
        } finally {
            JNIEnv.DeleteGlobalRef (jniClass);
        }
    }
    
  • LeonidZapechelyukLeonidZapechelyuk RUMember
    edited June 2013

    I tried use shared object in mono project:

    Code of libchdir.so

    JNIEXPORT void JNICALL Java_com_example_ctest_chdirtest_chdir (JNIEnv * e, jobject o, jstring jpath) { char *path = (char*) (*e)->GetStringUTFChars(e, jpath, 0); int = chdir(path); LOG("chdir returns %d",res); LOG(path); }

    Added extern method

    [DllImport("libchdir.so", EntryPoint="Java_com_example_ctest_chdirtest_chdir")] public static extern void Java_com_example_ctest_chdirtest_chdir (IntPtr env, IntPtr jniClass, IntPtr path);

    And called it:

    JavaSystem.LoadLibrary ("chdir"); IntPtr env = IntPtr.Zero; IntPtr jniClass = JNIEnv.FindClass (typeof(Java.Lang.Object)); Java.Lang.String s = new Java.Lang.String (FilesDir.AbsolutePath); Java_com_example_ctest_chdirtest_chdir (env, jniClass, s.Handle);

    And got exception:

    System.NullReferenceException: Object reference not set to an instance of an object

    >

    at testlibagain2.Activity1.OnCreate (Android.OS.Bundle) [0x0007d] in >c:\Users\user1211\Documents\testlibagain2\testlibagain2\MainActivity.cs:36

    >

    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) [0x00010] in >/Users/builder/data/lanes/monodroid-lion->bs1/0cc7ae3b/source/monodroid/src/Mono.Android/platforms/android->8/src/generated/Android.App.Activity.cs:1490

    >

    at at (wrapper dynamic-method) object.e3ea3ea4-328d-41d6-ae4a-f235625dcf18 >(intptr,intptr,intptr)

    >

    at

    Befor trying to use intptr instead string, i wrote

    public static extern void Java_com_example_ctest_chdirtest_chdir (IntPtr env, IntPtr jniClass, string path);

    and

    public static extern void Java_com_example_ctest_chdirtest_chdir (string path);

    but got same exception...

  • I resolve this by compile functions without JNIEnv* env and JNIEnv jobject params. But it is workaround. But for now it works for me.

  • AntonVenemaAntonVenema CAUniversity ✭✭

    JNIEnv.Handle can be used instead of IntPtr.Zero as the JNIEnv parameter.

  • AntaoAlmadaAntaoAlmada PTMember ✭✭

    Hi Jonathan,
    Does your reply still hold true? Is it still impossible to p/invoke a NDK generated native library?
    When you say "use JNI to invoke the Java-side native method", do you mean to use the RegisterAttribute, JNIEnv.FindClass, and so on? Something like the "Android Java Bindings Library" projects generate for jars?
    Thanks in advance,
    Antao

Sign In or Register to comment.