C#内存加载dll和EXE是不是差不多,主要是EXE有入口点?
是的!在 C# 中,内存加载 DLL 和 EXE(仅限 .NET 托管程序集)的核心逻辑几乎完全一致,唯一的关键差异确实集中在「EXE 有入口点(EntryPoint)」,但本质上二者都是 CLR 可加载的托管程序集,加载和反射调用的底层规则完全相同。
下面我们从「核心共性」「关键差异」「代码示例」三个维度讲清楚,帮你彻底理解:
一、核心共性(加载 DLL/EXE 完全一致)
无论加载的是 .NET DLL 还是 EXE,只要是托管程序集,内存加载的底层逻辑、反射调用方法的规则完全相同:
- 加载方式一致:都通过
Assembly.Load(byte[])从内存字节数组加载(而非LoadFile/LoadFrom读文件),避免磁盘依赖; - 反射调用一致:加载后都通过
Type.GetMethod/MethodInfo.Invoke调用方法(无论是否静态、有无参数 / 返回值); - 依赖处理一致:若程序集依赖其他 DLL/EXE,都需通过
AppDomain.CurrentDomain.AssemblyResolve事件处理内存加载依赖; - 类型访问一致:都能访问程序集中的公开类、方法、属性(只要权限允许)。
简单说:CLR 并不区分「DLL」和「EXE」扩展名,只认「是否是符合 .NET 规范的托管程序集」—— EXE 只是「带入口点的 DLL」,DLL 是「无入口点的 EXE」。
二、关键差异(仅 2 点,核心是入口点)
| 维度 | .NET DLL | .NET EXE |
|---|---|---|
| 入口点(EntryPoint) | 无默认入口点(除非手动定义) | 必有入口点(Main方法,可通过Assembly.EntryPoint获取) |
| 编译 / 运行特性 | 无法直接独立运行(需宿主程序) | 可直接双击运行(CLR 自动调用入口点) |
| 反射调用重点 | 找「自定义业务方法」(如DoWork()) | 可调用「入口点Main」或「自定义业务方法」 |
补充:EXE 的入口点也可以是无参 / 有参、有返回值 / 无返回值(如
int Main(string[] args)),和 DLL 自定义方法的调用规则完全一致。
三、C# 代码示例(对比 DLL/EXE 内存加载)
1. 通用内存加载方法(DLL/EXE 共用)csharp
using System; using System.Reflection; using System.IO; public static class AssemblyLoader { // 核心:从字节数组加载托管程序集(DLL/EXE 通用) public static Assembly LoadFromMemory(byte[] assemblyBytes) { // 内存加载,不落地磁盘(区别于 LoadFile/LoadFrom) return Assembly.Load(assemblyBytes); } // 辅助:从文件读取字节(实际可从网络/加密存储获取) public static byte[] GetAssemblyBytes(string filePath) { return File.ReadAllBytes(filePath); } }2. 内存加载 DLL(调用自定义方法)
假设 DLL 中有如下代码:
// 被加载的 DLL 代码 namespace MyDll { public class Calculator { // 自定义有返回值方法 public static int Add(int a, int b) { return a + b; } } }内存加载并调用:
csharp
运行
public static void CallDllFromMemory() { try { // 1. 读取 DLL 字节(内存加载核心) byte[] dllBytes = AssemblyLoader.GetAssemblyBytes(@"C:\MyDll.dll"); Assembly assembly = AssemblyLoader.LoadFromMemory(dllBytes); // 2. 找 DLL 中的类型和方法(重点:自定义业务方法) Type calcType = assembly.GetType("MyDll.Calculator"); if (calcType == null) throw new Exception("未找到 Calculator 类型"); MethodInfo addMethod = calcType.GetMethod("Add", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(int), typeof(int) }, null); // 3. 调用方法并获取返回值 object result = addMethod.Invoke(null, new object[] { 100, 200 }); Console.WriteLine($"DLL 方法返回值:{result}"); // 输出 300 } catch (Exception ex) { Console.WriteLine("DLL 调用失败:" + ex); } }3. 内存加载 EXE(调用入口点 + 自定义方法)假设 EXE 中有如下代码:
// 被加载的 EXE 代码 namespace MyExe { class Program { // EXE 入口点(有参数、有返回值) static int Main(string[] args) { return args?.Length ?? -1; // 返回参数个数,无参数返回 -1 } } // EXE 中的自定义公开方法(和 DLL 一样可调用) public class ExeHelper { public static string GetInfo() { return "EXE 自定义方法:" + DateTime.Now; } } }内存加载并调用:
public static void CallExeFromMemory() { try { // 1. 读取 EXE 字节(和 DLL 加载逻辑完全一致) byte[] exeBytes = AssemblyLoader.GetAssemblyBytes(@"C:\MyExe.exe"); Assembly assembly = AssemblyLoader.LoadFromMemory(exeBytes); // ========== 场景1:调用 EXE 入口点(Main) ========== MethodInfo entryMethod = assembly.EntryPoint; // 核心:获取 EXE 入口点 if (entryMethod == null) throw new Exception("未找到 EXE 入口点"); // 调用入口点并获取返回值(和 DLL 方法调用规则一致) object entryResult = entryMethod.Invoke(null, new object[] { new string[] { "param1", "param2" } }); Console.WriteLine($"EXE 入口点返回值:{entryResult}"); // 输出 2 // ========== 场景2:调用 EXE 自定义方法(和 DLL 完全一致) ========== Type helperType = assembly.GetType("MyExe.ExeHelper"); MethodInfo infoMethod = helperType.GetMethod("GetInfo"); object infoResult = infoMethod.Invoke(null, null); Console.WriteLine(infoResult); // 输出 EXE 自定义方法:2025-xx-xx... } catch (Exception ex) { Console.WriteLine("EXE 调用失败:" + ex); } }四、关键总结
- 核心结论:C# 内存加载托管 DLL/EXE 几乎无差异,唯一区别是 EXE 多了「入口点
Main」,但调用入口点的逻辑和调用 DLL 自定义方法完全一致; - 本质逻辑:CLR 加载的是「托管程序集」,扩展名(DLL/EXE)仅影响「是否可独立运行」,不影响内存加载和反射调用;
- 注意事项:
- 仅适用于 .NET 托管程序集(非托管 DLL/EXE 需用
LoadLibrary/P/Invoke); - EXE 调用入口点时,需匹配
Main的参数 / 返回值类型(如string[] args、int返回值); - 依赖处理:若 EXE/DLL 依赖其他程序集,需通过
AssemblyResolve事件内存加载依赖项。
- 仅适用于 .NET 托管程序集(非托管 DLL/EXE 需用
简单记:EXE = DLL + 入口点,内存加载和反射调用的核心逻辑完全复用。