第一部分
打开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中的,实际用到的引用没有这么多的,完全是“宁多不可少”! foreach ( string 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,添加插件内部自己特有的引用 foreach ( string 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.cs384行
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学习系列一:顺利起航篇
转载于:https://www.cnblogs.com/wuhenke/archive/2009/12/16/1625939.html
相关资源:数据结构—成绩单生成器