Receiving a String from a DLL Function

There 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 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 temporary file 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. The following 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(), 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 a Java-friendly wrapper 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.