Вопрос

I'm trying to call a Java method from C#, it's called like this from 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) {
            }
        });

The first two parameters are ok, but how do I pass the EgamePayListner through in C#? Simply creating a C# class with the same functions won't work...

Here's what I'm doing currently:

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

Obviously that's wrong, how can I handle this situation so that I can get notified back in C# land when those functions are fired?

Это было полезно?

Решение 2

if you are in a context of Unity3D, a better way to interact from Java native code to Unity3D C# script would be the calling the UnitySendMessage method. You can call this method in you Java code, and a message will be sent to C#, so you can get a specified method executed in C#.

You could add a gameObject in your Unity scene and create a MonoBehavior script which contains these three methods (paySucess(string message), payFailed(string message) and payCancel(message)). Then attach the new created script to the gameObject (let us assume the name of this gameObject to be "PayListener") and make sure the gameObject existing in your scene when the Java code be executed (you can call DontDestroyOnLoad on the gameObject in Awake, for example).

Then, in your Java code, just write like these:

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

It would prevent the java file to build successfully without the com.unity3d.player.UnityPlayer class. You could find the /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar file and add it as a dependency library for your java project. Then you can build, generate and use the new jar file without error.

Because of the UnitySendMessage method can only take 1 parameter, so you might have to connect the payFailed result alias and errorInt by a strategy, and parse it back in Unity C# side.

By using the new built jar file and load it as a AndroidJavaClass or AndroidJavaObject, the corresponding method in the PayListener script should be called when the one in Java is get called.

For documentation of the Android plugin with UnitySendMessage, you can visit the official guide of how to implement a Android JNI plugin here, in the example 3.

Другие советы

You can link these projects together in VS201X via references. From here, you should be able to fill in the other layers(JNI/Java), and then start passing your pointers(as a long) around the system and invoking your functions.

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 Layer - DLL C++ project w/ CLR support(/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);
}

Here is JNI Field Descriptor

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[]

Method descriptors make use of the fields descriptors and describe the structure of a Java method. There are no spaces between the field descriptors in a method descriptor.

Apart from the void return type which is denoted by V, all other return types use the field descriptor. The table below describes the Java method declaration and the corresponding JNI descriptor. The JNI method descriptor is used when calling a Java method from C# via JNI.

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

The first thing that needs to be done is to create a dictionary object that will contain all of the parameters to pass to the Java Virtual Machine. In the example below, I am doing the minimum of setting the class path that will tell the JVM where to look for the classes and packages.

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

Once the JVM parameters have been assigned to the dictionary object, an instance of JavaNativeInterface can be created. Once created, the method LoadJVM needs to be called with the JVM parameters, and this will then load up the Java Virtual Machine. Once loaded, the user calls the method to instantiate the Java object (note that the use of the method InstantiateJavaObject is optional as the user may just want to call a static method, in which case, this method does not need to be called; however, it will not case any harm).

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

Once the JVM has been loaded and a class instantiated, the user may call any method they wish. First create an object list that will contain all of the parameters to pass into the Java method. As it is an object list, it can hold parameters of different types as everything inherits from an object.

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

Next, simply call the generic method CallMethod passing in the return type of the Java method as the template type. In the example below, I am calling CallMethod which means that the return type of my Java method that I want to call is a string.

Next, pass in the name of the Java method to call and the method descriptor (see above); finally, pass in the list of all of the parameters. (Note: If no parameters are required, then pass in an empty list.)

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

Well, I guess that wraps it all up, but remember that there is so much more you can do with JNI. The test application was just a quick and dirty way to demonstrate the basics of the JNI component. For a full understanding of JNI.

Either you can get more on this link

Have a look at http://jni4net.sourceforge.net/. I have successfully used it to communicate between CLR and JVM. Java application example that calls .NET classes can be found here. Events (bound to java listener interfaces) are supported too.

I ended up solving this problem myself, the other answers posted here while good unfortunately did not address the fact the problem I was facing was in Unity.

The way I solved it was by writing a custom listener class in Java with a 'GetResult' function that returned a string explaining what the outcome was (i.e. which function was called and with what result) and a get function for the result which C# called through AndroidJNI.

After making the purchase call it was a matter of calling the get function from C# until I had a result.

Sounds like a mess. What are you trying to accomplish exactly and why? Why not just run the JVM separately and CLR separately, interface over REST calls or something similar?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top