Delphi: WSAAddressToString returns 错误代码 10022 (WSAEINVAL)

Delphi: WSAAddressToString returns error code 10022 (WSAEINVAL)

我正在尝试使用 Winsock2 API 界面扫描附近的蓝牙设备以获取其 MAC 地址。 使用下面的代码我可以找到设备。但是当我尝试使用 WSAAddressToString 获取他们的地址时,出现 10022 (WSAEINVAL) 错误说 "An invalid argument was supplied".

代码是:

uses
   winsock2, bt_helper;

procedure test;
var
  ulFlags: u_long;
  QuerySet: WSAQUERYSET;
  QuerySize: u_long;
  HLookup: THandle;
  Result: Integer;

  pCSAddr: pCSADDR_INFO;
  pDeviceInfo: PBTH_DEVICE_INFO;
  pResults: lpWSAQUERYSET;
  Buffer: array [0..999] of Byte;

  ProtocolInfo: WSAPROTOCOL_INFO;
  ProtocolInfoSize: Integer;

  BufferLength, AddressSize: LongWord;
  addressAsString: array [0..1999] of Char;
begin

WSAStartup (02, Data);

ulFlags:=
    LUP_CONTAINERS or   //device inquiry
    LUP_RETURN_NAME or  //Friendly device name (if available) will be returned in lpszServiceInstanceName
    LUP_RETURN_ADDR or  //BTH_ADDR will be returned in lpcsaBuffer member of WSAQUERYSET
    LUP_FLUSHCACHE ;    //Flush the device cache for all inquiries, except for the first inquiry

QuerySize:= SizeOf(WSAQuerySet);
ZeroMemory (@QuerySet, SizeOf(QuerySet));

QuerySet.dwNameSpace:= NS_BTH;
QuerySet.dwSize:= QuerySize;

Result:= WSALookupServiceBegin(@QuerySet, ulFlags, HLookup);

if Result = 0 then
begin
  while true do
  begin    
    bufferLength:= sizeof(buffer);
    pResults:= lpWSAQUERYSET(@buffer);   
    Result:= WSALookupServiceNext (HLOOKUP, ulFlags, bufferLength, pResults);

    if Result = 0 then
    begin
      // Get the device info, name, address, etc.
      Memo1.Lines.Add(Format('The service instance name is %s', [pResults.lpszServiceInstanceName]));
      //pCSAddr.LocalAddr.lpSockaddr.sa_family:= AF_INET;
      pCSAddr:= PCSADDR_INFO(pResults.lpcsaBuffer);
      pDeviceInfo:= PBTH_DEVICE_INFO(pResults.lpBlob);

      // Print the local Bluetooth device address ...
      AddressSize:= sizeof(addressAsString);

      if WSAAddressToString(pCSAddr.LocalAddr.lpSockaddr^, pCSAddr.LocalAddr.iSockaddrLength,
        @ProtocolInfo, @AddressAsString, AddressSize) = 0
      then
        Memo1.Lines.Add(Format ('The localAddress: %s', [AddressAsString]))
      else
        Memo1.Lines.Add(Format ('WSAAddressToString for localAddress failed with error code %d: %s',
            [WSAGetLastError, SysErrorMessage (WSAGetLastError)]));

      // Print the remote Bluetooth device address ...
      AddressSize:= sizeof(addressAsString);
      IF WSAAddressToString(pCSAddr.RemoteAddr.lpSockaddr^, pCSAddr.RemoteAddr.iSockaddrLength,
        @ProtocolInfo, @AddressAsString, Addresssize) = 0
      then
        Memo1.Lines.Add (Format ('The remote device address: %s', [AddressAsString]))
      else
        Memo1.Lines.Add (Format ('WSAAddressToString for remoteAddress failed with error code %d: %s',
            [WSAGetLastError, SysErrorMessage(WSAGetLastError)]));
    end
    else
    begin
        Memo1.Lines.Add(SysErrorMessage(WSAGetLastError));
        break;
    end;
  end;
end;    
WSALookupServiceEnd(HLookup);

这是备忘录中的结果:

The service instance name is BTDevice1
WSAAddressToString for localAddress failed with error code 10022: An invalid argument was supplied
WSAAddressToString for remoteAddress failed with error code 10022: An invalid argument was supplied
---------------------------------
No more results can be returned by WSALookupServiceNext

使用以下单元进行编译:

unit bt_helper;

interface

uses
    winsock2, Winapi.Windows;

const
    BTH_MAX_NAME_SIZE = 248;
    BTHPROTO_RFCOMM= 3;
  BT_PORT_ANY = -1;

type
  BTH_ADDR = int64;

  SOCKADDR_BTH = packed record
    addressFamily       :word;            // Always AF_BTH
    btAddr              :BTH_ADDR;        // Bluetooth device address
    serviceClassId      :TGUID;           // [OPTIONAL] system will query SDP for port
    port                :dword;           // RFCOMM channel or L2CAP PSM
  end;

  BTH_COD = ULONG;

  _BTH_DEVICE_INFO = record
    flags: ULONG;                      // Combination BDIF_Xxx flags
    address: BTH_ADDR;                 // Address of remote device.
    classOfDevice: BTH_COD;            // Class Of Device.
    name: array [0..BTH_MAX_NAME_SIZE - 1] of CHAR;    // name of the device
  end;
  {$EXTERNALSYM _BTH_DEVICE_INFO}
  BTH_DEVICE_INFO = _BTH_DEVICE_INFO;
  {$EXTERNALSYM BTH_DEVICE_INFO}
  PBTH_DEVICE_INFO = ^BTH_DEVICE_INFO;
  {$EXTERNALSYM PBTH_DEVICE_INFO}
  TBthDeviceInfo = BTH_DEVICE_INFO;
  PBthDeviceInfo = PBTH_DEVICE_INFO;    

implementation

end.

如果你仔细阅读 documentation of WSAAddressToString 你会注意到这一段:

lpProtocolInfo [in, optional] A pointer to the WSAPROTOCOL_INFO structure for a particular provider. If this is parameter is NULL, the call is routed to the provider of the first protocol supporting the address family indicated in the lpsaAddress parameter.

因此,您不应提供伪造的 WSA_PROTOCOL 信息结构,而应传入 nil。第二个问题是您使用 SizeOf() 来确定字符串缓冲区的长度,这是不正确的,您应该使用 Length():

AddressSize:= Length(addressAsString);

if WSAAddressToString(pCSAddr.LocalAddr.lpSockaddr^, pCSAddr.LocalAddr.iSockaddrLength,
    nil, @AddressAsString, AddressSize) = 0 then
 begin
   SetLength(AddressAsString, AddressSize-1);// resize to returned length minus last null character
   ...