Reference parameter passing from Java to .NET

846 views
Skip to first unread message

Boštjan Klemenc

unread,
Sep 6, 2014, 9:24:13 AM9/6/14
to jni...@googlegroups.com
Hi.

Suppose I have a following function in C#:

public Object changeTheVariable(ref int value)
{
         value = value + 5;

         return "Change has been done";
}



Now, proxygen will make a proxy function, looking like this:

system.Object changeTheVariable(net.sf.jni4net.Ref);

My question:
How do I create this reference parameter ?
I made an instance of this class "net.sf.jni4net.Ref" and passed it as an argument but I get this error:

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Stack trace:

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. at net.sf.jni4net.jni.JNIEnv.CallIntMethod(JniHandle obj, MethodId methodIdNative, Value[] args) at net.sf.jni4net.utils.Convertor.PrimJ2C(JniLocalHandle obj, JNIEnv env, Type type) at net.sf.jni4net.utils.Convertor.FullJ2C[TRes](JNIEnv env, JniLocalHandle obj) at assemblyLoader.__Reflektor.changeTheVariable14(IntPtr __envp, JniLocalHandle __obj, JniLocalHandle value) at assemblyloader.Reflektor.changeTheVariable(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at coldfusion.runtime.java.JavaProxy.invoke(JavaProxy.java:97) at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2432) at cfDotNet2ecfc691337788$func......

Pavel Šavara

unread,
Sep 7, 2014, 2:25:19 PM9/7/14
to jni...@googlegroups.com
Hi Bostjan,

I just tested it under sun's JDK 1.5 and open JDK 1.8 and both worked fine with trunk code.

In \trunk\jni4net.test.j\src\test\java\net\sf\jni4net\test\TestClr.java
I added folowing test
@Test()
public void testCWithJavaStatic() {
int res= JavaCallBack.callBackStatic(1,2);
Assert.assertThat(res, is(3));
}

See also
\trunk\jni4net.tested.n\src\StaticMethods.cs
TestRefParam(ref int num)

If you could add test which fails it would be great.
Note that on 64bit system, Geert reported and memory corruption issue recently, which i'm going to release fix for soon.

release candidate here. Could you please try it with new version, perhaps it will fix your issue too ?

Also I'm curious, what kind of runtime you have with under coldfusion. And any lessons learned which you could share with community about jni4net on the platform.

Cheers
Pavel


--
--
You received this message because you are subscribed to jni...@googlegroups.com
http://groups.google.com/group/jni4net?hl=en-GB?hl=en-GB
http://jni4net.sf.net/

---
You received this message because you are subscribed to the Google Groups "jni4net" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jni4net+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Boštjan Klemenc

unread,
Sep 10, 2014, 12:58:11 PM9/10/14
to jni...@googlegroups.com
Hi Pavel,

thank you for your quick reply. I've been doing a lot of research on using the bridge. I have found out that my variable has switched its type from integer to string somewhere before it was used as a parameter. ColdFusion is not strong typed language and primitive types are automatically selected before translation to Java byte code (unless you explicitly use javaCast() function on them).
I guess weak typed language is easier to work with for some people (I personally don't like such "automatisation" because it allows some bad programming).

I have tried your 0.8.7 version and I also managed to compile jni4net sources on my machine. Everything worked the same as before, so I will stick with 0.8.6 version until next release..
I use JRE 1.7.0 inside Coldfusion10 on 64 bit Windows 8.1 but I can proudly say that bridge also works stable Windows Server 2012, XP, Windows 7 (32 and 64 bit) using Coldfusion 7, 8 , 9, 10 and Railo (32 and 64 bit Java). Coldfusion 11 crashes with jni4net and when I'll have some more time, I will post the error here.

I've made  an assembly loader as a CF component (*.cfc file) for loading custom assemblies, using JNI4nets system.reflection.* and other proxy types.
If anyone is interested, I can publish the sources of *.cfc files here. I've been working alone on this matter and I'm sure some things can still be improved for better usage.
Some clients are using Railo instead ColdFusion and that was the reason why I had to find alternative solution like JNI4NET (ColdFusion supports calling DotNet objects by using integrated JNBridge service but Railo doesn't have that support).

Some quick tips if anyone uses this bridge on CF/Railo:
 - init() function name is reserved for calling constructors, so it's smart to create a wrapper directly inside java/class file and use the wrapper, to call Bridge.init(File) function
- I've been using Mark Mandels JavaLoader to load the bridge and this URL class loader is kept on server scope for multiple application usage
- it's necessary, to use javaCast() function for any primitive java type before passing it to Bridge
- loading and calling functions from custom assemblies in runtime requires parameters to be passsed as CLR types so I made converter functions from Java to CLR for primitive types, before they are passed to system.Object array - MethodInfo.Invoke(Object, Object[])

There is still an issue here: when using reference parameters, their values don't get updated after CLR execution. Maybe this is a ColdFusion issue, I haven't tested the calls directly in Java but I have tried many different examples. Could this have something to do with the fact, that Java only supports parameters by value ? If I use primitive type system.ValueType as a reference parameter, it's value will always be copied, even if passed inside array system.Object[]  ?

Pavel Šavara

unread,
Sep 11, 2014, 3:00:27 AM9/11/14
to jni...@googlegroups.com
Great post thanks!

Maybe I guessed the answer to your ref troubles? 
Let me try: do you keep the instance of JVM Ref<xxx> class on heap after the call passing it is completed ? Do you expect that it would share memory spaces and would be updated even later ?
Or perhaps, does your runtime make any such expectations for you ? Is it doing any hidden operation reordering or asynchrony ?
How ref works with jni4net is that the JVM helper Ref<> object is populated at start of the call and the value is read at the return from function. After that, the helper is dead/useless.

Pavel

--

Boštjan Klemenc

unread,
Sep 11, 2014, 7:20:33 PM9/11/14
to jni...@googlegroups.com
Hi,

I have made an example in Java - reference type Array is passed by reference, but it acts like passed by value.. all the functions with "ref" parameters loose their meaning..

C# assemblyloader.dll code (wrapped with proxygen):

namespace assemblyLoader
{
    public class Reflektor
    {
        private Type currentType;
        private Object classInstance;
        enum nullenumerator { nullValue };

        //konstruktor
        public Reflektor(Type tip)
        {
            this.currentType = tip;
        }

        //other methods
        .
        .
        .
        .
        .
        public Object castToCLRType(int value)
        {
            return value;
        }

        public void addByReference(ref Array a)
        {
            a.SetValue((int)a.GetValue(0) + 10, 0);
            //I create a new integer array
            int[] zacasni = new int[2] {999,888};
            //if called in C#, a would become zacasni, but in java I get back the modified a
            a = zacasni;

        }
}

Java code:
import net.sf.jni4net.Bridge;
import java.io.IOException;
import java.io.File;
import assemblyloader.Reflektor;
import net.sf.jni4net.Ref;

public class JniBridge
{
    public static void main(String[] args) throws IOException {
        Bridge.init();
        Bridge.LoadAndRegisterAssemblyFrom(new File("D:\\zaJavo\\assemblyLoader.j4n.dll"));
        //I create new system.Object - this is not necesary, but it's not static class and consturctor accepts System.Type parameter
        system.Object b = new system.Object();
        Reflektor myRuntime = new Reflektor(b.typeof());
        Integer integerValue = 5;
        //I cast the integer value to CLR type
        system.Object valType = myRuntime.castToCLRType(integerValue);
        system.Array dotNetArray = system.Array.CreateInstance(valType.typeof(), 1);
        dotNetArray.SetValue(valType, 0);
        myRuntime.addByReference(new Ref(dotNetArray));
        //array was updated but reference didn't work - the value should be 999 instead 15
        System.out.println(dotNetArray.getItem(0));
        system.Console.WriteLine("The bridge is working.");
    }
}

Sure I can make a wrapper and simply return modified arguments inside assemblyloader but I think it would mean a lot if reference would actually work as intended..
Pavel, I hope this example answers all four questions of what I am trying to achieve.

Boštjan

Pavel Šavara

unread,
Sep 25, 2014, 3:23:24 AM9/25/14
to jni...@googlegroups.com
Could you please find the code generated for this method in proxy and post it ?
Both java and C# parts


--

Boštjan Klemenc

unread,
Sep 25, 2014, 12:14:08 PM9/25/14
to jni...@googlegroups.com
Hi, here are generated codes:

JVM:
@net.sf.jni4net.attributes.ClrMethod("(LSystem/Array;)V")
 public native void addByReference(net.sf.jni4net.Ref<system.Array> a);

CLR:
private static void addByReference14(global::System.IntPtr @__envp, global::net.sf.jni4net.utils.JniLocalHandle @__obj, global::net.sf.jni4net.utils.JniLocalHandle a) {
            // (Lnet/sf/jni4net/Ref;)V
            // (LSystem/Array;)V
            global::net.sf.jni4net.jni.JNIEnv @__env = global::net.sf.jni4net.jni.JNIEnv.Wrap(@__envp);
            try {
            global::System.Array @__ref_a = net.sf.jni4net.Ref.GetValue<global::System.Array>(@__env, a);
            global::assemblyLoader.Reflektor @__real = global::net.sf.jni4net.utils.Convertor.StrongJp2C<global::assemblyLoader.Reflektor>(@__env, @__obj);
            @__real.addByReference(ref __ref_a);
            net.sf.jni4net.Ref.SetValue<global::System.Array>(@__env, a, @__ref_a);
            }catch (global::System.Exception __ex){@__env.ThrowExisting(__ex);}

Pavel Šavara

unread,
Sep 25, 2014, 1:14:14 PM9/25/14
to jni...@googlegroups.com
This is strange
StrongJp2C<global::assemblyLoader.Reflektor>(...)

the generic type argument should be System.Array here, not assemblyLoader.Reflektor I think.
And that probably why the memory corruption.
Could it be caused by ColdFusion dynamic nature during proxygen ? Is coldfusion part of your proxygen process ?



Pavel Šavara

unread,
Sep 25, 2014, 1:28:18 PM9/25/14
to jni...@googlegroups.com
No, I just got confused. 
assemblyLoader.Reflektor is the class which has the method addByReference() on it, right ?

Pavel Šavara

unread,
Sep 25, 2014, 1:34:50 PM9/25/14
to jni...@googlegroups.com
public class JniBridge
{
    public static void main(String[] args) throws IOException {

....
 
       Ref<system.Array> myref=new Ref(dotNetArray)
       myRuntime.addByReference(myref);
// this is the part you were missing
       dotNetArray=myref.getValue()

....

    }
}

Please confirm it soles the issue I didn't try it :) 


Boštjan Klemenc

unread,
Sep 25, 2014, 2:59:53 PM9/25/14
to jni...@googlegroups.com
Excellent !!!! :)
So the key thing is to call Refs getValue() function to pick up the correct pointer of the object....
I tried the call in directly Java and trought ColdFusion - it works flawlessly :)

Thank you very much Pavel. I owe you a beer or two... :)
Reply all
Reply to author
Forward
0 new messages