[英]Windows Firewall C++ API - How to correctly clean up the COM resources?
我正在嘗試使用基於 COM 的 Windows 防火牆 API 來遍歷現有的防火牆規則,並找出其中是否存在一個特定的規則。
目前我很難理解這個例子的Cleanup
部分發生了什么( https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-enumrating-firewall-rules ) :
/********************************************************************++
Copyright (C) Microsoft. All Rights Reserved.
Abstract:
This C++ file includes sample code for enumerating Windows Firewall
rules using the Microsoft Windows Firewall APIs.
********************************************************************/
#include <windows.h>
#include <stdio.h>
#include <comutil.h>
#include <atlcomcli.h>
#include <netfw.h>
#pragma comment( lib, "ole32.lib" )
#pragma comment( lib, "oleaut32.lib" )
#define NET_FW_IP_PROTOCOL_TCP_NAME L"TCP"
#define NET_FW_IP_PROTOCOL_UDP_NAME L"UDP"
#define NET_FW_RULE_DIR_IN_NAME L"In"
#define NET_FW_RULE_DIR_OUT_NAME L"Out"
#define NET_FW_RULE_ACTION_BLOCK_NAME L"Block"
#define NET_FW_RULE_ACTION_ALLOW_NAME L"Allow"
#define NET_FW_RULE_ENABLE_IN_NAME L"TRUE"
#define NET_FW_RULE_DISABLE_IN_NAME L"FALSE"
// Forward declarations
void DumpFWRulesInCollection(INetFwRule* FwRule);
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2);
int __cdecl main()
{
HRESULT hrComInit = S_OK;
HRESULT hr = S_OK;
ULONG cFetched = 0;
CComVariant var;
IUnknown *pEnumerator;
IEnumVARIANT* pVariant = NULL;
INetFwPolicy2 *pNetFwPolicy2 = NULL;
INetFwRules *pFwRules = NULL;
INetFwRule *pFwRule = NULL;
long fwRuleCount;
// Initialize COM.
hrComInit = CoInitializeEx(
0,
COINIT_APARTMENTTHREADED
);
// Ignore RPC_E_CHANGED_MODE; this just means that COM has already been
// initialized with a different mode. Since we don't care what the mode is,
// we'll just use the existing mode.
if (hrComInit != RPC_E_CHANGED_MODE)
{
if (FAILED(hrComInit))
{
wprintf(L"CoInitializeEx failed: 0x%08lx\n", hrComInit);
goto Cleanup;
}
}
// Retrieve INetFwPolicy2
hr = WFCOMInitialize(&pNetFwPolicy2);
if (FAILED(hr))
{
goto Cleanup;
}
// Retrieve INetFwRules
hr = pNetFwPolicy2->get_Rules(&pFwRules);
if (FAILED(hr))
{
wprintf(L"get_Rules failed: 0x%08lx\n", hr);
goto Cleanup;
}
// Obtain the number of Firewall rules
hr = pFwRules->get_Count(&fwRuleCount);
if (FAILED(hr))
{
wprintf(L"get_Count failed: 0x%08lx\n", hr);
goto Cleanup;
}
wprintf(L"The number of rules in the Windows Firewall are %d\n", fwRuleCount);
// Iterate through all of the rules in pFwRules
pFwRules->get__NewEnum(&pEnumerator);
if(pEnumerator)
{
hr = pEnumerator->QueryInterface(__uuidof(IEnumVARIANT), (void **) &pVariant);
}
while(SUCCEEDED(hr) && hr != S_FALSE)
{
var.Clear();
hr = pVariant->Next(1, &var, &cFetched);
if (S_FALSE != hr)
{
if (SUCCEEDED(hr))
{
hr = var.ChangeType(VT_DISPATCH);
}
if (SUCCEEDED(hr))
{
hr = (V_DISPATCH(&var))->QueryInterface(__uuidof(INetFwRule), reinterpret_cast<void**>(&pFwRule));
}
if (SUCCEEDED(hr))
{
// Output the properties of this rule
DumpFWRulesInCollection(pFwRule);
}
}
}
Cleanup:
// Release pFwRule
if (pFwRule != NULL)
{
pFwRule->Release();
}
// Release INetFwPolicy2
if (pNetFwPolicy2 != NULL)
{
pNetFwPolicy2->Release();
}
// Uninitialize COM.
if (SUCCEEDED(hrComInit))
{
CoUninitialize();
}
return 0;
}
// Output properties of a Firewall rule
void DumpFWRulesInCollection(INetFwRule* FwRule)
{
variant_t InterfaceArray;
variant_t InterfaceString;
VARIANT_BOOL bEnabled;
BSTR bstrVal;
long lVal = 0;
long lProfileBitmask = 0;
NET_FW_RULE_DIRECTION fwDirection;
NET_FW_ACTION fwAction;
struct ProfileMapElement
{
NET_FW_PROFILE_TYPE2 Id;
LPCWSTR Name;
};
ProfileMapElement ProfileMap[3];
ProfileMap[0].Id = NET_FW_PROFILE2_DOMAIN;
ProfileMap[0].Name = L"Domain";
ProfileMap[1].Id = NET_FW_PROFILE2_PRIVATE;
ProfileMap[1].Name = L"Private";
ProfileMap[2].Id = NET_FW_PROFILE2_PUBLIC;
ProfileMap[2].Name = L"Public";
wprintf(L"---------------------------------------------\n");
if (SUCCEEDED(FwRule->get_Name(&bstrVal)))
{
wprintf(L"Name: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_Description(&bstrVal)))
{
wprintf(L"Description: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_ApplicationName(&bstrVal)))
{
wprintf(L"Application Name: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_ServiceName(&bstrVal)))
{
wprintf(L"Service Name: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_Protocol(&lVal)))
{
switch(lVal)
{
case NET_FW_IP_PROTOCOL_TCP:
wprintf(L"IP Protocol: %s\n", NET_FW_IP_PROTOCOL_TCP_NAME);
break;
case NET_FW_IP_PROTOCOL_UDP:
wprintf(L"IP Protocol: %s\n", NET_FW_IP_PROTOCOL_UDP_NAME);
break;
default:
break;
}
if(lVal != NET_FW_IP_VERSION_V4 && lVal != NET_FW_IP_VERSION_V6)
{
if (SUCCEEDED(FwRule->get_LocalPorts(&bstrVal)))
{
wprintf(L"Local Ports: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_RemotePorts(&bstrVal)))
{
wprintf(L"Remote Ports: %s\n", bstrVal);
}
}
else
{
if (SUCCEEDED(FwRule->get_IcmpTypesAndCodes(&bstrVal)))
{
wprintf(L"ICMP TypeCode: %s\n", bstrVal);
}
}
}
if (SUCCEEDED(FwRule->get_LocalAddresses(&bstrVal)))
{
wprintf(L"LocalAddresses: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_RemoteAddresses(&bstrVal)))
{
wprintf(L"RemoteAddresses: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_Profiles(&lProfileBitmask)))
{
// The returned bitmask can have more than 1 bit set if multiple profiles
// are active or current at the same time
for (int i=0; i<3; i++)
{
if ( lProfileBitmask & ProfileMap[i].Id )
{
wprintf(L"Profile: %s\n", ProfileMap[i].Name);
}
}
}
if (SUCCEEDED(FwRule->get_Direction(&fwDirection)))
{
switch(fwDirection)
{
case NET_FW_RULE_DIR_IN:
wprintf(L"Direction: %s\n", NET_FW_RULE_DIR_IN_NAME);
break;
case NET_FW_RULE_DIR_OUT:
wprintf(L"Direction: %s\n", NET_FW_RULE_DIR_OUT_NAME);
break;
default:
break;
}
}
if (SUCCEEDED(FwRule->get_Action(&fwAction)))
{
switch(fwAction)
{
case NET_FW_ACTION_BLOCK:
wprintf(L"Action: %s\n", NET_FW_RULE_ACTION_BLOCK_NAME);
break;
case NET_FW_ACTION_ALLOW:
wprintf(L"Action: %s\n", NET_FW_RULE_ACTION_ALLOW_NAME);
break;
default:
break;
}
}
if (SUCCEEDED(FwRule->get_Interfaces(&InterfaceArray)))
{
if(InterfaceArray.vt != VT_EMPTY)
{
SAFEARRAY *pSa = NULL;
pSa = InterfaceArray.parray;
for(long index= pSa->rgsabound->lLbound; index < (long)pSa->rgsabound->cElements; index++)
{
SafeArrayGetElement(pSa, &index, &InterfaceString);
wprintf(L"Interfaces: %s\n", (BSTR)InterfaceString.bstrVal);
}
}
}
if (SUCCEEDED(FwRule->get_InterfaceTypes(&bstrVal)))
{
wprintf(L"Interface Types: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_Enabled(&bEnabled)))
{
if (bEnabled)
{
wprintf(L"Enabled: %s\n", NET_FW_RULE_ENABLE_IN_NAME);
}
else
{
wprintf(L"Enabled: %s\n", NET_FW_RULE_DISABLE_IN_NAME);
}
}
if (SUCCEEDED(FwRule->get_Grouping(&bstrVal)))
{
wprintf(L"Grouping: %s\n", bstrVal);
}
if (SUCCEEDED(FwRule->get_EdgeTraversal(&bEnabled)))
{
if (bEnabled)
{
wprintf(L"Edge Traversal: %s\n", NET_FW_RULE_ENABLE_IN_NAME);
}
else
{
wprintf(L"Edge Traversal: %s\n", NET_FW_RULE_DISABLE_IN_NAME);
}
}
}
// Instantiate INetFwPolicy2
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2)
{
HRESULT hr = S_OK;
hr = CoCreateInstance(
__uuidof(NetFwPolicy2),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwPolicy2),
(void**)ppNetFwPolicy2);
if (FAILED(hr))
{
wprintf(L"CoCreateInstance for INetFwPolicy2 failed: 0x%08lx\n", hr);
goto Cleanup;
}
Cleanup:
return hr;
}
特別是,這些行讓我感到困惑:
// Release pFwRule
if (pFwRule != NULL)
{
pFwRule->Release();
}
pFwRule
指針在每次迭代時都會被覆蓋,所以這里我們明確地只Release
通過上面while
循環中的QueryInterface
獲得的最后一個規則。
Release
從成功調用QueryInterface
獲得的指針是合乎邏輯的,因為QueryInterface
在返回之前調用AddRef
(在文檔中明確說明)。
但我無法理解的是:
為什么我們不在查詢循環中的下一個之前Release
所有先前遍歷的規則? 它們是否在某處隱式釋放? QueryInterface
是否調用Release
卧底以防傳遞給它的非空指針?
為什么我們不在pFwRules
上調用Release
? INetFwPolicy2::get_Rules
函數不是給了我們一個指向 COM 對象的新指針,它在返回給我們之前被AddRef
'ed(因此最終必須由調用者Release
d)?
對同樣的問題pEnumerator
從獲得的指針get__NewEnum
:我們為什么不Release
這一個呢?
對接口的AddRef()
方法的每次調用都必須對其Release()
方法進行匹配調用。 任何輸出接口指針的函數調用都必須在退出前對其調用AddRef()
,然后調用者必須在其之后調用Release()
。
一般規則是,對於任何分配內存並將其返回給調用者的函數,調用者必須在使用完后釋放它。
所以,回答你的問題:
是的,在這段代碼中缺少對Release()
調用,所以有 COM 接口被泄露——特別是: pFwRules
、 pEnumerator
和pFwRule
沒有被正確地Release()
。
DumpFWRulesInCollection()
也會泄漏 COM 內存。 它不會釋放由FwRule
的方法輸出的任何BSTR
字符串。 而且,當它在循環中調用SafeArrayGetElement()
時,它不會在每次迭代時清除InterfaceString
。
不, QueryInterface()
不會隱式地Release()
一個非空指針。 正如SafeArrayGetElement()
不會清除正在寫入的元素一樣。
在研究示例代碼時感到困惑是一種合理的反應。 它確實泄漏了資源。
為什么我們不在查詢循環中的下一個之前
Release
所有先前遍歷的規則? 它們是否在某處隱式釋放?QueryInterface
是否調用Release
卧底以防傳遞給它的非空指針?
號QueryInterface
無條件覆蓋值指向其ppvObject
參數,無論是與一個NULL指針,如果COM對象沒有實現所請求的接口,或者與指針所請求的接口。 不調用Release
是一種資源泄漏。
為什么我們不在
pFwRules
上調用Release
?INetFwPolicy2::get_Rules
函數不是給了我們一個指向 COM 對象的新指針,它在返回給我們之前被AddRef
'ed(因此最終必須由調用者Released
)?
再次糾正。 get_Rules
返回調用者負責的資源。 不在返回的接口上調用Release
是一種資源泄漏。
對同樣的問題
pEnumerator
從獲得的指針get__NewEnum
:我們為什么不Release
這一個呢?
同樣的規則也適用於這里:調用者負責清理它收到的迭代器。 這也是一種資源泄漏。
關於 MSDN 示例的特別說明:盡管它們被標記為"C++" ,但 COM 的大多數代碼示例實際上是用 C 編寫的。與 C++ 不同,C 在自動資源管理方面沒有什么可提供的。
如果您使用 C++,您可以利用自動資源管理,並使用提供的智能指針類型之一(例如 ATL 的CComPtr或 Visual C++ 的_com_ptr_t )。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.