|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Introduction to Java/COM Custom Marshaling for User-Defined Data TypesThis article explains how to implement custom marshaling of user-defined types between Java and COM. Basically, this involves writing a hook class to do the marshaling, and using the jactivex tool with the /javatlb command-line switch to create a Java type library information file (.jnf) that describes the custom interface. A hook class is a special class that you write for each user-defined type you want to marshal from COM to Java. It contains the code that actually does the marshaling and translates the COM type to a Java type and the Java type to a COM type. Much of this article addresses what a hook class is and how to author one. Jactivex is a tool that can create Java libraries from existing COM interfaces. COM interfaces are based on type libraries, so a Java type library is a file that maps user-defined types in existing COM type libraries to hook classes. What Is Custom Marshaling and Who Uses It?Marshaling of data types is essential whenever a program spans languages, since data types are never exactly the same in any two languages. Since the Microsoft VM provides the ability to wrap COM libraries as Java classes, marshaling must occur between Java classes and COM type libraries. The Microsoft VM supports a certain level of intrinsic marshaling by default. As an example, suppose you have a COM library that uses various data types as parameters and return values for its methods. For these methods to be callable from Java, the data types need to be converted between the COM types and Java types. For some types, like integers, floats, booleans, and strings, the Microsoft VM for Java provides intrinsic marshaling support that automatically does the type conversion. In addition to the conversion work, the COM types must be represented as Java types to be callable from Java. This requires a Java .class file that contains type declarations in Java format. This class file is usually created by running a type-conversion tool such as jactivex (Jcom or Javatlb in older versions of the SDK) on the type library of the COM server. These conversion tools generate the correct type mappings for all types that are supported by the intrinsic marshaling of the Microsoft VM. The Microsoft VM does not, however, have intrinsic support for complex COM library data types, such as C structures. To marshal such data types requires that you create a user-defined Java type and write a hook class that does the marshaling. Since jactivex cannot automatically create type mappings that work for user-defined types, you will need to provide jactivex with information on how to generate the Java type declarations. This is done by creating a Java type library information file (.jnf) file, which describes your custom data types, and passing it to jactivex it creates the COM library class header file. The .jnf file has the following format: [Custom] <TypeLib Type>= <Java Type>, <Hook Class> Where:
An Example of Custom MarshalingThe following is an example of how to marshal a simple structure type between a COM type library and Java. Suppose you have a simple COM library, represented by the following pseudo-ODL (Object Description Language) example file. typedef struct POINT { long x; long y; } POINT; interface IPlotter : IUnknown { HRESULT DrawLine([in] POINT *start, [in] POINT *end); } coclass CPlotter { [default] interface IPlotter; } To implement or access this library with Java, you would use the following procedure.
The sample files for this article can be found in the SDK for Java version 3.0 pre-release 2. The sources for a working copy of this example is under the SKD-Java.20\Samples\CustDoc\plotter subdirectory. The sources for PointMarshaler are under the \CustDoc\point subdirectory. To execute the sample, change the current directory to the \CustDoc\prundir subdirectory, and run the go.bat file. How JAVA/COM Custom Marshaling WorksHow does the Microsoft VM for Java actually marshal the call from Java to Plotter? In the interface file IPlotter.class, there are special bits associated with the start and end parameters that indicate that these parameters are a user-defined class and that the hook class is PointMarshaler.class. These bits are all generated by jactivex.exe. When a Java application invokes the DrawLine method, the Microsoft VM loads the PointMarshaler class (if it hasn't already). It then looks for a public static field named cbByValSize, the presence of which indicates that the COM type is a fixed-size. In this example, PointMarshaler happens to expose this field. It is equal to 8, which is the size (in bytes) of a POINT structure. The Microsoft VM, therefore, allocates 8 bytes of space on the stack, and invokes another public static method on PointMarshaler called copyToExternal. The Microsoft VM passes the copyToExternal method a pointer to this 8-byte space and a reference to the end parameter, which in this case is java.awt.Point. Be aware that the pointer to the 8-byte space is passed as an integer value. With this information, copyToExternal translates the java.awt.Point to a POINT structure in that 8-byte space. The same routine is repeated for the end argument. The COM DrawLine method ultimately receives a pointer to the two stack buffers as its start and end parameters. Since both parameters were marked [in], there is no further action for this simple type. Note: The previous explanation was necessarily simplified for brevity. Much of the hook class analysis implied here is done prior to any method calls and does not actually occur at method call time. Hook Class OverviewAside from the extra step of writing a hook class and listing your data types in the .jnf file, the previous procedure is no different from that of integrating any other COM library with Java. As previously discussed, a hook class is a collection of static methods and fields packaged as a Java class. The hook class must be installed and visible to the classpath on any system where the user-defined types are used. Otherwise, hook classes remain fairly hidden. The Microsoft VM loads the hook class as part of loading a jactivex-created interface file that references them, and invokes methods on the hook class to marshal the user-defined types. All hook methods and fields are static (that is, per-class, rather than per-instance). The Java/COM integration layer of the Microsoft VM will never create an instance of a hook class. There are approximately 10 fields and methods defined by the Hook Class Specification. All hook members, however, are either optional or have reasonable defaults. Most hook classes will need only to implement a subset of the members. The following list shows the responsibilities of a hook class:
In turn, the Microsoft VM is responsible for calling the appropriate hook methods at the right time whenever ETYPEs are passed to or from COM methods. Once the hook class is written, the VM automatically supports passing ETYPEs (if ETYPE is fixed-size), ETYPE*, and ETYPE**, as well as returning ETYPE*. The VM also implements the correct semantics for [in], [out], [in,out], and [out,retval] type library attributes, which jactivex passes along using the extra bits in the .class file. An [out] attribute or double indirection is mapped to a one-element array of JTYPE rather than to JTYPE itself. Hook Class ImplementationAlthough hook classes are packaged as Java classes, they cannot be written in Java. Hook classes must receive and pass machine addresses (pointers). Java methods can receive pointers by masquerading them as an int type. The Java language offers no way, however, to dereference, allocate, destroy, or otherwise manipulate pointers. For this reason, use of the Raw Native Interface (RNI) is a necessity for authoring hook classes. RNI allows Java methods to be implemented as C functions inside a DLL. RNI also offers a small set of APIs that can be called from C to create, manipulate, and destroy Java objects. Using Template Files to Write a Hook ClassThe SDK for Java contains samples that show how to write hook classes to achieve custom marshaling. These samples are installed in several directories under the SDK-Java.20\Samples\CustDoc directory. All directory paths referenced here are in relationship to the CustDoc directory. The easiest way to start is by copying the files under the template subdirectory to a new directory. The key files in template are:
To make the files compilable, you must follow the instructions in each of the comments marked "TODO" in TemplateMarshaler.java and TemplateMarshaler.c. This involves replacing ETYPE and JTYPE with your particular type, and uncommenting some optional methods. The template contains C functions for eight hook methods. The comments inside each function describe what that hook function is supposed to do. We will go into more detail about this as we proceed through the examples. First, a few salient points about the hook functions as a whole:
EXAMPLE 1: FixedPtMarshaler (The Basic Hook Class)The FixedPtMarshaler sample is about the simplest useful hook class. It exposes only two methods and one field. The JTYPE is "double", and the ETYPE is the Win32 FIXED structure for representing fixed point fractions, as shown here: struct FIXED { WORD fract; short //covvalue; } FIXED; The source code for FixedPtMarshaler can be found under the \fixedpt directory. The executables (FixedPtMarshaler.class and FixedPtMarshaler.dll) are under the \rundir directory. The members implemented by FixedPtMarshaler are as follows: public FixedPtMarshaler { public static int cbByValSize; // set of sizeof(FIXED) = 8 public static double toJava(int ppFIXED, int flags) { // convert **ppFIXED to a double // return the double } public static void copyToExternal(double javaval, int ppFIXED, int Flags) { // convert double to a FIXED // copy the FIXED to **ppFIXED } } Even with only these methods, this hook class can now be used in the
following ways:
Note that when using this hook class in a .jnf file, you should preceed the Java type double with the const modifier as in the following example: [Custom] FIXED=const double, FixedPtMarshaler If you do not include the const modifier, jactivex will interpret that [out] parameters are intended to pass along single values passed in by the caller (as a side-effect), rather than passing a new value out using a single-element array. That is, jactivex would map the FIXED type as follows:
This is not what you want. Because double is immutable, this method prototype cannot possibly return a value to the caller. EXAMPLE 2: VarStrMarshaler (Embedded Resources)The basic hook class, demonstrated by FixedPtMarshaler, assumes that ETYPE contained no embedded pointers or handles to allocated resources that need to be freed when ETYPE is no longer needed. In C++ terms, if ETYPE is a class in a basic hook class, it is assumed ETYPE does not have a destructor. Some structures, however, do need to clean up embedded resources. A well known example is the VARIANT structure used in ActiveX Automation. A VARIANT is a fixed-size structure, yet it can have allocated objects such as BSTR, SAFEARRAYS and COM objects referenced by it. The "destructor" for a VARIANT is the VariantClear API, which checks the type of the variant and performs the appropriate cleanup (for example, freeing the BSTR, freeing the SAFEARRAY, calling Release on the COM object, and so on). Arranging for proper cleanup of embedded resources requires only one new method: releaseByValExternal. The VarStr marshaler example maps VARIANT types, confining itself to only one case: marshaling BSTR VARIANT types to Java String objects. That is, the JTYPE is String, and the ETYPE is as follows: struct { short vt; // Always VT_BSTR for this example short unused; short unused1; short unused2; BSTR bstrVal; // Points to characters in BSTR long unused3; // never used in this example. } VARIANT; The source code for VarStrMarshaler can be found under the \varstr directory. The executables VarStrMarshaler.class and VarStrMarshaler.dll are under the \rundir directory. The members implemented by VarStrMarshaler are as follows: public VarStrMarshaler { public static int cbByValSize; // set of sizeof(VARIANT) = 16 public static String toJava(int ppVARIANT, int flags) { // convert **ppVARIANT to a String // return the String } public static void copyToExternal(String javaval, int ppVARIANT, int Flags) { // convert String to a VARIANT // copy the VARIANT to **ppVARIANT } /* NEW! */ public static void releaseByValExternal(int ppVARIANT, int Flags) { SysStringFree( (*ppVARIANT)->bstrVal ); } } This hook class can be used in the following ways.
As before, String is immutable so the const modifier should be used for it in the .jnf file. EXAMPLE 3: PointMarshaler (Mutable Java Objects)In the examples up to this point, all JTYPEs were immutable. This is why [out] parameters could only be handled by single element arrays: that is, to deal with immutable types, a caller has to write code similar to the following: { double ad1[] = {0}; double ad2[] = {0}; func(ad1, ad2); System.out.println("func gave back: " + ad1[0] + ", " + ad1[1]); } For immutable objects, it should be clear that the function: HRESULT func([out] FIXED *, [out] FIXED *); could not be usefully mapped to: func(double d1, double d2); because func() would have no way of returning information to the caller. However, consider the java class java.awt.Point (which represents a point in 2-dimensional space.) The Point class is mutable as its x and y fields are public and can set by anyone. Therefore, it is advantageous to use the non-array form for passing [out] parameters. To do this only requires two new methods: copyToJava and toUninitJava. The PointMarshaler class maps java.awt.Point to the Win32 POINT structure. The source code for PointMarshaler can be found under the \point directory. The executables PointMarshaler.class and PointMarshaler.dll are under the rundir directory. The members implemented by PointMarshaler are: public PointMarshaler { public static int cbByValSize; // set of sizeof(POINT) = 8 public static Point toJava(int ppPOINT, int flags) { // convert **ppPOINT to a Point // return the Point } public static void copyToExternal(Point javaval, int ppPOINT, int Flags) { // convert Point to a POINT // copy the POINT to **ppPOINT } /* NEW! */ public static void copyToJava(Point javaval, int ppPOINT, int Flags) { // modify "javaval" in place so it is "equivalent" to **ppPOINT; } /* NEW! */ public static Point toUninitJava(int ppPOINT, int flags) { // create a new Point with arbitrary x and y values. // the contents of **ppPOINT are completely undefined and should // be ignored for fixed-size hook classes. } } This hook class can be used in the following ways.
This differs from the basic hook class in that [out] POINT* becomes a single element, Point, rather than a single-element array, Point[]. To force jactivex to generate the non-array mapping, you must omit the const modifier from the Point entry in the .jnf file. EXAMPLE 4: RectMarshaler (Support for Custom Allocation)The hook class examples up to this point have provided no method for allocating or freeing the memory for ETYPE itself. They have relied entirely on the VM to allocate the actual memory for ETYPE. As a result, the use of custom data types up until now has been restricted to cases where ETYPE can be allocated on the stack and its lifetime is bounded by the lifetime of the call. While this is sufficient for some data types, there are cases where stack allocation is insufficient. The first case is when there is a need to marshal methods where an ETYPE is allocated by the callee but freed by the caller (or the other way around, as can happen with [in,out] parameters.) The second case is if the data type is variable-sized (such as a string), in which case, the VM cannot do the stack-allocation because the size is unknown. The first case is considered next, followed by the second case. It is the responsibility of the hook class to specify which API is used to allocate and release the memory for ETYPE. The new hook methods required to support this are toExternal and releaseExternal. The RectMarshaler example demonstrates this by marshaling the Win32 RECT structure into java.awt.Rect objects. The source code for RectMarshaler can be found under the \rect directory. The executables RectMarshaler.class and RectMarshaler.dll are under the \rundir directory. The members implemented by RectMarshaler are: public RectMarshaler { public static int cbByValSize; // set of sizeof(RECT) = 8 public static Rect toJava(int ppRECT, int flags) { // convert **ppRECT to a Rect // return the Rect } public static void copyToExternal(Rect javaval, int ppRECT, int Flags) { // convert Rect to a RECT // copy the RECT to **ppRECT } /*NEW*/ public static void toExternal(Rect javaval, int ppRECT, int Flags) { // allocate a new RECT, initialize it using javaval, // store pointer in *ppRECT } /*NEW*/ public static void releaseExternal(int ppRECT, int Flags) { // release *ppRECT. If RECT required a releaseByValExternal // (which it doesn't), this routine must do that work as well. } } Note this example could also have implemented copyToJava and toUninitJava here since Rect is mutable (like Point). However, for the sake of clarity, this was not done. Important Note: Even in the presence of these new Marshaling methods, the VM will optimize by allocating the data structure on the stack and thus override certain allocations. For example, for "[in] ETYPE*" calls, the COM method will receive a pointer to a VM-allocated ETYPE on the stack rather than one allocated by the toExternal method. If you want all ETYPEs to be allocated using toExternal(), you must omit the cbByValSize field. This will prevent the VM from optimizing to stack allocations as it cannot predict how many bytes to allocate. This hook class can be used in the following ways:
EXAMPLE 5: AnsiMarshaler (Variable-sized data types)So far in this article, all of the ETYPE examples have been fixed-size structures. For some data structures, this is a prohibitive limitation. The classic example is a simple null-terminated string. To define a variable-sized structure, the hook class must omit the cbByValSize field. The absence of this field marks the hook class as variable-size. Unlike a fixed-sized hook class, a variable-size hook class must support toExternal and releaseExternal in order to be useful (the VM cannot allocate a variable-sized structure on the stack). In addition certain mappings available to fixed-size hooks (in particular, those that pass the data type by value) are not available to variable-size hooks. More specifically, consider the simple case where ETYPE is char (not LPSTR!), and JTYPE is java.lang.String. (Both types can be considered immutable for this example.) This structure can be defined by a hook class with only three methods: toJava, toExternal and releaseExternal; the minimum useful variable-size hook class. The AnsiMarshaler class maps Strings to the LPSTR (ANSI null-terminated strings). The source code for AnsiMarshaler can be found under the \ansistr directory. The executables (AnsiMarshaler.class and AnsiMarshaler.dll) are under the rundir directory. The members implemented by AnsiMarshaler are: public AnsiMarshaler { public static String toJava(int ppCHAR, int flags) { // convert **ppCHAR to a String // return the String } public static void toExternal(String javaval, int ppCHAR, int Flags) { // allocate a new LPSTR, initialize it using javaval, // store pointer in *ppCHAR } public static void releaseExternal(int ppCHAR, int Flags) { // release *ppCHAR. If LPSTR required a releaseByValExternal // (which it doesn't), this routine must do that work as well. } } The allowed usages are as follows:
Running and Building the Sample ClientThe \client directory contains the sources for a sample Java client and a C++ inproc COM server which exchange all the various types implemented by the example. To run the example, simply change the current directory to "rundir", and type "GO". To build the sample, you will need to provide a makefile compatible with your build environment. Here are the steps:
|
© 1998 Microsoft Corporation. All rights reserved. Terms of use. |