How to enumerate through reference assemblies looking for specific interfaces
-
2012年4月14日 下午 06: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
- 已編輯 Johnny Larue 2012年4月14日 下午 06:10
- 已編輯 Johnny Larue 2012年4月14日 下午 06:13
- 已編輯 Johnny Larue 2012年4月14日 下午 11:12
所有回覆
-
2012年4月14日 下午 11:13
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
-
2012年4月14日 下午 11: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
- 已編輯 Johnny Larue 2012年4月14日 下午 11:26
-
2012年4月16日 上午 07:00版主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.
YiYi Feng Li [MSFT]
MSDN Community Support | Feedback to us
-
2012年4月16日 下午 01: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
- 已編輯 Johnny Larue 2012年4月16日 下午 01:40
- 已編輯 Johnny Larue 2012年4月16日 下午 01:44
- 已編輯 Johnny Larue 2012年4月16日 下午 01:48
-
2012年4月16日 下午 03:50
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
- 已編輯 Johnny Larue 2012年4月16日 下午 03:50
-
2012年4月16日 下午 04: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
- 已編輯 Johnny Larue 2012年4月16日 下午 04:48
-
2012年4月16日 下午 05:36版主
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
- 已提議為解答 Ed DoreMicrosoft Employee, Moderator 2012年4月16日 下午 05:36
-
2012年4月16日 下午 06: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
- 已編輯 Johnny Larue 2012年4月16日 下午 06:57
-
2012年4月16日 下午 08: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
- 已編輯 Johnny Larue 2012年4月17日 上午 01:19
- 已編輯 Johnny Larue 2012年4月17日 上午 01:58
- 已編輯 Johnny Larue 2012年4月17日 上午 02:47
-
2012年4月17日 上午 04:30
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
- 已編輯 Johnny Larue 2012年4月17日 上午 04:33
- 已編輯 Johnny Larue 2012年4月17日 上午 04:34
- 已編輯 Johnny Larue 2012年4月17日 上午 04:35
- 已編輯 Johnny Larue 2012年4月17日 上午 04:36
- 已編輯 Johnny Larue 2012年4月17日 上午 04:40
- 已編輯 Johnny Larue 2012年4月17日 上午 04:42
- 已編輯 Johnny Larue 2012年4月17日 上午 04:44
-
2012年4月17日 下午 05: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
- 已編輯 Johnny Larue 2012年4月17日 下午 05:39
-
2012年4月18日 上午 03:22版主
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
- 已提議為解答 Ed DoreMicrosoft Employee, Moderator 2012年4月18日 上午 03:22
- 已標示為解答 Johnny Larue 2012年4月18日 上午 04:05
-
2012年4月18日 上午 03:25版主
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
- 已提議為解答 Ed DoreMicrosoft Employee, Moderator 2012年4月18日 上午 03:26
- 已標示為解答 Johnny Larue 2012年4月18日 上午 04:05
-
2012年6月4日 下午 11: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- 已編輯 FXMC 2012年6月4日 下午 11:48

