Showing posts with label c#. Show all posts
Showing posts with label c#. Show all posts

Friday, July 20, 2007

Finding the interfaces you want

This is a simple little .NET reflection trick, but one which not a lot of people realize how to do (I get asked about this a lot). Suppose you want to search an assembly for all the types that implement a given interface. For example, your application supports plug-ins and you want to load a third-party assembly that potentially contains plug-in classes that implement your plug-in interface.

C#:

Assembly assembly = Assembly.LoadFile(@"C:\path\to\3rdparty\plug-in.dll");
foreach (Type type in assembly.GetTypes())
{
if (typeof(IMyPlugin).IsAssignableFrom(type))
{
IMyPlugin plugin = (IMyPlugin)assembly.CreateInstance(type.FullName);
// do something with the plugin ...
}
}


VB.NET:

Dim assembly As Assembly = Assembly.LoadFile("C:\path\to\3rdparty\plug-in.dll");
Dim type As Type
For Each type In assembly.GetTypes()
If GetType(IMyPlugin).IsAssignableFrom(type)
Dim plugin As IMyPlugin = CType(assembly.CreateInstance(type.FullName), IMyPlugin)
' do something with the plugin ...
End If
Next


It's pretty straightforward. First, we use the Assembly object to load the third party assembly from its DLL. Then, we use the Assembly.GetTypes() method to iterate through all of the classes present in the assembly. Inside the loop, we use Type.IsAssignableFrom() to determine whether the class can be assigned to our IMyPlugin interface. Finally, we create an instance of the class and assign it to a variable of type IMyPlugin so that we can actually do something with it (note that the above code assumes the class has a parameterless constructor).

Note that you want to check the possibilty of assignment in the order shown above, i.e. typeof(interface).IsAssignableFrom(class). If you do it the other way around, it won't work. Basically this statement is testing to see if this is possible:

C#:

interface variableName = new class();


VB.NET:

Dim variableName As interface = New class()

Thursday, June 21, 2007

References and classes... make sure you mean it

All too often, I see things like this:

C#:
public void DoSomethingToAClass(ref AClass objectToChange)
{
// ... change some member variables of objectToChange or whatever
}


VB.NET:
Public Sub DoSomethingToAClass(ByRef objectToChange As AClass)
' ... change some member variables of objectToChange or whatever
End Sub


This is totally redundant. Classes are reference types. Therefore, by default they are passed by reference. This means that you can drop the ref or ByRef and achieve the same results. What's worse is that in the examples above, you're creating a reference to a reference, which just creates extra work for the application dereferencing them twice to find the actual object.

On the other hand, if what you're passing in to the method is a value type, then this is what you want. Your intrinsic types (such as Int32) and struct (C#) or Structure (VB.NET) are all value types. These need the ref or ByRef tag, otherwise your changes will be lost when the method exits. Note that while Arrays and Strings are also technically reference types, the framework handles them somewhat differently, and you must use ref or ByRef with them to get the desired result.

There are times when a reference to a reference is what you want, especially with certain P/Invoke methods that ask for a "pointer to a pointer" as one of the parameters. In this case, using ref or ByRef with a reference type makes sense and is what you want.

Monday, April 9, 2007

Using .NET objects via COM

Recently I created an object in .NET that I needed to use both from ASP.NET applications and classic ASP. I really didn't want to create a web service interface for just this one little object, so I delved into the wonderful world of COM again.

If you want to instantiate your .NET object via COM, there are some basic rules you have to follow.
  • The object must implement a public, parameterless constructor.
  • Only your instance methods and properties will be visible via COM (i.e. no static stuff).
  • You have to take care when using overloaded methods.

First, let's look at the code in C#.

using System;
using System.Runtime.InteropServices; // for the custom attributes below

namespace MyCompany
{
[ComVisible(true)]
[Guid("4663D268-454F-4338-B6BB-44537F5A0266")]
[ProgId("MyCompany.MyProject.MyClass")]
public class MyClass
{
private bool boolfield;

public MyClass()
{
boolfield = false;
}

public bool BoolField
{ get { return boolfield; } set { boolfield = value; } }

public string PrintBoolField()
{
return "BoolField is currently " + BoolField;
}

public string PrintBoolField(bool newValue)
{
BoolField = newValue;
return PrintBoolField();
}
}
}


There's several things going on in the above example. First of all, it's a pretty simple class. We have an overloaded set of methods, one property, and one constructor. You'll notice it's decorated with three attributes:

ComVisibleAttribute: Necessary so that your class will be exported to COM. You can also set this one at the assembly level.

GuidAttribute: This allows you to explicitly set a GUID for the generated COM object. If you don't specify one, the steps below will create one automatically, but it's best if you keep a consistent GUID in each installation when working with the same object, since you never know who will try to reference you by GUID.

ProgIdAttribute: This is only necessary if you want to override the ProgId of your COM object. By default, the ProgId will be <namespace>.<classname>.

So once you've got that worked out, compile your object into a class library. The next thing you need to is register your assembly with COM.

In your .NET framework folder you'll find a handy tool called regasm. regasm lets you register .NET assemblies as COM objects and does all the magic behind-the-scenes work for you.

There are two ways you can set this up:
  • Put the assembly in the global assembly cache (GAC), and then register with regasm. This is good if you want your COM object to be globally accessible.
  • Put the assembly in the application folder of the client application you want to be able to call the object. Register with regasm using the /codebase flag. The COM object will only be accessible to that application.


The choice is up to you based on your particular needs, but I recommend using the GAC. This is because you might want to use the object again elsewhere, and it's a pain in the butt to unregister and then re-register COM objects due to locking issues.

Now that you've done that, you can create an instance of your COM object from classic ASP like normal:


<html>
<body>
<p>
<%
Dim myobj : Set myobj = Server.CreateObject("MyCompany.MyProject.MyClass")

myobj.BoolField = True
' Should print: BoolField is currently True
Response.Write myobj.PrintBoolField()
Response.Write "<br>"

' Should print: BoolField is currently False
Response.Write myobj.PrintBoolField_2(False)
%>
</p>
</body>
</html>


And it's that easy. You can now access your nice managed .NET object in classic ASP/VBScript as if it were a COM object. And, of course, this is not limited to ASP... you can do this anywhere that you would normally be able to instantiate COM objects. Although, make sure you remember that your COM object is actually .NET... it would be worrisome to forget and sometime down the road import it into a .NET project with a redundant Interop assembly.

You may notice in the example code above, the overload to PrintBoolField has been renamed to PrintBoolField_2. That didn't exist in the original code, did it? You're right, it didn't.

COM doesn't support method overloads. When regasm builds its COM magic, it automatically appends a suffix of _2, _3, etc. to any overloaded methods. If you get confused about which overload is which, you can use a tool like OLE View® to help you figure it out.

One last note: Make sure that any base classes or interfaces your objects implement are also COM visible as well. Otherwise, you will get a mysterious error code 80131509 whenever you try to create your object.

Monday, April 2, 2007

Close()? Dispose()? A .NET programmer needs not these things!

Hogwash.

In reading Tess' MSDN blog this morning, it got me thinking about some things with object disposal and garbage collection. Question 12 on her little pop quiz in particular:

12. Why is it important to close database connections and dispose of objects? Doesn't the GC take care of that for me?


Of course, the first poster already answers it:

12/ It does, in the finalizer thread, which takes a _long_ time to process things. Basically this shove stuff like connection pooling out of the window, not to mention that basically resources are held for much longer.


I've witnessed several occasions where the Close() and Dispose() methods are completely ignored by programmers. Streams are left hanging, web services are left dangling... it's a mess!

Usually Close() isn't so much of a problem. Developers are typically pretty aware when they're opening a file or database connection. Still, you need to be keenly aware of what you're doing when you write something such as this:


public class SqlConnectionMgr
{
public SqlConnection GetDBConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
}


This is overly simplified, but I've seen similar code dozens of times. Now you've just create a connection to the database, but you've thrown it out into the wild to hope that whomever calls your code remembers to close it. If you can trust that person, fine. If not, you may find it better do something like this:


public class SqlConnectionMgr : IDisposable
{
private SqlConnection connection;

public SqlConnection GetDBConnection(string connectionString)
{
if (connection != null && connection.State == ConnectionState.Open)
connection.Close();
connection = new SqlConnection(connectionString);
return connection;
}

public void Dispose()
{
if (connection != null && connection.State == ConnectionState.Open)
connection.Close();
GC.SuppressFinalize(this);
}

~SqlConnectionMgr()
{
Dispose();
}
}


Now you can retain management of the connection in your own class. You have to implement IDisposable now, but that's a more implicit direction to the consumer that something has to be done, and you can take part of the onus of managing resources off the consumer in case they forget.

Some things to remember when implementing IDisposable:
  • Make sure to also implement a finalizer which calls your Dispose() method in case the programmer forgets to dispose of the object.
  • In the Dispose() method, you need to call the garbage collector's SuppressFinalize(object) method. This will ensure that if the object was already disposed, the finalizer is not called needlessly.


(And yes, I know that in changing my example above it no longer is able to pass out multiple connections. I'll leave that as a challenge for the reader.)

IDisposable is an excellent way of managing resource, because, well, that's why it was added to the framework. You need to be keenly aware of whether or not the objects you're consuming implement this interface. (The easiest way to find out if you're in the middle of coding is to look through the object's IntelliSense list for the Dispose() method.)

C# provides an excellent mechanism for working with disposable objects known as the using statement:


using (MyDisposableObject obj = new MyDisposableObject())
{
// do something ...
}


When this code executes, the object is created and neatly disposed of within the scope of the using statement. This basically is just a clean, shorthand way of writing:


MyDisposableObject obj = new MyDisposableObject();
// do something ...
obj.Dispose();


There are a lot of objects in the .NET framework that use resources that need to be disposed, including unmanaged resources that can wreak havoc if left open waiting for the garbage collector to come along. One in particular that I have run into is the Bitmap class. If you don't dispose of a Bitmap when you're done with it, depending on the size of the image you could end up with a huge chunk of the heap sitting around doing nothing, waiting to be removed from memory at some indeterminate point in the future.

While .NET provides a lovely managed platform, be sure that you are still keenly aware of resources that you're using. While it's not as easy to lose pointers and form memory leaks in a .NET application as it is in an unmanaged C++ application, you can still waste plenty of system resources waiting around for the garbage collector to do your cleanup for you.

Sunday, February 4, 2007

Trust in ADO.NET

In reviewing some C# code for a project at work, I'm constantly running across the following construct in the code (and this is from different developers, not just one):


DataSet ds = SqlHelper.ExecuteDataSet(sConn, CommandType.StoredProc, sProc, aParams);
// ... snip ...
DataRow row = ds.Tables[0].Rows[0];
int someInt = Int32.Parse(row["intColumn"].ToString());


Now, while this code works like you would expect, there's still something wrong with it. You're basically wasting cycles on mindless conversions.

Whenever you use ADO.NET DataRow objects, it is true that the return from the indexer is a generic Object, so something needs to happen to get the result into a strongly-typed value. But, if the database column is a SQL int, ADO.NET already returned you an Int32!

What's written above is basically the same as this:


int a = 1;
int b = Int32.Parse(a.ToString());


You're wasting cycles first converting the Int32 to a String and then using Int32.Parse to get it back to the Int32 that it was in the first place. Trust in ADO.NET!


DataSet ds = SqlHelper.ExecuteDataSet(sConn, CommandType.StoredProc, sProc, aParams);
// ... snip ...
DataRow row = ds.Tables[0].Rows[0];
int someInt = (int)row["intColumn"];


There we go, much better. If you want something that feels a little "safer" than a direct cast, feel free to use Convert.ToInt32; it at least knows not to waste time doing needless conversions if it's already passed an Int32.

As a final thought, I'll leave you this completely failed code to think about:


DataSet ds = SqlHelper.ExecuteDataSet(sConn, CommandType.StoredProc, sProc, aParams);
// ... snip ...
DataRow row = ds.Tables[0].Rows[0];
bool someBool = (row["bitColumn"].ToString() == "1");


(Hint: The reason this code didn't work like the developer expected is because the DataRow already returned a Boolean.)

Tuesday, January 23, 2007

When iterating attributes, make sure you get the right thing

I'll admit I kind of dubbed out on this one. When you're trying to get the custom attributes off of an object, make sure you're getting the right object. In my case, I needed to get the Attributes of my assembly, not the Assembly class itself.

It should come as no surprise that the following code doesn't do what I expected:



object[] attribs = Assembly.GetExecutingAssembly().GetType().GetCustomAttributes(typeof(Attribute), false);



Instead I just needed to remove the call to GetType():



object[] attribs = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(Attribute), false);



The Assembly class provides a handy GetCustomAttributes() method since calling it on the Assembly type itself doesn't really help you much. The first example gets you a bunch of crazy attributes that really aren't what I was looking for; the second returns all of the assembly-decorating attributes declared in my AssemblyInfo.cs.

Saturday, January 20, 2007

Attempting to reach remote resources with C#

This is related to the project in my previous post. Now that we had the IVR working correctly, we needed a way to move the voice recordings from the IVR server to a web server where our customers can retrieve them. Last year I wrote a web service called ResourceProxy that accomplishes the task of moving files from one server to another very nicely and with very strict security restrictions (it requires authentication, it can restrict the paths and file types allowed, etc.). Unfortunately, the IVR is not a web server, it's a phone server. So installing IIS to allow web connectivity was not a solution we wanted to use.

So what we have are two file shares. One is read-only and allows me to pick up the recordings from the phone server, and a second is read/write and allows me to drop them off on the web server. I have to authenticate using two different domain accounts to access each share, and of course the web service that accomplishes this is running as the ASP.NET machine account.

So this creates a small problem. How does one authenticate a process running as the ASP.NET machine account to allow it to connect to remote servers? Looks like we need to get down and dirty with P/Invoke.

My first solution was elegant. I would use LogonUser to authenticate each account, impersonate the principal, and then connect to the file share UNCs. The code looks something like this:


[DllImport("advapi32.dll")]
private static int LogonUser(string lpszUsername, string lpszDomain,
string lpszPassword, int dwLogonType, int dwLogonProvider,
ref IntPtr phToken);

[DllImport("advapi32.dll")]
private static int DuplicateToken(IntPtr hExistingToken, int ImpersonationLevel,
ref IntPtr phNewToken);

public void ImpersonateUser(string domain, string user, string password)
{
IntPtr token = IntPtr.Zero;

int result = LogonUser(user, domain, password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token);

if (result != 0)
{
IntPtr newToken = IntPtr.Zero;
result = DuplicateToken(token,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
ref newToken);

if (result != 0)
{
WindowsIdentity id = new WindowsIdentity(newToken);
WindowsImpersonationContext context = id.Impersonate();
}
}
}


By the way, that's the incredibly pruned-down code. If you want a more extensive example, look at the WindowsImpersonationContext class.

This code worked beautifully. I could impersonate each of the domain accounts I needed to access the remote UNCs and copy my files over. Basically I just had to impersonate the first user, load the file into a MemoryStream, impersonate the other user, and write it to disk. (This was an acceptable approach because the files in question are always fairly small, typically less than 50k.)

It worked great on the development server, which is a Windows 2003 machine. Unfortunately, the production web server is still running Windows 2000. When we tried it there, everything broke. Argh!

For some reason, on Windows 2000 I was getting a bad username/password every time I tried to impersonate one of the domain principals, even though the exact same username/password combos worked fine on Windows 2003. I tried several different permutations of parameters to the LogonUser function to see if that would help. Eventually I ended up locating a Windows 2000 box on our development domain and tried my code there, just to see if it was an environment issue in production. It didn't work there either. Same code, same users and passwords; it just wouldn't work when moving from Windows 2003 to Windows 2000.

One thing that is mentioned on the page for LogonUser at MSDN is that on Windows 2000, the account executing LogonUser must have the SE_TCB_NAME privilege. You can grant this privilege to an account (such as the ASP.NET machine account in my case) by going in to the Local Security Policy for the server and adding the account to the "Act as a part of the operating system" policy. I tried this, and again it was no go.

So after fighting with that for a few hours, I decided to try a different approach. My boss mentioned that one technique he used in a classic ASP application to achieve the effect I wanted was to map the UNCs as network drives in the code before accessing them. This had some merit, so I investigated how to achieve this in C#.

The function we want this time is called WNetAddConnection2:


[DllImport("mpr.dll")]
private static int WNetAddConnection2(
ref NETRESOURCE lpNetResource,
string lpPassword, string lpUsername, int dwFlags);

public void MapNetworkDrive(string unc, string drive, string user, string password)
{
NETRESOURCE pRes = new NETRESOURCE();
int result = WNetAddConnection2(ref pRes, password, user, 0);
}


On the surface, this code looks a lot simpler, but I actually wrote a lot of peripheral code to make sure I wasn't trying to connect to the same resource twice and also to make sure I didn't map over an existing logical drive on the server. It doesn't hurt anything to map the resource twice, because WNetAddConnection2 will tell you what happened, but WNetAddConnection2 itself can be a somewhat costly operation, so I figured it better to avoid calling it if I can.

This code worked beautifully as well. After I had mapped the network shares I wanted, I could use the typical .NET IO classes to access the UNC directly without a problem.

Of course, after running it on Windows 2000, it broke again. Argh and double argh!

For some reason, on Windows 2000 this code kept throwing back an ERROR_WRONG_TARGET_NAME. According to the documentation I could find, this meant that the UNCs were not correct (target name referring the server destination). So why would the UNCs be correct on Windows 2003 but not on Windows 2000?

Who knows? At this point I was so frustrated with it I was willing to try anything. What I found is that if I specified the IP address (\\192.168.1.1\share as opposed to \\servername\share), Windows 2000 was perfectly happy with it. And now the code works fine in production, so I'm not going to play with it anymore.