构建插件式的应用程序框架(三)----动态加载 - 纶巾客 - 博客园

mikel阅读(1091)

来源: 构建插件式的应用程序框架(三)----动态加载 – 纶巾客 – 博客园

不管你采用什么方式实现插件式的应用程序框架,核心还是动态加载,换句话说,没有动态加载技术也就无所谓插件式的应用程序框架了。使用Com实现的话,你可以利用Com的API通过ProgID来动态创建COM对象,如果使用普通DLL,你需要使用Windows 的API函数LoadLibrary来动态加载DLL,并用GetProcAddress函数来获取函数的地址。而使用.NET技术的话,你需要使用Assembly类的几个静态的Load(Load,LoadFile,LoadFrom)方法来动态加载汇集。
一个Assembly里可以包含多个类型,由此可知,一个Assembly里也可以包含多个插件,就像前一篇文章所讲,只要它从IPlugin接口派生出来的类型,我们就承认它是一个插件类型。那么Assembly被动态加载了以后,我们如何获取Assembly里包含的插件实例呢?这就要用到反射(Reflection)机制了。我们需要使用Assembly的GetTypes静态方法来得到Assembly里所包含的所有的类型,然后遍历所有的类型并判断每一个类型是不是从IPlugin接口派生出来的,如果是,我们就使用Activator的静态方法CreateInstance方法来获得这个插件的实例。.NET的动态加载就是这几个步骤。下来,我做一个简单的例子来演练一下动态加载。首先声明一点,这个例子非常简单,纯粹是为了演练动态加载,我们的真正的插件式的应用程序框架里会有专门的PluginService来负责插件的加载,卸载。
我们的插件位于一个DLL里,所以我们首先创建一个Class library工程。创建一个FirstPlugin类让它派生于IPlugin接口,并实现接口的方法和属性,由于本文的目的是演示动态加载,所以IPlugin接口的Loading事件我们就不提供默认的实现了,虽然编译的时候会给出一个警告,我们不必理会它。这个插件的功能就是在应用程序里创建一个停靠在主窗体底部的ToolStrip,这个ToolStrip上有一个按钮,点击这个按钮,会弹出一个MessageBox显示“The first plugin”。下面是代码:
using System;
using System.Collections.Generic;
using System.Text;
using PluginFramework;
using System.Windows.Forms;

namespace FirstPlugin
{
public class FirstPlugin:IPlugin
{
private IApplication application = null;
private String name=””;
private String description = “”;

IPlugin Members
}
}

接下来我们创建一个Windows Application工程让主窗体派生于IApplication接口并实现IApplication接口的方法和属性,下来我们声明1个MenuStrip和1个StatusStrip,让他们分别停靠在窗口的顶部和底端,接下来我们声明4个ToolStripPanel,分别人他们停靠在上下左右四个边,最后我们创建一个ToolStrip,在上边添加一个按钮,当点击这个按钮的时候,我们动态的加载插件。
为了方便演示,我们把生成的Assembly放置到固定的位置,以方便主程序加载,在本例里,我们在应用程序所在的文件夹里创建一个子文件夹Plugins(E:\Practise\PluginSample\PluginSample\bin\Debug\Plugins),将插件工程产生的Assembly(FirstPlugin.dll)放置在这个子文件夹。下面是动态加载的代码:

private void toolStripButton1_Click(object sender, EventArgs e)
{
//动态加载插件,为了方便起见,我直接给出插件所在的位置
String pluginFilePath = Path.GetDirectoryName(Application.ExecutablePath) + “\\plugins\\FirstPlugin.dll”;
Assembly assembly = Assembly.LoadFile(pluginFilePath);

//得到Assembly中的所有类型
Type[] types = assembly.GetTypes();

//遍历所有的类型,找到插件类型,并创建插件实例并加载
foreach (Type type in types)
{
if (type.GetInterface(“IPlugin”) != null)//判断类型是否派生自IPlugin接口
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);//创建插件实例
plugin.Application = this;
plugin.Load();
}
}

}

我把完整源代码也附上,方便大家使用:源代码下载

获取Ecshop各个属性的库存的简单方法

mikel阅读(842)

原理:从ecshop数据库提取各个属性的库存,放到一个函数里,然后用模板调用出来。 需要修改的ecshop文件: 1.主题模板goods.dwt 2.includes下的lib_goods.php Step 1. 修改goods.dwt 在294行左右,原理:从ecshop数据库提取各个属性的库存,放到一个函数里,然后用模板调用出来。

需要修改的ecshop文件:

1.主题模板goods.dwt

2.includes下的lib_goods.php

 

Step 1.

修改goods.dwt

在294行左右,  <!– {foreach from=$spec.values item=value key=key} –>和    <!– {/foreach} –>之间,增加(库存:{$value.product_number} {$goods.measure_unit})

 

Step 2.

修改includes下的lib_goods.php

在625行/* 获得商品的规格 */中,修改成如下样子:红色部分是增加的调用

 

$SQL = “SELECT a.attr_id, a.attr_name, a.attr_group, a.is_linked, a.attr_type, “.
“g.goods_attr_id, g.attr_value, g.attr_price ” . “,p.product_sn,p.product_number ” .
‘FROM ‘ . $GLOBALS[‘ecs’]->table(‘goods_attr’) . ‘ AS g ‘ .
‘LEFT JOIN ‘ . $GLOBALS[‘ecs’]->table(‘attribute’) . ‘ AS a ON a.attr_id = g.attr_id ‘ .
‘LEFT JOIN ‘ . $GLOBALS[‘ecs’]->table(‘products’) . ‘ AS p ON g.goods_attr_id = p.goods_attr ‘ .
“WHERE g.goods_id = ‘$goods_id’ ” .
‘ORDER BY a.sort_order, g.attr_price, g.goods_attr_id’;
$res = $GLOBALS[‘db’]->getAll($SQL);

$arr[‘pro’] = array(); // 属性
$arr[‘spe’] = array(); // 规格
$arr[‘lnk’] = array(); // 关联的属性

foreach ($res AS $row)
{
$row[‘attr_value’] = str_replace(“\n”, ‘<br />’, $row[‘attr_value’]);

if ($row[‘attr_type’] == 0)
{
$group = (isset($groups[$row[‘attr_group’]])) ? $groups[$row[‘attr_group’]] : $GLOBALS[‘_LANG’][‘goods_attr’];

$arr[‘pro’][$group][$row[‘attr_id’]][‘name’] = $row[‘attr_name’];
$arr[‘pro’][$group][$row[‘attr_id’]][‘value’] = $row[‘attr_value’];
}
else
{
$arr[‘spe’][$row[‘attr_id’]][‘attr_type’] = $row[‘attr_type’];
$arr[‘spe’][$row[‘attr_id’]][‘name’] = $row[‘attr_name’];
$arr[‘spe’][$row[‘attr_id’]][‘values’][] = array(
‘label’ => $row[‘attr_value’],
‘price’ => $row[‘attr_price’],
‘format_price’ => price_format(abs($row[‘attr_price’]), false),
‘id’ => $row[‘goods_attr_id’],
‘product_sn’ => $row[“product_sn”],
‘product_number’ => $row[“product_number”]);
}

 

 

[转载]ASP.NET MVC (Razor)开发 总结与分享 - sheng.chao - 博客园

mikel阅读(719)

来源: [转载]ASP.NET MVC (Razor)开发 <> 总结与分享 – sheng.chao – 博客园

过去我们使用过一些周报工具来完成项目组或部门的周报填写与考核工作,但多少有些不理想,要么功能太过简单,要么功能特别繁杂,不接地气,使用不便。

后来我们就考虑自己开发一个简单的,实用的,易用的,接地气的周报填报考核系统。

 

技术选型:

ASP.NET MVC + Razor 视图引擎,JQuery,数据库 SQL Server 2008 R2

微软企业库

数据访问层使用了自有ORM解决方案

 

这是一个比较简单和基础的系统,使用的技术也都非常基础,下面我将对开发过程进行一个简单的回顾和总结。

 

关于 ASP.NET MVC

开发效率非常的高,对于企业级应用来说,能够轻易的构建出健壮,完善的项目,并能够使代码拥有非常好的可读性。对于有WEB开发基础的人来说上手基本没有难度, 再加上微软的产品一向是给正常人类使用的,所以掌握起来非常的 Easy,当然,要做出出色的WEB应用,深入学习和理解是必不可少的,师傅领进门,修行在个人。

 

关于 Razor 视图引擎

对于企业级开发来说,Razor 最大的优点是出色的生产性与极高的代码可读性,可维护性。如果你没有接触过,可以先简单的把 Razor 理解为一种强类型的模版引擎(当然这个理解并不精确),整个 View ,就是一个 HTML 模版,在此模版中,你可以使用强类型的方式,访问与 View 关联的 Model,不论是在 HTML 代码中,还是 JavaScript 代码中;还可以通过一系列辅助类,动态输出 HTML 或者 JavaScript 代码, Razor 与这两者有极高的交互性。

 

关于 JQuery

没啥特别说的,生产性+++

 

关于ORM

在本系统中,使用了弱类型的自有ORM系统,在之前的博文中我做过详细的介绍。

 

表结构设计

除了基本的组织机构,角色,人员,权限这些表以外,周报的表使用了一对多的两张关进行关联,一张主表用于存储周报主要信息,子表用于存储周报的内容条目,也就是说一个周报,是多条数据组织汇总起来的。

为了方便对数据的统计分析和查询,周报表中对周次,日期,年月等做了冗余。

 

此外,周报系统一个通用的底层功能就是对周次的计算,直接贴代码


/// &lt;summary&gt;
/// 获取指定日期,在为一年中为第几周
/// &lt;/summary&gt;
/// &lt;param name="dt"&gt;指定时间&lt;/param&gt;
/// &lt;reutrn&gt;返回第几周&lt;/reutrn&gt;
public static int GetWeekOfYear(DateTime dt)
{
GregorianCalendar gc = new GregorianCalendar();
int weekOfYear = gc.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
return weekOfYear;
}

/// &lt;summary&gt;
/// 获取指定日期为当月的第几周
/// &lt;/summary&gt;
/// &lt;param name="dt"&gt;&lt;/param&gt;
/// &lt;param name="weekStart"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static int GetWeekOfMonth(DateTime dt)
{
int weekStart = 1;

//WeekStart
//1表示 周一至周日 为一周
//2表示 周日至周六 为一周
DateTime FirstofMonth;
FirstofMonth = Convert.ToDateTime(dt.Date.Year + "-" + dt.Date.Month + "-" + 1);

int i = (int)FirstofMonth.Date.DayOfWeek;
if (i == 0)
{
i = 7;
}

if (weekStart == 1)
{
return (dt.Date.Day + i - 2) / 7 + 1;
}
if (weekStart == 2)
{
return (dt.Date.Day + i - 1) / 7;

}
return 0;
//错误返回值0
}

/// &lt;summary&gt;
/// 计算本周起始日期(礼拜一的日期)
/// &lt;/summary&gt;
/// &lt;param name="someDate"&gt;该周中任意一天&lt;/param&gt;
/// &lt;returns&gt;返回礼拜一日期,后面的具体时、分、秒和传入值相等&lt;/returns&gt;
public static DateTime CalculateFirstDateOfWeek(DateTime someDate)
{
int i = someDate.DayOfWeek - DayOfWeek.Monday;
if (i == -1) i = 6;// i值 &gt; = 0 ,因为枚举原因,Sunday排在最前,此时Sunday-Monday=-1,必须+7=6。
TimeSpan ts = new TimeSpan(i, 0, 0, 0);
return someDate.Subtract(ts);
}

/// &lt;summary&gt;
/// 计算本周结束日期(礼拜日的日期)
/// &lt;/summary&gt;
/// &lt;param name="someDate"&gt;该周中任意一天&lt;/param&gt;
/// &lt;returns&gt;返回礼拜日日期,后面的具体时、分、秒和传入值相等&lt;/returns&gt;
public static DateTime CalculateLastDateOfWeek(DateTime someDate)
{
int i = someDate.DayOfWeek - DayOfWeek.Sunday;
if (i != 0) i = 7 - i;// 因为枚举原因,Sunday排在最前,相减间隔要被7减。
TimeSpan ts = new TimeSpan(i, 0, 0, 0);
return someDate.Add(ts);
}

最终完成功能与效果:

 

一:周报的填写;

1)周报中的“工作类型”、“工作任务”、“完成情况”作为基础数据自由配置;

2)“工作类型”、“工作任务”只需简单设置名称即可,无需繁杂的参与人负责人进度时间等数据,术业有专攻,这部分工作交给项目管理软件处理,此外,周报与绩效考核的使用场景不单单是项目研发,行管团队或传统企业一般不需要有“项目”的概念,只需一个工作分类即可。

3)可按组织机构或按个人对周报进行查询;

二:考核功能

1)可自由设置人员之间的考核关系,不和组织机构绑定,因为多数时候项目领导或部门领导都不会直接考核每一个人,另外也存在人员临时借调的情况,与组织机构并不一定完全一致;

2)考核项可自由配置,可根据自身需求自行添加要考核的项,如工作完成情况,工作质量,是否加班等,考核结果可设置分值,用于绩效统计;

三:统计

1)根据指定的时间段,对指定组织机构中的人员经过考核的周报进行汇总,并将考核得分累计排名,人员的工作绩效情况一目了然。

四:其它

完整的组织机构管理,人员管理,角色权限管理功能,可在此基础上继续开发。

 

我把已经完成的功能部署在了公网上,可以访问地址:http://e.zkebao.com 查看

可以在此地址上免费使用,我会继续更新维护它,欢迎批评指正。

 

基于.NET MVC的高性能IOC插件化架构(二)之插件加载原理

mikel阅读(937)

上一篇博文简单介绍了下插件化的代码组成部门:http://www.cnblogs.com/gengzhe/p/4390932.html

这篇博客主要讲解下插件化实现的原理,先面先讲解几个概念:

一、契约

插件与系统必须有契约,系统才能发现插件并正确加载插件,我采用的是所有插件都实现Sun.Core里面的IPlugin接口。

二、自述

插件在被加载的时候,需要告诉系统,我是什么类型的插件,我的guid,我依赖的程序集,我的状态与权限,我的配置信息等等,这个行为是插件的自我描述,简称自述。

三、配置

插件必须能够进行配置(一般在安装的时候或者才系统启动之前进行配置),插件配置的使用过程大致为:系统发现插件-》加载插件-》加载插件配置文件-》把配置信息传入插件内实现了IPlugin接口的类对插件进行初始化。

—————————————————————————————

插件使用流程:插件开发(也可通过插件仓库下载需要的插件)-》插件zip文件上传-》配置插件-》启用插件

有了流程,我顺着流程一步一步分析实现原理:

一、插件开发

1.所开发的插件必须有一个类实现Sun.Core里面的接口,这个接口里面包含初始化、启动、停止、卸载几个方法,因为只有插件自己才了解自己,所以这些功能需要插件自己提供,系统只负责使用。

2.插件必须提供PluginConfig.Json文件,这里面的配置文件有固定的格式,用于向系统提供配置信息及保存用户的设置。

二、插件上传

step1:插件压缩为zip文件,上传保存到PluginZips文件夹下面。

step2:自动解压缩插件文件到PluginTemp(插件检测临时目录)。

step3:检测插件是否包含IPlugin的实现以及是否包含PluginConfig.Json文件,如果存在继续往下执行,如果不存在则删除临时文件以及zip文件,并反馈插件错误信息。

step4:通过插件配置文件,拷贝到Plugins(插件存放目录)下的相应插件类别目录下面。

…..(加载插件)

三、插件加载

1、系统启动加载

step1:遍历Plugins目录下面的所有插件文件,读取PluginConfig对象列表并把列表信息保存到插件管理器里面以便管理调用。

step2:筛选掉PluginConfig里面状态标识为error的插件。

step3:把插件程序集及依赖的不存在系统bin目录下的程序集加载到App_Data目录下的Plugins目录下(便于统一加载及不影响到原Plugins目录下插件的修改、删除)。

step4:加载Plugin程序集及依赖程序集(如果使用IOC,需要把程序集注册到IOC容器)。

step5:调用程序集下IPlugin的实现类,执行初始化,如果插件状态为启动,可直接启动插件。

2、上传插件加载(单个)

上传插件的加载与启动加载的不同之处在于前者不需要遍历,其它步骤一样。

备注:如果使用IOC容器,那么这里需要把新增的这些程序集注册到IOC容器并重置容器。

四、插件管理

因为在插件加载的过程中,插件的配置信息及IPlugin对象都已保存,这里就比较简单。通过列表可以筛选启动、停用、异常的插件,并可以直接调用IPlugin对象来启动、停用、卸载插件。

今天就简单介绍到这里,后续的文章会越来越细。

[转载]我的插件框架·前传 - 钟少 - 博客园

mikel阅读(1001)

来源: [转载]我的插件框架·前传 – 钟少 – 博客园

初识

话说很久很久以前,当我还是只菜菜鸟的时候,就说听过关于插件的传说。因为一直都是做富客户端模式的企业应用软件,所以对插件这种神奇的搭积木式的 挂接模式,无限向往之。再后来,听说了有关Eclipse的种种神奇传说,与日中天的名气令其在坊间一度被尊为神器,就连俺这么闭塞的.NET粉丝都能把 它的名字正确拼写出来,由此可见一番,终于在某个风高月夜伸手不见五指的大白天,俺把罪恶的手伸向了她那被万人膜拜的…

首先找了一些Eclipse的插件开发资料,硬着头皮看了些Java的代码后,始终不得要领,大部分的插件开发资料都属于依葫芦画瓢的讲解方式,而 对Eclipse插件架构的整体描述几无涉及,官方的资料中对整体架构的介绍也是粗略含糊,就我这Java门外汉来说,想要通过代码对其有高屋建瓴的理解 还真是不靠谱,苦熬之下也便不了了之。然哥早已不在江湖,却依然流传着Eclipse的传说……

时光荏苒,就在某个不经意的日子,突然晴天一声雷,一个名叫SharpDevelop的家伙半道杀出,在.NET社团掀起一阵骚动,趋之者无数,也再次撩动起我不甘寂寞的心,于是乎,历史再度上演…

可怜SharpDevelop官方几乎没有提供有什么价值的文档,网上的资料除了一点介绍性的文字外就剩旁边的广告链接了。也罢,直接看源码吧,也 没什么比C#代码更能讲清楚问题的了。SharpDevelop早期0.x版本的代码,坦白说,写得还真是那个啥,跟了跟它的代码,基本流程是清楚了,但 是对一些关键问题的疑惑还是没有解决(唉,看来俺的智商还是没有自诩的那么高啊,鄙视一下),问题主要有下:

1、插件节点(PluginNode)与插件(Plugin)以及代码子(Codon)这三角关系究竟隐藏着啥名堂?

2、Doozer(这个不知道咋翻译了)到底主司何职?它到底对Codon负责了什么?如果说他是Codon的构建器,那为啥Codon中还要有BuildItem方法,是不是功能重叠?

3、Codon中的BuildItem方法中还包含有ArrayList类型的subItems,难道是父Codon负责子Codon的创建?父创 建子,势必会造成父对子的依赖,如果这样的话,第三方开发人员如果想要基于已有插件进行功能扩展的话,已有插件是不可能了解子Codon的创建行为的,那 么这个subItems参数岂不是鸡肋或摆设?再则,如果父Codon只是调用子Codon的BuildItem方法,由子Codon自行构建,那么父如 何确保以正确的方式将子Codon挂接进插件树中?

4、插件如何获得环境信息?并如何确保扩展插件不会对依赖插件过度耦合?

5、消息通知机制如何处理?如何将插件自身提供的一些公共信息提供给其他插件共享?插件状态发生改变,如何以一种统一并无耦合的方式通知给感兴趣的其他插件?插件运行时平台如何将事件消息传递给订阅者,在传递过程中,如何以灵活的方式注入进去进行过滤处理?

6、是不是每个Codon都要有对应自己的Doozer?在很多时候,可能需要创建不同的对象,但是这些对象有很大的共性,只是创建过程和后续初始 化有差异,如何使用一个Doozer来做到这点?如果一个插件想提供一个服务给其他插件使用,其他插件是不是只能通过接口引用的方式进行解偶?这样的方 式,耦合度是不是还可以继续降低?

以上是当初的一部分问题,可能描述的还是笼统了,虽然陆续阅读源码,某些问题有了答案,但是对整个架构的理解还是不能很好的串联起来,故而就算能基 于SharpDevelop进行插件开发了,也总有暗箱操作的不安。另外,SharpDevelop中到处充斥着各种条件选择(Condition),尤 其是定义某些复杂Codon的时候,各种各样的Condition缠绕四处很是混乱不干净,当时虽说不出具体有啥特别不好,但却总觉有种坏味道隐隐围绕, 是不是应该有更好方式来解决Codon按条件进行挂载的问题呢?

带着这些问题,断断续续的纠结了一阵,我的惰性最终在时间的飞逝中发挥出它应有贡献,而江湖的风骚过后大家该吃吃该喝喝,歌舞升平依旧,社会一片和谐,总之形势大好。

 

起因

2008年上,经朋友介绍去到一家Mini型软件公司负责一个管理软件的产品开发,当初该老板介绍的前景不错,待遇也可以,俺屁颠屁颠的带上吃饭的家伙就杀过去了。由于对这个行业不了解以及过度自信,当初也跟着老板喊了不少口号、放了不少卫星(汗一个)。还不错,手下有两个程序员可以使唤(其中一个是从ACCP带过去的学生,小伙非常优秀,2009年底已经出来自己创业了,他们的Ideal非常棒,祝福他们),就这样,我这个技术架构师兼项目经理、主力程序员就这样荣耀登场了。

正如您的高见,我本敲代码者,很多时候不自觉就追求设计的完美去了,等绕了半天之后,才发现原来自己还是个项目经理,还有开发周期在候着呢,更杯具 的是,还得去收回当初信誓旦旦跟老板一起放出的那些卫星。通过这个事情,终于切身的体会到一个问题,如果没有制度上的制约,监督者与执行者又是一家人的 话,混乱与腐败就是必然的了(呵呵,和谐社会请勿过度联想)。OK,非技术问题在此就不深入展开了,如果是PLMM,则欢迎来电来函来家,彻夜深入探讨, 其他者请绕行。

在这个产品中,有个需求:不同的用户登录后,操作界面必须根据其角色进行自动调配,白话说就是用户登录后,如果其是收银员,那么那就只能操作收银窗 口及其只能看到或者使用相关的一些功能窗口的入口菜单或者导航树节点。另外,该产品分为不同的发布版本,有精简版、标准版、超级宇宙无敌奥特曼版等等,那 么在这些各种版本的发布中包含的功能窗口和模块是不一样的,也就是说,同一种东西要切成不同部分去兜售。看到这里,您或许已经告诉我该怎么做了:在登录成 功后的事件中写代码去隐藏相关菜单、导航树节点、主窗口的图形按钮等入口元素,将不同的模块放在不同的Library项目中,发布的相关版本引用相关程序 集即可。得幸,当初在下也是这么想的,但是、但是,依咱哥们的秉性,这种傻大笨粗的搞法实在太对不起架构师这个亮闪闪的头衔和Boss当初那饱含热泪殷切 期望的眼神了,再怎么地,也得想个稍微对得起咱名声的搞法,嗯,嗯,那个项目周期先缓缓,容俺想想,很快就好的了…

彗星撞地球,果然灵光一闪,插件、全面插件化的架构绝对是彻底、一劳永逸解决这个问题的根本性的宇宙无敌之方案!!!而且,它还可以带来很多别的好 处…譬如1、2、3、4… 我总得先说服自己吧.. 嘿嘿,既然这么多好处,那就赶快搞吧。嗯,嗯,那个项目周期,可以再探讨的嘛,这个东西的价值是非常可观的,而且还可以为今后公司所有的产品服务,虽然现 在还不知道其他产品在哪,但公司在迅猛发展总会有的噻…

 

初试

既然已经决定要搞,那么怎么个搞法就得先定下来,兵先阀谋还是知道的。做法有二:1、基于SharpDevelop的插件框架来做;2、自己设计开发。

如果采用策略一,首先得把SharpDevelop的核心插件库剥离出来窃为己用,但是必须确保对SharpDevelop有深入的掌控,对其设计 思想和架构模式做到了然于胸,以免发生后期无法实现的想法或出现解决不了的Bugs。诚然,要做到如上要求,短期内怕是不太可行,而且有过前些年看其源码 的惨痛经历,如今已是心有戚戚焉。

还是采用策略二吧,但首先面临的问题就是,从头搭建一个框架无论是时间成本还是技术风险都是难以预估的,但凡有点良知的兄弟都能看出来,俺要真这么 干,就有点利令智昏不知死活了,基本可以拉出去枪毙五分钟。嘿嘿,所幸俺还是能在激动关头保持最后一点理智的,再三权衡,最终决定先做个能满足目前需求的 这样一个界面层自动适应功能的类库出来,其他伟大的想法等日后择机再搞吧。

要满足将功能入口点动态挂载到主窗口中去,必须得定义一个配置文件,这个文件起码需要描述挂入的类型(如菜单、导航树、主窗口的桌面快捷图标)、目 标窗口的类型、挂载点的显示文本、图标等,不同的角色需要有不同的配置方案,那么可能还要包含一个对挂载配置进行选择的机制。嗯,差不多先就这些了,呵 呵,至此俺的想法还是这么纯朴,真为自己骄傲,没有引入其他脱离实际的高调想法进来,一切看起来都在控制之中,不错不错,暗爽一下先。

好了,就从这个配置文件开始吧,有了它,思路就会一步步清晰起来的,顺着这个思路往下理,后面的实现部分就快了。看起来是这个样子:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="themeManager" type="Zongsoft.Applications.Configuration.ThemeManagerSection, Zongsoft.Applications" />
    </configSections>
    <themeManager>
        <theme name="" resourceName="" resourceAssembly="">
            <menu name="BaseMenu" style="Menu" generatorType="Zogsoft.Applications.Themes.ThemeMenuElementGenerator, Zongsoft.Applications">
                <menuItem name="" text="" icon="" toolTip="" shortcut="" commandType="Zongsoft.Applications.WinForms.ShowFormCommand, Zongsoft.Applications">
                    <properties>
                        <add name="Command.TargetType" type="System.String" value="Zongsoft.AMS.Basic.FTimeSpan, Zongsoft.AMS" />
                        <add name="Command.ShowDialog" type="System.Boolean" value="False" />
                        <add name="Command.Singleton" type="System.Boolean" value="True" />
                        <add name="Constructor.ModuleName" type="System.String" value="" />
                        <add name="Constructor.DefaultDateTimePart" type="Zongsoft.AMS.Common.DateTimePart, Zongsoft.AMS.Common" value="Time" />
                        <add name="CustomProperty" type="System.Object" value="" />
                    </properties>
                </menuItem>
            </menu>
            <menu name="Navigator" style="Navigator" generatorType="Zogsoft.Applications.Themes.ThemeMenuGenerator, Zongsoft.Applications">
                <menuItem name="">...</navigate>
            </menu>
        </theme>
    </themeManager>
</configuration>

上述配置文件可包含多个主题,主题下面是具体菜单和菜单项,不同的菜单由style属性指定其对应的是菜单还是导航树抑或别的什么,在点击菜单或者 导航树的时候,通过菜单项的扩展属性集里面的Command.TargetType项指定的类型来创建对应的对象,可能不同的目标类型需要使用特定的构造 函数签名进行初始化,那么通过名称以“Constructor”打头的扩展属性项来指定构造函数的参数定义,当然,如果觉得这些还不够用,可以自定义一个 Generator类来解析这些配置内容,将该自定义的Generator类型名称放在菜单元素的generatorType属性中指定即可。需要定义的 类,大致如下:

public abstract class GeneratorBase
{
    //初始化生成器,必须指定其对应的
    protected GeneratorBase(Theme theme)
    {
        this.Theme = theme;
        /* ... more ... */
    }
    public Theme Theme
    {
        get;
        private set;
    }
    //根据菜单元素生成对饮的菜单或者树型节点之类的目标对象
    public abstract object Generate(MenuElement element);
}
public class Theme
{
    //该主题对象对应的<theme>配置元素
    private ThemeElement _element;
    internal Theme(ThemeManager manager, ThemeElement element)
    {
        this.Manager = manager;
        _element = element;
        /* ... more ... */
    }
    public ThemeManager Manager
    {
        get;
        private set;
    }
    //表示通过GeneratorBase的生成方法后激发,可以在外部进行其他附加处理
    public event EventHandler<ItemGeneratedEventArgs> Generated;
    //调用GeneratorBase的Generate方法生成目标对象,并将该目标对象与caller参数指定的容器进行粘合
    public object Build(object caller, string menuName);
    //对Build方法的复合处理
    public object[] BuildAll(object caller);
}
public class ThemeManager
{
    //根据配置节创建Theme类,并初始化资源服务
    public void Initialize(ThemeManagerSection section);
    //根据指定的资源项名称获取对应的资源对象
    public object GetResourceObject(string name);
    //同上方法的泛型版本
    public T GetResourceObject<T>(string name);
    //获取主题对象集合
    public ThemeCollection Themes
    {
        get;
    }
}</theme>

就这么三个核心类,基本可以应付目前这种需求了,代码也比较简单,就不再叽歪了。

 

插曲

.NET社团的老大,就是那个有点微微软的家伙,总喜欢在我们为自己取得一些积累的时候,突然抛一个新玩意出来刺激下我们衰弱的神经,顺带消灭我们 那些颇为自得的成绩,而社团内的众多粉丝又都是一帮热衷自残的家伙,于是乎,大家铺天盖日的一番跟风猛跑,杯具的是,俺也是这帮家伙中的积极分子。

就在.NET 3.5发布之际,俺盯上了那个传说中叫作 System.Addin 的美女,不关三七二十八呼啦冲上去就是一阵蹂躏。唉,传说与现实往往是相对的,又或许别人眼中的美女跟自己根本就是阻抗严重不匹配吧,总之,经过俺一番彻 底细致的摆弄之后,发现这个东东压根不是俺臆想中的样子,这使得我早已疲惫的心再次惨遭无情痛击。

在 MAF(System.Addin)中,插件与插件宿主之间需要通过严谨的Contract来进行阻抗匹配,宿主与插件内部都有各自的视图用以隔离实现与 通讯协定,而各自的视图再通过对应的适配器与之协定进行转换。这个高度繁琐的模型就是为了确保插件与宿主之间阻抗的正确匹配,还可以在不影响宿主或外接程 序的情况下更改适配器和协定。想法是好的,设计是糟糕的,在没有亲手写代码去实验的情况下,要想把这么绕的理念整明白,还是有些难度的,为了搞清楚这到底 是怎么回事,俺只好先身体力行的按照MSDN上的指引,一步步把Demo重做了一遍,虽然Demo期间我无数次的问候过一些人,但还是咬牙坚持了下来,一 个小Demo总共需要7个项目,就算除掉Host之外也还有6个,如果你做完Demo后还能神志清醒的淡定高呼一声“Microsoft”的话,那么恭喜 你,本年度最佳盖子奖非您莫属。

 

一不小心,咱也奔三了,梦想中的插件框架一直萦绕心头已成为挥之不去的梦魇。这期间又做过一些项目,对俺上次做的那个简单的主题类库也进行了一番升级改造,但是,底层设计思想决定了高层结构,胚子太丑再怎么拾辍始终还是装不出美女来,不进行基因变异,是没有出路的。

 

前戏

老天爷突然睡醒,一摆手丢了个机会下来,不偏不倚正好砸在白天没啥鸟事,晚上鸟没啥事的本少爷头上 ——嘿嘿,有个哥们找我做个富客户端的企业应用项目。赶紧把俺那套混饭的家伙拾掇拾掇开工干吧,一切按部就班,也没啥叛逆的想法就盼着早点把东西做完交活 收钱,然天有不测风云,人有发癫打狂,就在一切顺风顺水的时候,突然地,看着那个主题类库就莫名开始抽经,越看越郁闷,越郁闷就越想废掉它。这次,这个项 目时间非常宽松,基本任由着我的性子来,怎么办?要不要搞?到底是要还是要?!呵呵,以前那么多闲淡时间因为没有做项目,就没觉着这个东西还是那么重要, 这次切肤之不爽,再不彻底把她整服帖了,以后还是不好混啊,而且MAF这个不靠谱的家伙也让我彻底死心了。

承蒙天意,皇恩浩荡,俺在历经一阵痛苦的抽搐之后,终于痛下决心!某位非著名人士有云:人生的重大事件往往都是短短几分钟内决定的。

 

把玩Unity

根据以往搭建那个主题类库的经验,我首先面临一个对象动态创建和依赖注入的问题,因为各种组件类必须动态创建,这个问题好办,一般使用反射(Reflection)就可以轻松拿下,但是当类之间存在彼此依赖的话,那么这个问题就变得不太好办了,而且在面临多种注入方式(构造子注入、属性注入、方法注入), 甚至在构造出对象后,还需要自动调用一些非注入性的初始化方法或者响应处理一些事件的话,这个问题就变得相当棘手了。所幸我对Unity这位仁兄略有耳 闻,知道他能够帮我处理这些非插件框架核心但却是基本功能的问题,不过对于如何使用Unity与插件框架进行整合,我还没有清晰的思路,更让我抑郁的是, 至今还没听说过SharpDevelop或者Eclipse中集成相关IoC容器的做法,我不确定我这个想法的方向是否正确,而且以前从 SharpDevelop的源码中也没看到过类似这么复杂的创建或依赖注入的代码。怀疑终归是怀疑,我还是需要一点论证,至少我需要仔细了解下Unity 的内部构造,起码也要看看能不能从另外一个角度帮我理清下思路。

先上博客园找了一些介绍Unity的文章做到心中有数,一番风卷残云般的快速阅读,大致理念弄清楚了。再从Unity的官方网站下载最新源码,对照一些朋友写的剖析Unity的文章(看来跟我一样喜欢扒源码的窥窃男不在少数哈), 一边看源码一边对资料,对着对着发现路子不对,因为这些兄弟写的文章基本是依照老版本写的,本着从高从新的伟大理念,也懒得去找老版源码了,就这么凑合看 吧,基本把ObjectBuilder的东西看了一遍,大致意思是明白了,主要是Strategies指使Policies去干活,由Lifetime负 责管控对象的生命周期和提供容器,一些细节如定位查找等交由Locator去打理,ObjectBuilder只是个对外的前台小妹。 UnityContainer内部把对象创建和管理的工作都交给ObjectBuilder处理,自己专注其他辅助功能,譬如配置管理、注入依赖的关联操 作并由此引申出来的一大坨杂七杂八的事项。看到这里,基本也没兴趣深挖下去了,因为跟我想要的插件思想没有关系,看来这位仁兄不能带给我额外的思路,而 且,如果一个插件框架需要包含一个这么复杂的构建容器是不是有些本末倒置之嫌,这不符合我历来讲究的精巧优雅的架构原则,嗯,可以把它排除了。

 

细探Addin

锁定SharpDevelop,不把她拿下绝不睡觉!

老套路,还是从她的官方网站(http://www.icsharpcode.net/opensource/sd/)下载最新源码,乖乖,现在都3.2版本了,已经长成大姑娘了,嘿嘿~ 文档还是一如既往的精炼,官方论坛的一些零星讨论不能解决我的大问题,只好把那本已经压在床脚多年的《C#软件项目开发全程剖析——全面透视SharpDevelop软件的开发内幕》(http://www.china-pub.com/13944)一书翻出来,在杀死我一些脑细胞之后,除了令我比以前更抑郁外,就是使我更加怀疑自己到底有没有学过中文,这些年说得是不是火星语。

熟练的扒掉她的外套,仔细阅读源码,这次汲取上次的失败,不再从StartUp项目入手而是打开她的AddIn-Scout工具,高瞻远瞩的从整体 结构布局入手,一边用该工具查看插件结构,一边对照ICSharpCode.SharpDevelop.addin源文件,一点一点的摸索她的规律,再结 合这些年自己断断续续的一些所思所得去推敲她为什么要有一个这样的结构,这些结构究竟是为了解决什么问题,她的核心思想是什么。在深夜里俺不怀好意的死盯 她的内部结构,时而跟踪看看类的定义,时而看看对应的Addin文件定义项,时而停下来思考下为什么的问题,终于在自虐了数个不眠之夜、耗光多包‘红色利 群’和槟榔后,被俺想通了几个关键问题,譬如那个非著名的暧昧三角关系以及这些内部构件之间的协同作战问题,同时也想明白了为什么插件架构不需要使用像 Unity这样的专业IoC框架来处理构件的创建和依赖注入的问题。

 

主题

前面调戏了这么久,再不切入主题就该挨埋怨了。感谢各位东西方仙界朋友的保佑,费了九牛二虎之力把那些关键问题想通之后,再不果断下手就不只是挨埋怨那么简单了,让人怀疑能力有问题那才真麻烦呢。

开搞

在本文的「初识」部分谈到过我对SharpDevelop的一些不爽之处,除此之外还有个令我一直如鲠在喉的不快,那就是对Codon和 Doozer这些东西的命名甚感纠结,虽然SharpDevelop开发者在书中也谈到过这个命名问题,但我还是认为这些名字太过生僻。一个好的命名会让 人望文生义,这也是我得到偏执者名号的缘由之一。所以,我慎重决定重新厘清这些庞杂系统的概念一并更新命名。首先附上主要的概念性名字解释(含英文名):

  • 插件(Plugin):该英文单词有Plugin和PlugIn的两种写法,前种写法表专有名词,后种为词组,为了照顾编码时书写的整洁 度以及.NET通用的命名规则,而采用Plugin写法。插件是插件框架中的逻辑组织单位,可以对应到一个或多个程序集(Assembly),每个插件必 须对应一个XML格式的插件定义文件(*.plugin),一个插件由众多构件(Builtin)组成,插件定义文件详细描述该插件内各个构件的组合关 系。
  • 构件(Builtin):英文Built-in原意为“内置件、零件”,在此将该词组进行合并引申为构件、 部件之意,这个意思非常符合其在插件框架中的角色定位,该元素完全等同于SharpDevelop中的Codon。构件为插件(Plugin)的组成部 分,其与插件的关系就好比人体器官(如心脏、头、四肢)与人体,其不能脱离插件独立存在,也是基本功能的具体载体,众构件(Builtins)必须通过插 件定义文件将其发布到插件框架中。
  • 插件树(PluginTree):该英文为词组,遵循.NET开发规范对类 名、方法名、属性名等一律使用Pascal命名规范,下同。首先必须高调澄清一个事实,插件树并不是对插件进行数据结构定义的概念,虽然我在这个地方也一 度陷入SharpDevelop中的泥潭难以自拔。插件树是对构件(Builtin)对象在内存中的一种树型数据结构的定义,之所以要称之为 PluginTree而不是“BuiltinTree”,那是因为它不只是负责管理某一个插件内的所有构件定义,而是全局唯一的,要负责管理整个运行时所 有插件内的构件。
  • 插件树节点(PluginTreeNode):顾名思义这个元素描述插件树中的某个节点的数据 结构,主要包含两个构成:1、该节点在插件树中所处的路径(Path);2、该节点对应的构件(Builtin)。一个节点只对应一个构件对象,通过它我 们可以在插件树中任意导航。
  • 构建器(Builder):注意区分该中文名词与Builtin的中文名中的“件” 和“建”字。该概念大致类似于SharpDevelop中的Doozer,它的唯一功能就是根据插件框架提供给它的构件对象生成真实具体干实事的目标对 象。正是因为这个东西的存在,插件框架才能彻底摆脱对庞杂的IoC框架的依赖,因为它非常清楚它要构建的对象是什么。但是,我当初的一个主要疑惑就是当它 要构建对象的构造函数参数需要引用插件平台的一些公共服务,譬如当前的登录用户名、或者数据访问服务接口对象,或者该参数还需要引用另外一个插件中的构件 对象实例以及这个构件实例中的某个属性值,那么这个构建器如何以一种简单优雅的方式去获得这些依赖对象?再次感谢万能的主,因为插件架构的这种树型数据结 构,导致了她天然就有解决这个棘手问题的良方,而且是以一种异常优美简洁的方式来实现的,呵呵,稳住稳住,注意控制好节奏,想想美妙的序曲才刚开始,后面 还有高潮部分在等着咱呢。
  • 类型(Type):该元素是本人在设计该插件框架时思忖许久决定引入的一个新概念,跟我设计数据访问框架一样,我的一个原则就是尽量利用.NET中 现有或者业界主流设计思想中的概念,非万不得已才引入额外的设计元素,将易用性和广泛适用性原则始终摆在框架设计的最高级别。该元素是为了解决构建器创建 构件对应的目标对象时,需要使用到对一些非插件框架服务以及其他插件所能提供的常量型数据的问题。该元素只作为构件的子元素出现在插件定义文件中,是对构件目标类型的一种补充描述。

    好吧,就算可以引入该元素,但我还是要找到能100%说服自己有绝对充分的理由和必要。为什么SharpDevelop中没有包含类似的概念?经过 我的一番努力查找源码,发现SharpDevelop这种应用主要是处理开发工具这种需求,在这种应用中基本没有在企业管理软件中一个窗体在构建过程中需 要使用一些固定常量的情况,OK,这个理由不够说服力,因为我无法从根本上枚举出所有的需求情景,那么理由二:对于这种简单常量的初始化参数如果也使用构 建器中描述的那种依赖注入方式,明显有高射炮打蚊子之嫌,而且对于插件的实现者而言,明显会增加不必要的开发量,更严重的是,它会破坏我一直提倡的简洁优 雅的设计和使用理念。呵呵,这个理由足够充分了罢,而且增加这个概念之后并不会给系统带来额外的复杂性和臃肿感,无论对于普通插件开发人员还是框架实现者 本人都不会造成额外的工作量,相反它只是简化插件开发的工作并使之更易于理解和配置。

  • 类型别名(TypeAlias):在构 件定义过程中,经常需要指定一些没有挂入插件框架的类型(如:System.Windows.Forms.Form, System.Windows.Forms),如果不幸这些类型又需要频繁使用的话,那么在每个地方都重写一遍,无疑是劳民伤财的,与朝廷提倡的建设低碳 节能型社会是背道而驰滴,是不与时俱进滴,是没有充分理解带三个表滴,是反XXX…. (汗) 所以,我就让这个概念华丽登场了,应该不会有不知时务者反对吧,呵呵,河蟹好河蟹好。
  • 解 析器(Parser):这个对应到SharpDevelop中的解析器类,但是处理手法上与SharpDevelop不同,我把这部分的功能单独出来别于 普通构件,让其专司解析之职。它只有一个重要职责:负责将插件定义文件中的某些构件的设置属性的文本解析成它真正需要的东西。听来有点绕口,举例:假如某 菜单项构件在插件定义文件中的定义如:
    <menuItem name="Print" type="MenuItem"
                text="{res:PrintLabel}"
                icon="{res:Icons.16x16.Print}"
                shortcut="Control|P"
                command="/Workspace/Commands/PrintCommand" />

    看到text和icon特性(XML元素的Attribute)中的那些{res:xxxxxxx}字符么咯?Good,恭喜你答对了,Parser就是负责解析这些字符串的家伙,它会返回一些别的什么玩意给构件(Builtin)对象,其中的res:部分就是指定用哪个解析器来解析冒号(:)后面的内容。

 

以上较为详细的介绍了本插件框架中涉及的几个重要核心概念,需要特别指出的是,所有这些元素都是生而平等的,他们在插件环境中都占有同等地位的位 置,虽然概念和职责各不相同,但都是为人民服务,身为它们的God,俺是绝对不允许这些家伙中有歧视存在,Never!每个插件结构都可以通过插件树查找 到这些元素,并随时为您服务。

 

不妙,有情况

在此,还是忍不住要批评一下SharpDevelop这位国际友人,虽然她如此无私的将她的Body任由我多番蹂躏,也曾陪伴我走过很多个落寞的夜晚,曾让我时而抑郁、时而激昂,时而欢欣、时而捶胸顿足,但不管怎样她都始终一如既往的陪伴着我欢乐悲喜的一路成长。

不幸在一些主要类中看到这样的代码:

static AddInTree()
{
    doozers.Add("Class", new ClassDoozer());
    doozers.Add("FileFilter", new FileFilterDoozer());
    doozers.Add("String", new StringDoozer());
    doozers.Add("Icon", new IconDoozer());
    doozers.Add("MenuItem", new MenuItemDoozer());
    doozers.Add("ToolbarItem", new ToolbarItemDoozer());
    doozers.Add("Include", new IncludeDoozer());
            
    conditionEvaluators.Add("Compare", new CompareConditionEvaluator());
    conditionEvaluators.Add("Ownerstate", new OwnerStateConditionEvaluator());
            
    ApplicationStateInfoService.RegisterStateGetter("Installed 3rd party AddIns", GetInstalledThirdPartyAddInsListAsString);
}
 

这披露了一个性质非常严重的事件,这个 AddInTree 类是SharpDevelop插件框架的核心类,在其静态构造函数中硬编码对某些特殊Doozer和ConditionEvaluator进行初始化动 作,这本身就有悖于所有插件结构均一视同仁的插件化理念,正是因为这些少数的特殊化分子导致了后面的插件应用有样学样的一通胡搞。这个问题,我必须得严肃 反应一下,虽然我不否认这些个Doozer在整个框架中的重要性,但是就算其功能再重要也不能脱离插件平台的法则约束,所谓法则就是我上面提到的“所有插件结构在平台中都具有生而平等的不容侵犯的插件权”,就算某个元素告诉我他掌管着诸多构件的生杀大权,那依然得遵守‘天赋插权’的法则,总统都还得轮着坐呢。难怪我先前在Addin-Scout工具中怎么都不能在插件树中找到这些Doozer的位置,感情都跑到这里潜规则来了。

 

再来围观一下 WorkbenchSingleton 类中的 InitializeWorkbench() 方法

public static void InitializeWorkbench(IWorkbench workbench, IWorkbenchLayout layout)
{
    WorkbenchSingleton.workbench = workbench;
    DisplayBindingService.InitializeService();
    LayoutConfiguration.LoadLayoutConfiguration();
    FileService.InitializeService();
    StatusBarService.Initialize();
    DomHostCallback.Register(); // must be called after StatusBarService.Initialize()
    ParserService.InitializeParserService();
    Bookmarks.BookmarkManager.Initialize();
    Project.CustomToolsService.Initialize();
    Project.BuildModifiedProjectsOnlyService.Initialize();
    /* ... more ... */
}

还有 CoreStartup 类中的 StartCoreServices() 方法

public void StartCoreServices()
{
    if (configDirectory == null)
        configDirectory = Path.Combine(
                            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                            applicationName);
    PropertyService.InitializeService(configDirectory,
                dataDirectory ?? Path.Combine(FileUtility.ApplicationRootPath, "data"),
                            propertiesName);
    PropertyService.Load();
    ResourceService.InitializeService(FileUtility.Combine(PropertyService.DataDirectory, "resources"));
    StringParser.Properties["AppName"] = applicationName;
}

这些特殊分子的榜样力量由此可见一番,其他方面的影响就不一而足了。整个事件给我的感受就有如:当酣畅淋漓正准备放开拳脚大干一番的时候,突然发现某种介质上出现了个洞洞,大煞风景之事,轻者造成一次机体伤害,严重的话,嘿嘿,为此而因噎废食就搞大了。

 

高潮

主题切入这么久了,按理也应该到大家期盼已久的高潮部分了,但是很遗憾,不是每次都能有的,虽然我也很想在这篇文章中,把我设计的插件定义文件作为高潮的产物奉献给大家。无奈,身心俱惫,已经连续苦熬了两个通宵,就俺这个年纪而言,已实属不易。

当自己真正开始插件框架的开发后,里面非常多的细节问题开始逐一浮现,然本文无法一一介绍到,但是,挑战总与成就感结伴同行,上帝与魔鬼将伴随我此后一段相当长的日子,我想这次真的会坚持下去,起码我已能隐约感受到黎明时的温暖。

 

尾声

与日方长,不争一朝一夕,有意者请恕半老不小生不道德的先撤了,后会无期…… bye-bye


作者:钟峰(Popeye Zhong)目前是 北大青鸟(深圳中青)培训中心 的ACCP讲师,负责讲授.NET和SQL数据库开发课程。他曾经使用 C 语言做过图形程序设计,在相当长的一段时期内从事 COM/COM+ 组件的开发和设计工作,并且短暂的做过 Lotus/Notes 和 Dialogic 语音卡程序的开发,从2003年初开始使用.NET这个充满趣味和挑战的开发平台,还领导过.NET平台下的 Windows Mobile 几个项目的开发,对WinForm和WebForm均比较熟悉。感兴趣的除了企业应用架构设计、组件开发、安全、图像处理外还对汽车和枪械模型、边境牧羊 犬有浓厚的兴趣。如果希望与他联系,可访问 http://www.cnblogs.com/sw515 或者Email Zongsoft # gmail.com (将#换成@)

[转载]ARP欺骗与中间人攻击 - 有价值炮灰 - 博客园

mikel阅读(1455)

来源: [转载]ARP欺骗与中间人攻击 – 有价值炮灰 – 博客园

前言:

上一篇WPA/WAP2wifi 密码破解笔记说 到如何探测附近开放的AP并且破解进入,那么进入别人据局域网我们能干些什么呢?换句话说如果别人进入了我们内部网络,会有什么影响?本文简要介绍了 ARP和MITM原理,并在实际环境中对目标主机的流量进行劫持。曝露了公共网络中普遍存在的问题,藉此来看看局域网中的客户端究竟面临着怎样的隐私泄漏 和安全风险。

 

ARP与MITM原理

什么是ARP

  ARP全称为Address Resolution Protocol,即地址解析协议。是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所 有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本 机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主 发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;由此攻击者就可以向某一主机发送伪ARP应答报文,使其 发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加 或删除静态对应关系等。

  例如,在Linux命令行下

$sudo arp -a

Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.1.1            ether    0A-11-22-33-44-01   C                     wlan0
192.168.1.2            ether    0A-11-22-33-44-02   C                     wlan0

可以看到局域网中其他客户端的内网地址和MAC地址。

工作过程

  现假设主机A的IP地址为192.168.1.1,MAC地址为0A-11-22-33-44-01;
主机B的IP地址为192.168.1.2,MAC地址为0A-11-22-33-44-02;
当主机A要与主机B通信时,地址解析协议可以将主机B的IP地址(192.168.1.2)解析成主机B的MAC地址,以下为工作流程:
第1步:根据主机A上的路由表内容,IP确定用于访问主机B的转发IP地址是192.168.1.2。然后A主机在自己的本地ARP缓存中检查主机B的匹配MAC地址。
第2步:如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.2的硬件地址,从而将ARP请求帧广 播到本地网络上的所有主机。源主机A的IP地址和MAC地址都包括在ARP请求中。本地网络上的每台主机都接收到ARP请求并且检查是否与自己的IP地址 匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。
第3步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。
第4步:主机B将包含其MAC地址的ARP回复消息直接发送回主机A。
第5步:当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。本机缓存是有生存期的,生存期结束后,将再次重复上面的过程。主机B的MAC地址一旦确定,主机A就能向主机B发送IP通信了。

工作要素:ARP缓存

  ARP缓存是个用来储存IP地址和MAC地址的缓冲区,其本质就是一个IP地址->MAC地址的对应表,表中每一个条目分别记录了网络上其他主机的IP地址和对应的MAC地址。每一个以太网或令牌环网络适配器都 有自己单独的表。当地址解析协议被询问一个已知IP地址节点的MAC地址时,先在ARP缓存中查看,若存在,就直接返回与之对应的MAC地址,若不存在, 才发送ARP请求向局域网查询。为使广播量最小,ARP维护IP地址到MAC地址映射的缓存以便将来使用。ARP缓存可以包含动态和静态项目。动态项目随 时间推移自动添加和删除。每个动态ARP缓存项的潜在生命周期是10分钟。新加到缓存中的项目带有时间戳,如果某个项目添加后2分钟内没有再使用,则此项 目过期并从ARP缓存中删除;如果某个项目已在使用,则又收到2分钟的生命周期;如果某个项目始终在使用,则会另外收到2分钟的生命周期,一直到10分钟 的最长生命周期。静态项目一直保留在缓存中,直到重新启动计算机为止。

ARP欺骗与MITM

MITM,全称为Man In The Middle,即中间人。地址解析协议是建立在网络中各个主机互相信任的基础上的,它的诞生使得网络能够更加高效的运行,但其本身也存在缺陷。ARP地址 转换表是依赖于计算机中高速缓冲存储器动态更新的,而高速缓冲存储器的更新是受到更新周期的限制的,只保存最近使用的地址的映射关 系表项,这使得攻击者有了可乘之机,可以在高速缓冲存储器更新表项之前修改地址转换表,实现攻击。ARP请求为广播形式发送的,网络上的主机可以自主发送 ARP应答消息,并且当其他主机收到应答报文时不会检测该报文的真实性就将其记录在本地的MAC地址转换表,这样攻击者就可以向目标主机发送伪ARP应答 报文,从而篡改本地的MAC地址表。ARP欺骗可以导致目标计算机与网关通信失败,更会导致通信重定向,所有的数据都会通过攻击者的机器。攻击者再对目标 和网关之间的数据进行转发,则可作为一个“中间人”,实现监听目标却又不影响目标正常上网的目的。

 

具体攻击实现

具体的中间人攻击,大体上可以分为以下几个步骤:

1. 对目标主机进行ARP欺骗,声称自己是网关。

2. 转发目标的NAT数据到网关,维持目标的外出数据。

3. 对网关进行ARP欺骗,声称自己是目标主机。

4. 转发网关的NAT数据到目标主机,维持目标的接收数据。

5. 监听劫持或者修改目标的进入和外出数据,从而实现攻击。

其中1,3都是ARP欺骗;2,4都是转发,下面会分别说到。

ARP欺骗

ARP欺骗又叫ARP投毒(ARP poisoning),实现方式有很多,但其原理都是通过不断向攻击目标发送ARP replies,从而实现重定向从一个主机(或所有主机)到另一个主机的数据包的目的。

这里以dsniff工具集的arpspoof命令为例:

#  arpspoof [-i interface] [-c own|host|both] [-t target] [-r] host

其中,

-i 指定使用的接口。

-c 指定当还原arp配置时t使用的MAC地址,默认为使用原来的MAC。(即当停止arpspoof命令后,默认取消毒化)

-t 指定要毒化的主机,如果不指定的话默认为局域网下所有主机。

-r 双向毒化(host和target),从而双向扑捉数据。(仅当同时指定 -t的时候才有效)

host 为你想要伪装成的主机,通常是本地网关。所有从target到host的数据包都被重定向到本机。

 

数据流转发

毒化了目标主机的ARP缓存表之后,其经过网关外出的数据流将会发送到攻击者的主机。但此时由于目的地址出错,目标主机对外的请求将无法到达,亦即无法访问互联网了。为了维持目标正常上网,需要我们在收到目标来的数据时将其进行转发到真正的网关。

首先开启端口转发,允许本机像路由器一样转发数据:

# echo 1 > /proc/sys/net/ipv4/ip_forward

此时本机收到的数据包含源地址和目的地址(外网地址),我们需要将其转发到网关。这里进行了(NAT)网络地址转换的功能,关于NAT可参考我上一章P2P通信原理与实现(C++)

# iptables -t nat -A POSTROUTING -p tcp -s 192.168.1.0/24 -j SNAT --to-source 192.168.1.110
这条命令的作用是修改本机的转发规则,将所有从本机出去的源地址为192.168.1.XXX的数据包都修改为本机地址192.168.1.110
其中,
-t 指定要操作的表,这里是nat
-A 即--append,表示增加一条规则。nat有三个内建规则,分别是
  PREROUTING,在数据包进入时改变其地址
  POSTROUTING,在数据包将要出去时改变其地址
  OUTPUT,在路由前改变本地产生的数据包地址
-p 表示数据包要检查的协议
-s 指定源地址。地址格式可以是网络名字,主机名字,IP地址或者是address/mask格式的网络地址。可以指定多个,-s前加!表示取补集
-j 指定规则的目标,即如果数据包符合规则该如何做;SNAT表示修改数据包的源地址,且此次链接的余下数据包都不再检查
--to-source 在SNAT选项下指定所转发数据包的新的源地址,这里设为192.168.1.110,即攻击者的地址

关于iptables的详细用途可以man iptables查看。上述命令描述了外出数据的转发规则,那么从外部来的数据要转发到被攻击主机上也是同理:

# iptables -t nat -A PREROUTING -p tcp -d 192.168.1.110 -j DNAT --to 192.168.1.114
在本机转发之前将数据包目的地址为192.168.1.110的修改为192.168.1.114(被攻击主机),然后转发出去。

 

其他工具

上面为了说明原理,吧arp毒化和数据转发分开做,其实利用现有的工具,可以很简单地执行上面的工作,例如用ettercap:

# ettercap -i wlan0 -Tq -M arp:remote /target/ /host/

一条命令即可完成arp毒化和转发的功能。其中-M表示指定MITM攻击。

 

后记

一旦中间人建立攻击成功,劫持了目标的上网流量,那么接下来也就为所欲为了。轻则监控目标的上网行为,就如同二十四小时站在你身后看你上网;或者劫持目标的cookie从而用目标的帐号登录诸如微博,百度,豆瓣等网站,浏览其隐私数据,进行发贴删贴等操作;重责进行钓鱼攻击,使目标下载恶意程序或者执行恶意脚本,从而威胁其计算机安全。当然,应对这种攻击的防范方法也有很多,最简单的一种就是绑定MAC地址,并且在网关MAC地址改变的时候发出提醒,从而防止ARP毒化攻击。还有就是最好不要在公共WiFi下浏览有重要隐私的网站,万一碰上不怀好意的人就在局域网中,那可就是真的“在阳光下行走”了。

[转载]终于可以发布Delphi下收点OSGI精髓皮毛而设计的插件框架WisdomPluginFramework - 斑马猪 - 博客园

mikel阅读(888)

来源: [转载]终于可以发布Delphi下收点OSGI精髓皮毛而设计的插件框架WisdomPluginFramework – 斑马猪 – 博客园

这是一个Delphi实现的插件框架,
我耗费了相当相当相当多的精力来设计她,虽然仅闪着点我微薄智慧的光芒,但我还是决定用Wisdom来命名她,
也因它是我绝无仅有的,在完成1年多后回头来看仍能相当满意的作品,
所以我补充了展示她特性的Demo和管理工具,决定将她开源发布给仍在坚持着的Delphi Fans。

 

我发现,人年龄越大,越害怕复杂的东西,越偏向喜欢清澈简单明了的事物,因此,这个插件框架的设计我是费尽心思地往简单上整,
务求扫上几眼几分钟就大概懂了原理,就能上手用起来;务求使用中只需遵守最少的使用守则!
为此,我把OSGI和Eclipse的插件设计精华提取提取再提取,思考思考再思考,终于得偿所愿,最终汇成了她!

好了,吹牛吹过头又吹过瘾了,就还是回到正事,介绍下这个框架都具备什么特性,有什么好处来吸引你。
1、她是微内核的设计
什么是微内核?就是核心实现小如原子,其它一切皆是插件,通过插件的相互构织构建出大千世界。
与此相对应的,那些构建了基本框架,给你一堆既定接口让你完善细处的叫巨内核设计,我讨厌学习一堆接口,因此坚决支持微内核设计,
微内核下整个实现的核心代码可能就2000行内,对高手来说三刀两斧就阅读完了,代码虽小,却是灵活强大。
用巨内核的叫水泥工,而在微内核外,那是你自由奔放的灵感创造,你就是世界的构筑者。

2、面向接口,任意角落调用
插件核心、插件服务全部以纯接口方式暴露,任何插件在任何时候可以获取任何其它插件所声明提供的接口服务,不用关心怎么处理和
这些插件的关系,人人为我,我为人人嘛。

3、扩展点概念让插件无限扩展
每个插件实现自己梦想的同时,能力大的还可以留给别人实现梦想的空间,这个空间就是扩展点,每个插件都可以向别的插件声明扩展空间,
任何其它插件都可以来实现这个扩展点,就如同一个插头又预留有插头口一样,插头和插头间可以互插而互连形成树状获得无限扩展能力,但
对于任何一个插件角度看去,任何其它插件又是平面化的,就像人与人一样,你可以与任何一个人你见到的人打交道,而不需要一定有介绍人。
扩展点概念是Eclipse的设计精髓之一,通过扩展点,能让你的界面灵活得就像Eclipse一样。

4、她只使用纯DLL做插件(当然也可用BPL,我认为那只是打个钩的事情)
Delphi下的BPL确实很好,但你一定咬牙切齿要维护一个支持那时的BPL的编译环境和控件,我也是这样想的,因此坚决支持纯正DLL。

5、她能支持除Delphi外的其它编译器生成的DLL
像VC++有这么多优秀的库,不用甚是可惜,所以,支持它编译的DLL!如果万一还有支持不住的怎么办?比如像易语言?比如在Linux下跑?
好得我也考虑到了,刚才我有把微内核比作原子,大家都知道,原子还能更细分到电子,所以,微内核里的东西还能细分出来更换呢!内核里我就有一个DLL加载器,你只要为特殊的DLL重定义一个加载器,就能跑起来了,看到这里,是不是有点跃跃欲试了哈。

6、她不需要重启就能动态更新/更换新版本的DLL插件
这功能在不能停的服务端那是相当有用对不对,听起来也很高大上对不对,其实实现很简单,你只需遵守一条规则就行,那就是她叫你释放
某个接口时你要立马无条件接受并立刻按她说的做,当然,一瞬间后她会立刻把你需要的服务接口送还给你。

7、插件懒加载,务求最少资源占用
插件框架提供总是、按需、自动管理三种方式载入插件,不必要还没有用起的插件就留着在磁盘上吧,保持她苗条秀丽的身材跑得快效率高。
在自动这种方式下,如果插件在一段时间发现没人用,就把插件卸出内存,哇,这功能好啊!很遗憾,我没有做,不是懒,因为我细想后,觉得
这件事情可划入由外部插件来插手的领域,不是原子性的范畴,以保证微内核是真的微哈。

8、手动式还是配置全由你
提供全套API让你可在程序中手动获取、管理、卸载插件DLL,当然也提供xml配置的方式自动管理插件,要不,小小程序也要配一个xml就说不过去了。

9、可能可以在Linux下跑
我只用到了D7的语言特性,因此,小改一点应可以用Lazarus跑起来,但我是Linux菜鸟,没有发言权,所以只能说可能,求高手上斧。

感觉整个篇幅下来都是我吹嘘之词。。。唉,吹壮君子胆嘛,高手莫见笑。

 

然后再说下怎么使用,还是很简单地:
1、解压到目录A后,在Delphi环境中,Tools–>Environment Options,打开Library标签,在Browsing Path和Library Path中包含源码中的Core目录
2、新建项目,在Project–>View Source下的uses中,首先第一个打上”WisdomFramework“,在需要跑起的地方加一句TWisdomFramework.Run就可以了
3、在需要调用框架接口的地方,uses一下接口说明文件即可,其中”WisdomCoreInterfaceForD“是给Delphi用的,给C++用的头文件在Demo的Cplus里哦
这里有一条规则:如果搞有DLL中的界面元素显示到Host主窗口,那么在主窗口的OnClose或Destroy中,一定要调用一下TWisdomFramework.Stop

具体特性参看Demo演示。

 

最后,说一下这个框架的设计理念,这只需要一张图就足够了,如下:

 

 

在哪下载?我放到Delphi盒子上了,求大家留赠几根火柴,可否,请大伙阅后批示!
地址为:http://bbs.2ccc.com/topic.asp?topicid=458999

感谢:
感谢飞鸿,要不是他时不时问一下的督促,我估计至今还没有完成这作品,要不是他提了好些建议,这可能还是一件粗糙遗憾的作品。

使用OpenSSL做RSA签名验证 支付宝移动快捷支付 的服务器异步通知

mikel阅读(938)

由于业务需要,我们需要使用支付宝移动快捷支付做收款。支付宝给了我们《移动快捷支付应用集成接入包支付接口》见支付宝包《WS_SECURE_PAY_SDK》。

支付宝给的服务器demo只有Java、C#、PHP三种,而我们服务器端使用的是C++。这其中就涉及到接收支付宝的服务器异步通知。为了确保接 收到的服务器异步通知来至支付宝,我们就必须验证支付宝的签名。坑爹的是,原来PC端使用MD5做签名,估计支付宝考虑到移动端的风险更高,于是改用 RSA做移动快捷支付应用的签名。这无疑增加了我们迁移到移动端的开发成本。

支付宝文档中说明是使用openssl,我们这边就决定使用openssl做rsa签名验证。

由于第一次使用openssl做RSA验证签名,我们碰到了各种坑,为了避免其他项目也碰到类似问题,分享如下:

首先要说明的是RSA签名和签名验证的过程。

RSA签名的过程(支付宝操作)如下:对需要签名的字符串按key的字母升序排序,使用=和&连接,形成一个签名字符串。对该字符串做摘要 (可以使用MD5或者SHA1,支付宝使用的是SHA1),然后对摘要字符串(即接口中的hash参数)使用支付宝私钥做RSA加密,获得加密字符串,即 为签名字符串(放在sign中),设置sign_type=RSA。这样,就完成了发送字符串的签名。

RSA签名验证的过程(我们第三方企业操作)如下:接收到发送过来的字符串(如果字符串没有做url decode解码,需要做url decode解码),拆分为key、value对,按照支付宝的文档,根据key的字母升序排序,使用=和&链接,获得被签名字符串。被签名字符 串做SHA1摘要算法,获得SHA1摘要字符串。如果sign_type=RSA,先将sign字段做base64解码,然后使用支付宝公钥做RSA解 密,得到SHA1摘要字符串。比较两个SHA1摘要字符串,如果SHA1摘要字符串一致,则签名验证成功。

特别说明的是:支付宝的公钥字符串为以—–BEGIN PUBLIC KEY—–\n开始,以\n—–END PUBLIC KEY—–\n结束,中间的字符串需要每64个字符换行一次,即为:

  1. —–BEGIN PUBLIC KEY—–
  2. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRA
  3. FljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQE
  4. B/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5Ksi
  5. NG9zpgmLCUYuLkxpLQIDAQAB
  6. —–END PUBLIC KEY—–

理论说完了,再解释一下使用的函数吧。

验证签名函数为:int verifyAlipayNotify(const std::string& recvString, const std::string& alipayPublicKey);

recvString为接收的字符串,未做urldecode。

alipayPublicKey为本地内存中存储的支付宝公钥,已经保证包含特殊说明的条件。

使用的openssl函数如下:

int RSA_verify(int type, const unsigned char *m, unsigned int m_length,
const unsigned char *sigbuf, unsigned int siglen, RSA *rsa);

type 使用何种摘要算法,这里由于使用的是SHA1算法,填写NID_sha1

m 摘要字符串

m_length 摘要字符串长度

sigbuf 支付宝返回的签名,已经做了base64解码

siglen 支付宝返回的签名长度,这里应该为128

rsa openssl的RSA密钥结构体,这里由支付宝公钥转化而来的

返回值:负数为执行错误,0为签名验证失败(估计是有黑客攻击你),1为签名验证成功

verifyAlipayNotify代码如下:


#include &lt;openssl/rsa.h&gt;
#include &lt;openssl/sha.h&gt;
#include &lt;openssl/md5.h&gt;
#include &lt;openssl/rand.h&gt;
#include &lt;openssl/objects.h&gt;
#include &lt;openssl/pem.h&gt;
#include &lt;openssl/bio.h&gt;

#include &lt;string&gt;
#include &lt;map&gt;

#include "urlcodec.h"
#include "base64.h"

struct ltstr
{
bool operator()(std::string s1, std::string s2) const{return (s1.compare(s2) &lt; 0);}
};

int verifyString(const std::string&amp; signString, const std::string&amp; sign, const std::string&amp; alipayPublicKey)
{
//获得支付宝的签名字节串
char szSign[128];
unsigned long szSignLen = 128;
bool decodeResult = CBase64::Decode(sign, (unsigned char*)szSign, &amp;szSignLen);//CBase::Decode是Base64解码函数,您可以在网上随便下载一个
if(!decodeResult)
{
return -1;
}

//获得SHA1摘要字符串
unsigned char sha1Origin[20];
SHA1((unsigned char*)signString.c_str(), signString.size(), sha1Origin);

//由支付宝公钥内存字符串转化为openssl的RSA结构
BIO* memBIO = NULL;
memBIO = BIO_new(BIO_s_mem());
int bioWriteLen = BIO_write(memBIO, alipayPublicKey.c_str(), alipayPublicKey.length());
RSA* rsa = PEM_read_bio_RSA_PUBKEY(memBIO, NULL, NULL, NULL);
&amp;nbsp;&amp;nbsp;&amp;nbsp; if(NULL == rsa)
&amp;nbsp;&amp;nbsp; &amp;nbsp;{
&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;return -2;
&amp;nbsp;&amp;nbsp; &amp;nbsp;}
//签名验证
int verifyResult = RSA_verify(NID_sha1, sha1Origin, SHA_DIGEST_LENGTH, (unsigned char*)szSign, szSignLen, rsa);
return verifyResult;
}

int verifyAlipayNotify(const std::string&amp; alipayNotifyData, const std::string&amp; alipayPublicKey)
{
std::string strAlipayNotifyData = alipayNotifyData;
std::string sign;
std::map&lt;std::string, std::string, ltstr&gt; omap;
std::string::size_type pos = strAlipayNotifyData.find("&amp;");
while(std::string::npos != pos)
{
std::string one = strAlipayNotifyData.substr(0, pos);
std::string::size_type subpos = one.find("=");
if(std::string::npos != subpos)
{
std::string key = one.substr(0, subpos);
if("sign_type" != key &amp;&amp; "sign" != key)
{
std::string value = one.substr(subpos+1);
std::string newValue = UrlDecode(value);//UrlDecode是URL解码函数,您可以在网上随便下载一个
omap.insert(std::make_pair(key, newValue));
}
else if("sign" == key)
{
sign = UrlDecode(one.substr(subpos+1));
}
}

strAlipayNotifyData = strAlipayNotifyData.substr(pos + 1);
pos = strAlipayNotifyData.find("&amp;");
}
std::string::size_type subpos = strAlipayNotifyData.find("=");
if(std::string::npos != subpos)
{
std::string key = strAlipayNotifyData.substr(0, subpos);
if("sign_type" != key &amp;&amp; "sign" != key)
{
std::string value = strAlipayNotifyData.substr(subpos+1);
std::string newValue = UrlDecode(value);
omap.insert(std::make_pair(key, newValue));
}
else if("sign" == key)
{
sign = UrlDecode(strAlipayNotifyData.substr(subpos+1));
}
}

//获得支付宝被签名字符串
std::string signString = "";
std::map&lt;std::string, std::string, ltstr&gt;::iterator itr = omap.begin();
for(; itr != omap.end(); ++itr)
{
signString += itr-&gt;first;
signString += "=";
signString += itr-&gt;second;
signString += "&amp;";
}
if(!signString.empty())
{
signString.erase(signString.length() - 1);
}

return verifyString(signString, sign, alipayPublicKey);
}

有时候,你本地存储的公钥是没有包含头尾的,如


MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB

为此,提供一个函数支持转化为完整公钥的函数:


std::string completeAlipayPublicKey(std::string strPublicKey)
{
int nPublicKeyLen = strPublicKey.size();      //strPublicKey为base64编码的公钥字符串
for(int i = 64; i &lt; nPublicKeyLen; i+=64)
{
if(strPublicKey[i] != '\n')
{
strPublicKey.insert(i, "\n");
}
i++;
}
strPublicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n");
strPublicKey.append("\n-----END PUBLIC KEY-----\n");
return strPublicKey;
}

最后,测试代码如下:


int main(int argc, char **argv)
{

std::string strPublicKey = "**********";
std::string strAlipayData = "**********";
std::string strCompletePublicKey = completeAlipayPublicKey(strPublicKey);
int result = verifyAlipayNotify(strAlipayData, strCompletePublicKey);
if(1 == result)
{
printf("verify sign ok!\n");
}
else if(0 == result)
{
printf("mock alipay notify data");
}
else
{
printf("error\n");
}
return 0;
}

 

 

[转载]虚拟现实之机械拆装项目架构 - 决晴谷 - 博客园

mikel阅读(833)

来源: [转载]虚拟现实之机械拆装项目架构 – 决晴谷 – 博客园

一般的机械拆卸模拟用来培训演示,例如大型机械的拆装都是非常耗时的,需要一个软件来模拟下拆装的步骤,给不熟练的工人演示注意事项等等。应用非常广泛。因此设计一个通用的框架,可以很好适应变化的需求。

使用unity可以很方便的开发虚拟现实类的软件,程序部分只需要普通的程序员即可完成整个项目。

机械的拆装模拟是最近两年比较常见的项目,大量的订单和需要制作的模拟。阿亮在大二的时候也是接到了一个这样的单子。就是对某大型机械设备进行拆装,维修演示。

先说一下这个项目的常规方案(一开始我也是这么做的),就是利用unity的动画系统,制作每个零件的动画,如位移,旋转等。制作完成后,通过程序去调用播放零件的动画,来实现拆装的演示。

这个方案的缺点是非常明显的,一是,零件繁多,制作动画本身就是个耗时较长的活,总体下来制作时长。二,动画系统的不完善和平滑过渡的调整,都将困 扰开发者,使动画的制作不稳定,效率低下。三,动画做完后,动画的修改,如移动的速度,位置,角度等调整,又是一个庞大的任务,而代码并不能帮上忙,这对 项目的维护是非常要命的。四,扩展性极差,如果有新的零件填进来,或者新的动作都将考验系统的适应能力,而动画系统下的架构是难以适应这一变化的。

总之,除非你做的模拟零件比较少,而且一切都是固定的,可以采用上述方案。否则,你的项目就算成功了,也花费了大量的时间和精力,这些都是金钱。

一般的机械模拟,零件断然不会少,动作繁多,有先后的顺序。移动的方案也是不确定的,例如零件的位置,放置的角度等等。零件的移动速度有时也是有要求的。例如小零件移动的快,大零件移动的慢。

基于以上要求和特点,关于机械的模拟,应该使用transform来实现。底层写好零件的移动,旋转,速度等接口。同时旋转也是不确定轴向的,有时 候旋转的轴并不在物体身上(不规则零件),所以底层接口应该考虑到这些。如果不想写,可以借助ITween,并封装好相关函数。

本文不会提供源码,只提供开发的方案和思路,如果想要源码,可以联系我,当然那并不是免费的。(实际上方案才是最贵的)

接着说,底层实现移动是第一步,接下来是设计的第二步,第一步中普通的程序员完全可以胜任。

因为有大量的零件,你不可能为每个零件都写一个移动方法。所以我们需要定义一些通用的规则,例如移动,旋转等,并生成相应的字段。或许你已经猜到 了,第二步就是做一个数据持久层,关于这个数据库的设计,我就不详谈了,看个人需求。定义零件的移动,旋转,位置,速度等参数,然后存起来,运行程序时读 取。这样的好处是,可以动态的修改零件的动作。即使不会编程,不懂unity的也可以通过直接修改数据达到改变机械模拟的“动画”。关于这部分的设计还是 比较简单的,看具体需求而定。关于数据库,一般本地数据库足矣。现成的数据库有SQLite,但是跨平台不好。Xml,简单方便,跨平台。具体可以看我以 前的博客。

第三步,算法结构的封装。这个层次主要负责零件操作的设计,提供零件的各种操作方法,如添加动作,修改动作等等,无外乎一些位置,角度,速度的访问等等。这个类一般的程序员也能做好,关键是看各位的具体需求了。

第四步,做个UI,一个可视化的“动画”编辑器。此动画编辑器非unity的动画编辑器。可以理解为,对前三步的方法的调用。如果直接在数据库中修改参数,没有直观的感受,调参数也是非常麻烦,每次改完重新打开程序才看到效果,因此需要制作一个零件的动作编辑器来完成。

具体的做法是,在一个场景中,加载所有的零件,然后通过鼠标点击获取零件。完成这个功能很简单,所有的零件加个meshcollder,然后使用射 线碰撞就行了。因为零件数量较多,我们可以动态的添加collder组件,这个过程比较缓慢,极容易卡顿,建议使用协程。选定零件之后,就可以对零件的动 作进行编辑了。具体的UI如何设计就看大家的需求了,但是功能无外乎,移动(选择轴向),旋转(选择轴向),速度参数等等。为了方便查看还可以添加预览功 能,编辑完成后就可以查看零件的移动轨迹了。话说这个功能也是很好完成的,使用一个表,存储起点位置,然后读取一下移动参数就行了。

当然快捷键也可以加入UI设计中,其目的就是方便零件动作的设计。到后期,还可以模拟unity引擎对物体的操作,鼠标拖拉零件,通过类似的锚点,存储关键点,设置速度等等,一个简单方便的“动画”编辑器就很完美了。

动画编辑器可以作为高级功能,让用户自定义零件的动作,模拟拆卸动画的同时,还能实时修改。这样的话,软件拥有更好的灵活性。同时,还可以方便的添 加新零件。不仅如此,在面对一个新的模拟项目时,无需再修改代码,直接让美工做好的模型放到指定的文件夹下就ok了。这要看你的资源加载设计了。

 

 

本文连接:http://www.cnblogs.com/jqg-aliang/p/4825460.html,转载请申明出处,谢谢!

插件框架Java

mikel阅读(910)

HOW TO MAKE PLUGIN FRAMEWORK

本文将为你展示如何基于Java[1]构建一个完整可用的插件框架(Plugin Framework)。
关于Plugin Wikipedia[2]是这样描述的

Wikipedia 写道
插件(Plug-in,又稱addin, add-in, addon 或 add-on),简单说,就是电脑程序,通过和应用程序(例如网页浏览器,电子邮件服务器)的互动,提供一些所需要的特定的功能。

 

我们可以简单的理解插件就是应用程序的一个模块,但是这个模块又是相对独立的自治域,这个模块可以引用(依赖)其他的模块,这个模块可以通过某种方式于其他的模块交互。应用程序不依赖任何插件就可以运行,但是需要加载相应的插件才能实现特定的功能。

在了解了插件的基本知识后,我们分析一下构建一个插件框架需要处理那些问题

 

 

1:定义
1.1:物理布局
1.2:逻辑实体
2:插件管理器
2.1:依赖关系
2.2:库隔离
2.3:生命周期管理
2.4:插件之间的通讯

1.定义

 

我们希望插件保持内聚,这样能方便插件的安装和卸载,在逻辑实体上希望插件的定义尽可能的简单,下面我们给出插件的

物理存储布局和逻辑实体
1.1物理布局
所谓物理布局就是插件是如何存储的,下图给出了一个简单的插件布局

 

samplePlugin/
|– config
| `– default.xml
|– libs
| |– liba.jar
| `– libb.so
| |– sampleplugin.jar
`– plugin.xml

插件以文件夹为单位,每个文件夹是单独的插件,每个插件文件夹都应该包含libs目录和config目录这两个目录分别存储了
插件需要的库文件和配置文件,libs目录下的文件会在插件加载的过程中自动加载,还有就是最重要的插件描述文件plugin.xml。一个简单的插件描述文件如下所示

 

 

  1. <? xml  version = “1.0”   encoding =“utf-8” ?>
  2. < plugin  id = “cn.edu.nwpu.as.VmasPlugin”  name = “vmas”   version =“1.0.0”   author = “liuqiang” >
  3.     < runtime   path = “cn.edu.nwpu.as.VmasPlugin.jar” />
  4.     < require   id = “org.opensolaris.gear.processtree.ProcessTreePlugin”   version = “1.0.1” />
  5. </ plugin >
  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <plugin id=“cn.edu.nwpu.as.VmasPlugin” name=“vmas” version=“1.0.0” author=“liuqiang”>
  3.     <runtime path=“cn.edu.nwpu.as.VmasPlugin.jar”/>
  4.     <require id=“org.opensolaris.gear.processtree.ProcessTreePlugin” version=“1.0.1”/>
  5. </plugin>

插件应该通过插件描述文件plugin.xml描述自身的一些基本属性,包括 插件的id,这个id应该是全局唯一的。插件的名字,版本
,作者等等。除此之外插件还应该描述自身的运行时环境,依赖的插件。
关于运行时环境
插件应该将自己所需的库文件和class文件都放到libs目录下,这些文件将被加载到插件的classpath中。以来关系也非常重
要,在插件加载的过程中,插件管理器会根据plugin.xml中描述的依赖关系选择插件加载的次序。不正确的插件依赖关系会导致
加载失败。

 

1.2逻辑实体
一个简单的接口定义了插件的逻辑实体

 

  1. public   interface Plugin {
  2.     public   void init();
  3.     public   void start();
  4.     public   void destory();
  5.     public  IExtension getExtension(String id);
  6. }
  1. public interface Plugin {
  2.     public void init();
  3.     public void start();
  4.     public void destory();
  5.     public IExtension getExtension(String id);
  6. }

所有实现了这个接口的类都可以认为是一个插件,其中init方法在插件初始化的时候调用并且只调用一次,start方法在插件启
动的时候调用,destory在插件销毁的时候调用。为了确保重复加载,start方法和destory方法应该保证对称。

 

2 插件管理器
插件管理器负责管理和插件有关的一切,在系统启动的时候,插件管理期扫描插件目录,找到符合定义的插件目录,验证合
法性,读取插件描述文件,扫描库文件。然后根据插件描述文件构建插件依赖关系图,确定插件启动顺序,然后按照顺序启动插
件,设定安全管理器,设定classloader,执行init方法,执行start方法。
以上就是插件管理器加载插件的全过程,在详细讲解每一步之前我们先了解下Java的classloader。

 

以下是JDK中关于ClassLoader的解释[3]

 

类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生
成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。
每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。
数组类的 Class 对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类加载器由
Class.getClassLoader() 返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没
有类加载器。
应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。
类加载器通常由安全管理器使用,用于指示安全域。
ClassLoader 类使用委托模型来搜索类和资源。每个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时
,ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。虚拟机的内置类加载
器(称为 “bootstrap class loader”)本身没有父类加载器,但是可以将它用作 ClassLoader 实例的父类加载器。

 

下面这张图说明了Java类加载器的结构

 

写道
+—————————–+
|          Bootstrap             |
|                 |                      |
|           System                |
|                 |                      |
|         Application          |
|                / /                     |
|      PluginFramework  |
|                     / /                |
|          Plugin1 Plugin2 |
+—————————–+

 

上图很好的解释了Java程序的类加载过程,首先是Bootstrap加载jre的类,然后时System加载CLASSPATH下的类,然后是应用
程序的类加载器(如果有的话)加载属于应用程序自己的类,接着PluginFrameWork加载插件管理器所需类,从图中我们可以看
到插件管理器加载的类被所有的插件共享,但是每个插件都有自己的命名空间,这很好的解决了插件之间的库隔离问题,两个
不同的插件现在可以加载同一个类的两个版本而不会有任何冲突。

 

为了实现上面的类加载方式,我们简单的扩展URLClassLoader实现了简单的PluginClassLoader

  1. public   class  PluginClassLoader  extends URLClassLoader {
  2.     private  PluginImage image;
  3.     private  ClassLoader[] requireClassLoader;
  4.     public  PluginClassLoader(URL[] urls) {
  5.         super(urls);
  6.     }
  7.     public  PluginClassLoader(PluginImage image,ClassLoader parent) {
  8.         super (image.getUrls(),parent);
  9.         this .image = image;
  10.         image.pluginClassLoader = this;
  11.         if  (image.getRequires() == null) {
  12.             requireClassLoader = null;
  13.         } else {
  14.             requireClassLoader = new ClassLoader[image.getRequires().size()];
  15.             for ( int  i= 0 ;i<image.getRequires().size();i++){
  16.                 requireClassLoader[i] = PluginManager.getInstance()
  17.             .getPluginImage(image.getRequires().get(i).getId()).getPluginClassLoader();
  18.             }
  19.         }
  20.     }
  21.     protected  Class<?> findClass(String name) throws ClassNotFoundException {
  22.         Class<?> c = null ;
  23.         try {
  24.             c = super .findClass(name);
  25.         } catch  (ClassNotFoundException e) {
  26.             if  (requireClassLoader == null)
  27.                 throw   new ClassNotFoundException();
  28.             else {
  29.                 for  (ClassLoader l : requireClassLoader) {
  30.                     try {
  31.                         if  (l != null)
  32.                             c = l.loadClass(name);
  33.                     } catch  (ClassNotFoundException ee) {}
  34.                 }
  35.                 if  (c ==  null) {
  36.                     throw   new ClassNotFoundException();
  37.                 }
  38.             }
  39.         }
  40.         return c;
  41.     }
  42. protected  String findLibrary(String name) {
  43.         Hashtable<String, String> libs = image.getLibrarys();
  44.         if  (libs.containsKey(name)) {
  45.             return  libs.get(name);
  46.             // full name
  47.         } else   if  (libs.containsKey( “lib”  + name +  “.so”)) {
  48.             return  libs.get(“lib”  + name +  “.so”);
  49.         } else {
  50.             return   null;
  51.         }
  52.     }
  53.     public  PluginImage getImage() {
  54.         return image;
  55.     }
  56. }