Callbacks (part 1)
Customers sometimes come to us saying they have a .NET-to-Java project, and asking how they can pass a real .NET object to a method in the proxied Java object. Often they’ll subclass a proxy or implement a proxied interface, but when they actually pass the .NET object to the proxy, they get an exception. I explain that what they really are trying to do is to implement a callback: the .NET code has called the Java side, and during that execution, the Java code “calls back” some .NET object that’s previously been registered as a “callback.” If you’re familiar with Java listener interfaces, you’ll know how this all works.
Callbacks are easy to implement. In this post, we’ll discuss how to implement a callback in a .NET-to-Java project. In the next post, we’ll discuss callbacks in Java-to-.NET projects.
Let’s start with a Java class that allows other classes to register themselves as listeners for particular events, then notifies those listeners that those events occur. All listeners must implement a particular listener interface, which must have at least one method that will be executed when the event occurs. The method can take parameters, and can return values or throw exceptions. Here’s a listener interface:
// this is Java public interface MyListener { public void fireEvent(); }
Now that we’ve got the listener, here’s the event generator class we’ll be working with:
// this is Java public class MyEventGenerator { private static java.util.ArrayList listeners = new java.util.ArrayList(); public static void registerListener(MyListener aListener) { listeners.add(aListener); } public static void fireListeners() { for(int i = 0; i < listeners.size(); i++) { MyListener aListener = (MyListener) listeners.get(i); aListener.fireEvent(); } } }
If we’ve proxied the MyListener
and MyEventGenerator
classes, we can implement .NET classes that can register themselves with MyEventGenerator
as listeners:
// this is C# [Callback("MyListener")] public class DotNetListener : MyListener { public void fireEvent() { Console.WriteLine("event fired!"); } }
Note that DotNetListener
implements the proxied MyListener
interface. Also note that it has the [Callback]
attribute. These both are essential. The [Callback]
attribute’s class is really com.jnbridge.jnbcore.CallbackAttribute
, so you’ll need to make sure you’ve imported that namespace into your program (using the import
(if VB.NET) or using
(if C#) keywords). The [Callback]
attribute needs the fully-qualified name of the interface, so if the interface is in a package, or if it’s nested inside another class, you’ll need to supply the entire name. Also note the method fireEvent()
that any class implementing MyListener
must implement.
Now we can use DotNetListener
wherever a proxy expects a MyListener
:
// this is C# MyEventGenerator.registerListener(new DotNetListener()); MyEventGenerator.fireListener();
The console will now print "event fired!"
.
Callbacks can take parameters, return values, or throw exceptions, but the parameters, return values, and exceptions must be of classes that the Java side “understands,” which means they need to be proxies, primitives, strings, or arrays of those.
A callback class can implement multiple listener interfaces, but if it does, you’ll need to annotate the callback class with a [Callback]
attribute for each listener interface it implements.
One subtlety to be aware of is that when the Java side calls the callback, the Java thread doing the calling suspends until the callback returns. This is done to preserve the expected callback semantics. Since the Java thread has suspended, it’s likely that the .NET thread that originally called the Java side has suspended, since it’s waiting for the Java side to return. Usually this is fine and expected, but sometimes, as when you’re using the callback inside a Windows Form, it can lead to deadlock. To get around this problem, use an asynchronous callback, which is designated using the [AsyncCallback]
attribute rather than the [Callback]
attribute. When an asynchronous callback is called, the Java-side thread doesn’t wait around until the callback returns, but goes on its way. This avoids the deadlock issue. The tradeoff is that since the Java side doesn’t wait around for the result of the callback, asynchronous callbacks can’t return values or throw exceptions.
That’s all there is to callbacks! In our next post, we’ll explain how callbacks work in the opposite direction: in Java-to-.NET projects.