: code tutorial (v3)

Lesson 8: Calling Methods

Introduction

Calling methods is as easy as accessing fields, which we learned about in the previous lesson.

The proxy function names are as close to the Java names as possible. It is not always possible to have exactly the same name because certain names clash with C# keywords like unchecked or implicit. Java also allows the declaration of methods with the same name as the class, even though they are not constructors. .NET does not allow that. In all these cases the proxy methods will (most likely) have an underscore appended to their Java name (though the exact details are configurable in the JuggerNET code generator).

Passing Arguments

The proxy types closely mirror the inheritance hierarchy of their underlying Java types. This means that you can pass a proxy type instance as a method argument in any place where the underlying Java type would be acceptable. The runtime will also make sure that C# string literals (System.String instances) can be used in place of Java Object instances.

The java.lang.String and java.lang.Object types are treated differently. Arguments of these types map to their .NET counterparts System.String and System.Object.

Java.Util.Hashtable   ht = new Java.Util.Hashtable();
string                key = "foo";
Java.Lang.Object      value = new Java.Lang.Object();

// put a key/value pair in the hashtable
// the key (.NET String) is converted to a Java string on the fly
ht.Put( key, value );

// put another key/value pair in the hashtable
// both key and value are converted on the fly
ht.Put( "FOO", "BAR" );

Returning Results

In Java, what you regard as an object is actually always a reference to an object. All non-primitive method results are therefore implicitly returned by reference. When a Java method is declared to return a java.lang.Object it actually rarely means that you will get back an instance of type java.lang.Object. That declaration just establishes that you will get back an object of a type that is assignable to java.lang.Object.

Please see the following Java code snippet where we use our expert knowledge about the type of the stored value even though the get() function is declared to return Object:

Hashtable ht = new Hashtable();
ht.put( "key", "value" );
String  result = (String)ht.get("key");

This works because the result is returned by reference, allowing a String to be passed in place of an Object.

.NET proxy methods are declared to return the proxy type for the Java return type. The question is: What .NET type will the returned object have? In an ideal world, the returned proxy object would have the proxy type of the returned Java object, but this is not always possible. You might not have the proxy type for the Java type that is returned by the method call.

It would be a poor product if it could not properly deal with this issue. When a proxy method has to return an object, it returns a proxy of the type that is the most derived proxy type in the Java type's class inheritance chain. Again, in a perfect world that will be the exact proxy type for the returned Java type. If that proxy type is not available, the runtime checks whether we have a proxy type for the Java type's super class. If yes, that is the proxy type that is returned. If no, the search continues up the super class chain.

In a worst-case scenario, none of the proxy types for the concrete Java classes are available. If the method was declared to return an interface type, the runtime picks the interface proxy type's Impl type as the best possible return type. This allows you to use the interface contract on the returned instance without having to do anything special.

There is also a special case for java.lang.String instances. Java strings are always converted to .NET System.String instances.

null return values map to null as you would expect.

The key take-away from this discussion should be that you should get back the best available proxy type but not neceessarily the perfect proxy type.

Exceptions

Most of the operations you can perform in Java can result in an exception or an error being thrown. We're introducing the topic of exceptions here even though it also technically applies to accessing fields and invoking constructors. We're discussing the topic in greater detail in its own lesson but we quickly want to touch on it here.

The runtime library will check all Java calls to see whether an exception was thrown on the Java side. If so, it will look for the .NET proxy exception type that matches the Java exception type most closely and throw an instance of that type into .NET. This type mapping follows the same rules as outlined above in the discussion on return types.

Proxy exceptions are all derived from System.Exception so you can catch them via that type if you don't care about the particular exception type.