前几天看到一篇关于.net动态编译的文章 .NET中的动态编译
,很受启发。在此基础上我做了一些封装,为使调用更加简单,并增加了对动态代码调试的支持,相同代码只编译一次的支持,代码改动自动重新编译,代码引用文件的自动加载和手工加载等功能。


    C#代码动态编译、动态执行、动态调试_8905


     
如上图,我封装的类CSharpProvider很简单,下面说明一下一些公共成员的用法。

    公共属性

     
AssemblyFileName:这个属性指定动态编译后生成的配件名称。

     
CompilerParameters:这个属性指定编译的参数

     
References:这个属性指定被编译代码中的引用。调用者只要调用References.Add("xxx.dll"),就可以加入自己的引用,对于System命名空间的所有引用,不需要手工加入,该类会自动加载。对于用户自己的组件,如果不手工指定引用文件,该类会自动根据名字空间名进行猜测。

 
    SourceCodeFileEncoding:如果以文件形式编译,指定文件的编码类型。

    公共方法

 
    public bool Compile(string code) 

      输入代码字符串,并编译

     
public bool CompileFromFile(string sourceCodeFileName)

     
编译输入的代码文件

      public object CreateInstance(string code, string
typeFullName)

      创建类的实例

      如下面代码,可以输入 CreateInstance(code,
"MyInterface.IHelloWorld"),也可以输入CreateInstance(code,
"HelloWorld"),程序会根据

类型名称来自动找到符合条件的类并实例化。如果代码中有多个指定类型的类,将实例化第一个。



  1. using System;

  2. using MyInterface;



  3. [Serializable]

  4. public class HelloWorld : MarshalByRefObject, IHelloWorld

  5. {

  6.     public string Say()

  7.     {

  8.         return "Hi";

  9.     }

  10. }
复制代码
这里需要特别指出的是由于用到了AppDomain的远程调用,所有的动态加载的代码必须继承自MarshallByRefObject

如果仅仅声明为[Serializable]
虽然也可以执行,但主应用程序域会记录下子应用程序域的一个引用,这样导致子应用程序

域卸载后,依然无法完全释放内存,从而内存泄漏。所以这个很关键,一定要注意。

 
    public object CreateInstanceFromFile(string fileName, string typeFullName)


      从文件创建动态实例

      下面再谈谈对动态代码的调试

     
动态创建的代码如果不能调试,就像一个黑盒子,对系统的可维护性有较大破坏。未来实现这个功能,我们需要做以下工作,

第一、编译时要生成调试信息,这个可以通过设置
CompilerParameters.IncludeDebugInformation =
true;来实现

第二、我们必须告诉调试器源码对应的位置,对于从文件编译的情况,源码文件位置会被自动写入调试信息文件
*.pdb中,而对于从内存编译的情况,我还没有找到指定的方法,如果哪位朋友知道,还望赐教。所以目前如果要调试动态代码,必须从文件编译,也就是调用CompileFromFile,CreateInstanceFromFile。

第三、我们需要在代码中设置一个断点,这个可以在代码中加入
System.Diagnostics.Debugger.Break(); 来解决。

如下图所示,动态代码现在可以调试了。

C#代码动态编译、动态执行、动态调试_8906

 

应用程序域

未来避免内存泄漏,本程序封装了对应用程序域的使用,调用者基本不需要关心应用程序域的调用和卸载过程。本程序在

重新编译或者对象销毁时会自动卸载应用程序域,从而释放内存。由于做这个程序是在应用程序域上遇到了很多麻烦,所以

感觉还是有必要简单讲一下应用程序域。


C#代码动态编译、动态执行、动态调试_8907



如上图所示,应用程序与实际上有点像一个单独的进程,但这个进程是运行在当前进程里面的,当然这个比喻不够贴切。

对应用程序域的调用有点类似进程间采用
Remoting
方式的对象调用,也就是说默认应用程序域要调用其他应用程序域中的对象,

必须采用远程调用的方法,而不能直接调用,如果直接调用,默认应用程序域就会记录这个被调用的应用程序域的一个内存引用,

即使这个应用程序域执行了Unload
方法卸载后,内存依然无法释放,这也是我一开始操作应用程序域遇到的最大困扰。

另外所有暴露在两个应用程序域之间的类必须从MarshalByRefObject基础,这点非常重要,否则将导致内存无法释放。

本程序的一些缺陷

1、没有提供编译多文件的接口,其实要实现这个很简单,考虑到用于动态执行的代码脚本往往比较简单,所以偷懒没有做。

2、没有提供对动态代码中多个对象的枚举接口,以后再完善吧。

源码下载:http://www.pin5i.com/showtopic-20457.html






posted @ 2012-04-13 20:43 kenter 阅读(16) 评论(0) 编辑
遥测的死区也就是遥测变化的门槛阈值,是用于判断遥测是否变化的标准值。
遥测死区的大小也不是随便设置的,虽然理论上设置为0只要有微小的变化就可以反映出来,但是这并不是正确的做法。
遥测死区的设置需要综合考虑几个因素:(1)高级应用需要的遥测灵敏度,不同的测量对象的精度要求也不一样,所以最好能够针对遥测转发表的不同点可以进行不同的设置。(2)需要考虑通信的负担,遥测变化报文一般为突发主动上送,遥测死区的降低必然会导致报文量的增加。(3)遥测变化和其他类数据的优先级配合。在平时运行的时候有大量遥测报文上送的时候会不会影响到其他数据的上送,这和通信程序的实现有很大关系。(4)需要考虑测量源的实际精度。就是测控装置或者CT、PT等原始数据的采集精度。遥测死区的精度小于测量源的实际精度是没有意义的,所以一般来说,死区值应该大于等于千分之二。
posted @ 2012-04-06 08:53 kenter 阅读(38) 评论(0) 编辑
win7下:
1.运行gpedit.msc
2.计算机配置=>管理模板=>系统=>远程过程调用=>用于未验证的RPC客户端限制=>启用
posted @ 2012-03-27 09:15 kenter 阅读(4) 评论(0) 编辑
 

1关于C++ Layer的内存回收机制

Android C++层的内存收回主要是通过三个类来实现,分别是RefBase,sp,wp;

SP和WP是两个智能指针模板类,sp是strong pointer,wp则是weak pointer,亦我们常说的强引用和弱引用;实例化sp和wp这两个模板类的类型必须是派生自RefBase的类
1.1  RefBase类

因为这个类拥有对内存回收机制的默认实现,所以android上想要支持内存回收机制的类必须派生自RefBase

下面简单介绍下成员变量和成员函数:

mRefs:

weakref_impl对象,派生于RefBase::weakref_type, 包含了对strong ref和weak ref的具体实现,也就是说RefBase中只包含了一些对外的标准操作,具体的实现在weakref_impl内

void incStrong(const void *id):

强引用计数加1,参数id主要用在debug时跟踪调试,一般都为sp或者wp的对象指针

void decStrong(const void *id):

强引用计数减1,参数id含义同上

void forceIncStrong(const void *id):

强制引用计数加1

Int32_t getStrongCount():

获去强引用计数值

weakref_type * createWeak(const void *id)

弱引用计数加1,然后返回weakref_impl对象

weakref_type* getWeakRefs()

获取weakref_impl对象

void extendObjectLifetime(int32_t mode):

扩展对象的生命期,默认为0,可设置为

OBJECT_LIFETIME_WEAK   = 0x0001,

OBJECT_LIFETIME_FOREVER = 0x0003

这几个参数的作用在下面会详细描述

virtual void onFirstRef()

虚函数,在第一次新增引用计数时,会调用此函数,接下去的其他函数都类似

 

上面也有提到了,RefBase有一个内部基类weakref_type,

它主要包含了对弱引用计数的基本操作,

void incWeak(const void*id):

弱引用计数加1,id参数的意义同上

void decWeak(const void *id):

弱引用计数减1,id同上

bool attemptIncStrong(const void *id):

尝试增加强引用计数,这个函数会在wp promote获取sp时被调用,主要确认wp promote为sp是否成功

bool attemptIncWeak(const void *id):

尝试增加弱引用计数,这个功能只在object lifetime设置为OBJECT_LIFETIME_FOREVER有效

Int32_t getWeakCount():

获取弱引用计数值

 

在RefBase中,可以通过extendObjectLifetime来设置lifetime,有三种life time:

1:default(0),强引用和弱引用的默认行为,不管弱应用计数的值为多少,只要强引用计数的值为0,就释放对象

2:OBJECT_LIFETIME_WEAK,在这种状态下,如果强引用为0时,对象不会被释放,只有在弱引用计数为0的情况下,对象才会被释放

3:OBJECT_LIFETIME_WEAK | OBJECT_LIFETIME_FOREVER,在这种状态下,对象永不会释放

 

第三种情况比较猛,设置了,除非主动delete raw pointer,否则在sp和wp的规则下,是不会被释放的,当然,promote也是永远都会成功的

 

在增加或者减少强引用计数的同时,弱引用计数也会被增加或减少,它们总是配对出现的,下面简单看下几个关键部分的代码:

//增加强引用计数

void RefBase::incStrong(const void* id) const

{

weakref_impl* const refs = mRefs;

//store the object which makes strong reference up, just for track & debug, it is an empty //function

refs->addWeakRef(id);

//increment weak reference

    refs->incWeak(id);

//store the object which makes strong reference up, just for track & debug, if not in debug, it //is an empty function, nothing will be done.

    refs->addStrongRef(id);

    const int32_t c = android_atomic_inc(&refs->mStrong);

    LOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);

#if PRINT_REFS

    LOGD("incStrong of %p from %p: cnt=%d\n", this, id, c);

#endif

    if (c != INITIAL_STRONG_VALUE)  {

        return;

}

 

//if the previous value of mStrong equals INITIAL_STRONG_VALUE

//first for increment strong reference

android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);

//notify first reference done

    const_cast<RefBase*>(this)->onFirstRef();

}

 

//增加弱引用计数

void RefBase::weakref_type::incWeak(const void* id)

{

weakref_impl* const impl = static_cast<weakref_impl*>(this);

//如上面所写,保存id只为做debug用

    impl->addWeakRef(id);

    const int32_t c = android_atomic_inc(&impl->mWeak);

    LOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);

}

 

接下是两个减少引用计数的函数,因为这两个函数涉及到对象的销毁,所以讲的详细点

//减少强引用计数

void RefBase::decStrong(const void* id) const

{

    weakref_impl* const refs = mRefs;

    refs->removeStrongRef(id);

    const int32_t c = android_atomic_dec(&refs->mStrong);

#if PRINT_REFS

    LOGD("decStrong of %p from %p: cnt=%d\n", this, id, c);

#endif

LOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);

//强引用减完后为零,尝试销毁对象

    if (c == 1) {

        const_cast<RefBase*>(this)->onLastStrongRef(id);

        //如果未标明OBJECT_LIFETIME_WEAK,也就是默认的life time,销毁对象

        if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

            delete this;

        }

    }

    refs->removeWeakRef(id);

    refs->decWeak(id);

}

 

//减少弱引用计数

void RefBase::weakref_type::decWeak(const void* id)

{

    weakref_impl* const impl = static_cast<weakref_impl*>(this);

    impl->removeWeakRef(id);

    const int32_t c = android_atomic_dec(&impl->mWeak);

    LOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);

    if (c != 1) return;

    //c == 1,弱引用为0,开尝试释放对象

if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

    //默认life time ,并且对象强引用计数为默认初始值(无强引用计数),释放对象

        if (impl->mStrong == INITIAL_STRONG_VALUE)

            delete impl->mBase;

        else {

//LOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase) ;

    //反之释放内部引用管理类

            delete impl;

        }

} else {

  //OBJECT_LIFETIME_WEAK,并且life time不包含OBJECT_LIFETIME_FOREVER,释放对象

        impl->mBase->onLastWeakRef(id);

        if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {

            delete impl->mBase;

        }

    }

}

 

如果对上面一会释放imple->mBase,一会释放weakref_impl有疑问,继续看RefBase的析构函数:

RefBase::~RefBase()

{

    if (mRefs->mWeak == 0) {

        delete mRefs;

    }

}

RefBase被销毁时,只有当弱引用计数为0时,才会释放weakref_imp对象,上面decWeak中第一和第三个delete,直接释放impl->mBase,肯定没问题,因为这时候弱引用计数已经为0,weakref_imp对象在析构中正常被释放

 

再看第二个delete,在默认life time下,如果RefBase通过sp建立了强引用,在强引用为0的情况下在执行RefBase::decStrong中的以下代码,

if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {

     delete this;

}

RefBase对象肯定会被释放,在释放时调用析构时,由于mRefs->mWeak == 0不成立,内部weaktype_imp对象不会被释放,所以这里需要delete impl以释放内部引用管理对象
1.2  创建sp和wp对象

创建sp:

    sp(T* other):直接使用Raw pointer来创建sp对象

sp(const sp<T>& other):从已有的sp对象拷贝构造

创建wp:

    wp(T* other):直接使用raw pointer来创建wp对象

    wp(const wp<T>& other):从已有的wp对象拷贝

wp(const sp<T>& other):从已有的sp对象拷贝
1.3  关于promote

如果想通过已有的wp对象获取对应的内建对象,需要调用promote来尝试提升为sp,然后通过sp来判断promote是否成功,如果成功,说明内建对象还存在,可以继续使用,反之,需要创建新的内建对象

 

下面来详细查看下promote做了哪些操作:

template<typename T>

sp<T> wp<T>::promote() const

{

    //使用wp内的raw pointer和引用管理类来创建sp

    return sp<T>(m_ptr, m_refs);

}

 

template<typename T>

sp<T>::sp(T* p, weakref_type* refs)

    : m_ptr((p && refs->attemptIncStrong(this)) ? p : 0)

{

}

调用引用管理类的函数attemptIncStrong来尝试增加强引用计数,如成功,说明raw pointer保存的对象还未被释放,可以继续使用,反之,返回false,promote失败

接下去详细看下attemptIncStrong的实现:

bool RefBase::weakref_type::attemptIncStrong(const void* id)

{

    incWeak(id);

    weakref_impl* const impl = static_cast<weakref_impl*>(this);

  

    int32_t curCount = impl->mStrong;

    LOG_ASSERT(curCount >= 0, "attemptIncStrong called on %p after underflow",

               this);

    //如果强引用计数不为初始值并且大于0,说明对象还活着,赶紧将引用计数+1

    while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {

        if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {

            break;

        }

        curCount = impl->mStrong;

    }

//如果强引用计数<=0或者初始值,就代表对象已经被释放了吗?

//不一定

    if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {

        bool allow;

        if (curCount == INITIAL_STRONG_VALUE) {

            // attempting to acquire first strong reference...  this is allowed

            // if the object does NOT have a longer lifetime (meaning the

            // implementation doesn't need to see this), or if the implementation

            // allows it to happen.

            /*curCount为初始值,说明sp未被创建,从一开始都是wp独自掌管,这种情况下,对象肯定存在,剩下就是根据程序需要,让不让它promote成功

            根据以下代码,有两种情况:

            1:生命期不为OBJECT_LIFETIME_WEAK,这时第一判断为true,onIncStrongAttempted结果忽略,allow为true

            2:生命期为OBJECT_LIFETIME_WEAK时,这时就要看onIncStrongAttempted的结果了,默认该函数返回都为true,也就是说,程序的默认行为,allow都为true;当然你也可以在自己的类中

根据需要修改onIncStrongAttempted的默认行为来控制是否允许在这种情况下让其promote成功*/

            allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK

                  || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);

        } else {

            // Attempting to revive the object...  this is allowed

            // if the object DOES have a longer lifetime (so we can safely

            // call the object with only a weak ref) and the implementation

            allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK

                  && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id);

        }

        //不允许, promote失败

        if (!allow) {

            decWeak(id);

            return false;

        }

        curCount = android_atomic_inc(&impl->mStrong);

 

        // If the strong reference count has already been incremented by

        // someone else, the implementor of onIncStrongAttempted() is holding

        // an unneeded reference.  So call onLastStrongRef() here to remove it.

        // (No, this is not pretty.)  Note that we MUST NOT do this if we

        // are in fact acquiring the first reference.

        if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) {

            impl->mBase->onLastStrongRef(id);

        }

    }

  

    impl->addWeakRef(id);

    impl->addStrongRef(id);

 

#if PRINT_REFS

    LOGD("attemptIncStrong of %p from %p: cnt=%d\n", this, id, curCount);

#endif

 

    if (curCount == INITIAL_STRONG_VALUE) {

        android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong);

        impl->mBase->onFirstRef();

    }

  

    return true;

}

 

attemptIncStrong成功,promote成功,继续使用已有的Refbase对象实例,如果失败了,则需重新创建新的对象。


1.4  用法

在android中,在实现基于RefBase的类中,大部分都使用默认的生命期,只有BpBinder会调用extendObjectLifetime(OBJECT_LIFETIME_WEAK)来更改默认生命期;针对默认生命期,其用处和Java的强引用和弱引用类似,下面用代码来粗略的描述下sp和wp的使用:

class A : public RefBase{

//…

}

 

sp<A> sp_inst(new A());

wp<A> wp_weak(sp_inst);

 

然后wp在使用前,需要

sp<A> sp_promote = wp.promote();

//promote 失败

If (sp_promote == NULL)

{

//重新创建对象

sp<A> sp_new(new A());

sp_promote = sp_new;

}

posted @ 2012-03-26 10:35 kenter 阅读(34) 评论(0) 编辑

 在VS 2008之前的MFC版本,自定义菜单字体是一件比较繁琐的事情,常规的做法是自绘菜单项,即定义一个CMenu的派生类,重载MeasureItem和重载DrawItem两个函数。我发现利用VS2008 Feature Pack可以很轻易地做到这件事。具体就是使用CMFCMenuBar类的接口SetMenuFont函数,SetMenuFont已包含了自绘操作,即调用后立即更新菜单栏。下面是一个简单的使用例子:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 // 设置菜单栏字体大小
    LOGFONT m_lf;  
    memset(&m_lf, 0, sizeof(LOGFONT));     

     m_lf.lfHeight = 16;                    

    _tcsncpy_s(m_lf.lfFaceName, LF_FACESIZE, _T("Arial"), 7);  
    m_wndMenuBar.SetMenuFont(&m_lf); 
 // end

}

 

LOGFONT是Windows内部字体的逻辑结构,主要用于设置字体格式,其定义如下:
typedef struct tagLOGFONTA
{
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
CHAR lfFaceName[LF_FACESIZE];
} LOGFONTA


其各个字段的含义如下:
lfHeight:指定逻辑单位的字符或者字符元高度
lfWidth:指定逻辑单位的字体字符的平均宽度
lfEscapement:指定每行文本输出时相对于设备x轴的角度,其单位为1/10度
lfOrientation:指定字符基线相对于设备x轴的角度,其单位为1/10度。此值在Win9X中和lfEscapement具有相同的值,而在WinNT下有时候可能不同
lfWeight:指定字体的重量,Windows中字体重量表示字体的粗细程度,其范围在0~1000之间,正常为400,粗体为700,若此值为空,则使用默认的字体重量
lfItalic:此值为TRUE时,字体为斜体
lfUnderline:此值为TRUE时,字体带下划线
lfStrikeOut:此值为TRUE时,字体带删除线
lfCharSet:指定所使用的字符集,如GB2312_CHARSET,CHINESEBIG5_CHARSET等
lfOutPrecision:指定输出精度,它定义了输出与所要求的字体高度、宽度、字符方向及字体类型等相接近的程度
lfClipPrecision:指定剪辑精度,它定义了当字符的一部分超过剪辑区域时对字符的剪辑方式
lfQuality:指定输出质量,它定义了GDI在匹配逻辑字体属性到实际的物理字体时所使用的方式
lfPitchAndFamily:指定字体的字符间距和族
lfFaceName:指向NULL结尾的字符串的指针,此字符串即为所使用的字体名称,其长度不能超过32个字符,如果为空,则使用系统默认的字体

 

CMFCMenuBar::SetMenuFont

Visual Studio 2010

 

Sets the font for all menu bars in your application.

static BOOL SetMenuFont(
   LPLOGFONT lpLogFont,
   BOOL bHorz = TRUE
);

[in]lpLogFont

A pointer to aLOGFONTstructure that defines the font to set.

[in]bHorz

TRUE if you want thelpLogFontparameter to be used for the vertical font, FALSE if you want it to be used for horizontal font.


TRUEif the method was successful; otherwiseFALSE.


Two fonts are used for allCMFCMenuBarobjects. These separate fonts are used for horizontal and vertical menu bars.

The font settings are global variables and affect allCMFCMenuBarobjects.


Header:afxmenubar.h


posted @ 2012-03-13 08:46 kenter 阅读(231) 评论(0) 编辑
摘要: Query.All("name","a","b");//通过多个元素来匹配数组Query.And(Query.EQ("name","a"),Query.EQ("title","t"));//同时满足多个条件Query.EQ("name","a");//等于Query.Exists("type",true);//判断键值是否存在Query.GT("value",2);//大于&g阅读全文
posted @ 2012-02-24 16:24 kenter 阅读(55) 评论(0) 编辑
摘要: usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Text;usingSystem.Windows.Forms;usingDevExpress.XtraEditors;usingDevExpress.XtraCharts;{publicpartialclassa:DevExpress.XtraEditors.XtraForm{publica(){InitializeComponent();}privateD阅读全文
posted @ 2012-02-23 09:01 kenter 阅读(140) 评论(0) 编辑
摘要: 发现个开源很好的C++框架库,共享一下网址:http://www.codeproject.com/KB/cpp/SystemFramework.aspx介绍: IntroductionStandard native C++ does not have a true object-oriented garbage collector, object-oriented function pointers (delegates), truly object-oriented exception handling, and object-oriented multithreading system. 阅读全文
posted @ 2011-12-30 11:53 kenter 阅读(34) 评论(0) 编辑
摘要: //Hex转BCD码#defineHEX2BCD(x)(((x)%10)+((((x)/10)%10)<<4))/*20->20H*///按照LSB格式把两个字节转化为一个word#defineFLIPW(ray)((((word)(ray)[0])*256)+(ray)[1])//按照LSB格式把一个word转化为两个字节#defineFLOPW(ray,val)(ray)[0]=((val)/256);\(ray)[1]=((val)&0xFF)//得到一个字的高位和低位字节#defineWORD_LO(xxx)((byte)((word)(var)&25阅读全文
posted @ 2011-12-21 13:48 kenter 阅读(21) 评论(0) 编辑
摘要: #pragmaonce//锁接口classILock{public:virtual~ILock(){}virtualvoidLock()=0;virtualvoidUnlock()=0;};/************************************************************************//*CRITICAL_SECTION*//************************************************************************/classCriticalSectionLocker:ILock{publ阅读全文
posted @ 2011-12-21 13:44 kenter 阅读(17) 评论(0) 编辑