gpt4 book ai didi

c# - 以编程方式将调试器附加到在另一个应用程序域中运行的代码

转载 作者:行者123 更新时间:2023-12-03 06:00:49 25 4
gpt4 key购买 nike

我正在开发一个 Visual Studio 扩展,其中一个函数创建一个新的应用程序域并将程序集加载到该应用程序域中。然后它在应用程序域中运行一些功能。我想做的,但不确定是否可行,是让我的扩展程序将调试器附加到在新应用程序域中运行的代码,这样当代码失败时,我实际上可以看到发生了什么。现在我正在盲目飞行,调试动态加载的程序集很痛苦。

所以我有一个类可以创建我的应用程序域,如下所示:

domain = AppDomain.CreateDomain("Test_AppDomain", 
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);

然后创建一个像这样的对象:
myCollection = domain.CreateInstanceAndUnwrap(
typeof(MyCollection).Assembly.FullName,
typeof(MyCollection).FullName,
false,
BindingFlags.Default,
null,
new object[] { assemblyPath }, null, null);
MyCollection在它的构造函数中做这样的事情:
_assembly = Assembly.LoadFrom(assemblyPath);

因此,现在该程序集已加载到 Test_AppDomain自从 MyCollection对象是在该域中创建的。我需要能够将调试器附加到的加载程序集。

在某个时候 myCollection创建一个对象的实例并连接一些事件:
currentObject = Activator.CreateInstance(objectType) as IObjectBase;
proxy.RunRequested += (o, e) => { currentObject?.Run(); };

基本上我有 RunRequested 的处理程序它运行 currentObject?.Run() ,我想附加一个调试器,尽管如果早点附加调试器,它可能不会成为问题(并且实际上可能工作得更好)。

那么有没有办法实现这一目标?当用户触发将导致 Run 的事件时,是否可以以编程方式附加调试器?在被调用的新 AppDomain 中创建的对象的函数?如何让调试器附加到它(而不是扩展本身)?

我试过这样的事情:
var processes = dte.Debugger.LocalProcesses.Cast<EnvDTE.Process>();
var currentProcess = System.Diagnostics.Process.GetCurrentProcess().Id;
var process = processes.FirstOrDefault(p => p.ProcessID == currentProcess);
process?.Attach();

但似乎 ID 来自 System.Diagnostics.Process.GetCurrentProcess().Id不存在于 LocalProcesses 中?

最佳答案

尽管您可能已经继续前进,但我发现这个问题非常有趣(并且与我一直在研究的内容有关 blog about )。所以我试了一下作为实验——我不确定你打算如何触发 Run()带有事件的方法(即使它对您的用例很重要),所以我选择了一个简单的方法调用。
注入(inject) Debugger.Launch()作为一个 PoC,我最终发出了一个派生类,并在将其传递给动态加载的方法之前注入(inject)了一个调试器启动调用:

public static object CreateWrapper(Type ServiceType, MethodInfo baseMethod)
{
var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"newAssembly_{Guid.NewGuid()}"), AssemblyBuilderAccess.Run);
var module = asmBuilder.DefineDynamicModule($"DynamicAssembly_{Guid.NewGuid()}");
var typeBuilder = module.DefineType($"DynamicType_{Guid.NewGuid()}", TypeAttributes.Public, ServiceType);
var methodBuilder = typeBuilder.DefineMethod("Run", MethodAttributes.Public | MethodAttributes.NewSlot);

var ilGenerator = methodBuilder.GetILGenerator();

ilGenerator.EmitCall(OpCodes.Call, typeof(Debugger).GetMethod("Launch", BindingFlags.Static | BindingFlags.Public), null);
ilGenerator.Emit(OpCodes.Pop);

ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Call, baseMethod, null);
ilGenerator.Emit(OpCodes.Ret);

/*
* the generated method would be roughly equivalent to:
* new void Run()
* {
* Debugger.Launch();
* base.Run();
* }
*/

var wrapperType = typeBuilder.CreateType();
return Activator.CreateInstance(wrapperType);
}
触发方法
为加载的方法创建一个包装器似乎就像定义一个动态类型并从目标类中选择一个正确的方法一样简单:
var wrappedInstance = DebuggerWrapperGenerator.CreateWrapper(ServiceType, ServiceType.GetMethod("Run"));
wrappedInstance.GetType().GetMethod("Run")?.Invoke(wrappedInstance, null);
继续使用 AppDomain
上面的代码似乎不太关心代码将在哪里运行,但是在试验时我发现我能够确保代码是正确的 AppDomain通过利用 .DoCallBack() 或确保我的 Launcher 助手是使用 .CreateInstanceAndUnwrap() 创建的:
public class Program
{
const string PathToDll = @"..\..\..\ClassLibrary1\bin\Debug\ClassLibrary1.dll";

static void Main(string[] args)
{
var appDomain = AppDomain.CreateDomain("AppDomainInMain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);

appDomain.DoCallBack(() =>
{
var launcher = new Launcher(PathToDll);
launcher.Run();
});
}
}
public class Program
{
const string PathToDll = @"..\..\..\ClassLibrary1\bin\Debug\ClassLibrary1.dll";

static void Main(string[] args)
{
Launcher.RunInNewAppDomain(PathToDll);
}
}
public class Launcher : MarshalByRefObject
{
private Type ServiceType { get; }

public Launcher(string pathToDll)
{
var assembly = Assembly.LoadFrom(pathToDll);
ServiceType = assembly.GetTypes().SingleOrDefault(t => t.Name == "Class1");
}

public void Run()
{
var wrappedInstance = DebuggerWrapperGenerator.CreateWrapper(ServiceType, ServiceType.GetMethod("Run"));
wrappedInstance.GetType().GetMethod("Run")?.Invoke(wrappedInstance, null);
}

public static void RunInNewAppDomain(string pathToDll)
{
var appDomain = AppDomain.CreateDomain("AppDomainInLauncher", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);

var launcher = appDomain.CreateInstanceAndUnwrap(typeof(Launcher).Assembly.FullName, typeof(Launcher).FullName, false, BindingFlags.Public|BindingFlags.Instance,
null, new object[] { pathToDll }, CultureInfo.CurrentCulture, null);
(launcher as Launcher)?.Run();

}
}

关于c# - 以编程方式将调试器附加到在另一个应用程序域中运行的代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39877549/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com