gpt4 book ai didi

c# - 如何即时编译、加载和使用 C# DLL

转载 作者:太空狗 更新时间:2023-10-30 00:15:20 26 4
gpt4 key购买 nike

A) 动态编译 C# EXE 和 DLL 相对容易。
B) 执行一个 EXE 意味着运行一个新的应用程序。加载 DLL 意味着可以在应用程序或项目之间共享的情况下使用方法和函数。

现在,可以从 MSDN 中找到编译 EXE(或稍作修改后的 DLL)的最快最简单的方法。或者为了您的方便:

private bool CompileCSharpCode(string script)
{
lvErrors.Items.Clear();
try
{
CSharpCodeProvider provider = new CSharpCodeProvider();
// Build the parameters for source compilation.
CompilerParameters cp = new CompilerParameters
{
GenerateInMemory = false,
GenerateExecutable = false, // True = EXE, False = DLL
IncludeDebugInformation = true,
OutputAssembly = "eventHandler.dll", // Compilation name
};

// Add in our included libs.
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");
cp.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll");

// Invoke compilation. This works from a string, but you can also load from a file using FromFile()
CompilerResults cr = provider.CompileAssemblyFromSource(cp, script);
if (cr.Errors.Count > 0)
{
// Display compilation errors.
foreach (CompilerError ce in cr.Errors)
{
//I have a listview to display errors.
lvErrors.Items.Add(ce.ToString());
}
return false;
}
else
{
lvErrors.Items.Add("Compiled Successfully.");
}
provider.Dispose();
}
catch (Exception e)
{
// never really reached, but better safe than sorry?
lvErrors.Items.Add("SEVERE! "+e.Message + e.StackTrace.ToString());
return false;
}
return true;
}

既然您可以即时编译,那么加载 DLL 的方式就会有一些差异。通常来说,您会将其添加为 Visual Studios 中的引用以编译到项目中。这很容易,你可能已经重复了很多次,但我们想在我们当前的项目中使用它,我们不能很好地要求用户每次想要测试他们的新 DLL 时都重新编译整个项目.因此,我将简单地讨论如何“即时”加载库。这里的另一个术语是“以编程方式”。为此,在成功编译后,我们按如下方式加载程序集:

Assembly assembly = Assembly.LoadFrom("yourfilenamehere.dll");

如果你有一个 AppDomain,你可以试试这个:

Assembly assembly = domain.Load(AssemblyName.GetAssemblyName("yourfilenamehere.dll"));


现在 lib 已被“引用”,我们可以打开它并使用它。有两种方法可以做到这一点。一个要求您知道该方法是否有参数,另一个会为您检查。我稍后做,你可以查看MSDN对于另一个。

// replace with your namespace.class
Type type = assembly.GetType("company.project");
if (type != null)
{
// replace with your function's name
MethodInfo method = type.GetMethod("method");

if (method != null)
{
object result = null;
ParameterInfo[] parameters = method.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
if (parameters.Length == 0) // takes no parameters
{
// method A:
result = method.Invoke(classInstance, null);
// method B:
//result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, null);
}
else // takes 1+ parameters
{
object[] parametersArray = new object[] { }; // add parameters here

// method A:
result = method.Invoke(classInstance, parametersArray);
// method B:
//result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, parametersArray);
}
}
}

问题:首先编译工作正常。第一次执行工作正常。但是,重新编译尝试会出错,提示您的 *.PDP(调试器数据库)正在使用中。我听说过一些关于编码和 AppDomains 的提示,但我还没有完全解决这个问题。只有在加载 DLL 后,重新编译才会失败。


当前尝试编码 && AppDomain:

class ProxyDomain : MarshalByRefObject
{
private object _instance;
public object Instance
{
get { return _instance; }
}
private AppDomain _domain;
public AppDomain Domain
{
get
{
return _domain;
}
}
public void CreateDomain(string friendlyName, System.Security.Policy.Evidence securityinfo)
{
_domain = AppDomain.CreateDomain(friendlyName, securityinfo);
}
public void UnloadDomain()
{
try
{
AppDomain.Unload(_domain);
}
catch (ArgumentNullException dne)
{
// ignore null exceptions
return;
}
}
private Assembly _assembly;
public Assembly Assembly
{
get
{
return _assembly;
}
}
private byte[] loadFile(string filename)
{
FileStream fs = new FileStream(filename, FileMode.Open);
byte[] buffer = new byte[(int)fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();

return buffer;
}
public void LoadAssembly(string path, string typeName)
{
try
{
if (_domain == null)
throw new ArgumentNullException("_domain does not exist.");
byte[] Assembly_data = loadFile(path);
byte[] Symbol_data = loadFile(path.Replace(".dll", ".pdb"));

_assembly = _domain.Load(Assembly_data, Symbol_data);
//_assembly = _domain.Load(AssemblyName.GetAssemblyName(path));
_type = _assembly.GetType(typeName);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.ToString());
}
}
private Type _type;
public Type Type
{
get
{
return _type;
}
}
public void CreateInstanceAndUnwrap(string typeName)
{
_instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName, typeName);
}
}

_instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName, typeName) 上的错误;说我的程序集不可序列化。尝试将 [Serializable] 标签添加到我的类(class),但没有成功。仍在研究修复。

当您看不到它们的使用方式时,事情似乎会变得有点困惑,所以这里让一切变得简单?

private void pictureBox1_Click(object sender, EventArgs e)
{
pd.UnloadDomain();

if (CompileCSharpCode(header + tScript.Text + footer))
{
try
{
pd.CreateDomain("DLLDomain", null);
pd.LoadAssembly("eventHandler.dll", "Events.eventHandler");
pd.CreateInstanceAndUnwrap("Events.eventHandler"); // Assembly not Serializable error!

/*if (pd.type != null)
{
MethodInfo onConnect = pd.type.GetMethod("onConnect");

if (onConnect != null)
{
object result = null;
ParameterInfo[] parameters = onConnect.GetParameters();
object classInstance = Activator.CreateInstance(pd.type, null);
if (parameters.Length == 0)
{
result = pd.type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, null);
//result = onConnect.Invoke(classInstance, null);
}
else
{
object[] parametersArray = new object[] { };

//result = onConnect.Invoke(classInstance, parametersArray);
//result = type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, parametersArray);
}
}
}*/
//assembly = Assembly.LoadFrom(null);

}
catch (Exception er)
{
MessageBox.Show("There was an error executing the script.\n>" + er.Message + "\n - " + er.StackTrace.ToString());
}
finally
{
}
}
}

最佳答案

一旦您将 DLL 加载到正在运行的进程(的默认应用程序域)中,磁盘上的文件就无法被覆盖,直到该进程终止。 DLL 不能像在非托管代码中那样在托管代码中卸载。

您需要在宿主进程中创建一个新的应用程序域,并将新创建的 DLL 程序集加载到该应用程序域中。当您准备编译新版本的 DLL 时,您可以处理掉 appdomain。这将从内存中卸载 DLL 并释放对 DLL 文件的锁定,以便您可以将新的 DLL 编译到同一文件。然后,您可以构建一个新的应用程序域来加载新的 DLL。

使用 appdomains 的主要危险是所有跨越 appdomain 边界的调用都必须被编码,很像 IPC 或网络 RPC。尽量将需要跨应用域边界调用的对象的接口(interface)保持在最低限度。

您还可以将程序集编译到内存中,接收字节数组或流作为输出,然后将该程序集加载到单独的应用程序域中。这样可以避免在磁盘上产生最终需要删除的碎片。

不要使用编译到内存作为文件锁定问题的解决方法。核心问题是程序集加载到进程的默认应用程序域时无法从内存中删除。如果您想在进程生命周期的后期从内存中卸载该程序集,则必须创建一个新的应用程序域并将 DLL 加载到该应用程序域中。

以下是如何在另一个应用程序域的上下文中构造对象的粗略概述:

    var appdomain = AppDomain.CreateDomain("scratch");
byte[] assemblyBytes = // bytes of the compiled assembly
var assembly = appdomain.Load(assemblyBytes);
object obj = appdomain.CreateInstanceAndUnwrap(assembly.FullName, "mynamespace.myclass");

在此序列之后,obj 将包含对链接到应用域内实际对象实例的代理的引用。您可以使用反射调用 obj 上的方法,或将 obj 类型转换为通用接口(interface)类型并直接调用方法。准备好进行调整以支持方法调用参数的 RPC 编码。 (请参阅 .NET 远程处理)

当使用多个应用程序域时,您必须小心访问类型和程序集的方式,因为许多 .NET 函数默认在调用者的当前应用程序域中运行,当您有多个应用程序域时,这通常不是您想要的.例如,compilerResult.CompiledAssembly调用者的应用程序域 内部执行生成程序集的加载。您想要的是将程序集加载到您的其他应用程序域中。你必须明确地这样做。

更新:在您最近添加的显示您如何加载应用程序域的代码片段中,这一行是您的问题:

 _assembly = Assembly.LoadFrom(path);

这会将 DLL 加载到当前 appdomain(调用者的 appdomain),而不是目标 appdomain(在您的示例中由 _domain 引用)。您需要使用 _domain.Load() 将程序集加载到那个应用程序域。

关于c# - 如何即时编译、加载和使用 C# DLL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15815363/

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