JNA Function for Windows API GetVolumePathNamesForVolumeName

I have successfully used JNA to call several Windows API functions, but I am stuck with this

GetVolumePathNamesForVolumeName

Full ad C:

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

The prototype of the My Kernel32 interface method for this:

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

and I use below to load the interface

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

I 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, but my code explodes.

These are the types of work below:

 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 is that the first value turned out great, but after that you can see that there are encoding problems that indicate an incorrect offset.

+4
source share
3 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(); } 

Testing:

 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, at a DOS prompt, type: mountvol

Edited: To improve the value of the result ...

Change the return value of the getPathNames() method from:

 return Arrays.toString(pathNames); 

to

 return new String(pathNames); 

In my test application, you can simply:

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

My only problem is how JNA processes Unicode strings with lpszVolumePathNames NULL characters with the lpszVolumePathNames parameter in the Kernel32 GetVolumePathNamesForVolumeNameW() method, because:

lpszVolumePathNames [out]

Pointer to a buffer that receives a list of letters and disk space. GUID paths. The list is an array of null-terminated strings, terminated by an optional NULL character. If the buffer is not large enough to make a complete list, the buffer is stored as much of the list as possible.

Although, the JNI spec says (I'm not sure about the JNA side of things):

10.8 Completing Unicode Strings

Unicode strings received from GetStringChars or GetStringCritical do not end with NULL. Call GetStringLength to find out the number of 16-bit Unicode characters in a string. Some operating systems, such as Windows NT, expect two zero byte values ​​to complete a Unicode string. You cannot pass the result of GetStringChars to the Windows NT APIs that expect a Unicode string. You must make another copy of the string and paste the value of the two trailing zero bytes.

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

Edited by:

My code seems to be in order, as the lpszVolumePathNames parameter returns Unicode strings in zero correctly, checking for the presence of the string "\ u0000" in it:

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

Thanks for your reply. This led me to the following. You answered almost completely. You just need the last bit to split the resulting char [] into path components that are 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; } 
+1
source

Using my version:

Change the return value of the getPathNames() method from:

 return Arrays.toString(pathNames); 

to

 return new String(pathNames); 

In my test application, you can simply:

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

Edited: this post will be updated in my previous post

0
source

Source: https://habr.com/ru/post/1343729/


All Articles