Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A C# wrapper for nearby Bluetooth devices discovery under Windows Mobile

0.00/5 (No votes)
24 Mar 2010 1  
A C# wrapper for nearby Bluetooth devices discovery under Windows Mobile that uses a C++ DLL built based on the Winsock 2 API.

Introduction

This code sample is named WMBluetoothWrapper. It demonstrates simple Bluetooth device discovery via the Winsock 2 API, and provides a C# wrapper class for running the discovery. Both the names of the mobile devices and their addresses are retrieved and marshaled to C# data types. I provide both the Visual Studio 2010 DLL project WMBluetoothWrapper which present the WinSock 2 based discovery function as well as the C# file including the DevicesDiscoveryWrapper wrapper class. Using the retrieved addresses, the current wrapper can be extended to include further functions, such as Bluetooth based connection and data exchanging functions.

Background

As part of my plan to write a mobile social network application (coming soon) using Windows Mobile Bluetooth capable devices, I need a way to know what Bluetooth devices are near me. I'm developing a mobile social network application in C# using the .NET Compact Framework 3.5, and thus I was confronted with the need of writing the whole low level Bluetooth based functionalities in C++, compiling them into a DLL, and finally writing a C# wrapper class and using P/Invoke(s) in order to run the Bluetooth functions. Before going ahead with the development of the wrapper, I've tried to find third-party libraries in C# exposing Bluetooth discovery functionalities, and I found 32feet.NET which seems to be popular from what I've read; however, it seems to present some license limitations, and after all it is better to have the control on a simpler and customizable wrapper.

Architecture

The DevicesDiscoveryWrapper class is written in C#, and it uses P/Invoke to gain access to exported functions within the WMBluetoothWrapper DLL file. The latter is written in C++, and makes use of Bluetooth functionalities provided by the Winsock 2 API in order to build the devices discovery function.

wa

Using the Code

In order to use the Bluetooth devices discovery wrapper when developing your application, you will just add the C# file DevicesDiecoveryWrapper.cs to your project and use the class DevicesDiscoveryWrapper. For deployment, you should not forget to copy the DLL file WMBluetoothWrapper.dll into your project mobile working directory. Below, I provide a detailed description of both the devices discovery function written using the Bluetooth Winsock 2 API and the C# wrapper class.

The WMBluetoothWrapper DLL Code

WMBluetoothWrapper.h:

// Declaring the DevicesDiscovery function which will be exported 
// and called from the managed code.
extern "C" int DevicesDiscovery(wchar_t * devicesList);

WMBluetoothWrapper.cpp:

// Don't forget to include the ws2bth.h for the Winsock 2 Bluetooth library.

// The unmanaged implementation of the DevicesDiscovery function. 
// The later returns the list of found devices via the 
// devicesList pointer which points to a StringBuilder data type 
// already declared in the C# wrapper code (to be described 
// later). The return value of DevicesDiscovery is the number of identified devices.
// If DevicesDiscovery return -1, then a message describing 
// the Error is copied to the StringBuilder variable pointed by 
// devicesList.
extern "C" int DevicesDiscovery(wchar_t * devicesList)
{
    // Prepare the caller application by providing Winsock-related data
    WSADATA wsd;
    WSAStartup (MAKEWORD(1,0), &wsd);
    
    HANDLE hLookup;

    union {
        CHAR buf[5000];
        double __unused; // ensure proper alignment
    };

    LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf;
    DWORD dwSize  = sizeof(buf);
    BOOL bHaveName;
    
    // Create and initialize a WSAQUERYSET variable to specify search parameters.
    // Set the dwNameSpace member to NS_BTH to restrict the 
    // query to Bluetooth devices
    WSAQUERYSET wsaq;
    ZeroMemory(&wsaq, sizeof(wsaq));
    wsaq.dwSize = sizeof(wsaq);
    wsaq.dwNameSpace = NS_BTH;
    wsaq.lpcsaBuffer = NULL;

    //Start an inquiry
    //LUP_CONTAINERS is passed in the dwFlags parameter. 
    //This enables Service Discovery 
    //Protocol (SDP) to search for other Bluetooth devices within range.
    if (ERROR_SUCCESS != WSALookupServiceBegin (&wsaq, LUP_CONTAINERS, &hLookup))
    {
        wsprintf(devicesList, 
            L"WSALookupServiceBegin failed %d\r\n", GetLastError());
        return -1;
    }
    
    //To enumerate devices that were scanned in the previous 
    //call to WSALookupServiceBegin, 
    //call the WSALookupServiceNext function. 
    //This function returns a pointer to a buffer that stores the result set in a
    //WSAQUERYSET structure.

    ZeroMemory(pwsaResults, sizeof(WSAQUERYSET));
    pwsaResults->dwSize = sizeof(WSAQUERYSET);
    pwsaResults->dwNameSpace = NS_BTH;
    pwsaResults->lpBlob = NULL;
    int numberRetrivedDevices = 0;
    while (ERROR_SUCCESS == WSALookupServiceNext (hLookup, LUP_RETURN_NAME 
        | LUP_RETURN_ADDR, &dwSize, pwsaResults))
    {
        wchar_t currentDev[1024];    
        if (pwsaResults->dwNumberOfCsAddrs != 1)
        {
            wsprintf(devicesList, 
            L"WSALookupServiceNext failed %d\r\n", GetLastError());
            return -1;
        }
        if(numberRetrivedDevices > 0)
            wcscat(devicesList, L",");
        BT_ADDR b = ((SOCKADDR_BTH *)pwsaResults->
            lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr;
        bHaveName = pwsaResults->lpszServiceInstanceName && 
            *(pwsaResults->lpszServiceInstanceName);
        wsprintf (currentDev, L"%s%s%04x%08x%s\n", bHaveName ? 
            pwsaResults->lpszServiceInstanceName : L"", 
        bHaveName ? L"(" : L"", GET_NAP(b), 
            GET_SAP(b), bHaveName ? L")" : L"");
        wcscat(devicesList, currentDev);
        numberRetrivedDevices ++;
    }
    
    WSALookupServiceEnd(hLookup);
    WSACleanup();
    
    return numberRetrivedDevices;
}

The DevicesDiscoveryWrapper Wrapper Class

Below you find the code for the wrapper class developed in C#. A common scenario for using this wrapper would be to periodically start a detached thread and to specify the RunDevicesDiscovery method as its start point. Then, the managed thread will run a P/Invoke and call the unmanaged devices discovery function provided by the DLL file. Once done, the thread modifies the devicesList and numberDevices according to the found results. So, the user can retrieve the number and the list of available devices. I've also used the Singleton Design Pattern within the wrapper class in order to ensure that it will be instantiated only once.

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

class DevicesDiscoveryWrapper
{
    //Declaring a C# method that is implemented in an unmanaged DLL. 
    //The method DevicesDiscovery is declared with the
    //static and extern modifiers and has the DllImport attribute 
    //which tells the compiler that the implementation comes 
    //from WMBluetoothWrapper.dll.
    
     [DllImport("WMBluetoothWrapper.dll")]
    private static extern System.Int32 DevicesDiscovery
        ([MarshalAs(UnmanagedType.LPWStr)] StringBuilder listDevices);
        
    private static string devicesList;
    private static int numberDevices;
    // Used for the Singleton design pattern, 
    // the DevicesDiscoveryWrapper object will be instantiated once 
    // and can be accessed only by calling the GetDevicesDiscoveryWrapper method.
    private static DevicesDiscoveryWrapper _wrapper = null;

    // The constructor is made private for the Singleton design pattern
    private DevicesDiscoveryWrapper()
    {
        devicesList = null;
        numberDevices = 0;
    }
    
    
    // You can use this public and static method in order to retrieve the unique 
    // DevicesDiscoveryWrapper object through which 
    // you can run the devices discovery procedure
    public static DevicesDiscoveryWrapper GetDevicesDiscoveryWrapper()
    {
        if(_wrapper == null)
        {
            _wrapper = new DevicesDiscoveryWrapper();
        }
        
        return _wrapper;
    }
    
    // You can use this getter to retrieve the list of found devices 
    // (Ids + Addresses) once you run the                  
    // RunDevicesDiscovery method.
    public string GetDevices
    {
        get
        {
            lock (this)
            {
                return devicesList;
            }
        }
    }
    
    // You can use this getter to retrieve the number of found devices 
    // once you run the RunDevicesDiscovery method.
    public int GetNumberOfDevices
    {
        get
        {
            lock (this)
            {
                return numberDevices;
            }
        }
    }
    
    // This is the main method which make a P/Invoke to DevicesDiscovery method.
    // You can execute a thread periodically and 
    // specify this method as a start point
    // and in the same time you can access the number 
    // and the list of retrieved devices
    // using another thread via the other getters: GetNumberOfDevices and GetDevices
    public void RunDevicesDiscovery()
    {
        lock (this)
        {
            StringBuilder tmpListDevices = new StringBuilder();
            // Max capacity, to change if needed
            tmpListDevices.Capacity = 1024;
            int res = DevicesDiscovery(tmpListDevices);
            if( res == -1)
            {
                    MessageBox.Show
                    ("Error occurred while calling the 
                        unmanaged DevicesDiscovery functions: " + 
                        tmpListDevices.ToString());
            
            }else
            {
                // All is ok            
                devicesList = tmpListDevices.ToString();
                numberDevices = res;
            }
        }
    }
}

Tools of Interest

The DLL Export Viewer tool was of great help to me for verifying/debugging the list of exported functions and their virtual memory addresses for the WMBluetoothWrapper DLL file. More details about this tool can be found at: http://www.nirsoft.net/utils/dll_export_viewer.html.

Contact

For bug reports and suggestions, feel free to contact me at krifa.amir@gmail.com.

History

  • 18th March, 2010: Initial post.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here