.NET8反射EntryPoint入口原理

💡 原文中文,约8700字,阅读约需21分钟。
📝

内容提要

本文介绍了通过EntryPoint反射调用Main函数的过程。CLR获取IL二进制代码并调用JIT编译是EntryPoint调用Main的关键。通过示例代码演示了如何使用Assembly.Load和MethodInfo.Invoke来实现EntryPoint调用Main函数。文章还介绍了一些相关的函数和代码。

🔎

延伸解读

反射入口与普通反射的异同

文章指出,通过EntryPoint反射调用Main函数与普通反射在本质上没有区别,但关键差异在于反射的目标是托管程序的固定入口Main,而非任意方法。这意味着反射机制同样需要获取IL二进制代码并触发JIT编译,只是入口点由CLR在加载程序集时确定。理解这一点有助于将反射知识迁移到入口点调用场景。

Assembly.Load如何构建入口点要素

Assembly.Load接收字节数组后,会创建IndividualAssemblyLoadContext并调用InternalLoad,将二进制数据传入非托管层。非托管函数AssemblyNative_LoadFromStream会构建PEImage并验证IL格式,最终生成程序集对象。这一过程填充了EntryPoint等内部字段,使得后续可以通过asm.EntryPoint直接获取入口方法信息。

Invoke调用链与JIT编译的触发

MethodInfo.Invoke最终会调用InvokeDirectByRefWithFewArgs,该函数填充参数并调用非托管的RuntimeMethodHandle::InvokeMethod。在这个非托管方法中,CLR会触发JIT编译,将Main函数的IL转换为机器码并执行。因此,反射调用入口点的核心步骤之一就是通过JIT将IL编译为可执行代码。

❓

Q&A

EntryPoint反射调用Main函数的关键步骤是什么?

关键步骤有两个:一是CLR获取IL的二进制代码,二是通过相应的函数IL二进制代码调用JIT进行编译。

如何通过反射调用另一个程序的Main方法?

可以使用Assembly.Load加载目标DLL的字节数组,然后通过asm.EntryPoint获取MethodInfo,最后调用Invoke方法并传入参数。

Assembly.Load方法内部是如何处理字节数组的?

Assembly.Load会创建一个IndividualAssemblyLoadContext,然后调用InternalLoad方法,将字节数组和长度传入非托管代码进行处理。

InternalLoad方法的作用是什么?

InternalLoad方法将托管DLL的二进制和长度传入非托管代码,通过LoadFromStream调用Qcall的AssemblyNative_LoadFromStream。

AssemblyNative_LoadFromStream主要完成了哪些工作?

它主要构建程序的IL镜像pILImage和托管DLL的程序集pLoadedAssembly,并填充内部字段如EntryPoint。

Invoke方法最终是如何调用到Main函数的?

Invoke方法会调用InvokeWithOneArg,进而调用InvokeDirectByRefWithFewArgs,最终通过RuntimeMethodHandle::InvokeMethod调用JIT编译并运行Main函数。

🏷️

标签

➡️

继续阅读