You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取加载在独立AppDomain中的C#程序集的内存基地址

获取独立AppDomain中加载程序集的内存基地址

首先,你遇到的问题根源在于:Process.GetCurrentProcess().Modules是进程级别的枚举,它只会列出被加载到默认AppDomain的托管程序集,或者作为原生模块加载的文件。而你在独立的Temporal AppDomain中通过Assembly.Load(buffer)加载的程序集,属于该AppDomain的私有加载上下文,不会被进程级的Modules集合包含。

下面是几种无需内存扫描就能获取目标程序集基地址的方法,都符合你的需求(允许不安全/非托管代码):

方法1:在目标AppDomain内直接获取并跨域传递

这是最直接且可靠的方式,因为你的Loader类已经是MarshalByRefObject,可以在目标AppDomain的上下文里执行代码并返回结果。

你只需要修改CallInternal方法,在加载程序集后直接读取它的模块基地址,然后把地址和方法执行结果一起返回:

首先定义一个可序列化的类来承载结果和基地址(跨AppDomain传递需要序列化支持):

[Serializable]
public class ExecutionResult
{
    public object MethodResult { get; set; }
    public IntPtr AssemblyBaseAddress { get; set; }
}

然后修改Loader的相关方法:

public class Loader : MarshalByRefObject
{
    ExecutionResult CallInternal(string dll, string method, string[] parameters)
    {
        byte[] buffer = File.ReadAllBytes(dll);
        Assembly a = Assembly.Load(buffer);
        IntPtr baseAddr = IntPtr.Zero;
        // 获取程序集的第一个模块基地址(托管程序集通常只有一个模块)
        if (a.Modules.Count > 0)
        {
            baseAddr = a.Modules[0].BaseAddress;
        }

        Type[] types = a.GetTypes();
        MethodInfo m = null;
        Type myType = null;
        foreach (var type in types)
        {
            m = type.GetMethod(method);
            if (m != null)
            {
                myType = type;
                break;
            }
        }

        object methodResult = null;
        if (m != null && myType != null)
        {
            var myInstance = Activator.CreateInstance(myType);
            methodResult = m.Invoke(myInstance, new object[] { parameters });
        }

        return new ExecutionResult
        {
            MethodResult = methodResult,
            AssemblyBaseAddress = baseAddr
        };
    }

    public static ExecutionResult Call(string dll, string method, params string[] parameters)
    {
        AppDomain dom = AppDomain.CreateDomain("Temporal");
        Loader ld = (Loader)dom.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName);
        ExecutionResult result = ld.CallInternal(dll, method, parameters);
        AppDomain.Unload(dom); // 记得用完卸载AppDomain,避免内存泄漏
        return result;
    }
}

这样调用Loader.Call后,就能直接拿到目标程序集的基地址,完全不需要内存扫描。

方法2:使用CLR非托管API(进阶)

如果你需要在默认AppDomain中直接获取其他AppDomain的加载信息,可以使用CLR的非托管API(比如ICorRuntimeHost或ICorProfilerInfo)来枚举所有AppDomain及其加载的程序集。不过这种方法需要编写非托管互操作代码,复杂度较高,不如方法1简洁。

举个简单的例子,通过ICorRuntimeHost获取所有AppDomain:

using System.Runtime.InteropServices;
using System.Collections;

[ComImport, Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICorRuntimeHost
{
    void _VtblGap1_1();
    void EnumDomains(out IntPtr enumHandle);
    void NextDomain(IntPtr enumHandle, out AppDomain appDomain);
    void CloseEnum(IntPtr enumHandle);
}

public static AppDomain[] GetAllAppDomains()
{
    ArrayList domains = new ArrayList();
    ICorRuntimeHost host = (ICorRuntimeHost)RuntimeEnvironment.GetRuntimeInterfaceAsObject(null, typeof(ICorRuntimeHost).GUID);
    IntPtr enumHandle;
    host.EnumDomains(out enumHandle);
    AppDomain domain;
    while (true)
    {
        host.NextDomain(enumHandle, out domain);
        if (domain == null) break;
        domains.Add(domain);
    }
    host.CloseEnum(enumHandle);
    return (AppDomain[])domains.ToArray(typeof(AppDomain));
}

拿到目标AppDomain后,你还需要进一步枚举它加载的程序集,再获取模块基地址。但这种方法同样需要在目标AppDomain的上下文里执行代码(因为Assembly对象是AppDomain特定的),本质上和方法1没有太大区别,只是多了一层非托管调用的复杂度。

注意事项

  • 托管程序集的Module.BaseAddress返回的是该模块在内存中的加载基址,对于动态加载的程序集(通过Assembly.Load(byte[])),这个地址是CLR分配的内存区域。
  • 记得在使用完独立AppDomain后调用AppDomain.Unload(dom),避免内存泄漏。
  • 你的原始Loader.Call方法里有个参数错误:ld.CallInternal(dll, typename, method, parameters)中的typename未定义,应该是笔误,我在修改后的代码里已经修正了。

内容的提问来源于stack exchange,提问作者Kurosh D.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 10:29:06