JNA for Windows API函数GetVolumePathNamesForVolumeName

Han*_*ger 4 java jna

我已经成功地使用JNA调用了几个Windows API函数,但是我遇到了这个问题

GetVolumePathNamesForVolumeName

完整的C声明是:

BOOL WINAPI GetVolumePathNamesForVolumeName(
  __in   LPCTSTR lpszVolumeName,
  __out  LPTSTR lpszVolumePathNames,
  __in   DWORD cchBufferLength,
  __out  PDWORD lpcchReturnLength
);
Run Code Online (Sandbox Code Playgroud)

我的Kernel32接口方法原型是:

boolean GetVolumePathNamesForVolumeName(String lpszVolumeName, Pointer lpszVolumePathNames, int cchBufferLength, Pointer lpcchReturnLength);
Run Code Online (Sandbox Code Playgroud)

我使用下面的方法加载界面

Native.loadLibrary('kernel32', Kernel32.class, W32APIOptions.UNICODE_OPTIONS)
Run Code Online (Sandbox Code Playgroud)

我试过了:

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);
}
Run Code Online (Sandbox Code Playgroud)

这根本不起作用.尚不确定异常,但我的代码爆炸了.

以下是这样的作品:

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;
}
Run Code Online (Sandbox Code Playgroud)

这个会发生什么,第一个值很好,但之后可以看到存在编码问题,表明我的偏移量是错误的.

eee*_*eee 5

我的版本\\?\Volume{5b57f944-8d60-11de-8b2a-806d6172696f}\应该得到C:\

Kernel32接口:

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();
}
Run Code Online (Sandbox Code Playgroud)

测试应用:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

[C, :, \, , ]
Run Code Online (Sandbox Code Playgroud)

要仔细检查它,我在DOS命令提示符下键入: mountvol

编辑:改善结果值...

更改getPathNames()方法的返回值,来自:

return Arrays.toString(pathNames);
Run Code Online (Sandbox Code Playgroud)

return new String(pathNames);
Run Code Online (Sandbox Code Playgroud)

在我的测试应用程序中,您可以:

String[] points = getPathNames().split("\u0000"); //split by Unicode NULL

for(String s: points) System.out.println("mount: " + s);
Run Code Online (Sandbox Code Playgroud)

我唯一担心的是JNA如何处理来自lpszVolumePathNamesKernel32 GetVolumePathNamesForVolumeNameW()方法中的参数的NULL终止的Unicode字符串,因为:

lpszVolumePathNames [out]

指向缓冲区的指针,该缓冲区接收驱动器号和卷GUID路径列表.该列表是一个以空值终止的字符串数组,由另一个NULL字符终止.如果缓冲区不足以容纳完整列表,则缓冲区将尽可能多地保留列表.

虽然,JNI规范说(我不确定JNA方面的事情):

10.8终止Unicode字符串

从GetStringChars或GetStringCritical获取的Unicode字符串不以NULL结尾.调用GetStringLength以查找字符串中的16位Unicode字符数.某些操作系统(如Windows NT)需要两个尾随零字节值来终止Unicode字符串.您不能将GetStringChars的结果传递给期望Unicode字符串的Windows NT API.您必须创建该字符串的另一个副本并插入两个尾随的零字节值.

http://java.sun.com/docs/books/jni/html/pitfalls.html

编辑:

似乎我的代码没问题,因为lpszVolumePathNames参数通过验证其中是否存在"\ u0000"字符串,正确地返回Unicode中以NULL结尾的字符串:

String point = getPathNames().replaceAll("\u0000", "-");
Run Code Online (Sandbox Code Playgroud)