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






4.77/5 (9投票s)
一个 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 提供的蓝牙功能来构建设备发现函数。
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 文件提供的非托管设备发现函数。完成后,线程会根据找到的结果修改 devicesList
和 numberDevices
。因此,用户可以检索可用设备的数量和列表。我还使用了单例设计模式在封装类中,以确保它仅被实例化一次。
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 日:初步发布。