Callbacks (part 2, at long last)

It’s been a while since I’ve blogged (hey, we’ve been busy :-)). I think the best place to pick up is with the long-promised second part of our article on callbacks. In the first part, I wrote about how to register .NET classes as listeners for Java events (in .NET-to-Java projects). In this post, I’ll talk about how to write Java classes that can be registered as listeners for .NET events (in Java-to-.NET projects).

Let’s start by describing the .NET event generator class:

public delegate void MyEventHandler(string message);

public class EventGenerator
{
   public event MyEventHandler myEvent;

   public void fireEvents(string message)
   {
      myEvent(message);
   }
}

The first thing to note is that our .NET event generator class uses events and delegates, rather than Java-style listener interfaces. This is the typical .NET style, and it can be confusing at first if you’re used to Java listeners. Because this is the encouraged .NET style for callbacks (and the way that the .NET Base Class Library does it), it’s the style we support, too. (In the next callback post, I’ll discuss how to create Java callbacks for .NET classes that use listener-style callback interfaces.) The above code includes a delegate type to represent the event handler, and an event (of that delegate type) as part of the event generator class. There’s also a method used to fire all the registered events. To register an event handler in .NET, we typically create a method:

public void sampleEventHandler(string message)
{
   Console.WriteLine(“sample event handler: message = “ + message);
}

then designate it as a delegate and add it to the event:

myEvent += new MyEventHandler(sampleEventHandler);

then, when fireEvents() is called, all such methods added to myEvent will be called. Since delegates and events don’t exist in Java, we need to handle them in a somewhat different way on the .NET side. Start by proxying EventGenerator and MyEventHandler. Include supporting classes. MyEventHandler is proxied as an interface. It has one method, Invoke(), whose signature is identical to that of the underlying delegate:

void Invoke(string);

Our Java callback class must implement MyEventHandler:

public class CallbackClass implements MyEventHandler
{
   // method implementation required by MyEventHandler
   public void Invoke(String message)
   {
      System.out.println("callback fired: message = " + message);
   }
}

The event myEvent in EventGenerator is proxied on the Java side as a pair of methods, add_myEvent() and remove_myEvent(), which are called to add or remove an event handler.

In our Java code, we instantiate EventGenerator (by instantiating its proxy), then instantiate CallbackClass and register it with myEvent. We can do this because CallbackClass implements the proxied delegate MyEventHandler.

// create the event generator
EventGenerator eg = new EventGenerator();</span>

// register an event handler
eg.add_myEvent(new CallbackClass());

Now, when we call eg.fireEvents(), all the registered event handlers will execute, including all Invoke() methods of registered Java-side event handlers.

Note that, just like in the .NET-to-Java situation, in Java-to-.NET projects the .NET side will suspend when it calls the Java-side callback and wait until it returns, in order to preserve the expected callback semantics. As in the .NET-to-Java situation, this can sometimes lead to deadlock, particularly in cases involving multi-threaded applications and GUIs. In the previous callback post, we discussed how this problem was addressed in .NET-to-Java projects through use of the [AsyncCallback] attribute. In Java-to-.NET projects, we do something similar: Java callback classes, in addition to implementing their proxied delegate interfaces, can also implement the marker interface com.jnbridge.jnbcore.AsyncCallback, which has no methods. When the .NET side calls a Java callback object implementing AsyncCallback, it doesn’t wait for the callback method to return, but rather continues on. This means that values returned by asynchronous callbacks, and exceptions thrown by them, are ignored.

In the third post on callbacks, we’ll discuss what to do if we have a .NET assembly that implements callbacks using a Java-style listener interface.