JNA for Windows API函数GetVolumePathNamesForVolumeName

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

GetVolumePathNamesForVolumeName

完整的C声明是:

BOOL WINAPI GetVolumePathNamesForVolumeName( __in LPCTSTR lpszVolumeName, __out LPTSTR lpszVolumePathNames, __in DWORD cchBufferLength, __out PDWORD lpcchReturnLength ); 

我的Kernel32接口方法原型是:

 boolean GetVolumePathNamesForVolumeName(String lpszVolumeName, Pointer lpszVolumePathNames, int cchBufferLength, Pointer lpcchReturnLength); 

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

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

我试过了:

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

这根本不起作用。 不确定exception,但我的代码爆炸了。

以下是这种作品:

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

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

我的版本\\?\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(); } 

测试应用:

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

结果:

 [C, :, \, , ] 

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

编辑:改善结果值……

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

 return Arrays.toString(pathNames); 

 return new String(pathNames); 

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

 String[] points = getPathNames().split("\u0000"); //split by Unicode NULL for(String s: points) System.out.println("mount: " + s); 

我唯一担心的是JNA如何处理来自Kernel32 GetVolumePathNamesForVolumeNameW()方法中的lpszVolumePathNames参数的以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参数通过validation其中是否存在“\ u0000”字符串,正确地在Unicode中返回以NULL结尾的字符串:

 String point = getPathNames().replaceAll("\u0000", "-"); 

谢谢你的回答。 它引导我到以下。 你回答差不多完成了。 只需要最后一点将结果char []拆分为由空字符分隔的路径组件。

 // 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 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 list = new LinkedList(); 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; } 

使用我的版本:

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

 return Arrays.toString(pathNames); 

 return new String(pathNames); 

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

 String[] points = getPathNames().split("\u0000"); //split by Unicode NULL for(String s: points) System.out.println("mount: " + s); 

编辑:这篇文章将更新到我以前的post