I am writing a debugging visualizer for Visual Studio that renders expression trees into C# or VB.NET pseudo-code.
I want that the default rendering language should match that of the current debugging window.
Also, I want to add functionality to generate a Watch expression for a given node in the expression tree; this requires knowing which language is currently being debugged.
How can I detect from within the visualizer code which language is currently being debugged?
(I"m assuming this isn't possible from arbitrary runtime code, as the code is compiled down to IL.)
(Project issue)
Enable “Debugging” for Debugging But to have better experience you can enable diagnostics. Go to Tools -> Options -> Debugging -> Output Window : Now, to test your new visualiser you can set a breakpoint just before your type appears in a debugging session.
Begin code stepping by selecting F10 or F11. Doing so allows you to quickly find the entry point of your app. You can then continue to press step commands to navigate through the code. Run to a specific location or function, for example, by setting a breakpoint and starting your app.
In the Visual Studio toolbar, make sure the configuration is set to Debug. To start debugging, select the profile name in the toolbar, such as <project profile name>, IIS Express, or <IIS profile name> in the toolbar, select Start Debugging from the Debug menu, or press F5.
Thanks to @dymanoid and @Homer Jay final version is:
public enum SourceLanguage
{
Unknown, // probably C# for it's the only language without any particular symptom
VB,
FSharp,
Cpp
}
static class Extensions
{
private static readonly Dictionary<Assembly, SourceLanguage> cache = new Dictionary<Assembly, SourceLanguage>();
public static SourceLanguage GetSourceLanguage(this Type type) => type.Assembly.GetSourceLanguage();
public static SourceLanguage GetSourceLanguage(this Assembly assembly)
{
if (cache.TryGetValue(assembly, out var sourceLanguage))
return sourceLanguage;
var name = assembly.GetName().Name;
var resources = assembly.GetManifestResourceNames();
var assemblies = assembly.GetReferencedAssemblies().Select(a => a.Name);
var types = assembly.DefinedTypes;
if (assemblies.Contains("Microsoft.VisualBasic") &&
resources.Contains($"{name}.Resources.resources"))
sourceLanguage = SourceLanguage.VB;
else if (assemblies.Contains("FSharp.Core") &&
resources.Contains($"FSharpSignatureData.{name}") &&
resources.Contains($"FSharpOptimizationData.{name}"))
sourceLanguage = SourceLanguage.FSharp;
else if (types.Any(t => t.FullName.Contains("<CppImplementationDetails>.")))
sourceLanguage = SourceLanguage.Cpp;
else
sourceLanguage = SourceLanguage.Unknown;
cache[assembly] = sourceLanguage;
return sourceLanguage;
}
}
Usage:
If one has types CSType
, VBType
, FSType
and CppType
created with C#
, VB.NET
, F#
and C++/CLI
respectively
class Program
{
static readonly Dictionary<SourceLanguage, string> dict = new Dictionary<SourceLanguage, string>()
{
[SourceLanguage.Unknown] = "C#",
[SourceLanguage.VB] = "VB.NET",
[SourceLanguage.FSharp] = "F#",
[SourceLanguage.Cpp] = "C++/CLI"
};
static void Main(string[] args)
{
Console.WriteLine(string.Format($"Entry assembly source language: {dict[Assembly.GetEntryAssembly().GetSourceLanguage()]}"));
foreach (var t in new[] { typeof(CSType), typeof(VBType), typeof(FSType), typeof(CppType) })
Console.WriteLine($"{t.Name} source language: {dict[t.GetSourceLanguage()]}");
}
}
results into
Entry assembly source language: C#
CSType source language: C#
VBType source language: VB.NET
FSType source language: F#
CppType source language: C++/CLI
For more details see edits.
The Visual Studio Custom Visualizer API is pretty concise. It doesn't provide any way to get additional information about the code being debugged.
Indeed, you could go the way proposed by @Alex. However, there are some drawbacks. E.g. you might need to manually load the assembly being debugged (into a temporary AppDomain
). Furthermore, this way is not really reliable because it's perfectly allowed for an assembly implemented in C# to reference the Microsoft.VisualBasic
assembly.
For a more generic and safe approach, you could use the Visual Studio Debugger API.
Here is an idea how to implement this:
IDebugEventCallback2
interface; the Visual Studio Debugger will notify your code using the IDebugEventCallback2.Event
method.IVsDebugger
instance.IVsDebugger.AdviseDebugEventCallback
method providing your IDebugEventCallback2
implementation as an event sink.Event
method, check for IDebugBreakpointEvent2
- this is when the process being debugged hits a break point; you might need to use QueryInterface
to check that - see Note for Callers.IDebugThread2
instance to obtain an IEnumDebugFrameInfo2
object, see IDebugThread2.EnumFrameInfo
.IEnumDebugFrameInfo2.Next
method to get a FRAMEINFO
structure.IDebugStackFrame2
instance ( the m_pFrame
field) and use its GetLanguageInfo
method to obtain the language that implements the code at the current break point.A long way, yes. But definitely the (maybe only) reliable one.
Despite the Visual Studio Debugger API referenced above looks like native code, you can easily use it in managed code too. This API is based on COM, and the .NET platform fully supports it. You can either install the Visual Studio SDK or just use the corresponding NuGet packages, e.g. Microsoft.VisualStudio.Debugger.Interop. By adding those packages, you obtain the managed wrappers for the COM types and can use them in your code as if they were pure managed C# types.
For the documentation, take a look on Visual Studio debugger extensibility. You can also check out the Visual Studio SDK samples on GitHub.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With