JNA for Windows API function GetVolumePathNamesForVolumeName

后端 未结 3 1647
庸人自扰
庸人自扰 2021-01-19 13:54

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:<

相关标签:
3条回答
  • 2021-01-19 14:33

    Thanks eee for your answer. It led me to the following. You answer was almost complete. Just needed a last bit to split the resultant char[] into the path components which is separated by null characters.

    // Decleration...
    
    public interface Kernel32 extends StdCallLibrary   {
    
       public boolean GetVolumePathNamesForVolumeName(
            WString lpszVolumeName,
            char[] lpszVolumePathNames,
            int cchBufferLength,
            IntByReference lpcchReturnLength
       );
    
       // Other methods....
    }
    

    ...

    // Instantiation
    Native.loadLibrary('kernel32', Kernel32.class, W32APIOptions.UNICODE_OPTIONS)
    

    ...

    // Usage
    
    public List<String> getMountPoints() {
        char[] pathNames = new char[100];
        IntByReference len = new IntByReference();
        if (!kernel32.GetVolumePathNamesForVolumeName(new WString(this.getGuidPath()), pathNames, 100, len)) {
            if (kernel32.GetLastError() == WindowsConstants.ERROR_MORE_DATA) {
                pathNames = new char[len.getValue()];
                if (!kernel32.GetVolumePathNamesForVolumeName(new WString(this.getGuidPath()), pathNames, len.getValue(), len)) {
                    throw new WinApiException(kernel32.GetLastError());
                }
            } 
            else
                throw new WinApiException(kernel32.GetLastError());
        }
        List<String> list = new LinkedList<String>();
        int offset = 0;
        for (int i = 0; i < pathNames.length; i++) {
            if (pathNames[i] == '\u0000') {
                list.add(String.valueOf(pathNames, offset, i-offset));
                offset = i+1;
                if (pathNames[i+1] == '\u0000')
                    break;
            }
        }
        return list;
    }
    
    0 讨论(0)
  • 2021-01-19 14:35

    Using my version:

    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);
    

    Edited: this post will be updated into my previous post

    0 讨论(0)
  • 2021-01-19 14:44

    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", "-");
    
    0 讨论(0)
提交回复
热议问题