Friday, February 4, 2011

Success with Reflector.NET

At my day job, we had a custom application in the field, a one-off, that a long-time customer had recently requested an important change for. The trouble was that we didn't seem to have source for it, and no one could remember who in our company had written it. All we knew was roughly when it was written (a few years ago) and that it was probably written with C#.NET.

So I ran RedGate's .NET Reflector on the hope of maybe getting some usable source code. I'd poked around inside assemblies before, but I'd never disassembled an executable to generate code. My hopes were exceeded - I have a very usable C# project now, with meaningful method names and so on! Here's how I did it (I'm sure this story has been countless times, but I enjoyed doing it, so I'm presenting my experience.)

I launched .NET Reflector and it asked me to pick a .NET framework to load default assemblies from, but I just clicked Cancel. File | Open ..., browse to my application, click open. After briefly poking around inside my app, I clicked my .exe in the tree and selected Tools | Disassemble, making sure that C# was selected in the language combo box on the toolbar. Then Tools | Export. Reflector wrote out a couple .cs files (Global.cs and AssemblyInfo.cs) and then prompted me for the location of an assembly that it could not locate. Fortunately, the field engineer had included that assembly with when he gave me the executable to experiment on. So I browsed to the assembly and clicked OK.

(Side note: I'm actually writing about this as I repeat the process at home, for a second time, from scratch. I hadn't taken sufficient notes at work to be able to write this post. The first time I did this, I skipped the step telling Reflector about the assembly. What that cost me was that while this second time reflector knew about the enums that were defined in the DLL, it didn't the first time. I had to find them by inspecting the method prototypes, using DevStudio's object browser, reading error messages in the code, and knowing generally what the app was supposed to do. It wasn't a lot of extra work, and I enjoyed my sleuthing but I'm glad to know that Reflector can do it for me.)

Reflector went on to write a Form1.cs, two .cs files that I recognized as containing functions common to many of my company's products, a .resources file, and a .csproj file. Things were starting to look good.

The .csproj opened fine in Visual Studio 2003. The first thing I noticed was that even though I had told Reflector where the referenced assembly was, the reference to it in the project was wrong, so I easily fixed that. Next I opened Form1.cs and was welcomed with the familiar WinForm layout of the app I had started with. Crossing my fingers, I started the solution building. 74 build errors, but many of those were duplicates. This might not be too bad.

There were ten or more errors of the form:

'System.Environment.CurrentDirectory.get': cannot explicitly call operator or accessor

on lines like this:

string str = Environment.get_CurrentDirectory();

That's not the model that .NET uses for getters! Changing the line to:

string str = Environment.CurrentDirectory;

got it to compile without even a warning. There were also setter lines I had to fix, like this:

Environment.set_CurrentDirectory(WorkingDirectory);

which became:

Environment.CurrentDirectory = WorkingDirectory;

And then there were the lines that looked to be assigning delegates:

objFMon.add_ReceiveCodes2(new __CCodeMonitor_ReceiveCodes2EventHandler(this.objFMon_ReceiveCodes2));

which I fixed in this way:

objFMon.ReceiveCodes2 += (new __CCodeMonitor_ReceiveCodes2EventHandler(objFMon_ReceiveCodes2));

Another example of this type of error was was:

FAULT_CODE fault_code = this.m_aryFaultCodes.get_Item(i);

which became:

FAULT_CODE fault_code = (FAULT_CODE) m_aryFaultCodes[i];


There were about 25 errors of the form:

Method 'MyApplication.Form1.Form1_Load(object, System.EventArgs)' referenced without parentheses

that referred to a line like:

btnAck.Click += new EventHandler(this, this.btnAck_Click);

I changed that one to:

btnAck.Click += new EventHandler(btnAck_Click);

I'm not sure what inspired me to think that the signiature was wrong except, perhaps, the vague feeling that the handler already knows that I'm giving it a method on 'this'.

Including all the delegates, there were about a dozen that I fixed by changing them from the function style to the property style.

Interestingly, Reflector didn't do a great job getting the number type right on a value given to a switch statement. I got this error:

A value of an integral type expected

on the first line of this switch statement:

switch (num2)
{
case -1f:
base.WindowState = FormWindowState.Minimized;
goto Label_0474;

case 0f:
base.WindowState = FormWindowState.Normal;
goto Label_0474;
}


because num2 was a float. Notice that Reflector added "f" after the integer values in the case lines. Searching upward in the code, there didn't seem to be any reason for num2 to need to be a float, so I changed it to an int and got rid of the "f"s. I had to do this for a second switch statement as well.

It wasn't hard to figure out that I had to cast several returns from ResourceManager.GetObject as Image or Icon as in this case:

base.Icon = (Icon) manager.GetObject("$this.Icon");

The following line called a function familiar to me, with my company's SpecialRead function. I knew that returning zero was a success code, so I was confident that I could replace null with 0.

if (SpecialRead(specialHandle, ref retValue, ref this.retLength, this.lenBufferInt16) == null)

There were 10 or 11 of those that I fixed.


This error had me stumped briefly:

Resources 'MyApplication.Form1.resources' and 'MyApplication\Form1.resx' have the same manifest resource name

'MyApplication.Form1.resources'.

But after a brief trip out to google, I decided to try deleting the .resources file. That seemed to do the trick. So with the compile and link errors gone, I cautiously pressed F5. I was disappointed when I got:

An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in MyApplication.exe
Additional information: COM object with CLSID {XXXXXXXX-YYYY-ZZZZ-MMMM-NNNNNNNNNNNN} is either not valid or not registered.


I was missing a COM object that needed to be registered.

So I took what I had built to the original engineer. His laptop was fully configured to run the product. Sure enough, my app ran successfully on his laptop. He sent it off to the customer to see that it runs the same in the field as the original. If it does, then I'll make the original requested change.


UPDATE: After we shipped the executable I built, another engineer found the original source code, in a remote, nearly neglected corner of our version control system. It's possible that the code in version control is not as current as the (now lost) code that was used to build the original exe I was given. So my next task will be to compare the two sets of source and see if I can determine if one is newer that the other

2 comments:

  1. Nice article and easy guide that is really very helpful. Specially the issue and then "which became:/I changed that one to:/Changing the line to:/which I fixed in this way:" etc.
    I am also getting one error with Reflector.NET. when i debug the code it stop at
    base.Icon = (Icon) manager.GetObject("$this.Icon");
    The details shows that "{"Could not find any resources appropriate for the specified culture or the neutral culture. Make sure \"xyz.resources\" was correctly embedded or linked into assembly \"solutionClient\" at compile time, or that all the satellite assemblies required are loadable and fully signed."}"
    I added the resource icon with name icon.rc and Icon.ico to the solution but still getting the error. With project properties under application i tried to setup the icon and manifest to the icon.ico instead of (default icon) but not worked. Anything that i missed?

    ReplyDelete
  2. That was over a year ago, so I'm really stretching my memory here. I can't think of anything obvious you missed.

    1. Do you have the right sized icon (16x16, 32x32, 48x48)?

    2. Alternately, could you force it to use an icon from an external file, by replacing the line with something like the following?
    base.Icon = new Icon(@"C:\sample.ico");

    ReplyDelete