第一部分
打开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.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学习系列一:顺利起航篇