Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Resource Hacker in C# .NET

0.00/5 (No votes)
6 Mar 2012 1  
How to get into a .NET DLL file and extract its types, show their information, extract the members of each type, and even invoke them.

Introduction

In this article I’m going to get into a .NET DLL file and extract its types, show their information, extract the members of each type, and even invoke them!!! Yes, a useful and complete Resource Hacker. All of these are done by making use of the System.Reflection namespace. To better understand this application, I suggest that you get yourself familiarized with the concept of Reflection and the System.Reflection namespace itself. The Microsoft MSDN would be a nice resource. There is also a complete Windows Forms application for the sake of understanding this better. Enough with all this and let’s get our hands dirty.

Please note that these objects are declared globally within the main form as we will be using them a lot:

string fileName = string.Empty;
Assembly assem;
Type selectedType=typeof(Int32);
object selectedTypeInstance=null;

Letting the user select a DLL file

We have got to let the user select the DLL file they want to, so I have written the following lines of code:

private void selectDllToolStripMenuItem_Click(object sender, EventArgs e)
{
    OpenFileDialog dialog = new OpenFileDialog();
    dialog.Filter = ".Net Dlls|*.dll";
    if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        fileName = dialog.FileName;
        PopulateListBoxes();
    }
}

Getting the members of the selected DLL file

The last line of code, there is a call to a method named PopulateListBoxes which populates the listboxes we have on the form. Here it is:

private void PopulateListBoxes()
{
    Reset();
    assem = Assembly.LoadFile(fileName);
    if (assem.GetTypes().Count() > 0)
    {
        btnGetInfo.Enabled = true;
    }
    else
    {
        btnGetInfo.Enabled = false ;
    }

    foreach (Type oType in assem.GetTypes())
    {
        cmbType.Items.Add(oType);
        MemberInfo[] members = oType.GetMembers();
        foreach (MemberInfo member in members)
        {
            switch (member.MemberType)
            {
                case MemberTypes.Constructor:
                    lstConstructor.Items.Add(member.Name);
                    break;
 
                case MemberTypes.Event:
                    lstEvents.Items.Add(member.Name);
                    break;
 
                case MemberTypes.Method:
                    lstMethods.Items.Add(member.Name);
                    break;
 
                case MemberTypes.Property:
                    lstProperties.Items.Add(member.Name);
                    break;
 
                default:
                    lstOthers.Items.Add(member.Name);
                    break;
            }
        }
    }
    cmbType.SelectedIndex = 0;
}

These ListBoxes contain the members of each type within the selected DLL file. One ListBox for methods, one for properties, one for events, one for constructors, and finally one for everything else. There is also a dropdownlist which has the types within the selected DLL file in case there is more than one type in it. The Reset method is nothing special. It just clears the listboxes and the dropdownlist, it actually resets the application interface to its initial state.

In this PopulateListBoxes method, I have loaded the selected DLL file into an Assembly object using the LoadFile method which receives a file name as the argument: that is the file name that the Open File dialog returns. Then I have iterated through the types within the Assembly object and added them to the dropdownlist (there may be more than a type within a single assembly), and for each type, I have collected information about its members by using an array of MemberInfo. Then, I have added each item within that array to the appropriate ListBox.

In the SelectedIndexChanged event of the dropdownlist, I have written the following code which I think doesn’t require any explanation as it’s almost the same as the code for the PopulateListBoxes method. The reason behind this code is to update the listboxes when the user selects a different type within the assembly. (All of the types within the assembly were added to the cmbType dropdownlist already.)

lstConstructor.Items.Clear ();
lstEvents.Items.Clear ();
lstMethods.Items.Clear ();
lstProperties.Items.Clear ();
Type selectedType = assem.GetType ( cmbType.Text );
MemberInfo[] members = selectedType.GetMembers ();

foreach ( MemberInfo member in members )
{
    switch ( member.MemberType )
    {
        case MemberTypes.Constructor:
            lstConstructor.Items.Add ( member.Name );
            break;
 
        case MemberTypes.Event:
            lstEvents.Items.Add ( member.Name );
            break;
 
        case MemberTypes.Method:
            lstMethods.Items.Add ( member.Name );
            break;
 
        case MemberTypes.Property:
            lstProperties.Items.Add ( member.Name );
            break;
 
        default:
            lstOthers.Items.Add ( member.Name );
            break;
        }
}

Creating an object of the selected type

The code for creating a new object of the selected type is as follows. This method actually makes a call to the selected constructor within the lstConstructors listbox. If the selected constructor is a parameterless one, it simply creates an object. However, if the selected constructor requires any arguments, it prompts the user to enter the arguments one by one:

selectedType = assem.GetType ( cmbType.Text );
List<Type> constructorArgs =new List<Type>();
List<object> passingArgs = new List<object> ();
            
ConstructorInfo[] ctors = selectedType.GetConstructors ();
 
foreach ( ParameterInfo param in ctors[lstConstructor.SelectedIndex].GetParameters () )
{
    constructorArgs.Add( param.ParameterType);
}

ConstructorInfo ctor = selectedType.GetConstructor ( constructorArgs.ToArray<Type>() );
 
if ( ctor.GetParameters ().Count () == 0 )
{
    selectedTypeInstance = ctor.Invoke ( null );
}
else
{
    InputBox inputBox;
    foreach ( ParameterInfo item in ctor.GetParameters () )
    {
        inputBox = new InputBox ();
        inputBox.Prompt.Text = 
        string.Format ( "Enter a value for ' {0} ' in {1}", 
                        item.Name, item.ParameterType );
        inputBox.ShowDialog ();
        switch (item.ParameterType.Name)
        {
            case "Int32":
                passingArgs.Add(Convert.ToInt32(inputBox.SelectedValue.Text));
                break;
 
            case "String":
                passingArgs.Add(Convert.ToString(inputBox.SelectedValue.Text));
                break;
 
            case "Double":
                passingArgs.Add(Convert.ToDouble(inputBox.SelectedValue.Text));
                break;
 
            case "Char":
                passingArgs.Add(Convert.ToChar(inputBox.SelectedValue.Text));
                break;
 
             default:
                 passingArgs.Add((object)inputBox.SelectedValue.Text);
                 break;
         }
     }
     selectedTypeInstance = ctor.Invoke ( passingArgs.ToArray<object> () );
}

Here, inputBox is just a Windows Form I have created myself for getting the arguments required to invoke the method. I have made use of the ConstructorInfo class to know the number and type of each argument that the selected constructor requires. The Invoke method in the last line which actually invokes the constructor requires the arguments to be an array of objects. I have created a generic List of objects and in the final step, I have created it as an array of objects.

Invoking a method

The code for invoking a method of the selected type is very similar to invoking the constructor. Look at the following lines of code which makes a call to a selected method in the lstMethods listbox and returns the value if there is any.

object retVal = null;
List<object> passingArgs = new List<object>();
InputBox inputBox;
 
selectedType = assem.GetType ( cmbType.Text );
if ( selectedTypeInstance==null || selectedTypeInstance.GetType() != selectedType )
{
    MessageBox.Show("No object exists of this type. Create an object first.");
    return;
}
 
MethodInfo methodInfo = selectedType.GetMethod ( lstMethods.Text );
 
if ( selectedType.GetMethod ( lstMethods.Text ).GetParameters ().Count () == 0 )
{
    retVal = methodInfo.Invoke ( selectedTypeInstance, null );
}
else
{
    foreach ( ParameterInfo item in selectedType.GetMethod ( lstMethods.Text ).GetParameters () )
    {
        inputBox = new InputBox ();
        inputBox.Prompt.Text = string.Format ( 
           "Enter a value for ' {0} ' in {1}", item.Name, item.ParameterType );
        inputBox.ShowDialog ();
                    
        switch (item.ParameterType.Name  )
        {
            case "Int32":
                passingArgs.Add(Convert.ToInt32( inputBox.SelectedValue.Text));
                break;
 
            case  "String":
                passingArgs.Add(Convert.ToString( inputBox.SelectedValue.Text));
                break;
 
            case "Double":
                passingArgs.Add(Convert.ToDouble ( inputBox.SelectedValue.Text));
                break;
 
            case "Char":
                passingArgs.Add(Convert.ToChar ( inputBox.SelectedValue.Text));
                break;
 
             default:
                passingArgs.Add((object) inputBox.SelectedValue.Text);
                break;
         }
     }
     retVal = methodInfo.Invoke ( selectedTypeInstance, passingArgs.ToArray<object>() );
}

if ( retVal == null )
{
    lblInfo.Text=  "Return Value: Null" ;
}
else
{
   lblInfo.Text= ( string.Format ( "Return Value: {0}", retVal.ToString () ) );
}

Here I have created a MethodInfo object for getting the type of each argument that the method requires. If the method is parameterless, I simply call it and return the value, but if the method requires parameters, I have to know the type of each one, so I call the GetParameters() method of the selected method within the selected type and iterate within each one by making use of a ParameterInfo object (a little confused here!!!). In the iteration loop, I prompt the user to enter the value for each parameter one by one. And finally I call the Invoke method on the selected method and pass the gathered arguments and get the returned value.

Setting and getting the values of a property

For setting the values of properties and getting values back, we have two options. We can either call the setter and getter methods of each property that is listed in the lstMethods listbox or we can get and set the values directly using the properties themselves. I have used the latter approach. The following lines of code are for setting the values of a property selected in the lstProperties listbox:

selectedType = assem.GetType(cmbType.Text);
if (selectedTypeInstance == null || selectedTypeInstance.GetType() != selectedType)
{
    MessageBox.Show("No object exists of this type. Create an object first.");
    return;
}
 
if (! selectedType.GetProperty ( lstProperties.Text ).CanWrite  )
{
    lblInfo.Text= "This property is Read-Only." ;
    return;
}
InputBox inputBox = new InputBox ();
inputBox.Prompt.Text = string.Format ( "Enter a value for ' {0} ' in {1}", 
  lstProperties.Text, selectedType.GetProperty ( lstProperties.Text ).PropertyType );
inputBox.ShowDialog ();
 
Type type = selectedTypeInstance.GetType ();
PropertyInfo prop = type.GetProperty ( lstProperties.Text );
switch (prop.PropertyType.Name )
{
    case "Int32":
        prop.SetValue ( selectedTypeInstance,Convert.ToInt32( inputBox.SelectedValue.Text), null );
        break;
 
    case "String":
        prop.SetValue ( selectedTypeInstance,Convert.ToString( inputBox.SelectedValue.Text), null );
        break;
 
    case "Double":
        prop.SetValue ( selectedTypeInstance,Convert.ToDouble( inputBox.SelectedValue.Text), null );
        break;
 
    case "Char":
        prop.SetValue ( selectedTypeInstance,Convert.ToChar ( inputBox.SelectedValue.Text), null );
        break;
 
    default:
        prop.SetValue ( selectedTypeInstance,(object) (inputBox.SelectedValue.Text), null );
        break;
}

I have created a PropertyInfo object to get to the selected property in lstProperties and then depending on the type of the property, I set the gathered value using the SetValue method of the PropertyInfo class. Almost all of the codes we have in this application such as invoking a method and setting the values of the properties require an object to operate on. This object must be of the type of the class we are invoking a method from. The selectedTypeInstance object here is the object of the selected type that we do our operations on.

The following is for getting the value back from a property, which is much simpler:

if (selectedTypeInstance == null || selectedTypeInstance.GetType() != selectedType)
{
    MessageBox.Show("No object exists of this type. Create an object first.");
    return;
}
if ( !selectedType.GetProperty ( lstProperties.Text ).CanRead )
{
    lblInfo.Text= "This property is Write-Only." ;
    return;
}
Type type = selectedTypeInstance.GetType ();
PropertyInfo prop = type.GetProperty ( lstProperties.Text );
object propertyValue= prop.GetValue  ( selectedTypeInstance,null);
lblInfo.Text = string.Format ( "Property Value: {0}", propertyValue.ToString () );

In both getting and setting the values of a property, we have to make sure we have the authorization. I mean, if the property is read-only, we cannot set its value and if it’s write-only, we cannot get its value. This is checked by the CanWrite and CanRead properties of the PropertyInfo class, respectively.

Viewing the body of a method

I have added the following code for viewing the machine code of a selected method. After getting the machine code, we can convert it to the MSIL code and then to the original C# code of the method. Amazing, huh? I haven’t gone that far in this application, however.

StringBuilder sb = new StringBuilder ();
Type selectedType = assem.GetType ( cmbType.Text );
MethodInfo mi = selectedType.GetMethod ( lstMethods.Text  );
MethodBody mb = mi.GetMethodBody ();
sb.AppendLine (string.Format( "Method: {0}", mi.Name  ));
sb.AppendLine ( string.Format ( "Local variables are initialized: {0}", mb.InitLocals ) );
sb.AppendLine ( string.Format ( "Maximum number of items on the operand stack: {0}", 
                                mb.MaxStackSize ) );
foreach ( LocalVariableInfo lvi in mb.LocalVariables )
{
    sb.AppendLine(string.Format ( "Local variable: {0}", lvi ));
}
 
sb.AppendLine ();
sb.AppendLine ( "The Machine Code of the Method: " );
          
byte[] ilb = mb.GetILAsByteArray ();
for ( int i = 0 ; i < ilb.Length ; i++ )
    sb.AppendLine(string.Format  ( "{0:X} ", ilb[i] ));
 
lblInfo.Text = sb.ToString ();

Showing the information about each member

I wanted this application to be very informative. Therefore, I have put some lines of code in the SelectedIndexChanged of the listboxes that hold the members. When a member is selected in each listbox, some useful information about it will be shown to the user. I have put the following code in the SelectedIndexChanged of the lstConstructors listbox.

if (lstConstructor.SelectedIndex == -1)
{
    button1.Enabled= false;
}
else
{
    button1.Enabled = true;
}
StringBuilder sb = new StringBuilder ();
Type selectedType = assem.GetType ( cmbType.Text );
ConstructorInfo[] ctors = selectedType.GetConstructors ();
 
foreach ( ParameterInfo param in ctors[lstConstructor.SelectedIndex].GetParameters () )
{
    sb.AppendLine ( ( string.Format ( "Parameter {0} is named {1} and is of type {2}", 
                      param.Position, param.Name, param.ParameterType ) ) );
}
if ( sb.ToString () == string.Empty )
{
    lblInfo.Text = "This is a default constructor and doesn't have any parameters.";
}
else
{
    lblInfo.Text = sb.ToString ();
}

For getting the information about the selected method in the lstMethods listbox, I have put the following lines of code in the SelectedIndexChanged event handler of the lstMethods listbox:

if (lstMethods.SelectedIndex == -1)
{
    button6.Enabled = button2.Enabled = false;
}
else
{
    button6.Enabled = button2.Enabled = true;
}
Type selectedType = assem.GetType(cmbType.Text);
MethodInfo mi = selectedType.GetMethod(lstMethods.Text);
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Name: {0}", mi.Name));
sb.AppendLine(string.Format("Is Public: {0}", mi.IsPublic));
sb.AppendLine(string.Format("Is Private: {0}", mi.IsPrivate));
sb.AppendLine(string.Format("Is Static: {0}", mi.IsStatic));
sb.AppendLine(string.Format("Is Virtual: {0}", mi.IsVirtual));
sb.AppendLine(string.Format("Return Parameter: {0}", mi.ReturnParameter));
sb.AppendLine(string.Format("Return Type: {0}", mi.ReturnType));
sb.AppendLine(string.Format("Parameters:"));
foreach (ParameterInfo item in mi.GetParameters())
{
    sb.AppendLine(string.Format("Name: {0}", item.Name));
    sb.AppendLine(string.Format("Parameter Type: {0}", item.ParameterType));
    sb.AppendLine(string.Format("Is In: {0}", item.IsIn));
    sb.AppendLine(string.Format("Is Out: {0}", item.IsOut));
    sb.AppendLine(string.Format("Is Optional: {0}", item.IsOptional));
    sb.AppendLine(string.Format("Is Retval: {0}", item.IsRetval));
    sb.AppendLine(string.Format("Default Value: {0}", item.DefaultValue));
    sb.AppendLine();
}

lblInfo.Text = sb.ToString();

For getting to the information about the selected property in the lstProperties listbox:

if (lstProperties.SelectedIndex==-1)
{
    button4.Enabled = button5.Enabled = false;
}
else
{
    button4.Enabled = button5.Enabled = true;
}
Type selectedType = assem.GetType(cmbType.Text);
PropertyInfo pi = selectedType.GetProperty(lstProperties.Text);
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Name: {0}", pi.Name));
sb.AppendLine(string.Format("DeclaringType: {0}", pi.DeclaringType));
sb.AppendLine(string.Format("Can Read: {0}", pi.CanRead));
sb.AppendLine(string.Format("Can Write: {0}", pi.CanWrite));
sb.AppendLine(string.Format("PropertyType: {0}", pi.PropertyType));
lblInfo.Text = sb.ToString();

In the SelectedIndexChanged event handler of the lstEvents listbox, we have:

Type selectedType = assem.GetType(cmbType.Text);
EventInfo  ei = selectedType.GetEvent(lstEvents.Text);
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Name: {0}", ei.Name));
sb.AppendLine(string.Format("Is Multicast: {0}", ei.IsMulticast));
sb.AppendLine(string.Format("Is SpecialName: {0}", ei.IsSpecialName));
sb.AppendLine(string.Format("Raise Method: {0}",ei.GetRaiseMethod(true).Name ));
sb.AppendLine(string.Format("Remove Method: {0}", ei.GetRemoveMethod().Name  ));
 
lblInfo.Text = sb.ToString();

And finally, to get some information about the selected type itself, I have the following lines of code:

Type selectedType = assem.GetType(cmbType.Text);
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("AssemblyQualifiedName: {0}", selectedType.AssemblyQualifiedName));
sb.AppendLine(string.Format("Namespace: {0}", selectedType.Namespace));
sb.AppendLine(string.Format("Is IsClass: {0}", selectedType.IsClass));
sb.AppendLine(string.Format("BaseType: {0}", selectedType.BaseType));
sb.AppendLine(string.Format("Is ValueType: {0}", selectedType.IsValueType));
sb.AppendLine(string.Format("Is IsSerializable: {0}", selectedType.IsSerializable));
sb.AppendLine(string.Format("Is IsSealed: {0}", selectedType.IsSealed));
sb.AppendLine(string.Format("Is IsPublic: {0}", selectedType.IsPublic));
sb.AppendLine(string.Format("Is IsNested: {0}", selectedType.IsNested));
sb.AppendLine(string.Format("Is IsInterface: {0}", selectedType.IsInterface));
sb.AppendLine(string.Format("Is IsGenericType: {0}", selectedType.IsGenericType));
sb.AppendLine(string.Format("Is IsAbstract: {0}", selectedType.IsAbstract));
lblInfo.Text=sb.ToString();

And that’s all. Hope you find it useful and practical. Till my next article, have a good time programming.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here