<?xml version="1.0" encoding="utf-8" ?><rss version="2.0"><channel><title>DongPad</title><link>http://www.dongpad.com</link> <description>Every day is a new beginning!</description><copyright>2.0 beta 03</copyright> <language>zh-cn</language><item><title>AppDomain 和动态加载</title><description><![CDATA[<p>今天回顾了下AppDomain，发现一篇不错的文章，在此分享。</p>  <p><a href="http://www.microsoft.com/china/MSDN/library/default.aspx">欢迎来到 MSDN</a> > <a href="http://www.microsoft.com/china/MSDN/library/LangTool/default.mspx">开发语言和工具</a></p>  <h3>AppDomain 和动态加载</h3>  <h4></h4>  <p>发布日期： 5/17/2002 | 更新日期： 6/25/2004</p>  <p>Eric Gunnerson</p>  <p>Microsoft Corporation </p>  <p>下载 <a href="http://download.microsoft.com/download/4/e/3/4e310faa-2e18-4779-a650-3aea8a34f748/supergraphfiles.exe">supergraphfiles.exe</a> 示例文件。</p>  <p>这个月，我刚开完 ASP.NET 会议，正坐在 Palm Springs 国际机场候机厅里，等着飞回西雅图。</p>  <p>这个月，我的最初计划（某种程度上我还是“有”计划的）是对上个月 SuperGraph 应用程序的表达式分析部分做一些工作。然而，过去的几周内，我收到几封电子邮件询问我什么时候做完 AppDomain 部件中程序集的加载和卸载，因此我决定先集中精力解决该问题。</p>  <p><img title="" height="6" alt="*" src="http://img.microsoft.com/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/3squares.gif" width="30" border="0" /></p>  <h6>本页内容</h6>  <p><a href="http://www.microsoft.com/#ENB"><img height="9" alt="应用程序体系结构" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#ENB">应用程序体系结构</a></p>  <p><a href="http://www.microsoft.com/#E3B"><img height="9" alt="创建 AppDomain" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#E3B">创建 AppDomain</a></p>  <p><a href="http://www.microsoft.com/#EGC"><img height="9" alt="程序集绑定日志查看器" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#EGC">程序集绑定日志查看器</a></p>  <p><a href="http://www.microsoft.com/#EKC"><img height="9" alt="动态加载程序集" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#EKC">动态加载程序集</a></p>  <p><a href="http://www.microsoft.com/#ETC"><img height="9" alt="重新加载程序集" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#ETC">重新加载程序集</a></p>  <p><a href="http://www.microsoft.com/#EFD"><img height="9" alt="检测新的程序集" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#EFD">检测新的程序集</a></p>  <p><a href="http://www.microsoft.com/#EYD"><img height="9" alt="拖放" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#EYD">拖放</a></p>  <p><a href="http://www.microsoft.com/#EAE"><img height="9" alt="状态" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#EAE">状态</a></p>  <p><a href="http://www.microsoft.com/#EDE"><img height="9" alt="其他资料" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#EDE">其他资料</a></p>  <p><a href="http://www.microsoft.com/#ELE"><img height="9" alt="下个月" hspace="4" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_down.gif" width="7" vspace="2" border="0" /></a>    <br /><a href="http://www.microsoft.com/#ELE">下个月</a></p>  <p><a name="ENB"></a></p>  <h4>应用程序体系结构</h4>  <p>在我专攻代码之前，我想谈谈我尝试做的事。您可能记得，SuperGraph 让您从函数列表中进行选择。我希望能够在具体的目录中放置外接程序程序集，让 SuperGraph 检测它们，加载它们，并找到它们中包含的所有函数。</p>  <p>如果 SuperGraph 自己能完成此操作则不需要单独的 AppDomain。Assembly.Load() 通常运行良好，但程序集无法独立卸载（只有 AppDomain 可以卸载）。这意味着如果您正在编写服务器，而且您希望用户无需启动和停止服务器即能更新他们的外接程序，那么您将无法使用默认的 AppDomain 实现此任务。</p>  <p>要实现此功能，我们将在一个独立的 AppDomain 中加载所有外接程序程序集。当添加或修改文件时，我们将卸载 AppDomain，创建新的 AppDomain，然后将当前文件加载到其中。这样，一切就都完美无缺了。</p>  <p>为了把这个讲得更明白一点，我创建了一个典型方案，如图 1 所示。</p>  <p><img height="308" alt="csharp05162002-fig01.gif" src="http://www.microsoft.com/china/msdn/library/langtool/vcsharp/art/csharp05162002-fig01.gif" width="550" border="0" /></p>  <p><b>图 1：典型的 AppDomain 方案</b></p>  <p>在这个图表中，Loader 类创建一个名为 Functions 的新 AppDomain。创建 AppDomain 之后，Loader 在新的 AppDomain 中创建 RemoteLoader 的实例。</p>  <p>要加载程序集，请在 RemoteLoader 上调用加载函数。该函数打开新的程序集，找到程序集中的所有函数，将函数打包到 FunctionList 对象中，然后将该对象返回到 Loader。然后，就可以通过 Graph 函数使用此 FunctionList 中的 Function 对象。</p>  <p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>  <p><a name="E3B"></a></p>  <h4>创建 AppDomain</h4>  <p>第一项任务是创建 AppDomain。要以正确的方式创建 AppDomain，我们需要向 AppDomain 传递一个 AppDomainSetup 对象。一旦您理解了这一切的工作原理，关于这些的文档就足够使用了，但是如果您正在试图理解其工作原理，那么这些文档的帮助并不大。当关于该主题的 Google 搜索将上个月的专栏作为较高的匹配之一返回时，我怀疑我可能有点麻烦了。</p>  <p>必须处理的基本问题是如何在运行时加载程序集。默认情况下，运行时将查看全局程序集缓存或当前应用程序目录树。而我们希望从完全不同的目录中加载我们的外接程序。</p>  <p>当您查看 AppDomainSetup 的文档时，您将发现可以把 ApplicationBase 属性设置为要搜索程序集的目录。然而，我们也需要参考原始的程序目录，因为那是 RemoteLoader 类存在的地方。</p>  <p>AppDomain 的创作者们理解这一点，因此他们已经提供了额外的位置，用于从中搜索程序集。我们将使用 ApplicationBase 引用外接程序目录，然后将 PrivateBinPath 设置为指向主应用程序目录。</p>  <p>下面是来自 Loader 类的代码，可实现此功能：</p>  <pre>AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = functionDirectory;
setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
setup.ApplicationName = "Graph";
appDomain = AppDomain.CreateDomain("Functions", null, setup);

remoteLoader = (RemoteLoader)  
    appDomain.CreateInstanceFromAndUnwrap("SuperGraph.exe", 
        "SuperGraphInterface.RemoteLoader");</pre>

<p>创建 AppDomain 之后，使用 CreateInstanceFromAndUnwrap() 函数在新的应用程序域中创建 RemoteLoader 类的实例。请注意，需要使用类所在的程序集的文件名以及类的全名。</p>

<p>当执行此调用时，我们返回如同 RemoteLoader 一样的实例。实际上，它是一个小型代理类，将所有调用转发到其他 AppDomain 中的 RemoteLoader 实例中。这和 .NET Remoting 使用的是同一种结构。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="EGC"></a></p>

<h4>程序集绑定日志查看器</h4>

<p>当您编写代码实现此功能时，您会产生错误。本文档对如何调试应用程序并未提供什么建议，但是如果您知道该向谁询问，他们将告诉您有关程序集绑定日志查看器（名为“fuslogvw.exe”，因为加载子系统称为“fusion”）的信息。运行查看器时，您可以要求它记录故障，然后当您运行的应用程序出现加载程序集的问题时，您可以刷新查看器，获得当前情况的详细信息。</p>

<p>例如，您会发现 Assembly.Load() 的文件名末尾不需要 .dll，这一点非常有用。您可以从日志中获知这一点，因为它将告诉您它曾试图加载 f.dll.dll。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="EKC"></a></p>

<h4>动态加载程序集</h4>

<p>因此，既然我们已经创建了应用程序域，下一步应该搞清楚如何加载组件并从中提取函数。这需要两段相互独立的代码。第一段代码在目录中查找文件，然后加载找到的每个文件：</p>

<pre>void LoadUserAssemblies()
{
    availableFunctions = new FunctionList();
    LoadBuiltInFunctions();

    DirectoryInfo d = new DirectoryInfo(functionAssemblyDirectory);
    foreach (FileInfo file in d.GetFiles("*.dll"))
    {   
        string filename = file.Name.Replace(file.Extension, "");
        FunctionList functionList = loader.LoadAssembly(filename);

        availableFunctions.Merge(functionList);
    }
}</pre>

<p>Graph 类中的函数在外接程序目录中查找所有的 dll 文件，删除它们的扩展名，然后告诉加载程序加载它们。返回的函数列表将并入当前的函数列表。</p>

<p>第二段代码在 RemoteLoader 类中，它实际加载程序集并查找函数：</p>

<pre>public FunctionList LoadAssembly(string filename)
{
    FunctionList functionList = new FunctionList();
    Assembly assembly = AppDomain.CurrentDomain.Load(filename);

    foreach (Type t in assembly.GetTypes())
    {
        functionList.AddAllFromType(t);
    }    
    return functionList;
}</pre>

<p>这段代码只是对传入的文件名（实际是程序集名称）调用 Assembly.Load()，然后将所有有用的函数加载到 FunctionList 实例中返回给调用程序。</p>

<p>此时，应用程序可以启动，加载外接程序程序集，然后用户就可以引用它们。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="ETC"></a></p>

<h4>重新加载程序集</h4>

<p>下一项任务是能够按照需要重新加载这些程序集。最终，我们希望能够自动实现该任务，但是出于测试目的，我将 Reload 按钮添加到窗体中，以使程序集能够重新加载。该按钮的处理程序仅调用 Graph.Reload()，它需要执行以下操作：</p>

<p>1.</p>

<p>卸载 AppDomain。</p>

<p>2.</p>

<p>创建新的 AppDomain。</p>

<p>3.</p>

<p>在新的 AppDomain 中重新加载程序集。</p>

<p>4.</p>

<p>将图形线条挂钩到新创建的 AppDomain。</p>

<p>步骤 4 是必需的，因为 GraphLine 对象包含来自原 AppDomain 的 Function 对象。卸载 AppDomain 后，函数对象无法再被使用。</p>

<p>为解决此问题，HookupFunctions() 修改了 GraphLine 对象，使它们从当前应用程序域指向正确的函数。</p>

<p>代码如下：</p>

<pre>loader.Unload();
loader = new Loader(functionAssemblyDirectory);
LoadUserAssemblies();
HookupFunctions();
reloadCount++;

if (this.ReloadCountChanged != null)
    ReloadCountChanged(this, new ReloadEventArgs(reloadCount));</pre>

<p>只要执行重新加载操作，最后两行将引发一个事件。其作用是更新窗体上的重新加载计数器。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="EFD"></a></p>

<h4>检测新的程序集</h4>

<p>下一步是能够检测在外接程序目录中显示的新的或修改过的程序集。该框架提供 FileSystemWatcher 类来实现此功能。下面是我添加到 Graph 类构造函数中的代码：</p>

<pre>watcher = new FileSystemWatcher(functionAssemblyDirectory, "*.dll");
watcher.EnableRaisingEvents = true;
watcher.Changed += new FileSystemEventHandler(FunctionFileChanged);
watcher.Created += new FileSystemEventHandler(FunctionFileChanged);
watcher.Deleted += new FileSystemEventHandler(FunctionFileChanged);</pre>

<p>当创建 FileSystemWatcher 类时，我们告诉它要在什么目录中查找，要跟踪哪些文件。EnableRaisingEvents 属性表示当它检测到更改时，我们是否需要它发送事件。最后 3 行将事件挂钩到类中的某个函数。该函数仅仅调用 Reload() 以重新加载程序集。</p>

<p>这种方法有一些累赘的地方。在更新程序集时，我们必须卸载程序集才能够加载新的版本，但是添加或删除文件时不需要卸载程序集。在这种情况下，对所有更改执行此操作的成本并不是很高，而且它使代码更简单。</p>

<p>在构造此代码之后，我们运行该应用程序，然后尝试把新的程序集复制到外接程序目录中。正如我们所希望的那样，我们获得了文件更改事件，当重新加载完毕时，新的函数就可供使用。</p>

<p>然而，当我们试图更新现有的程序集时，我们遇到了一个问题。运行时已经锁定该文件，这意味着我们无法将新的程序集复制到外接程序目录中，我们收到一个错误。</p>

<p>AppDomain 类的设计人员意识到这是一个问题，因此他们提供一种不错的解决方法。当 ShadowCopyFiles 属性设置为 true（字符串 true，不是布尔常数 true。不要问我为什么……）时，运行时将把程序集复制到缓存目录中，然后打开该程序集。这样，原文件就不会被锁定，我们也就能更新正在使用的程序集。ASP.NET 使用了这种机制。</p>

<p>为了启用此功能，我在 Loader 类的构造函数中添加了以下行：</p>

<pre>setup.ShadowCopyFiles = "true";</pre>

<p>然后我重新生成了该应用程序，并得到相同的错误。我查看了 ShadowCopyDirectories 属性的文档，该文档明确指出 PrivateBinPath 指定的所有目录（包括 ApplicationBase 指定的目录）是阴影复制的（如果未设置此属性）。记得我是如何说该文档在这个方面不是很好的吗？</p>

<p>有关此属性的文档肯定是错了。我没有验证确切的表现方式，但是我可以告诉您 ApplicationBase 目录中的文件在默认情况下并不是阴影复制的。明确指定目录可以解决此问题：</p>

<pre>setup.ShadowCopyDirectories = functionDirectory;</pre>

<p>搞明白这一点至少花了我半个小时。</p>

<p>现在我们可以更新现有文件并将其正确地加载进去。可我刚把这个理顺，又遇到了另外一个小的问题。当我们从窗体的按钮上运行重新加载函数时，重新加载总是和绘制发生在同一个线程中，这意味着在重新加载过程中我们不可能尝试绘制直线。</p>

<p>既然我们已经切换到文件更改事件，那么在卸载 AppDomain 之后和加载新的 AppDomain 之前，有可能会进行绘制。如果发生这种情况，我们会得到一个异常。</p>

<p>这是传统的多线程编程问题，使用 C# lock 语句很容易处理。我在绘图函数和重新加载函数中添加了 lock 语句，这就确保了它们不会同时发生。这就解决了该问题，添加程序集的更新版本将使程序自动切换到函数的新版本。这相当不错。</p>

<p>还有一个奇怪的现象。原来用于检测文件更改的 Win32® 函数发送的更改数量很大，因此对文件做一次更新将发送五个更改事件，程序集也将被重新加载五次。解决方法是编写更智能的、可以将这些操作组合在一起的 FileSystemWatcher，但是此版本中没有提供这种解决方法。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="EYD"></a></p>

<h4>拖放</h4>

<p>将文件复制到目录中不是很方便，因此我决定在该应用程序中添加拖放功能。实现该任务的第一步是把窗体的 AllowDrop 属性设置为 true，这将打开拖放功能。下一步，我将一个例程挂钩到 DragEnter 事件。当光标在对象上移动进行拖放操作以确定当前对象是否接受拖放时，将调用该事件。</p>

<pre>private void Form1_DragEnter(
    object sender, System.Windows.Forms.DragEventArgs e)
{
    object o = e.Data.GetData(DataFormats.FileDrop);
    if (o != null)
    {
        e.Effect = DragDropEffects.Copy;
    }
    string[] formats = e.Data.GetFormats();
}</pre>

<p>在此处理程序中，我查看是否有可用的 FileDrop 数据（也就是说，文件被拖放到窗口中）。如果有，我把效果设置为“复制”，这将相应地设置光标，并且如果用户释放鼠标按钮，将发送 DragDrop 事件。该函数中的最后一行完全是出于调试目的，用于查看操作中有哪些可用信息。</p>

<p>下一项任务是为 DragDrop 事件编写处理程序：</p>

<pre>private void Form1_DragDrop(
    object sender, System.Windows.Forms.DragEventArgs e)
{
    string[] filenames = (string[]) e.Data.GetData(DataFormats.FileDrop);
    graph.CopyFiles(filenames);
}</pre>

<p>此例程获得与此操作关联的数据（文件名数组），将其传递到图形函数，然后图形函数把文件复制到外接程序目录中，触发文件更改事件以便重新加载它们。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="EAE"></a></p>

<h4>状态</h4>

<p>此时，您可以运行该应用程序，把新的程序集拖到程序上，程序将很快加载它们并保持运行。这相当不错。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="EDE"></a></p>

<h4>其他资料</h4>

<p><b>C# </b><b>社区站点</b></p>

<p>我已经建立了 Visual C#® 社区通讯，因此 C# 产品组能更好地同我们的用户交流。当我们的社区站点 http://www.gotdotnet.com/team/csharp（英文）有新内容时，我将通过它来宣布，也会通过它让您知道我们将参加研讨会还是用户组会议。您可以访问该社区站点进行注册。</p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p><a name="ELE"></a></p>

<h4>下个月</h4>

<p>如果我继续讨论 SuperGraph，我可能会开发不发送很多外部事件的 FileSystemWatcher 版本，也可能会探讨表达式求值。当然，我也可能会谈一谈另一个小示例。</p>

<p><a href="http://msdn.microsoft.com/library/en-us/dncscol/html/csharp05162002.asp?frame=true">源文链接</a></p>

<p><a href="http://www.microsoft.com/#top"><img height="9" alt="返回页首" src="http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/arrow_px_up.gif" width="7" border="0" /></a><a href="http://www.microsoft.com/#top">返回页首</a></p>

<p>trackback:<a title="http://www.microsoft.com/china/msdn/library/langtool/vcsharp/csharp05162002.mspx?mfr=true" href="http://www.microsoft.com/china/msdn/library/langtool/vcsharp/csharp05162002.mspx?mfr=true">http://www.microsoft.com/china/msdn/library/langtool/vcsharp/csharp05162002.mspx?mfr=true</a></p>]]></description><author>Jack</author><link>http://www.dongpad.com/CSharp-20090104-143.html</link><pubdate>2009-1-4 21:59:01</pubdate></item></channel></rss>