I've successfully used JNA to call a couple of Windows API functions but I get stuck at this one
GetVolumePathNamesForVolumeName
The full C declaration is:
BOOL WINAPI GetVolumePathNamesForVolumeName(
__in LPCTSTR lpszVolumeName,
__out LPTSTR lpszVolumePathNames,
__in DWORD cchBufferLength,
__out PDWORD lpcchReturnLength
);
My Kernel32 interface method prototype for this is:
boolean GetVolumePathNamesForVolumeName(String lpszVolumeName, Pointer lpszVolumePathNames, int cchBufferLength, Pointer lpcchReturnLength);
and I use the below to load the interface
Native.loadLibrary('kernel32', Kernel32.class, W32APIOptions.UNICODE_OPTIONS)
I've tried:
public String[] getPathNames() {
Memory pathNames = new Memory(100);
Memory len = new Memory(4);
if (!kernel32.GetVolumePathNamesForVolumeName(this.getGuidPath(), pathNames, 100, len)) {
if (kernel32.GetLastError() == WindowsConstants.ERROR_MORE_DATA) {
pathNames = new Memory(len.getInt(0));
if (!kernel32.GetVolumePathNamesForVolumeName(this.getGuidPath(), pathNames, len.getInt(0), len)) {
throw new WinApiException(kernel32.GetLastError());
}
}
else
throw new WinApiException(kernel32.GetLastError());
}
int count = len.getInt(0);
return pathNames.getStringArray(0, true);
}
which does not work at all. Not sure about the exception yet but my code bombs out.
This below sort of works:
public String[] getPathNames() {
Memory pathNames = new Memory(100);
Memory len = new Memory(4);
if (!kernel32.GetVolumePathNamesForVolumeName(this.getGuidPath(), pathNames, 100, len)) {
if (kernel32.GetLastError() == WindowsConstants.ERROR_MORE_DATA) {
pathNames = new Memory(len.getInt(0));
if (!kernel32.GetVolumePathNamesForVolumeName(this.getGuidPath(), pathNames, len.getInt(0), len)) {
throw new WinApiException(kernel32.GetLastError());
}
}
else
throw new WinApiException(kernel32.GetLastError());
}
int count = len.getInt(0);
String[] result = new String[count];
int offset = 0;
for (int i = 0; i < count; i++) {
result[i] = pathNames.getString(offset, true);
offset += result[i].length() * Native.WCHAR_SIZE + Native.WCHAR_SIZE;
}
return result;
}
What happens with this one is that the first value come out fine but after that one can see that there is encoding problems which indicate that I've got the offset wrong.
My version \\?\Volume{5b57f944-8d60-11de-8b2a-806d6172696f}\
should get C:\
Kernel32 Interface:
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
public interface Kernel32 extends StdCallLibrary {
public boolean GetVolumePathNamesForVolumeNameW(
WString lpszVolumeName,
char[] lpszVolumePathNames,
DWORD cchBufferLength,
IntByReference lpcchReturnLength
);
public int GetLastError();
}
Test application:
import java.util.Arrays;
import com.sun.jna.Native;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.ptr.IntByReference;
public class TestJNA {
static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32.dll", Kernel32.class);
/**
* @param args
*/
public static void main(String[] args) {
try {
System.out.println(getPathNames());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String getPathNames() throws Win32Exception {
DWORD value = new DWORD(100);
char[] pathNames = new char[100];
IntByReference len = new IntByReference();
if (kernel32.GetVolumePathNamesForVolumeNameW(getGuidPath(), pathNames, value, len)) {
if (kernel32.GetLastError() == WindowsConstants.ERROR_MORE_DATA) {
pathNames = new char[len.getValue()];
DWORD sz = new DWORD(len.getValue());
if (!kernel32.GetVolumePathNamesForVolumeNameW(getGuidPath(), pathNames, sz, len)) {
throw new Win32Exception(kernel32.GetLastError());
}
}
else
throw new Win32Exception(kernel32.GetLastError());
}
return Arrays.toString(pathNames);
}
private static WString getGuidPath() {
final WString str = new WString("\\\\?\\Volume{5b57f944-8d60-11de-8b2a-806d6172696f}\\");
return str;
}
}
Result:
[C, :, \, , ]
To double-check it, I type at DOS command prompt: mountvol
Edited: To improve of the result value...
Change the return value of getPathNames()
method, from:
return Arrays.toString(pathNames);
to
return new String(pathNames);
In my test application, you can just:
String[] points = getPathNames().split("\u0000"); //split by Unicode NULL
for(String s: points) System.out.println("mount: " + s);
My only concern is how JNA handles NULL-terminated Unicode strings from lpszVolumePathNames
parameter in Kernel32 GetVolumePathNamesForVolumeNameW()
method since:
lpszVolumePathNames [out]
A pointer to a buffer that receives the list of drive letters and volume GUID paths. The list is an array of null-terminated strings terminated by an additional NULL character. If the buffer is not large enough to hold the complete list, the buffer holds as much of the list as possible.
Though, JNI specification says (I am not sure on JNA side of thing):
10.8 Terminating Unicode Strings
Unicode strings obtained from GetStringChars or GetStringCritical are not NULL-terminated. Call GetStringLength to find out the number of 16-bit Unicode characters in a string. Some operating systems, such as Windows NT, expect two trailing zero byte values to terminate Unicode strings. You cannot pass the result of GetStringChars to Windows NT APIs that expect a Unicode string. You must make another copy of the string and insert the two trailing zero byte values.
http://java.sun.com/docs/books/jni/html/pitfalls.html
Edited:
It seems that my code is OK as the lpszVolumePathNames
parameter returns NULL-terminated strings in Unicode correctly by verifying the presence of "\u0000" strings within it:
String point = getPathNames().replaceAll("\u0000", "-");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With