Определять исходный язык во время выполнения из визуализатора отладки - программирование

Определять исходный язык во время выполнения из визуализатора отладки

Я пишу визуализатор отладки для Visual Studio, который отображает деревья выражений в псевдокод С# или VB.NET.

Я хочу, чтобы язык рендеринга по умолчанию соответствовал языку текущего окна отладки.

Также я хочу добавить функциональность для генерации выражения Watch для данного узла в дереве выражений; для этого необходимо знать, какой язык в настоящее время отлаживается.

Как определить из кода визуализатора, какой язык в настоящее время отлаживается?

(Я предполагаю, что это невозможно из произвольного кода времени выполнения, так как код компилируется в IL.)

(Вопрос проекта)

4b9b3361

Ответ 1

Благодаря @dymanoid и @Homer Jay окончательная версия:

public enum SourceLanguage
{
    Unknown, // probably C# for it 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;
    }
}

Использование:

Если у вас есть типы CSType, VBType, FSType и CppType созданные с C#, VB.NET, F# и C++/CLI соответственно

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()]}");
    }
}

результаты в

Entry assembly source language: C#
CSType source language: C#
VBType source language: VB.NET
FSType source language: F#
CppType source language: C++/CLI

Для более подробной информации см. Правки.

Ответ 2

Visual Studio Custom Visualizer API довольно лаконичен. Он не предоставляет никакой возможности получить дополнительную информацию об отлаживаемом коде.

Действительно, вы можете пойти по пути, предложенному @Alex. Однако есть некоторые недостатки. Например, вам может понадобиться вручную загрузить отлаживаемую сборку (во временный AppDomain). Кроме того, этот способ не совсем надежен, поскольку он позволяет сборке, реализованной на С#, ссылаться на сборку Microsoft.VisualBasic.

Для более общего и безопасного подхода вы можете использовать Visual Studio Debugger API.

Вот идея, как это реализовать:

  • IDebugEventCallback2 интерфейс IDebugEventCallback2; отладчик Visual Studio уведомит ваш код с IDebugEventCallback2.Event метода IDebugEventCallback2.Event.
  • Получить экземпляр IVsDebugger.
  • Вызовите метод IVsDebugger.AdviseDebugEventCallback предоставив вашу реализацию IDebugEventCallback2 в качестве приемника событий.
  • В вашем методе Event проверьте наличие IDebugBreakpointEvent2 - это когда отлаживаемый процесс достигает точки IDebugBreakpointEvent2; вам может понадобиться использовать QueryInterface чтобы проверить это - см. Примечание для абонентов.
  • При достижении точки IDebugThread2 используйте предоставленный экземпляр IDebugThread2 чтобы получить объект IEnumDebugFrameInfo2, см. IDebugThread2.EnumFrameInfo.
  • Вызовите метод IEnumDebugFrameInfo2.Next чтобы получить структуру FRAMEINFO.
  • Из этой структуры получите экземпляр IDebugStackFrame2 (поле m_pFrame) и используйте его метод GetLanguageInfo чтобы получить язык, реализующий код в текущей точке GetLanguageInfo.

Долгий путь, да. Но определенно (может быть, только) надежный.


Обновить

Несмотря на то, что API отладчика Visual Studio, упомянутый выше, выглядит как собственный код, его можно легко использовать и в управляемом коде. Этот API основан на COM, и платформа .NET полностью его поддерживает. Вы можете установить Visual Studio SDK или просто использовать соответствующие пакеты NuGet, например Microsoft.VisualStudio.Debugger.Interop. Добавляя эти пакеты, вы получаете управляемые оболочки для типов COM и можете использовать их в своем коде, как если бы они были чисто управляемыми типами С#.

Для документации, посмотрите на расширяемость отладчика Visual Studio. Вы также можете ознакомиться с примерами Visual Studio SDK на GitHub.

Ответ 3

Нужно учитывать, что есть другая возможность. Сборка может быть написана в C++/CLI.

Но это интересно просто. Это указано в манифесте. Все остальное в манифесте C++/CLI совпадает с версией С#.

// Metadata version: v4.0.30319 (C++/CLI)
.assembly extern mscorlib
{
...
}
...