COM 之 VARIANT_BOOL,失败的设计

本文深入探讨了COM中针对VARIANT_BOOL设计的缺陷,详细分析了其在不同语言环境下导致的错误行为及根源,指出设计中允许默认容错处理的潜在风险,并举例说明了此类bug的常见形式及影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

COM 的总体设计是很成功的,但针对 VARIANT_BOOL 而做的设计(约束)则相当失败。当一个程序员为 COM 提供 VARIANT_BOOL 返回值时,如果不给予特殊的认真对待,则极易潜伏下难以察觉却影响深远的 bug。在 VC 中为 COM 提供 VARIANT_BOOL 返回值时,正确的做法应该是这样的,不论内部表示(如本例m_editable)被申明成了什么数据类型: 
STDMETHODIMP CMyObject::get_Editable(VARIANT_BOOL * pval) { 
    ATLASSERT(pval); 
    if(m_editable) { 
        *pval = VARIANT_TRUE; 
    } else { 
        *pval = VARIANT_FALSE; 
    } 
    return S_OK; 
}

一个会出 bug 的例子:

STDMETHODIMP CMyObject::get_Editable(VARIANT_BOOL * pval) { 
    ATLASSERT(pval); 
    *pval = m_editable; 
    return S_OK; 

另一个会出 bug 的例子:

STDMETHODIMP CMyObject::get_Editable(VARIANT_BOOL * pval) { 
    ATLASSERT(pval); 
    *pval = ! m_non_editable; 
    return S_OK; 

所出 bug 通常都直接发生在 COM 的客户端,但仅从客户端入手则很难发现它,这类 bug 的根源就在以上接口函数实现上的疏忽。比如从 VB 使用这个 COM 并访问这个属性,有如下代码:

dim isNonEditable as Boolean 
isNonEditable = not myobject.Editable

isNonEditable 为 True 的可能性 非常非常 大, 有一些按正常逻辑期望得到False的情况,但表现却是True ,下面揭示相关的原因。

原 因是在 COM 规范中,VARIANT_BOOL 数据类型只有两个有效值 VARIANT_TRUE (-1) 和 VARIANT_FALSE (0)。而在 VARIANT_BOOL 的实现层面上,实际是一个 short 整数,当然可以包容除 VARIANT_TRUE 和 VARIANT_FALSE 之外的许多其它取值,并且这些值都可以直接通过 COM 调用不作修改地传递给调用者。几乎任何 COM 调用者也都能够容允 VARIANT_TRUE 和 VARIANT_FALSE 之外的值,而且都会一致地解释为 true (比如 VB)。如果光是上面所说的容错情况表面上合理而不足够引起关注的话,下面描述的不同语言之间的处理差异将值得警惕。

在 C++ 语言中,默认使用 1 表示真值,0 表示假值,当遇到任何非 0 值时都理解为真值,在进行逻辑运算之后如果得到真值则返回 1。逻辑运算与位操作运算分离,逻辑运算进行的是整体性操作而不是按位操作,另外有专用的按位操作运算。

在 VB 语言中,默认使用 -1 表示真值,0 表示假值,当遇到任何非 0 值时都理解为真值,在进行逻辑运算之后如果得到真值则返回原值。逻辑运算和按位运算不分离,进行逻辑运算时本质上进行的是按位运算。

在 C++ 中当要为 VARIANT_BOOL 输出真值时如果不特意指定输出规范约定的 VARIANT_TRUE (-1) ,十有八九会输出缺省的 true (1)。当然在 VB 中得到的也是 1,出于容错,它把这个值理解成了 true。在之后的过程中,如果 VB 不对这个值进行逻辑运算,仅从表面上看则是正确的。一旦在 VB 中进行逻辑运算,把 1 参与到某个按位操作的逻辑运算式中,几乎总是得到一个非 0 的值(取异或可以得到 0),既然有容错,所以 VB 一率把这些结果看作 true 。 所以上面的代码 isNonEditable 为 True 的可能性很大,而且这种错误可以随着其参与的逻辑运算进一步扩散。

下面以整数 x 为例,列出了它在VARIANT_BOOL(COM)、Boolean(VB)、bool(C/C++)情况下所代表的内容以及在不同语言下进行“非”操作之后的内容:

问题发生在VARIANT_BOOL的无效范围中,而C/C++的默认true值正好发生在这个无效范围中。注意上表中进行非操作时在VB和C/C++中的不同情形就可以清楚地看到问题所在了。这就是从 COM 返回 VARIANT_BOOL 频繁导致 bug 的原因,并且这里没有“防呆设计”,将来也不会有相关的“防呆设计”。所以从根本上讲,“在 COM 中为 VARIANT_BOOL 规定了取值规范仅限于 VARIANT_TRUE 和 VARIANT_FALSE,但允许其它值被默认容错处理”的设计是失败的设计。


注:--------------------------by: arthur_yu-------------------------

hr = pIDispatch->Invoke(dispid_getparam,IID_NULL,GetUserDefaultLCID(),DISPATCH_METHOD,&param,&rv,NULL,&iError);  

取rv.boolVal 总是0x0000ffff  即-1,查了好多文章,以上这篇是解释了这个原因。


// 检查任务是否存在 bool TaskExists(const wchar_t* taskName) { HRESULT hr = S_OK; bool exists = false; CoInitializeEx(NULL, COINIT_MULTITHREADED); ITaskService* pService = NULL; hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (LPVOID*)&pService); if (SUCCEEDED(hr)) { hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t()); if (SUCCEEDED(hr)) { ITaskFolder* pRootFolder = NULL; hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder); if (SUCCEEDED(hr)) { IRegisteredTask* pTask = NULL; hr = pRootFolder->GetTask(_bstr_t(taskName), &pTask); if (SUCCEEDED(hr)) { exists = true; pTask->Release(); } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { exists = false; } else { ShowError(L"获取任务信息失败", hr); } pRootFolder->Release(); } } pService->Release(); } CoUninitialize(); return exists; } // 修改参数类型为 char* 和 std::string // 修改参数类型为 char* 和 std::string bool CreateStartupTask(char* taskName, std::string appPath) { // 将输入参数转换为宽字符 std::wstring wTaskName = AnsiToWide(taskName); std::wstring wAppPath = StringToWide(appPath); // 首先检查任务是否存在 if (TaskExists(wTaskName.c_str())) { //MessageBoxW(NULL, L"任务已存在,无需创建", L"信息", MB_ICONINFORMATION); return true; } HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { //ShowError(L"初始化COM失败", hr); return false; } hr = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL ); if (FAILED(hr) && hr != RPC_E_TOO_LATE) { //ShowError(L"初始化安全库失败", hr); CoUninitialize(); return false; } ITaskService* pService = NULL; hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (LPVOID*)&pService); if (FAILED(hr)) { //ShowError(L"创建任务计划服务实例失败", hr); CoUninitialize(); return false; } hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t()); if (FAILED(hr)) { //ShowError(L"连接到任务计划服务失败", hr); pService->Release(); CoUninitialize(); return false; } ITaskFolder* pRootFolder = NULL; hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder); if (FAILED(hr)) { //ShowError(L"获取根文件夹失败", hr); pService->Release(); CoUninitialize(); return false; } // 删除已存在的任务 (虽然我们已经检查过,但还是做一次以防万一) hr = pRootFolder->DeleteTask(_bstr_t(wTaskName.c_str()), 0); if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { //ShowError(L"删除现有任务失败", hr); } // 创建新任务 ITaskDefinition* pTask = NULL; hr = pService->NewTask(0, &pTask); if (FAILED(hr)) { //ShowError(L"创建新任务失败", hr); pRootFolder->Release(); pService->Release(); CoUninitialize(); return false; } // 设置任务注册信息 IRegistrationInfo* pRegInfo = NULL; hr = pTask->get_RegistrationInfo(&pRegInfo); if (SUCCEEDED(hr)) { pRegInfo->put_Author(_bstr_t(L"Your Company")); pRegInfo->Release(); } // 设置任务主体 ITaskSettings* pSettings = NULL; hr = pTask->get_Settings(&pSettings); if (SUCCEEDED(hr)) { pSettings->put_StartWhenAvailable(VARIANT_TRUE); pSettings->put_RunOnlyIfNetworkAvailable(VARIANT_FALSE); pSettings->put_DisallowStartIfOnBatteries(VARIANT_FALSE); pSettings->put_StopIfGoingOnBatteries(VARIANT_FALSE); pSettings->Release(); } // 创建登录触发器 ITriggerCollection* pTriggerCollection = NULL; hr = pTask->get_Triggers(&pTriggerCollection); if (SUCCEEDED(hr)) { ITrigger* pTrigger = NULL; hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger); if (SUCCEEDED(hr)) { ILogonTrigger* pLogonTrigger = NULL; hr = pTrigger->QueryInterface(IID_ILogonTrigger, (LPVOID*)&pLogonTrigger); if (SUCCEEDED(hr)) { pLogonTrigger->put_Id(_bstr_t(L"LogonTrigger")); pLogonTrigger->put_UserId(_bstr_t(L"")); // 当前用户 pLogonTrigger->Release(); } pTrigger->Release(); } pTriggerCollection->Release(); } // 创建操作 IActionCollection* pActionCollection = NULL; hr = pTask->get_Actions(&pActionCollection); if (SUCCEEDED(hr)) { IAction* pAction = NULL; hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction); if (SUCCEEDED(hr)) { IExecAction* pExecAction = NULL; hr = pAction->QueryInterface(IID_IExecAction, (LPVOID*)&pExecAction); if (SUCCEEDED(hr)) { pExecAction->put_Path(_bstr_t(wAppPath.c_str())); pExecAction->Release(); } pAction->Release(); } pActionCollection->Release(); } // 注册任务 IRegisteredTask* pRegisteredTask = NULL; hr = pRootFolder->RegisterTaskDefinition( _bstr_t(wTaskName.c_str()), pTask, TASK_CREATE_OR_UPDATE, _variant_t(), // 空用户名 = 当前用户 _variant_t(), // 空密码 = 当前用户 TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(L""), &pRegisteredTask); if (FAILED(hr)) { //ShowError(L"注册任务失败", hr); } else { //MessageBoxW(NULL, L"成功创建登录启动任务", L"信息", MB_ICONINFORMATION); pRegisteredTask->Release(); } pTask->Release(); pRootFolder->Release(); pService->Release(); CoUninitialize(); return SUCCEEDED(hr); } 修改代码绕过QVM360检测 不要修改原有变量
07-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值