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.
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
...