Introduction
The .NET 4.0 framework introduces a new keyword, dynamic
. On MSDN, the dynamic
keyword is described as follows:
The type is a static type, but an object of type dynamic bypasses static type checking. In most cases, it functions like it has a type object. At compile time, an element that is typed as dynamic is assumed to support any operation. Therefore, you do not have to be concerned about whether the object gets its value from a COM API, from a dynamic language such as IronPython, from the HTML Document Object Model (DOM), from Reflection, or from somewhere else in the program. However, if the code is not valid, errors are caught at run time.
Scott has a great post on the dynamic
keyword, with a few good examples: http://www.hanselman.com/blog/ C4AndTheDynamicKeywordWhirlwindTourAroundNET4AndVisualStudio2010Beta1.aspx.
In short, you can write anything you want for dynamic objects and your application will always compile. What you wrote will be evaluated at run time.
How it was
For one of my projects, I was required to add exceptions in the Windows Firewall.
MSDN did provide me with some examples of how to do this: http://msdn.microsoft.com/en-us/library/aa366415(v=VS.85).aspx. The downside: these examples only cover C/C++/VBScript.
VBScript
Ah yes, VBScript. Look at this script to handle the Windows Firewall API:
Dim fwMgr
Set fwMgr = CreateObject("HNetCfg.FwMgr")
Dim profile
Set profile = fwMgr.LocalPolicy.CurrentProfile
That's it, easy. No references, no mapping with COM objects... You just look at the MSDN reference and you write the code. That's being productive.
But I don't say that this is perfect. The downside of this is that you are missing the strength of the compiler. If you would make an error and write LocalLopicy
instead of LocalPolicy
, well, you'd only find out when running that line of code.
C/C++
HRESULT hr = S_OK;
INetFwMgr* fwMgr = NULL;
INetFwPolicy* fwPolicy = NULL;
_ASSERT(fwProfile != NULL);
*fwProfile = NULL;
hr = CoCreateInstance(
__uuidof(NetFwMgr),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwMgr),
(void**)&fwMgr
);
if (FAILED(hr))
{
printf("CoCreateInstance failed: 0x%08lx\n", hr);
goto error;
}
hr = fwMgr->get_LocalPolicy(&fwPolicy);
if (FAILED(hr))
{
printf("get_LocalPolicy failed: 0x%08lx\n", hr);
goto error;
}
hr = fwPolicy->get_CurrentProfile(fwProfile);
if (FAILED(hr))
{
printf("get_CurrentProfile failed: 0x%08lx\n", hr);
goto error;
}
Having to write that much code is not productive... enough said.
C# with ComImport
Jon Cole wrote a nice article on using ComImport
in C# to talk to the Windows Firewall 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);
}
For me, this is a semi-clean solution. But it involves a lot of trial and error, and also research... And it's a lot of code too...
George Mamaladze also implemented a few functionalities using FirewallAPI.dll.
How it is since .NET 4.0
The dynamic
keyword can be put to good use when doing COM. And in turn, you could be more productive when you need to complete tasks regarding the Windows API (that expose COM functionality). Now I can access COM objects as if I were working in VBScript:
Type fwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr");
dynamic fwMgr = Activator.CreateInstance(fwMgrType);
bool isFwEnabled = fwMgr.LocalPolicy.CurrentProfile.FirewallEnabled;
How it works:
- Get the type of the COM object you want to initialize. In this case, I decided to use the ProgID (unique 'name'). Why? Because it was given to me in the MSDN example: http://msdn.microsoft.com/en-us/library/aa366423(v=VS.85).aspx
- Create an instance of this type using
Activator
, and put it in a 'dynamic object'. - And after that, I can write whatever code I think is right...
This gives you the possibility to just take existing VBScript code, modify it a bit, and you'll have compliable .NET code. All those things that were easy to do in VBScript are now easy to do in .NET
And with the modern unit testing tools, I think the missing compiler support isn't such a big issue after all.
Windows Firewall API using 'dynamic'
And now for our example. I've written some classes that implement a few interesting parts of the Windows Firewall API. To be honest, I only tested this on my local machine (Windows Server 2008 R2), so please let me know if you encounter any issues while using the code.
I've done three things:
- Map a few enums
- Map a few objects
- Created two 'worker classes'
Mapping the enums
public enum NetFwAction
{
Block = 0,
Allow = 1,
Max = 2
}
Nothing fancy about this. This is just a mapping with the information found on MSDN. Current enums:
NetFwAction
NetFwIpProtocol
NetFwIpVersion
NetFwProfile
NetFwRuleDirection
NetFwScope
Mapping the objects
I also needed the following objects: NetFwAuthorizedApplication
and NetFwRule
.
First I tried it like this:
public class NetFwAuthorizedApplication
{
...
internal NetFwAuthorizedApplication(dynamic comObj)
{
this.Name = comObj.Name;
this.RemoteAddress = comObj.RemoteAddress;
this.Enabled = comObj.Enabled;
...
}
}
Again, this shows the power of the dynamic
keyword. Just by looking at the reference (http://msdn.microsoft.com/en-us/library/aa365100(v=VS.85).aspx), I can access the properties without any extra code. If I want the RemoteAddress
of the object, I just write comObj.RemoteAddress
.
For me, this is a clean solution, but maybe too much work. If we have to do the mapping for each new object we want, this is not so productive. That's why I also wrote a base class that handles the mapping automatically. It uses Reflection so you might get a (small) performance hit doing it this way.
public class DynamicComMapper
{
protected void Map(dynamic comObj)
{
foreach (PropertyInfo property in this.GetType().GetProperties())
{
try
{
object comPropertyValue = typeof(Object).InvokeMember(
property.Name, BindingFlags.GetProperty, null, comObj, null);
property.SetValue(this, comPropertyValue, null);
}
catch (COMException)
{
}
}
}
}
This base class for "object mappings" tries to get the value from the COM object's property matching our current object's property. Meaning, all properties of my NetFwAuthorizedApplication
object will be populated automatically.
Creating the worker classes
Since we're working with COM, we also have to think about cleaning up resources. That's why we'll use the DynamicComBase
as the base class for our worker classes. This class implements IDisposable
for cleaning up things automatically (since the dynamic object comObj
is included in this base class).
It also provides functionality to clean up temporary COM objects that will be used in our "mapped objects".
protected void ReleaseCom(dynamic comObj)
{
Marshal.ReleaseComObject(comObj);
}
protected void CleanAllReleased()
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Now that we have a base class, we can create the NetFwMgr
:
public class NetFwMgr : DynamicComBase
{
public NetFwMgr()
{
Type fwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr");
base.comObj = Activator.CreateInstance(fwMgrType);
}
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.");
Type fwPortType = Type.GetTypeFromProgID("HNetCfg.FwOpenPort");
dynamic fwPort = Activator.CreateInstance(fwPortType);
fwPort.Name = name;
fwPort.Protocol = (int)protocol;
fwPort.Port = port;
fwPort.Scope = (int)scope;
fwPort.Enabled = enabled;
comObj.LocalPolicy.GetProfileByType((int)profile).GloballyOpenPorts.Add(fwPort);
}
public bool IsEnabled
{
get
{
return comObj.LocalPolicy.CurrentProfile.FirewallEnabled;
}
}
public List<NetFwAuthorizedApplication> GetAuthorizedApplications()
{
List<NetFwAuthorizedApplication> applications =
new List<NetFwAuthorizedApplication>();
foreach (dynamic application in
comObj.LocalPolicy.CurrentProfile.AuthorizedApplications)
{
applications.Add(new NetFwAuthorizedApplication(application));
ReleaseCom(application);
}
CleanAllReleased();
return applications;
}
}
How does this class work?
- First, we get the type of the object we want to create, based on the ProgID
- We create an instance of this type using the
Activator
and put it in a dynamic object (residing in the base class) - Next, we use our mapped objects (e.g.:
NetFwAuthorizedApplication
) and the dynamic method calls (e.g.: comObj.LocalPolicy.CurrentProfile.AuthorizedApplications
) - Clean up temporary objects using
ReleaseCom
and CleanAllReleased
The project also includes a test application that uses the classes, and this is the result:
What's next?
Currently, the classes I've written allow you to:
- Enable/Disable a port on the firewall
- Verify if the firewall is active for the current profile
- Get a list of authorized applications for the current profile
- Get a list of all rules in the firewall
But actually, I've done nothing more than just modify the existing VBScript examples available on MSDN: http://msdn.microsoft.com/en-us/library/aa366415(v=VS.85).aspx. Using the base classes, you can easily add your own code to handle other common tasks, and you can start by looking at the VBScript code.
On the other hand, you can also use these base classes to 'port' other VBScripts to .NET. Just take a look here: http://gallery.technet.microsoft.com/ScriptCenter/en-us and you'll be able to access things like backups, Windows update, ... in no time.
The only thing not to forget is writing unit tests! Since we don't have help from the compiler for tracing errors in dynamic objects, our unit tests are the only line of defense...
I hope this article showed you a few of the strengths of the new dynamic
keyword and you'll start using it too..
Enjoy!