更好地仿真VC++关键字__uuidof

转帖请注明出处 http://www.cppblog.com/cexer/archive/2008/07/05/55419.html
世界上有个叫__uuidof的关键字。这是一个家喻户晓且其被广泛使用的关键字,几乎可以说,有COM程序员的地方,就有它 __uuidof的存在。其很好很强大的程度是人所共见的,夸张一点比喻:离开它的COM程序员,就像失去了点火器的火箭,虽然可以人工点火,但是不安全且无效率。

  不过很多人并不知道,这其实是一个编译器扩展关键字,提供了此关键字的仅VC一家别无它店。幸运的是,强大的C++让我们能够轻易仿真出这个关键字的大部分功能。

  网上能够找到一种仿真的方法,见许式伟:《仿真VC++提供的关键字__uuidof》。该方法的实现是:特化模板类的成员函数,然后运行时调用函数根据UUID字符串产生出UUID,由于是生成于运行时,所以它无可避免地有两个缺点:

  • 存在运行时消耗。
  • 无法作为非类型模板参数传递给模板。

  那些整天流着口水追求效率的C++程序员们,是不能忍受任何不必要的运行时消耗的。对于第二点,VC的关键字__uuidof取出来的UUID是能够作为非类型模板参数传递的,ATL中就大量地使用了这样的参数传递形式,所以目前的这种实现功能有限,仿真度还不够高。

  其实只要能让它能够编译期决定UUID的值,那么这两个问题就迎刃而解了。而这是肯定可以实现的,并且很简单。我曾经在自己写的一个COM库里实现过这样的方法,虽然那个库已经不知丢到哪里去了,不过那个方法还记得。

  解决的途径还是离不开模板特化。类的成员包括成员函数和成员变量,函数是运行时作用的,然而static const的成员变量可以是编译期就决定。所以解决的方法就在眼前了:特化模板的成员变量。

  以下是我的实现方法。

  先定义一个类模板,它有一个static const ,UUID类型的成员变量:

    template<typename T>   
struct _uuid_of_impl
{
static const UUID id;
};
template<typename T>
const UUID _uuid_of_impl<T>::id=GUID_NULL;

  有了这个简单的东西就好办了,只需要针对某个接口特它的成员变量就行了,如:

    template<>    
const UUID _uuid_of_impl<IUnknown>::id=IID_IUnknown;

template<>
const UUID _uuid_of_impl<IDispatch>::id=IID_IDispatch;


  然后我们就可以这样取得接口的UUID:

    IID IunknownID=_uuid_of_impl<IUnknown>::id;   
IID IdispatchID=_uuid_of_impl<IDispatch>::id;


  作为非类型模板参数传递:

    template<const IID* t_iid>   
struct __uuid_of_test
{
__uuid_of_test()
{}

void test()
{
t_iid;
}
};

__uuid_of_test<&(_uuid_of_impl<IDispatch>::id) > obj;


  不过现在这种实现还有一些问题,看以下代码:

    IID ITypelibID=_uuid_of_impl<ITypeLib>::id;


  注意我们并没有事先对模板__uuid_of_impl特化ITypeLib的版本。但是以上语句却能够编译通过,在运行时,__uuid_of_impl<ITypeLib>的值将会是错误的值GUID_NULL。这是因为,我们定义模板的时候,同时在模板外定义了模板的静态成员变量并赋值为GUID_NULL,所以没有用特化的方法定义UUID的接口,都将使用GUID_NULL这个通用值。这当然不是我们想要的。所以我们想在没有定义UUID的时候让编译器警告我们,要达到这样的效果只需要去掉上面那句:

    template<typename T>   
const UUID _uuid_of_impl<T>::id=GUID_NULL;


  现在再进行编译,编译器会告诉你,有一个无法解析的符号。根据编译器提供的相关信息,很容易就能确定问题所在。这样能够在编译期极大地减小安全隐患。

  最后加上我们定义的几个宏,这是最后的全部实现:

    template<typename T>   
struct _uuid_of_impl
{
static const UUID id;
};

#define uuid_of(x) _uuid_of_impl<x>::id
#define DEFINE_UUID(x,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) /
template<> /
const UUID _uuid_of_impl<x>::id={l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}


  用以下代码测试通过:

    struct ITest{};   

DEFINE_UUID(ITest,0x96289151,0xf059,0x4049,0x88,0x19,0x61,0xa6,0xe9,0x79,0xc,0xf1);

template<const IID* t_iid>
struct uuid_of_test
{
uuid_of_test(){}
};

int main()
{
IID xxxxID=uuid_of(ITest);
uuid_of_test<&(uuid_of(ITest))> obj;

return 0;
}


  需要注意的是DEFINE_UUID应该在实现文件(*.cpp,*.cxx,……)当中使用。到这里,仍有一些使用方法与VC的关键字是不一样的,所以仍没做到仿真度100%。不过我相信通过预处理元编程,能够相当程度地逼近它,只是我对预处理元编程不是很了解,所以就不在这里献丑了。

<think>好的,用户现在问的是“primary_service_uuid会在什么地方表现出来”。之前他们问过关于GATT数据库结构的问题,现在可能是在进一步理解服务声明中的各个部分具体应用在哪里。 首先,我需要回顾一下之前的对话。用户之前解析了四个GATT声明,其中第一个是服务声明,使用了primary_service_uuid。现在他们想知道这个UUID具体在什么地方表现,也就是在蓝牙通信的哪个环节会被用到。 接下来,我需要确认primary_service_uuid的作用。根据蓝牙规范,服务声明的UUID是用来标识服务的类型,比如心率服务是0x180D,电池服务是0x180F。这个UUID会被包含在GATT服务声明属性中,当设备广播服务或者客户端发现服务时,就会看到这个UUID。 然后,用户可能想知道这个UUID在代码中的具体位置,以及在实际通信中的表现。比如,在代码中,它可能被定义为一个常量,比如GATTS_SERVICE_UUID_TEST,而这个常量的值可能是一个16位或128位的UUID。当设备发送广告数据或者响应服务发现请求时,这个UUID会被包含在相应的数据包中。 还需要考虑用户可能的实际应用场景。比如,他们可能在开发一个蓝牙设备,需要定义自己的服务,这时候需要正确设置primary_service_uuid,以便客户端能够正确识别服务类型。或者他们在调试时发现服务无法被识别,需要检查UUID是否正确设置。 另外,用户可能对UUID的格式和规范不太清楚,比如16位和128位的区别,标准UUID和自定义UUID的应用场景。需要解释这些内容,帮助他们理解为什么选择特定的UUID值。 还可能涉及到蓝牙协议中的服务发现过程,客户端通过读取服务声明属性来获取服务列表,这时候服务声明中的UUID就是关键信息。用户可能需要知道如何通过工具(如蓝牙调试器)查看这些UUID,以验证配置是否正确。 最后,要确保回答结构清晰,分点说明,涵盖定义、代码位置、数据包中的表现、客户端交互以及示例,帮助用户全面理解primary_service_uuid的应用场景和重要性。</think>以下是关于`primary_service_uuid`在蓝牙协议中的具体表现及其作用的详细解析: --- ### **1. `primary_service_uuid`的定义** - **本质**:`primary_service_uuid` 是一个 **16位UUID**,其固定值为 `0x2800`。 - **作用**:它是一个特殊的标识符,用于标记某个属性(Attribute)为 **服务声明**(Service Declaration),即告诉客户端:“这是一个服务的起点”。 --- ### **2. 在GATT数据库中的表现** 在GATT数据库的代码定义中,`primary_service_uuid` 出现在 **服务声明条目** 的 `UUID` 字段中: ```c // 服务声明示例 { {ESP_GATT_AUTO_RSP}, { ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, // 这里使用0x2800 ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST } } ``` - **关键字段**: - `primary_service_uuid`(0x2800)表示这是一个服务声明。 - `GATTS_SERVICE_UUID_TEST` 是该服务本身的UUID(例如心率服务是`0x180D`,自定义服务可能是`0xFF00`等)。 --- ### **3. 在蓝牙协议数据包中的表现** 当客户端(如手机)通过 **服务发现** 过程读取服务信息时,服务声明属性会通过以下方式传输: 1. **属性句柄(Handle)**:服务声明的属性句柄(如`0x0001`)。 2. **属性类型(UUID)**:`0x2800`(即`primary_service_uuid`)。 3. **属性值**:服务本身的UUID(如心率服务的`0x180D`)。 例如,客户端发送一个 **读取服务声明** 的请求后,服务端返回的数据包格式如下: ``` 属性句柄 | UUID类型 (0x2800) | 服务UUID (如0x180D) ``` --- ### **4. 在客户端交互中的表现** - **服务发现**:客户端通过扫描或主动查询,发现设备支持的服务时,会读取所有标记为`0x2800`(服务声明)的属性,并解析其对应的服务UUID。 - **服务匹配**:客户端根据服务UUID判断设备功能。例如: - 若服务UUID为`0x180D`,客户端知道这是心率服务。 - 若为自定义UUID(如`0xFF00`),客户端需提前知晓其含义才能正确交互。 --- ### **5. 实际应用场景** #### **场景1:标准服务** - **心率服务**: - 服务声明UUID:`0x2800`(固定)。 - 服务UUID:`0x180D`(标准心率服务)。 - 客户端看到`0x180D`后,自动调用心率数据处理逻辑。 #### **场景2:自定义服务** - **自定义UUID(如0xFF00)**: - 服务声明UUID:`0x2800`(固定)。 - 服务UUID:`0xFF00`(自定义)。 - 客户端需预先知道`0xFF00`对应的服务定义(如数据传输协议)。 --- ### **6. 代码中的关联** 在ESP-IDF开发中,`primary_service_uuid`通常定义在头文件中: ```c // 蓝牙标准UUID定义(esp_gatt_defs.h) #define ESP_GATT_UUID_PRI_SERVICE 0x2800 ``` - 开发者通过`&primary_service_uuid`引用它,而服务本身的UUID(如`GATTS_SERVICE_UUID_TEST`)需自行定义: ```c // 自定义服务UUID(例如0xFF00) static const uint16_t GATTS_SERVICE_UUID_TEST = 0xFF00; ``` --- ### **7. 总结** - **何时表现**:在服务声明属性被客户端读取时(如服务发现过程)。 - **如何表现**:通过协议数据包中的UUID字段`0x2800`标识服务声明,并通过其属性值传递服务本身的UUID。 - **重要性**:是客户端识别服务类型(标准或自定义)的核心依据。 --- 通过理解`primary_service_uuid`的作用,可以更清晰地设计GATT服务结构,并确保客户端能正确解析设备功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值