© 2004 Microsoft Corporation. All rights reserved.

Figure 1 MetaInfoOutput.txt
TypeRef #1 (01000001)
———————————————————————————
Token:             0x01000001
ResolutionScope:   0x23000001
TypeRefName:       System.Windows.Forms.Form
    MemberRef #1
    ———————————————————————————
        Member: (0a000002) .ctor: 
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: Void
        No arguments.
    MemberRef #2
    ———————————————————————————
        Member: (0a000005) Dispose: 
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: Void
        No arguments.

TypeRef #2 (01000002)
———————————————————————————
Token:             0x01000002
ResolutionScope:   0x23000001
TypeRefName:       System.Windows.Forms.TreeNode
    MemberRef #1
    ———————————————————————————
        Member: (0a00002f) get_Nodes: 
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: Class System.Windows.Forms.TreeNodeCollection
        No arguments.
    MemberRef #2
    ———————————————————————————
        Member: (0a000050) get_Text: 
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: String
        No arguments.
    MemberRef #3
    ———————————————————————————
        Member: (0a000056) .ctor: 
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: Void
        No arguments.
Figure 3 MetaDataHelper.cpp
// This is the main DLL file.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "MetaDataImportWrapper.h"
#define ARRAYSIZE( x )  (sizeof(x) / sizeof(x[0]))

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

namespace Wheaty
{

namespace UnmanagedMetaDataHelper
{
    __gc public class MemberRefInfo
    {
        public:
        String *    m_strMemberName;
        Byte        m_signatureBlob[];
    };

    __gc public class TypeRefInfo
    {
        public:        Int32           m_mdTypeRef;
        String*         m_strTypeName;
        String*         m_strAssemblyName;
        MemberRefInfo*  m_arMemberRefs[];

        TypeRefInfo(){  m_mdTypeRef = 0;
                        m_strTypeName = S"";
                        m_strAssemblyName = S"";
                        m_arMemberRefs = 0; }
    };

    typedef TypeRefInfo* TYPEREFINFO [];

    __gc public class TypeRefInfoHelper
    {
        public:

        TYPEREFINFO public GetTypeRefInfo( String * strFilename )
        {
            MetaDataImportWrapper * pMetaDataWrapper = 0;
            IntPtr memAnsiIntPtr = 0;

            try
            {
               memAnsiIntPtr = Marshal::StringToCoTaskMemAnsi(strFilename);
               char *pszFilename = (char *)memAnsiIntPtr.ToPointer();

               // New a CMetaDataImportHelper class.  This lets us
               // catch the exception if the metadata isn't loaded
               // properly by the constructor          
                
               pMetaDataWrapper = new MetaDataImportWrapper(pszFilename);

               // Free this up, now that we don't need it any more
               Marshal::FreeCoTaskMem( memAnsiIntPtr );
               memAnsiIntPtr = 0;

               IMetaDataImport * pIMetaData =
                   pMetaDataWrapper->m_pIMetaDataImport;

               HCORENUM    hEnum = 0;
               mdTypeDef   rTypeRefs[2048];
               ULONG       cTypeRefs = ARRAYSIZE(rTypeRefs);
                
               HRESULT hr = pIMetaData->EnumTypeRefs(  &hEnum,
                                                       rTypeRefs,
                                                       cTypeRefs,
                                                       &cTypeRefs );
               if ( FAILED(hr) )
                   return 0;

               // We don't need the HCORENUM open anymore
               pIMetaData->CloseEnum( hEnum );

               TypeRefInfo * arTypeRefs[] = new TypeRefInfo* [cTypeRefs];
               for ( unsigned i = 0; i < cTypeRefs; i++ )
               {
                   arTypeRefs[i] = new TypeRefInfo;
                   arTypeRefs[i]->m_mdTypeRef = rTypeRefs[i];

                   wchar_t wszTypeRef[512];
                   ULONG   cchTypeRef = ARRAYSIZE(wszTypeRef);
                   mdToken tkResolutionScope;

                   HRESULT hr =
                   pIMetaData->GetTypeRefProps(    rTypeRefs[i],
                                                   &tkResolutionScope,
                                                    wszTypeRef,
                                                    cchTypeRef,
                                                    &cchTypeRef);
                    if ( FAILED(hr) )
                        continue;

                    wchar_t wszAssemblyName[512] = { 0 };
                    ULONG   cchAssemblyName = ARRAYSIZE(wszAssemblyName);

                    if (TypeFromToken(tkResolutionScope) == mdtAssemblyRef)
                    {
                        pMetaDataWrapper->m_pIMetaDataAssemblyImport->
                                GetAssemblyRefProps(
                                                    tkResolutionScope,
                                                    0,
                                                    0,
                                                    wszAssemblyName,
                                                    cchAssemblyName,
                                                    &cchAssemblyName,
                                                    0,
                                                    0,
                                                    0,
                                                    0 );

                        if ( FAILED(hr) )
                            continue;
                    }

                    arTypeRefs[i]->m_strTypeName = wszTypeRef;
                    arTypeRefs[i]->m_strAssemblyName = wszAssemblyName;

                    // Now spin through all the member refs of this type
                    hEnum = 0;
                    mdMemberRef rMemberRefs[2048];
                    ULONG cMemberRefs = ARRAYSIZE(rMemberRefs);
                    
                    hr = pIMetaData->EnumMemberRefs(&hEnum,
                                                    rTypeRefs[i],
                                                    rMemberRefs,
                                                    cMemberRefs,
                                                    &cMemberRefs );
                    if ( FAILED(hr) )
                        continue;

                    // We don't need the HCORENUM open anymore
                    pIMetaData->CloseEnum( hEnum );
                    
                    arTypeRefs[i]->m_arMemberRefs =
                                       new MemberRefInfo * [ cMemberRefs ];

                    // Spin through all the MemberRefs
                    for ( ULONG j = 0; j < cMemberRefs; j++ )
                    {
                        wchar_t wszMember[512];
                        ULONG   cchMember = ARRAYSIZE(wszMember);

                        PCCOR_SIGNATURE pvSigBlob;
                        ULONG cbSig;

                        // Get member name and signature blob
                        hr = pIMetaData->GetMemberRefProps(
                                        rMemberRefs[j],
                                        0,
                                        wszMember,
                                        cchMember,
                                        &cchMember,
                                        &pvSigBlob,
                                        &cbSig );
                        if ( FAILED(hr) )
                            continue;
                        
                        arTypeRefs[i]->m_arMemberRefs[j] = 
                            new MemberRefInfo;
                        arTypeRefs[i]->m_arMemberRefs[j]->m_strMemberName
                            = wszMember;

                        // Create a managed Byte array to hold the
                        // signature blob
                        Byte mgdSigBlob[] = new Byte[cbSig];

                        // Copy the bytes from the unmanaged array to the
                        // managed array
                        for ( ULONG k = 0; k < cbSig; k++ )
                            mgdSigBlob[k] = *pvSigBlob++;

                        arTypeRefs[i]->m_arMemberRefs[j]->m_signatureBlob
                                                            = mgdSigBlob;
                    }

                }

                // Return all our results to the caller
                return arTypeRefs;
            
            }
            catch( ... )    // Ooops!
            {
                if ( memAnsiIntPtr != 0 )
                    Marshal::FreeCoTaskMem( memAnsiIntPtr );
            }

            delete pMetaDataWrapper;
                
            return 0;
        }

    };  // end class definition
};

}   // end namespace definition
Figure 4 MetaDataImportWrapper.cpp
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "MetaDataImportWrapper.h"

namespace Wheaty
{

namespace UnmanagedMetaDataHelper
{

MetaDataImportWrapper::MetaDataImportWrapper( LPCSTR pszFileName ) : 
    m_pIMetaDataDispenser( 0 ),
    m_pIMetaDataImport( 0 ),
    m_pIMetaDataAssemblyImport( 0 )
{
    CoInitialize( 0 );

    HRESULT hr;

    // Create the IMetaDataDispenser instance.  We need this to create
    // the IMetaDataImport and IMetaDataAssemblyImport interfaces
    hr = CoCreateInstance(  CLSID_CorMetaDataDispenser, 0,
                            CLSCTX_INPROC_SERVER,
                            IID_IMetaDataDispenser,
                            (LPVOID *)&m_pIMetaDataDispenser );
    if ( FAILED(hr) )
        throw "Unable to create IMetaDataDispenser";

    wchar_t wszFileName[MAX_PATH];
    mbstowcs( wszFileName, pszFileName, lstrlen(pszFileName)+1 );

    // Create the IMetaDataImport interface
    hr = m_pIMetaDataDispenser->OpenScope( wszFileName, ofRead,
                                    IID_IMetaDataImport,
                                    (LPUNKNOWN *)&m_pIMetaDataImport );
    if ( FAILED(hr) )
        throw "Unable to create IID_IMetaDataImport";

    // Create the IMetaDataAssemlyImport interface
    hr = m_pIMetaDataDispenser->OpenScope( wszFileName, ofRead,
         IID_IMetaDataAssemblyImport,
         (LPUNKNOWN *)&m_pIMetaDataAssemblyImport);
    if ( FAILED(hr) )
        throw "Unable to create IID_IMetaDataAssemblyImport";
}

MetaDataImportWrapper::~MetaDataImportWrapper()
{
    // Clean up our interface instances
    if ( m_pIMetaDataImport )
    {
        m_pIMetaDataImport->Release();
        m_pIMetaDataImport = 0;
    }

    if ( m_pIMetaDataAssemblyImport )
    {
        m_pIMetaDataAssemblyImport->Release();
        m_pIMetaDataAssemblyImport = 0;
    }

    if ( m_pIMetaDataDispenser )
    {
        m_pIMetaDataDispenser->Release();
        m_pIMetaDataDispenser = 0;
    }
}LPCSTR MetaDataImportWrapper::TokenTypeName( mdToken token )
{
    token = TypeFromToken( token );

#define TokenToName(x) case mdt##x: return #x;

    switch( token )
    {
        TokenToName( Module )
        TokenToName( TypeRef )
        TokenToName( TypeDef )
        TokenToName( FieldDef )
        TokenToName( MethodDef )
        TokenToName( ParamDef )
        TokenToName( InterfaceImpl )
        TokenToName( MemberRef )
        TokenToName( CustomAttribute )
        TokenToName( Permission )
        TokenToName( Signature )
        TokenToName( Event )
        TokenToName( Property )
        TokenToName( ModuleRef )
        TokenToName( TypeSpec )
        TokenToName( Assembly )
        TokenToName( AssemblyRef )
        TokenToName( File )
        TokenToName( ExportedType )
        TokenToName( ManifestResource )
        TokenToName( String )
        TokenToName( Name )
        TokenToName( BaseType )
        default: return "<unknown>";
    }
}

}

}
Figure 5 TypeRefViewer.cs
using System;
using System.Drawing;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
using System.Text;
using Wheaty.UnmanagedMetaDataHelper;

namespace TypeRefViewer
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
    private System.Windows.Forms.TreeView treeView1;
    private System.Windows.Forms.Button buttonBrowse;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.Label label2;
    private System.Windows.Forms.Button buttonExport;

    // A collection for storing all the namespaces we've
    // seen before, along with their index into the
    // treeview.
    protected ListDictionary imported_namespaces;

    public Form1()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();

        imported_namespaces = new ListDictionary();

        String [] args = Environment.GetCommandLineArgs();
        if ( args.Length > 1 )
            DisplayTypeRefsFromFile( args[1] );             
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    public override void Dispose()
    {
        base.Dispose();
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        System.Resources.ResourceManager resources =
            new System.Resources.ResourceManager(typeof(Form1));

        this.treeView1 = new System.Windows.Forms.TreeView();
        this.buttonExport = new System.Windows.Forms.Button();
        this.buttonBrowse = new System.Windows.Forms.Button();
        this.label1 = new System.Windows.Forms.Label();
        this.label2 = new System.Windows.Forms.Label();
        this.treeView1.Location = new System.Drawing.Point(16, 64);
        this.treeView1.Size = new System.Drawing.Size(776, 432);
        this.treeView1.TabIndex = 2;
        this.treeView1.Anchor =
            AnchorStyles.Left | AnchorStyles.Right |
            AnchorStyles.Top | AnchorStyles.Bottom;
        this.treeView1.AfterSelect +=
            new System.Windows.Forms.TreeViewEventHandler
           (treeView1_AfterSelect);

        this.buttonExport.Location = new System.Drawing.Point(16, 504);
        this.buttonExport.Size = new System.Drawing.Size(136, 40);
        this.buttonExport.TabIndex = 0;
        this.buttonExport.Text = "Export...";
        this.buttonExport.Click +=
             new System.EventHandler(this.buttonExport_Click);
        this.buttonExport.Anchor = AnchorStyles.Left | AnchorStyles.Bottom;
        this.buttonBrowse.Location = new System.Drawing.Point(16, 8);
        this.buttonBrowse.Size = new System.Drawing.Size(136, 40);
        this.buttonBrowse.TabIndex = 0;
        this.buttonBrowse.Text = "Browse...";
        this.buttonBrowse.Click
            += new System.EventHandler(this.buttonBrowse_Click);
        this.label1.Location = new System.Drawing.Point(176, 16);
        this.label1.Size = new System.Drawing.Size(608, 32);
        this.label1.TabIndex = 1;
        this.label1.Text =
            "Click the Browse button to select a .NET file to display";
        this.label2.Location = new System.Drawing.Point(176, 512);
        this.label2.Size = new System.Drawing.Size(608, 32);
        this.label2.TabIndex = 5;
        this.label2.Text = "assembly name";
        this.label2.Anchor = AnchorStyles.Left | AnchorStyles.Bottom;
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(803, 551);
        this.Controls.AddRange(new System.Windows.Forms.Control[]
                { this.buttonExport,
                  this.treeView1,
                  this.label1,
                  this.label2,
                  this.buttonBrowse});
        this.Text = "TypeRefViewer - Matt Pietrek 2001";
    }
    #endregion

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main() 
    {
        Application.Run(new Form1());
    }

    protected bool DisplayTypeRefsFromFile( String filename )
    {
        // Prepare for a fresh start.  Clear out the tree control
        // and any namespaces we saw for the previously viewed
        // assembly
        treeView1.Nodes.Clear();
        imported_namespaces.Clear();

        // Get an instance of our helper class in the C++ DLL
        // This class will call  the unmanaged metadata APIs for us
        TypeRefInfoHelper helper = new TypeRefInfoHelper();

        // Call the helper to return an array of TypeRefInfo's for
        // the given assembly
        TypeRefInfo[] typeRefs = helper.GetTypeRefInfo( filename );

        if ( typeRefs == null ) // Make sure there's something to display!
        {
            MessageBox.Show("Could not find .NET metadata in this file");
            return false;
        }

        label1.Text = filename; // Looks OK to set this now

        // Disable treeview redraws while we're stuffing it with data
        treeView1.BeginUpdate();    
        
        // For each imported .NET Type...
        foreach( TypeRefInfo tr in typeRefs )
        {
            try
            {
                String strQualifiedTypeName;

                // If we have the name of the assembly DLL containing the
                // Type, create a partially qualified name.  The
                // Type.GetType() method (below) works better this way!
                if ( tr.m_strAssemblyName.Length > 0 )
                    strQualifiedTypeName = tr.m_strTypeName + ","
                                            + tr.m_strAssemblyName;
                else
                    strQualifiedTypeName = tr.m_strTypeName;

                // Get an instance of the Type, given its name
                Type mytype = Type.GetType( strQualifiedTypeName );

                // Get the namespace from the Type instance     
                String strNamespace = mytype.Namespace;

                // Variable for keeping track of where we inserted
                // this TypeRefInfo into the treeview
                int iTreeNodeIndex;

                // Have we already seen other types in this namespace?  If
                // yes, then get the treeview index so that we can
                // insert this type under the namespace node
                if ( imported_namespaces.Contains( strNamespace ) )
                {
                    // Fix this cast!
                    iTreeNodeIndex =(int)imported_namespaces[strNamespace];
                }
                else    // A new namespace we haven't seen before, so
                {       // create a new namespace node for it

                    TypeRefTreeNode node = new
                        TypeRefTreeNode(strNamespace,tr.m_strAssemblyName);
                    iTreeNodeIndex = treeView1.Nodes.Add( node );

                    // Add knowledge of the new namespace to Dictionary
                    imported_namespaces[strNamespace] = iTreeNodeIndex;
                }

                // Figure out the index of the namespace node that we'll 
                // add the new Type to.
                TypeRefTreeNode namespaceNode =
                        (TypeRefTreeNode)treeView1.Nodes[iTreeNodeIndex];

                // Add the new Type under the namespace node                    
                TypeRefTreeNode typeNode = new
                        TypeRefTreeNode(mytype.Name, tr.m_strAssemblyName);
                namespaceNode.Nodes.Add( typeNode );

                // Iterate through each imported member of the imported
                // Type, and insert it under the Type node we just added.
                foreach ( MemberRefInfo memberRef in tr.m_arMemberRefs )
                {
                    // Call reflection method to return all methods
                    // with the specified name
                    MemberInfo[] members =
                            mytype.GetMember( memberRef.m_strMemberName );

                    // If just one member is returned, we know we got
                    // the right one
                    if ( members.Length == 1 )
                    {
                        // Call helper function to do pretty things with 
                        // the member name before addding it.
                        AddMemberNodeToTree( members[0], typeNode,
                                            memberRef.m_strMemberName );
                    }
                    else    // This method is overloaded.  
                    {       // See if we can find the right member

                        // Get number of params from signature
                        int cParams = memberRef.m_signatureBlob[1];

                        // If we find a matching method, remember it
                        MethodBase matchingMethod = null;

                        // Examine each overloaded method to see if it's
                        // the one we're looking for.  We'll compare each
                        // method to the one returned in the
                        // MemberRefInfo struct
                        foreach ( MethodBase mb in members )
                        {
                            // Call reflection method to get the parameters
                            // for this method
                            ParameterInfo[] paramInfo = mb.GetParameters();

                            // Does the number of params (as seen via
                            // reflection) match the number of params from
                            // the signature?  If so, we may have a match.
                            if ( paramInfo.Length == cParams )
                            {
                                // If this is the first match, we *may*
                                // have found a match.
                                if ( matchingMethod == null )
                                {
                                    matchingMethod = mb;
                                }
                                else    // A 2nd match.  We didn't find
                                {       // it, so bail out
                                    matchingMethod = null;
                                    break;
                                }
                            }
                        }

                        if ( matchingMethod != null  )
                        {
                            AddMemberNodeToTree( matchingMethod, typeNode,
                                            memberRef.m_strMemberName );
                        }
                        else
                        {
                            typeNode.Nodes.Add(new TypeRefTreeNode
                                (memberRef.m_strMemberName
                                + " (???) - Overloaded", strNamespace));
                        }
                    }
                }
            }
            catch ( Exception e )   // Something went wrong!
            {
                String strException = e.ToString();
                treeView1.Nodes.Add( new TypeRefTreeNode(
                        String.Format("Error with {0}.{1}",
                                        tr.m_strAssemblyName,
                                        tr.m_strTypeName), "" ) );
            }
        }

        
        treeView1.EndUpdate();  // Let the treeview redraw itself again

        return true;
    }

    static void AddMemberNodeToTree( MemberInfo member,
                                     TypeRefTreeNode typeNode,
                                     string strMemberName )
    {
        // Given a MemberInfo, decorate it with parameters and return value
        int memberNode;

        if (    (member.MemberType == MemberTypes.Method)
             || (member.MemberType == MemberTypes.Constructor) )
        {
            MethodBase method = (MethodBase)member;

            String strParams = 
                FormatParameterString(method.GetParameters());

            if ( member.MemberType == MemberTypes.Method ) // Normal method
            {
                TypeRefTreeNode n =
                    new TypeRefTreeNode( strMemberName + strParams  +
                        " returns " +
                        ((MethodInfo)method).ReturnType.ToString(),
                        typeNode.m_strAssembly );

                memberNode = typeNode.Nodes.Add( n );
            }
            else    // Constructor
            {
                TypeRefTreeNode n =
                    new TypeRefTreeNode( strMemberName + strParams,
                                            typeNode.m_strAssembly );

                memberNode = typeNode.Nodes.Add( n );
            }
        }
        else    // Not a method or constructor
        {
            memberNode = typeNode.Nodes.Add(
                            new TypeRefTreeNode(strMemberName,
                                                typeNode.m_strAssembly) );
        }
    }

    static String FormatParameterString( ParameterInfo[] arParameters )
    {
        StringBuilder str = new StringBuilder();

        str.Append( "(" );

        int paramNumber = 0;

        foreach ( ParameterInfo param in arParameters )
        {
            if ( paramNumber > 0 )  // Tack on a comma before adding the 
                str.Append( ", " ); // next parameter.  But not for the
                                    //  0'th param
            
            str.Append( param.ParameterType );

            String strParamName = param.Name;
            if ( strParamName != null )
            {
                str.Append( " " );
                str.Append( strParamName );
            }

            paramNumber++;
        }

        str.Append( ")" );

        return str.ToString();
    }

    private void buttonBrowse_Click(System.Object sender, 
                                    System.EventArgs e)
    {
        OpenFileDialog openFileDialog1 = new OpenFileDialog();

        openFileDialog1.Filter =
            "Executable files (*.exe)|*.exe|DLL files 
            (*.dll)|*.dll|All Files (*.*)|*.*";
        openFileDialog1.FilterIndex = 1;
        openFileDialog1.RestoreDirectory = true ;

        if ( openFileDialog1.ShowDialog() == DialogResult.OK )
        {
            DisplayTypeRefsFromFile(openFileDialog1.FileName);
        }
    }

    private void buttonExport_Click(System.Object sender, 
                                    System.EventArgs e)
    {
        // Get the name of the file to write to
        SaveFileDialog saveFileDialog1 = new SaveFileDialog();

        saveFileDialog1.RestoreDirectory = true ;

        if ( saveFileDialog1.ShowDialog() != DialogResult.OK )
            return;

        TextWriter writer = new StreamWriter( saveFileDialog1.FileName );
        TreeNodeCollection assemblyNodes = treeView1.Nodes;

        // Emit some basic header / copyright type info
        writer.Write( "TypeRefViewer - Matt Pietrek, 2001\n\n" );
        writer.Write( "File: {0}\n\n", label1.Text );

        // Iterate through each tree node, and dump it to the file
        // Just minimal formatting is done here
        foreach ( TypeRefTreeNode assemblyNode in assemblyNodes )
        {
            writer.Write( "————————————————————\n" );
            writer.Write( "Namespace: {0}\n", assemblyNode.Text );

            foreach ( TypeRefTreeNode classNode in assemblyNode.Nodes )
            {
                writer.Write( "\tclass: {0}\t\tassembly: {1}\n",
                                classNode.Text, classNode.m_strAssembly );

                foreach( TypeRefTreeNode memberNode in classNode.Nodes )
                {
                    writer.Write( "\t\t{0}\n", memberNode.Text );
                }
                writer.Write( "\n" );
            }
        }

        writer.Close();
    }

    private void treeView1_AfterSelect( object sender,
                                        TreeViewEventArgs e)
    {
        // Set the form's bottom label to the name of the assembly
        // that we stored in the node.
        label2.Text = ((TypeRefTreeNode)e.Node).m_strAssembly;
    }
}
}
Figure 6 TypeRefTreeNode.cs
//==========================================
// Matt Pietrek
// FILE: TypeRefTreeNode.cs
//==========================================
namespace TypeRefViewer
{
    using System;
    using System.Windows.Forms;

    public class TypeRefTreeNode : TreeNode
    { 
        // The System.Reflection.MemberInfo associated with this node
        public String m_strAssembly;
                    
        public TypeRefTreeNode(String caption, String strAssembly)
        {
            Text = caption;
            m_strAssembly = strAssembly;
        }
    }
}
Show: