• WorldWind学习系列八:Load/Unload Plugins——直捣黄龙篇


    第一部分

    打开PluginDialog.cs窗体时,会调用273行的

      private void PluginDialog_Load(object sender, System.EventArgs e)
      {
       
    //加载插件到ListView控件中     
       AddPluginList();
       
       
    //Force UI state update
       listView_SelectedIndexChanged(this,EventArgs.Empty);
       
    //根据ListView当前选中项,更新窗体按钮功能的可用性
       UpdateUIStates();
      }

      
    /// <summary>
      
    /// Fill the list view with currently installed plugins.
      
    /// </summary>
      void AddPluginList()
      {
       listView.Items.Clear();
       
    foreach (PluginInfo pi in compiler.Plugins) 
       {
        PluginListItem li 
    = new PluginListItem(pi);
        listView.Items.Add(li);
       }
      }

    此处PluginInfo里面有个知识点,请看下面两个截图:(暂缺)

    就是怎样读取文件头的元数据?PluginInfo.cs 188行ReadMetaData()通过一行行地读取文件内容,从而解析出所需的原数据。

    /// <summary>
      
    /// Reads strings from the source file header tags
      
    /// </summary>
      private void ReadMetaData()
      {
       
    try
       {
        
    if(m_fullPath==null)
         
    // Source code comments not available
         return;

        
    // Initialize variables (prevents more than one call here)
        if(m_name==null)
         m_name 
    = "";
        
    if(m_description==null)
         m_description 
    = "";
        
    if(m_developer==null)
         m_developer 
    = "";
        
    if(m_webSite==null)
         m_webSite 
    = "";
        
    if(m_references==null)
         m_references 
    = "";

        
    using(TextReader tr = File.OpenText(m_fullPath))
        {
         
    //注意:这里将插件文件的所有行内容都读取一遍啦,其实元数据都在前面几行的。
         while(true)
         {
          
    string line = tr.ReadLine();
          
    if(line==null)
           
    break;

          FindTagInLine(line, 
    "NAME"ref m_name);
          FindTagInLine(line, 
    "DESCRIPTION"ref m_description);
          FindTagInLine(line, 
    "DEVELOPER"ref m_developer);
          FindTagInLine(line, 
    "WEBSITE"ref m_webSite);
          FindTagInLine(line, 
    "REFERENCES"ref m_references);
                                             
    //下面是我修改添加的,为了提升一定的效率
        if(m_name!=string.Empty&& m_description!=string.Empty&& m_developer!=string.Empty&& m_webSite!=string.Empty&&m_references!=string.Empty)
          
    return;

         }
        }
       }
       
    catch(IOException)
       {
        
    // Ignore
       }
       
    finally
       {
        
    if(m_name.Length==0)
         
    // If name is not defined, use the filename
         m_name = Path.GetFileNameWithoutExtension(m_fullPath);
       }
      }

    我们看看236行的FindTagInLine方法。

     /// <summary>
      
    /// Extract tag value from input source line.
      
    /// </summary>
      static void FindTagInLine(string inputLine, string tag, ref string value)
      {
       
    if(value!=string.Empty)
        
    // Already found
        return;

       
    // Pattern: _TAG:_<value>EOL
       tag = " " + tag + "";
       
    int index = inputLine.IndexOf(tag);
       
    if(index<0)
        
    return;
       
    //获取冒号后面的所有内容。
       value = inputLine.Substring(index+tag.Length);
      }

    第二部分

       窗体中的Load和Unload功能,分别调用了306行PluginLoad(PluginListItem pi)、324行的 public void PluginUnload(PluginListItem pi)。
      真正实现装载和卸载的是PluginCompiler.cs里的244行的 Load(PluginInfo pi)和277行的 Unload(PluginInfo pi)。

     /// <summary>
      
    /// Load a plugin
      
    /// </summary>
      public void Load(PluginInfo pi)
      {
       
    if(pi.Plugin == null)
       {
        
    // Try to find a suitable compiler
        string extension = Path.GetExtension(pi.FullPath).ToLower();
        Assembly asm 
    = null;
        
    if(extension==".dll")
        {
         
    // Load pre-compiled assembly ,此处利用了反射动态加载
         asm = Assembly.LoadFile(pi.FullPath);
        }
        
    else
        {     
    //CodeDomProvider知识点
         CodeDomProvider cdp = (CodeDomProvider)codeDomProviders[extension];
         
    if(cdp==null)
          
    return;
         
    //使用特定的编译器,将插件类文件编译为dll
         asm = Compile(pi, cdp);
        }

        pi.Plugin 
    = GetPluginInterface(asm);
       }

       
    string pluginPath = MainApplication.DirectoryPath;
       
    if( pi.FullPath != null && pi.FullPath.Length > 0)
        pluginPath 
    = Path.GetDirectoryName(pi.FullPath);

       pi.Plugin.PluginLoad(worldWind, pluginPath);
      }

      从代码中,可看到加载插件分为两种方式:一种是加载预编译的插件程序集(即:dll文件);一种是加载插件类文件,实现动态编译。
    第一种方式:通过反射机制的Assembly,实现运行时加载插件DLL,关键是学习这种方式, Assembly asm = null; asm = Assembly.LoadFile(pi.FullPath);
    第二种方式:CodeDomProvider请参看(强烈推荐CodeDomProvider学习系列网址http://www.cnblogs.com/lichdr/category/12610.html
    codeDomProviders就是一个HashTable对象,里面存放的是类文件的后缀名(.cs,.vb),是在PluginCompiler.cs构造函数中调用

       AddCodeProvider(new Microsoft.CSharp.CSharpCodeProvider() );
       AddCodeProvider(
    new Microsoft.VisualBasic.VBCodeProvider() );
       AddCodeProvider(
    new Microsoft.JScript.JScriptCodeProvider() );

    C#、VB、J#都是WorldWind里支持动态编译插件类的语言。
      /// <summary>
      /// Adds a compiler to the list of available codeDomProviders
      /// </summary>
      public void AddCodeProvider( CodeDomProvider cdp )
      {
       // Add leading dot since that's what Path.GetExtension uses
       codeDomProviders.Add("."+cdp.FileExtension, cdp);
      }
      我们看看WW是如何做到运行时动态编译的,261行代码:asm = Compile(pi, cdp);原来是通过Complie()方法实现将插件类文件编译为dll。

         /// <summary>
      
    /// Compiles a file to an assembly using specified compiler.
      
    /// </summary>
      Assembly Compile( PluginInfo pi, CodeDomProvider cdp )
      {
       
    // Compile
       
    //ICodeCompiler compiler = cdp.CreateCompiler();

       
    if(cdp is Microsoft.JScript.JScriptCodeProvider)
        
    // JSCript doesn't support unsafe code
        cp.CompilerOptions = "";
       
    else
        cp.CompilerOptions 
    = "/unsafe";

       
    // Add references
       cp.ReferencedAssemblies.Clear();
       
    //添加引用:PluginCompiler.cs构造函数中88-99行代码worldWind下的所有引用到集合对象m_worldWindReferencesList中的,实际用到的引用没有这么多的,完全是“宁多不可少”!
     foreachstring reference in m_worldWindReferencesList)
        cp.ReferencedAssemblies.Add(reference);

       
    // Add reference to core functions for VB.Net users ,添加VB核心编译引用
       if(cdp is Microsoft.VisualBasic.VBCodeProvider)
        cp.ReferencedAssemblies.Add(
    "Microsoft.VisualBasic.dll");

       
    // Add references specified in the plugin,添加插件内部自己特有的引用
       foreachstring reference in pi.References.Split(','))
        AddCompilerReference( pi.FullPath, reference.Trim() );
       
    //调用CompileAssemblyFromFile方法实现编译
       CompilerResults cr = cdp.CompileAssemblyFromFile( cp, pi.FullPath );
       
    if(cr.Errors.HasErrors || cr.Errors.HasWarnings)
       {
        
    // Handle compiler errors
        StringBuilder error = new StringBuilder();
        
    foreach (CompilerError err in cr.Errors)
        {
         
    string type = (err.IsWarning ? "Warning" : "Error");
         
    if(error.Length>0)
          error.Append(Environment.NewLine);
         error.AppendFormat(
    "{0} {1}: Line {2} Column {3}: {4}", type, err.ErrorNumber, err.Line, err.Column, err.ErrorText );
        }
                    Log.Write(Log.Levels.Error, LogCategory, error.ToString());
                    
    if(cr.Errors.HasErrors)
            
    throw new Exception( error.ToString() );
       }

       
    // Success, return our new assembly,返回编译结果
       return cr.CompiledAssembly;
      }

    继续分析,看PluginCompiler.cs中264行, pi.Plugin = GetPluginInterface(asm);从编译后的插件工程中,获取Plugin对象实例。static Plugin GetPluginInterface(Assembly asm)中关键的和值得我们学习借鉴的就是:
         Plugin pluginInstance = (Plugin) asm.CreateInstance( t.ToString() ); 
         return pluginInstance;
    学习Assembly的CreateInstance方法。
    PluginCompiler.cs中271行pi.Plugin.PluginLoad(worldWind, pluginPath);中开始调用各用户插件(Plugin)中重写的Load()方法将加载到WorldWind.
    我在查找资料学习CodeDomProvider花了不少实际,原来网上很多资料了,原来运行时编译的强大功能早就有了。等稍后有时间我一定深入学习一下该部分内容。

    插件Unload功能:
    使用的是public void PluginUnload(PluginListItem pi),里面328行调用了PluginCompiler.cs中的public void Uninstall(PluginInfo pi),该函数方法的关键代码是pi.Plugin.PluginUnload();继续跟踪进去,发现真正实现插件卸载的是Plugin.cs及其子类重载过的Unload()方法。自己写插件时需要重写该方法的。

    Install插件功能:
    PluginDialog.cs
    384行 

     private void buttonInstall_Click(object sender, System.EventArgs e)
      {
       Form installDialog 
    = new PluginInstallDialog(compiler);
       installDialog.Icon 
    = this.Icon;
       installDialog.ShowDialog();

       
    // Rescan for plugins
       compiler.FindPlugins();
       AddPluginList();
      }

    此处我们需要关注学习两方面:PluginInstallDialog.cs 和compiler.FindPlugins();
    PluginInstallDialog中插件来源分为:Web 和File。
    查看代码171行:

        if(IsWeb)
         InstallFromUrl(
    new Uri(url.Text));
        
    else if(IsFile)
         InstallFromFile(url.Text);
        
    else
        {
         MessageBox.Show(
    "Please specify an existing filename or a web url starting with 'http://'.""Not found", MessageBoxButtons.OK, MessageBoxIcon.Error );
         url.Focus();
         
    return;
        }

     
       从网络上安装插件,实质上就是使用WebDownload类下载插件文件到插件目录下。

      /// <summary>
      
    /// Install plugin from web (url).
      
    /// </summary>
      
    /// <param name="pluginUrl">http:// URL</param>
      void InstallFromUrl( Uri uri )
      {
       
    string fileName = Path.GetFileName( uri.LocalPath );
       
    string destPath = GetDestinationPath( fileName );
       
    if(destPath == null)
        
    return;

       
    using(WebDownload dl = new WebDownload(uri.ToString()))
        dl.DownloadFile(destPath);

       ShowSuccessMessage( fileName );
      }

     从文件系统中安装插件,直接拷贝插件文件到插件目录Plugins下。

      /// <summary>
      
    /// Install plugin from local file.
      
    /// </summary>
      
    /// <param name="pluginPath">Plugin path/filename.</param>
      void InstallFromFile( string pluginPath )
      {
       
    string fileName = Path.GetFileName( pluginPath );
       
    string destPath = GetDestinationPath( fileName );
       
    if(destPath == null)
        
    return;

       File.Copy(pluginPath, destPath);

       ShowSuccessMessage( fileName );
      }

     compiler.FindPlugins();调用了PluginCompiler类的FindPlugins()方法,用来重新扫描Plugins目录及子目录,获取到所有的插件。接着调用PluginDialog.cs的AddPluginList();用来更新插件列表。

      /// <summary>
      
    /// Build/update the list of available plugins.
      
    /// </summary>
      public void FindPlugins()
      {
       
    if(!Directory.Exists(m_pluginRootDirectory))
        
    return;

       
    // Plugins should reside in subdirectories of path
       foreach(string directory in Directory.GetDirectories(m_pluginRootDirectory))
        AddPlugin(directory);

       
    // Also scan Plugins base directory
       AddPlugin(m_pluginRootDirectory);
      }

    Uninstall插件功能:
    关键代码:421行compiler.Uninstall( pi.PluginInfo );

    卸载代码
      /// <summary>
      
    /// Uninstall/delete a plugin.
      
    /// </summary>
      
    /// <param name="pi"></param>
      public void Uninstall(PluginInfo pi)
      {
       
    // Unload the plugin
       Unload(pi);

       File.Delete( pi.FullPath );

       m_plugins.Remove( pi );
      }

    首先调用用Unload插件功能,然后把插件文件删除掉,更新插件列表m_plugins.Remove( pi )。

     

    其他部分:

    WorldWind学习系列七:Load/Unload Plugins——投石问路篇

    WorldWind学习系列六:渲染过程解析篇

    WorldWind学习系列五:插件加载过程全解析

    WorldWind学习系列四:功能分析——Show Planet Axis、Show Position 、Show Cross Hairs功能

    WorldWind学习系列三:简单功能分析——主窗体的键盘监听处理及拷贝和粘贴位置坐标功能

    WorldWind学习系列三:功能分析——截屏功能和“关于”窗体分析

    WorldWind学习系列二:擒贼先擒王篇2

    WorldWind学习系列二:擒贼先擒王篇1

    WorldWind学习系列一:顺利起航篇


     

  • 相关阅读:
    MySQL--lsblk命令查看块设备
    MySQL--linux IO调度算法
    一致性哈希
    MySQL--查询表统计信息
    MySQL--区分表名大小写
    MySQL--Online DDL
    MySQL--MODIFY COLUMN和ALTER COLUMN
    MySQL--修改表字段
    MySQL--增加或修改列注释
    鼠标事件
  • 原文地址:https://www.cnblogs.com/wuhenke/p/1625939.html
Copyright © 2020-2023  润新知