Lesson 10: Casting Between Proxy Types
Introduction
No, we're not talking about the latest theatrical broadway production, this is all about dealing with the type system represented by the proxy classes and interfaces. Let's take a quick look at a common snippet of Java to illustrate the issues:
InitialContext ictx = new InitialContext(); MyInterface ifc = (MyInterface)ictx.lookup( "scope/ifc" ); MyOtherInterface ifc2 = null; // the returned instance might also implement another interface if( ifc instanceof MyOtherInterface ) ifc2 = (MyOtherInterface)ifc;
How would you do this with proxy types? Here's a C# snippet that will probably work (we'll go into the reasons for the "probably" a little later):
InitialContext ictx = new InitialContext(); MyInterface ifc = (MyInterface)ictx.lookup( "scope/ifc" ); MyOtherInterface ifc2 = null; if( ifc is MyOtherInterface ) ifc2 = (MyOtherInterface)ifc;
This looks very nice and easy to use, but why did we use this disconcerting word "probably"? The reason is that
whether or not this works depends on the set of proxy types that you have. The lookup()
method is
declared to return Object
, so it doesn't provide us a lot of help in figuring out what is the best proxy
type to return from an invocation. Consequently, we inspect the Java type of the returned object and go looking for
the "best" .NET proxy type that is available.
To that effect, the JuggerNET managed runtime maintains a mapping between Java and available .NET proxy types. If we find an exactly matching proxy type, we will use it and everything works as expected because that proxy type will implement both proxy interfaces. But what if we don't have an exactly matching proxy type? Then we have to continue looking for a "best possible" match and this is where we get into very ambiguous territory.
In the above example, we have some out-of-band expert knowledge about the fact that the returned object implements
two different interfaces and we use that knowledge in the code we are writing. The proxy classes and the runtime library
don't have that expert knowledge. If no exact proxy type is available, the runtime has to pick a "decent" match
and go with it. It might for example decide to return a proxy interface Impl
type for the MyInterface
type
or it might decide to return a proxy interface Impl
type for the MyOtherInterface type. The key word here is
"or". It will pick one of the two choices and as a result of that choice one of the above casts or type tests
will fail. So how do you avoid this problem?
The From()
Method
Every proxy type declares a special static casting method called From()
that is guaranteed to work because it
inspects the Java side's type information and allows you to provide your expert knowledge, just like in the Java snippet.
Here's what the guaranteed-to-work snippet looks like:
InitialContext ictx = new InitialContext(); MyInterface ifc = MyInterfaceImpl.From( ictx.lookup( "scope/ifc" ) ); MyOtherInterface ifc2 = null; ifc2 = MyOtherInterfaceImpl.From( ifc );
You might have noticed the Impl
types in the above snippet. Interface types are not allowed to
contain static members in .NET. That's why every proxy interface type has a corresponding Impl
class type
which declares its static members, including the From()
method.
Essentially, in the above snippet you're saying that you want the lookup()
result to be treated as a MyInterface
instance. A few lines later, you want it to be treated as a
MyOtherInterface
instance. In either case, the From()
returns a properly typed proxy
instance that wraps around the same Java object that was provided as input (via a duplicated JNI object reference).
The From()
method allows you to perform safe type casts independent of the set of proxy types that you have
available.
The .class
Problem
The one additional thing that you might run into has to deal with the class
keyword. In Java, you can gain
access to a class
instance by simply using the <classname>.class
notation. class
is a reserved word in C# that cannot be used in the same way, so we cannot generate a property called class
that would allow you the same usage. The workaround is simple: use Class.ForName()
. The only thing you need to
remember is to use the fully qualified classname, for example:
Class clsJavaString = Class.ForName( "java.lang.String" );
With this information, you should be able to tackle any casting and class-related problem from .NET.