2011-03-15 26 views
4

我已經成功地使用JNA調用幾個Windows API函數,但我得到停留在這一個JNA的Windows API函數GetVolumePathNamesForVolumeName

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

不工作的時候。不知道有關例外,但我的代碼炸彈了。

這類作品如下:

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

與這一個會發生什麼情況是,第一個值出來很好,但一個後可以看到有編碼這表明,我已經得到了偏移問題錯誤。

+0

GetVolumePathNamesForVolumeName有兩種類型:GetVolumePathNamesForVolumeNameW(統一)和GetVolumePathNamesForVolumeNameA(ANSI)。你能告訴我們你的Kernel32 JNA接口的GetVolumePathNamesForVolumeName嗎? – eee 2011-03-15 11:23:08

+0

我編輯了Kernel32 JNA接口到我的問題。謝謝 – 2011-03-15 15:09:58

回答

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

測試應用程序:

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中處理來自lpszVolumePathNames參數NULL結尾的Unicode字符串GetVolumePathNamesForVolumeNameW()的方法,因爲:

lpszVolumePathNames [OUT]

的指針,該接收 緩衝驅動器號和卷的列表 GUID路徑。該列表是 以空字符結尾的字符串數組,該字符串由 以附加的NULL字符結尾。如果 緩衝區不足以容納完整列表,緩衝區將盡可能保留爲 。

雖然,JNI規範說(我不知道的事情JNA側):

10.8終止Unicode字符串

Unicode字符串從 GetStringChars獲得或GetStringCritical 不是空值終止。請致電 GetStringLength查找 字符串中的16位Unicode字符的編號 。某些操作系統(如Windows NT的 )預計會有兩個結尾的 零字節值來終止Unicode 字符串。您無法將 GetStringChars的結果傳遞給 需要Unicode字符串的Windows NT API。您必須製作 字符串的另一個副本,並插入兩個尾隨的零字節值 。

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

編輯:

看來,我的代碼是OK的lpszVolumePathNames參數正確地在它檢驗的「\ u0000的」串存在返回的Unicode空值終止字符串:

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

它似乎是你使用兩個不同的SO帳戶名稱「eee」,也許是偶然。您可能想在[email protected]上放一個便箋,以便將[eee](http://stackoverflow.com/users/656123/eee2)與其他[eee]合併(http://stackoverflow.com/users/316238/EEE)。 – 2011-03-16 11:12:19

+0

@Win Coenen:兩個賬戶都沒有實際註冊。爲了學習和傳播知識,我發佈了SO。 – eee 2011-03-16 12:33:13

+0

我只是把它指出來,因爲作爲另一個SO用戶進行的編輯需要批准。如果您使用相同的SO用戶,那麼立即對您自己的帖子進行編輯,沒有人需要花時間審閱和批准它們。 – 2011-03-17 09:59:38

1

非常感謝eee的回答。它讓我看到以下內容。你的回答幾乎完成。只需要最後一位將生成的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<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

使用我的版本:

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

編輯:這篇文章將被更新到我以前的帖子