Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect source language at runtime from within debugging visualizer

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)

like image 932
Zev Spitz Avatar asked May 02 '19 13:05

Zev Spitz


People also ask

How do I Debug Natvis?

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.

How can I see the flow of code in Visual Studio?

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.

How do I enable debugging in Visual Studio?

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.


Video Answer


2 Answers

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.

like image 82
Alex Avatar answered Oct 25 '22 07:10

Alex


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:

  • Implement the IDebugEventCallback2 interface; the Visual Studio Debugger will notify your code using the IDebugEventCallback2.Event method.
  • Obtain an IVsDebugger instance.
  • Call the IVsDebugger.AdviseDebugEventCallback method providing your IDebugEventCallback2 implementation as an event sink.
  • In your 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.
  • When a break point is hit, use the provided IDebugThread2 instance to obtain an IEnumDebugFrameInfo2 object, see IDebugThread2.EnumFrameInfo.
  • Call the IEnumDebugFrameInfo2.Next method to get a FRAMEINFO structure.
  • From that structure, obtain an 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.


Update

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.

like image 5
dymanoid Avatar answered Oct 25 '22 09:10

dymanoid