I'm using Xamarin and need to call through to two static java methods in a jar I've linked to. Here is the java class in the jar:
package com.test;
public class Car {
public static Car makeCar(String name);
public void drawCar(ImageView imageview);
}
I'm not sure how to pass in the parameters for the two methods to the JNI code. The JNIEnv.Call**Method() class takes a JValue[] array for parameters, I'm trying to use it to wrap a C# string and call it all like so:
// C#
string carName = "mustang";
JValue[] paramCarName = new JValue[] {
new JValue(JNIEnv.NewString(carName))
};
IntPtr theClass = JNIEnv.FindClass("com.test.Car");
IntPtr theMethod = JNIEnv.GetMethodID(theClass,
"makeCar", "()Ljava/lang/String;");
IntPtr resultCar = JNIEnv.CallStaticObjectMethod(
theClass, theMethod, paramCarName);
Is that correct usage? I'm having the same problem with calling the second method, which refers to a C# version of android's ImageView:
// C#
// Xamarin provides an ImageView wrapper class.
ImageView imageview = ...;
// Is it alright to use JNIEnv.ToJniHandle here to reference the imageview?
JValue[] paramCarName = new JValue[] {
new JValue (JNIEnv.ToJniHandle (imageview))
};
...
The above currently compiles ok, but I can't run it since I only have the free version. Any info on this would be great as I'm sure I'm misusing this.
Thanks
If you are having trouble getting a jar to bind you may be able to fix the errors with Transforms/Metadata.xml (documentation here). With that file you can suppress bindings for classes or entire packages that cause code generation issues.
If Metadata.xml cannot get the job done, another (less desirable) option is to create your own Java library that wraps the one that won't bind, and exposes only the methods you need to access from C#. Then bind that jar.
But, it sounds like you have the free version of Xamarin (probably why you are trying to avoid jar binding since that requires the paid version) so I'll attempt to fix your JNI code:
// C#
IntPtr classHandle;
IntPtr theClass = JNIEnv.FindClass("com/test/Car", classHandle);
IntPtr theMethod = JNIEnv.GetMethodID(theClass,
"makeCar", "(Ljava/lang/String;)V");
// Just create a new JValue with your C# Android object peer
JNIEnv.CallStaticObjectMethod(classRef, methodRef, new JValue[] {
new JValue(imageView)
});
My JNI is not super-advanced so take the above with some grains of salt. But according to code generated by Xamarin Studio for jar binding, the above should be (close to) correct.
Related
I'm trying to pass a C# object to a WebView2 using AddHostObjectToScript. After not succeeding to retrieve the object from the webview, I've used the debugger and found out that the AddHostObjectToScript call is never completing.
Here is the full code snippet:
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class Example
{
public string Prop { get; set; } = "example";
}
namespace Example_UWP
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
InitializeAsync();
}
public async Task InitializeAsync()
{
await ExampleView.EnsureCoreWebView2Async();
ExampleView.Source = new Uri("http://localhost:3000");
ExampleView.CoreWebView2.OpenDevToolsWindow();
ExampleView.CoreWebView2.AddHostObjectToScript("example", new Example());
}
}
}
The example object is as a result not available in chrome.webview.hostObjects or chrome.webview.hostObjects.sync. The function throws the following error:
The group or resource is not in the correct state to perform the requested operation.
I've tried different alternatives without success, such as:
Keeping a reference to the Example instance in an attribute inside Example_UWP to avoid potential GC
Adding the host object before and after each of the previous steps within InitializeAsync
Wait for the event NavigationCompleted to add the host object.
Wait for 5 seconds before adding the host object.
I'm using Microsoft.Web.WebView2 version 1.0.1264.42
In order to interact with your third-party lib, you need to add a very specific C++ project, Windows Runtime Component (C++/WinRT), to your solution that must be called WinRTAdapter.
Next, you must install a lib to your C++ project from NuGet called Microsoft.Web.WebView2:
After this is done, you must your third-party lib as a reference.
Next, go to your C++ project properties go to Common Properties and choose WebView2:
Here you have to do four changes:
Set Use WebView2 WinRT APIs to No.
Set Use the wv2winrt tool to Yes.
Set Use Javascript case to Yes.
Edit Include filters and add the following ones:
Windows.System.UserProfile
Windows.Globalization.Language
CallJSInterface
CallJSInterface is the name of my third-party's namespace.
You click on OK and build your C++ lib.
After you have built your C++ lib (WinRTAdapter), you must add it to your main project as a reference.
Now, we need to do some changes to be able to invoke the functions from our third-party lib. The first one is to register it. We do it in the same LoadLocalPage() function from before or on NavigationCompleted:
var namespacesName = "CallJSInterface";
var dispatchAdapter = new WinRTAdapter.DispatchAdapter();
core_wv2.AddHostObjectToScript(namespacesName, dispatchAdapter.WrapNamedObject(namespacesName, dispatchAdapter));
Where CallJSInterface is your namespace. After this, you need to register your function in your JS like this:
var callJS;
if (chrome && chrome.webview) {
chrome.webview.hostObjects.options.defaultSyncProxy = true;
chrome.webview.hostObjects.options.forceAsyncMethodMatches = [/Async$/];
chrome.webview.hostObjects.options.ignoreMemberNotFoundError = true;
window.CallJSInterface = chrome.webview.hostObjects.sync.CallJSInterface;
callJS = new CallJSInterface.CallJSCSharp();
}
Where CallJSInterface is one more time your namespace. Now, you can invoke JS like this (the async() is mandatory):
callJS.async().KeepScreenOn()
If you need more details, I have a full tutorial on my website:
https://supernovaic.blogspot.com/2022/10/from-webview-to-webview2-in-uwp.html
I am working on my own scripting language using C# and ANTLR, and I've been able to implement almost everything I wanted.
I know that one can't make a perfect language on themselves, so I wanna build in a way to import functions from C# scripts. For that, i've researched about DLLImport anc calling functions from that, but i just cant seem to get that to work.
I am currently stuck at an EntryPointNotFoundException, however, my system uses object instead of strictly defined types, which threw a PInvoke: cannot return variants exception.
Here's some code i tried:
Program.cs
[DLLImport("mydll.dll", EntryPoint = "main", Charset = Charset.Unicode)]
static extern object main(object[] args)
main(Array.empty<object>())
C# class library used for creatng the dll
public class Test
{
public static object main(object[] args)
{
Console.WriteLine("Test sucessful!");
return 0;
}
}
Be forgiving if i am just overthinking this or don't know something obvious, I am still a pretty inexperienced developer.
For everyone who tries to acheive the same thing, here is the solution based on #PMF's comment:
var asm = Assembly.LoadFrom("YourDLL.dll");
var type = asm.GetType("YourNamespace.YourClass");
var method = type.GetMethod("YourMethod");
object[] args = new object[0];
method.Invoke(Activator.CreateInstance(type, Array.Empty<object>()), args);
Following various examples from MS and elsewhere, I have written this piece of test code...
[ComImport]
[Guid("4AEEEC08-7C92-4456-A0D6-1B675C7AC005")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
and..
private async Task<bool> TestCode()
{
StoreContext Store = StoreContext.GetDefault();
StoreAppLicense licence = await Store.GetAppLicenseAsync();
bool trial = licence.IsTrial;
bool full = licence.IsActive;
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)Store;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
StoreProductResult App = await Store.GetStoreProductForCurrentAppAsync();
StoreProduct p = App.Product; // Title, price
string title = p.Title;
StorePrice price = p.Price;
return true;
}
And I call it with using
bool x = TestCode().Result;
It all compiles and runs, so I presumably have all the right usings and references added. But when run, the line:
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)Store;
stops with the exception..
Unable to cast object of type 'Windows.Services.Store.StoreContext'
to type 'IInitializeWithWindow'
and I have no clue why.
This is a C# program with a UWP wrapper creating an MSIX package.
This seems to be a pretty standard block adapted from various examples from MS.
Within VS 2019, I have associated the program with the store app.
The 'trail' and 'full' variables seem to be populating correctly.
I have called this from various locations, Constructor, random button, etc.
My questions...
Why does the cast throw an exception?
Is this an old way of doing things that no longer applies?
Does associating the package in VS 2019 to the store app make the call to IInitalizeWithWindow redundant?
How do I fix the code so that 'title' and 'price' populate correctly?
Heaps of head bashing and I finally have it working...
Considering that in the last few days there was not a combination/permutation that I did not try, I don't know really the logic of it working now, but anyway..
Within the UWP installer project I associated the project with the App in the Microsoft Store, then I removed the lines:
[ComImport]
[Guid("4AEEEC08-7C92-4456-A0D6-1B675C7AC005")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)Store;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
The rest is now working just fine. Funny as I had already associated the app with the store AND removed the offending lines. I must have done something just a little bit different this time!
I know the basic premise for creating a plug ins for Unity is to create a wrapper for all the classes you want to call from within Unity. However, all of the docs and examples I have found have all expected a corresponding .cpp (if c++) or .mm (if objective-c) file.
However I'm trying to create a plug in for some native iOS code that doesn't contain any source files. All I have access to is four header files and a single .a file. Which I have gotten from here.
Now, because I don't have any .mm files to wrap I'm a bit confused as to how I can go about bringing in these files into Unity so that I can call them from within. Has anyone ever done this before?
Can someone point me to some documentation, or anything that may help me, in bringing in 4 header files and .a file into Unity as a plug in?
Please remember, there are no source files that I have access to. Only the header files themselves.
You don't need source files, even the header files are not needed as long as you know the functions' declarations.
As described in Building Plugins for iOS:
1.) Put your .a file in Assets/Plugins/iOS
2.) Look at the header files to get the function signatures you need e.g.
void RegisterUnityIPodCallbackListener (const char* gameObject, const char* method);
3.) Declare RegisterUnityIPodCallbackListener within a C# class by:
public class IPodHandler {
[DllImport("__Internal")]
private static extern void RegisterUnityIPodCallbackListener (string gameObject, string method);
public static void MyRegisterUnityIPodCallbackListener () {
if (Application.platform == RuntimePlatform.IPhonePlayer) {
RegisterUnityIPodCallbackListener (GAME_OBJECT, METHOD);
}
}
}
4.) Call at an appropriate location:
IPodHandler.MyRegisterUnityIPodCallbackListener ("MyCallbackGameObject", "MyCallbackMethod");
Common pitfalls:
You should not build directory structures within Assets/Plugins/iOS otherwise files don't get copied to the generated Xcode project
Provide a fallback solution when testing in editor player
Here is another guide: How to build Unity3d Plugin for iOS
A few notes if you get an error something like "Undefined symbols for architecture..."
1) Make sure the used architecture in .a-file matches yours (for example armv7)
2) Make sure the .a-file is compiled with libstdc++ (GNU C++ standard library) since Unity requires that.
The Estimote SDK you're are using has ObjectiveC classes. Functions in ObjectiveC can't be called directly via a Unity plugin (either can C++ classes for that matter). The reason is that function names are managled when you compile.
The interface between a Unity C# class and a library must a pure C interface. That is why there is often .mm or .cpp files along side a .a file. These are to wrap a C++ or ObjectiveC class in a pure C wrapper.
Your job is a little easier because most of the Estimote functions seem to be class functions. So you don't need to write wrapper functions to create and delete NSObjects.
Here is an example I wrote, a pure C wrapper around the TestFlightSDK. https://github.com/notoes/TestFlightUnity/blob/master/src/TestFlightCBinding.mm
Notice the extern "C" block, forcing the file the code to be compiled without name mangling. A c function signature and then an ObjectiveC call within the function.
So using the Esitmote ESTBeacon class as an example, the connectToBeacon call could like this:
extern "C" {
void ESTBeacom_Connect()
{
[ESTBeacon connectToBeacon];
}
}
And the .cs function would look like this:
class ESTBeacon {
[DllImport ("__Internal")]
private static extern void ESTBeacon_Connect();
public static void Connect() {
if( Application.platform == RuntimePlatform.IPhonePlayer )
ESTBeacon_Connect()
}
}
Put the .cs, .mm and .a files in Plugins/iOS and you'll be all good.
Use this reference to find out how to pass various data types. http://www.mono-project.com/Interop_with_Native_Libraries
I have created a fairly simple Activity in Mono for Android project:
[Activity(Label = "AndroidApplication1", MainLauncher = true, Icon = "#drawable/icon")]
public class Activity1 : Activity
{
private string value = "intitial";
[Export]
public string GetString()
{
return value;
}
[Export]
public void SetString(string newValue)
{
value = newValue;
}
}
The activity should only serve as a proof-of-concept, hence its simplicity. Now, I'd like to be able to call the method GetString() from the "normal", Java-based Android application.
In Xamarin's docs, I've read about Java to Managed interop, which seems to be exactly what I'm looking for. If I understand it correctly, when Mono for Android app compiles, it generates Java classes that are referred to as Android Callable Wrappers (ACW). I should be then able to call methods on these ACWs from Java-based Android application.
The question is, how exactly do I reference compiled Mono for Android application (the apk file) from the Java-based Android app?
This is where I'm now stuck and was unable to find any concrete examples. There are similar questions here on SO (this one and this one) and some blogposts, but they just boil down to "use ACWs". But how exactly? Maybe I am missing something obvious here, being no Android guy.
What I've tried is to dynamically load the dex file that I yanked from my Mono for Android apk. I've simply put it on the storage card and then tried to use DexClassLoader from Java-based Android app to load it (I've followed this blog post). The ACW class was found, but when I tried to create its instance, I got the following error:
No implementation found for native Lmno/android/Runtime;.register (Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)
I suppose that I have to somehow include Mono for Android runtime to the Java-based app, but I have no idea how.
EDIT:
This is the code I am trying to load the dex with:
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
getClassLoader());
try {
Class<?> classActivity1 = cl.loadClass("androidapplication1.Activity1");
// the following line throws the exception
Object a = classActivity1.newInstance();
Method getStringMethod = classActivity1.getMethod("GetString");
Object result = getStringMethod.invoke(angel);
result = null;
} catch (Exception e) {
e.printStackTrace();
}
EDIT2:
I am now reading here that it should be possible to directly start activities written in Mono for Android from Java. It is still not clear to me how to reference the Mono for Android from Java and Googling yields no relevant hits. Really stumped now.
If I'm understanding correctly what you're trying to do, this isn't really possible. As the error message you got implies, an Activity within a Mono for Android application relies on the Mono runtime in order to function properly. The callable wrapper isn't useful on its own in this case, since it's just a thin Java wrapper class that calls into the Mono runtime. You can actually see the generated callable wrappers yourself if you look in the obj/Debug/android/src folder after you build your project. For example:
package androidapplication9;
public class Activity1
extends android.app.Activity
implements
mono.android.IGCUserPeer
{
static final String __md_methods;
static {
__md_methods =
"n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" +
"";
mono.android.Runtime.register ("AndroidApplication9.Activity1, AndroidApplication9, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", Activity1.class, __md_methods);
}
public Activity1 ()
{
super ();
if (getClass () == Activity1.class)
mono.android.TypeManager.Activate ("AndroidApplication9.Activity1, AndroidApplication9, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "", this, new java.lang.Object[] { });
}
public void onCreate (android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (android.os.Bundle p0);
java.util.ArrayList refList;
public void monodroidAddReference (java.lang.Object obj)
{
if (refList == null)
refList = new java.util.ArrayList ();
refList.add (obj);
}
public void monodroidClearReferences ()
{
if (refList != null)
refList.clear ();
}
}
That said, due to the way Android works, you could have a Java application start an activity that is defined in a Mono for Android application in the same way you'd start an external Java activity. This relies on both applications being installed, of course, but would result in the Mono for Android application and Mono runtime actually starting up to run that activity.
Edit
Updating to answer the questions you posed in your comment. The ExportAttribute basically just gives you some more control in how the ACW gets generated, allowing you to specify that a particular method or field should be present in the ACW and what name it should have. This can be useful when you want to use things like an android:onClick attribute in a layout, for example, where by default the ACW wouldn't contain the method you want to reference.
You can't get much use out of an ACW outside of the context of a Mono for Android application since the Mono runtime wouldn't be present. Code written in C# is executed on top of the Mono runtime, and not translated into Java behind the scenes during compilation or anything like that. At runtime there are then two runtimes going side by side, Dalvik (Android's runtime) and Mono, and the callable wrappers are there to allow the two to communicate back and forth. Because of that, even a Mono for Android class library would still depend on the Mono runtime, so you cannot use it independently of that runtime.
This diagram shows what the architecture looks like, and how the runtimes relate to each other:
Hope this helps clear things up!