Programmatically access "Find All References" data in Visual Studio 2019 - c#

I am attempting to use automation in Visual Studio 2019 to help with repetitive tasks as part of a large code merge. I've researched quite a lot on this and am aware that using Roslyn can get me the information I need, but since I am performing other automation tasks in the IDE, I would rather persevere at extracting everything from the IDE as it is presented to me. I got quite a few bits working, but can't seem to be able to manipulate the Find All References tool windows. I can invoke the dialog using dte.ExecuteCommand("Edit.FindAllReferences"). I can also access the Window object from EnvDTE.Windows that corresponds to an existing "Find All References" window, but can't do much with it or the Window.Object property. I am also able to get the IVsWindowFrame object for an existing "Find All References" window, but again I am unable to do very much with it. The way to go appears to be to acquire IFindAllReferencesService but for me, GetService() always returns null. I've since tried the VCmd extension which lets you code macros against the IDE. I found that the macro code in this extension runs in the same process as the IDE - I don't know if it's for this reason or not, but the same GetService() code inside a macro returns a valid interface (Microsoft.VisualStudio.ErrorListPkg.FindAllReferencesService) which is what I need to progress being able to get references information out of a tool window.
I have the following code so far:
using EnvDTE80;
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
//using Microsoft.VisualStudio.Shell;
//using Microsoft.VisualStudio.Data.Framework;
namespace Util2
{
public static class StackOverflowQuestion1
{
[DllImport("ole32.dll")]
private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc);
[DllImport("ole32.dll")]
private static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
public static object GetInstanceBySolutionFileName(string FileName)
{
GetRunningObjectTable(0, out var rot);
rot.EnumRunning(out var enumMoniker);
enumMoniker.Reset();
var fetched = IntPtr.Zero;
var moniker = new IMoniker[1];
CreateBindCtx(0, out var bindCtx);
object comObject;
string displayName;
EnvDTE.DTE dte;
while (enumMoniker.Next(1, moniker, fetched) == 0)
{
moniker[0].GetDisplayName(bindCtx, null, out displayName);
if (!displayName.StartsWith("!VisualStudio.DTE.16.0:"))
continue;
rot.GetObject(moniker[0], out comObject);
dte = (EnvDTE.DTE)comObject;
if (dte.Solution.FullName == FileName)
return comObject;
}
return null;
}
public static void Test()
{
Microsoft.VisualStudio.Shell.FindAllReferences.IFindAllReferencesService findAllReferencesService;
//IServiceProvider serviceProvider;
var dte = GetInstanceBySolutionFileName(#"C:\YOUR PROJECT PATH\YOUR_SOLUTION_FILE.sln");
var serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
//serviceProvider = new Microsoft.VisualStudio.Data.Framework.ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
findAllReferencesService = serviceProvider.GetService(
typeof(Microsoft.VisualStudio.Shell.FindAllReferences.SVsFindAllReferences)) as
Microsoft.VisualStudio.Shell.FindAllReferences.IFindAllReferencesService;
if (findAllReferencesService == null)
MessageBox.Show("NULL!");
else
MessageBox.Show("OK!");
//MessageBox.Show(findAllReferencesService.GetType().ToString());
}
}
}
I am "supposed" to be using packages instead of automation from another application, but the concept of packages and their purpose don't fit what I am trying to achieve. I am trying to create a bunch of code snippets to help me automate a large merge, after that I will not need it. Also, there is a possibility I will need to manipulate 2 VS instances, so even the fact that the code works inside a package, it should not work if that package attempts to hook into another VS instance (i.e. it will have the same problem as my application).
For what it's worth, here's the code that can get me the IVsWindowFrame for the Find All Reference window(s):
var shell = (IVsUIShell)serviceProvider.GetService(typeof(SVsUIShell));
var guid = new Guid("a80febb4-e7e0-4147-b476-21aaf2453969");
shell.FindToolWindowEx((uint)__VSFINDTOOLWIN.FTW_fFindFirst, ref guid, 0, out var windowFrame);
Note, the GUID comes from EnvDTE.Windows.Item().ObjectKind for a references window. Here is a little more about such a window, as an example:
Caption='txtTest_TextChanged' references
Kind=Tool
ObjectKind={A80FEBB4-E7E0-4147-B476-21AAF2453969}
Type=vsWindowTypeToolWindow
As a hack for now, I will call the extension's macro from my application (e.g. VCmd.Command01) which in turn will locate the Find All References window (or search for references for symbol at cursor) then write the results somewhere (e.g. file) and my application would read the results.
EDIT: My hack didn't work as planned and even though it had mileage in the end, I found a quicker way (sort of). After executing command Edit.FindAllReferences, ensure the Visual Studio window itself is focused, the 'Find references' tool/tab would be in a focused state, so I sent it a CTRL+A then CTRL+C using SendInput - all the details I need are in the clipboard which achieves my goal. Similar could be achieved by sending keys to navigate the box, but I haven't got the need for that yet. Obviously this is quite hacky and being able to use IFindAllReferences from another application would be preferrable. Using the macro extension VCMD, I was able to acquire IFindAllReferencesService ref, and was able to call StartSearch(). Looking in more detail, StartSearch merely initialises the window that it returns which is returned as IFindAllReferencesWindow. I haven't tried this, but I think it's possible to hold on to the IFindReferencesWindow interface returned by StartSearch (passing it the exact same label name as it would appear in the IDE e.g. "'button7' references"), then perform Edit.FindAllReferences command, then access the IFindAllReferencesWindow's properties to hopefully get the data. But there were other difficulties...so Clipboard method wins for now :)

Related

Editor Pane in IWpfTextView is not Editable

I am trying to create a Visual Studio extension that will open a document window containing a custom control of mine that hosts an editor pane. I can get the document to load, the correct content type loads with my custom content extensions (classifier, quick info), a window with my custom control showing the editor pane loads, but the text cannot be modified. I can select and highlight text, but no keys work, no commands, and no mouse input other than text selection.
The following is what I have in my Package class:
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideEditorExtension(typeof(MyCustomEditorFactory), ".cust")]
[Guid(MyCustomExtensionsPackage.PackageGuidString)]
public sealed class MyCustomExtensionsPackage : AsyncPackage
{
public const string PackageGuidString = "ec2c4646-d0cc-42c6-b0a6-d0ff3e318cef";
#region Package Members
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
RegisterEditorFactory(new MyCustomEditorFactory(this));
}
#endregion
}
I have verified that the RegisterEditorFactory method is called, and that my editor factory is called when files with the .cust extension are opened.
The following is my editor factory:
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using System;
using System.ComponentModel.Composition;
using System.Runtime.InteropServices;
[ComVisible(true)]
[Guid(EditorFactoryGuidString)]
public sealed class MyCustomEditorFactory : IVsEditorFactory
{
public const string EditorFactoryGuidString = "38AD95BA-8891-46A2-A5EA-25F5F36EEAE0";
private MyCustomExtensionsPackage _package;
private Microsoft.VisualStudio.OLE.Interop.IServiceProvider _vsServiceProvider;
[Import]
public IContentTypeRegistryService ContentTypeRegistry { get; set; }
[Import]
public ITextEditorFactoryService TextEditorFactory { get; set; }
public MyCustomEditorFactory(MyCustomExtensionsPackage package)
{
_package = package;
}
public int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
{
_vsServiceProvider = psp;
return (VSConstants.S_OK);
}
public int Close()
{
return (VSConstants.S_OK);
}
public int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
{
pbstrPhysicalView = null;
return (VSConstants.LOGVIEWID_Primary == rguidLogicalView ? VSConstants.S_OK : VSConstants.E_NOTIMPL);
}
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
{
ThreadHelper.ThrowIfNotOnUIThread();
ppunkDocView = IntPtr.Zero;
ppunkDocData = IntPtr.Zero;
pbstrEditorCaption = string.Empty;
pguidCmdUI = VSConstants.GUID_TextEditorFactory;
pgrfCDW = 0;
int retVal = VSConstants.E_FAIL;
if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) != 0)
{
IVsTextLines textBuffer = null;
if (punkDocDataExisting == IntPtr.Zero)
{
IComponentModel mef = _package.GetService<SComponentModel, IComponentModel>();
mef.DefaultCompositionService.SatisfyImportsOnce(this);
IVsEditorAdaptersFactoryService eafs = mef.GetService<IVsEditorAdaptersFactoryService>();
textBuffer = eafs.CreateVsTextBufferAdapter(_vsServiceProvider, ContentTypeRegistry.GetContentType("CUST")) as IVsTextLines;
string fileText = System.IO.File.ReadAllText(pszMkDocument);
textBuffer.InitializeContent(fileText, fileText.Length);
string[] roles = new string[]
{
PredefinedTextViewRoles.Analyzable,
PredefinedTextViewRoles.Editable,
PredefinedTextViewRoles.Interactive,
PredefinedTextViewRoles.Document,
PredefinedTextViewRoles.PrimaryDocument
};
IWpfTextView dataView = TextEditorFactory.CreateTextView(eafs.GetDataBuffer(textBuffer), TextEditorFactory.CreateTextViewRoleSet(roles));
dataView.Options.SetOptionValue(DefaultTextViewHostOptions.LineNumberMarginName, true);
dataView.Options.SetOptionValue(DefaultTextViewHostOptions.ShowCaretPositionOptionName, true);
dataView.Options.SetOptionValue(DefaultTextViewHostOptions.ChangeTrackingName, true);
dataView.Options.SetOptionValue(DefaultTextViewOptions.ViewProhibitUserInputName, false);
IWpfTextViewHost wpfHost = TextEditorFactory.CreateTextViewHost(dataView, false);
MyCustomEditor editor = new MyCustomEditor(wpfHost);
ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);
ppunkDocView = Marshal.GetIUnknownForObject(editor);
retVal = VSConstants.S_OK;
}
else
{
//code for document already open
}
else
{
retVal = VSConstants.E_INVALIDARG;
}
}
return (retVal);
}
}
There's a lot to unpack there, but ultimately I'm just
Creating an IVsTextBuffer from an IVsEditorAdaptersFactoryService
Loading the contents of the text buffer
Creating an IWpfTextView with the IVsTextBuffer from a ITextEditorFactoryService
Creating an IWpfTextViewHost with the IWpfTextView from the same ITextEditorFactoryService
Creating my my custom window pane and passing in the IWpfTextViewHost
Returning the IVsTextBuffer as the document data and my custom window pane as the document view
My custom window pane code is simply:
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;
using System.Runtime.InteropServices;
[ComVisible(true)]
public sealed class MyCustomEditor : WindowPane
{
private IWpfTextViewHost _wpfHost;
public MyCustomEditor(IWpfTextViewHost wpfHost)
{
_wpfHost = wpfHost;
Content = new MyCustomEditorControl(wpfHost.HostControl);
}
}
I've omitted the code for the MyCustomEditorControl for the sake of brevity, but just assume it's a simple WPF UserControl with a ContentPresenter. The constructor just sets Content property of the ContentPresenter with the wpfHost.HostControl that's passed in.
All of this works insofar as displaying the contents of a file, but I just can't edit anything. It's like the input bindings aren't wired up or enabled, but I can't find what properties to set or enable on which objects, and the documentation is rather poor on this topic. I've gone through the Visual Studio extensions walkthroughs on MSDN, but it seems to be a mish-mash of pre-WPF and post-WPF APIs with no clear guidance on what the authoritative approach is.
Admittedly, I may be naive in assuming that the IWpfTextView would automatically handle user input, but in my defense it's created from an ITextEditorFactoryService and having "TextEditor" in the name carries certain implications.
I'm going to go through a rather exhaustive list of things I've tried and checked to hopefully help narrow down the possibilities or where my mistake is:
After the document window is open, I've checked the Running Document Table and there is an entry for it that is automatically created after CreateEditorInstance exits. It links to the data buffer that's returned, the correct file path, and has an edit lock but no read lock. I've inspected the RDT when C# files are opened in a normal editor window and those have read and write locks. I tried manually setting a read lock on the RDT as well, the counter increments, but that doesn't seem to make a difference.
I've inspected the return result from ITextBuffer.CheckEditAccess on the data buffer and it returns true so the text buffer is reporting it's editable.
The textview roles DO have an effect on the final rendered IWpfTextViewHost. If I add or omit roles, the appearance and functionality changes. For example, adding or removing "ZOOMABLE" adds or removes the dropdown box with the control zoom level. However, "EDITABLE" has no effect on editability.
The editor options DO have an effect. For example, adding or removing the line number margin options does show/hide the line numbers. However, ViewProhibitUserInputName has no effect on editability.
I don't create a code window with CreateVsCodeWindowAdapter because the resulting IWpfTextViewHost that can be retrieved from GetWpfTextViewHost is already parented to another control. If I disconnect it with (IWpfTextViewHost.HostControl.Parent as Border).Child = null, then the contents of IWpfTextViewHost.HostControl gets disposed of for some reason. The IWpfTextViewHost.HostControl loads and it displays a window with margins and scrollbars, but the content is blank and if I click into the left margin of the content pane then Visual Studio throws an ObjectDisposedException. Please note I'm not nulling out the host control itself or its contents, I'm only telling its existing parent it no longer has a child.
I've inspected the editor GUID property of the IVsWindowFrame that opens my document and it's set to the value returned in the pguidCmdUI parameter, which I believe is correct.
I'm not certain if the value being set for the pguidCmdUI parameter in the editor factory CreateEditorInstance is correct. I'm aware it's used for command routing, which might explain why I can't type or use the mouse, but I'm not sure what the correct value should be. I read somewhere it's supposed to be the GUID of the factory that created the editor, so I set it to the text editor factory GUID since that's what created the IWpfTextView. I tried setting it to my editor factory GUID at the top of the file, as well as typeof(IWpfTextView).GUID, Guid.Empty, and assorted other GUIDs, but there's no change.
If I pass my IWpfTextView into IVsEditorAdapterFactoryService.GetViewAdapter, I get back null.
If I create a IVsTextView from IVsEditorAdapterFactoryService.CreateVsTextViewAdapter, I can have both an IVsTextView and an IWpfTextView, but the two don't know about each other and I don't know see any way to map them. I don't even know if they need to be mapped, or if IVsTextView needs to exist or is just the legacy text view interface.
When I inspect the created IVsTextView, it's base type is a SimpleTextViewWindow that is an undocumented class that's internal to Microsoft's Visual Studio implementation, it has WpfTextView and WpfTextViewHost properties, but they're both null (or throw an exception that they can't be read) and are unsettable. Microsoft obviously has some internal voodoo magic to map IVsTextViews to WpfTextViews in their own code, but I don't know what it is.
I'm aware of other code samples that create an IVsInvisibleEditor and get the data buffer from that, but that seems kind of hacky. I mean, it technically works and no one seems to know of any alternative because the documentation in this area is so poor, so I'm not judging or criticizing, it just seems like that shouldn't be necessary. It's my (possibly incorrect) understanding that an invisible editor is a virtual editor that is created for documents that are opened in memory but not hosted in a window, but I'm hosting mine in a window. Additionally all of the working code samples I could find are all copy/pasting from the same Microsoft VSIX code example. In that example, the virtual editor is created in a tool window that's created under the assumption that a document is already open in an existing editor window. So that tool window is creating a virtual editor side-by-side to a physical one for a tool window. If I follow the same invisible editor approach, then I'm effectively creating a virtual editor in memory and then a second physical document editor window. So basically, I'm creating two editors for one document. That doesn't seem right. Again, it technically works, but it seems off.
If the expectation is that we have to implement IOleCommandTarget on our editor window and manually handle every single keystroke, shortcut, and mouse button and manipulate the underlying text buffer directly, then that is going to be the most depressing thing ever. That would mean they tout being able to use WPF, but then give us a document view without a WPF control that natively handles text editing even though they exist in the framework.
Sorry for the ridiculously long post, but I wanted to provide as much information as I possibly could about where I'm at and what I've tried.
Regarding CreateVsCodeWindowAdapter, see the "official" explanation how to make it work: https://developercommunity.visualstudio.com/t/projectionbuffertutorial-gives-error-in-dev16/498617

Why can I not get this Microsoft Store snippet of code to work?

Following various examples from MS and elsewhere, I have written this piece of test code...
[ComImport]
[Guid("4AEEEC08-7C92-4456-A0D6-1B675C7AC005")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
and..
private async Task<bool> TestCode()
{
StoreContext Store = StoreContext.GetDefault();
StoreAppLicense licence = await Store.GetAppLicenseAsync();
bool trial = licence.IsTrial;
bool full = licence.IsActive;
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)Store;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
StoreProductResult App = await Store.GetStoreProductForCurrentAppAsync();
StoreProduct p = App.Product; // Title, price
string title = p.Title;
StorePrice price = p.Price;
return true;
}
And I call it with using
bool x = TestCode().Result;
It all compiles and runs, so I presumably have all the right usings and references added. But when run, the line:
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)Store;
stops with the exception..
Unable to cast object of type 'Windows.Services.Store.StoreContext'
to type 'IInitializeWithWindow'
and I have no clue why.
This is a C# program with a UWP wrapper creating an MSIX package.
This seems to be a pretty standard block adapted from various examples from MS.
Within VS 2019, I have associated the program with the store app.
The 'trail' and 'full' variables seem to be populating correctly.
I have called this from various locations, Constructor, random button, etc.
My questions...
Why does the cast throw an exception?
Is this an old way of doing things that no longer applies?
Does associating the package in VS 2019 to the store app make the call to IInitalizeWithWindow redundant?
How do I fix the code so that 'title' and 'price' populate correctly?
Heaps of head bashing and I finally have it working...
Considering that in the last few days there was not a combination/permutation that I did not try, I don't know really the logic of it working now, but anyway..
Within the UWP installer project I associated the project with the App in the Microsoft Store, then I removed the lines:
[ComImport]
[Guid("4AEEEC08-7C92-4456-A0D6-1B675C7AC005")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)Store;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
The rest is now working just fine. Funny as I had already associated the app with the store AND removed the offending lines. I must have done something just a little bit different this time!

ICE: trying to add a local var with the same name, but different types. during [_RegisterClipboardFormat]

I have a PoC to use some existing Java-codebase in some UWP-app using the most current Visual Studio Community 19 version 16.3.2 and the latest released IKVM 8.1.7195.0. The app builds and runs fine in Debug-mode, but fails to build already in Release-mode with the following error:
MCG0004:InternalAssert Assert Failed: ICE: trying to add a local var
with the same name, but different types. during
[_RegisterClipboardFormat] Ams.Oms.Poc
RegisterClipboardFormat is part of IKVM:
#DllImportAttribute.Annotation(value = "user32.dll", EntryPoint = "RegisterClipboardFormat")
private native static int _RegisterClipboardFormat(String format);
#cli.System.Security.SecuritySafeCriticalAttribute.Annotation
private static int RegisterClipboardFormat(String format)
{
return _RegisterClipboardFormat(format);
}
https://github.com/ikvm-revived/ikvm/blob/master/openjdk/sun/awt/IkvmDataTransferer.java#L95
What I'm wondering is which local variable the error message is referring to? Might be something added implicitly or might have to do with String in Java vs. string in C#? OTOH that file is clearly named .java.
Didn't find much about the error message in general, only the following two links seems to be more interesting:
Variables having same name but different type
Why doesn't C# allow me to use the same variable name in different scopes?
So I'm currently even unsure where the message comes from, Visual Studio/C# directly or IKVM during running code during building Release-mode. I strongly suspect the error is coming from Visual Studio/C#, though.
Searching for the function itself doesn't reveal much of help as well:
Sorry, AWT is not a supported part of IKVM.
https://sourceforge.net/p/ikvm/bugs/225/
Others seemed to have the same problem, because CN1 simply disabled that code entirely in their fork of IKVM:
//#DllImportAttribute.Annotation(value = "user32.dll", EntryPoint = "RegisterClipboardFormat")
//private native static int _RegisterClipboardFormat(String format);
#cli.System.Security.SecuritySafeCriticalAttribute.Annotation
private static int RegisterClipboardFormat(String format)
{
throw new Error("Not implemented");
//return _RegisterClipboardFormat(format);
}
https://github.com/ams-ts-ikvm/cn1-ikvm-uwp/blob/master/openjdk/sun/awt/IkvmDataTransferer.java#L95
Any ideas? Thanks!
There seems to be a workaround by not changing any code at all: The settings of the Release-build contain a checkbox if to use the .NET native toolbox for the build, which is enabled by default. By disabling that the build succeeds without any code change and is as fast as the Debug-build again. Before changing that, the Release-build took a lot longer as well.
Don't know what that means regarding actually calling native code, if that fails or not, because my app doesn't use those. I guess it would fail, depending on if it works in Debug or not. Additionally, I'm not sure if the Windows store accepts such a modified Release-build, but as UWP-apps aren't forced to use native code at all, I guess there's a good chance things are going to work.

VS SDK ContentType does not work

I am trying to include a custom language support for Visual Studio.
To start with, I need to have GoToDefinition support. And I am struggling to get the context menu to include this command.
I have defined a ContentTypeDefinition and have included the FileExtensionToContentTypeDefinition such as:
internal sealed class GaugeFileContentType
{
[Export]
[Name("Gauge")]
[BaseDefinition("code")]
internal static ContentTypeDefinition GaugeContentTypeDefinition = null;
[Export]
[FileExtension(".spec")]
[ContentType("Gauge")]
internal static FileExtensionToContentTypeDefinition GaugeFileExtensionDefinition = null;
}
Now, despite this, on debugging, I see that DTE.ActiveDocument.Type is text, despite me adding the [BaseDefinition('code')] attribute. What am I missing here?
Are the above definitions enough to tell Visual Studio to bring up Context menu for code?
I am using Visual Studio 2013 Ultimate.
After a few days of head banging, I managed to figure out a way.
I was using the Experimental Instance for debugging, and it did not clean and reinstall the extension, and thus Visual Studio continued to treat the ContentType as 'Plain Text', since that was what I had originally.
When I build a VSIX and installed, opened the same file in a new instance of Visual Studio, it brought up the right context menu.
However, it brought out more than what I wanted (i.e Run Unit Tests from Resharper). So I did some more digging up.
In order to ensure that Visual Studio can handle a command, it checks for it by calling IOleCommandTarget.QueryStatus method.
All I had to do was set the CommandFlag as (uint)OLECMDF.OLECMDF_ENABLED | (uint)OLECMDF.OLECMDF_SUPPORTED and return VSConstants.S_OK when the cmdId is VSConstants.VSStd97CmdID.GotoDefn.
The final method looks like this:
public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
if ((VSConstants.VSStd97CmdID)prgCmds[0].cmdID == VSConstants.VSStd97CmdID.GotoDefn)
{
prgCmds[0].cmdf = (uint)OLECMDF.OLECMDF_ENABLED | (uint)OLECMDF.OLECMDF_SUPPORTED;
return VSConstants.S_OK;
}
return Next.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText);
}

Using Reflection to use a namespace on certain OSs

I'm creating a program that uses the CodeProject CoreAudioApi (pretty popular framework for manipulating audio), but the problem is the CoreAudioApi uses system calls that aren't available in any versions of Windows earlier than Vista. If I run a program with CoreAudioApi compiled with it (using a using statement as normal), the program will crash on anything earlier than Vista.
I've created this function to get the version number of the current environment:
win_version = Environment.OSVersion.Version.Major;
That returns the major version number I need. '6' is Vista/7, anything else is not, which is all I need to determine. Utilizing this, I need to determine whether or not to include the CoreAudioApi namespace if the OS is over or equal to '6'. From research, usings need to be compiled with the program, but I've also read about something called Reflection - which might be what I need.
Once I get the CoreAudioApi namespace using'd (sorry for the lack of terminology), the rest is easy. How can I do this?
TL;DR
I need some form of code that would effectively do this:
using System;
using System.Text;
//etc
if(currentWindowsVersion>=6) using CoreAudioApi;
Except control structures won't work outside of a class, and all namespaces are compiled with the program, not controlled individually.
Thanks!
EDIT: So far, I'm using this to load the CoreAudioApi namespace as a compiled assembly:
if(win_version>=6){
CoreAudioApi = Assembly.LoadFrom("CoreAudio.dll");
CoreAudioApi.GetLoadedModules();
CoreAudioApi.GetTypes();
MessageBox.Show("Loaded CoreAudioApi");
}
From here, what I need to do is actually use the types, and methods from the API. My code that works on Windows Vista/7 is this:
public static MMDeviceEnumerator devEnum;
public static MMDevice defaultDevice;
//later in a mute method:
defaultDevice.AudioEndpointVolume.Mute = true/false;
I don't even really need devEnum AFAIK, so really the only important lines are the last two (besides the comment).
I've just tried the following:
Create a new console application project
Add the CoreAudioApi project from CodeProject to the solution
Add a project reference to CoreAudioApi in my console app
Create the following classes:
interface IAudio { void SetVolume(float level); }
class XpAudio : IAudio {
public void SetVolume(float level) {
// I do nothing, but this is where your old-style code would go
}
}
class VistaAudio : IAudio {
public void SetVolume(float level) {
MMDeviceEnumerator devEnum = new MMDeviceEnumerator();
MMDevice defaultDevice = devEnum
.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
defaultDevice.AudioEndpointVolume.MasterVolumeLevel = level;
}
}
class Program {
static void Main(string[] args) {
IAudio setter = Environment.OSVersion.Version.Major >= 6
? (IAudio)new VistaAudio()
: (IAudio)new XpAudio();
float val = float.Parse(Console.ReadLine());
setter.SetVolume(val);
Console.ReadLine();
}
}
This runs on both my server (~ Windows 7) and local (Windows XP) machines. On my XP machine it'll happily take in a value and ignore it; on my server, it throws an exception, (presumably because I don't have a sound output). If I make my XP machine run the CoreAudioApi, I get an exception when I input a value, not before.
The question is, what are you doing differently to make your application break? Are you using CoreAudioApi code at startup?
EDIT: After seeing your edit, if you do this, you shouldn't need to mess about with Assembly.LoadFrom at all. The framework should dynamically load that assembly if (and only if) and when it needs to.
COREAUDIOAPI.dll does not work on XP or earlier, because they cant handle MMDEVICE API (Device Enumeration). I dont know about Vista.

Categories