How to enumerate through reference assemblies looking for specific interfaces

Answered How to enumerate through reference assemblies looking for specific interfaces

  • 14 April 2012 18:08
     
     

    I am creating a DSL and within this DSL I would like to reflect back to the user a list of any class names that support specific interfaces.  These may be from project items they have defined within their solution or they could be project items from referenced assemblies

    As an example:

    Assume I have an interface named IDoSomething and that I implement this on two classes called DoesSomeThingOnce, DoesSomeThingTwice.

    Assume that the DoesSomeThingOnce class is defined in a referenced assembly on the user's solution called TheDoSomeThing assembly.

    Assume that the DoesSomeThingTwice class is defined within a project within the current solution.

    I am looking for support on the implementation of a function called

    GetClassNames(string interfaceName) 

    which returns a list of strings per each class it finds that supports the name of the interface that is passed into it for the given solution.

    In the scenario above it would return a list with two strings. "DoSomeThingOnce" and "DoSomeThingTwice".

    There will be a lot more meta data that I will wish to return but if you could assist me with this simple automation I think I could embellish the remainder of my needs.

    I know how to wire up the supporting menu commands within Visual Studio package, I am just looking for support on the automation code.

    As an added bonus (for me),  most of my classes with supporting interfaces include a MEF Export, as such if you could show how to return the class name(s) as well as return Export Names I'd be extremely grateful.


    Johnny Larue




Semua Balasan

  • 14 April 2012 23:13
     
      Memiliki Kode

    The following code enumates every project item within the project... I would like to make it so that I can detect for a specific interface (which it does not) and Mef Export name ...

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.ComponentModel.Design;
    using System.Windows.Forms;
    using EnvDTE;
    using EnvDTE80;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.Shell;
    namespace SoftLanding.Learn
    {
         
        [PackageRegistration(UseManagedResourcesOnly = true)]
        [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
        [ProvideMenuResource("Menus.ctmenu", 1)]
        [Guid(GuidList.guidLearnPkgString)]
        public sealed class LearnPackage : Package
        {
            public LearnPackage()
            {
                Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString()));
            }
            #region Package Members
            protected override void Initialize()
            {
                Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString()));
                base.Initialize();
                 //Add our command handlers for menu (commands must exist in the .vsct file)
                var mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
                if (null == mcs) return;
                // Create the command for the menu item.
                var menuCommandID = new CommandID(GuidList.guidLearnCmdSet, (int)PkgCmdIDList.cmdidSoftLanding);
                var menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
                mcs.AddCommand( menuItem );
            }
            #endregion
           
            private void MenuItemCallback(object sender, EventArgs e)
            {
               
                var app = (DTE) GetService(typeof (SDTE));
                try
                {
                    var currentSolution = (Solution2) app.Solution;
                    if (currentSolution == null) return;
                    if (currentSolution.Projects != null)
                    {
                        foreach (Project project in currentSolution.Projects)
                        {
                            Debug.WriteLine(string.Format("Processing project {0}", project.Name));
                            EnumerateProjectItems(project, ProcessProjectItem);
                        }
                    }
                }
                catch (SystemException ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }
            public delegate void ProjectItemCallBack(ProjectItem pi);
            public void EnumerateProjectItems(Project proj, ProjectItemCallBack psr)
            {
                if (proj.ProjectItems == null) return;
                foreach (ProjectItem projItem in proj.ProjectItems)
                {
                    var projItems = projItem.ProjectItems;
                    if ((projItems != null) && projItems.Count > 0)
                    {
                        EnumerateProjectItems(projItems, psr);
                    }
                    else
                    {
                        psr.Invoke(projItem);
                    }
                }
            }
            private void EnumerateProjectItems(ProjectItems projectItems, ProjectItemCallBack psr)
            {
                foreach (ProjectItem projItem in projectItems)
                {
                    var projItems = projItem.ProjectItems;
                    if ((projItems != null) && projItems.Count > 0)
                    {
                        EnumerateProjectItems(projItems, psr);
                    }
                    else
                    {
                        psr.Invoke(projItem);
                    }
                }
            }
            private void ProcessProjectItem(ProjectItem prjItm)
            {
                if (prjItm == null) return;
                Debug.WriteLine(string.Format("Processing project Item {0}", prjItm.Name));
                 
                var cm2 = prjItm.FileCodeModel;
                if (cm2 != null)
                {
                    try
                    {
                        ShowCodeElements(cm2.CodeElements);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.ToString());
                    }
                }
            }
            private void ShowCodeElements(CodeElements colCodeElements)
            {
                if ((colCodeElements == null)) return;
                foreach (CodeElement objCodeElement in colCodeElements)
                {
                    ShowCodeElement(objCodeElement);
                }
            }
            private void ShowCodeElement(CodeElement objCodeElement)
            {
                try
                {
                    Debug.WriteLine(("\t\t\t" + objCodeElement.FullName + " (Kind: " + objCodeElement.Kind + ")"));
                }
                catch (Exception)
                {
                    // Ignore
                }
                if (objCodeElement is CodeNamespace)
                {
                    var objCodeNamespace = (CodeNamespace)objCodeElement;
                    ShowCodeElements(objCodeNamespace.Members);
                }
                else if (objCodeElement is CodeType)
                {
                    var objCodeType = (CodeType)objCodeElement;
                    ShowCodeElements(objCodeType.Members);
                }
                else if (objCodeElement is CodeFunction)
                {
                    ShowCodeElements(((CodeFunction)objCodeElement).Parameters);
                }
            }
        }
    }


    Johnny Larue

  • 14 April 2012 23:25
     
     

    ... and this is what I am seeking as per the MEF Export name list. ..

    In this real life example, I would like to locate all projectitems which contain classes that support IDockShellRoot. In this real life scenario, there could be many class files within my project(s) that support support IDockShellRoot, and so I want to be able to get the list of Export Names which in the image below would be "ArcherShellRoot"...


    Johnny Larue


  • 16 April 2012 7:00
    Moderator
     
     
    Hello ,
    Thank you for your question.
    I am trying to involve someone familiar with this topic to further look at this issue. There might be some time delay. Appreciate your patience.
    Thank you for your understanding and support.
    Yi

    Yi Feng Li [MSFT]
    MSDN Community Support | Feedback to us

  • 16 April 2012 13:30
     
     

    Awesome, I should point out that I am attempting to target only Visual Studio 2010 for now and do not have a need to target Visual Studio 2005/2008.

    Further.. I believe that information I lack relates to the automation types I need to cast to in order to retrieve the attributes I require.

    Specifically, when searching for a specific Interface by name that is being implemented, I am guessing I would test the Kind property of my CodeElement(s) to see if it is equal to the vsCMElement enumeration values of either vsCMElementImplementsStmt or vsCMElementInheritsStmt   and if so that I would need to cast the code element to a supporting type to allow me to interogate for the interface name, although I am not certain. 

    ... and when searching for the Export attribute that I would look for CodeElement(s) with a Kind property is set to vsCMElement enumeration value of vsCMElementAttribute and if found cast to an appropriate type to access the attrbute properties, but again this I am uncertain of as to whether or not that is the correct approach, however if it is, then I would need to know what the types I need to cast over to are.

    I found this table whilst searching for support, clearly it shows the more common types to cast to, but lacks the information I seek.

    Table 10.5. Mapping the vsCMElement Enumeration Values

    Enumeration Value

    Type

    Description

    vsCMElementAssignmentStmt

    An assignment statement

    vsCMElementAttribute

    An attribute

    vsCMElementClass

    CodeClass

    A class

    vsCMElementDeclareDecl

    A declaration

    vsCMElementDefineStmt

    A define statement

    vsCMElementDelegate

    CodeDelegate

    A delegate

    vsCMElementEnum

    CodeEnum

    An enumeration

    vsCMElementEvent

    CodeEvent

    An event

    vsCMElementEventsDeclaration

    An event declaration

    vsCMElementFunction

    CodeFunction

    A function

    vsCMElementFunctionInvokeStmt

    A statement invoking a function

    vsCMElementIDLCoClass

    An IDL co-class

    vsCMElementIDLImport

    An IDL import statement

    vsCMElementIDLImportLib

    An IDL import library

    vsCMElementIDLLibrary

    An IDL library

    vsCMElementImplementsStmt

    An implements statement

    vsCMElementImportStmt

    CodeImport

    An import statement

    vsCMElementIncludeStmt

    An include statement

    vsCMElementInheritsStmt

    An inherits statement

    vsCMElementInterface

    CodeInterface

    An interface

    vsCMElementLocalDeclStmt

    A local declaration statement

    vsCMElementMacro

    A macro

    vsCMElementMap

    A map

    vsCMElementMapEntry

    A map entry

    vsCMElementModule

    A module

    vsCMElementNamespace

    CodeNamespace

    A namespace

    vsCMElementOptionStmt

    An option statement

    vsCMElementOther

    CodeElement

    A code element not otherwise identified in this enum

    vsCMElementParameter

    CodeParameter

    A parameter

    vsCMElementProperty

    CodeProperty

    A property

    vsCMElementPropertySetStmt

    A property set statement

    vsCMElementStruct

    CodeStruct

    A structure

    vsCMElementTypeDef

    A type definition

    vsCMElementUDTDecl

    A user-defined type

    vsCMElementUnion

    A union

    vsCMElementUsingStmt

    CodeImport

    A using statement

    vsCMElementVariable

    A variable

    vsCMElementVBAttributeGroup

    A Visual Basic attribute group

    vsCMElementVBAttributeStmt

    A Visual Basic attribute statement

    vsCMElementVCBase

    A Visual C++ base


    Johnny Larue




  • 16 April 2012 15:50
     
      Memiliki Kode

    It's apparent when I cast my discovered class to type CodeType that within the Dynamic Members view under debug that I can see the Dynamic Member called ImplementedInterfaces

    Given this code how can I get to the ImplementedInterfaces collection?

     else if (objCodeElement is CodeType)
                {
                    var objCodeType = (CodeType)objCodeElement;
                    ShowCodeElements(objCodeType.Members);
                }


    Johnny Larue


  • 16 April 2012 16:33
     
     

    Using this very "hard-coded" approach I was able to zero in on the exact values .. of course a ton of refactoring will be required along with some parsing support on the class attribute export value but this will work. 

    I hope this helps someone else.

    I am looking for a more optimal approach if any others can provide one, plus I want to derive the same level of details from a referenced assembly if anyone can assist that would be much appreciated!


    Johnny Larue


  • 16 April 2012 17:36
    Moderator
     
     Saran Jawaban Memiliki Kode

    Hi Johnny,

    The only other way to do this that I can think of, would be to use Reflection. For example:

    Assembly assembly = Assembly.LoadFrom("c:\pathtosome\assembly.dll");
    var classes = from type in assembly.GetTypes()
       where (type.GetInterface("ISomeInterface") != null)
       select type;
     
    foreach (Type t in classes)
       Console.WriteLine(t.FullName);

    However, if you are running this from a VS extension, you would wind up locking the assembly in question, and fail any subsequent attempts to rebuild. To avoid that problem, you would have to run the above code in a secondary app domain, and then unload that domain to release the lock on the assembly. Which might make your CodeModel solution the easier of the two.

    Sincerely,


    Ed Dore

  • 16 April 2012 18:03
     
     

    Thanks Ed... the enumeration of the ProjectItems within the solution seems perfectly fine for my needs as such I will likely stick with what I have working for code file items however I am wondering if you can tell me how given the list of referenced assemblies from within each project of my solution via automation is it possible to walk these assemblies without reflection .. in otherwords much like the FileCodeModel but for referrenced assemblies?

    Thanks


    Johnny Larue


  • 16 April 2012 20:27
     
     

    Trying to use reflection on referrenced assemblies using a secondary domain, but having a lot of issues so far.

    I am getting this exception ...  The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)

    .. when I try to load a SilverLight base assembly into my secondary domain.   Is there a code base option one needs to apply to their secondary domain so that this does not throw this exception?

     Johnny Larue




  • 17 April 2012 4:30
     
      Memiliki Kode

    I am getting very close to where I am able to enumerate assembly references within projects within a solution however I have one last blocker which is not a huge show stopper but would like some community advice.

    Having scoured the internet for recommended solutions I have the following code which almost works.

     [Serializable]
        public class AssemblyTypeChecker : MarshalByRefObject
        {
            public static void ScanForSupportedTypes(string assemblyPath, string typeName)
            {
                if (!File.Exists(assemblyPath))
                    throw new FileNotFoundException("File not found", assemblyPath);
                
                var setup = new AppDomainSetup
                                {
                                    ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                                    ApplicationName = "AssemblyTypeChecker",
                                };
              
                var domain = AppDomain.CreateDomain("AssemblyTypeChecker", null, setup);
                AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainAssemblyResolve;
                AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
                try
                {
                    
                    var fullName = typeof (AssemblyTypeChecker).FullName;
                    if (fullName != null)
                    {
                        var assemblyTypeChecker =  (AssemblyTypeChecker)domain.CreateInstanceFromAndUnwrap(
                                Assembly.GetExecutingAssembly().Location,
                                fullName);
                        assemblyTypeChecker.ScanForTypes(assemblyPath, typeName);
                    }
                }
                catch (Exception)
                {
                }
                finally
                {
                    //unload domain
                    AppDomain.Unload(domain);
                }
            }
            static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
            {
                try
                {
                    var assembly = Assembly.Load(args.Name);
                    if (assembly != null)
                        return assembly;
                }
                catch { // ignore load error 
                }
                var parts = args.Name.Split(',');
                var file = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + parts[0].Trim() + ".dll";
                return Assembly.LoadFrom(file);
            }
              
      
            public void ScanForTypes(string filePath, string typeName)
            {
                if (!File.Exists(filePath)) return;
               
                var location = Path.GetDirectoryName(filePath);
                //resolve any dependencies
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
                    delegate(object sender, ResolveEventArgs args)
                    {
                        var findName = args.Name;
                        var simpleName = new AssemblyName(findName).Name;
                        if (location != null)
                        {
                            var assemblyPath = Path.Combine(location, simpleName) + ".dll";
                            if (File.Exists(assemblyPath))
                                return Assembly.ReflectionOnlyLoadFrom(assemblyPath);
                        }
                        return null;
                    };
                //load the assembly into bytes and load it
                var assemblyBytes = File.ReadAllBytes(filePath);
                var a = Assembly.ReflectionOnlyLoad(assemblyBytes);
                try
                {
                    Debug.WriteLine(string.Format("Total Types = {0}", a.GetTypes().Count()));
                    foreach (var t in a.GetTypes())
                    {
                        try
                        {
                            Debug.WriteLine(string.Format("\t\tType Name = {0}", t.Name));
                            var i = t.GetInterface(typeName);
                            if(i != null)
                            {
                                Debug.WriteLine(string.Format("\t\tSupports Interface by Name = {0}", typeName));
                            }
                        }
                        catch (Exception)
                        {
                             
                        }
                      
                    }
                   
                }
                catch (Exception)
                {
                }
            } 
        }

    I enumerate every referenced assembly within each project within the solution and a call is made to ScanForSupportedTypes on the static class AssemblyTypeChecker  in which I pass in the assemblies path and the type I am scanning for as follows ...

    ..

    AssemblyTypeChecker.ScanForSupportedTypes(assemblyPath,"IDockShellRoot");

    Within the ScanForSupportTypes method a temporary appdomain is created to host the targeted assembly, after which a call to ScanForTypes is made and the assembly is loaded into the temporary appdomain.

    All works perfectly in that I am able to enumerate all types of the loaded assembly except if I do not have all dependent assemblies within the the target assembly's folder. In those cases, when it can't resolve an assembly, the code calls through ReflectionOnlyAssemblyResolve to resolve any dependent assemblies. When this function can't resolve the dependent assembly it returns null, which in turn causes an exception.

    The thing is, that I really do not want to run the code, I simply want to enumerate for specific types which is why I chose to load my target assemblies via the ReflectionOnlyLoad method however whether or not I load using ReflectionOnlyLoad or Load it always wants to have all dependencies resolved when I make a call to GetTypes().  Is there a way that I can avoid the need to drop dependent assemblies in every target folder? 


    Johnny Larue








  • 17 April 2012 17:38
     
     

    The concept of creating a secondary app domain to enumerate out the Export Names for a list of given Interfaces works very well and very fast for those project solution referenced assemblies.

    Combined with the codefile scan approach (described previously above which rips through the users source files within every project within their solution) I think most of the bases are covered although I still need to figure out a way to resolve dependencies that don't reside in the target assemblies folder, so any help would be greatly appreciated !


    Johnny Larue


  • 18 April 2012 3:22
    Moderator
     
     Jawab

    Hi Johnny,

    if you case the EnvDTE.Project object to a VSLangProj.VSProject, it has a References property that lists the assemblies the project references.

    Sincerely,


    Ed Dore

  • 18 April 2012 3:25
    Moderator
     
     Jawab

    Hi Johnny,

    VS uses an AppDomain.AssemblyResove event to look for assemblies in additional spots (like the ....\PublicAssemblies and ....\PrivateAssemblies subdirectories). You might want to consider using this with your custom appdomain, to assist in finding the assemblies that fail to load on the first go around

    Sincerely,


    Ed Dore

  • 04 Juni 2012 23:48
     
     

    If you are using MEF, you could just drop the inheritedAttribute on your interfaces, then enumerate the catalog ?

    Here's a blog post about it.

    http://blogs.geniuscode.net/JeremiahRedekop/?p=235
    • Diedit oleh FXMC 04 Juni 2012 23:48
    •