65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Mobile 上用于发现附近蓝牙设备的 C# 包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (9投票s)

2010年3月18日

CPOL

3分钟阅读

viewsIcon

56885

downloadIcon

2255

一个 C# 封装,用于在 Windows Mobile 下发现附近的蓝牙设备,它使用基于 Winsock 2 API 构建的 C++ DLL。

引言

此代码示例名为 WMBluetoothWrapper。它演示了通过 Winsock 2 API 进行简单的蓝牙设备发现,并提供了一个 C# 封装类来运行发现。检索并封送了移动设备的名称及其地址到 C# 数据类型。我提供了 Visual Studio 2010 DLL 项目 WMBluetoothWrapper(它将基于 WinSock 2 的发现功能呈现为以及 C# 文件,包括 DevicesDiscoveryWrapper 封装类。使用检索到的地址,当前的封装可以扩展到包括更多功能,例如基于蓝牙的连接和数据交换功能。

背景

作为我使用 Windows Mobile 蓝牙设备开发移动社交网络应用程序(即将推出)计划的一部分,我需要一种方法来了解我附近的蓝牙设备。我正在使用 .NET Compact Framework 3.5 在 C# 中开发移动社交网络应用程序,因此我遇到了需要用 C++ 编写整个低级蓝牙功能,将它们编译成 DLL,最后编写一个 C# 封装类并使用 P/Invoke(s) 来运行蓝牙功能。在继续开发封装之前,我尝试在 C# 中找到第三方库,它们公开了蓝牙发现功能,我找到了 32feet.NET,从我读到的内容来看,它似乎很受欢迎;但是,它似乎存在一些许可证限制,而且最终拥有一个更简单和可定制的封装更好。

架构

DevicesDiscoveryWrapper 类是用 C# 编写的,它使用 P/Invoke 访问 WMBluetoothWrapper DLL 文件中的导出函数。后者是用 C++ 编写的,并利用 Winsock 2 API 提供的蓝牙功能来构建设备发现函数。

wa

Using the Code

为了在开发应用程序时使用蓝牙设备发现封装,您只需将 C# 文件 DevicesDiecoveryWrapper.cs 添加到您的项目中,并使用 DevicesDiscoveryWrapper 类。对于部署,您不应忘记将 DLL 文件 WMBluetoothWrapper.dll 复制到您的项目移动工作目录中。下面,我提供了关于使用蓝牙 Winsock 2 API 编写的设备发现功能和 C# 封装类的详细说明。

WMBluetoothWrapper DLL 代码

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;
}

DevicesDiscoveryWrapper 封装类

下面您找到了用 C# 开发的封装类的代码。使用此封装的常见场景是定期启动一个分离的线程,并将 RunDevicesDiscovery 方法指定为其起始点。然后,托管线程将运行一个 P/Invoke 并调用 DLL 文件提供的非托管设备发现函数。完成后,线程会根据找到的结果修改 devicesListnumberDevices。因此,用户可以检索可用设备的数量和列表。我还使用了单例设计模式在封装类中,以确保它仅被实例化一次。

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;
            }
        }
    }
}

感兴趣的工具

DLL Export Viewer 工具对验证/调试导出函数的列表及其 WMBluetoothWrapper DLL 文件的虚拟内存地址非常有帮助。有关此工具的更多详细信息,请访问:http://www.nirsoft.net/utils/dll_export_viewer.html

联系方式

对于错误报告和建议,请随时通过以下方式与我联系:krifa.amir@gmail.com

历史

  • 2010 年 3 月 18 日:初步发布。
© . All rights reserved.