ikvmnet / ikvm

A Java Virtual Machine and Bytecode-to-IL Converter for .NET
Other
1.22k stars 111 forks source link

JNI calling Java method from native with long parameter is not marshalled correctly #344

Closed TheLastRar closed 1 year ago

TheLastRar commented 1 year ago

IKVM version : image-netcoreapp3.1-win7-x64 https://github.com/ikvmnet/ikvm/commit/29d8b854dd18d6c161e4b630e2e349f041f7530c, Build taken from this Action

Given Java code

package jnitest;

import java.lang.reflect.Method;

public class Main {

    static {
        System.loadLibrary("JNITestNative");
    }

    private static native void foo();
    private static native void setCallback(Method callback);

    public static void main(String[] args) {

        Method callback;
        try {
            callback = Main.class.getDeclaredMethod("bar", long.class);
            setCallback(callback);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return;
        } catch (SecurityException e) {
            e.printStackTrace();
            return;
        }

        foo();
    }

    private static void bar(long bignumber)
    {
        System.out.println(String.format("Got %d", bignumber));
    }
    }
}

And C code

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jnitest_Main */

#ifndef _Included_jnitest_Main
#define _Included_jnitest_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jnitest_Main
 * Method:    foo
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jnitest_Main_foo
  (JNIEnv *, jclass);

/*
 * Class:     jnitest_Main
 * Method:    setCallback
 * Signature: (Ljava/lang/reflect/Method;)V
 */
JNIEXPORT void JNICALL Java_jnitest_Main_setCallback
  (JNIEnv *, jclass, jobject);

#ifdef __cplusplus
}
#endif
#endif
#include <cstdint>

#include "jnitest_Main.h"

static jclass testClass;
static jmethodID javaRelay;

JNIEXPORT void JNICALL Java_jnitest_Main_foo(JNIEnv* env, jclass)
{
    int64_t a = 3987654321;

    printf("Call Java with %lld\n", a);
    env->CallStaticLongMethod(testClass, javaRelay, (jlong)a);
}

JNIEXPORT void JNICALL Java_jnitest_Main_setCallback(JNIEnv* env, jclass clazz, jobject method)
{
    testClass = (jclass)env->NewGlobalRef(clazz);
    javaRelay = env->FromReflectedMethod(method);
}

JVM produces the following output

Call Java with 3987654321
Got 3987654321

IKVM instead produces this output

Call Java with 3987654321
Got -307312975
wasabii commented 1 year ago

So, the issue here was with the vararg trampoline. Was calling va_arg with 'long'. Turns out on Win32, long is 32 bit.

AaronRobinsonMSFT commented 1 year ago

So, the issue here was with the vararg trampoline. Was calling va_arg with 'long'. Turns out on Win32, long is 32 bit.

Correct. In .NET 6 we introduced CLong and CULong to help with this - https://learn.microsoft.com/dotnet/standard/native-interop/best-practices#cc-long.

TheLastRar commented 1 year ago

The inconsistencies with long in C/C++ is why I prefer to use fixed width integer types instead. At least Java and .Net is consistent in making long always 64bit.

I will have to remember CLong and CULong next time I do Native interop, that's a pretty neat addition

wasabii commented 1 year ago

Should be resolved in 8.5.1