J/Direct Revealed

As simple as the two previous apps might seem to the Java programmer, J/Direct performed several important and nontrivial tasks to make our Beep and SetSystemTime examples execute properly. Let's talk about the steps that J/Direct must perform to enable a call from a Visual J Plus Plus v6 program to a C function residing in a DLL.

DLL Loading

The most obvious problem that J/Direct solves is the otherwise difficult task of loading the necessary DLLs. A DLL is a library of routines that isn't necessarily resident in memory. A DLL is loaded into memory immediately before a program that needs it begins to run. The memory occupied by the DLL can be reclaimed by the operating system when the DLL is no longer referenced by any currently running app. (This might seem obvious to Windows programmers, but there was no equivalent to the DLL in operating systems developed prior to Windows.)

As explained previously, the Win32 API functions J/Direct accesses are located within Windows DLLs. J/Direct can also be instructed to access user-defined functions, as long as these functions reside within a DLL. In both cases, J/Direct can't assume that a target DLL is memory-resident. Therefore, before J/Direct can perform the conversion call from Visual J Plus Plus v6 to C or C++, it must make sure that the target DLL is memory-resident.

Thus, when the button1_click() method invokes Win32.Beep() in the previous Beep app, J/Direct checks whether KERNEL32.DLL is already memory- resident. (Remember that Win32.Beep() is part of KERNEL32.DLL.) If KERNEL32.DLL isn't in memory, J/Direct makes the system calls necessary to load it.

Marshaling

Once J/Direct has loaded the proper DLL into memory, and before J/Direct can call the C function from Visual J Plus Plus v6, J/Direct must convert any arguments to the function from the Java data type to the equivalent C or C++ data type. Once the call to the C or C++ function is complete, J/Direct must convert these arguments plus the return value back into Java data types. This process of converting data types from the calling language to the target language and back is called marshaling.

The following table shows the data type conversions J/Direct performs to marshal arguments from Java to C and back. The capitalized native data types are common Windows #define constants for intrinsic C types. (Calls to C++ functions work the same as calls to C functions, but calls to C++ methods aren't supported by J/Direct.)

Conversion of data types between Java and 32-bit native functions

Java Data Type Native Function
Data Type
Comments
void void This is a return type only. However, J/Direct does handle functions that have no arguments.
byte BYTE or char  
short short or WORD  
int int, unsigned int, long, unsigned long, UINT, ULONG, or DWORD J/Direct maps signed and unsigned to the same type because Java doesn't support unsigned.
char unsigned short, TCHAR A C char value is normally 8 bits; a TCHAR corresponds to the Unicode style 16-bit char value.
long __int64 __int64 is a special Visual C++ 64-bit type.
float float  
double double  
boolean int, BOOL C uses the rule that 0 is false and all else is true.
String const TCHAR*, LPCTSTR Not allowed by J/Direct as a return type.
StringBuffer TCHAR*, LPTSTR Not allowed by J/Direct as a return type. Set the StringBuffer capacity to hold the largest possible return value.
java[] C* java is the Java intrinsic type, and C is the C intrinsic type; that is, int[] maps to DWORD*
Object struct* J/Direct maps a pointer to a C structure to a reference to a static class; J/Direct can't handle the passing of structures by value.
com.ms.dll.Callback function pointer  

To marshal an intrinsic type, such as a char to a short, J/Direct simply casts the argument from one type to the other. There are numerous problems with marshaling more complex objects, and these problems are outlined in the following sections.

Passing Pointers

Many C functions return a value to the caller by using pointers to intrinsic values as arguments. Let's call these types of pointers intrinsic pointers (in contrast to pointers to structure objects). For example, functions like the following aren't uncommon in C:

void divideInt(int numerator, int denominator,
 int* pResult, int* pRemainder)
{
 *pResult = numerator / denominator;
 *pRemainder = numerator % denominator;
}


The function divideInt() accepts two simple integers and two pointers to integers. The results of dividing numerator by denominator are returned to the caller through the intrinsic pointers pResult and pRemainder. Java has no direct equivalent to the C intrinsic pointer—the nearest match is an array of length 1. If the divideInt() function were part of the MATH.DLL dynamic-link library, the following Java isPrime() function would call the divideInt() function, properly receiving the returned integer values in the arrays result and remainder:

class MyMath
{
 /** dll.import("MATH") */
 private static native void divideInt(int numerator,
 int denominator,
 int[] result,
 int[] remainder);
 public static boolean isPrime(int n, int d)
 {
 int[] result = new int[1];
 int[] remainder = new int[1];
 // make the call
 divideInt(n, d, result, remainder);
 // prime if remainder is 0
 return remainder[0] == 0;
 }
}


Declaring the variables result and remainder to be arrays of length 1 signals J/Direct to pass these references as pointers and to allocate space for the integer returned. The value returned is always in array element zero.

Handling C Structures

A C structure presents J/Direct with a number of problems. The Java class doesn't correspond directly to the C structure. When a Visual J Plus Plus v6 method passes to a C function a reference to a class object, J/Direct must take the following steps:

  1. It creates a C structure that has the same data members as the Visual J Plus Plus v6 class. (We'll talk more about this later.)
  2. It assigns to each data member from the Java class the corresponding data member in the C structure.
  3. It passes a pointer to the C structure.
  4. It reverses the process on the way back from the native function to Visual J Plus Plus v6.

As simple as this sounds, several problems that we'll discuss in the following sections can arise during the creation and assignment steps.

Handling Strings

The String class is the most commonly used class in the Java library. Unfortunately, the Java String object is quite different from the C ASCIIZ string. The Java String class contains not only the string itself but a length data member. In addition, Java assigns no particular meaning to any given character. In contrast, the C string has no length. Instead, C strings use the character 0x0 to indicate the end of the string. When converting a Java string into a C ASCIIZ string, J/Direct must add a 0x0 character to the end of the character string data before passing it to the C function. (The app code must make sure that none of the characters in the string are 0x0.) Upon return, J/Direct counts the number of characters up to the 0x0 character and uses this as the length of the String object.

In addition, the Java String class always refers to Unicode characters. In contrast, C strings can be Unicode or ANSI characters.

WARNING
The Win32 TCHAR is a 16-bit character type. J/Direct doesn't support the conversion of a String object to simple 8-bit char* type C strings. For this conversion, the programmer should convert the String object into a byte array using the String method getBytes(), and then pass the byte array rather than the String.

Finally, the Win32 API defines different functions to handle ANSI TCHAR strings than to handle Unicode TCHAR strings. By default, J/Direct assumes the ANSI version. Thus, when Java calls the following method, J/Direct converts the String object into a null-terminated ANSI character array:

/** @dll.import("USER32") */
static native int MessageBox(int hwnd, String text, String title, int sytle);


There are three different types of Win32 functions. Older Win32 functions carry a generic name such as MessageBox(). This generic function handles ANSI character arrays. More recent Win32 functions come in two forms. A function with a name like MessageBoxA() also processes an ANSI character array. A function like MessageBoxW() handles the equivalent task for Unicode character arrays.

The dll.import() directive by default accepts the ANSI version, so by default you can use MessageBox() or MessageBoxA(). The dll.import() directive also enables you to specify the MessageBoxW() Unicode API version by using the following code:

/** @dll.import("USER32", unicode) */
static native int MessageBox(int hwnd, String text, String title, int sytle);


Unfortunately, neither the ANSI version nor the Unicode version is ideal for all Win32 platforms. ANSI mode is required by Windows 95 and Windows 98, which don't support Unicode, but it is inefficient for Windows NT, which defaults to Unicode. (Windows NT does grudgingly support ANSI mode.)

The dll.import() directive's auto modifier provides a way out of this ANSI vs. Unicode problem. The auto modifier directs J/Direct to use the optimum type of conversion for the current operating system, as shown in the following code:

/** dll.import("USER32", auto) */
public static native void MessageBox(int hwndOwner, String text, String title, int style);


For example, the following method openMB(String text) calls the method MessageBox() to display the specified text using the optimum method for the host version of Windows.

class ShowMessageBox
{
 public static void openMB(String text)
 {
 MessageBox(0, text, "Message", 0);
 }
 /** dll.import("USER32", auto) */
 private static native int MessageBox(int hnd, String text,
 String title, int style);
 }


Since this character mode decision is made at run time, a Visual J Plus Plus v6 program written using the auto directive always invokes the most efficient Win32 API function for the current version of Windows.

Matching C Structure Details

The C programmer has more control over the layout of C structures than the Java programmer does. Fortunately for Java programmers, Visual J Plus Plus accounts for this situation with modifiers to the dll.struct() directive.

Packing

One consideration in laying out C structures is packing. Most CPUs can only access memory one word at a time, and different processors in the Intel series define "word" differently. Pentium processors and faster processors access memory on 4-byte (32-bit) boundaries. If an integer is located on an address that is a multiple of 4 bytes, the entire integer can be read in a single memory read. This is called an aligned read. If the integer is stored at an address that isn't a multiple of 4 bytes, the CPU must perform two reads and combine the necessary parts of these two words into one. This is called an unaligned read. Obviously, an unaligned read is slower than an aligned read.

To avoid this slowdown, the Microsoft Visual C++ compiler enables you to specify that integers be packed on 4-byte boundaries. To see the results of this decision, consider the structure definition below.

struct X
{
 char a;
 int b;
};


Suppose char a, which is 1 byte in length, is at address 100. You might think that int b would be at address 101, but this normally is not the case. By default, the Visual C++ compiler would put b at address 104, thereby ensuring word alignment and optimum access performance.

The down side of packing for performance is that it wastes space. Thus, Visual C++ enables you to change the packing size to decrease the memory storage requirements. This option might be important when a program contains a large number of structure objects that are accessed infrequently. In such a case, the memory concerns outweigh the performance degradation.

Java has no such control over the packing size. With Java, you must live with whatever the packing rules might be. At the same time, J/Direct must allow the Visual J Plus Plus v6 programmer to access a Visual C++ structure no matter how it is packed.

The pack modifier enables you to match J/Direct conversion to the packing of the structure in memory:

/** @dll.struct(pack = n) */


In this code, n is either 1, 2, 4, or 8, and the unit of n is byte. The default value of pack is 8, to match the pack pragma default in Visual C++. The default value of pack for structures within the Win32 API is also 8.

Fixed length arrays within a structure

A second problem in converting Java classes to C and C++ structures lies in the way Java and C create data members. It's possible in C to declare a fixed-length array within a structure, whereas in Java you declare a reference to an array. J/Direct handles this discrepancy with the dll.structmap() directive. Consider the following example, which demonstrates the use of dll.structmap(). The C structure is defined as follows:

structure Sample
{
 TCHAR array1[32];
 float array2[15];
};


You would use the dll.struct() directive with dll.structmap() to describe this structure to J/Direct:

// example class name class Win32
{
 /** dll.struct() */
 class Sample
 {
 /** @dll.structmap([type=TCHAR[32]]) */
 String array1;
 /** @dll.structmap([type=FIXEDARRAY, size=15]) */
 float[] array2;
 };
}


The data member array1 is declared as a 32-character String, while array2 is declared as a fixed array containing 15 elements of float data type.

Accessing Win32 Error Codes

Most Win32 API functions return error codes as an integer value, with 0 indicating that no error occurred. Some Win32 API functions signal an error by returning a value of false—indicating the operation failed—and storing the integer code into a global variable called errno to indicate the specific error that occurred. (This is particularly true of the older functions that make up the C function library.)

When calling functions that use errno, you should not attempt to access the global error indicator directly. After the error has occurred, J/Direct might make further Win32 calls that could write over the error indicator in errno before returning control to your Visual J Plus Plus v6 method.

The dll.import() directive modifier setLastError instructs J/Direct to capture the error code immediately after it invokes the Win32 API call. When using the setLastError modifier, the static method com.ms.dll.DllLib.getLastWin32Error() returns the error code.

setLastError modifier

The following code is a short example of how to use the setLastError modifier.

class Win32
{
 /** @dll.struct() */
 class WIN32_FIND_DATA
 {
 …define the structure here…
 }
 /** @dll.import("KERNEL32", setLastError) */
 static native boolean FindNextFile(int hFindFile,
 Win32.WIN32_FIND_DATA wfd);
 }
 class MyCode
 {
 // declare the WIN32_FIND_DATA structure
 Win32.WIN32_FIND_DATA wfd = new Win32.WIN32_FIND_DATA();
 // keep the error code
 int errCode;
 /**
 * Find the next file by using a Win32 API call; set error code.
 */
 boolean findNextFile(int hFindFile)
 {
 // assume no error
 errCode = 0;
 // invoke the method
 boolean f = Win32.FindNextFile(hFindFile, wfd);
 // if it didn't work…
 if (f == false)
 {
 // capture the error code from Win32 call
 errCode = com.ms.dll.DllLib.getLastWin32Error();
 }
 return f;
 }
 }


In this example, the class WIN32_FIND_DATA was either created by the J/Direct Call Builder or carefully constructed by hand to match the WIN32_FIND_DATA structure declared in the windows.h include file. The structure's contents aren't listed here. The Win32 API function FindNextFile() is declared to accept the integer variable hFindFile (the variable's meaning and purpose are irrelevant for this example) and a WIN32_FIND_DATA object. In addition, the function declaration instructs J/Direct to capture any error that might occur while calling the API function.

The class MyCode contains a method, findNextFile(), which invokes the Win32 API call FindNextFile() using the WIN32_FIND_DATA object wfd. If find NextFile() returns false indicating that an error occurred in the C function FindNextFile(), the method findNextFile() captures the error code by calling com.ms.dll.DllLib.getLastWin32Error().

NOTE
Don't capture the Win32 API error code if the value returned indicates that the function worked properly. Many API calls do not set the error code unless something goes wrong.

Garbage Collection

There are several other problems that J/Direct must overcome when calling a C function from Visual J Plus Plus v6 and that are unrelated to class and struct issues. One problem is Java's garbage collector.

The garbage collector is a background process that returns unused objects to the pool of unused memory. This pool is called the heap in both the Java and C++ languages.

To make more efficient use of the heap, Java moves objects around in memory during the garbage collection process. When an object is moved in memory, the garbage collector updates the references to the object accordingly. This makes the change in an object's memory address invisible to other Java classes.

C has no concept that corresponds with garbage collection. You are expected to direct your program to return objects once they are no longer in use. In addition, C expects its objects to stay put in memory. C would consider moving objects about in memory to be decidedly unfriendly.

J/Direct solves this problem by storing interface objects in a special section of the heap where objects aren't repacked by the garbage collector.

Name Mangling

Another concern in accessing C or C++ functions is that the name of a C or C++ function is different in the source file than it is in the object file. C simply adds an underscore to the name of the function when it puts the function in the object file. Thus, myFunction(int) in the source file becomes _myFunction in the object file. In the case of C++, the differences in function names are more severe. Thus, myFunction(int) might become myFunction$i. The suffix $i in the C++ function name indicates the number and the types of the function arguments.

This process of changing function names is called name mangling. Fortunately, J/Direct understands the way C and C++ mangle names and takes care of tracking function names for you.

Aliasing

Aliasing enables you to assign Java-like names to Win32 API functions. You or the creators of the language might have established naming conventions for the names of methods. For example, by convention Java method names begin with a small letter, and Java class names begin with a capital letter. The names of Win32 API functions begin with a capital letter.

You can change the name of the C function you are calling to a name more to your liking by creating an alias, as shown in the code below.

/** dll.import("USER32", auto, entrypoint="MessageBox") */
public static native int messageBox(int hnd, String text,
 String title, int style);


Handling Callbacks

It's not uncommon for C functions to make use of what is known as a callback function. This concept is explained in the following section.

Declaring callbacks in C

Consider the following C example of a generic sort routine. (This example shows neither the struct MO nor the #define LENGTH.)

// the order function can put an array of MO objects in any order
// as long as you can provide a pointer to a function that compares
// the objects. This pointer to a function is often called a
// callback.
void order(int (*pCompare)(MO* p1, MO* p2), MO* pArray, int length);
// compareMO - compare two MO objects int compareMO(MO* p1, MO* p2)
{
 .
 .
 .
}
void myProg()
{
 struct mo[LENGTH];
 .
 .
 .
 // order the array of MO objects
 order(compareMO, mo, LENGTH);
}


In this code, the order() function sorts an array of MO objects using the compareMO() function to establish the sequencing of objects.

To be more specific, the first argument to order() is a function that takes two pointers to MO objects and returns an int value that indicates whether or not p1 comes before p2 in the sorted list.

When calling order(), the myProg() function provides a pointer to the function compareMO(), whose declaration matches exactly the declaration of the pointer required by order(). Somewhere within its logic, the order() function will invoke this programmer-provided compareMO() function indirectly through the function pointer provided.

NOTE
The C callback is similar in concept to the Visual J Plus Plus v6 delegate. A delegate is the Visual J Plus Plus v6 analog to a function pointer. The delegate is associated with a method before it is passed to an addX() method such as addOnClick(). When the onClick() event occurs, Visual J Plus Plus v6 invokes the delegate method.

The term callback comes from the fact that the order() function calls back to the compare() function.

NOTE
There are many other uses for callback functions, but they all involve passing a pointer to a function to another function.

Handling callbacks in J/Direct

The following example demonstrates how to declare a function in Visual J Plus Plus v6 that accepts a callback function:

// some app programmer defined class class MyDLL
{
 /** @dll.struct() */
 static class MO
 {
 int value;
 }
 /** @dll.import("MyDLL") */
 static native void order(com.ms.dll.Callback compare,
 MO[] objs, int length);
}


In this example, compare is a callback method that is being passed to the C function order().

The callback function must be a nonstatic method named callback(). It must be a method of a class that extends the class Callback. The arguments to the method callback() must match those required by the calling function—in this case, order():

class MyCallbackClass extends com.ms.dll.Callback
{
 int callback(MyDLL.MO mo1, MyDLL.MO mo2)
 {
 // …whatever comparison…
 }
}


A Visual J Plus Plus program would invoke the order() function as follows:

MyDLL.order(new MyCallbackClass(), MyDLL.MO, MyDLL.MO.length);


In this case you, the Visual J Plus Plus v6 programmer, have passed an object of class MyCallbackClass to J/Direct. J/Direct constructs a callback function which it passes on to the C function order(). When order() calls the provided callback function, J/Direct passes the call on to MyCallbackClass.callback().

Callback example

The following example shows a class MyFunc, whose constructor declares an array of MyDLL.MO objects. The constructor then sorts the objects using the native method order() by means of a Java callback function:

class MyFunc extends com.ms.dll.Callback
{
 // declare an array of MO objects
 MyDLL.MO[] mo = new MyDLL.MO[]
 {
 new MyDLL.MO(),
 new MyDLL.MO(),
 new MyDLL.MO()
 };
 /**
 * Sort the MO objects using the native order function.
 */
 public MyFunc()
 {
 // sort the array mo using the method callback()
 // to perform the comparisons (this is legal since
 // the current class extends Callback)
 MyDLL.order(this, mo, mo.length);
 }
 /**
 * Provide the callback function that performs the comparison.
 * (This function must match exactly the requirements of
 * the callback function as defined by order().)
 */
 int callback(MyDLL.MO mo1, MyDLL.MO mo2)
 {
 if (mo1.value > mo2.value)
 {
 return 1;
 }
 if (mo1.value < mo2.value)
 {
 return -1;
 }
 return 0;
 }
}


Comments