|
Hi there,
I am trying to implement a standalone application which, given the path to a Visual Studio solution file, will start up the Visual Studio DTE, open the solution within it and then get it to run through all the public methods in the solution and,using the "find symbol" functionality, seewhere each public method is called.
I am new to VS automation and extensibility, so please bear with me! I am coding in VB and, reading thru the VS SDK docs (boy is that a task!),I am at the point where I need to use a call to IserviceProvider.GetService for the SVsObjectSearch interface IVsObjectSearch so that I can perform the Symbol find functionality.
Anyone care to give me a few clues as to how I get the call to GetService to provide me with a useful interface instead of "Nothing"? As I say, I am new to this and all the examples I can find on the interweb are for plugins and add-ons and extensions which are running within VS and just call GetService direct. I assume that the context they are running in enables them to get something useful back. In my case I creating an instance of VS DTE from a stand alone app and this is, I believe, where my problem lies, since any call to GetService needs to be in the context of the created instance, only it ain't!
Any help gratefully taken!
TIA,
/\/\ - Changed TypeChao KuoMSFT, ModeratorTuesday, September 29, 2009 9:55 AMNot follow up
-
| | Martyn_Bannister Tuesday, September 22, 2009 7:38 AM | Hi, TIA
For the class Package in the Microsoft.VisualStudio.Shell.Package namespace has already implemented the IserviceProvider interface, so any class derived from the Package class can use the GetService method directly. Another class called component also has the method GetService, so any class derive from it such as UserControl canget the service.
If your class derived from neither of the class I metioned before, you have to use the Package.GetGlobalService method, and the class Package is in theMicrosoft.VisualStudio.Shellnamespace. Hope this could help you! Thanks Chao | | Chao Kuo Wednesday, September 23, 2009 8:26 AM | Hi Chao,
Many thanks for your reply. The application that I am writing is a stand-alone application which creates an instance of Visual Studio and wants to query its loaded solutionfrom "outside", rather than a package which VS loads on startup and is therefore bound into the VS instance.
The way I see things, because my application is not a Package or a Component, I have no access to IServiceProvider as you describe.Similarly, my applicationhas no access to Package.GetGlobalService either?
Please correct me if I am wrong.
Below is some VBcode for a fictitious console app which hopefully demonstates what I am trying to do....
<DUMMYCODE> Imports System.ComponentModel.Design Imports System.IO Imports EnvDTE ' C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies\EnvDTE.dll Imports Microsoft.VisualStudio.Shell.Interop ' Visual Studio SDK
Module Module1
Sub Main()
' Create an instance of Visual Studio
Dim VS2008Instance As EnvDTE.DTE = Nothing
VS2008Instance = DirectCast(CreateObject("VisualStudio.DTE.9.0"), EnvDTE.DTE)
Dim arg As String = "C:\WORK\TestSolution.sln"
Dim soln As Solution = VS2008Instance.Solution
' Open a solution within it
soln.Open(arg)
' Create the groundwork necessary to query the symbols
Dim objList As IVsObjectList = Nothing
Dim serviceProvider As IServiceProvider
' Somehow, we need to get a handle on IServiceProvider!!!!
Dim fs As IVsObjectSearch = _ CType(serviceProvider.GetService(GetType(SVsObjectSearch)), IVsObjectSearch)
If fs IsNot Nothing Then
Dim SCrit(0) As VSOBSEARCHCRITERIA
With SCrit(0)
.eSrchType = VSOBSEARCHTYPE.SO_ENTIREWORD
.grfOptions = CUInt(_VSOBSEARCHOPTIONS.VSOBSO_NONE)
.szName = "SomeFunctionName"
End With
Try
fs.Find(CUInt(__VSOBSEARCHFLAGS.VSOSF_EXPANDREFS), SCrit, objList)
Catch ex As Exception
' Apparently, Find will ALWAYS throw an exception, because of a bug. 'It still succeeds however!
End Try
End If
End Sub
End Module </DUMMYCODE> | | Martyn_Bannister Wednesday, September 23, 2009 10:32 AM | Hi Martyn,
You can only marshal the automation interfaces (which ultimately derive from IDispatch) across the process boundary. IServiceProvider, IVsObjectSearch etc are not marshallable across processes. This code will need to run in-process to DevEnv.exe.
Sincerely, Ed Dore | | Ed Dore Wednesday, September 23, 2009 5:32 PM | Hi, Martyn
Yes, Ed Dore was right, this will make your applicationaccross the process boundary and you should run your application in-process to DevEnv. Why can not you use add-in instead, you are able to use the Service in anadd-in.
ServiceProvider sp = new ServiceProvider((IOleServiceProvider)_applicationObject);
IVsObjectSearch fs = sp.GetService(typeof(SVsObjectSearch).GUID) as IVsObjectSearch;
Hope this could help Thanks Chao | | Chao Kuo Thursday, September 24, 2009 3:30 AM | We are changing the issue type to “General Discussion�because you have not followed up with the necessary information. If you have more time to look at the issue and provide more information, please feel free to change the issue type back to “Question�by opening the Options list at the top of the post window, and changing the type. If the issue is resolved, we will appreciate it if you can share the solution so that the answer can be found and used by other community members having similar questions.
Thank you! Chao | | Chao Kuo Tuesday, September 29, 2009 9:55 AM | Hi Chao,
I didn't want to use it as an Add-In because the concept of the application is that it stands alone and separate from VS Devenv.exe. In this way, the app SHOULD be able to open multiple solutions, one after the other, and do its processing on each one. If I use an Add-In from within VS, I am limited to the solution that VS currently has loaded.
Hmmm... this is looking like it won't work!
Rgds,
/\/\ | | Martyn_Bannister Wednesday, September 30, 2009 12:38 PM | Hi Martyn, You can give a try on below approach. Once DTE object is ready with solution, call below API to get the projects
EnvDTE.Projects VCProjects = (EnvDTE.Projects) DTE.GetObject("VCProjects");
Then loop through projects and get the CodeModel and CodeElements.
Let me know if it works. If you want details, Let me know.
Thanks & Regards PKR
| | Vic Vega Thursday, October 01, 2009 6:36 AM | Hi PKR,
Many thanks for your response. The DTE.GetObject call works for both "VCProjects" (which returns an empty list) and "VBProjects", which returns the correct list of two (in my test solution), mainly because they are VB Projects I suspect!!!!
Do you thinkI might be able to do a "GetObject" to hook into the service provider? I will look into that, but if you happen to know the object I would be looking for, I'd be grateful if you can let me know!
Rgds,
/\/\ | | Martyn_Bannister Thursday, October 01, 2009 7:53 AM | Hi, Martyn I think you should consider the words by Ed Dore, you needs is across the process bounday, Ed Dore is the developer of VSX in Microsoft, I think there is few body that have more acknowlege about VSX. Thanks Chao | | Chao Kuo Friday, October 02, 2009 9:52 AM | Hi Chao, Hi Ed,
Don't mean to offend. Don't know Ed personally. I take your word for it that Ed's answer is definitive and that what iI want to do cannot be done.
Rgds,
/\/\ | | Martyn_Bannister Friday, October 02, 2009 10:47 AM | No offense taken Martyn. As a point of clarification, I actually work on the Visual Studio Extensibility support team at Microsoft. I'm not a developer, though I do seem to write a lot of code :-) You should be able to retrieve any interface that is derived from IDispatch. But many of the core IDE services are actually derived from IUnknown and there is no marshalling support that would allow you to successfully retrieve these from another process. One additional approach you could consider is writing either an addin or package that extends the IDE, such that you can then invoke the functionality you are looking for from your external app. Meaning, you could author an addin that supported a specific command, and then invoke that command via the EnvDTE automation service. For example: the _EnvDTE.ExecuteCommand. Or you could proffer an automation (IDispatch based) service from a VSPackage and retrieve it via _EnvDTE.GetObject. While you cannot retrieve/access services like SVsObjectSearch, there are certainly ways to work around such limitations. With VS Extensibility, there is always more than one way to skin a cat. For example, the EnvDTE.Find interface :-) Sincerely,
Ed Dore | | Ed Dore Saturday, October 03, 2009 8:23 AM | OK Ed, many thanks for that.
Looks like my best bet is therefore to get my functionality working in an Add-In before thinking about exposing it. Thats what I'll work towards now. Thanks again for your help.
Rgds,
/\/\ | | Martyn_Bannister Monday, October 05, 2009 7:21 AM | Well Ed, I must admit that this has got me tearing my hair out (and I don't have too much of that left!)
Have now succeeded in writing an addin which gets an IVsObjectSearch interface using GetService(GetType(SVsObjectSaerch)). I can supply it with an VSOBSEARCHCRITERIA structure and perform a Find, passing an IVsObjectList by reference. What I can't now work out is how to then access the result!
The IVsObjectList returns as nothing, even though the output appears in the FindSymbolResults window, but I cannot find a way of accessing it from there either. Sorry to be a pest, but can you point me in the right direction Ed?
Rgds,
/\/\ | | Martyn_Bannister Monday, October 05, 2009 10:35 AM | Hi Martyn,
Stop pulling your hair out, or you'll end up like me :-)
If the info appears in the FindSymbolResults window, I think it'll be possible to retrieve the info. I'm just not sure if we can do that through the existing automation interfaces. I'll try poking around with this tomorrow and post back here.
Sincerely, Ed Dore | | Ed Dore Friday, October 09, 2009 4:39 AM | Hi Ed,
Many thanks for your time. I appreciate it.
I am now totally hairless. Not only could I not access the FindSymbolResults content, when I gave up on that, I started to investigate the IVsFindSymbol interface, which <QUOTE> replaces IVsObjectSearch </QUOTE>
Now I know that all Microsoft documentation is ONLY useful when you ALREADY KNOW THE SUBJECT BACKWARDS, but I can't for the life of me work out how IVsFindSymbol is meant to be used either! It has the "DoSearch" method, which sounds incredibily useful, but again the docs give no indication as to how to access the results which, you've guessed it, end up in the Find Symbol Reuslts window! Can it be that all this functionality is only there to populate this window so that the user can visually scan it? If so, then what a waste, since you could just execute the Find Symbol command. If you are able to access the results of the search, I would have thought a paragraph or two in the docuementation on how to do that wouldn't have gone amiss.
<sob>
/\/\
| | Martyn_Bannister Friday, October 09, 2009 8:45 AM | Hi Martyn, I think I've hit on a pretty good solution you can execute from your automation client, instead of having to use an addin. You can use the EnvDTE.Find2 interface to execute your search, and then retrieve the results from the results ToolWindow. I tested this out with the following macros. The macros run in an external process, so this should be easily converted to run in your original app. Note, after calling TextSelection.SelectAll(), the TextSelection.Text property will contain the contents of the results window.
' Returns a "Miscellany" OutputWindowPane Function GetMiscDumpPane() As OutputWindowPane Dim ow As OutputWindow = DTE.Windows.Item(Constants.vsWindowKindOutput).Object Dim pane As OutputWindowPane Try pane = ow.OutputWindowPanes.Item("Miscellany") Catch ex As Exception pane = ow.OutputWindowPanes.Add("Miscellany") End Try Return pane End Function
Sub TestSearch() ' Clear the results toolwindow Dim resultsWindow As Window = DTE.Windows.Item(Constants.vsWindowKindFindResults1) Dim txtSel As TextSelection = resultsWindow.Selection() txtSel.Delete()
' Do a search Dim find2 As Find2 = DTE.Find find2.MatchCase = True find2.FindWhat = "Microsoft" find2.Target = vsFindTarget.vsFindTargetSolution find2.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral find2.Action = vsFindAction.vsFindActionFindAll find2.ResultsLocation = vsFindResultsLocation.vsFindResults1 find2.WaitForFindToComplete = True DTE.Find.Execute()
' Select text in the results window txtSel.SelectAll()
' Display retrieved selection Text in our custom Output Window Pane Dim outputPane = GetMiscDumpPane() outputPane.Clear() outputPane.OutputString(txtSel.Text) End Sub
Cheers,
Ed Dore | | Ed Dore Friday, October 09, 2009 6:17 PM | Hi Ed,
Many thanks for all your help. I almost hate to say this, because you have put in a lot of effort on my behalf, for which I am very grateful, but........
your code, while working fine in itself, doesn't actually do what I want! Your code uses the find2 object to do a straight text search. What I need to do is a symbol search. The reason a straight text search doesn't meet my needs is that it will pick up all occurrences of the search string, but what I need to do is find references to functions. If I use the straight text search I need to check that the string found isn't inside a comment, isn't a substring of a larger string, is actually a function call, rather than a string which looks like one etc. etc.
If I can access the result of a FindSymbol search, I don't need to do any of this malarkey! In addition, I believe the Symbols search gets over the problem of function calls to overloaded functions etc.
I can perform the symbol search that I want. The search populates the FindSymbolResults window. I can get a handle on the output window --- "DTE.Windows.Item(EnvDTE.Constants.vsWindowKindFindSymbolResults)"
I thought I could use your "TextSelection" method to access the contents, but NO!
In the immediate window...
? resultswindow {System.__ComObject} System.__ComObject: {System.__ComObject} AutoHides: True Caption: "Find Symbol Results - 1 match found" Collection: {System.__ComObject} ContextAttributes: {System.__ComObject} Document: Nothing DocumentData: In order to evaluate an indexed property, the property must be qualified and the arguments must be explicitly supplied by the user. DTE: {System.__ComObject} Height: 192 HWnd: 0 IsFloating: False Kind: "Tool" Left: 111 Linkable: True LinkedWindowFrame: Nothing LinkedWindows: Nothing Object: Nothing ObjectKind: "{68487888-204A-11D3-87EB-00C04F7971A5}" Project: Nothing ProjectItem: Nothing Selection: Nothing Top: 597 Type: vsWindowTypeToolWindow {15} Visible: True Width: 1228 WindowState: {"Error HRESULT E_FAIL has been returned from a call to a COM component."}
As you can see, the "Selection" property is Nothing, the "Object" property is Nothing. so I have no way, or so it seems, of accessing the contents. What I really need is not the Find2 object, but the FindSymbol2 object! Or a way of getting at the FindSymbolResults window contents.
AAARRGGGHHH!!!
More hair pulling needed!
Rgds,
/\/\
| | Martyn_Bannister Monday, October 12, 2009 12:04 PM | My fault, I didn't realize which toolwindow you needed here.
That "Find Symbol Results" toolwindow is an entirely different beast. It's actually an IVsLiteTreeList based creature. Unfortunately, this puts us back into the mode of having to find a way to export the data back out of processes, as there is no automation support on this particular toolwindow.
I'll have to poke around some more and see what all can be done to extract the info.
Ed Dore | | Ed Dore Monday, October 12, 2009 6:33 PM | Hi Ed,
I guessed it might be something to do with the IVsLiteTree, but can't ifnd anything in the docs to help me further with that either. Many thanks for your continuing help, persistence and patience!!!
/\/\ | | Martyn_Bannister Tuesday, October 13, 2009 7:55 AM |
|