Search code examples
delphibluetoothdelphi-xe2winsock2

Delphi: WSAAddressToString returns error code 10022 (WSAEINVAL)


I am trying to scan nearby Bluetooth device for their MAC address using Winsock2 API interface. Using code below I can found devices. But when I try to get their address using WSAAddressToString get a 10022 (WSAEINVAL) error say "An invalid argument was supplied".

The code is:

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 ($0202, 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);

Here is the result inside memo:

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

Use the following unit in order to compile:

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.

Solution

  • If you read the documentation of WSAAddressToString closely you would have noticed this paragraph:

    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.

    so instead of supplying a fake WSA_PROTOCOL info structure, you should pass in nil. The second problem is that you use SizeOf() to determine the length of the String buffer, this is incorrect and you should use 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
       ...