【问题标题】:How to pass a JNI C# class into Java or handle this situation?如何将 JNI C# 类传递给 Java 或处理这种情况?
【发布时间】:2014-03-04 17:42:03
【问题描述】:

我正在尝试从 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# 领域得到通知?

【问题讨论】:

  • The first two parameters are ok 怎么样?显示您的代码..
  • 公平调用,我认为它们是因为它们是基本类型,但我添加了我试图通过 C# 调用它的方式
  • @soshiki 您将无法直接将 C# 类提供给 Java。最多可以给它一个地址指针,然后在native层访问对象。
  • @Samhain 最终我的目标是能够调用这些函数,然后让 C# 代码知道它,这样做的正确方法是什么?
  • @soshiki 您可以创建多个图层以按照自己的方式在语言之间工作。最后,您将拥有:C# 层、C++/CLI 层、C/C++ JNI 层、Java 层。任何层都只能与它旁边的层“对话”(例如,C++/CLI 层可以与 C# 和 C/C++ JNI“对话”。)

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


【解决方案1】:

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

C#层

程序.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 (/clr) 的 DLL C++ 项目

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);
}

【讨论】:

    【解决方案2】:

    如果您处于 Unity3D 上下文中,从 Java 原生代码与 Unity3D C# 脚本交互的更好方法是调用 UnitySendMessage 方法。你可以在你的Java代码中调用这个方法,然后会向C#发送一条消息,这样你就可以得到一个在C#中执行的指定方法。

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

    然后,在你的 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插件的文档,您可以访问官方指南如何实现Android JNI插件here,在示例3中。

    【讨论】:

    • 嘿,谢谢你的回答,它绝对比我实现它的方式更优雅!
    【解决方案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获得更多信息

    【讨论】:

    • 嗨 Ashish,感谢您的回答,但我认为您实际上并没有回答我要问的问题,我的问题是让 java 在 C# 中调用函数或让 C# 知道什么是正在发生...
    • 我试图提供一个提示。
    • 对不起,也许我错过了?只是我的问题是特定于找出如何让 Java 与 C# 对话,您的答案似乎围绕着如何从 C# 调用 Java 函数...
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      我最终自己解决了这个问题,但不幸的是,这里发布的其他答案虽然很好,但并没有解决我面临的问题是在 Unity 中的事实。

      我解决这个问题的方法是在 Java 中编写一个自定义侦听器类,该类带有一个“GetResult”函数,该函数返回一个字符串,解释结果是什么(即调用了哪个函数以及结果是什么)和一个获取结果的函数哪个 C# 通过 AndroidJNI 调用的。

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

      【讨论】:

        【解决方案6】:

        听起来很乱。你到底想完成什么,为什么?为什么不单独运行 JVM 和 CLR,通过 REST 调用接口或类似的东西?

        【讨论】:

        • –1 这是评论,不是答案。
        猜你喜欢
        • 2018-02-17
        • 2020-05-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-17
        • 1970-01-01
        相关资源
        最近更新 更多