Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JNA for Windows API function GetVolumePathNamesForVolumeName

Tags:

java

jna

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.

like image 786
Hannes de Jager Avatar asked Mar 15 '11 07:03

Hannes de Jager


1 Answers

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", "-");
like image 140
eee Avatar answered Nov 15 '22 11:11

eee