5.6.2 Interoperating with native code
When Microsoft designed Visual J++ 6.0, they chose not to implement the Java Native Interface (JNI), which was (and remains to this day) the means of accessing native code from Java (and vice versa). Instead, Microsoft opted to implement their own Windows-friendly solution.
In Java, any method may be declared with the native keyword. Such a method is expected to be implemented in native code, and is part of a shared library that runs in the address space of the JVM (and can thus easily crash the JVM, or corrupt its state). JNI mandates that all native functions follow certain naming conventions, and take JNI-specific arguments. The first such argument, of type JNIEnv, gives access to the services of the JVM using a dispatch table (the JNIEnv argument happens to use the same binary memory layout as COM and most C++ compilers; this solution enables native code to be insulated from the inner workings of the JVM). Native code must interact with the JVM in order to process an invocation. This includes converting strings to a format usable by native code, throwing exceptions and converting local object references to global object references (that the native code can store for later use, without fear of the garbage collector prematurely collecting them). To use the services of a native library, a Java developer must manually write a native wrapper for it that forwards all calls to the native library.
Creating such wrappers is a burdensome task, especially if many native libraries are used. As Microsoft intended for their version of Java to be a better way of writing Windows applications, they needed a solution that would make calling on native platform services almost effortless. Besides introducing their own JNI-like technology (termed the Raw Native Interface), Microsoft also introduced a technology known as J/Direct to enable Java developers on Windows to easily make use of Windows services (Eckel 1998).
Native methods that were to use J/Direct were declared with the native keyword. In contrast to JNI, however, J/Direct methods were preceded by a Java comment with content that was taken into account by Microsoft’s Java compiler. At a bare minimum, such a comment had to specify the name of the shared library that was to be loaded. J/Direct then took care of marshalling all arguments into the format expected by the native code, which was not privy to the fact that it was called through J/Direct. Hence, native libraries could be used directly, and no native wrappers had to be written. Special J/Direct directives could be used to handle callbacks, and to instruct the JVM to marshal strings as wide strings or as strings using single-byte character sets. Microsoft even provided a ready-made Java package, containing the Java equivalent to the function prototypes and constants found in the C header files shipped as part of the Windows software development kit, enabling developers to effortlessly use Windows services.
Visual J++ 6.0 also featured deep COM integration, enabling Java classes to seamlessly access COM servers and conversely make Java classes available to native code through COM. To enable Java code to access COM servers, Microsoft shipped a tool that generated Java bindings from a type library. The COM objects used from Java were proxies partly implemented in native code, that marshalled arguments and forwarded calls to the native COM object. HRESULT return values that indicated failure were transparently converted to Java exceptions.
Java classes were COM-enabled using a tool that directly parsed compiled Java files and generated a type library, all without involving an interface description language. The generated COM interfaces could optionally be dual interfaces, enabling both late and very late binding (thus making it possible to use the Java COM objects from both native code and interpreted code). Microsoft’s JVM provided COM clients with COM-compatible proxy objects that marshalled arguments and forwarded calls to Java code.
The Platform Invocation Services of .NET, commonly referred to simply as P/Invoke, are broadly similar to J/Direct. P/Invoke declarations are not contained in comments, but in .NET attributes, which are a formalized means of associating metadata with language constructs (Java “annotations” are roughly equivalent). Microsoft does not ship a .NET equivalent to the C header files that enable access to Windows functions, meaning that users must manually author the P/Invoke metadata (Bukovics 2006).
.NET also features deep COM integration, much like that provided by Visual J++ 6.0. Making COM objects available to .NET involves generating bindings from type libraries. To call on the services of a COM object, .NET uses a proxy called a Runtime Callable Wrapper, which handles marshalling and forwarding. Conversely, .NET can expose managed code through a COM Callable Wrapper, which, again, is a proxy. In its simplest form, .NET exposes the complete class interfaces of classes (including methods inherited from the base class, System.Object). It is recommended that interfaces specifically meant to be exposed through COM are written instead. Again like Visual J++ 6.0, .NET supports both late and very late binding through COM dual interfaces.
The first versions of .NET enabled the use of enterprise services solely through COM+. The COM+ support is integrated directly in .NET, and as such, developers do not need to use COM+ services through COM interfaces. As of this writing, some of the COM+ services are being replaced with pure .NET services, such as those provided by the Windows Communication Foundation.