Функция JNA для Windows API GetVolumePathNamesForVolumeName - PullRequest
4 голосов
/ 15 марта 2011

Я успешно использовал 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);
}

, который не работает вообще. Пока не уверен насчет исключения, но мой код взрывается.

Это ниже виды работ:

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

Что происходит с этим, так это то, что первое значение получается хорошо, но после этого можно увидеть, что есть проблемы с кодированием, которые указывают, что у меня неправильное смещение.

Ответы [ 3 ]

5 голосов
/ 15 марта 2011

Моя версия \\?\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);

My onlyпроблема заключается в том, как JNA обрабатывает строки Юникода с нулевым символом в конце из параметра lpszVolumePathNames в методе Kernel32 GetVolumePathNamesForVolumeNameW():список букв дисков и томов GUID путей.Список представляет собой массив строк с нулевым символом в конце, оканчивающихся на дополнительный символ NULL.Если буфер недостаточно велик для размещения полного списка, буфер содержит как можно большую часть списка.

Хотя спецификация JNI гласит (я не уверен в части JNA):

10.8 Завершение строк Unicode

Строки Unicode, полученные из GetStringChars или GetStringCritical, не заканчиваются NULL.Вызовите GetStringLength, чтобы узнать количество 16-битных символов Unicode в строке.Некоторые операционные системы, такие как Windows NT, ожидают, что два завершающих нулевых байта завершают строки Unicode.Вы не можете передать результат GetStringChars в API-интерфейсы Windows NT, которые ожидают строку Unicode.Вы должны сделать еще одну копию строки и вставить два завершающих нулевых байтовых значения.

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

Отредактировано:

Itкажется, что мой код в порядке, так как параметр lpszVolumePathNames возвращает строки с нулевым символом в Юникоде, проверяя наличие в нем строк "\ u0000":

String point = getPathNames().replaceAll("\u0000", "-");
1 голос
/ 16 марта 2011

Спасибо за ответ.Это привело меня к следующему.Ваш ответ был почти завершен.Просто нужен последний бит для разбиения результирующего символа [] на компоненты пути, разделенные нулевыми символами.

// 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 голосов
/ 16 марта 2011

Используя мою версию:

Измените возвращаемое значение метода 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);

Отредактировано: этот пост будет обновлен до моего предыдущего поста

...