Christophe's profileChristophe Nasarre - "Su...PhotosBlogLists Tools Help

Christophe Nasarre - "Support Aérien demandé"

Developer's annotations
March 29

View the Exception hierarchy in .NET

In his post “Common Exception Types”, Brad Abrams shows the hierarchy of exceptions in .NET. It is not too difficult to get this hierarchy as you can see by yourself.

 

With Assembly.LoadFrom(), I collect the assemblies from the CLR folder based on the version of mscorlib.dll loaded inside the current AppDomain. For each assembly, all public types are listed using Assembly.GetExportedTypes() and if it derives from Exception, it is added into the hierarchy.

 

The output is simplistic with the sorting of children based on the type name with the namespace by implementing ISortable (see TypeNode.cs for implementation details). The assembly where the exception is defined is also printed if you add -a to the command line; could be useful if you need to add it as a reference to your project in Visual Studio.

 

As a final note, the helper classes are built in a way that you could very easily get the hierarchy of any base class (see TypeHierarchy/ExceptionHierarchy for details). I find it useful for the GUI classes related to Control for example.

 

The next step should be to integrate this knowledge within Visual Studio to offer a list of meaningful exception when you type “throw new”. This is not the case in VS.NET 2005 Beta 1; maybe in Beta 2. This could be a good idea for writing an AddIn…

March 25

Obsolete types and members in .NET - part3

Even though Reflection makes it easy to retrieve information about obsolete types and members, the initial step to get the assembly you really mean is not obvious. My tool is supposed to work only on files and does not dig into the GAC.

 

I understand that it could be interesting to plan the required changes for the next version of the .NET framework. So, you simply go into the subfolder of <C:\WINDOWS>\Microsoft.NET\Framework corresponding to the version you are interested in and call obso on the right assembly file (don’t forget the file extension).

 

For a complete view, no wildcard is needed since the Command Shell does it for you: for %1 in (system*.dll) do obso %1 -s.

 

 

In fact, the .NET Framework sometimes makes it harder to load the assembly you want. Without digging to much into the details about assemblies into the GAC or not, it is clear that if an assembly with the same name as the one you try to load using Assembly.LoadXXX() methods is already loaded into an AppDomain, you’re doomed. Why? Because these methods will return an Assembly but which might not describe the one you have requested, rather the one already loaded.

 

The first consequence is the fact that you will always see the mscorlib.dll corresponding to the version used to build it. For example, if you try to load mscorlib.dll from the v1.1.4322 subfolder with an application written with Whidbey, you’ll get the one from v2. More surprising the first time you try it, a 1.1 application trying to load mscorlib.dll from v2.0.40607 (version numbers for Beta 1) folder will succeed! But you’ll get mscorlib from 1.1. This is due to the fact that before loading any assembly, a probing step is done by the runtime to find the “right” assembly (see blog references for more details). And “right” for the runtime does not always mean right for your needs.

 

The “mscorlib” case is solved for me because I’m cheating: I always start the parser built against the same version of the runtime as the one of the target assembly. For the other assemblies, a choice has to be made between Assembly.LoadFrom() and Assembly.LoadFile(). I preferred the name of the latter but there is one tiny limitation you need to be aware of: the path given as parameter must be an “absolute path” or an ArgumentException is thrown with “Absolute path information is required.” as message.

 

Finally, talking about obsolete members, note that Assembly.LoadWithPartialName() is marked with this attribute: another signal for us that Microsoft is pushing the “strongly named assemblies for all” in a post Whidbey timeframe…

 

 

References

Assembly.LoadFrom/LoadFile/Load(byte[]) prefers GAC

Junfeng Zhang blog

Mscorlib.dll

Suzanne Cook

 

March 17

Obsolete types and members in .NET - part2

 

To get the list of obsolete types and members of an assembly, I explained in the previous post that it is better to know which version of the CLR is expected by an assembly and then start the parser application of the same version. The different versions of the parsers binaries are embedded inside the launcher assembly as resource. Nothing complicated here: simply add the two executables to the project, right-click on each to set the Build Action property to “Embedded Resource”.

 

At runtime, when the expected version of the CLR in the assembly to dissect is known, it is time to extract the right binary resource and copy it as the parser executable to start.

 

    1     private static bool ExtractFile(string resourceName, string filename)
    2     {
    3         Assembly runningAssembly = Assembly.GetExecutingAssembly();
    4         using (
    5             Stream stream =
    6                 runningAssembly.GetManifestResourceStream(resourceName)
    7                 )
    8         {
    9             // copy it in the directory of the application
   10             AssemblyName application =
   11                 Assembly.GetExecutingAssembly().GetName();
   12             string executableFilename =
   13                 application.CodeBase.Substring(
   14                     0,
   15                     application.CodeBase.LastIndexOf(
   16                             string.Format(
   17                             @"/{0}",
   18                             application.Name
   19                             )
   20                         )
   21                     );
   22             executableFilename =
   23                 executableFilename.Substring(
   24                     application.CodeBase.IndexOf("///")+3
   25                     );
   26             executableFilename =
   27                 Path.Combine(
   28                     executableFilename,
   29                     filename
   30                     );
   31 
   32             return(CopyStreamToDisk(stream, executableFilename));
   33         }
   34     }

 

Given the name of the resource, GetManifestResourceStream() returns a stream to read its content. And the CopyStreamToDisk() helper transfers it to a file in the same directory.

 

    1     private static bool CopyStreamToDisk(Stream reader, string filename)
    2     {
    3         const int BLOCK_SIZE = 4096;
    4         using (
    5             FileStream writer =
    6                 new FileStream(
    7                     filename,
    8                     FileMode.Create,
    9                     FileAccess.Write,
   10                     FileShare.None,
   11                     BLOCK_SIZE,
   12                     false
   13                 )
   14             )
   15         {
   16             try
   17             {
   18                 int size = Convert.ToInt32(reader.Length);
   19                 byte[] block = new byte[size];
   20 
   21                 // read the whole file
   22                 reader.Read(block, 0, size);
   23 
   24                 // write it as a single blob
   25                 writer.Write(block, 0, size);
   26 
   27                 return(true);
   28             }
   29             catch(IOException x)
   30             {
   31                 Debug.WriteLine(
   32                     string.Format(
   33                         "Impossible to copy stream to {0}\r\n {1}",
   34                         filename,
   35                         x.Message
   36                         )
   37                     );
   38                 return(false);
   39             }
   40         }
   41     }

 

It is not time to run the parser application with the parameters received by the launcher. One tiny detail needs to be fixed: how to get the output of the parser in the open console? I have already explained how to do this in C++ for one of my article. Here is the corresponding managed way to do so. First, keep track of the process into a member variable:

 

    private static Process _parserProcess = null;

 

Then, start the process with the same command line:

 

    1     // run the dumper and redirect output to the same console
    2     _parserProcess = new Process();
    3     _parserProcess.StartInfo.Arguments = commandLine.ToString();
    4     _parserProcess.StartInfo.FileName = appName;
    5     _parserProcess.StartInfo.CreateNoWindow  = true;
    6     _parserProcess.StartInfo.UseShellExecute = false;
    7     _parserProcess.StartInfo.RedirectStandardOutput = true;
    8     _parserProcess.StartInfo.RedirectStandardError = true;
    9     _parserProcess.Start();
   10 
   11     // start the threads listing to the std out/error pipes
   12     Thread readOutputThread = new Thread(new ThreadStart(ReadStdOut));
   13     readOutputThread.Start();
   14     Thread readErrorThread = new Thread(new ThreadStart(ReadStdError));
   15     readErrorThread.Start();
   16 
   17     _parserProcess.WaitForExit();
   18 
   19     // the threads might still be running
   20     while(!_bEndOfProcess)
   21     {
   22         Thread.Sleep(100);
   23     }

 

Finally, the two threads are redirecting the errors and standard output to the current console:

 

    1     private static void ReadStdOut()
    2     {
    3         try
    4         {
    5             string line;
    6             while ((line = _parserProcess.StandardOutput.ReadLine()) != null)
    7             {
    8                 Console.WriteLine(line);
    9             }
   10         }
   11         catch
   12         {
   13             Debug.WriteLine("----exception in stdout----");
   14         }
   15 
   16         _bEndOfProcess = true;
   17     }
   18 
   19     private static void ReadStdError()
   20     {
   21         try
   22         {
   23             string line;
   24             while ((line = _parserProcess.StandardError.ReadLine()) != null)
   25             {
   26                 Console.WriteLine(line);
   27             }
   28         }
   29         catch
   30         {
   31             Debug.WriteLine("----exception in stderror----");
   32         }
   33     }

 

Some tedious details about obtaining the obsolete types and members will be explored in the next post.

 

 

 

References

Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities,Part 2

Christophe Nasarre

Q190351 "HOWTO: Spawn Console Processes with Redirected Standard Handles"

MSDN

 

 

March 16

Obsolete types and members in .NET - part1

 

During the 11h49 long flight to reach San Francisco from Paris Charles De Gaulle airport, I finally have the time to finish writing the tiny tool I was thinking about for a couple of weeks. The idea is simple: I’m working with Whidbey Beta 1 and I need to know what type and methods will become obsolete.

 

The .NET Framework provides the ObsoleteAttribute to tag any type or member and state if this should be an error to use them and an explanation or hint about a possible workaround. As usual, this is not the part of the code which is responsible for retrieving the information out from the attribute that is complicated. Here is the code for a type:

 

    1  ObsoleteAttribute[] attributes =
    2      Type.GetCustomAttributes(typeof(ObsoleteAttribute), false)
    3      as ObsoleteAttribute[];

 

Call GetMembers() with the right BindingFlags to get the members of a Type and it is then almost the same to get obsolete information for a MemberInfo:

 

    1  ObsoleteAttribute[] attributes =
    2      member.GetCustomAttributes(typeof(ObsoleteAttribute), false)
    3      as ObsoleteAttribute[];

 

In both cases, if attributes.Length is equal to 1, you simply look at the two properties of attributes[0]:

  • IsError is true when the compiler will refuse to even call this property in your code
  • Message provides a string explaining why this type/member is deprecated and sometimes gives a workaround.

 

 

But before reaching the right assembly must be loaded to extract the types from. And this is where it hurts. Not 100% of the times but the small percentage that is mandatory to cover. That small percentage is mscorlib.dll.

 

How to load the mscorlib.dll of a different version of the CLR the application is compiled against or even simply the last installed on the machine? It seems that it is simply not possible. So I would need to build as many versions of my tool as many versions of the CLR I want to be able to scan. In a previous post, I had to compile the same code twice to get two executables; one for Everett (CLR 1.1) and one for Whidbey (CLR 2.0). This time, I decided to find a way to only have one single application.

 

This statement seems to be incompatible with the fact that only one version of mscorlib.dll can be loaded at a time. Well… I should rather say, “I only want to deploy one single application”. Let’s compile and build one version for Everett and one for Whidbey. Then, embed these two executables as binary resources into a third application which acts as a “launcher”. This application is built against the smallest version of the CLR you want to support (the 1.1 version in my case). I’ll explain how to do that in a forthcoming post.

 

Let’s suppose I have these two versions as resource embedded into a launcher; how do I know which one to pick and start? I was convinced that it should be somewhere in the PE file itself. Not a bad guess but more complicated as I thought. Starting from the source I have already written in C++, itself based on Matt Pietrek code written in 1997 and updated with the Platform SDK winnt.h header file, I was ready to start.

 

The first IMAGE_OPTIONAL_HEADER header

    1 [StructLayout(LayoutKind.Sequential)]
    2 struct IMAGE_OPTIONAL_HEADER
    3 {
    4     //
    5     // Standard fields.
    6     //
    7     public ushort Magic;
    8     public byte MajorLinkerVersion;
    9     public byte MinorLinkerVersion;
   10     public uint SizeOfCode;
   11     public uint SizeOfInitializedData;
   12     public uint SizeOfUninitializedData;
   13     public uint AddressOfEntryPoint;
   14     public uint BaseOfCode;
   15     public uint BaseOfData;
   16 
   17     //
   18     // NT additional fields.
   19     //
   20     public uint ImageBase;
   21     public uint SectionAlignment;
   22     public uint FileAlignment;
   23     public ushort MajorOperatingSystemVersion;
   24     public ushort MinorOperatingSystemVersion;
   25     public ushort MajorImageVersion;
   26     public ushort MinorImageVersion;
   27     public ushort MajorSubsystemVersion;
   28     public ushort MinorSubsystemVersion;
   29     public uint Win32VersionValue;
   30     public uint SizeOfImage;
   31     public uint SizeOfHeaders;
   32     public uint CheckSum;
   33     public ushort Subsystem;
   34     public ushort DllCharacteristics;
   35     public uint SizeOfStackReserve;
   36     public uint SizeOfStackCommit;
   37     public uint SizeOfHeapReserve;
   38     public uint SizeOfHeapCommit;
   39     public uint LoaderFlags;
   40     public uint NumberOfRvaAndSizes;
   41// IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] DataDirectory;
   42// read one directory after the other
   43 };

contains a lot of version information but the only one which is different between Everett and Whidbey is the major linker versions: 6 for the former and 8 for the latter. But there must be a more “.NET” based way to make the difference!

 

Let’s jump to the IMAGE_COR20_HEADER

    1 [StructLayout(LayoutKind.Sequential)]
    2 struct IMAGE_COR20_HEADER
    3 {
    4     // Header versioning
    5     public uint                cb;             
    6     public ushort               MajorRuntimeVersion;
    7     public ushort               MinorRuntimeVersion;
    8 
    9     // Symbol table and startup information
   10     public IMAGE_DATA_DIRECTORY MetaData;       
   11     public uint                Flags;          
   12     public uint                EntryPointToken;
   13 
   14     // Binding information
   15     public IMAGE_DATA_DIRECTORY Resources;
   16     public IMAGE_DATA_DIRECTORY StrongNameSignature;
   17 
   18     // Regular fixup and binding information
   19     public IMAGE_DATA_DIRECTORY CodeManagerTable;
   20     public IMAGE_DATA_DIRECTORY VTableFixups;
   21     public IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
   22 
   23     // Precompiled image info (internal use only - set to zero)
   24     public IMAGE_DATA_DIRECTORY ManagedNativeHeader;

which contains details related to .NET only. Unfortunately, the Major/Minor runtime version information stays the same…

 

No problem: find the .NET Metadata documentation provided by Visual Studio and decipher it. After the IMAGE_COR20_HEADER, there are major and minor fields but always with the same 1.1 value…

 

Just before I gave up, I started to read Steven Pratschner’s book in the flight back from Redmond last week. In addition to providing a great deal of details about how the CLR runtime starts, he mentions that a code sample is available to figure out which version of the .NET Framework was expected for a particular assembly. A few seconds to download it and the information is there: just after the documented COR header... A string with the expected version: “v1.1.4322” for Everett and “v2.0.40607” for Whidbey Beta 1 (read PEHeader.cs in the obso project). Thanks Steven

 

 

 

References

Customizing the Microsoft .NET Framework Common Language Runtime book By Steven Pratschner

Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities

Christophe Nasarre

An In-Depth Look into the Win32 Portable Executable File Format

Matt Pietrek

“Partition II Metadata.doc” from Program Files\Microsoft Visual Studio ...\SDK\v…\Tool Developers Guide\docs for metadata layout in PE files

www.pinvoke.net web site for managed signatures of most structures and unamanged API.

 

 

March 15

Syntax coloring

There was a change in my previous post: syntax coloring of the code.

I finally found a reliable tool to cut code from VS.NET and paste it into MSN Spaces.

Very very handy  

 

Christophe Nasarre

Egypte  
Photo 1 of 11