Hosting managed code and garbage collection - c#

I have a C++ out-of-process COM server that hosts a lot of C# code to support the API exposed by the C++ COM objects.
For a variety of reasons, I am considering eliminating the C++ portion of my solution. However, because of constraints outside of my control I have to retain the out-of-process COM server. Microsoft does have a canonical example of this here.
Looking at this example there is something I don't understand. Before the message loop starts, a timer is created to call GC.Collect every 5 seconds. The only mention of this that I can find indicates it's to ensure the COM objects are released in a reasonable timeframe. I'm a little confused about this...does my C++ host currently call GC.Collect automatically? I'm certainly not doing it. And yet I am creating managed objects (with COMVisible(true) as COM objects in the C++ code. Does that mean I should be calling GC.Collect every 5 seconds now? If not, why do I need to call it in this new C# out of process server. Is that to make up for the automatic process that cleans up unreferenced COM objects in a normal C++ application? (Which I assume is happening sometime during the message loop.)
Calling GC.Collect every 5 seconds seems like it could be a bad idea. Am I wrong to worry? Is there some other method by which I could achieve the same results?
I am using .NET 4.5 and Visual Studio 2012.

IMO, the easiest way to create a COM out-of-proc server in C# is to use a DLL surrogate process.
You're still limited to dual interfaces (ComInterfaceType.InterfaceIsDual), and you'd need to register the generated type library (and do it as part of deployment, too):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe ManagedServer.dll /codebase /tlb
This will allow you to utilize the COM type library marshaller, because you don't have a dedicated COM proxy/stuf DLL for your C# COM objects.
Make sure to use the correct RegAsm.exe binary, depending on the target bit-ness of your ManagedServer.dll assembly. The above assumes x86 code.
Here is a complete working template example. It takes care of the surrogate registration:
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
namespace ManagedServer
{
[ComVisible(true), Guid("1891CF89-1282-4CA8-B7C5-F2608AF1E2F1")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IManagedComObject
{
string ComMethod(string data);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IManagedComObject))]
[Guid("989162CD-A6A6-4A7D-A7FB-C94086A4E90A")]
[ProgId("Noseratio.ManagedComObject")]
public class ManagedComObject : IManagedComObject
{
// public constructor
public ManagedComObject()
{
}
// IManagedComObject
public string ComMethod(string data)
{
return data;
}
// registration
[ComRegisterFunction()]
public static void Register(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(#"AppID\" + guid))
{
appIdKey.SetValue("DllSurrogate", String.Empty);
}
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(#"CLSID\" + guid))
{
appIdKey.SetValue("AppId", guid);
}
}
[ComUnregisterFunction()]
public static void Unregister(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.OpenSubKey(#"AppID\" + guid, writable: true))
{
if (appIdKey != null)
appIdKey.DeleteValue("DllSurrogate", throwOnMissingValue: false);
}
Registry.ClassesRoot.DeleteSubKeyTree(#"CLSID\" + guid, throwOnMissingSubKey: false);
}
}
}
By default, the objects will be created in MTA apartment, so the interface methods may possibly be called on any thread, you'd need to implement thread safety.
If you need an STA thread with message pump inside the surrogate process for your objects, you could do that explicitly by implementing a factory singleton and using CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream to export objects outside the STA thread (this might be related).
Another point, your COM client code should use CLSCTX_LOCAL_SERVER when creating this an instance of ManagedComObject. This is not the case with Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject")), which apparently uses CLSCTX_ALL. This can be easily taken care of:
using System;
using System.Runtime.InteropServices;
namespace Client
{
class Program
{
static void Main(string[] args)
{
// dynamic obj = Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject"));
dynamic obj = ComExt.CreateInstance(
Type.GetTypeFromProgID("Noseratio.ManagedComObject").GUID,
localServer: true);
Console.WriteLine(obj.ComMethod("hello"));
}
}
// COM interop
public static class ComExt
{
const uint CLSCTX_LOCAL_SERVER = 0x4;
const uint CLSCTX_INPROC_SERVER = 0x1;
static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
[DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
static extern void CoCreateInstance(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
uint dwClsContext,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object rReturnedComObject);
public static object CreateInstance(Guid clsid, bool localServer)
{
object unk;
CoCreateInstance(clsid, null, localServer ? CLSCTX_LOCAL_SERVER : CLSCTX_INPROC_SERVER, IID_IUnknown, out unk);
return unk;
}
}
}

Related

When GC.KeepAlive(this) is needed when doing P/Invoke on unmanaged resources?

I have a TestNet wrapper for a native component. The native component exposes a blocking TestNative::Foo() that communicates with managed part through calling managed callbacks and a weak GCHandle that is used to retrieve the reference to the .NET wrapper and provides a context. The GCHandle is weak since the .NET wrapper is meant to hide the fact that is handling unmanaged resources to user and deliberately doesn't implement the IDisposable interface: being non weak it would prevent TestNet instances from being collected at all, creating a memory leak. What's happening is that in Release build only the garbage collector will collect reference to .NET wrapper while executing the managed callback, even before both TestNative::Foo() and surprisingly TestNet::Foo() unblocks. I understood the problem my self and I can fix it by issuing a GC.KeepAlive(this) after the P/Invoke call but since the knowledge of this is not very widespread, it seems a lot of people are doing it wrong. I have few questions:
Is GC.KeepAlive(this) always needed in a managed method if last instruction is a P/Invoke call on unmanaged resources or it's just needed in this special case, namely the switch to managed execution context while marshaling the managed callback from native code? The question could be: should I put GC.KeepAlive(this) everywhere? This old microsoft blog (original link is 404, here is cached) seems to suggest so! But this would be game changer and basically it would mean that most people never did P/Invoke correctly, because this would require reviewing most P/Invoke calls in wrappers. Is there for example a rule that say that garbage collector (EDIT: or better the finalizer) can't run for objects that belong to the current thread while execution context is unamanaged (native)?
Where I can find proper documentation? I could find CodeAnalysis policy CA2115 pointing to generically use GC.KeepAlive(this) any time a unmanaged resource is accessed with P/Invoke. In general GC.KeepAlive(this) seems to be very rarely needed when dealing with finalizers.
Why is this happening only in Release build? It looks like an optimization but not being needed at all in Debug build hides an important behavior of the garbage collector.
NOTE: I have no problem with delegates being collected, that is a different issue which I know how to handle properly. The issue here is with objects holding unmanaged resources being collected when P/Invoke calls are not finished yet.
It follows code that clearly manifest the problem. Creates a C# console application and a C++ Dll1 project and build them in Release mode:
Program.cs:
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var test = new TestNet();
try
{
test.Foo();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
class TestNet
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void Callback(IntPtr data);
static Callback _callback;
IntPtr _nativeHandle;
GCHandle _thisHandle;
static TestNet()
{
// NOTE: Keep delegates references so they can be
// stored persistently in unmanaged resources
_callback = callback;
}
public TestNet()
{
_nativeHandle = CreateTestNative();
// Keep a weak handle to self. Weak is necessary
// to not prevent garbage collection of TestNet instances
_thisHandle = GCHandle.Alloc(this, GCHandleType.Weak);
TestNativeSetCallback(_nativeHandle, _callback, GCHandle.ToIntPtr(_thisHandle));
}
~TestNet()
{
Console.WriteLine("this.~TestNet()");
FreeTestNative(_nativeHandle);
_thisHandle.Free();
}
public void Foo()
{
Console.WriteLine("this.Foo() begins");
TestNativeFoo(_nativeHandle);
// This is never printed when the object is collected!
Console.WriteLine("this.Foo() ends");
// Without the following GC.KeepAlive(this) call
// in Release build the program will consistently collect
// the object in callback() and crash on next iteration
//GC.KeepAlive(this);
}
static void callback(IntPtr data)
{
Console.WriteLine("TestNet.callback() begins");
// Retrieve the weak reference to self. As soon as the istance
// of TestNet exists.
var self = (TestNet)GCHandle.FromIntPtr(data).Target;
self.callback();
// Enforce garbage collection. On release build
self = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("TestNet.callback() ends");
}
void callback()
{
Console.WriteLine("this.callback()");
}
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr CreateTestNative();
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeTestNative(IntPtr obj);
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void TestNativeSetCallback(IntPtr obj, Callback callback, IntPtr data);
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void TestNativeFoo(IntPtr obj);
}
}
Dll1.cpp:
#include <iostream>
extern "C" typedef void (*Callback)(void *data);
class TestNative
{
public:
void SetCallback(Callback callback1, void *data);
void Foo();
private:
Callback m_callback;
void *m_data;
};
void TestNative::SetCallback(Callback callback, void * data)
{
m_callback = callback;
m_data = data;
}
void TestNative::Foo()
{
// Foo() will never end
while (true)
{
m_callback(m_data);
}
}
extern "C"
{
__declspec(dllexport) TestNative * CreateTestNative()
{
return new TestNative();
}
__declspec(dllexport) void FreeTestNative(TestNative *obj)
{
delete obj;
}
__declspec(dllexport) void TestNativeSetCallback(TestNative *obj, Callback callback1, void * data)
{
obj->SetCallback(callback1, data);
}
__declspec(dllexport) void TestNativeFoo(TestNative *obj)
{
obj->Foo();
}
}
The output is consistently:
this.Foo() begins
TestNet.callback() begins
this.callback()
this.~TestNet()
TestNet.callback() ends
TestNet.callback() begins
System.NullReferenceException: Object reference not set to an instance of an object.
If one uncomment the GC.KeepAlive(this) call in TestNet.Foo() the program correctly never ends.
Summarizing very useful comments and research done:
1) Is GC.KeepAlive(this) always needed in a managed instance method if last instruction is a P/Invoke call using unmanaged resources hold by the instance?
Yes, if you don't want the user of the API to have last responsibility of holding a non-collectible reference for the instance of the managed object in pathological cases, look the example below. But it's not the only way: HandleRef or SafeHandle techiniques can also be used to prolong the lifetime of a managed object when doing P/Invoke Interop.
The example will subsequently call native methods through managed instances holding native resources:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
new Thread(delegate()
{
// Run a separate thread enforcing GC collections every second
while(true)
{
GC.Collect();
Thread.Sleep(1000);
}
}).Start();
while (true)
{
var test = new TestNet();
test.Foo();
TestNet.Dump();
}
}
}
class TestNet
{
static ManualResetEvent _closed;
static long _closeTime;
static long _fooEndTime;
IntPtr _nativeHandle;
public TestNet()
{
_closed = new ManualResetEvent(false);
_closeTime = -1;
_fooEndTime = -1;
_nativeHandle = CreateTestNative();
}
public static void Dump()
{
// Ensure the now the object will now be garbage collected
GC.Collect();
GC.WaitForPendingFinalizers();
// Wait for current object to be garbage collected
_closed.WaitOne();
Trace.Assert(_closeTime != -1);
Trace.Assert(_fooEndTime != -1);
if (_closeTime <= _fooEndTime)
Console.WriteLine("WARN: Finalize() commenced before Foo() return");
else
Console.WriteLine("Finalize() commenced after Foo() return");
}
~TestNet()
{
_closeTime = Stopwatch.GetTimestamp();
FreeTestNative(_nativeHandle);
_closed.Set();
}
public void Foo()
{
// The native implementation just sleeps for 250ms
TestNativeFoo(_nativeHandle);
// Uncomment to have all Finalize() to commence after Foo()
//GC.KeepAlive(this);
_fooEndTime = Stopwatch.GetTimestamp();
}
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr CreateTestNative();
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeTestNative(IntPtr obj);
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void TestNativeFoo(IntPtr obj);
}
}
For the native call to be always safe we expect finalizer to be called only after Foo() return. Instead we can easily enforce violations by manually invoking garbage collection in a background thread. Output follows:
Finalize() commenced after Foo() return
WARN: Finalize() commenced before Foo() return
Finalize() commenced after Foo() return
Finalize() commenced after Foo() return
Finalize() commenced after Foo() return
WARN: Finalize() commenced before Foo() return
Finalize() commenced after Foo() return
2) Where I can find documentation?
Documentation of GC.KeepAlive() provides an example very similar to the managed callback in the original question. HandleRef has also very interesting considerations about lifecycle of managed objects and Interop:
If you use platform invoke to call a managed object, and the object is
not referenced elsewhere after the platform invoke call, it is
possible for the garbage collector to finalize the managed object.
This action releases the resource and invalidates the handle, causing
the platform invoke call to fail. Wrapping a handle with HandleRef
guarantees that the managed object is not garbage collected until the
platform invoke call completes.
Also link[1] found by #GSerg explains when an object is eligible for collection, pointing that this reference is not in the root set, allowing it to be collected also when instance method has not returned.
3) Why is this happening only in Release build?
It's an optimization and can happen also in Debug build, with optimization enabled, as pointed by #SimonMourier. It's not enabled by default also in Debug because it could prevent debugging of variables in the current method scope, as explained in these other answers.
[1]
https://devblogs.microsoft.com/oldnewthing/20100810-00/?p=13193?

Send status update to C# from C++ using delegate?

I have a C++ function which performs a number of tasks "PerformJob()". I have a C# wrapper which calls PerformJob(). The job takes a while and I would like the C++ method to send "status updates" back up to the calling C# class. The C++ code is not exposed to the C# class. Adding the C# project as a reference would cause a circular dependency.
I've attempted to pass a delegate through as a parameter but I'm not familiar enough with C++ syntax to make this work (or if it is even possible?). Is there an appropriate way to pass a delegate into C++ as a parameter? Is there a better method to facilitate this communication? I'd like to avoid a dllimport, as I only need to receive updates from this one class.
CSharpClass.cs:
public delegate void CallbackDelegate(ref string status);
public CallbackDelegate jobStatusDelegate;
public void UpdateJobStatus(ref string status)
{
Job.JobStatus = status;
}
public void StartJob()
{
jobStatusDelegate = new CallbackDelegate(UpdateJobStatus);
CPlusClass jobHelper = new CPlusClass();
jobHelper.PerformJob(jobStatusDelegate);
}
CPlusClass.h:
public ref class CPlusClass
{
public:
void PerformJob(delegate del); // is there c++ delegate type?
};
CPlusClass.cpp:
void CPlusClass::PerformJob(delegate del)
{
// ....
}
C++ doesn't have delegates, but C++/CLI does, and based on ref class your code already is C++/CLI.
Delegates work much like any other reference type, they get stored as a tracking handle with the ^.
In order to not create a dependency on the C#, which would be in the wrong direction, I suggest you use a System::Action<System::String^>^ instead of defining your own delegate type.
public ref class CPlusClass
{
public:
void PerformJob(System::Action<System::String^>^ del)
{
del->Invoke(gcnew String("Hello World"));
}
};

C# How do you get an instance of a COM interface

I've been doing quite a bit of googling trying to find the standard way to get an instance of a COM interface.
Microsoft provides an example of this in their article COM Interop Part 1: Client Tutorial:
// Create an instance of a COM coclass:
FilgraphManager graphManager = new FilgraphManager();
// See if it supports the IMediaControl COM interface.
// Note that this will throw a System.InvalidCastException if
// the cast fails. This is equivalent to QueryInterface for
// COM objects:
IMediaControl mc = (IMediaControl) graphManager;
// Now you call a method on a COM interface:
mc.Run();
However, it appears as though they are instantiating a COM object and casting it to a COM interface.
For the interface I am interested in, IDesktopWallpaper, there does not seem to be an implementing COM object to instantiate.
An example I found here defines some class that gets instantiated and then casts it to the interface the same way that the msdn example does:
[ComImport, Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
internal class IDesktopWallpaper
{
}
[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B"), //B92B56A9-8B55-4E14-9A89-0199BBB6F93B
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface DesktopWallpaperInterface
{
// declared members
}
I'm not understanding what the instantiated object is. It seems like an arbitrary object, it has a GuidAttribute which seems to indicate that it is an actual COM object.
Another example i found here System.Type and System.Runtime.InteropServices.Marshal to instantiate an object and then casts it to the interface:
IntPtr ptrRet;
SHGetMalloc(out ptrRet);
System.Type mallocType = System.Type.GetType("IMalloc");
Object obj = Marshal.GetTypedObjectForIUnknown(ptrRet,mallocType);
IMalloc pMalloc = (IMalloc)obj;
This method seems to be requesting a pointer to an existing instance of the interface. I can't find any methods like SHGetMalloc for IDesktopWallpaper in the Windows Shell documentation.
Question
So, long story short, what's the standard way to get an instance of a COM interface?
In the event that there is no one-size-fits-all solution, what are the standard ways that one can use to get an instance of a COM interface and in what circumstance is each of these ways most useful?
Edit
After downloading the Windows 10 SDK and referencing that against the Requirements section of the IDesktopWallpaper interface documentation, I have discovered that you can look up the MIDL from Shobjidl.h and use that in the GuidAttribute for your interface declaration and then look up the CLSID from Shobjidl.idl and use that in conjunction with Type.GetTypeFromCLSID(Guid) and Activator.CreateInstance(Type) to get an instance of an object that implements IDesktopWallpaper.
I also see now that the CLSID is what is used in the second method listed above for the GuidAttribute of the seemingly arbitrary object. It seems like this method allows you to mimic managed instantiation of the object by instantiating the class and then casting the instance to the COM interface.
However
I am still interested to know if this is the best way to do this and what pros and cons may be associated with this method vs others.
You can get a pointer to a COM object reference by a variety of methods:
P/Invoke CoCreateInstance
P/Invoke CLSIDFromProgID → CoCreateInstance
P/Invoke IRunningObjectTable.GetObject
Type.GetTypeFromCLSID → Activator.CreateInstance
Type.GetTypeFromProgID → Activator.CreateInstance
new SomeType() where SomeType is marked with ComImport
Activator.CreateInstance and new SomeType() eventually hit CoCreateInstance (if they do not get intercepted by various in-app-domain stuff). Calls to CoCreateInstance for an out-of-process server will eventually hit IRunningObjectTable with a class moniker (I think). The best option depends on what you are trying to do:
For an in-process server, just use ComImport
For an out-of-process server that is not implemented in .Net, ComImport will work, I would prefer to call CoCreateInstance to pass the right CLSCTX.
For a .net implemented out-of-process server implemented in .Net, you must call CoCreateInstance directly to avoid the "optimizations" added by ComImport that would result in the server being run in-process
If you are dealing with a moniker, use IRunningObjectTable
If you are starting out with a ProgID rather than a CLSID, use either CLSIDFromProgID or Type.GetTypeFromProgID
Regardless of how we get a reference to an object, we start out with IUnknown (object in .Net), and then have to call IUnknown->QueryInterface to get a pointer to a particular interface. Calling QueryInterface in .Net is effected by casting to an interface marked as ComVisible (and usually annotated with GuidAttribute).
In the example you named, you would end up with:
// based off of https://bitbucket.org/ciniml/desktopwallpaper
[ComImport]
[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDesktopWallpaper
{
void SetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.LPWStr)] string wallpaper);
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetMonitorDevicePathAt(uint monitorIndex);
[return: MarshalAs(UnmanagedType.U4)]
uint GetMonitorDevicePathCount();
[return: MarshalAs(UnmanagedType.Struct)]
Rect GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
void SetBackgroundColor([MarshalAs(UnmanagedType.U4)] uint color);
[return: MarshalAs(UnmanagedType.U4)]
uint GetBackgroundColor();
void SetPosition([MarshalAs(UnmanagedType.I4)] DesktopWallpaperPosition position);
[return: MarshalAs(UnmanagedType.I4)]
DesktopWallpaperPosition GetPosition();
void SetSlideshow(IntPtr items);
IntPtr GetSlideshow();
void SetSlideshowOptions(DesktopSlideshowDirection options, uint slideshowTick);
void GetSlideshowOptions(out DesktopSlideshowDirection options, out uint slideshowTick);
void AdvanceSlideshow([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.I4)] DesktopSlideshowDirection direction);
DesktopSlideshowDirection GetStatus();
bool Enable();
}
[ComImport]
[Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
public class DesktopWallpaper
{
}
[Flags]
public enum DesktopSlideshowOptions
{
None = 0,
ShuffleImages = 0x01
}
[Flags]
public enum DesktopSlideshowState
{
None = 0,
Enabled = 0x01,
Slideshow = 0x02,
DisabledByRemoteSession = 0x04
}
public enum DesktopSlideshowDirection
{
Forward = 0,
Backward = 1
}
public enum DesktopWallpaperPosition
{
Center = 0,
Tile = 1,
Stretch = 2,
Fit = 3,
Fill = 4,
Span = 5,
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
An example use of which would be:
public partial class Form1 : Form
{
private IDesktopWallpaper Wallpaper;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Wallpaper = (IDesktopWallpaper)new DesktopWallpaper();
uint monitorCount = Wallpaper.GetMonitorDevicePathCount();
for (uint i = 0; i < monitorCount; i++)
{
lbMonitors.Items.Add(Wallpaper.GetMonitorDevicePathAt(i));
}
}
private void lbMonitors_SelectedValueChanged(object sender, EventArgs e)
{
var path = (string)lbMonitors.SelectedItem;
tbWallpaper.Text = Wallpaper.GetWallpaper(path);
}
}
Which produces the form:

How to pass a JNI C# class into Java or handle this situation?

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

How to create managed object in unmanaged C++ while wrapping it for JNA between Java and C#?

I am trying to make a callback interface between C# and Java using JNA.
C# <--CLI--> Visual C++ 2010 <--JNA--> Java
Between Java and C++ I am using unmanaged structures to get callback functionality. In C++ I am trying to wrap structure, that has callback pointers, into managed object.
Between Java and C++ everything works until I'm trying to use gcroot for managed object generation in unmanaged code.
UPDATE it even fails without gcroot. Just with "Logger^ logger = gcnew Logger(logStruct);"
My current solution is as follows:
Java
LoggerStruct.java
package jnatest;
import com.sun.jna.Callback;
import com.sun.jna.Structure;
import java.util.logging.Logger;
public class LoggerStruct extends Structure {
private Logger logger;
public interface GetLevelCallback extends Callback {
int callback();
}
public GetLevelCallback getLevel;
public LoggerStruct(Logger log) {
super();
this.log = log;
getLevel = new GetLevelCallback() {
public int callback() {
return logger.getLevel().intValue();
}
}
setFieldOrder(new String[] {"getLevel"});
}
}
ITestLib.java
package jnatest;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface ITestLib extends Library {
ITestLib INSTANCE = (ITestLib) Native.loadLibrary("JNATestC", ITestLib.class);
int callbackTest(LoggerStruct logStruct);
}
Main.java
package jnatest;
import com.sun.jna.NativeLibrary;
import java.util.logging.Logger;
import java.util.logging.FileHandler;
public class MainClass {
public static void main(String[] args) throws Exception {
NativeLibrary.addSearchPath("JNATestC", "C:\\JNATest");
Logger log = Logger.getLogger("Test");
FileHandler fileTxt = new FileHandler("Logging.txt");
log.addHandler(fileTxt);
LoggerStruct logStruct = new LoggerStruct(log);
ITestLib.INSTANCE.callbackTest(logStruct);
}
}
C++
JNATestC.h
#pragma once
extern "C" {
struct LoggerStruct {
int (*getLevel)();
}
__declspec(dllexport) void callbackTest(LoggerStruct * logStruct);
}
namespace JnaWrapperTypes {
public ref class Logger { // "public ref" because I have to use it in C# as well
private:
LoggerStruct * logStruct;
public:
Logger(LoggerStruct * logStruct);
~Logger() {}
int getLevel();
};
}
JNATestC.cpp
#include "stdafx.h"
#include <vcclr.h>
#include "JNATestC.h"
namespace JnaWrapperTypes {
Logger::Logger(LoggerStruct * logStruct) {
this->logStruct = logStruct;
}
Logger::getLevel() {
return logStruct->getLevel();
}
}
using namespace JnaWrapperTypes;
using namespace StaticCSharpNamespace; // Just an example. Not existing C# lib.
extern "C" {
__declspec(dllexport) void callbackTest(LoggerStruct * logStruct) {
int level = logStruct->getLevel();
gcroot<Logger^> logger = gcnew Logger(logStruct); // IF I ADD "gcroot" FOR "Logger" THEN WHOLE INVOKE FAILS
level = logger->getLevel();
StaticCSharpClass::staticMethod(logger); // I want to pass Managed object to C# later
gcroot<System::String^> str = gcnew System::String(""); // This doesn't generate error
}
}
I wrote these on the fly. I hope these validate as well.
What am I doing wrong? For example if I use...
gcroot<System::String^> str = gcnew System::String("");
...everything works just fine.
Is there another way to pass managed object to C#?
Log for this error LOG
UPDATE
It seems that anykind of my own Class usage will head me to failure.
UPDATE
Anykind of my own Managed object or function use heads me to failure.
UPDATE
StaticCSharpClass::staticMethod(); fails as well. Looks like all operations related to Managed objects fail.
UPDATE
If I invoke the same method from .NET, everything works fine.
just to make this problem more findable
Internal Error (0xe0434352)
Should have googled for error "Internal Error (0xe0434352)".
http://jira.talendforge.org/browse/TDI-19427
It leads to point that I have to register the dll for GAC (Global Assembly Cache), because Java searches only GAC and Application Base directories for dll. And because Java.exe paths aren't configurable.
== Solution ==
Use post build event to register assembly for GAC:
gacutil /i "$(TargetPath)"
Great monologue! =)

Categories