C# 运行时 DLL 加载和 ref 参数

C# Runtime DLL loading and ref parameters

简而言之,我希望在运行时加载一个.DLL文件,并要求它修改我作为参数传递给它的ref值。 (.DLL 将用 C# 编写)但我不知道合适的技术是什么。

我有一个 class "ALotOfData",其中包含大约 1 GB 的变量。

我想动态加载一个包含方法的 .DLL 文件

"DoWork(ref ALotOfData thedata){ thedata.value = ... }

然后执行方法,然后卸载DLL,对另一个DLL做同样的事情。 (在 运行 时能够 load/unload DLL 非常重要)

显然,一个解决方案是传递值本身的副本,return 修改后的副本,然后将其放回我的 class。

不过,这是不可能的,如果是DLL文件就得做决定了。根据数据,修改哪些数据(考虑:它可能需要访问所有数据)。

仅仅复制整个数据包是......一个绝对可怕的想法,因为整个数据大约有 1 GB 大。

我怎样才能动态地(在 运行 时间)从 .DLL 导入一个方法并通过 ref 向它传递一个参数,我的意思是,实际上传递一个引用,而不仅仅是复制它? (非常重要的是将 ref 传递给 class,无需复制)

伪代码可能有助于解释我的观点:

class ALotOfData{ ... } // ~ about 1GB of initialized values inside

Main(){
DLL = loadDLL("mydll.dll");
DLL.Invoke("DoWork",ref AlotOfData); // This needs to actually change my class's contents
DLL.unload();
}

dll 内部:

DoWork(ref ALotOfData data){
    if(data.value){
        foreach( value A in data.value ){ ... } // ~100 iterations
    }
}

我可以在我的主程序中添加这个决策,但这会破坏能够 load/unload DLL 文件的目的。

您可能来自 C# 以外的其他语言。

ref 关键字与您认为的不同。 class 总是 通过引用传递。如果您只是完全删除 ref 关键字,您的代码就可以工作。

现在动态加载和卸载代码是使用 Reflection. You should probably start out from Assembly.LoadFrom. However, you cannot unload an Assembly. Once loaded, it's there. What you can do is use multiple AppDomains 完成的,并删除那些包含您不需要的 dll 的代码。

你唯一需要用 ref 传递 class 的情况如下:

void DoWork(ref ALotOfData data)
{
    data = SomethingElse(); // or
    data = new ALotOfData();
}

如果您不将该对象替换为另一个对象,而只是访问它的 properties/methods,那么您不需要引用。

正如其他人所提到的,您将需要使用多个 AppDomain 来 load/unload 程序集。您需要确保您的 ALotOfData class 和从 properties/methods 返回的任何对象继承自 MarshalByRefObject,这样它们就不会跨应用域复制。

在运行时加载程序集非常简单:

Assembly.LoadFrom("mydll.dll");

遗憾的是,无法卸载程序集。相反,您必须卸载程序集加载到的整个 AppDomain。通常,您只有一个包含所有 运行 代码的 AppDomain;它会在您的应用程序退出时卸载。

如果您希望能够在 运行 时卸载程序集,您必须创建第二个 AppDomain 并在那里加载程序集:

// Create a new domain with the dynamic assembly.
var domain = AppDomain.Create("Dynamic Assembly Domain");
domain.Load("mydll.dll");

// Do some work with the dynamic domain...

// Unload the domain.
AppDomain.Unload(domain);

这里的麻烦是AppDomains之间有一个边界,必须跨越这个边界才能与每个域中的对象进行通信。

问题(您似乎已经意识到)是域间通信的默认机制是创建对象的副本。制作一个完整的克隆并传递到另一个域,当您处理大量信息时,这不适合。

这道题的答案是MarshalByRefObject:

MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy. Objects that do not inherit from MarshalByRefObject are implicitly marshal by value. When a remote application references a marshal by value object, a copy of the object is passed across application domain boundaries.

MarshalByRefObject objects are accessed directly within the boundaries of the local application domain. The first time an application in a remote application domain accesses a MarshalByRefObject, a proxy is passed to the remote application. Subsequent calls on the proxy are marshaled back to the object residing in the local application domain.

因此,为了在不创建批量副本的情况下在域之间传递数据,提供数据的 class 需要继承自 MarshalByRefObject。您还应该创建一个可以在远程域中加载的类型,它也继承自 MarshalByRefObject,因此本地域具有远程域的代理:

// Load your 1GB of data.
var data = ALotOfData.Load();

// Create a new domain with the dynamic assembly.
var domain = AppDomain.Create("Dynamic Assembly Domain");
domain.Load("primary.dll"); // Assembly containing your primary code.
domain.Load("mydll.dll");

// Create the remote proxy.
var remote = domine.CreateInstanceAndUnwrap("primary", "primary.RemoteControl");

// Invoke the logic in the loaded dll.
remote.Execute("mydll", "mydll.DLL", data);

// Unload the domain.
AppDomain.Unload(domain);