|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
How Data Types are MarshaledThe Microsoft Win32 VM for Java infers the native type of each parameter and the return value from the declared (compile-time) Java type of the parameter. For example, a parameter declared as a Java integer is passed as a 32-bit integer; a parameter declared as a Java String object is passed as a null-terminated string, and so forth. There are no invisible attributes that provide information about the native types. In Java, what you see is what you get. The following sections describe in detail how the Java data types map to native data types.
Quick ReferenceThe following two tables list the native type that corresponds to each Java type. The first table describes the mappings for parameters and return values, and the second table shows the mappings that are used with the @dll.struct directive. Parameter and Return Value Mappings
Mappings Used with @dll.struct
Basic Scalar TypesThe basic scalar types are mapped as you would expect.
There is no direct representation of unsigned integer types in Java, except by using the signed types as a two's complement representation. For example, the integer type can be used without loss of representation for the common DWORD (unsigned 32-bit) type. CharsThe Java char type becomes a CHAR (an 8-bit ANSI character) unless the unicode or ole modifier is in effect, in which case it becomes a WCHAR (a 16-bit Unicode character). BooleansThe Java boolean type maps to the Win32 BOOL type, which is a 32-bit type. As a parameter, the Java true maps to 1, and false maps to 0. As a return value, all non-zero values map to true. Note that BOOL and VARIANT_BOOL (the internal boolean type in Microsoft® Visual Basic®) are not interchangeable. To pass a VARIANT_BOOL to a Visual Basic DLL, you must use the Java short type and use -1 for VARIANT_TRUE, and 0 for VARIANT_FALSE. StringsThis section explains how you can pass a string in ANSI or Unicode format to a DLL function. It also discusses two ways to return a string from a DLL function. Passing a String to a DLL FunctionTo pass a standard null-terminated string to a DLL function, just pass a Java String. For example, to change the current directory, you can access the Kernel32 function CopyFile function as follows. class ShowCopyFile { public static void main(String args[]) { CopyFile("old.txt", "new.txt", true); } /** @dll.import("KERNEL32") */ private native static boolean CopyFile(String existingFile, String newFile, boolean f); } Strings are read-only in Java, so the Microsoft VM will only convert the String object as an input. To allow virtual machine implementations to marshal Strings without copying the characters, String object parameters should not be passed to DLL functions that can modify the string. If the DLL function might modify the string, pass a StringBuffer object. Strings are converted to ANSI unless the unicode or ole modifier is used, in which case the string is passed in Unicode format. Strings cannot be declared as return types of a DLL function except in ole mode, where the native return type is assumed to be a LPWSTR allocated using the CoTaskMemAlloc function. Receiving a String From a DLL FunctionThere are two common ways of passing a string back from a function: either the caller allocates a buffer that is filled in by the function, or the function allocates the string and returns it to the caller. Most Win32 functions use the first method, but OLE functions use the second method. (See Invoking OLE API Functions to learn about the special support that Microsoft® J/Direct provides for calling OLE functions.) One function that uses the first method is the Kernel32 function GetTempPath, which has the following prototype. DWORD GetTempPath(DWORD sizeofbuffer, LPTSTR buffer); This function simply returns the path of the system tempfile directory (such as "c:\tmp\"). The buffer argument points to a caller-allocated buffer that receives the path, and sizeofbuffer indicates the number of characters that can be written to the buffer. (This is different from the number of bytes in the Unicode version.) In Java, Strings are read-only, so you cannot pass a String object as the buffer. Instead, you can use Java's StringBuffer class to create a writable StringBuffer object. Here is an example that invokes the GetTempPath function. class ShowGetTempPath { static final int MAX_PATH = 260; public static void main(String args[]) { StringBuffer temppath = new StringBuffer(MAX_PATH); GetTempPath(temppath.capacity()+1, temppath); System.out.println("Temppath = " + temppath); } /** @dll.import("KERNEL32") */ private static native int GetTempPath(int sizeofbuffer, StringBuffer buffer); } To understand this example, it is important to distinguish between a StringBuffer's length and its capacity. The length is the number of characters logically in the string currently stored in the StringBuffer. The capacity is the actual amount of storage currently allocated to that StringBuffer. After the following statement executes, StringBuffer sb = new StringBuffer(259); the value of sb.length is zero, and the value of sb.capacity is 259. When you invoke a DLL method passing a StringBuffer, the Microsoft VM examines the capacity of the StringBuffer, adds one for the null terminator, multiplies by 2 if Unicode is the default character size, and then allocates that many bytes of memory for the buffer that is passed to the DLL function. In other words, you use the capacity, not the length, to set the size of the buffer. Be careful not to make the following mistake. StringBuffer sb = new StringBuffer(); //Wrong! GetTempPath(MAX_PATH, sb); Invoking the StringBuffer constructor with no arguments creates a StringBuffer object with a capacity of 16, which is probably too small. MAX_PATH was passed to the GetTempPath method, indicating that there was enough room in the buffer to hold 260 characters. Thus, GetTempPath will probably overrun the buffer. If you were planning to use GetTempPath extensively, you should wrap it in the following manner. public static String GetTempPath() { StringBuffer temppath = new StringBuffer(MAX_PATH-1); int res = GetTempPath(MAX_PATH, temppath); if (res == 0 || res > MAX_PATH) { throw new RuntimeException("GetTempPath error!"); } return temppath.toString(); // can't return a StringBuffer } This method offers both convenience and safety, and it maps the error return value of the function to a Java exception. Notice that you cannot return StringBuffer objects. ArraysJ/Direct automatically handles arrays of scalars. The following Java array types translate directly into native pointer types as follows.
The char[] array type maps to CHAR* unless the unicode modifier is in effect, in which case it maps to WCHAR*. All scalar array parameters can be modified by the caller (like [in,out] parameters). Array types cannot be used as return types. There is no support for arrays of objects or strings. Typically, this facility is used by OLE functions to return values. (OLE functions reserve the "function" return value to return the HRESULT error code.) See Invoking OLE API Functions to learn how to obtain the return value for OLE functions. StructuresThe Java language does not directly support the concept of a structure. Although Java classes containing fields can be used to emulate the concept of a structure within the Java language, ordinary Java objects cannot be used to simulate structures in native DLL calls. This is because the Java language does not guarantee the layout of the fields and because the garbage collector is free to move the object around in memory. Therefore, to pass and receive structures from DLL methods, you need to use the @dll.struct compiler directive. When applied to a Java class definition, this directive causes all instances of the class to be allocated in a memory block that will not move during garbage collection. In addition, the layout of the fields in memory can be controlled using the pack modifier (see Structure Packing). For example, the Win32 SYSTEMTIME structure has the following definition in the C programming language. typedef struct { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME; The correct declaration of this structure in Java is as follows. /** @dll.struct() */ class SYSTEMTIME { public short wYear; public short wMonth; public short wDayOfWeek; public short wDay; public short wHour; public short wMinute; public short wSecond; public short wMilliseconds; } The following example uses the SYSTEMTIME structure in a DLL method call. class ShowStruct { /** @dll.import("KERNEL32") */ static native void GetSystemTime(SYSTEMTIME pst); public static void main(String args[]) { SYSTEMTIME systemtime = new SYSTEMTIME(); GetSystemTime(systemtime); System.out.println("Year is " + systemtime.wYear); System.out.println("Month is " + systemtime.wMonth); // etc. } } Note: Classes declared with @dll.struct are considered unsafe and therefore cannot be used by untrusted applets. Correspondence Between Types Inside StructuresThe following table describes how scalar types map inside structures.
Reference types (Java objects and classes) normally map to embedded structures and arrays. Each supported mapping is described in the following table.
There is no direct support for pointers inside structures, due to the large number of possible ways referenced objects could be allocated and disposed of. To represent a structure with an embedded pointer, declare the pointer field as type int. You will need to make explicit DLL calls to the appropriate allocation functions and initialize the memory blocks yourself. (You could use DllLib.ptrToStruct to map the blocks onto @dll.struct classes.) Nested StructuresA structure can embed another structure simply by naming the other structure as the field type. For example, the Windows MSG structure embeds a POINT structure as follows. typedef struct { LONG x; LONG y; } POINT; typedef struct { int hwnd; int message; int wParam; int lParam; int time; POINT pt; } MSG; This translates directly into Java as follows. /** @dll.struct() */ class POINT { int x; int y; } /** @dll.struct() */ class MSG { public int hwnd; public int message; public int wParam; public int lParam; public int time; public POINT pt; } Performance tip: Although embedding structures is handy, the fact remains that Java does not truly support embedded objects only embedded references to objects. The Microsoft VM must translate between these two formats each time a nested structure is passed. Therefore, in a critical code path, you can improve performance by nesting structures manually (by copying the fields of the nested structure into the containing structure). For example, the pt field in the MSG structure could easily be declared as two integer fields, pt_x and pt_y. Fixed-size Strings Embedded within StructuresSome structures have fixed size strings embedded in them. The LOGFONT structure is defined as follows. typedef struct { LONG lfHeight; LONG lfWidth; /* <many other fields deleted for brevity> */ TCHAR lfFaceName[32]; } LOGFONT; This structure can be expressed in Java using an extension syntax to specify the size. /** @dll.struct() */ class LOGFONT { int lfHeight; int lfWidth; /* <many other fields deleted for brevity> */ /** @dll.structmap([type=TCHAR[32]]) */ String lfFaceName; } The @dll.structmap directive indicates the size of the fixed string as measured in characters (including space for the null terminator). Fixed-size Scalar Arrays Embedded within StructuresFixed-size arrays of scalars embedded in structures can be specified using the @dll.structmap directive. Here is a C language structure that contains fixed-size scalar arrays. struct EmbeddedArrays { BYTE b[4]; CHAR c[4]; SHORT s[4]; INT i[4]; __int64 l[4]; float f[4]; doubl d[4]; }; You can specify the EmbeddedArrays structure by using @dll.structmap in the following way. /** @dll.struct() */ class EmbeddedArrays { /** @dll.structmap([type=FIXEDARRAY, size=4]) */ byte b[]; /** @dll.structmap([type=FIXEDARRAY, size=4]) */ char c[]; /** @dll.structmap([type=FIXEDARRAY, size=4]) */ short s[]; /** @dll.structmap([type=FIXEDARRAY, size=4]) */ int i[]; /** @dll.structmap([type=FIXEDARRAY, size=4]) */ long l[]; /** @dll.structmap([type=FIXEDARRAY, size=4]) */ float f[]; /** @dll.structmap([type=FIXEDARRAY, size=4]) */ double d[]; } Structure PackingStructure fields are padded and aligned according to ANSI 3.5.2.1. The packing size can be set using the pack modifier. /** @dll.struct(pack=n) */ where n can be 1, 2, 4 or 8. The default is 8. For users of the Microsoft® Visual C++® compilers, "pack=n" is equivalent to "#pragma pack(n)". The Relationship Between @dll.struct and @com.structThe @dll.struct directive is very similar to the @com.struct directive emitted by the jactivex tool and emitted (implicitly) by the javatlb tool. (The javatlb tool has been replaced by jactivex.) The main difference is that the default type mappings are suited for Microsoft® Windows® function calling instead of COM object calling. Given this information, it follows that you can also generate @dll.struct classes by describing the structure in a type library and using jactivex to generate the Java class. However, it's usually faster to generate the classes manually. PointersJava does not support a pointer data type. However, instead of passing a pointer, you can pass a one-element array. You can store pointers in Java integers, and you can read and write data from raw pointers. Return Value PointersWin32 functions that have multiple return values typically handle them by having the caller pass a pointer to a variable to be updated. For example, the GetDiskFreeSpace function has the following prototype. BOOL GetDiskFreeSpace(LPCTSTR szRootPathName, DWORD *lpSectorsPerCluster, DWORD *lpBytesPerCluster, DWORD *lpFreeClusters, DWORD *lpClusters); GetDiskFreeSpace is typically called as follows. DWORD sectorsPerCluster, bytesPerCluster, freeClusters, clusters; GetDiskFreeSpace(rootname, §orsPerCluster, &bytesPerCluster, &freeClusters, &clusters); In Java, this is just a special case of passing scalar arrays where the array size is one element. The following example shows how to call the GetDiskFreeSpace function. class ShowGetDiskFreeSpace { public static void main(String args[]) { int sectorsPerCluster[] = {0}; int bytesPerCluster[] = {0}; int freeClusters[] = {0}; int clusters[] = {0}; GetDiskFreeSpace("c:\\", sectorsPerCluster, bytesPerCluster, freeClusters, clusters); System.out.println("sectors/cluster = " + sectorsPerCluster[0]); System.out.println("bytes/cluster = " + bytesPerCluster[0]); System.out.println("free clusters = " + freeClusters[0]); System.out.println("clusters = " + clusters[0]); } /** @dll.import("KERNEL32") */ private native static boolean GetDiskFreeSpace(String rootname, int pSectorsPerCluster[], int pBytesPerCluster[], int pFreeClusters[], int pClusters[]); } Raw PointersA pointer to an unknown or particularly difficult structure can be stored in a plain Java integer. If your application only needs to store the pointer and not dereference it, this is the simplest (and most efficient) approach. You might want to use this technique to store a pointer that has been returned by a DLL function that allocates a memory block. In fact, you can use this technique to store a pointer returned by any DLL function. Needless to say, using raw pointers eliminates many of the safety advantages of Java. So, an alternative approach should be used whenever possible. However, there are situations when you might choose to use raw pointers. With that in mind, there are two ways to read and write data from raw pointers. Casting to a Reference to an @dll.struct ClassOne way to read and write data through a raw pointer is to cast the raw pointer to a reference to an @dll.struct class. Once this is done, you can read and write the data using normal field access syntax. For instance, suppose you have a raw pointer that you wish to access as a RECT. You can use the system method DllLib.ptrToStruct as follows. /** @dll.struct() */ class RECT { int left; int top; int right; int bottom; } import com.ms.dll.*; int rawptr = ...; RECT rect = (RECT)DllLib.ptrToStruct(RECT.class, rawptr); rect.left = 0; rect.top = 0; rect.right = 10; rect.bottom = 10; The ptrToStruct method wraps the raw pointer in a RECT instance. Unlike instances created by the new operator, this RECT instance will not attempt to free the raw pointer upon reclamation by the garbage collector because the RECT object has no way of knowing how the pointer was allocated. In addition, because the native memory was already constructed at the time ptrToStruct was called, the RECT class constructor is not called. Using the DllLib Copy MethodsAnother method for reading and writing data through a raw pointer is to use the overloaded copy methods in DllLib. These methods copy data between Java arrays of various types and raw pointers. If you need to treat a raw pointer as a pointer to a string (LPTSTR), you can use one of the DllLib methods ptrToStringAnsi, ptrToStringUni, or ptrToString to parse the string and convert it into a java.lang.String object. import com.ms.dll.*; int rawptr = ...; String s = DllLib.ptrToStringAnsi(rawptr); Warning: All Java objects are subject to movement in memory or reclamation by the garbage collector. Therefore, you should not attempt to obtain a pointer to a Java array by calling a DLL function that does generic casting. The following example shows you an incorrect way to obtain the pointer. // Do not do this! /** @dll.import("MYDLL") */ private native static int Cast(int javaarray[]); // Inside MYDLL.DLL LPVOID Cast(LPVOID ptr) { // Do not do this! return ptr; // comes in as a Java array; goes out as a Java int } The value of ptr is guaranteed to be valid only for the duration of the call to the Cast function. This is because VM implementations are allowed to implement passing of arrays by copying rather than pinning and because garbage collection may cause the physical location of the array to be different after the call to the Cast function returns. Polymorphic ParametersSome Win32 functions declare a parameter whose type depends on the value of another parameter. For example, the WinHelp function is declared as follows. BOOL WinHelp(int hwnd, LPCTSTR szHelpFile, UINT cmd, DWORD dwData); The innocent-looking dwData parameter can actually be any one of the following: a pointer to a string, a pointer to a MULTIKEYHELP structure, a pointer to a HELPWININFO, or a plain integer, depending on the value of the cmd parameter. J/Direct offers two ways to declare such a parameter:
Declaring the Parameter as Type ObjectHere is how to declare WinHelp by declaring dwData as type Object. /** @dll.import("USER32") */ static native boolean WinHelp(int hwnd, String szHelpFile, int cmd, Object dwData); When WinHelp is invoked, J/Direct will use the runtime type to determine how to translate dwData. The following table describes how the types are translated.
Overloading the FunctionAnother way to declare the WinHelp function is to overload the function for each possible type. /** @dll.import("USER32") */ static native boolean WinHelp(int hwnd, String szHelpFile, int cmd, int dwData); /** @dll.import("USER32") */ static native boolean WinHelp(int hwnd, String szHelpFile, int cmd, String dwData); /** @dll.import("USER32") */ static native boolean WinHelp(int hwnd, String szHelpFile, int cmd, MULTIKEYHELP dwData); /** @dll.import("USER32") */ static native boolean WinHelp(int hwnd, String szHelpFile, int cmd, HELPWININFO dwData); You cannot handle a polymorphic return value using overloading because Java methods cannot be overloaded on return value only. Therefore, you need to give each variant of the function a different Java name and use the entrypoint modifier to link them all to the same DLL method. (See Aliasing (Method Renaming) to learn more about renaming DLL methods.) Comparison Between the Two MethodsIn most cases (including the WinHelp case), overloading is the preferred approach because it offers superior run time performance as well as better type checking. In addition, overloading avoids the need to wrap integer arguments inside an Integer object. However, declaring the parameter as type Object can be useful in cases where there is more than one polymorphic parameter. You might also choose this method when you want to access a service that acts generically on a wide variety of types, such as a function that can accept any object declared with @dll.struct. CallbacksYou can write callbacks in Java by extending the system class com.ms.dll.Callback. Declaring a Method that Takes a CallbackTo represent a callback parameter in Java, declare the Java type to be either type com.ms.dll.Callback or a class that derives from Callback. For example, the Microsoft® Win32® EnumWindows function is prototyped as follows. BOOL EnumWindows(WNDENUMPROC wndenumproc, LPARAM lparam); The corresponding Java prototype is shown in the following example. import com.ms.dll.Callback; /** @dll.import("USER32") */ static native boolean EnumWindows(Callback wndenumproc, int lparam); Invoking a Function that Takes a CallbackTo invoke a function that takes a callback, you need to define a class that extends Callback. The derived class must expose one non-static method whose name is callback (all lowercase). Continuing with the EnumWindows previous example, the C language definition of WNDENUMPROC looks like this. BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lparam); To author an EnumWindowsProc in Java, you declare a class that extends Callback as follows. class EnumWindowsProc extends Callback { public boolean callback(int hwnd, int lparam) { StringBuffer text = new StringBuffer(50); GetWindowText(hwnd, text, text.capacity()+1); if (text.length() != 0) { System.out.println("hwnd = " + Integer.toHexString(hwnd) + "h: Text = " + text); } return true; // return TRUE to continue enumeration. } /** @dll.import("USER32") */ private static native int GetWindowText(int hwnd, StringBuffer text, int cch); } You can invoke EnumWindows with this Callback in the following way. boolean result = EnumWindows(new EnumWindowsProc(), 0); Restrictions on Types Accepted by the Callback MethodThe return type of the callback method must be void, int, boolean, char, or short. The only parameter type currently allowed is the int type. Fortunately, this is not as restrictive as it sounds. You can use the DllLib methods ptrToStringAnsi, ptrToStringUni, and ptrToString to treat a parameter as an LPTSTR. You can use the ptrToStruct method to treat a parameter as a pointer to an @dll.struct class. Associating Data with a CallbackFrequently, it is necessary to pass some data from the caller of the function to the callback. This explains why EnumWindows takes an extra lparam argument. Most Win32 functions that take callbacks accept one extra 32-bit parameter that is passed to the callback without interpretation. With the Callback mechanism, it is not necessary to pass data using the lparam argument. Because the callback method is non-static, you can store your data as fields in the EnumWindowsProc object. The Lifetime of a CallbackSome care is required to ensure that the Callback is not reclaimed by garbage collection before the native function is finished with it. If the callback is short-term (only callable for the duration of one function call), no special action is required because a Callback passed to a DLL function is guaranteed not to be reclaimed by garbage collection while the call is in progress. If a callback is long-term (used across function calls), you will need to protect the Callback from being reclaimed, typically by storing a reference to it in a Java data structure. You can also store references to Callbacks within native data structures by using the com.ms.dll.Root class to wrap the Callback inside a root handle. The root handle is a 32-bit handle that prevents the Callback from being reclaimed until the handle is explicitly freed. For example, a root handle to a WndProc can be stored in the application data area of an HWND structure, and then explicitly freed on the WM_NCDESTROY message. Embedding a Callback Inside a StructureTo embed a callback inside a structure, you can first call the com.ms.dll.Root.alloc method to wrap the Callback in a root handle. Then pass the root handle to the DllLib.addrOf method to obtain the actual (native) address of the callback. Then, store this address as an integer. For example, the WNDCLASS structure can be declared in Java as follows. /** @dll.struct() */ class WNDCLASS { int style; int lpfnWndProc; // CALLBACK ... /* <other fields deleted for brevity> */ } Let's assume you have extended Callback as follows. class WNDPROC extends Callback { public int callback(int hwnd, int msg, int wparam, int lparam) { ... } } To store a pointer to the callback inside the WNDCLASS object, use the following sequence. import com.ms.dll.*; WNDCLASS wc = new WNDCLASS(); int callbackroot = Root.alloc(new WNDPROC()); wc.lpfnWndProc = DllLib.addrOf(callbackroot);
|
© 1998 Microsoft Corporation. All rights reserved. Terms of use. |