pcs2需要的cs2系统异常插件怎么搞

pCS2+广宿主载体使用说明_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
pCS2+广宿主载体使用说明
上传于|0|0|暂无简介
阅读已结束,如果下载本文需要使用1下载券
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,查找使用更方便
还剩3页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢实现可用的插件系统
来源:博客园
Jusfr 原创,文章所用代码已给出,转载请注明来自。






开始之前还是得说:插件机制老生常谈,但一下子到某工厂或 MAF 管线我相信不少园友吃不消。授人以鱼不如授人以渔,个人觉得思考过程的引导和干货一样重要,不然大家直接看 MSDN 或者 API 文档好了。
1. 插件机制与 AppDomain
“CLR不提供缷载单独程序集的能力。如果CLR允许这样做,那么一旦线程从某个方法返回至已缷载的一个程序集中的代码,应用程序就会崩溃。健壮性和安全性是CLR最优先考虑的目标,如果允许应用程序以这样的一种方式崩溃,就和它的设计初衷背道而驰了。缷载应用程序集必须缷载包含它的整个 AppDoamin 。” ———— 出自《CLR via C#》519页。
想要达到插件化目的,必须手动创建 AppDomain 作为插件容器和边界,在需要时卸载 AppDomain 以达到卸载插件的目的。这里不得不提及 MEF 和 MAF。MEF 使用 Import 与 Export 进行类型发现和元数据查找,还维护了组件生命周期,但与插件机制并无关联,多数情况下把它归纳到注入工具比较合适;MAF 极为强大但仍然是上述原理的运用,过于厚重关注有限。
2. 示例与现实
.Net 下插件限制已经在文章开始的时候进行了描述,机制就是自定义 AppDomain 的创建与缷载,实现并不复杂,贴一段 Demo:

1
static void Main(string[] args) {
2
var pluginDomain = AppDomain.CreateDomain("ad#1");
3
var pluginType = typeof(Plugin); // Other ways
4
var pluginInstance = (IPlugin)pluginDomain.CreateInstanceAndUnwrap(pluginType.Assembly.FullName, pluginType.FullName);
5 
6
// Do stuff with pluginInstance
7
AppDomain.Unload(pluginDomain);
8
}

我们可以通过反射拿到定义在其他程序集中的 pluginType ,并在 AppDomain.Unload() 调用后删掉该程序集,它满足动态缷载的要求。
但是这个 Demo 程序实在是有太多问题:
1)如果 IPlugin 是空的标记接口,那么宿主无法调用实现类的业务逻辑;如果 IPlugin 是非空的业务接口,那么类库职责与应用职混淆在了一起? 2)接口实现类和关联类型必须使用 [Serializable] 标记或者从 MarshalByRefObject 派生,由于生产环境存在相当多的数据类型及引用,可能需要把业务上的数据结构改个遍,甚至不能实现; 3)插件的隔离性没有体现出来,不同插件可能有不同的数据库连接和独立的第三方类库引用,程序发布成为难题;
3. 目标与设想
前文列举的问题就是我们要解决的问题: 
1)可运行时加载/缷载,基本原理在 Demo 中得到了体现,但是实现得非常丑陋,管理 AppDomain 是核心的底层逻辑,不应该出现在启动过程中; 2)划清类库开发与应用开发边界,我期望创建出可重复使用的插件机制而不要混入一大坨业务逻辑; 3)保证隔离性,插件需要拥有独立配置文件、各自升级的能力;
我们先进入下一节作些准备工作;
4. [Serializable] 与 MarshalByRefObject
.Net 进程总是会创建默认 AppDomain,由于插件化需要额外的 AppDomain,难免出现跨 AppDomain 边界访问对象的问题,比如宿主调用插件、为插件传递参数、获取插件的计算结果等等,我们知道有两种方法可以使用:标记 [Serializable] 以按值封送、从 MarshalByRefObject 派生以按引用封送。 
举例,我们定义某接口包含了推送消息的方法 bool Push(Message message) ,如果期望在自定义 AppDomain 中创建实现类,那么该实现类需要标记 [Serializable] 以按值封送或从 MarshalByRefObject 派生以按引用封送;额外地,按引用封送时,被依赖的 Message 对象也需要满足跨边界访问要求。
那么按值封送时类型 Message 不用特殊处理 ?确实如此,简单解释下,为封送方式的选择作出解释。
使用过 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 的同学应该和 "SerializationException: Type 'xxoo' in Assembly 'ooxx' is not marked as serializable." 打过交道。按值封送是一个序列化和反序列化的过程,看起来我们在自定义 AppDomain 中进行了类型实例化并拿到引用,实际上发生了更多事情:原始实例被序列化为字节数组传回调用逻辑所在 AppDomain,然后字节数组反序列化,该类型所在和相关的程序集被视需求加载,最后得到了是对原始对象的精确拷贝及该拷贝的引用,而原始类型实例会在垃圾回收中被销毁。
按值封送的类型实例化过程中,相关程序集已在调用方 AppDomain 完成加载即我们已经拥有 Message 类型信息,调用 Push() 方法时不会存在跨 AppDomain 边界访问对象的问题,故 Message 对象无须处理。
按引用封送拿到的是类型实例的代理,我们通过它与原始对象打交道。基于上述描述和可缷载的插件化要求,我们应该选择按引用封送。
接着关注下性能问题,以下是基本测试。

 1
public interface IPlugin {
 2
Int32 X { get; set; }
 3
}
 4 
 5
public class Plugin : IPlugin {
 6
public Int32 X { get; set; }
 7
}
 8 
 9
[Serializable]
10
public class MarshalByRefValuePlugin : IPlugin {
11
public Int32 X { get; set; }
12
}
13 
14
public class MarshalByRefTypePlugin : MarshalByRefObject, IPlugin {
15
public Int32 X { get; set; }
16
}
17 
18
public class MarshalByRefTypePluginProxy : MarshalByRefObject {
19
private readonly IPlugin h = new Plugin();
20 
21
public void Proceed() {
22
h.X++;
23
}

MarshalByRefTypePluginProxy 相对其他实现比较特殊,它是一个装饰器模式;调用测试如下,PerformanceRecorder 是我写的测试类,它内部包含一个 Stopwatch,接收整型数及一个委托列表,返回每个委托执行声明次数所需要的时间等结果;

 1
static void Main(string[] args) {
 2
var h1 = new Plugin();
 3
var h2 = new MarshalByRefValuePlugin();
 4
var h3 = new MarshalByRefTypePlugin();
 5 
 6
AppDomain ad = AppDomain.CreateDomain("ad#2");
 7
var t1 = typeof(MarshalByRefTypePlugin);
 8
var h4 = (IPlugin)ad.CreateInstanceAndUnwrap(t1.Assembly.FullName, t1.FullName);
 9
var t2 = typeof(MarshalByRefValuePlugin);
10
var h5 = (IPlugin)ad.CreateInstanceAndUnwrap(t2.Assembly.FullName, t2.FullName);
11 
12
var t3 = typeof(MarshalByRefTypePluginProxy);
13
var py = (MarshalByRefTypePluginProxy)ad.CreateInstanceAndUnwrap(t3.Assembly.FullName, t3.FullName);
14 
15
var records = PerformanceRecorder.Invoke(100000,
16
() =& h1.X++, () =& h3.X++, () =& h2.X++, () =& h4.X++, () =& h5.X++, py.Proceed);
17
foreach (var r in records) {
19
Console.WriteLine("{0} {1,4} {2}",
20
r.RunningTime, r.CollectionCount, r.TotalMemory);
21
}

可以看到结果:标记 [Serializable] 的 MarshalByRefValuePlugin,由于实例调用并不会发生跨 AppDomain 边界的对象访问,无论是直接创建还是使用自定义 AppDomain 创建都没有显著的性能差异;而继承自 MarshalByRefObject 的 MarshalByRefTypePlugin,在默认 AppDomain 中调用时性能十分接近,一旦在自定义 AppDomain 中创建、在默认 AppDomain 中访问时,性能直跌谷底。
00:00:00.0016055
3 6:00:00.0020829
6 6:00:00.0019477
9 6:00:01.6 7:00:00.9 7:00:00.2 71648
Press any key to continue . . .


采取装饰器模式的 MarshalByRefTypePluginProxy 很有意思,它依赖 IPlugin 实例工作,因为 IPlugin 调用发生在自定义 AppDomain 内部,这里没有跨 AppDomain 边界的对象访问! 虽然相比直接调用存在不小性能差距,但相比直接引用 IPlugin 在自定义 AppDomain 中的实例还是高效太多,有所启示吗?
X++ 就是业务逻辑,通过调用 MarshalByRefTypePluginProxy.Proceed() 间接调用业务逻辑,我们得到了性能收益,同时因为不再对 IPlugin 的实现有封送要求,我们做到了对业务逻辑没有入侵。
5. 思路与实现
一方面接口可以有相当多的实现,而去操作每个实例过于细粒度;另一方面实践中我们常常以项目即 Visual Studio 里的 Project 定义业务,所以我选择使用项目编译结果作为插件边界。使用文件夹分隔能很方便地保证物理隔离,同时配合 AppDomainSetup 初始化 AppDomain 能做到配置文件和第三方类库引用独立!
另一方面前文提到的 MarshalByRefTypePluginProxy 相对直接的插件调用有一定的性能优势,我们可以将其与自定义 AppDomain 关联、充当宿主与插件的桥梁,达到调用业务逻辑、插件管理的目的。

核心类型为 IPluginCatalog 与 IPluginCatalogProxy。前者并供应用开发人员扩展以操作业务逻辑,后者聚合前者,通过路径管理自定义 AppDomain 和 IPluginCatalog 实例;IPluginResolver 承担默认的类型发现职责。

IPluginCatalog 与相关实现:IPluginCatalog 仅定义了插件目录,泛型 IPluginCatalog&out T& 定义了插件类型查找方法,PluginCatalog&T& 继承自 MarshalByRefObject 作为默认实现,FindPlugins() 被标记为虚方法,应用开发人员可以很方便地重写,而 InitializeLifetimeService() 方法返回 null 以避免原始对象被垃圾回收。
IPluginCatalogProxy 与相关实现: IPluginCatalogProxy 定义了泛型的 Construct&T, P&() 方法和约束,T 被要求从 IPluginCatalog&P& 定义。PluginCatalogProxy.Construct() 方法调用前会检查内部字典以创建或获取自定义 AppDomain,接着在该 AppDomain 上创建类型为 T 的 IPluginCatalog&P& 实例;Release() 方法执行 AppDomain 的查找和卸载逻辑,用户扩展的 IPluginCatalog 实例还可以定义资源清理工作,例如停止计数器、释放数据库连接。
注意:本例中的IPluginCatalog 实现及类型的实例均调用了的使用了 AppDomain.CreateInstanceAndUnwrap(string assemblyName, string typeName) 重载,该方法将调用目标类型的无参构造函数,其他重载更强大也很复杂,请自行查看。
逻辑不过百来行,就不打包了。

1 using S
2 using System.Collections.G
3 using position.H
4 using System.IO;
5 using System.R
6 using System.L
7 using System.T
8 using System.Threading.T
9 
 10 namespace ChuyeEventBus.Plugin {
 11
#region 类型发现相关
 12
public interface IPluginResolver {
 13
IEnumerable&T& FindAll&T&(String pluginFolder);
 14
}
 15 
 16
public class MefPluginResolver : IPluginResolver {
 17
public IEnumerable&T& FindAll&T&(String pluginFolder) {
 18
var catalog = new AggregateCatalog();
 19
catalog.Catalogs.Add(new DirectoryCatalog(pluginFolder));
 20
var container = new CompositionContainer(catalog);
 21
return container.GetExportedValues&T&();
 22
public class ReflectionPluginResolver : IPluginResolver {
 26
public IEnumerable&T& FindAll&T&(String pluginFolder) {
 27
var basePluginType = typeof(T);
 28
var pluginTypes = Directory.EnumerateFiles(pluginFolder, "*.dll", SearchOption.TopDirectoryOnly)
 29
.Concat(Directory.EnumerateFiles(pluginFolder, "*.exe", SearchOption.TopDirectoryOnly))
 30
.SelectMany(f =& Assembly.LoadFrom(f).ExportedTypes)
 31
.Where(t =& basePluginType.IsAssignableFrom(t) && t != basePluginType 
 32
&& !t.IsInterface && !t.IsAbstract);
 33
foreach (var pluginType in pluginTypes) {
 34
yield return (T)Activator.CreateInstance(pluginType);
 35
#endregion
 40
public interface IPluginCatalog {
 42
String PluginFolder { get; set; }
 43
public interface IPluginCatalog&out T& : IPluginCatalog {
 46
// 这里其实并不希望被跨 AppDoamin 访问,文末有补救
 47
IEnumerable&T& FindPlugins();
 48
public class PluginCatalog&T& : MarshalByRefObject, IPluginCatalog&T& {
 51
public String PluginFolder { get; set; }
 52 
 53
// 避免原始对象被释放
 54
public override object InitializeLifetimeService() {
 55
return null;
 56
}
 57 
 58
public virtual IEnumerable&T& FindPlugins() {
 59
var resolver = new ReflectionPluginResolver();
 60
return resolver.FindAll&T&(PluginFolder);
 61
public interface IPluginCatalogProxy {
 65
// T 类型需要有无参构造函数
 66
T Construct&T, P&(String pluginFolder) where T : IPluginCatalog&P&, new();
 67
void Release(String pluginFolder);
 68
void ReleaseAll();
 69
public class PluginCatalogProxy : IPluginCatalogProxy, IDisposable {
 72
private readonly Dictionary&String, AppDomain& _pluginDomains
 73
= new Dictionary&String, AppDomain&();
 74 
 75
public T Construct&T, P&(String pluginFolder) where T : IPluginCatalog&P&, new() {
 76
var pluginCatalogType = typeof(T);
 77
//todo: 如果期望区分同一目录获取不同的 IPluginCatalog&P& 实例,则需要做更多工作
 78
var pluginDomain = CreatePluginDomain(pluginFolder);
 79
var pluginCatalog = (IPluginCatalog)pluginDomain.CreateInstanceAndUnwrap(
 80
pluginCatalogType.Assembly.FullName,
 81
pluginCatalogType.FullName);
 82
pluginCatalog.PluginFolder = pluginF
 83
return (T)pluginC
 84
}
 85 
 86
protected virtual AppDomain CreatePluginDomain(String pluginFolder) {
 87
var cfg = GetPluginConfiguration(pluginFolder);
 88
var bins = new[] { pluginFolder.Substring(AppDomain.CurrentDomain.BaseDirectory.Length) };
 89
var setup = new AppDomainSetup();
 90
if (File.Exists(cfg)) {
 91
setup.ConfigurationFile =
 92
setup.ApplicationBase = AppDomain.CurrentDomain.BaseD
 94
setup.PrivateBinPath = String.Join(";", bins);
 95 
 96
AppDomain pluginD
 97
if (!_pluginDomains.TryGetValue(pluginFolder, out pluginDoamin)) {
 98
pluginDoamin = AppDomain.CreateDomain(pluginFolder, null, setup);
 99
_pluginDomains.Add(pluginFolder, pluginDoamin);
100
return pluginD
102
}
103 
104
// 可以定义自己的规则
105
protected virtual String GetPluginConfiguration(String pluginFolder) {
106
var config = bine(pluginFolder, "main.config");
107
if (!File.Exists(config)) {
108
config = bine(pluginFolder, Path.GetFileName(pluginFolder) + ".dll.config");
109
if (!File.Exists(config)) {
111
var configs = Directory.GetFiles(pluginFolder, "*.dll.config", SearchOption.TopDirectoryOnly);
112 
113
if (config.Length & 1) {
114
Debug.WriteLine(String.Format("Unknown configuration as too many .dll.config files in \"{0}\""
115
, Path.GetFileName(pluginFolder)));
116
else if (config.Length == 1) {
118
config = configs[0];
119
return
122
}
123 
124
public void Release(String pluginFolder) {
125
AppDomain pluginD
126
if (_pluginDomains.TryGetValue(pluginFolder, out pluginDoamin)) {
127
AppDomain.Unload(pluginDoamin);
128
_pluginDomains.Remove(pluginFolder);
129
}
131 
132
public void ReleaseAll() {
133
var unloadTasks = _pluginDomains.Select(async p =&
134
await Task.Run(action: () =& AppDomain.Unload(p.Value))).ToArray();
135
Task.WaitAll(unloadTasks);
136
_pluginDomains.Clear();
137
}
138 
139
public void Dispose() {
140
ReleaseAll();
141
}

View Code
业务逻辑的入口在哪里?我们来看一个场景和实例。计数应用需要从特定队列出队,然后操作数据库。我们定义接口 IFeature 及其实现;扩展 PluginCatalog&IFeature& 添加 StartAll() 作为业务入口;

 1
public interface IFeature {
 2
void Start();
 3
}
 4 
 5
public class MyFeature : IFeature {
 6 
 7
public void Start() {
 8
Console.WriteLine("MyFeature.Start()");
 9
// Grab message from message queue, calculate & persistence 
10
}
12 
13
public class MyPluginCatalog : PluginCatalog {
14 
15
public void StartAll() {
16
Console.WriteLine("MyPluginCatalog.StartAll()");
17
foreach (var feature in FindPlugins()) {
18
feature.Start();
19
}

PluginCatalogProxy.Construct() 方法获取到了用户定义的 PluginCatalog&T& 子类对象,而 FindPlugins() 在 MyPluginCatalog 内部使用,使得任何 IFeature 都不需要跨 AppDomain 边界访问;这里忽略掉了不是重点的 Timer 相关代码。

1
static void Main(string[] args) {
2
var pluginCatalogProxy = new PluginCatalogProxy();
3
var pluginFolder = AppDomain.CurrentDomain.BaseD // Define your own plugin folder
4
var pluginCatalog = pluginCatalogProxy.Construct(pluginFolder);
5 
6
pluginCatalog.StartAll();
7
pluginCatalogProxy.Release(pluginFolder);
8
}

IPluginCatalog 是前文 MarshalByRefTypePluginProxy 逻辑的体现,配合 PluginCatalogProxy.Construct() 方法,应用开发人员可以获取到自定义 IPluginCatalog 实现类的实例而不仅仅是 IPluginCatalog 接口,这为应用开发人员提供业务入口,并将业务逻辑隔离在自定义 AppDomain 中处理,规避了实现类的跨 AppDomain 边界问题; PluginCatalogProxy 管理维护着自定义 AppDomain 的生命周期,控制了其可见性。
应用开发人员通过引用 PluginCatalogProxy 和自定义 IPluginCatalog 实例可以完成业务调用、资源清理;也可以重写相关实现定制 AppDomain;在上层应用中通过文件监视,动态的插件加载、卸载不在话下;原理并不复杂,园友完全可以自行实现,处理好 AppDomain 边界问题即可。
在技术上,可以对 PluginCatalogProxy 使用单例模式,只是丧失了对其内部实现的修改能力; IPluginCatalog&out T&.FindPlugins() 也只是希望在子类调用而不是任何地方,可以在其实现中显式实现该接口来达到目的,大概是这样子:

 1 public class PluginCatalog : MarshalByRefObject, IPluginCatalog {
 2
//...
 3
protected virtual IEnumerable FindPlugins() {
 4
var resolver = new ReflectionPluginResolver();
 5
return resolver.FindAll(PluginFolder);
 6
}
 7 
 8
IEnumerable IPluginCatalog.FindPlugins() {
 9
return FindPlugins();
10
}

View Code
6. 后记
泛型与逆变使用可能有些晦涩,看多两次也不是太难理解,思路最重要。
关于插件的部署方式,我的实践如前文所提,宿主程序的根目录下创建文件夹,各业务实现再分别创建子文件夹;为了达到不停止插件宿主更新插件的目标,我们并不能直接在上述文件夹中进行类型发现和加载,而是需要使用一个拷贝目录,监视插件目录并原样复制,当发现插件目标更新时,优雅地停止相关业务逻辑、卸载对应 AppDomain、更新对应的文件拷贝、重新启动业务逻辑。
不得说说 Asp.Net,实践中我们知道无论是修改 Web.config 还是覆盖新的 dll,下次访问时站点会再次 JIT 编译,原理和刚才描述的大致相同,站点被复制到了特定临时文件夹,w3wp 通过额外的 AppDomain 寄宿了我们的站点。从这个意义 MVC 应用的插件化重点并不在于如何管理 AppDomain,路由注册、虚拟目录和视图查找才是重点,盗图一张帮助理解。

插件系统中的异常处理是不小的话题,自定义 AppDomain 里的异步线程下未处理异常是进程 Crash 的罪魁祸首——— w3wp 进程常常这么没了,而MAF 具有防止宿主崩溃的特性;而资源监控和分配需要更深入的实践。
以上代码已在项目中使用,稍后整理了丢上来。
Jusfr 原创,转载请注明来自。
免责声明:本站部分内容、图片、文字、视频等来自于互联网,仅供大家学习与交流。相关内容如涉嫌侵犯您的知识产权或其他合法权益,请向本站发送有效通知,我们会及时处理。反馈邮箱&&&&。
学生服务号
在线咨询,奖学金返现,名师点评,等你来互动1.为什么需要插件化系统
  “编程就是构建一个一个自己的小积木, 然后用自己的小积木搭建大系统”。
  但是程序还是会比积木要复杂, 我们的系统必须要保证小积木能搭建出大的系统(必须能被组合),有必须能使各个积木之间的耦合降低到最小。
  传统的程序结构中也是有模块的划分,但是主要有如下几个缺点:
    a: c++二进制兼容
    b: 模块对外暴露的东西过多,使调用者要关心的东西过多
    c: 封装的模块只是作为功能的实现者封装,而不是接口的提供者
    d: 可替换性和可扩展性差
  而插件式的系统架构就是为了解决这样的问题。插件化设计的优点?插件化设计就是为了解决这些问题的,所以以上的缺点就是咱的优点
2.插件化系统的原理
  指导性原则:“面向接口编程而不是实现编程”
  其接口的定义为interface, 其实转换一下的意思是面向纯虚类编程,当然也可以包装成面向服务和组件编程。
&&&&   如我可以这样定义一个接口(interface)
interfacecptf IRole{
  virtual
cptf ::ulong getHealth() = 0;
  virtual
cptf ::ulong getHurt() = 0;
  virtual
wstring getName() = 0;
  插件的目标就是实现IRole, 业务层的目标就是调用IRole, 业务层不知道IRole具体是如何实现的,而实现者也不用关心业务层是如何调用的。
3.插件化系统的目标
  1). 使用者能通过规范,开发自己的插件,实用已有的插件,插件又能控制对外暴露的内容。
&&&&   2). 运行时候能动态安装、启动、停在、卸载
&&&&   3). 每一个插件提供一个或多个服务,其他插件是根据接口来获取服务提供者
4. 一个插件化系统应该是怎么构成的
  OSGI,Java中影响力最大的插件化系统就是OSGI标准
&&&&&&&   OSGI的定义:The dynamic module system for java
  借鉴osgi对插件系统的定义,我认为一个典型的插件系统应该有如下几个方面构成:
  “基础库+微内核+系统插件+应用插件”
  其中微内核 负责如下功能:
&&&&     1、 负责插件的加载,检测,初始化。
&&&&     2、 负责服务的注册。
&&&&     3、 负责服务的调用。
&&&&     4、 服务的管理。
5. 一个简单场景的随想
&&   比如设计下如下的游戏场景:一个RPG游戏, 玩家控制一个英雄,在场景中有不同的怪物,而且随着游戏的更新,
  英雄等级的提升又会有不同的怪物出现, 这里就想把怪物设计为插件。
  首先工程是这样的布局的
首先要在做的是定义接口, 这里我需要一个英雄的接口,有需要一个怪物的接口。
interfacecptf IHero :
public cptf ::core:: IDispatch
&&&&&&&&&&&&&&&&&&&&&&&&&&&,
public IRole {
&&&&&&virtual&&&
cptf ::ulong attack() = 0;
interfacecptf IOgre :
public cptf ::core:: IDispatch
&&&&&&&&&&&&&&&&&&&&&&&&&&&,
public IRole {
然后作为插件我需要实现一个Hero, 和多个Ogre
Hero : public
ServiceCoClass&Hero &
&&&&&&&&&&&&&&&&,
public ObjectRoot &SingleThreadModel&
&&&&&&&&&&&&&&&&,
public cptf ::core:: IDispatchImpl&IHero &{
Wolf : public
ServiceCoClass&Wolf &
&&&&&&&&&&&&&&&&&&&&&&&&&&&,
public ObjectRoot&SingleThreadModel &
&&&&&&&&&&&&&&&&&&&&&&&&&&&,
public cptf::core ::IDispatchImpl& IOgre&
Tiger : public
ServiceCoClass&Tiger &
&&&&&&&&&&&&&&&&&&&&&&&&&&&,
public ObjectRoot&SingleThreadModel &
&&&&&&&&&&&&&&&&&&&&&&&&&&&,
public cptf::core ::IDispatchImpl& IOgre& 
最后,在主工程用我要用到这些插件
void BattleMannager ::run()
hero_ = static_cast&IHero *&(serviceContainer_. getService(Hero_CSID , IHero_IID));
if (!hero_ )return;
printHero(hero_ );
list&IService *& services = serviceContainer_ .getServices( IOgre_IID);
list&IOgre *& ogres = CastUtils::parentsToChildren &IService, IOgre&(services );
for_each(ogres .begin(), ogres.end (), bind(&BattleMannager ::printOgre, _1));
services = serviceContainer_ .getServices( IHumanOgre_IID);
list&IHumanOgre *& hummanOgres = CastUtils::parentsToChildren &IService, IHumanOgre&(services );
for_each(hummanOgres .begin(), hummanOgres.end (), bind(&BattleMannager ::printHumanOgre, _1));
  以上, 因为逻辑层和插件实现层都已经好了, 整个流程也已经跑通,但是还是的疑问:服务是怎么加载的?
6. 如何进行插件的加载以及服务的注册
& & 借鉴OSGI, 我这里把系统设计为bundle+service的组合。 bundle是service的容器,service是功能的具体实现者。
  在windows下,bundle用dll来表示。
& &&那bundle在windwos下加载就很简单了LoadLibrary Api就行了& &
& & 但是再c++中dll的接口还必须要考虑的一个问题就是c++的二进制兼容性:现在没有标准的 C++ ABI。这意味着,不同编译器(甚至同一编译器的不同版本)会编译出不同的目标文件和库。这个问题导致的最显而易见的问题就是,不同编译器会使用不同的名称改写算法。这样对插件的接口来说是致命的。当然我们可以用c api来作为接口,但是这样势必会对整体的设计产生影响,而且作为一个装B的c++程序员,我们怎么能容忍要借用低级语言的特性来实现我们的功能呢。当然幸亏还有另外一种方式,那就是虚表。当然不是所有的c++编译器对虚表的实现也是不一样的(好吧~~),但是至少主流(多主流~~不能确定)的编译器虚表都是在对象的第一个位置。好吧,现在决定用虚表来对插件接口的实现了,所以我们就可以用这样的方式来计算具体实现类的地址了
CPTF_PACKING 8
#define cptf_offsetofclass (base, derived) \
(( cptf::ulong )(static_cast& base*&((derived *)CPTF_PACKING))- CPTF_PACKING)
  &哇,好神奇的代码, 这个是为什么呢。 这个就需要对c++内存对象模型需要深入得了解了,可能需要拜读&c++内存对象模型&,这里篇幅有限这里就不解释了。但是如果有看官想要问“你为什么这么天才能想出这样的写法?”,虽然我很想说我很天才,但是其实正是情况是我参考的atl中的源码,而且整个插件加载过程我都是山寨了atl中的相关代码的。&
& & 但是还是有一个问题, 在GameMain中,认识的是IHero, 根本不知道有个Hero的实现,所有可能有这样的代码IHero* hero = New Hero() 这样动作。
那我们要如何进行这样的new动作。 当然我们说Hero是在Role dll中的, 在dll被加载的时候可以new Hero, 然后把hero对象的地址放到某个堆中,标志让GameMain使用。作为一个转换的伪设计人员, 我也是认为这样会有性能问题的, 我不仅要做到加载, 还要做到懒加载。
& & 那如何做到懒加载呢?
& & 感谢微软,在vc++中有机制帮我们做到,在其他的编译器中也会有其他的实现,但是这里我们只做了vc++中的实现。
& & 首先声明一个自己的段,段名可以叫cptf:
#pragma section (&CPTF$__a&, read, shared )
#pragma section (&CPTF$__z&, read, shared )
#pragma section (&CPTF$__m&, read, shared )
  然后在编译的时候,把具体实现的类的Create函数地址放到这个段中
#define CPTF_OBJECT_ENTRY_AUTO (class) \
  __declspec(selectany ) AutoObjectEntry __objMap_##class = {class::clsid (), class:: creatorClass_::createInstance }; \
  extern &C& __declspec( allocate(&CPTF$__m& )) __declspec(selectany ) AutoObjectEntry* const __pobjMap_ ##class = &__objMap_ ##class; \
  CPTF_OBJECT_ENTRY_PRAGMA(class )
 最后在加载的时候,变量这个段,如果csid命中,则调用Create方法
inline bool cptfModuleGetClassObject( const CptfServiceEntities * cpfgModel
, const cptf::IID & csid
, const cptf::IID & iid
, void** rtnObj)
bool rtn (false);
assert(cpfgModel );
for (AutoObjectEntry ** entity = cpfgModel-&autoObjMapFirst_
entity != cpfgModel -&autoObjMapLast_; ++entity)
AutoObjectEntry* obj = *
if (obj == NULL) continue;
if (obj -&crateFunc != NULL && csid == obj-& iid){
= obj -&crateFunc( iid, rtnObj );
  总结下流程:
    1. GameMian使用的是IHero,
    2. Hero是IHero的实现者,在编译的规程中,把Create Hero的方法编译到固定段中
    3. GameMian进行new的时候其实调用的是Dll固定段中的函数地址
    4. 利用 上面的cptf_offsetofclass 宏实现对IHero的
7. 服务的管理
  每一个服务都需要一个id来标志它, 这里就用guid, 命名为IID---interface id
  每一个服务的实现者也必须要有id来标志, 这也是一个guid, 命名为csid
  我们把服务和服务实现者的管理信息用配置文件管理起来,services.xml, 对Hero的定义
&bundle&Role.dll&/bundle&
&csid&-7c2a-11e3-8c28-bc305bacf447&/csid&
&description&hero&/description&
&name&Hero&/name&
&serviceId&99f9dd8f-7c1a-11e3-9f9d-bc305bacf447&/serviceId&
&serviceName&IHero&/serviceName&
&/service&
  当然一个插件的管理器也是必须的, 管理Service的注册,缓存,析构、获取,查询等。这里用ServiceContainer实现
8. 基于插件的架构
& &&基于插件系统的架构:
& & &主要分三部分: 1. 使用其对象模型的主系统或主应用程序
& & & & & & & & & & & & &2. 插件管理器
& & & & & & & & & & & & &3. 插件
& & 所有的插件但是从IService, 是参考Com中IUnkown
interfacecptf IService{
cptf ::ulong addRef() = 0;
virtual cptf ::ulong release() = 0;
virtual bool queryInterface( const cptf ::IID& iid, void**rntObj ) = 0;
  其实插件的内核并不复杂,复杂的是对插件接口的定义和封装,如何根据不同的业务场景抽象出不同的interface。
&  本文不是很水的理论,所有的理论都是经过代码验证的。&
& & &本文涉及到的代码在我的github上,
& & &工程的目标是建立一个跨平台的c++插件开发框架, 现在的是一个能成功在vc++下运行demo的插件化framework
& & &用了boost和stl,如果要深入了解core中的代码,还需要对模板有了解, 水深请勿轻易尝试
& & &当然有的看官会对core中的代码非常熟悉,那可能你发现了, 我是山寨atl实现的
8. 今后改进的方向
& & &1. service如何释放, 还在考虑是用野指针还是智能指针还是垃圾回收机制
& & &2. 错误处理
& & &3. 跨平台和跨编译器
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:9632次
排名:千里之外
原创:41篇
(1)(2)(1)(1)(2)(2)(1)(1)(8)(1)(2)(4)(2)(5)(6)(2)(8)(1)

我要回帖

更多关于 cs2系统异常 的文章

 

随机推荐