Winsock2 и JNA, не удается заставить WSAEnumProtocols() работать должным образом

(возможно, я объяснил проблему с раздражающим уровнем детализации, версия tl;dr находится внизу)

Я пытался использовать JNA для создания простого тестового приложения Winsock на Java. Первой функцией, которую я вызвал, была WSAStartup(), использующая следующий код:

public interface Ws2_32 extends Library {
    Ws2_32 INSTANCE = (Ws2_32) Native.loadLibrary("ws2_32", Ws2_32.class);

    int WSAStartup(short version, LPWSADATA lpwsaData);

}

public static void main(String[] args){
    LPWSADATA   lpwsaData = new LPWSADATA();
    short       version = 2;
    int         result = Ws2_32.INSTANCE.WSAStartup(version,lpwsaData);

    System.out.println("WSAStartup() returned: " + result);

    if((resultado = Ws2_32.INSTANCE.WSAStartup(version,lpwsaData)) == 0){
        System.out.println("LPWSADATA struct:");
        System.out.println("wVersion: " + lpwsaData.wVersion);
        System.out.println("wHighVersion: " + lpwsaData.wHighVersion);

        System.out.print("szDescription: ");
        for(byte b : lpwsaData.szDescription){
            System.out.print((char) b);
        }
        System.out.print("\n");

        System.out.print("szSystemStatus: ");
        for(byte b : lpwsaData.szSystemStatus){
            System.out.print((char) b);
        }
        System.out.print("\n");

        System.out.println("iMaxSockets: " + lpwsaData.iMaxSockets);
        System.out.println("iMaxUdpDg: " + lpwsaData.iMaxUdpDg");
    }
}

Это работает, и я получаю следующие значения:

wВерсия: 2

wHighVersion: 514

szОписание: WinSock 2.0

szSystemStatus: работает

iMaxSockets: 0

iMaxUdpDg: 0

После успешного возврата WSAStartup() я попытался вызвать WSAEnumProtocols(), но получил следующую ошибку:

Исключение в потоке «основной» java.lang.UnsatisfiedLinkError: ошибка при поиске функции «WSAEnumProtocols»: указанная процедура не найдена.

Затем я открыл ws2_32.dll с помощью Dependency Walker и обнаружил, что функции с таким именем нет. Я нашел только 3 с похожим именем: WSAEnumProtocolsA(), WSAEnumProtocolsW() и WSCEnumProtocols(). Я собираюсь использовать WSAEnumProtocolsA() в качестве примера, но я использовал следующую процедуру со всеми тремя и получил одинаковые результаты:

Сначала я вызвал WSAStartup(), и никакой ошибки не было возвращено. Согласно определению WSAEnumProtocols в MSDN, первый вызов функции должен быть примерно таким: WSAEnumProtocols(null, wsaprotocol_info, lpdwBufferLength) буфер. Если эта длина равна нулю, то функция должна вернуть -1 (SOCKET_ERROR), а вызов WSAGetLastError() должен вернуть WSAENOBUFS, что означает, что буфер недостаточно велик, чтобы содержать информацию, возвращаемую WSAEnumProtocols(), и должен установить переменную lpdwBufferLength. с минимальным размером буфера, который можно передать в WSAEnumProtocols для получения всей запрошенной информации. Я не могу заставить это работать. WSAEnumProtocols() возвращает -1, но значение lpdwBufferLength не изменяется, а WSAGetLastError() возвращает 0 вместо 10055 (WSAENOBUFS)

Я также пытался использовать WSASetLastError() и установить для него другой код ошибки, а затем вызвать WSAGetLastError(), но он всегда возвращал 0.

tl;dr Я не могу заставить WSAEnumProtocols(null, wsaprotocol_info, lpdwBufferLength) работать. WSAEnumProtocols() возвращает -1, но значение lpdwBufferLength не изменяется, а WSAGetLastError() возвращает 0 вместо 10055 (WSAENOBUFS)

ОБНОВЛЕНИЕ:

Это интерфейс, который я использую для объявления функций Winsock.

public interface Ws2_32 extends Library {
    Ws2_32 INSTANCE = (Ws2_32) Native.loadLibrary("ws2_32", Ws2_32.class);

    int WSAStartup(short version, LPWSADATA lpwsaData);
    int WSAEnumProtocolsW(int[] lpiProtocols, WSAPROTOCOL_INFO lpProtocolBuffer, int lpdwBufferLength);
    int WSACleanup();
    int WSAGetLastError();
    int WSASetLastError(int iError);
}

Это код, из которого я вызываю функции:

public class TestWSAEnumProtocolsA {

    public void start(){

        WSAPROTOCOL_INFO        wsaprotocol_info = new WSAPROTOCOL_INFO();
        LPWSADATA               lpwsaData = new LPWSADATA();
        int                     lpdwBufferLength = -2;
        int                     result = 0;
        short                   version = 514;

        if((result = Ws2_32.INSTANCE.WSAStartup(version, lpwsaData)) != 0){
            System.out.println("Error #" + result + " at WSAStartup()");
            return;
        } else {
            System.out.println("WSAStartup() finished correctly.");

            if((result = Ws2_32.INSTANCE.WSAEnumProtocols(null, wsaprotocol_info, lpdwBufferLength)) == -1){
                System.out.println("WSAEnumProtocolsW() returned: " + result);
                System.out.println("lpdwBufferLength is: " + lpdwBufferLength);

                System.out.println("WSAGetLastError() returned: " + Ws2_32.INSTANCE.WSAGetLastError());

                System.out.println("Now I'm setting it to 10004");
                Ws2_32.INSTANCE.WSASetLastError(10004);
                System.out.println("WSAGetLastError() returned: " + Ws2_32.INSTANCE.WSAGetLastError());
            }
        }
    }
}

Этот код выдал следующий результат:

WSAStartup() завершен правильно.

WSAEnumProtocolsW() вернул: -1

lpdwBufferLength: -2

WSAGetLastError() возвращено: 0

Теперь я устанавливаю его на 10004

WSAGetLastError() возвращено: 0

Вот как я определил структуры, которые я использую при вызове этих функций:

public class WinSock2_structs {

    public static class LPWSADATA extends Structure{

        public short            wVersion;
        public short            wHighVersion;
        public byte             szDescription[] = new byte[256+1];
        public byte             szSystemStatus[] = new byte[128+1];
        public short            iMaxSockets;
        public short            iMaxUdpDg;
        public char             lpVendorInfo;       
}

    public static class WSAPROTOCOLCHAIN extends Structure{

        public int   ChainLen;
        public int   ChainEntries[] = new int[7];
    }

    public static class GUID extends Structure{

        public int      Data1;
        public short    Data2;
        public short    Data3;
        public short    Data4;
        public byte     Data5[] = new byte[8];

    }

    public static class WSAPROTOCOL_INFO extends Structure{

        public int                  dwServiceFlags1;
        public int                  dwServiceFlags2;
        public int                  dwServiceFlags3;
        public int                  dwServiceFlags4;
        public int                  dwProviderFlags;
        public GUID                 ProviderId;
        public int                  dwCatalogEntryId;
        public WSAPROTOCOLCHAIN     ProtocolChain;
        public int                  iVersion;
        public int                  iAddressFamily;
        public int                  iMaxSockAddr;
        public int                  iMinSockAddr;
        public int                  iSocketType;
        public int                  iProtocol;
        public int                  iProtocolMaxOffset;
        public int                  iNetworkByteOrder;
        public int                  iSecurityScheme;
        public int                  dwMessageSize;
        public int                  dwProviderReserved;
        public char                 szProtocol[] = new char[256];

    }
}

person Hernán Erasmo    schedule 28.02.2013    source источник
comment
Пожалуйста, покажите свой код, который пытается использовать WSAEnumProtocols() и WSAGetLastError().   -  person Remy Lebeau    schedule 01.03.2013


Ответы (1)


Вы вызываете WSAStartup() дважды, что потребует от вас двойного вызова WSACleanup() для правильной выгрузки WinSock. Вы должны звонить WSAStartup() только один раз.

Элемент szProtocol структуры WSAPROTOCOL_INFO представляет собой массив из TCHAR элементов. TCHAR сопоставляется с char или wchar_t в зависимости от того, скомпилировано ли вызывающее приложение для ANSI/MBCS или UNICODE. Именно поэтому в ws2_32.dll нет функции WSAEnumProtocols(). Вместо этого есть отдельные функции WSAEnumProtocolsA() (для Ansi) и WSAEnumProtocolsW() (для Unicode). Поскольку Java использует строки Unicode, в коде JNA следует использовать WSAEnumProtocolsW(). WSAStartup() не использует TCHAR, только char, поэтому для него нет отдельных функций WSAStartupA() и WSAStartupW().

Если ваш код JNA не может заставить WSAEnumProtocols() и WSAGetLastError() работать правильно, вполне вероятно, что вы объявляете/используете их неправильно, но вы не показали какой-либо из этого кода, поэтому никто не может точно сказать, почему он не работает для вас. .

Обновление: попробуйте что-то вроде этого (я не использую JNA, поэтому может потребоваться некоторая настройка, но это даст вам общее представление):

public interface Ws2_32 extends Library {

    // I don't know how to declare fixed size arrays in JNA,
    // so you will have to adjust these Structue declarations
    // as needed...

    public static class WSAData extends Structure {
        short wVersion;
        short wHighVersion;
        byte  szDescription[257];
        byte  szSystemStatus[129];
        short iMaxSockets;
        short iMaxUdpDg;
        String lpVendorInfo;
    };

    public static class WSAPROTOCOLCHAIN extends Structure {
        int ChainLen;
        int ChainEntries[7];
    };

    public static class WSAPROTOCOL_INFOW extends Structure {
        int dwServiceFlags1;
        int dwServiceFlags2;
        int dwServiceFlags3;
        int dwServiceFlags4;
        int dwProviderFlags;
        GUID ProviderId;
        int dwCatalogEntryId;
        WSAPROTOCOLCHAIN ProtocolChain;
        int iVersion;
        int iAddressFamily;
        int iMaxSockAddr;
        int iMinSockAddr;
        int iSocketType;
        int iProtocol;
        int iProtocolMaxOffset;
        int iNetworkByteOrder;
        int iSecurityScheme;
        int dwMessageSize;
        int dwProviderReserved;
        char szProtocol[256];
    };

    Ws2_32 INSTANCE = (Ws2_32) Native.loadLibrary("ws2_32", Ws2_32.class);

    int WSAStartup(short version, WSADATA lpwsaData);
    int WSAEnumProtocolsW(int[] lpiProtocols, WSAPROTOCOL_INFOW[] lpProtocolBuffer, IntByReference lpdwBufferLength);
    int WSACleanup();
    int WSAGetLastError();
    int WSASetLastError(int iError);
}

.

public static void main(String[] args){
    WSADATA     wsaData = new WSADATA();
    short       version = 2;
    int         result = Ws2_32.INSTANCE.WSAStartup(version, wsaData);

    System.out.println("WSAStartup() returned: " + result);

    if(result == 0){
        System.out.println("WSADATA struct:");
        System.out.println("wVersion: " + wsaData.wVersion);
        System.out.println("wHighVersion: " + wsaData.wHighVersion);

        System.out.print("szDescription: ");
        for(byte b : wsaData.szDescription){
            System.out.print((char) b);
        }
        System.out.print("\n");

        System.out.print("szSystemStatus: ");
        for(byte b : wsaData.szSystemStatus){
            System.out.print((char) b);
        }
        System.out.print("\n");

        System.out.println("iMaxSockets: " + wsaData.iMaxSockets);
        System.out.println("iMaxUdpDg: " + wsaData.iMaxUdpDg");
    }
}

.

public class TestWSAEnumProtocolsA {

    public void start(){

        WSAPROTOCOL_INFOW[]     wsaprotocol_info = new WSAPROTOCOL_INFOW[1];
        WSADATA                 wsaData = new WSADATA();
        IntByReference          dwBufferLength = new IntByReference(628); // sizeof WSAPROTOCOL_INFOW, in bytes
        int                     result = 0;
        short                   version = 2;

        if((result = Ws2_32.INSTANCE.WSAStartup(version, wsaData)) != 0){
            System.out.println("Error #" + result + " at WSAStartup()");
            return;
        }

        System.out.println("WSAStartup() finished correctly.");

        if((result = Ws2_32.INSTANCE.WSAEnumProtocolsW(null, wsaprotocol_info, dwBufferLength)) == -1){
            System.out.println("WSAEnumProtocolsW() returned: " + result);
            System.out.println("dwBufferLength is: " + dwBufferLength.getValue());
            System.out.println("WSAGetLastError() returned: " + Ws2_32.INSTANCE.WSAGetLastError());
        }
    }
}
person Remy Lebeau    schedule 28.02.2013
comment
Спасибо за быстрый ответ и разъяснение разницы между ansi и unicode. Возможно, я не очень хорошо объяснил в первом посте, но я вызываю WSAStartup() только один раз в начале кода и когда это не так. t возвращает ошибку, тогда я вызываю WSAEnumProtocolsW(). Я обновил вопрос и добавил код, который вы просили. Еще раз спасибо за вашу помощь. - person Hernán Erasmo; 01.03.2013
comment
@H3rnst: в показанном вами main() вы дважды звоните WSAStatup(): int result = Ws2_32.INSTANCE.WSAStartup(version,lpwsaData); ... if((resultado = Ws2_32.INSTANCE.WSAStartup(version,lpwsaData)) == 0){. Вместо этого измените оператор if на if (result == 0). - person Remy Lebeau; 01.03.2013
comment
@ H3rnst: вы не показали свое объявление WSAPROTOCOL_INFO. Поскольку вы вызываете WSAEnumProtocolsW(), убедитесь, что члены вашей WSAPROTOCOL_INFO соответствуют структуре WinSock Unicode WSAPROTOCOL_INFOW. - person Remy Lebeau; 01.03.2013
comment
@ H3rnst: Кстати, LPWSADATA - это указатель на структуру WSADATA. Вам нужно выделить фактический WSADATA и передать его WSAStartup() для заполнения. Похоже, вы только выделяете указатель, но никуда его не указываете. Подобно WSAEnumProtocolsW(), вы выделяете только один экземпляр WSAPROTOCOL_INFO, но вы устанавливаете lpdwBufferLength на -2 вместо фактического размера байта этого одного экземпляра WSAPROTOCOL_INFO. Если установлено несколько протоколов, вам необходимо выделить место более чем для одного WSAPROTOCOL_INFO. - person Remy Lebeau; 01.03.2013
comment
Вы правы, теперь я это вижу. Но этот фрагмент кода — просто тест, который я сделал, чтобы убедиться, что у меня правильные структуры и что я получаю действительный и правильный ответ от WSAStartup(). После его запуска я получил вывод, который я опубликовал сначала, а затем я использовал часть этого кода в классе TestWSAEnumProtocolsA (который я разместил в обновлении) (не обращайте внимания на имя, я не изменил его, но я вызываю WSAEnumProtocolsW() внутри). Я опубликовал вывод, полученный при запуске этого класса, который показывает, что WSAGetLastError() и WSAEnumProtocolsW() не работают. - person Hernán Erasmo; 01.03.2013
comment
@H3rnst: Похоже, вы неправильно понимаете, как использовать буферы с WinSock. - person Remy Lebeau; 01.03.2013
comment
@H3rnst: когда вы публикуете код на SO для проверки, публикуйте весь соответствующий код, включая объявления переменных и типов, используемых в коде. - person Remy Lebeau; 01.03.2013
comment
Согласно MSDN, о lpdwBufferLength: On input, number of bytes in the lpProtocolBuffer buffer passed to WSAEnumProtocols. On output, the minimum buffer size that can be passed to WSAEnumProtocols to retrieve all the requested information. Вот почему я вызываю WSAEnumProtocols() со значением lpdwBufferLength, равным -2. Если он работает правильно, то он должен установить значение lpdwBufferLength на минимальный размер требуемого буфера. Вот почему я вывожу это значение перед вызовом WSAGetLastError(). Извините за соответствующий код, я публикую его прямо сейчас. - person Hernán Erasmo; 01.03.2013
comment
Я не отметил вас в своих ответах, но уже обновил вопрос с кодом. Спасибо! - person Hernán Erasmo; 01.03.2013
comment
@ H3rnst: я добавил несколько примеров в свой ответ. - person Remy Lebeau; 01.03.2013
comment
@H3rnst: -2 не является допустимым размером буфера для lpdwBufferLength на входе. Но настоящая причина, по которой он не изменяется при выводе, заключается в том, что вы передаете свою переменную lpdwBufferLength по значению в WSAEnumProtocol(), когда вместо этого вам нужно передать ее по ссылке. - person Remy Lebeau; 01.03.2013
comment
Так просто, но так мучительно трудно найти. Заменив int на IntByReference, WSAEnumProtocolsW() изменяет lpdwBufferLength на правильное значение. Теперь я получаю НАРУШЕНИЕ ДОСТУПА ИСКЛЮЧЕНИЯ, но это, вероятно, какая-то ошибка в коде, я все еще пытаюсь понять, как это работает. Спасибо за ваше терпение и время. - person Hernán Erasmo; 01.03.2013