如何将JNI C#类传递给Java或处理这种情况?

时间:2014-02-22 08:20:33

标签: c# java java-native-interface unity3d

我正在尝试从C#调用Java方法,它从java中调用如下:

EgamePay.pay(thisActivity, payAlias, new EgamePayListener() {
            @Override
            public void paySuccess(String alias) {
            }

            @Override
            public void payFailed(String alias, int errorInt) {
            }

            @Override
            public void payCancel(String alias) {
            }
        });

前两个参数都可以,但是如何在C#中传递EgamePayListner?简单地创建具有相同功能的C#类将无法正常工作......

这是我目前正在做的事情:

using (AndroidJavaClass jc = new AndroidJavaClass("cn.egame.terminal.smspay.EgamePay"))
        {
            System.IntPtr cls_Activity = AndroidJNI.FindClass("com/unity3d/player/UnityPlayer");
            System.IntPtr fid_Activity = AndroidJNI.GetStaticFieldID(cls_Activity, "currentActivity", "Landroid/app/Activity;");
            object[] p = { fid_Activity, "payAlias", new EgamePayListener() };
            jc.CallStatic("pay", p);
        }

..

 class EgamePayListener
    {
        public void paySucess(string alias)
        {
        }

        public void payFailed(string alians, int errorInt)
        {
        }

        public void payCancel(string alias)
        {
        }
    }

显然这是错的,我该如何处理这种情况,以便在这些函数被触发时我可以在C#land中得到通知?

6 个答案:

答案 0 :(得分:4)

您可以通过参考在VS201X中将这些项目链接在一起。从这里开始,您应该能够填写其他层(JNI / Java),然后开始在系统周围传递指针(作为一个长整数)并调用您的函数。

C#Layer

Program.cs的

namespace CSharpLayer
{
class Program : CLILayer.CLIObject
{
    static void Main(string[] args)
    {
        Program p = new Program();

        p.invokeJava();
    }

    public void invokeJava()
    {
        //Call into CLI layer function to loadJVM, call Java code, etc
        loadJava();
    }

    public override void callback(string data)
    {
        //This will be called from the CLI Layer.
    }

}
}

C ++ / CLI层 - 具有CLR支持的DLL C ++项目(/ clr)

CLIObject.h

#pragma once

namespace CLILayer
{
public ref class CLIObject
{
    public:
        CLIObject();
        ~CLIObject();

        void loadJava(System::String^ jvm, System::String^ classpath);

        virtual void callback(System::String^ data) = 0;
};
}

CLIObject.cpp

#include "CLIObject.h"
#include <string>

#include <msclr/marshal_cppstd.h>
#include <msclr/marshal.h>

using namespace msclr::interop;
using namespace CLILayer;

CLIObject::CLIObject()
{   
}

CLIObject::~CLIObject()
{
}
CLIObject::loadJava(System::String^ jvmLocaion, System::String^ classpath)
{
    std::string _jvmLoc = marshal_as<std::string>(jvmLocation);
    std::string _classpath = marshal_as<std::string>(classpath);
}

答案 1 :(得分:3)

这是 JNI字段描述符

JavaLanguage     Type
--------------------------------
Z                 boolean
B                 byte
C                 char
S                 short
I                 int
J                 long
F                 float
D                 double
Ljava/lang/String; string
[Ljava/lang/Object; object[]

方法描述符使用字段描述符并描述Java方法的结构。方法描述符中的字段描述符之间没有空格。

除了由V表示的void返回类型之外,所有其他返回类型都使用字段描述符。下表描述了Java方法声明和相应的JNI描述符。当通过JNI从C#调用Java方法时,使用JNI方法描述符。

 Java Method Declaration    JNI Method Descriptor
----------------------------------------------------
 String foo();          "()Ljava/lang/String;"
 Void bar(int I, bool b);   (IZ)V

首先需要做的是创建一个字典对象,该对象将包含要传递给Java虚拟机的所有参数。在下面的示例中,我至少设置了类路径,它将告诉JVM在哪里查找类和包。

private Dictionary<string, string> jvmParameters = new Dictionary<string, string>();
jvmParameters.Add("-Djava.class.path", Location of the java class);

将JVM参数分配给字典对象后,可以创建JavaNativeInterface的实例。创建后,需要使用JVM参数调用LoadJVM方法,然后加载Java虚拟机。加载后,用户调用该方法来实例化Java对象(请注意,InstantiateJavaObject方法的使用是可选的,因为用户可能只想调用静态方法,在这种情况下,不需要调用此方法;但是,它不会造成任何伤害。)

Java = new JavaNativeInterface();
Java.LoadVM(jvmParameters, false);
Java.InstantiateJavaObject(Name of the java class excluding the extension);

一旦加载了JVM并实例化了类,用户就可以调用他们想要的任何方法。首先创建一个对象列表,其中包含要传递给Java方法的所有参数。由于它是一个对象列表,它可以保存不同类型的参数,因为所有内容都是从对象继承的。

 List<object> olParameters = new List<object>();
 olParameters.Add(Value of the parameter to be passed to the java method);

接下来,只需调用泛型方法CallMethod,将Java方法的返回类型作为模板类型传递。在下面的例子中,我调用了CallMethod,这意味着我想调用的Java方法的返回类型是一个字符串。

接下来,传入要调用的Java方法的名称和方法描述符(参见上文);最后,传递所有参数的列表。 (注意:如果不需要参数,则传入一个空列表。)

Java.CallMethod<string>("AddTwoNumbers", "(IILjava/lang/String;)I", olParameters);

嗯,我想这包装了一切,但请记住,你可以用JNI做更多的事情。测试应用程序只是一种快速而肮脏的方式来演示JNI组件的基础知识。为了全面了解JNI。

您可以在link

上获得更多信息

答案 2 :(得分:3)

如果您在Unity3D的上下文中,从Java本机代码到Unity3D C#脚本进行交互的更好方法是调用UnitySendMessage方法。您可以在Java代码中调用此方法,并将消息发送到C#,这样您就可以获得在C#中执行的指定方法。

您可以在Unity场景中添加gameObject并创建一个包含这三种方法的MonoBehavior脚本(paySucess(字符串消息),payFailed(字符串消息)和payCancel(消息))。然后将新创建的脚本附加到gameObject(让我们假设此gameObject的名称为“PayListener”)并确保在执行Java代码时场景中存在gameObject(您可以在其上调用DontDestroyOnLoad例如,Awake中的gameObject。

然后,在您的Java代码中,只需写下:

EgamePay.pay(thisActivity, payAlias, new EgamePayListener() {
    @Override
    public void paySuccess(String alias) {
        com.unity3d.player.UnityPlayer.UnitySendMessage("PayListener","paySuccess", alias);
    }

    @Override
    public void payFailed(String alias, int errorInt) {
        com.unity3d.player.UnityPlayer.UnitySendMessage("PayListener","payFailed", alias + "#" + errorInt);
    }

    @Override
    public void payCancel(String alias) {
        com.unity3d.player.UnityPlayer.UnitySendMessage("PayListener","payCancel", alias);
    }
});

如果没有com.unity3d.player.UnityPlayer类,它将阻止java文件成功构建。您可以找到/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar文件并将其添加为Java项目的依赖库。然后,您可以无错误地构建,生成和使用新的jar文件。

由于UnitySendMessage方法只能使用1个参数,因此您可能必须通过策略连接payFailed结果别名和errorInt,然后在Unity C#侧解析它。

通过使用新构建的jar文件并将其加载为AndroidJavaClassAndroidJavaObject,当调用Java中的那个时,应调用PayListener脚本中的相应方法。

有关使用UnitySendMessage的Android插件的文档,您可以访问示例3中有关如何实现Android JNI插件here的官方指南。

答案 3 :(得分:1)

看看http://jni4net.sourceforge.net/。我已成功使用它在CLR和JVM之间进行通信。可以找到调用.NET类的Java应用程序示例here。也支持事件(绑定到java侦听器接口)。

答案 4 :(得分:1)

我自己最终解决了这个问题,其他答案在这里发布了,但很遗憾没有解决我遇到的问题是在Unity中。

我解决它的方法是在Java中使用&#39; GetResult&#39;编写自定义侦听器类。函数返回一个字符串,解释结果是什么(即调用了哪个函数,结果是什么),以及C#通过AndroidJNI调用的结果的get函数。

在进行购买之后,我需要从C#调用get函数,直到我得到结果。

答案 5 :(得分:0)

听起来像一团糟。你想要完成什么,为什么?为什么不单独运行JVM和CLR,通过REST调用或类似的东西进行接口?