65.9K
CodeProject 正在变化。 阅读更多。
Home

“动态”关键字提高生产力:Windows 防火墙 API 示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (11投票s)

2010年5月20日

Ms-PL

6分钟阅读

viewsIcon

24140

downloadIcon

355

在进行 COM 操作时,“dynamic”关键字可以派上用场。反过来,当你需要完成与 Windows API(公开 COM 功能)相关的任务时,你可以更高效。

引言

.NET 4.0 框架引入了一个新关键字:dynamic。在 MSDN 上,dynamic 关键字的描述如下:

该类型是一个静态类型,但 dynamic 类型的对象会绕过静态类型检查。在大多数情况下,它的功能类似于具有对象类型。在编译时,被类型化为 dynamic 的元素被假定支持任何操作。因此,你不必担心该对象的值是来自 COM API、来自 IronPython 等动态语言、来自 HTML 文档对象模型 (DOM)、来自反射,还是来自程序中的其他地方。但是,如果代码无效,则会在运行时捕获错误。

Scott 有一篇关于 dynamic 关键字的精彩文章,并附带了一些不错的示例:http://www.hanselman.com/blog/C4AndTheDynamicKeywordWhirlwindTourAroundNET4AndVisualStudio2010Beta1.aspx

简而言之,你可以为 dynamic 对象编写任何你想要的代码,你的应用程序将始终编译。你编写的代码将在运行时进行评估。

过去是怎样的

对于我的一个项目,我需要在 Windows 防火墙中添加例外。

MSDN 确实为我提供了一些示例,说明如何做到这一点:http://msdn.microsoft.com/en-us/library/aa366415(v=VS.85).aspx。缺点是:这些示例只涵盖 C/C++/VBScript。

VBScript

啊,是的,VBScript。看看这个脚本来处理 Windows 防火墙 API

' Create the firewall manager object.
Dim fwMgr
Set fwMgr = CreateObject("HNetCfg.FwMgr")
 
' Get the current profile for the local firewall policy.
Dim profile
Set profile = fwMgr.LocalPolicy.CurrentProfile

就是这样,很简单。没有引用,没有与 COM 对象的映射……你只需查看 MSDN 参考并编写代码。这就是提高生产力。

但我并不是说这很完美。缺点是你缺少编译器的强大功能。如果你犯了一个错误,将 LocalPolicy 写成了 LocalLopicy,那么,你只有在运行那行代码时才会发现。

C/C++

HRESULT hr = S_OK;
INetFwMgr* fwMgr = NULL;
INetFwPolicy* fwPolicy = NULL;

_ASSERT(fwProfile != NULL);

*fwProfile = NULL;

// Create an instance of the firewall settings manager.
hr = CoCreateInstance(
        __uuidof(NetFwMgr),
        NULL,
        CLSCTX_INPROC_SERVER,
        __uuidof(INetFwMgr),
        (void**)&fwMgr
        );
if (FAILED(hr))
{
    printf("CoCreateInstance failed: 0x%08lx\n", hr);
    goto error;
}

// Retrieve the local firewall policy.
hr = fwMgr->get_LocalPolicy(&fwPolicy);
if (FAILED(hr))
{
    printf("get_LocalPolicy failed: 0x%08lx\n", hr);
    goto error;
}

// Retrieve the firewall profile currently in effect.
hr = fwPolicy->get_CurrentProfile(fwProfile);
if (FAILED(hr))
{
    printf("get_CurrentProfile failed: 0x%08lx\n", hr);
    goto error;
}

不得不写那么多代码效率不高……无需多言。

C# 和 ComImport

Jon Cole 写了一篇不错的文章,关于在 C# 中使用 ComImport 与 Windows 防火墙 API 通信

[ComImport, ComVisible(false), Guid("F7898AF5-CAC4-4632-A2EC-DA06E5111AF2"), 
System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface INetFwMgr
{
     INetFwPolicy LocalPolicy { get; }
     FirewallProfileType CurrentProfileType { get; }
  
     void RestoreDefaults();
     void IsPortAllowed(string imageFileName, IPVersion ipVersion, 
          long portNumber, string localAddress, IPProtocol ipProtocol, 
          [Out] out bool allowed, [Out] out bool restricted);
     void IsIcmpTypeAllowed(IPVersion ipVersion, string localAddress, 
          byte type, [Out] out bool allowed, [Out] out bool restricted);
}

对我来说,这是一个半干净的解决方案。但它涉及大量的试错和研究……而且代码也很多……

George Mamaladze 也使用 *FirewallAPI.dll* 实现了一些功能。

.NET 4.0 以后是怎样的

在进行 COM 操作时,dynamic 关键字可以派上用场。反过来,当你需要完成与 Windows API(公开 COM 功能)相关的任务时,你可以更高效。现在我可以像在 VBScript 中工作一样访问 COM 对象了

Type fwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr");
dynamic fwMgr = Activator.CreateInstance(fwMgrType);

bool isFwEnabled = fwMgr.LocalPolicy.CurrentProfile.FirewallEnabled;

工作原理

  • 获取要初始化的 COM 对象的类型。在这种情况下,我决定使用 ProgID(唯一“名称”)。为什么?因为它在 MSDN 示例中提供给了我:http://msdn.microsoft.com/en-us/library/aa366423(v=VS.85).aspx
  • 使用 Activator 创建此类型的实例,并将其放入“动态对象”中。
  • 之后,我就可以编写任何我认为正确的代码了……

这让你可以直接获取现有的 VBScript 代码,稍微修改一下,就能得到可编译的 .NET 代码。所有那些在 VBScript 中很容易完成的事情,现在在 .NET 中也变得容易了。

有了现代单元测试工具,我认为缺失的编译器支持毕竟不是什么大问题。

使用“dynamic”的 Windows 防火墙 API

现在来看我们的示例。我编写了一些类,实现了 Windows 防火墙 API 的一些有趣部分。老实说,我只在我的本地机器(Windows Server 2008 R2)上测试过,所以如果你在使用代码时遇到任何问题,请告诉我。

我做了三件事

  • 映射了一些枚举
  • 映射了一些对象
  • 创建了两个“工作类”

映射枚举

/// <summary>
/// Msdn reference: http://msdn.microsoft.com/en-us/library/aa366282(v=VS.85).aspx
/// </summary>
public enum NetFwAction
{
    Block = 0,
    Allow = 1,
    Max = 2
}

这没什么特别的。这只是与 MSDN 上找到的信息进行映射。当前枚举:

  • NetFwAction
  • NetFwIpProtocol
  • NetFwIpVersion
  • NetFwProfile
  • NetFwRuleDirection
  • NetFwScope

映射对象

我还需要以下对象:NetFwAuthorizedApplicationNetFwRule

我首先是这样尝试的

/// <summary>
/// Msdn reference: http://msdn.microsoft.com/en-us/library/aa365100(v=VS.85).aspx
/// </summary>
public class NetFwAuthorizedApplication
{
    ...
   
    /// <summary>
    /// Initialize the application object.
    /// </summary>
    /// <param name="comObj"></param>
    internal NetFwAuthorizedApplication(dynamic comObj)
    {
        this.Name = comObj.Name;
        this.RemoteAddress = comObj.RemoteAddress;
        this.Enabled = comObj.Enabled;
        ...
    }
}

这再次展示了 dynamic 关键字的强大功能。只需查看参考(http://msdn.microsoft.com/en-us/library/aa365100(v=VS.85).aspx),我就可以在没有任何额外代码的情况下访问属性。如果我想要对象的 RemoteAddress,我只需编写 comObj.RemoteAddress

对我来说,这是一个干净的解决方案,但可能工作量太大了。如果我们想为每个新对象都进行映射,那就没那么高效了。这就是为什么我还编写了一个基类来自动处理映射。它使用反射,所以你可能会因此受到(轻微的)性能影响。

public class DynamicComMapper
{ 
     /// <summary>
     /// Map all the properties of the object to the properties of the COM object.
     /// </summary>
     /// <param name="comObj"></param>
     protected void Map(dynamic comObj)
     {
         foreach (PropertyInfo property in this.GetType().GetProperties())
         {
             try
             {
                 // Retrieve the value of a property of the COM object.
                 object comPropertyValue = typeof(Object).InvokeMember(
                   property.Name, BindingFlags.GetProperty, null, comObj, null);
    
                 // Set value in our managed object.
                 property.SetValue(this, comPropertyValue, null);
             }
             catch (COMException)
             {
                 // You could skip errors for unknown properties.
                 // if (ex.ErrorCode != -2147352570)
                 //    throw ex;
             }
         }
     }
}

这个“对象映射”的基类尝试从 COM 对象的属性中获取与我们当前对象的属性匹配的值。这意味着,我的 NetFwAuthorizedApplication 对象的所有属性都将自动填充。

创建工作类

由于我们使用的是 COM,我们还必须考虑清理资源。这就是为什么我们将使用 DynamicComBase 作为工作类的基类。这个类实现了 IDisposable 以自动清理(因为动态对象 comObj 包含在这个基类中)。

它还提供了清理将用于我们“映射对象”中的临时 COM 对象的功能。

/// <summary>
/// Release a COM object so that it can be cleaned up.
/// </summary>
/// <param name="comObj"></param>
protected void ReleaseCom(dynamic comObj)
{
    Marshal.ReleaseComObject(comObj);
}
  
/// <summary>
/// Clean up all released COM objects.
/// </summary>
protected void CleanAllReleased()
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

现在我们有了基类,我们可以创建 NetFwMgr

public class NetFwMgr : DynamicComBase
{
    /// <summary>
    /// Default constructor.
    /// </summary>
    public NetFwMgr()
    { 
        // Initialize the COM object.
        Type fwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr");
        base.comObj = Activator.CreateInstance(fwMgrType);
    }
  
    /// <summary>
    /// Set the port in the firewall.
    /// </summary>
    /// <param name="profile"></param>
    /// <param name="name"></param>
    /// <param name="protocol"></param>
    /// <param name="port"></param>
    /// <param name="scope"></param>
    /// <param name="enabled"></param>
    public void SetPort(NetFwProfile profile, string name, NetFwIpProtocol protocol, 
                        int port, NetFwScope scope, bool enabled)
    {
         if (profile == NetFwProfile.All)
             throw new ArgumentException("The profile 'All' is not allowed here.");
  
         // Create a port object.
         Type fwPortType = Type.GetTypeFromProgID("HNetCfg.FwOpenPort");
         dynamic fwPort = Activator.CreateInstance(fwPortType);
  
         // Configure the port.
         fwPort.Name = name;
         fwPort.Protocol = (int)protocol;
         fwPort.Port = port;
         fwPort.Scope = (int)scope;
         fwPort.Enabled = enabled;
  
         // Add to profile.
         comObj.LocalPolicy.GetProfileByType((int)profile).GloballyOpenPorts.Add(fwPort);
    }
  
    /// <summary>
    /// Verify if the firewall is enabled.
    /// Note, this will fail if the service is stopped.
    /// </summary>
    public bool IsEnabled
    { 
         get
         {
             return comObj.LocalPolicy.CurrentProfile.FirewallEnabled;
         }
    }
  
    /// <summary>
    /// Get a list of authorized applications.
    /// </summary>
    public List<NetFwAuthorizedApplication> GetAuthorizedApplications()
    {
         // Create a new list.
         List<NetFwAuthorizedApplication> applications = 
             new List<NetFwAuthorizedApplication>();
  
         // Get all applications and create typed objects.
         foreach (dynamic application in 
            comObj.LocalPolicy.CurrentProfile.AuthorizedApplications)
         {
             applications.Add(new NetFwAuthorizedApplication(application));
  
             // Clean current COM object.
             ReleaseCom(application);
         }
  
         // Clean up all released COM objects.
         CleanAllReleased();
  
         // Done.
         return applications;
    }
}

这个类是如何工作的?

  • 首先,我们根据 ProgID 获取要创建的对象的类型。
  • 我们使用 Activator 创建此类型的一个实例,并将其放入一个动态对象中(位于基类中)。
  • 接下来,我们使用映射的对象(例如:NetFwAuthorizedApplication)和动态方法调用(例如:comObj.LocalPolicy.CurrentProfile.AuthorizedApplications)。
  • 使用 ReleaseComCleanAllReleased 清理临时对象。

该项目还包含一个使用这些类的测试应用程序,结果如下:

demoappwinfirewall.png

接下来呢?

目前,我编写的类允许你

  • 启用/禁用防火墙上的端口
  • 验证防火墙是否对当前配置文件处于活动状态
  • 获取当前配置文件的授权应用程序列表
  • 获取防火墙中所有规则的列表

但实际上,我所做的不过是修改 MSDN 上现有的 VBScript 示例:http://msdn.microsoft.com/en-us/library/aa366415(v=VS.85).aspx。使用这些基类,你可以轻松添加自己的代码来处理其他常见任务,你可以从查看 VBScript 代码开始。

另一方面,你也可以使用这些基类将其他 VBScript“移植”到 .NET。只需看看这里:http://gallery.technet.microsoft.com/ScriptCenter/en-us,你就能很快访问备份、Windows 更新等功能。

唯一不能忘记的是编写单元测试!由于我们没有编译器的帮助来追踪动态对象中的错误,我们的单元测试是唯一的防线……

我希望这篇文章向你展示了新 dynamic 关键字的一些优点,你也会开始使用它了……

尽情享用!

© . All rights reserved.