Windows 防火墙 C++ API - 如何正确清理 COM 资源?

Posted

技术标签:

【中文标题】Windows 防火墙 C++ API - 如何正确清理 COM 资源?【英文标题】:Windows Firewall C++ API - How to correctly clean up the COM resources? 【发布时间】:2020-03-21 19:38:30 【问题描述】:

我正在尝试使用基于 COM 的 Windows 防火墙 API 来遍历现有的防火墙规则并找出其中是否存在一个特定规则。

目前我很难理解此示例 (https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-enumerating-firewall-rules) 的 Cleanup 部分中发生的情况:

/********************************************************************++
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 指针在每次迭代时都会被覆盖,所以这里我们明确地Releaseing 仅在上面的while 循环中通过QueryInterface 获得的last 规则。

Release从成功调用QueryInterface获得的指针是合乎逻辑的,因为QueryInterface在返回之前调用AddRef(在文档中明确说明)。

但我无法理解的是:

    为什么我们不Release 所有之前遍历的规则,然后再查询循环中的下一个规则?它们是否在某处被隐式释放? QueryInterface 是否调用 Release 卧底以防非空指针被传递给它?

    我们为什么不在pFwRules 上打电话给ReleaseINetFwPolicy2::get_Rules 函数不是给我们一个指向 COM 对象的新指针,它在返回给我们之前是 AddRef'ed(因此调用者最后必须是 Released)?

    关于从get__NewEnum获得的pEnumerator指针的同样问题:我们为什么不把Release这个也一样?

【问题讨论】:

看起来像泄漏。 是的,示例包含错误。需要在QueryInterface 之后调用pFwRule-&gt;Release(); 内部循环就可以了。需要pEnumerator-&gt;Release()打电话 “但我使用了 C++ 包装器,它释放了接口指针”。没有。混合的危险。 如果有任何安慰,请the copy of the sample in the samples repo has been fixed 解决这个问题和其他问题。 【参考方案1】:

代码确实在泄漏 COM 内存。

对接口的AddRef() 方法的每次调用都必须对其Release() 方法进行匹配的调用。任何输出接口指针的函数调用都必须在退出之前调用AddRef(),然后调用者必须在之后调用Release()

一般规则是,对于任何向调用者分配和返回内存的函数,调用者必须在使用完毕后释放它。

所以,回答你的问题:

是的,此代码中缺少对 Release() 的调用,因此有 COM 接口被泄露 - 具体而言:pFwRulespEnumeratorpFwRule 不是 Release()'d正确。

DumpFWRulesInCollection() 也在泄漏 COM 内存。它不会释放由FwRule 的方法输出的任何BSTR 字符串。 而且,当它在循环中调用SafeArrayGetElement() 时,它不会在每次迭代时清除InterfaceString

不,QueryInterface() 不隐含 Release() 一个非空指针。正如SafeArrayGetElement() 不会清除正在写入的元素。

【讨论】:

【参考方案2】:

在研究示例代码时感到困惑是合理的反应。它确实会泄漏资源。

我们为什么不在循环中查询下一个规则之前Release 所有之前遍历的规则?它们是否在某处被隐式释放? QueryInterface 是否调用 Release 卧底以防非空指针传递给它?

没有。 QueryInterface 无条件地覆盖其 ppvObject 参数所指向的值,如果 COM 对象没有实现所请求的接口,则使用 NULL 指针,或者使用指向所请求接口的指针。不调用Release 是资源泄漏。

我们为什么不在pFwRules 上调用ReleaseINetFwPolicy2::get_Rules 函数不是给我们一个指向 COM 对象的新指针,它在返回给我们之前是 AddRef'ed(因此调用者最后必须是 Released)?

再次更正。 get_Rules 返回调用者负责的资源。在返回的接口上不调用Release 是资源泄漏。

关于从get__NewEnum获得的pEnumerator指针的同样问题:我们为什么不把Release这个也一样?

同样的规则在这里也适用:调用者负责清理它收到的迭代器。这也是资源泄漏。


关于 MSDN 示例的特别说明:尽管它们被标记为 “C++”,但大多数 COM 代码示例实际上是用 C 编写的。与 C++ 不同,C 在尊重方面没有太多优势到自动资源管理。

如果您使用 C++,则可以利用自动资源管理,并使用提供的智能指针类型之一(例如 ATL 的 CComPtr 或 Visual C++ 的 _com_ptr_t)。

【讨论】:

"如果您使用 C++,则可以利用自动资源管理,并使用提供的智能指针类型之一" - 这很有趣,因为这个 Microsoft 示例确实使用了一些智能 VARIANT 包装器(CComVariantvariant_t),因此它显然是要编译为 C++,但它没有有效或正确地使用它们来避免泄漏。是的,根本没有使用智能界面包装器。

以上是关于Windows 防火墙 C++ API - 如何正确清理 COM 资源?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中使用 Windows API 隐藏桌面图标?

Windows C++ API:如何将整个二进制文件读入缓冲区?

用windows API 编写一个C++窗口

我们如何在 C++ 和 windows API 中验证 openssl 数字签名

C++ Windows API - 如何检索字体缩放百分比

windows和linux的差异:c++中的反斜杠和正斜杠