By Marcus Daley
When people think about Java they usually associate it with applets and "thin" visual applications used in a Web browser. This image has been changing as Java matures and becomes a leading language for application designers and back-end system engineers. Microsoft provides the tools and controls necessary to exploit this growing trend. More specifically, they supplement their Java Virtual Machine with special Java classes and Windows-specific tools that facilitate a faster and more usable Java language.
One of the Windows-specific features that has been enabled is the use of shared memory. With J/Direct and the com.ms.dll package, you can memory-map files, share memory between processes, and do just about everything you want to do with the Win32 API. This is a very powerful feature for system programmers using the Java language. But why would anyone want to share memory between processes, and what does that really mean in a Win32 environment? The purpose of this article is to answer these questions, and show you how to solve them in Java.
What Is Shared Memory?Sharing memory enables different processes to rapidly exchange data directly through memory, rather than through the hard disk or another slow media. Data exchanged this way is the best solution for eliminating communication delay between processes. Shared memory is useful when real-time processes or applications need to execute in parallel against the same pages of memory. The power of shared memory can also be extended through Distributed Shared Memory (DSM). This is when the same memory is shared between different machines. Although DSM is a fascinating way to extend the benefits of shared memory to the distributed computing community, it is unfortunately beyond the scope of this article. This article focuses on sharing memory between Java processes on the same machine.
In the early days of computing, when consumer operating systems were not as advanced as they are now, shared memory was an oxymoron. Each process viewed the same memory and could do whatever they wished with it. This included writing data over another process, or even over the operating system itself. Programmers writing software for these operating systems needed to be extremely careful to build processes that were friendly to the occupied memory of other processes and the operating system.
Under a Win32 operating system, each process has its own address space, so even inspecting the memory of another process isn't a trivial matter. This increases the reliability of the operating system and makes it more secure. Users who made the jump from Windows 3.1 to Windows 95, several years ago, know how true this is. It is even more evident for those who bypassed Windows 95 and went straight to Windows NT.
Memory-mapped FilesCurrently, there is only one way to share memory between applications on a Win32 system: by using memory-mapped files. At first you may disagree and claim that PostMessage and SendMessage are additional mechanisms for facilitating shared memory. However, both of these functions use memory-mapped files internally. Memory-mapped files are either a physical file on the hard disk, or a block of memory in the operating system. When you map a file from the hard disk directly to memory you gain several benefits. These benefits include speeding I/O, and all the advantages of treating a file like RAM.
There is a Win32 API function that creates file-mapping objects. This function can be used to give the file a special name that can be recognized by more than one process. By using a special name, two or more processes can open the same file and map it directly to memory. This effectively shares the same block of disk space between the two processes.
Sharing a block of memory is achieved in the same fashion as sharing a hard-disk file. Instead of mapping a file on the hard disk, the system creates a block of memory in the pagefile. The memory is then shared between processes. This is done using either the Win32 CreateFileMapping or OpenFileMapping functions (see Figure 1).
Function | Description |
CreateFileMapping | Creates a mapping of a physical file on the hard disk, or a block of memory in the pagefile. If a file-mapping object already exists, then CreateFileMapping acts like OpenFileMapping and returns a handle to the existing file-mapping object. |
OpenFileMapping | Opens an existing file-mapping object. This function can only open file-mapping objects that have already been created and given a special name. |
CloseHandle | Closes an open handle. |
Figure 1: Win32 file-mapping functions.
The special name for a file-mapping object is specified in the sixth parameter of the CreateFileMapping function. When using Java, the special name is passed via an instance of the String class. After the file-mapping object has been created, more than one process can use the special name to open the same block of shared memory. To keep another process from using your memory block accidentally, you need to use a String representation of a GUID, or another unique identifier that cannot be easily reproduced. If you don't want another process to ever open your memory block, then you must pass null for the sixth parameter.
Calling the Win32 API GetLastError function tells you if a call to CreateFileMapping has successfully opened an existing shared memory block, or created a new one. The GetLastError function was originally designed to return the last error code value for a thread. When it's used with the CreateFileMapping function, it returns a value stating whether the file-mapping object already exists (ERROR_ALREADY_EXISTS). This is useful because it lets you know if the file-mapping object existed, or if it was just created. If it was created then you need to initialize the new shared memory block with valid data.
When you call CreateFileMapping, it returns a handle to a kernel object. The handle can then be used in subsequent API calls. In Java, a handle is represented as an int. As with all handles, once you're finished using a handle, it must be closed. This is accomplished with the CloseHandle function. The CloseHandle function accepts the handle as its only parameter, and will close any system resources associated with the handle.
Viewing a File-mapping ObjectWhen the CreateFileMapping function is used for shared memory, a portion of the system pagefile isn't allocated to accommodate the new memory block. For this to happen, the process must call the MapViewOfFile function. It's not until after you call this function that the memory is reserved and mapped into the address space of the process. The MapViewOfFile function then prepares the system resources to accommodate activity against the mapped pages of memory (see Figure 2).
Function | Description |
MapViewOfFile | Maps a view of a file or block of memory into a process' address space. This view can be of varying sizes. It cannot be larger than the size specified in the file-mapping object returned from CreateFileMapping or OpenFileMapping. |
UnmapViewOfFile | This function unmaps the memory from the calling process and writes changes. |
Figure 2: The MapViewOfFile and UnmapViewOfFile functions.
Under certain circumstances, and depending on the operating system, a call to MapViewOfFile can fail. When using Windows 95 the pagefile must be large enough to contain the full size of the memory block indicated by the file-mapping object, or a call to MapViewOfFile will fail. Under Windows NT, the pagefile only needs to be large enough to contain the size of the view for a call to succeed.
When a view of the memory block indicated by the file-mapping object has been successfully created, the memory won't be committed to the pagefile until it has been written or read. In other words, a contiguous range of addresses have been committed for the file view in your process's address space, but only faulted memory pages are written to the pagefile. This helps eliminate unnecessary I/O, and is one reason why shared memory is such a high performance means of data exchange.
A call to MapViewOfFile returns a pointer to the starting address of the mapped view. If this pointer references an entire structure, then it can be cast into whatever structure it references. Otherwise, the returned pointer can be used to iterate over each byte of the view. Either way, MapViewOfFile provides a convenient way to access a portion of shared memory.
When a process is finished with the view, and no longer needs it in its address space, you should unmap it by calling the UnmapViewOfFile function. This function has one parameter that accepts the original pointer returned by MapViewOfFile. By calling UnmapViewOfFile, the region of memory mapped by MapViewOfFile will be released. Interestingly, you can call CloseHandle on the handle returned from CreateFileMapping before calling UnmapViewOfFile. In fact, this is recommended whenever possible to ensure against accidental resource leaks.
Accessing Raw Pointers with JavaJava is portrayed as both a pointer-less language, and a language where everything is a pointer. In either case, it's widely understood that Java runs in a garbage-collected environment. This "sandbox" is intended to keep programs written in Java away from the problems of operating system-dependent memory management, and the problematic memory leaks often associated with pointers. Making use of the special classes and language extensions in the Microsoft Virtual Machine, however, can open the outside world to Java programmers. Although this is nice, it sometimes comes at a high cost. Through the use of the Microsoft-specific Java classes and J/Direct, memory leaks and other difficult problems that are hard to debug can arise. These features can even create situations where Java becomes nothing more than a glorified version of C++.
Having said that, from a systems programmer's perspective, the Microsoft-specific APIs and J/Direct are an invaluable addition to Java. By using these extensions, accessing important system functions is possible - even easy. And if you obey the rules of the API you're using, everything should work as intended. This is the reason Microsoft has created the com.ms.dll package.
In the com.ms.dll package there are numerous classes that facilitate the use of pointers, DLLs, function callbacks, etc. This article uses the DllLib class found in this package. It contains a powerful set of methods designed for working with pointers. Three DllLib-specific methods are used in the sample code in this article (see Figure 3). There are numerous other methods - not addressed in this article - that the DllLib class provides. The entire com.ms.dll package is geared towards low-level system programming.
Method | Description |
DllLib.sizeOf | Directly parallels the C/C++ function sizeof. Calling this method returns an int representing the size (in bytes) of the Class or Object passed for the method argument. |
DllLib.ptrToStruct | Maps a raw pointer to a Java class. Raw pointers are represented in Java as primitive int values. The returned Java class must be declared using the @dll.struct comment. |
DllLib.propagateStructFields | Propagates the fields of certain complex field types to, or from, native memory. In other words, this method synchronizes native memory with a Java object returned from a call to DllLib.ptrToStruct. |
Figure 3: Three DllLib class methods used in this article.
The DllLib.sizeOf method has been overloaded to accept two, alternative parameter types. One of the methods accepts a parameter of type Class; the other method accepts a parameter of type Object. There is a distinct difference in behavior between these two methods. When the method that accepts a Class for its parameter is used, the return value will represent the maximum possible size of the Class. When the method that accepts an Object for its parameter is used, the return value will represent the size of the current instance being passed. For example, a Java class may be defined to contain a single String variable of size 255 (see the next section, "J/Direct"), but an instance of the class may only be occupying 10 of the possible 255 characters. In this example, the size of the actual Class would always be 255, but the size of the Object instance would only be 10.
The DllLib.propagateStructFields method is especially enabling. This method works together with DllLib.ptrToStruct to keep native memory synchronized with the Java object that's returned from a call to DllLib.ptrToStruct. Synchronization is important because Java cannot map complex fields directly to native memory. When a call to DllLib.ptrToStruct is made, the Java Virtual Machine maps the primitive fields directly into native memory. However, complex fields (such as arrays or embedded structures) are copied to a normal Java object. This happens because the Virtual Machine keeps track of type information, and other Java-specific data, for complex types. Java-specific information cannot be stored in a C-style structure. As a result, when you change values in an Object returned from DllLib.ptrToStruct that contains more than just primitive type fields, you must call DllLib.propagateStructFields for your changes to be reflected in native memory.
J/DirectJ/Direct is one of the more powerful features of the Microsoft Virtual Machine. J/Direct is a Microsoft-specific technology that provides access to external DLL's and C APIs. When compared to the more traditional solution of JNI or RNI, J/Direct is far more eloquent because it doesn't require the use of wrapper DLLs, or proxy code. This is especially enabling to system programmers who need high-speed access to the Win32 API, as well as third-party DLLs.
To share memory between multiple Java Virtual Machines, this article uses J/Direct and the Win32 API. Much of the J/Direct code was generated using the J/Direct Call Builder included with Visual J++ 6. The J/Direct Call Builder is a simple user interface that displays a list of Win32 API functions, structs, and constants. When you select any one of these from the list, the J/Direct Call Builder writes the paired J/Direct call into your selected Java file.
To see the J/Direct Call Builder, first load the Visual J++ 6 IDE. Then select View | Other Windows from the menu. You will then see a list of tools specifically designed for Visual J++ 6. These tools are important, and one of them is the J/Direct Call Builder. Once you select this tool you will be presented with its simple user interface (see Figure 4).
Figure 4: The Visual J++ 6 IDE with J/Direct Call Builder displayed.
When you view the source code in Listings One and Two, you'll see several J/Direct calls to specific Win32 API functions, and their required constants. Above each function declaration is a specially formulated Java comment. These comments are compiler directives, and are commonly used for Microsoft extensions to the Java specification. The compiler directive used to access DLLs is @dll.import. In the listings, the @dll.import directive indicates that the Win32 API function calls should be made through Kernel32.dll via J/Direct. If a function was defined in another DLL, then the DLL's name should appear in the @dll.import parameter, instead of Kernel32.dll. It's important to note that the keyword auto was included in the @dll.import directive. This means the Virtual Machine will automatically determine whether the platform it's running on supports the Unicode API (Windows NT), or the ANSI API (Windows 95). In special cases, when there is only an ANSI API or a Unicode API, it should be specified in the @dll.import directive as "ansi" or "unicode".
The SharedStruct class (see Figure 5) uses two types of J/Direct compiler directives. The first is @dll.struct. This directive specifies that the class following it represents a C-language structure. The directive locks the class in memory, so it won't move during garbage collection. The other compiler directive is @dll.structmap, which defines C-language, fixed-sized, scalar arrays. The @dll.structmap directive remaps the String variable into a common C-language char array.
The code in Figure 5 demonstrates the need to remap the String type when using shared memory. To use the DllLib.sizeOf method, a class with a clearly defined size must be used. The String variable of the SharedStruct class has been remapped to a fixed size of 255 characters. By defining the size, the DllLib.sizeOf method is able to correctly determine the maximum size of the class.
// This C structure is passed between VMs.
/**@dll.struct()*/
public class SharedStruct
{
/** @dll.structmap([type=TCHAR[255]]) */
public String data;
public int length;
}
Figure 5: The SharedStruct class.
Building a Simple Java ServerThere are several ways to use shared memory. The most common is between a server and multiple clients. The source code for a Java server and client have been included in this article. They're both command-prompt based Java programs (and both are available for download; see the end of this article for details). The Java server lets a user enter text at the command prompt. The text is then displayed by the client(s). You can load several clients at the same time, and because they all view the same shared memory, they always display the last entry submitted by the server.
A common class named SharedStruct is shared between the server and client. You can see the source code for the SharedStruct class in Figure 5, and the source code for the simple Java server in Listing One. The server source code demonstrates how to create a file-mapping object, how to map a view of an object into a process' address space, and how to make use of a shared memory block.
Stepping through the ServerStep 1. Before using a shared memory block, you must first create a file-mapping object. When creating a file-mapping object that represents a block of shared memory, the first parameter to CreateFileMapping must be given a handle of 0xFFFFFFFF. This tells the Win32 API that a block of memory needs to be allocated in the pagefile instead of on disk. The third parameter denotes that the memory needs to be opened for writing.
The largest size possible for the SharedStruct class was entered as the fifth parameter via a call to the DllLib.sizeOf function. This lets any instance of SharedStruct be stored in the shared memory block. In the sixth parameter, the memory block was named "TestSharedMemory". The sample Java client uses this name to open the new shared memory block. If null was passed into this parameter, the memory block would remain nameless and could not be mapped between processes.
Step 2. Once the file-mapping object is created, a view of the memory needs to be mapped into the process' address space. This is done by calling the MapViewOfFile function. The first parameter is filled with the handle that's returned after calling the CreateFileMapping function. The second parameter denotes the view is open for writing. To map the entire memory block into the process space, the last three parameters are filled with zeroes.
Step 3. To map the raw pointer returned from MapViewOfFile into something meaningful, a call to the DllLib.ptrToStruct method is required. This method accepts a raw pointer as its only parameter. Once the DllLib.ptrToStruct method is called, an instance of type Object is returned. This instance is then cast into the appropriate Java type, SharedStruct. Because the server has just created the view of memory, the values in the new SharedStruct instance will always be 0 or empty.
The int field of the SharedStruct instance is mapped directly to native memory. Changes to this field are instantly reflected in native memory. However, it's important to remember that the fixed array in the SharedStruct has been remapped to a normal Java object. Any changes to the fixed array won't be reflected in native memory until a call to DllLib.propagateStructFields is made (in Step 5).
Step 4. A new byte array was created to contain text entered on the command line. Please note that in the SharedStruct definition, the size of the byte array parallels the size of the char array. The System.in.read method is designed to read the maximum number of bytes from the input stream. If that number exceeds the size of the byte array, it continues reading from the input stream in a subsequent call to System.in.read.
The byte array is filled with text from the keyboard. Trailing white space is trimmed from the resulting String object. The String object and its length are then passed to the SharedStruct member variables. If quit is entered at the command prompt, the do...while loop will end, and the server will shut down.
Step 5. Anytime changes have been made to the SharedStruct instance, a call must be made to the DllLib.propagateStructFields method. This method copies the fixed array field in the SharedStruct instance to native memory. To initiate this copy, the SharedStruct instance is passed as the first parameter, and a boolean value of true was passed as the second parameter. This is done in the call to DllLib.propagateStructFields. Changes to the SharedStruct instance are reflected in native memory once the method returns.
Step 6. Calls to UnmapViewOfFile and CloseHandle were made to close system resources, and unmap the shared memory from the process' address space. Please note that the call to CloseHandle happens at the end of the server's lifetime. If the handle was closed immediately after calling MapViewOfFile (as done in the client source code), then the file-mapping object wouldn't be available to the clients. For a shared memory block to persist, there has to be an open handle to the file-mapping object. Once the handle is closed, the special name is no longer valid, unless another handle remains open elsewhere on the system.
Stepping through the ClientThe client code represented in Listing Two is similar to the server code. However, there are some subtle differences. For example, instead of using the CreateFileMapping function, the client makes a call to the OpenFileMapping function. Then the client calls MapViewOfFile, and immediately following that call, closes the handle to the file-mapping object. These differences and others are described in the following step-by-step explanation.
Step 1. The client relies on the server to create and open the shared memory block. For this reason, the client makes a call to OpenFileMapping instead of CreateFileMapping. The OpenFileMapping function assumes there is an existing file-mapping object on the system. If there isn't, the OpenFileMapping will return 0. In Listing Two, the client only requires that you have read access to the shared memory block. Therefore, it is only opened with FILE_MAP_READ access. The second parameter is passed as false because there will never be any child processes. The last parameter of OpenFileMapping requires a String object that contains the special name of the shared memory block. If the server was started and the shared memory block was created successfully, then OpenFileMapping will attempt to open the named shared block of memory. To facilitate this, the server and the client use the same name for the shared memory block.
Step 2. There is only one difference between the server and the client with regard to the MapViewOfFile function: the client only requests read access. To facilitate this, the second parameter is passed the constant FILE_MAP_READ. The remaining parameters are identical to the server in Listing One; they simply map a view of the entire shared memory block.
Next, the client closes the handle opened by OpenFileMapping in Step 1. The client doesn't need to leave the handle open, because the server keeps it open. Also, the operating system keeps an internal usage count for file-mapping objects. This lets a process continue using the view of a file even after the handle to the file-mapping object is closed.
Step 3. The current value of the SharedStruct instance stored in the shared memory block, is retrieved by a call to the DllLib.ptrToStruct method. This method casts a raw pointer into a Java class declared by the @dll.struct compiler directive. This allows you to effectively represent the current value of the data indicated by the raw pointer, as a specially mapped class instance.
Step 4. At this point, the client enters a while loop that checks the String variable in the SharedStruct instance for the String, "quit". If the client finds "quit," the while loop ends. Otherwise, the while loop runs indefinitely.
Next, a call to DllLib.propagateStructFields is made. As discussed earlier in this article, the fixed array in the SharedStruct instance has been remapped from native memory to a normal Java object. If changes are made in native memory, the client will only see changes to the int field until it makes a call to the DllLib.propagateStructFields method. Once called, this method will copy the current fixed array data from native memory to the normal Java object. To facilitate a copy from native memory, a value of false is passed for the second parameter in the call to DllLib.propagateStructFields.
Step 5. The client CPU usage is kept to a minimum because the client sleeps for 1000 milliseconds at the bottom of the while loop. Once the loop has ended, a call to UnmapViewOfFile is made. This closes the raw pointer, and unmaps the view of shared memory from the process's address space.
Memory SynchronizationThere are no major synchronization issues with the sample server and client. However, synchronization is usually a major concern - especially on a multi-processor system. For example, let's suppose there are two servers trying to write to the same block of shared memory. If one server process is unable to finish writing before the other server process begins writing, the data could become inaccurate or corrupt. Synchronization between processes keeps multiple processes from writing to the same block of memory at the same time.
Mutex and semaphore kernel objects are used to avoid this problem. Their strict use maintains data integrity. Mutexes and semaphores are provided by the Win32 API to synchronize data shared between threads and processes. J/Direct definitions for the CreateMutex function and the CreateSemaphore function are shown in Figure 6. The functions are used in a similar fashion. When using mutexes, a process first creates the mutex (via CreateMutex) and gives it a special name. The mutex's special name has a similar purpose to special names used in shared memory.
/** @dll.import("Kernel32", auto) */
public static native int CreateMutex(
com.ms.win32.SECURITY_ATTRIBUTES lpMutexAttributes,
boolean bInitialOwner, String lpName);
/** @dll.import("Kernel32", auto) */
public static native int CreateSemaphore(
com.ms.win32.SECURITY_ATTRIBUTES lpSemaphoreAttributes,
int lInitialCount, int lMaximumCount, String lpName);
Figure 6: Definitions of the CreateMutex and CreateSemaphore functions.
After CreateMutex is called, a kernel object is returned in handle form. This handle can be used with the Win32 API functions, WaitForSingleObject(Ex) or WaitForMultipleObjects(Ex). They both return values that indicate whether the mutex or semaphore object is signaled or nonsignaled. Then, depending on the object's state, the calling process can proceed.
Thread synchronization between Java processes will be covered in more detail in a future article. However, it's important to remember that if you plan to build onto the source code in this article, then you must make careful use of synchronization. In multithreaded or multiprocess environments where parallel data is shared, synchronization becomes a major issue regardless of your programming language.
ConclusionSharing memory between processes isn't something that usually comes to mind when thinking about Java. However, shared memory is often a necessity in Java when multiple processes running on one system require high-performance data exchange. Through the use of J/Direct and the com.ms.dll.DllLib class, shared memory and other system APIs are trivial to use. In systems where data is shared in parallel between threads or processes, synchronization is very important.
Microsoft has added several extensions to the Java language. Numerous Windows-specific packages are included in their latest SDK. It is through these powerful new tools that Java programmers are able to make use of the system APIs that are usually the sole bastion of C and C++ programmers.
Marcus Daley is a software engineer at Webridge, building Java applications for customer, partner, and channel management. He has been involved with Java development since its birth. His first major accomplishment with Java was in 1995 while working for Utah State University, where he pioneered research into 3D modeling applications written in Java and VRML. Later, he joined a startup GIS company based in northern Utah, where he developed Java-based intranet solutions for small businesses and local governments. Since then he has consulted several companies throughout northern Utah, and worked together with many respected professionals in the Java community. Marcus lives in Portland, OR, and can be reached at marcus@mdaley.net.
Begin Listing One - Server/**
* This class can take a variable number of ...
*/
import com.ms.dll.*;
public class Server
{
/**
* The main entry point for the application.
*
* @param args Array of parameters passed to the
* application via the command line.
*/
public static void main(String[] args)
throws Exception
{
int mappedFile = 0,
rawPointer = 0;
byte text[] = null;
SharedStruct shared = null;
// STEP 1
mappedFile = CreateFileMapping(0xFFFFFFFF, null,
PAGE_READWRITE, 0,
DllLib.sizeOf(SharedStruct.class),
"TestSharedMemory");
// STEP 2
rawPointer = MapViewOfFile(mappedFile,
FILE_MAP_WRITE, 0, 0, 0);
// STEP 3
shared = (SharedStruct)DllLib.ptrToStruct(
SharedStruct.class, rawPointer);
do
{
// STEP 4
text = new byte [255];
System.out.println(
"Input text to send to client(type \"quit\" to exit): ");
System.in.read(text);
shared.data = new String(text).trim();
shared.length = shared.data.length();
// STEP 5
DllLib.propagateStructFields(shared, false);
}
while (!shared.data.equals("quit"));
// STEP 6
UnmapViewOfFile(rawPointer);
CloseHandle(mappedFile);
}
// J/Direct Call Builder generated methods and constants.
/** @dll.import("KERNEL32",auto) */
public static native int CreateFileMapping(int hFile,
com.ms.win32.SECURITY_ATTRIBUTES
lpFileMappingAttributes, int flProtect,
int dwMaximumSizeHigh, int dwMaximumSizeLow,
String lpName);
/** @dll.import("KERNEL32",auto) */
public static native int MapViewOfFile(
int hFileMappingObject, int dwDesiredAccess,
int dwFileOffsetHigh, int dwFileOffsetLow,
int dwNumberOfBytesToMap);
/** @dll.import("KERNEL32",auto) */
public static native boolean UnmapViewOfFile(
int lpBaseAddress);
/** @dll.import("KERNEL32",auto) */
public static native boolean CloseHandle(int hObject);
public static final int PAGE_READWRITE = 0x04;
public static final int FILE_MAP_WRITE = 0x0002;
}
End Listing One
Begin Listing Two - Client
/**
* This class can take a variable number of ...
*/
import com.ms.dll.*;
public class Client
{
/**
* The main entry point for the application.
* ...
*/
public static void main(String[] args)
throws Exception
{
int mappedFile = 0,
rawPointer = 0,
oneSecond = 1000;
SharedStruct shared = null;
// STEP 1
mappedFile = OpenFileMapping(FILE_MAP_READ, false,
"TestSharedMemory");
// STEP 2
rawPointer = MapViewOfFile(mappedFile, FILE_MAP_READ,
0, 0, 0);
CloseHandle(mappedFile);
// STEP 3
shared = (SharedStruct)DllLib.ptrToStruct(
SharedStruct.class, rawPointer);
// STEP 4
while (!shared.data.equals("quit"))
{
DllLib.propagateStructFields(shared, true);
System.out.println("Data = " + shared.data);
System.out.println("Length = " + shared.length);
// STEP 5
Thread.sleep(oneSecond);
}
UnmapViewOfFile(rawPointer);
}
// J/Direct Call Builder generated methods and constants.
/** @dll.import("KERNEL32", auto) */
public static native int OpenFileMapping(
int dwDesiredAccess, boolean bInheritHandle,
String lpName);
/** @dll.import("KERNEL32",auto) */
public static native int MapViewOfFile(
int hFileMappingObject, int dwDesiredAccess,
int dwFileOffsetHigh, int dwFileOffsetLow,
int dwNumberOfBytesToMap);
/** @dll.import("KERNEL32",auto) */
public static native boolean UnmapViewOfFile(
int lpBaseAddress);
/** @dll.import("KERNEL32",auto) */
public static native boolean CloseHandle(int hObject);
public static final int FILE_MAP_READ = 0x0004;
}
End Listing Two