| 一片枫叶's profile追梦的人BlogLists | Help |
|
March 22 类似boost库function的免delete委托实现困难不因你害怕而不出现,只因你勇敢面对迎刃而解
------ 鼓励已成大龄程序员的自己 近来试验着重写一个C++的委托功能,就是.net里的delegate关键字描述的东东,似乎C++里喜欢说是成员函数closure,记不太清楚了。 原来的实现跟loki库里的思路差不多,就是利用虚函数描述委托函数的原型,这个原型与具体实现类无关,子类实现函数的功能,实现 里可以记住具体实现函数的类实例,现在看来没有什么很巧妙的东东了: 1、接口层,纯虚接口,与实现对象无关。
class IEvent { public: virtual ~IEvent(){} public: virtual bool operator () (const EventArg& arg)=0; }; 2、实现层,利用模板参数化实现对象类型。
template<typename Obj> class Event : public IEvent { typedef bool (Obj::*Fptr)(const EventArg&); public: Event(Obj* pObj,Fptr fptr) { mObj=pObj; mFunc=fptr; } bool operator () (const EventArg& arg) { return (mObj->*mFunc)(arg); } private: Obj* mObj; Fptr mFunc; }; 3、创建函数,利用模板推导自动提供对象类型。 template<typename Obj> IEvent* create(Obj* pObj,bool (Obj::*fptr)(const EventArg&) { return new Event<Obj>(pObj,fptr); } boost库里的function不使用虚函数机制,而是使用安全的强制转换实现了这种委托功能,它的核心技术在于对成员函数closure
的对象封装(对象指针可以与普通指针互相强制转型)以及模板类的静态成员函数指针(函数原型可以不依赖模板类的模板参数 ,而函数实现可以使用模板类的模板参数,从而实现虚函数接口与实现动态绑定的类似效果)。 委托的实质其实是用一个普通函数原型(实现类无关的)关联一个函数closure对象(绑定到具体实现类的),一个是接口层的,
一个是实现层的。 不管是虚函数的实现还是基于强制转型的实现,closure对象的数据部分主要包含一个对象指针与一个函数指针,这两个指针在委
托的生命周期内是必须有效的,委托无需关心它们指向对象的释放,委托需要关心的是closure对象实例的释放,因为closure对象 有实现类的信息,不能直接作为委托的数据成员(否则委托方就被绑定到一个具体实现类了,而委托的涵义是可以有任意类型的实 现类),所以基于虚函数的实现与boost库的实现均使用了堆内存分配closure对象,从而强加给委托方一个释放的职责。 closure对象因为只是两个指针,占用的空间很小,如果不倒致类型依赖的话,作为委托方的数据成员是完全合适的,由此我们可 以看到一个底层的实现,假设委托方类为Delegation,closure对象类为Closure: class Delegation { public: ... 接口层的东西 ... private: char space[sizeof(Closure)]; }; Delegation d;
Closure c; *(Closure*)&(d.space)=c;//直接浅拷贝 经过这样的处理,Delegation的成员space的空间里就会保存一个Closure实例的数据,在实现代码里再将此数据强制转成Closure
对象也就没问题了(boost的实现里的模板类静态成员函数invoke里正适合做此工作),这里还有一个问题,就是sizeof(Closure) ,其中出现了Closure,会倒致Delegation对Closure的依赖,不过我们只是想知道Closure实例的大小,这个依赖似乎是可以消除的 ,前面说过,Closure里主要是保存一个对象指针与一个成员函数指针,我们只需要伪造一个类似的虚拟类,用这个虚拟类来获取 实例大小即可(也就是Delegation依赖一个虚拟类获取大小,这个大小却适用于任何Closure对象),因为涉及成员函数指针,C++对 有多重继承的类与无多重继承的类的成员函数指针大小是不一样的,我们这里需要取尺寸的最大值,也就是使用一个多重继承的虚 拟类。 上面的方法是利用对象实例浅拷贝来存储实例数据的,与此类似,还有一个办法是像内存池的实现一样重载new操作,采用带placement
参数的new重载,明确的将空间指定,下面我们给出基于重载new的实现,对我而言,用重载new感觉上比对象实例浅拷贝更优雅一些。 //提供Closure存储空间的虚拟类
struct ObjectPlaceHolder { //用来模拟实际情况类的虚拟类,成员函数指针在有继承与无继承下大小不一样, //ObjectPlaceHolder需要提供足够的空间供MemFuncInvoker...实例存储,所以需 //要按多继承情形模拟成员函数指针。 class DummyBaseA {}; class DummyBaseB {}; class DummyClass : public DummyBaseA, public DummyBaseB {}; public: ObjectPlaceHolder() { clear(); } ObjectPlaceHolder(const ObjectPlaceHolder& other) { obj=other.obj; fptr=other.fptr; } inline bool isNull(void) const { return obj==NULL || fptr==NULL; } inline void clear(void) { obj=NULL; fptr=NULL; } private: DummyClass* obj; void (DummyClass::*fptr)(void); }; //Closure对象实现,同时实现了静态成员函数接口invoke,这是实现虚函数相似功能的关键
//重载的new方法使用指定的空间作为对象实例化的空间,因为我们实际上是用的委托方的空 //间,并不需要释放,所以delete方法什么也不做 template<typename Delegation,typename T,typename R> struct MemFuncInvoker0 { private: friend typename Delegation; typedef MemFuncInvoker0<Delegation,T,R> ThisType; typedef R (T::*Fptr)(void); MemFuncInvoker0(T* obj,Fptr func) { mObj=obj; mFunc=func; } static inline R invoke(const ObjectPlaceHolder* address) { const ThisType* pThis=reinterpret_cast<const ThisType*>(address); T* pObj=pThis->mObj; Fptr pFunc=pThis->mFunc; return (pObj->*pFunc)(); } static inline void* operator new (size_t size,const ObjectPlaceHolder* address,size_t space) { _ASSERT(size<=space); return (void*)address; } static inline void operator delete(void*,const ObjectPlaceHolder* address,size_t space) {} private: T* mObj; Fptr mFunc; }; //委托的实现
template<typename R> struct Delegation0 { public: typedef Delegation0<R> ThisType; typedef R (*InvokerType)(const ObjectPlaceHolder*); public: Delegation0() { detach(); } Delegation0(const Delegation0& other) { mObj=other.mObj; invoker=other.invoker; } template<typename T> Delegation0(T* obj,R (T::*fptr)(void)) { attach(obj,fptr); } template<typename T> inline void attach(T* obj,R (T::*fptr)(void)) { invoker=MemFuncInvoker0<ThisType,T,R>::invoke; new(&mObj,sizeof(ObjectPlaceHolder)) MemFuncInvoker0<ThisType,T,R>(obj,fptr); } inline void detach(void) { mObj.clear(); invoker=NULL; } inline bool isNull(void) const { return mObj.isNull() || invoker==NULL; } inline R operator () (void) { return invoker(&mObj); } private: ObjectPlaceHolder mObj;//用来保存MemFuncInvoker实例的空间 InvokerType invoker; }; 上面的实现是对有一个返回类型(含void)无参数的函数样式的委托,一个典型用法如下:
class A { public: char* run(void) { if(!onRun.isNull()) return onRun(); } public: Delegation0<char*> onRun; }; class B
{ public: char* test(void) { return "B::test called !"; } }; int main(void) { A a; B b; a.onRun.attach(&b,&B::test); std::cout<<a.run()<<std::endl; ::_getch(); return 0; } 可以看到动态的将A::run委托到B::test的实现了。 November 20 笔记:DataGridView用对象做数据源时可能存在一个BUG做了一个小的试验,一个DataGridView,一个BindingSource,然后用List<T>作实际的数据源。
当T为struct时,编辑功能失效,修改完按enter,cell里的内容立即恢复为初始值。
大致看了一下DataGridView的操作过程,
1、DataGridView用户操作的事件处理,比如按键、鼠标等;
2、DataGridView.CommitEdit方法;
3、DataGridViewCell.SetValue方法;
4、DataGridView.DataGridViewDataConnection.PushValue方法;
5、PropertyDescriptor.SetValue方法。
以前在用PropertyGrid时曾经研究过像PropertyDescriptor这一类.net设计时架构的关键元素,依稀记得对于struct与对object的处理有很大
的不同,于是试了试将T由struct改为class,再试,编辑功能正常。
没有时间研究具体原因了,以后有兴趣时再说吧,先记下。
从MS的几个大部头控件可以看到这向个控件背后有一个庞大的架构支撑,不过MS在介绍它们时就像介绍别的普通控件一样一寥寥数笔,实在是
令人费解,而且这个几个大部头控件的实现类相当多,不过大多为internal不为用户所见,如果不使用reflector这样的反编译工具,使用这些控件时出问题基本上是一头雾水的。
另外一点,MS的reflection API对.net framework的几个架构的支持非常重要,而reflection API基本上是目前.net程序安全的一个大问题,不知道以后MS如何处理这个矛盾(当然,从open source的角度看,这里本没有矛盾,呵呵)。 November 08 笔记:为ntdll.dll创建移入库ntdll.lib本来以为很简单的,结果费了一番周折,为了防止下次重蹈覆辙,记个笔记。
一开始的想法:
1、用impdef或dumpbin生成一个ntdll.dll的模块定义文件ntdll.def;
2、用lib /def:ntdll.def生成ntdll.lib;
3、按网上hacker给出的函数原形写个头文件ntdll.h.
结果编译时说无法链接_LdrLoadDll@16,看起来是__stdcall调用约定修饰名的原因,但是找不到办法将链接时的_LdrLoadDll@16转换到DLL实际输出的名称LdrLoadDll,查def文件格式时有EXPORTS部分的写法:
entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]
以为可以用这种方法换名,于是改def文件:
重新生成ntdll.lib后程序编译通过,但是运行提示找不到_LdrLoadDll@16入口点,看来这次反过来了,移入库里我名称对了,但还是没对应到DLL实际输出的名称。
想不出办法了,所以到网上搜了一下,发觉原MS在DDK提供了一个ntdll.lib,汗!
最后不知道从哪看到一个说自己建一个dll工程ntdll,然后生成移入库,于是试了试,自己建一个纯win32 dll项目,加一个模块定义文件:
EXPORTS
LdrLoadDll
再在程序里写一个与ntdll.dll里LdrLoadDll原型一样的空函数,编译一下,得到一个ntdll.lib,用这个编译原来的程序,编译通过,运行,
也OK了,用dumpbin看一下生成的ntdll.lib,呵呵,居然它就是用的_LdrLoadDll@16这个名字,但它却能正确的定位ntdll.dll里的LdrLoadDll,搞不明白了,为什么,lib /def:ntdll.def生成的库就不行:(
这方法麻烦点,不过可行,就这样吧,MS的东西,搞不明白的太多了!
August 16 .NET里的优化太差了!一般来说编译型,甚至许多解释型的语言教程里会说:请不要自己优化代码,语言编译器是足够聪明的。不过今天我却发现.NET 2.0的优化实在是太。。。。。。了!
我测试了一下foreach与for循环的代码(为了防止误解编译器的优化能力,我加了一个多线程锁,.NET工程没有单线程与多线程的属性,所以我猜测编译器不敢做优化是害怕多线程并发修改变量,于是特地告诉它这块不会有并发修改的情况):
private void TestColl()
{ List<int> intList=new List<int>(); for(int i=0;i<10;i++) { intList.Add(i); } lock (intList) { for (int i = 0; i < intList.Count; i++) { intList[i].CompareTo(1); intList[i].Equals(1); intList[i].GetHashCode(); intList[i].GetTypeCode(); intList[i].ToString(); } } foreach(int i in intList) { i.CompareTo(1); i.Equals(1); i.GetHashCode(); i.GetTypeCode(); i.ToString(); } } 我知道foreach是利用IEnumerable接口实现的,也就是它会在每次循环调用get_Current与MoveNext来得到集合中的下一个元素,而for语句是直白的,它按我们所写的那样去做,现在我假想一下一般编译器应该会做的优化(第二个循环),在循环开始调get_Item(i)来得到集合中元素的值,然后用这个值依次执行后续的方法调用,因为在这段代码里我们并没有改变集合中的元素,而且这段代码加锁了,确保集合在这期间不会被别的线程改变,所以先取出集合中的元素,再访问它的若干方法是最常见的提取公共表达式相似的优化。那么.NET是这么做的吗?我们来看一下对应的IL:
.method private hidebysig instance void TestColl() cil managed
{ .maxstack 2 .locals init ( [0] [mscorlib]System.Collections.Generic.List`1<int32> list1, [1] int32 num1, [2] int32 num2, [3] int32 num3, [4] [mscorlib]System.Collections.Generic.List`1<int32> list2, [5] int32 num4, [6] int32 num5, [7] int32 num6, [8] int32 num7, [9] int32 num8, [10] [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> enumerator1) L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() L_0005: stloc.0 L_0006: ldc.i4.0 L_0007: stloc.1 L_0008: br.s L_0015 L_000a: ldloc.0 L_000b: ldloc.1 L_000c: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) L_0011: ldloc.1 L_0012: ldc.i4.1 L_0013: add L_0014: stloc.1 L_0015: ldloc.1 L_0016: ldc.i4.s 10 L_0018: blt.s L_000a L_001a: ldloc.0 L_001b: dup L_001c: stloc.s list2 L_001e: call void [mscorlib]System.Threading.Monitor::Enter(object) L_0023: ldc.i4.0 L_0024: stloc.2 L_0025: br.s L_0082 L_0027: ldloc.0 L_0028: ldloc.2 L_0029: callvirt instance !0 [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32) L_002e: stloc.s num4 L_0030: ldloca.s num4 L_0032: ldc.i4.1 L_0033: call instance int32 int32::CompareTo(int32) L_0038: pop L_0039: ldloc.0 L_003a: ldloc.2 L_003b: callvirt instance !0 [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32) L_0040: stloc.s num5 L_0042: ldloca.s num5 L_0044: ldc.i4.1 L_0045: call instance bool int32::Equals(int32) L_004a: pop L_004b: ldloc.0 L_004c: ldloc.2 L_004d: callvirt instance !0 [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32) L_0052: stloc.s num6 L_0054: ldloca.s num6 L_0056: call instance int32 int32::GetHashCode() L_005b: pop L_005c: ldloc.0 L_005d: ldloc.2 L_005e: callvirt instance !0 [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32) L_0063: stloc.s num7 L_0065: ldloca.s num7 L_0067: call instance [mscorlib]System.TypeCode int32::GetTypeCode() L_006c: pop L_006d: ldloc.0 L_006e: ldloc.2 L_006f: callvirt instance !0 [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32) L_0074: stloc.s num8 L_0076: ldloca.s num8 L_0078: call instance string int32::ToString() L_007d: pop L_007e: ldloc.2 L_007f: ldc.i4.1 L_0080: add L_0081: stloc.2 L_0082: ldloc.2 L_0083: ldloc.0 L_0084: callvirt instance int32 [mscorlib]System.Collections.Generic.List`1<int32>::get_Count() L_0089: blt.s L_0027 L_008b: leave.s L_0095 L_008d: ldloc.s list2 L_008f: call void [mscorlib]System.Threading.Monitor::Exit(object) L_0094: endfinally L_0095: ldloc.0 L_0096: callvirt instance [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() L_009b: stloc.s enumerator1 L_009d: br.s L_00d1 L_009f: ldloca.s enumerator1 L_00a1: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() L_00a6: stloc.3 L_00a7: ldloca.s num3 L_00a9: ldc.i4.1 L_00aa: call instance int32 int32::CompareTo(int32) L_00af: pop L_00b0: ldloca.s num3 L_00b2: ldc.i4.1 L_00b3: call instance bool int32::Equals(int32) L_00b8: pop L_00b9: ldloca.s num3 L_00bb: call instance int32 int32::GetHashCode() L_00c0: pop L_00c1: ldloca.s num3 L_00c3: call instance [mscorlib]System.TypeCode int32::GetTypeCode() L_00c8: pop L_00c9: ldloca.s num3 L_00cb: call instance string int32::ToString() L_00d0: pop L_00d1: ldloca.s enumerator1 L_00d3: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext() L_00d8: brtrue.s L_009f L_00da: leave.s L_00ea L_00dc: ldloca.s enumerator1 L_00de: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> L_00e4: callvirt instance void [mscorlib]System.IDisposable::Dispose() L_00e9: endfinally L_00ea: ret .try L_0023 to L_008d finally handler L_008d to L_0095 .try L_009d to L_00dc finally handler L_00dc to L_00ea } 代码挺长的,不过没必要仔细看,数一下get_Item的个数就知道它有没有提取这个公共调用语句了,它的确没有做这种优化,为什么呢?难道是留给JIT编译器来做?不明白了,看来以后遇到这种有公共调用语句的情形,需要自己搞一个临时变量暂存一下结果,如果是集合的话,则优先使用foreach,这样的语句正好帮我们提取了这个公共调用。 VS.NET 里面即使是Release状态也是可以调试的,这有点出人意料,在这个函数里断下来,打开反汇编,可以看到如下显示:
private void TestColl()
{ List<int> intList=new List<int>(); 00000000 push ebp 00000001 mov ebp,esp 00000003 push edi 00000004 push esi 00000005 push ebx 00000006 sub esp,60h 00000009 mov esi,ecx 0000000b lea edi,[ebp-6Ch] 0000000e mov ecx,10h 00000013 xor eax,eax 00000015 rep stos dword ptr es:[edi] 00000017 mov ecx,esi 00000019 xor eax,eax 0000001b mov dword ptr [ebp-18h],eax 0000001e mov dword ptr [ebp-24h],ecx 00000021 cmp dword ptr ds:[00A79BC0h],0 00000028 je 0000002F 0000002a call 752AEFF6 0000002f xor edx,edx 00000031 mov dword ptr [ebp-68h],edx 00000034 xor edx,edx 00000036 mov dword ptr [ebp-28h],edx 00000039 xor edx,edx 0000003b mov dword ptr [ebp-2Ch],edx 0000003e xor edx,edx 00000040 mov dword ptr [ebp-6Ch],edx 00000043 mov ecx,7919F9FCh 00000048 call FBC7ED14 0000004d mov esi,eax 0000004f mov ecx,esi 00000051 call 74790818 00000056 mov dword ptr [ebp-68h],esi for(int i=0;i<10;i++) 00000059 xor edx,edx 0000005b mov dword ptr [ebp-28h],edx 0000005e nop 0000005f jmp 00000071 { intList.Add(i); 00000061 mov edx,dword ptr [ebp-28h] 00000064 mov ecx,dword ptr [ebp-68h] 00000067 cmp dword ptr [ecx],ecx 00000069 call 74791BA8 for(int i=0;i<10;i++) 0000006e inc dword ptr [ebp-28h] 00000071 cmp dword ptr [ebp-28h],0Ah 00000075 jl 00000061 } lock (intList) 00000077 mov eax,dword ptr [ebp-68h] 0000007a mov dword ptr [ebp-6Ch],eax 0000007d mov ecx,dword ptr [ebp-6Ch] 00000080 call 75090841 { for (int i = 0; i < intList.Count; i++) 00000085 xor edx,edx 00000087 mov dword ptr [ebp-2Ch],edx 0000008a nop 0000008b jmp 00000124 { intList[i].CompareTo(1); 00000090 mov edx,dword ptr [ebp-2Ch] 00000093 mov ecx,dword ptr [ebp-68h] 00000096 cmp dword ptr [ecx],ecx 00000098 call 74791278 0000009d mov esi,eax 0000009f mov dword ptr [ebp-34h],esi 000000a2 lea ecx,[ebp-34h] 000000a5 mov edx,1 000000aa call 7459E490 000000af nop intList[i].Equals(1); 000000b0 mov edx,dword ptr [ebp-2Ch] 000000b3 mov ecx,dword ptr [ebp-68h] 000000b6 cmp dword ptr [ecx],ecx 000000b8 call 74791278 000000bd mov esi,eax 000000bf mov dword ptr [ebp-38h],esi 000000c2 lea ecx,[ebp-38h] 000000c5 mov edx,1 000000ca call 7457AD14 000000cf nop intList[i].GetHashCode(); 000000d0 mov edx,dword ptr [ebp-2Ch] 000000d3 mov ecx,dword ptr [ebp-68h] 000000d6 cmp dword ptr [ecx],ecx 000000d8 call 74791278 000000dd mov esi,eax 000000df mov dword ptr [ebp-3Ch],esi 000000e2 lea ecx,[ebp-3Ch] 000000e5 call 7456B10C 000000ea nop intList[i].GetTypeCode(); 000000eb mov edx,dword ptr [ebp-2Ch] 000000ee mov ecx,dword ptr [ebp-68h] 000000f1 cmp dword ptr [ecx],ecx 000000f3 call 74791278 000000f8 mov esi,eax 000000fa mov dword ptr [ebp-40h],esi 000000fd lea ecx,[ebp-40h] 00000100 call 7456B398 00000105 nop intList[i].ToString(); 00000106 mov edx,dword ptr [ebp-2Ch] 00000109 mov ecx,dword ptr [ebp-68h] 0000010c cmp dword ptr [ecx],ecx 0000010e call 74791278 00000113 mov esi,eax 00000115 mov dword ptr [ebp-44h],esi 00000118 lea ecx,[ebp-44h] 0000011b call 7456B400 00000120 nop for (int i = 0; i < intList.Count; i++) 00000121 inc dword ptr [ebp-2Ch] 00000124 mov edi,dword ptr [ebp-2Ch] 00000127 mov ecx,dword ptr [ebp-68h] 0000012a cmp dword ptr [ecx],ecx 0000012c call 74790EDC 00000131 mov esi,eax 00000133 cmp edi,esi 00000135 jl 00000090 0000013b nop 0000013c mov dword ptr [ebp-1Ch],0 00000143 mov dword ptr [ebp-18h],0FCh 0000014a push 4DE3503h 0000014f jmp 00000151 00000151 mov ecx,dword ptr [ebp-6Ch] 00000154 call 75090ABB 00000159 pop eax 0000015a jmp eax foreach(int i in intList) 0000015c lea edx,[ebp-64h] 0000015f mov ecx,dword ptr [ebp-68h] 00000162 cmp dword ptr [ecx],ecx 00000164 call 747971F8 00000169 lea edi,[ebp-54h] 0000016c lea esi,[ebp-64h] 0000016f movs dword ptr es:[edi],dword ptr [esi] 00000170 movs dword ptr es:[edi],dword ptr [esi] 00000171 movs dword ptr es:[edi],dword ptr [esi] 00000172 movs dword ptr es:[edi],dword ptr [esi] 00000173 nop 00000174 jmp 000001BA 00000176 lea ecx,[ebp-54h] 00000179 call 747972B8 0000017e mov esi,eax 00000180 mov dword ptr [ebp-30h],esi { i.CompareTo(1); 00000183 lea ecx,[ebp-30h] 00000186 mov edx,1 0000018b call 7459E490 00000190 nop i.Equals(1); 00000191 lea ecx,[ebp-30h] 00000194 mov edx,1 00000199 call 7457AD14 0000019e nop i.GetHashCode(); 0000019f lea ecx,[ebp-30h] 000001a2 call 7456B10C 000001a7 nop i.GetTypeCode(); 000001a8 lea ecx,[ebp-30h] 000001ab call 7456B398 000001b0 nop i.ToString(); 000001b1 lea ecx,[ebp-30h] 000001b4 call 7456B400 000001b9 nop foreach(int i in intList) 000001ba lea ecx,[ebp-54h] 000001bd call 7479725C 000001c2 mov esi,eax 000001c4 test esi,esi 000001c6 jne 00000176 000001c8 nop 000001c9 mov dword ptr [ebp-1Ch],0 000001d0 mov dword ptr [ebp-18h],0FCh 000001d7 push 4DE34FAh 000001dc jmp 000001DE 000001de lea ecx,[ebp-54h] 000001e1 call 74797250 000001e6 pop eax 000001e7 jmp eax } } 000001e9 nop 000001ea lea esp,[ebp-0Ch] 000001ed pop ebx 000001ee pop esi 000001ef pop edi 000001f0 pop ebp 000001f1 ret 000001f2 mov dword ptr [ebp-18h],0 000001f9 jmp 000001E9 000001fb mov dword ptr [ebp-18h],0 00000202 jmp 0000015C 这些应该便是JIT编译后的最终代码了,不过在VS.NET中调试运行程序时JIT是按调试状态处理的代码,所以这里没办法确认JIT在实际运行时是否会做优化,反正上面的代码是没有优化的了。
注:
上面我说.NET编译器没做优化的原因欠妥,真实的原因可能是.NET语言中方法没有const修饰这个设计缺陷倒致编译器不能确认何时能提取公共调用语句,我没想到的那种情形牛顿顿说的挺明白的(呵呵,他总是想得比较全面):
“
这个,这个似乎不应该优化吧。
foreach 是不是只针对一个特定的可枚举的接口工作?(这和C++的原生数组不同--原生数组是编译器完全理解和控制的,反而和重载的[]很像)
那么实际上编译器不知道各个GetItem(我不了解具体的名字是什么)是否有副作用。
比如说我实现的GetItem 不恰当的在内部统计 本方法的调用次数。如果按照你说的思路自动优化,那么该计数必然失败。
也就是说,该优化是需要了解实现的,编译器很难基于接口外的具体实现去进行优化。 ” August 03 在.NET里使用脚本语言jscript.NET框架库里包含了对MS推出的几个语言的完整编译支持,所以这些语言理论上都可以用来作脚本,不过脚本是随手写的,还是动态语言作为脚本比较合适,我指的这个动态语言就是jscript,在.NET库里对编译的支持集中在一个抽象类System.CodeDom.Compiler.CodeDomProvider,编译与装入一个脚本的写法如下:
CodeDomProvider cdp=new JScriptCodeProvider();
CompilerParameters args = new CompilerParameters();
args.GenerateExecutable = false; args.GenerateInMemory = true; args.ReferencedAssemblies.Add("System.dll"); args.ReferencedAssemblies.Add("System.Data.dll"); args.ReferencedAssemblies.Add("System.Web.dll"); args.ReferencedAssemblies.Add("System.Windows.Forms.dll");
//执行编译,字符串参数scriptCode为脚本内容
CompilerResults res = cdp.CompileAssemblyFromSource(args, scriptCode);
if (res.Output.Count > 0) return null;//编译失败 //执行装入实例的功能,指定全限定名,设为MyLib.MyType
MyType obj=res.CompiledAssembly.CreateInstance("MyLib.MyType") as MyType; 之后就可以调用obj的方法了,上面的MyType是一个定义在宿主程序里的抽象类,脚本中继承这个类以提供具体的功能实现,使用宿主定义抽象类的好处在于宿主程序里可以像常规库一样的使用脚本提供的功能,而不需要使用Type.InvokeMember的怪异句法调用脚本,此外,通过抽象类宿主还可以向脚本展示宿主对象模型。
一个典型的抽象类如下:
public abstract class MyType
{
public MyDOM Dom
{
get{return dom;}
set{dom=value;}
}
public abstract void Run();
}
在脚本里的作法:
public class JSObj extends MyType
{
public override function Run()
{
//脚本功能实现代码
}
}
在宿主程序中将入脚本后,如前述我们得到了MyType实例obj,则调用脚本的过程为:
obj.Dom=innerDom;//向脚本暴露内部对象模型,虽然也可以直接在Run的参数里传递,不过用参数传递的对象数量不能
//过多,如果不是封装成一个根对象的DOM,则可以用多个特性提供。
obj.Run();//执行脚本提供的功能
上面这个过程脚本修改过便需要重新编译再装入,编译会消耗一些时间,我们可以利用JScript语言的特性来提供一个解释式的脚本,那就是使用eval函数。只需要改变一个JSObj类就可以了:
public class JSObj extends MyType
{
public override function Run()
{
//假设我们的脚本代码由一个控件提供,这个控件实例通过抽象类的特性Dom.ScriptEdit提供.
eval(Dom.ScriptEdit.Text,"unsafe");//unsafe参数比较关键,不然脚本会有诸多限制
}
}
现在只需要编译装入一次,之后每次修改Dom.ScriptEdit.Text之后,只要调用obj.Run执行就可以了。
解释执行虽然没有编译的时间消耗,但是执行效率会远低于编译执行,所以我的想法是解释与编译同时提供,解释用于执行短小的脚本,一般这类用于试验或测试,编译用于正式的功能脚本,性能较高。
解释与编译的另一个差别是JScript的eval里的代码不能识别Array,不过可以识别IList,做法很简单,将Array型的对象强制转为IList即可:
var listObj=IList(arrayObj);
.net 2.0开始支持泛型,不过JScript不能访问使用非封闭的泛型参数的方法(参照一下C++的函数模板,这种调用基于模板参数推导,作为动态语言可能不能提供足够的类型信息吧,这是我的猜测:) ),所以如果宿主的DOM里有这样的方法,需要在宿主提供这些方法的非泛型版本(其实就是对每个封闭的泛型参数写一个方法,方法实现就是调相应的泛型方法)。
在实际使用中,我对脚本的编译进行了封装,然后脚本代码主要基于模板实现,因为脚本通常是随手写的,通常不需要定义类与方法,往往就是一个代码片断而已,但是如前所述脚本代码需要实现抽象类,这些框架性的东西用一个模板来提供就可以了。
封装类代码如下:
using System;
using System.Collections.Generic; using System.CodeDom.Compiler; using System.IO; namespace MeshLib
{ public class ScriptInterpreter<T> where T : CodeDomProvider,new() { public ScriptInterpreter() {} /// <summary> /// 借用一个模板文件来构造一个含有一个装配与一个类的script源代码,模板文件中必须有二个可替换变量:#ASSEMBLYNAME#,#CLASSNAME#,分别对应装配名、类名。 /// </summary> public static string ReadTemplate(string templateFile, string assemblyName, string className) { try { StreamReader sr = new StreamReader(templateFile); string templateCode = sr.ReadToEnd(); sr.Close(); return templateCode.Replace("#ASSEMBLYNAME#", assemblyName).Replace("#CLASSNAME#", className); } catch(Exception) { return ""; } } public List<string> ReferencedAssemblies { get { return referencedAssemblies; } } public string CompilerOptions { get { return compilerOptions; } set { compilerOptions = value; } } public List<string> Results { get { return compilerResults; } } public R LoadCode<R>(string scriptCode, string assemblyName, string className) where R : class { try { T cdp = new T(); CompilerParameters args = new CompilerParameters(); args.GenerateExecutable = false; args.GenerateInMemory = true; foreach (string s in ReferencedAssemblies) { args.ReferencedAssemblies.Add(s); } args.CompilerOptions = CompilerOptions; CompilerResults res = cdp.CompileAssemblyFromSource(args, scriptCode); Results.Clear(); foreach (string s in res.Output) { Results.Add(s); } if (res.Output.Count > 0) return null; return res.CompiledAssembly.CreateInstance(assemblyName + "." + className) as R; } catch(Exception eee) { Results.Add(eee.Message); return null; } } public R LoadFile<R>(string file, string assemblyName, string className) where R : class { string content = ReadTemplate(file, assemblyName, className); if (content.Length <= 0) return null; return LoadCode<R>(content, assemblyName, className); } private List<string> referencedAssemblies = new List<string>();
private string compilerOptions = null; private List<string> compilerResults = new List<string>(); } } 脚本模板(一个编译型的模板,一个解释型的模板)
编译型:
import System;
import System.Collections; import System.Collections.Generic; import System.Data; import System.Data.OleDb; import System.IO; import System.Net; import System.Net.Sockets; import System.Diagnostics; import System.Drawing; import System.Web; import System.Web.SessionState; import System.Web.UI; import System.Web.UI.WebControls; import System.Web.UI.HtmlControls; import System.Text; import System.Text.RegularExpressions; import System.Windows.Forms; import System.Threading; import System.Xml; import MeshLib.ShapeFile; import MeshLib; import MeshUI; package #ASSEMBLYNAME#
{ public class #CLASSNAME# extends ScriptCode { public override function Run() { //此处开始写您的脚本代码 } } } 解释型: import System;
import System.Collections; import System.Collections.Generic; import System.Data; import System.Data.OleDb; import System.IO; import System.Net; import System.Net.Sockets; import System.Diagnostics; import System.Drawing; import System.Web; import System.Web.SessionState; import System.Web.UI; import System.Web.UI.WebControls; import System.Web.UI.HtmlControls; import System.Text; import System.Text.RegularExpressions; import System.Windows.Forms; import System.Threading; import System.Xml; import MeshLib.ShapeFile; import MeshLib; import MeshUI; package #ASSEMBLYNAME#
{ public class #CLASSNAME# extends ScriptCode { public override function Run() { eval(script.Text,"unsafe"); } } } 抽象类ScriptCode: public abstract class ScriptCode { public ShapeFile.ShapeFile shapeFile; public MeshPanel meshPanel; public Control script; public Control output; public Mesh<SharedData,NodeData, SideData, CellData> mesh; public List<Mesh<GisSharedData, GisNodeData, GisSideData, GisCellData>> gisMeshes; public abstract void Run(); } 针对编译型与解释型脚本的编译运行与编译+多次运行的方法
编译运行是将模板文件装入供用户编辑,然后直接编译运行最终编辑过的完整代码:
private void CheckCScript() { if (cscriptEdit.Text.Trim().Length <= 0) { string path = Application.ExecutablePath; path = Path.GetDirectoryName(path); string tempFile = Path.Combine(path, "CompileScript.js"); cscriptEdit.Text = ScriptInterpreter<JScriptCodeProvider>.ReadTemplate(tempFile, "CompiledMeshScript", "CompiledJScript"); } } private void CompileAndRunScript() { try { string path = Application.ExecutablePath; path = Path.GetDirectoryName(path); scriptResult.Text = ""; ScriptInterpreter<JScriptCodeProvider> si = new ScriptInterpreter<JScriptCodeProvider>(); si.ReferencedAssemblies.Add("System.dll"); si.ReferencedAssemblies.Add("System.Data.dll"); si.ReferencedAssemblies.Add("System.Drawing.dll"); si.ReferencedAssemblies.Add("System.Web.dll"); si.ReferencedAssemblies.Add("Microsoft.JScript.dll"); si.ReferencedAssemblies.Add("System.Windows.Forms.dll"); si.ReferencedAssemblies.Add(Path.Combine(path, "MeshLib.dll")); si.ReferencedAssemblies.Add(Path.Combine(path, "MeshUI.exe")); ScriptCode compiledScriptCode = si.LoadCode<ScriptCode>(cscriptEdit.Text, "CompiledMeshScript", "CompiledJScript"); if (compiledScriptCode != null) { compiledScriptCode.shapeFile = mainForm.shapeFile; compiledScriptCode.meshPanel = mainForm.meshPanel; compiledScriptCode.script = cscriptEdit; compiledScriptCode.output = scriptResult; compiledScriptCode.mesh = mainForm.mesh; compiledScriptCode.gisMeshes = mainForm.gisMeshes; compiledScriptCode.Run(); } else { string ss = "编译错误:"; foreach (string s in si.Results) { ss += "\r\n" + s; } scriptResult.Text = ss; } } catch (Exception eee) { scriptResult.Text = "产生异常:" + eee.Message; } } 解释型脚本检查是否更新,如有更新则编译:
private void CheckScript() { try { string path = Application.ExecutablePath; path = Path.GetDirectoryName(path); string tempFile = Path.Combine(path, "EvalScript.js"); DateTime dt = File.GetLastWriteTime(tempFile);
if (scriptTime != dt) { scriptTime = dt; } else { return; } ScriptInterpreter<JScriptCodeProvider> si = new ScriptInterpreter<JScriptCodeProvider>();
si.ReferencedAssemblies.Add("System.dll"); si.ReferencedAssemblies.Add("System.Data.dll"); si.ReferencedAssemblies.Add("System.Drawing.dll"); si.ReferencedAssemblies.Add("System.Web.dll"); si.ReferencedAssemblies.Add("Microsoft.JScript.dll"); si.ReferencedAssemblies.Add("System.Windows.Forms.dll"); si.ReferencedAssemblies.Add(Path.Combine(path, "MeshLib.dll")); si.ReferencedAssemblies.Add(Path.Combine(path, "MeshUI.exe")); scriptCode = si.LoadFile<ScriptCode>(tempFile, "MeshScript", "JScript"); if (scriptCode == null) { string ss = "编译脚本文件" + tempFile + "出现了错误:"; foreach (string s in si.Results) { ss += "\r\n" + s; } scriptResult.Text = ss; } } catch (Exception eee) { scriptResult.Text = "产生异常:" + eee.Message; } } private void RunScript()
{ if (scriptCode != null) { try { scriptResult.Text = ""; scriptCode.shapeFile = mainForm.shapeFile; scriptCode.meshPanel = mainForm.meshPanel; scriptCode.script = iscriptEdit; scriptCode.output = scriptResult; scriptCode.mesh = mainForm.mesh; scriptCode.gisMeshes = mainForm.gisMeshes; scriptCode.Run(); } catch (Exception eee) { scriptResult.Text = "产生异常:" + eee.Message; } } } 使用PropertyGrid来为应用提供兼容.NET设计时架构的特性编辑功能只要在应用中使用PropertyGrid控件来提供对应用中的对象模型实例的特性编辑,这便已经是兼容了.NET设计时架构了,不过我发现一个问题,
当我们需要一次性编辑许多对象的时候,量变引起质变,此时严重的性能问题会让我们怀疑是不是不该使用PropertyGrid了(这种量变引起质变的现象在使用第三方库的时候好象经常碰到,大概原因在于库的设计者当初定位的使用环境是常规的,而量变到一个程度时,问题已经不再是常规的了,这好象与库的设计是否可伸缩无关。另一个例子,一般情况动态数组可以容下足够多的东东,我们无需关心它的存储问题,但如果元素数目非常非常多,比如100G的数目,这时就必须要自己考虑了[呵呵,这个例子有点夸张了]。),然而介于.NET设计时架构的良好设计的吸引,实在不愿意就此放弃,所以我想看看如何改造一下PropertyGrid来适应这种新情况新问题。
前面提到的性能问题主要是PropertyGrid在编辑一组对象时是在循环中依次修改各个对象,数目大的时候,这个循环需要很长的时间(这个没办法改变,时间不大可能变少),此时应用界面会失去响应,这在WIN32 GUI里的解决办法是在循环中插入消息处理,顺便加上一个进度条。我们在.net程序里当然也是这个解决思路,不过PropertyGrid在设计时并没有为这个留下接口(附带说一下,PropertyGrid的设计复杂的很,它的代码主要集中在System.Windows.Forms.PropertyGridInternal名空间下,不过很可惜,主要类都是internal的),经过阅读PropertyGrid的代码(用reflector看的,这个不太好玩,关系实在太复杂,只能了解大概),发现直接使用PropertyGrid来编辑一组大数目的对象不失去响应不大可能,所以考虑变通的办法。
虽然PropertyGrid的实现非常复杂,但是在.net框架库里只暴露了很少的几个类:GridItem,PropertyGrid,PropertiesTab等,PropertiesTab类主要用于实现自定义特性(或动态特性)类的编辑,没有逮到什么机会。GridItem代表了特性编辑器里的一行,也就是说它代表对一个特性的编辑(因为特性编辑器的行是可以树状展开的,GridItem类也是组织成树状的,GridItem可以有若干个子GridItem),它有三个属性与我们的目标相关,一个Value代表当前编辑器里的值,一个PropertyDescriptor用来实现它对应的特性的修改,一个Parent用来获取父项目。PropertyGrid的成员N多,不过对我们有用的是一个事件:PropertyValueChanged ,在特性值改变之后,它会触发这个事件,不过在触发这个事件时,与PropertyGrid关联的那组对象的特性已经被修改了。
我们没办法让PropertyGrid直接修改全部需要编辑的对象,现在有一个可能的办法是,提取这组对象中的一部分与PropertyGrid关联,并在PropertyValueChanged事件里对剩下的对象进行更改。(当然也可以只与PropertyGrid控件关联一个对象,不过这样有一个问题是PropertyGrid会显示那个对象的所有特性值,当你的应用展示给最终用户时,用户选择了许多对象,结果展示的是其中一个对象的值,很容易让用户误解所有这些对象的特性值相同,而让PropertyGrid关联多个对象时,它会检查这组对象的特性值是否相同,不同的则设为空[对引用对象]或默认值[对值对象],显然我们可以挑出一些有代表性的对象关联[也就是如果有不同的对象,就用这几个不同的]从而利用PropertyGrid的这一特性)。
现在来看看PropertyValueChanged事件给了我们哪些信息:OldValue 该项目更改前的值,ChangedItem 发生更改的项目(一个GridItem), 也就是我们可以知道新值、旧值、以及一个用于修改对象特性的PropertyDescriptor,看起来好象够了:
PropertyDescriptor.SetValue(obj,ChangedItem.Value);
试验了一下,惨败!!!原因:
一、前面提到过GridItem是组织成树状的,也就是说当一个对象的特性又是一个对象,而我们编辑的是这个特性对象的某个特性时,ChangedItem.PropertyDescriptor是针对那个特性对象的那个特性的,我们调用PropertyDescriptor.SetValue时,第一个参数必须是那个特性对象,然而我们现在并不知道它;
二、如果PropertyGrid关联的是一组对象,那么ChangedItem.PropertyDescriptor不是一个单一对象的特性编辑器,PropertyDescriptor.SetValue的第一个参数是对象数组,并且,数组元素个数必须是PropertyGrid关联的那组对象的个数(我们显然不能假设我们要编辑的对象总是可以分解为PropertyGrid关联的对象个数的整数倍的,特别的,对象总数还可能是质数)。
对问题一,我们可以利用GridItem.Parent来向上回溯到顶级对象,再利用各级GridItem.PropertyGrid.GetValue来得到特性对象的值,直到更改的那个GridItem的上一级,然后,在我们修改了发生更改的那个特性对象的特性后,我们还必须再次回溯一次,因为特性对象有可能是一个值对象的特性,而值对象特性的修改必须通过赋值才能影响到它所属的对象(引用便无此要求)。
对问题二,我们需要的是一个单一对象的特性编辑器,看一下源代码(当然还是用reflector)
internal class MergePropertyDescriptor : PropertyDescriptor
{ ...
public PropertyDescriptor this[int index] { get; }
...
} 这个MergePropertyDescriptor就是编辑一组对象特性的PropertyDescriptor,它有一个索引化的属性可以得到它用来修改每个对象的PropertyDescriptor,现在剩下最后一个问题,便是MergePropertyDescriptor类是internal的,我们不能直接访问,怎么办?呵呵,利用
反射机制:Type.InvokeMember ,这个是通杀的!
基本实现机制差不多就是这样了,剩下的便是在对每个对象修改的空隙里调用Application.DoEvents(),然后更新一下进度条了。
前面说的有点太轻松了,实际做起来还是比较费劲的,好在reflector给了不少参考源代码,下面是对上述一、二的实现:
public static class PropertyAccess
{ public static void UpdateNodeValue<SharedDataT, NodeDataT, SideDataT, CellDataT>(PropertyValueChangedEventArgs e, Mesh<SharedDataT, NodeDataT, SideDataT, CellDataT> mesh, int startIndex) where SharedDataT : ISharedData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where NodeDataT : INodeData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where SideDataT : ISideData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where CellDataT : ICellData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() { LinkedList<AccessInfo> list = BuildPropertyHiberarchy(e); mesh.OnProgressTip("开始更新选中的结点数据......"); mesh.OnProgress(0); for (int i = startIndex; i < mesh.SelectedNodes.Count; i++) { mesh.OnProgress(100 * i / mesh.SelectedNodes.Count); object obj = mesh.SelectedNodes[i];
UpdatePropertyValue(e, obj, list); } mesh.OnProgress(0); mesh.OnProgressTip("结点数据更新完成."); } public static void UpdateSideValue<SharedDataT, NodeDataT, SideDataT, CellDataT>(PropertyValueChangedEventArgs e, Mesh<SharedDataT, NodeDataT, SideDataT, CellDataT> mesh, int startIndex) where SharedDataT : ISharedData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where NodeDataT : INodeData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where SideDataT : ISideData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where CellDataT : ICellData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() { LinkedList<AccessInfo> list = BuildPropertyHiberarchy(e); mesh.OnProgressTip("开始更新选中的边数据......"); mesh.OnProgress(0); for (int i = startIndex; i < mesh.SelectedSides.Count; i++) { mesh.OnProgress(100 * i / mesh.SelectedSides.Count); object obj = mesh.SelectedSides[i];
UpdatePropertyValue(e, obj, list); } mesh.OnProgress(0); mesh.OnProgressTip("边数据更新完成."); } public static void UpdateCellValue<SharedDataT, NodeDataT, SideDataT, CellDataT>(PropertyValueChangedEventArgs e, Mesh<SharedDataT, NodeDataT, SideDataT, CellDataT> mesh, int startIndex) where SharedDataT : ISharedData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where NodeDataT : INodeData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where SideDataT : ISideData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() where CellDataT : ICellData<SharedDataT, NodeDataT, SideDataT, CellDataT>, new() { LinkedList<AccessInfo> list = BuildPropertyHiberarchy(e); mesh.OnProgressTip("开始更新选中的多边形区域数据"); mesh.OnProgress(0); for (int i = startIndex; i < mesh.SelectedCells.Count; i++) { mesh.OnProgress(100 * i / mesh.SelectedCells.Count); object obj = mesh.SelectedCells[i];
UpdatePropertyValue(e, obj, list); } mesh.OnProgress(0); mesh.OnProgressTip("多边形区域数据更新完成."); } private static LinkedList<AccessInfo> BuildPropertyHiberarchy(PropertyValueChangedEventArgs e) { GridItem gridItem = e.ChangedItem; LinkedList<AccessInfo> list = new LinkedList<AccessInfo>(); GridItem gi = gridItem.Parent; while (gi != null && (gi.GridItemType == GridItemType.Category || gi.GridItemType == GridItemType.Root)) gi = gi.Parent; while (gi != null) { AccessInfo ai = new AccessInfo(GetMergePropertyDescriptorItem0(gi.PropertyDescriptor)); if (list.Count <= 0) { list.AddLast(ai); } else { list.AddFirst(ai); } gi = gi.Parent; while (gi != null && (gi.GridItemType == GridItemType.Category || gi.GridItemType == GridItemType.Root)) gi = gi.Parent; } return list; } private static void UpdatePropertyValue(PropertyValueChangedEventArgs e, object target, LinkedList<AccessInfo> list) { GridItem gridItem = e.ChangedItem; object obj = target; foreach (AccessInfo ai in list) { obj = GetPropertyValueCore(obj, ai.PropertyDescriptor); ai.Value = obj; } SetPropertyValueCore(obj, CopyValue(gridItem.Value), GetMergePropertyDescriptorItem0(gridItem.PropertyDescriptor)); LinkedListNode<AccessInfo> infoNode = list.Last; if (infoNode != null) { infoNode.Value.Value = obj; } while (infoNode != null) { obj = infoNode.Value.Value; if (obj.GetType().IsValueType || obj.GetType().IsArray) { if (infoNode.Previous != null) { SetPropertyValueCore(infoNode.Previous.Value.Value, obj, infoNode.Value.PropertyDescriptor); } else { SetPropertyValueCore(target, obj, infoNode.Value.PropertyDescriptor); } } infoNode = infoNode.Previous; } } /// <summary> /// 利用Type的动态成员访问方法获取MergePropertyDescriptor.Item[0],MergePropertyDescriptor类是System.Windows.Forms.PropertyGridInternal下的internal类,不能直接访问。 /// </summary> /// <param name="pd"></param> /// <returns></returns> /// <remarks> /// 另一种访问不可访问成员的办法是用TypeDescriptor.CreateProperty创建一个PropertyDescriptor,然后用PropertyDescriptor.GetValue得到特性值,不过这种方法无法访问对象索 /// 引化属性Item,不适于这里的需求。 /// </remarks> private static PropertyDescriptor GetMergePropertyDescriptorItem0(PropertyDescriptor pd) { Type t = pd.GetType(); PropertyDescriptor _pd = null; PropertyInfo pi=t.GetProperty("Item"); if(pi!=null) { object o = t.InvokeMember("Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.IgnoreCase, null, pd, new object[] { 0 }); _pd = o as PropertyDescriptor; } if(_pd!=null) { return _pd; } return pd; } private static object GetPropertyValueCore(object target, PropertyDescriptor pd) { if (pd == null) { return null; } if (target is ICustomTypeDescriptor) { target = ((ICustomTypeDescriptor)target).GetPropertyOwner(pd); } object obj = pd.GetValue(target); return obj; } private static void SetPropertyValueCore(object target, object value, PropertyDescriptor pd) { if (pd != null) { object obj = target; if (obj is ICustomTypeDescriptor) { obj = ((ICustomTypeDescriptor)obj).GetPropertyOwner(pd); } if (obj != null) { pd.SetValue(obj, value); } } } private static object CopyValue(object value) { if (value != null) { Type type = value.GetType(); if (type.IsValueType) { return value; } object obj = null; ICloneable cloneable = value as ICloneable; if (cloneable != null) { obj = cloneable.Clone(); } if (obj == null) { TypeConverter converter = TypeDescriptor.GetConverter(value); if (converter.CanConvertTo(typeof(InstanceDescriptor))) { InstanceDescriptor descriptor = (InstanceDescriptor)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(InstanceDescriptor)); if ((descriptor != null) && descriptor.IsComplete) { obj = descriptor.Invoke(); } } if (((obj == null) && converter.CanConvertTo(typeof(string))) && converter.CanConvertFrom(typeof(string))) { object _obj = converter.ConvertToInvariantString(value); obj = converter.ConvertFromInvariantString((string)_obj); } } if ((obj == null) && type.IsSerializable) { BinaryFormatter formatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); formatter.Serialize(stream, value); stream.Position = 0; obj = formatter.Deserialize(stream); } if (obj != null) { return obj; } } return value; } class AccessInfo
{ public PropertyDescriptor PropertyDescriptor { get { return propertyDescriptor; } set { propertyDescriptor = value; } } public object Value { get { return val; } set { val = value; } } public AccessInfo() { } public AccessInfo(PropertyDescriptor pd) { propertyDescriptor = pd; } private PropertyDescriptor propertyDescriptor = null;
private object val = null; } } 最后说点别的,与前面提到的值类型与引用类型处理的差异相关,当我们实现一个自定义的UITypeEditor的时候,因为我们是脱离了PropertyGrid控件来操作对象属性,对对象属性的直接更改将不能引起PropertyGrid的GridItem树的逐级更新,PropertyGrid在调用UITypeEditor.EditValue后,它会比较传给EditValue的obj以及由EditValue返回的obj,仅在二者不同时才触发更新,因为EditValue接受的参数是object的,即使是一个值类型的值,也会被装箱成引用对象,因此EditValue中一定不能直接在传入参数上进行in-place修改,而必须要COPY一份对象实例,然而如何才能COPY任意的对象实例呢,事实上不可能,上面的代码中有一个CopyValue,这是来自.net 2.0的实现代码的,可以看到有三种方法让一个对象支持COPY(在上面讲的UITypeEditor.EditValue实现中,值类型也需要这些条件),即:
1、实现ICloneable;
2、与对象类型关联一个TypeConverter并且它可以将特性实例转为InstanceDescriptor,或者,可以将特性实例转为字符串并能由字符串转为特性实例;
3、对象类型指明了[Serializable]属性。 注:这里所说的UITypeEditor.EditValue的要求是考虑一个通用的UITypeEditor时的,如果是一个特定对象,因为我们十分了解那个特定的对象,所以完全可以构造一份实例并逐个特性的赋值,或者,那个对象本来有一个Copy构造。
呵呵,这篇的技巧可是费了我相当多的时间与脑力呢。 使用Lambda表达式为jscript脚本提供一个快速搜索功能这几天为Ollydbg的插件OllyHtml加了一个快速搜索反汇编代码的API,起因是因为在jscript脚本里逐语句的查找太慢了,然而搜索条件又不方便简单的利用函数参数表示,最后我想到了Lambda表达式,感觉在为脚本提供的库中使用Lambda表达式来提高某些操作的效率与灵活性还是比较有用的。(算了,今天写的太多了,手酸了,直接贴代码了)
LAMBDA表达式实现,主要用于构造逻辑表达式,表达式中的基本数据来自ollydbg插件SDK的结构t_disasm:
#pragma once
#include "stdafx.h" #include "resource.h" #include "Plugin.h" #include <vector> #include <map> class ConditionExpression
{ public: virtual bool operator () (t_disasm* p)=0; public: ConditionExpression() { TotalObjectsRef()++; refCount=1; } virtual ~ConditionExpression(void) {} public: virtual void AddRef(void) { refCount++; } virtual void Release(void) { refCount--; if(refCount==0) { TotalObjectsRef()--; /* CString temp; temp.Format("release one , left : %u",TotalObjectsRef()); ::MessageBox(NULL,temp,"TotalCount",MB_OK); */ delete this;
} } public: static inline int TotalObjects(void) { return TotalObjectsRef(); } private: static inline int& TotalObjectsRef(void) { static int totalCount=0; return totalCount; } private: int refCount; }; class AndCondition : public ConditionExpression
{ public: virtual bool operator () (t_disasm* p) { if(!exp1 || !exp2) return false; if((*exp1)(p) && (*exp2)(p)) return true; else return false; } virtual ~AndCondition() { if(exp1) exp1->Release(); if(exp2) exp2->Release(); } public: AndCondition(ConditionExpression* p1,ConditionExpression* p2) { exp1=p1; exp2=p2; if(p1) p1->AddRef(); if(p2) p2->AddRef(); } private: ConditionExpression* exp1; ConditionExpression* exp2; }; class OrCondition : public ConditionExpression
{ public: virtual bool operator () (t_disasm* p) { if(!exp1 || !exp2) return false; if((*exp1)(p) || (*exp2)(p)) return true; else return false; } virtual ~OrCondition() { if(exp1) exp1->Release(); if(exp2) exp2->Release(); } public: OrCondition(ConditionExpression* p1,ConditionExpression* p2) { exp1=p1; exp2=p2; if(p1) p1->AddRef(); if(p2) p2->AddRef(); } private: ConditionExpression* exp1; ConditionExpression* exp2; }; class NotCondition : public ConditionExpression
{ public: virtual bool operator () (t_disasm* p) { if(!exp) return false; if(!(*exp)(p)) return true; else return false; } virtual ~NotCondition() { if(exp) exp->Release(); } public: NotCondition(ConditionExpression* p) { exp=p; if(p) p->AddRef(); } private: ConditionExpression* exp; }; typedef int (*IntValuePtr)(t_disasm* p); typedef char* (*StrValuePtr)(t_disasm* p); //-------------------------------------------------------- /* *CompareType : 0 = EQ , 1 = GE , -1 = LE */ //-------------------------------------------------------- template<int CompareType> class CompareCondition : public ConditionExpression { public: virtual bool operator () (t_disasm* p) { if(!p || !fptr) return false; return Compare<CompareType>::Do(p,fptr,value); } virtual ~CompareCondition() {} public: CompareCondition(IntValuePtr _fptr,int val) { fptr=_fptr; value=val; } private: IntValuePtr fptr; int value; private: template<int CT=0> class Compare { public: static inline bool Do(t_disasm* p,IntValuePtr fptr,int val) { return (*fptr)(p)==val; } }; template<> class Compare<1> { public: static inline bool Do(t_disasm* p,IntValuePtr fptr,int val) { return (*fptr)(p)>=val; } }; template<> class Compare<-1> { public: static inline bool Do(t_disasm* p,IntValuePtr fptr,int val) { return (*fptr)(p)<=val; } }; }; class StringLikeCondition : public ConditionExpression { typedef std::vector<CString> Strings; public: virtual bool operator () (t_disasm* p) { if(!p || !fptr) return false; CString src=fptr(p); Strings::iterator it=strs.begin(); while(it!=strs.end()) { if(src.Find(*it)>=0) return true; it++; } return false; } virtual ~StringLikeCondition() {} public: StringLikeCondition(StrValuePtr _fptr,const char* s) { fptr=_fptr; char *buf=new char[::strlen(s)+1]; ::strcpy(buf,s); char* token = strtok(buf, "%"); while( token != NULL ) { strs.push_back(CString(token)); token = strtok(NULL, "%"); } } private: StrValuePtr fptr; Strings strs; }; //下面是为表达式提供基本值的若干个类,一个类对应t_disasm结构的一个成员,它们实际上被用作函数指针。
#define FieldValueClass(NAME) NAME##FieldValue
#define DefFieldValueClass(NAME,FN) \
class FieldValueClass(NAME)\ {\ public:\ static inline int Value(t_disasm* p)\ {\ return (int)p->##FN;\ }\ }; #define DefStrFieldValueClass(NAME,FN) \
class FieldValueClass(NAME)\ {\ public:\ static inline char* Value(t_disasm* p)\ {\ return (char*)p->##FN;\ }\ }; DefFieldValueClass(IP,ip)
DefFieldValueClass(CmdType,cmdtype) DefFieldValueClass(MemType,memtype) DefFieldValueClass(PrefixNum,nprefix) DefFieldValueClass(Indexed,indexed) DefFieldValueClass(JumpConst,jmpconst) DefFieldValueClass(JumpTable,jmptable) DefFieldValueClass(AddrConst,adrconst) DefFieldValueClass(ImmConst,immconst) DefFieldValueClass(ZeroImm,zeroconst) DefFieldValueClass(FixupOffset,fixupoffset) DefFieldValueClass(FixupSize,fixupsize) DefFieldValueClass(JumpAddr,jmpaddr) DefFieldValueClass(Error,error) DefFieldValueClass(Warnings,warnings) DefFieldValueClass(OPType1,op[0].optype) DefFieldValueClass(OPType2,op[1].optype) DefFieldValueClass(OPType3,op[2].optype) DefFieldValueClass(OPSize1,op[0].opsize) DefFieldValueClass(OPSize2,op[1].opsize) DefFieldValueClass(OPSize3,op[2].opsize) DefFieldValueClass(OPSeg1,op[0].seg) DefFieldValueClass(OPSeg2,op[1].seg) DefFieldValueClass(OPSeg3,op[2].seg) DefFieldValueClass(OPConst1,op[0].opconst) DefFieldValueClass(OPConst2,op[1].opconst) DefFieldValueClass(OPConst3,op[2].opconst) DefFieldValueClass(OPEaxScale1,op[0].regscale[REG_EAX]) DefFieldValueClass(OPEcxScale1,op[0].regscale[REG_ECX]) DefFieldValueClass(OPEdxScale1,op[0].regscale[REG_EDX]) DefFieldValueClass(OPEbxScale1,op[0].regscale[REG_EBX]) DefFieldValueClass(OPEspScale1,op[0].regscale[REG_ESP]) DefFieldValueClass(OPEbpScale1,op[0].regscale[REG_EBP]) DefFieldValueClass(OPEsiScale1,op[0].regscale[REG_ESI]) DefFieldValueClass(OPEdiScale1,op[0].regscale[REG_EDI]) DefFieldValueClass(OPEaxScale2,op[1].regscale[REG_EAX]) DefFieldValueClass(OPEcxScale2,op[1].regscale[REG_ECX]) DefFieldValueClass(OPEdxScale2,op[1].regscale[REG_EDX]) DefFieldValueClass(OPEbxScale2,op[1].regscale[REG_EBX]) DefFieldValueClass(OPEspScale2,op[1].regscale[REG_ESP]) DefFieldValueClass(OPEbpScale2,op[1].regscale[REG_EBP]) DefFieldValueClass(OPEsiScale2,op[1].regscale[REG_ESI]) DefFieldValueClass(OPEdiScale2,op[1].regscale[REG_EDI]) DefFieldValueClass(OPEaxScale3,op[2].regscale[REG_EAX]) DefFieldValueClass(OPEcxScale3,op[2].regscale[REG_ECX]) DefFieldValueClass(OPEdxScale3,op[2].regscale[REG_EDX]) DefFieldValueClass(OPEbxScale3,op[2].regscale[REG_EBX]) DefFieldValueClass(OPEspScale3,op[2].regscale[REG_ESP]) DefFieldValueClass(OPEbpScale3,op[2].regscale[REG_EBP]) DefFieldValueClass(OPEsiScale3,op[2].regscale[REG_ESI]) DefFieldValueClass(OPEdiScale3,op[2].regscale[REG_EDI]) DefStrFieldValueClass(Dump,dump)
DefStrFieldValueClass(Disasm,result) DefStrFieldValueClass(Comment,comment) DefStrFieldValueClass(OPComment1,opinfo[0]) DefStrFieldValueClass(OPComment2,opinfo[1]) DefStrFieldValueClass(OPComment3,opinfo[2]) 下面是对这些表达式的IDispatch封装,从而可用于jscript:
//逻辑条件类,支持AND,OR,NOT逻辑操作
class Condition : public IDispatch
{ public: virtual BOOL __stdcall get_IsCondition(void) { return TRUE; } virtual IDispatch* __stdcall And(IDispatch* v) { BOOL r=DispatchDriver::GetProperty(v,L"IsCondition",Type2Type<BOOL>()); if(r && pCondition) { Condition* pv=static_cast<Condition*>(v); AndCondition* p=new AndCondition(pCondition,pv->GetCondition());
Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } return NULL; } virtual IDispatch* __stdcall Or(IDispatch* v) { BOOL r=DispatchDriver::GetProperty(v,L"IsCondition",Type2Type<BOOL>()); if(r && pCondition) { Condition* pv=static_cast<Condition*>(v); OrCondition* p=new OrCondition(pCondition,pv->GetCondition());
Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } return NULL; } virtual IDispatch* __stdcall Not(void) { if(pCondition) { NotCondition* p=new NotCondition(pCondition); Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } return NULL; } public: BEGIN_INTF(Condition) PROPERTYGET(IsCondition,true) METHOD(And) METHOD(Or) METHOD(Not) END_INTF() public: Condition() { pCondition=NULL; } ~Condition() { if(pCondition) pCondition->Release(); } void Init(ConditionExpression* p) { pCondition=p; if(p) p->AddRef(); } ConditionExpression* GetCondition(void) { return pCondition; } private: ConditionExpression* pCondition; }; #define DefFieldMember(NAME) \
virtual IDispatch* __stdcall get_##NAME(void)\ {\ IntegerFields* p=IntegerFields::CreateDispatch();\ p->Init(FieldValueClass(NAME)::Value);\ return p;\ } #define DefStrFieldMember(NAME) \
virtual IDispatch* __stdcall get_##NAME(void)\ {\ StringFields* p=StringFields::CreateDispatch();\ p->Init(FieldValueClass(NAME)::Value);\ return p;\ } //t_disasm结构中整型成员的封装,这个类提供基本值,以及由基本值参与比较运算EQ(等于),GE(大于等于),LE(小于等于)
//而得到的条件表达式,这样得到的条件表达式可以直接传给FindDisasm2方法用作条件,也可以经由前一个类的AND,OR,NOT操作
//合成新的条件表达式后再用于FindDisasm2方法。
class IntegerFields : public IDispatch
{ public: virtual IDispatch* __stdcall EQ(int v) { if(!fptr) return NULL; ConditionExpression* p=new CompareCondition<0>(fptr,v); Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } virtual IDispatch* __stdcall GE(int v) { if(!fptr) return NULL; ConditionExpression* p=new CompareCondition<1>(fptr,v); Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } virtual IDispatch* __stdcall LE(int v) { if(!fptr) return NULL; ConditionExpression* p=new CompareCondition<-1>(fptr,v); Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } virtual IDispatch* __stdcall get_IP(void) { IntegerFields* p=IntegerFields::CreateDispatch(); p->Init(FieldValueClass(IP)::Value); return p; } DefFieldMember(CmdType)
DefFieldMember(MemType) DefFieldMember(PrefixNum) DefFieldMember(Indexed) DefFieldMember(JumpConst) DefFieldMember(JumpTable) DefFieldMember(AddrConst) DefFieldMember(ImmConst) DefFieldMember(ZeroImm) DefFieldMember(FixupOffset) DefFieldMember(FixupSize) DefFieldMember(JumpAddr) DefFieldMember(Error) DefFieldMember(Warnings) DefFieldMember(OPType1) DefFieldMember(OPType2) DefFieldMember(OPType3) DefFieldMember(OPSize1) DefFieldMember(OPSize2) DefFieldMember(OPSize3) DefFieldMember(OPSeg1) DefFieldMember(OPSeg2) DefFieldMember(OPSeg3) DefFieldMember(OPConst1) DefFieldMember(OPConst2) DefFieldMember(OPConst3) DefFieldMember(OPEaxScale1) DefFieldMember(OPEcxScale1) DefFieldMember(OPEdxScale1) DefFieldMember(OPEbxScale1) DefFieldMember(OPEspScale1) DefFieldMember(OPEbpScale1) DefFieldMember(OPEsiScale1) DefFieldMember(OPEdiScale1) DefFieldMember(OPEaxScale2) DefFieldMember(OPEcxScale2) DefFieldMember(OPEdxScale2) DefFieldMember(OPEbxScale2) DefFieldMember(OPEspScale2) DefFieldMember(OPEbpScale2) DefFieldMember(OPEsiScale2) DefFieldMember(OPEdiScale2) DefFieldMember(OPEaxScale3) DefFieldMember(OPEcxScale3) DefFieldMember(OPEdxScale3) DefFieldMember(OPEbxScale3) DefFieldMember(OPEspScale3) DefFieldMember(OPEbpScale3) DefFieldMember(OPEsiScale3) DefFieldMember(OPEdiScale3) public:
BEGIN_INTF(IntegerFields) METHOD(EQ) METHOD(GE) METHOD(LE) PROPERTYGET(IP,true) PROPERTYGET(CmdType,true) PROPERTYGET(MemType,true) PROPERTYGET(PrefixNum,true) PROPERTYGET(Indexed,true) PROPERTYGET(JumpConst,true) PROPERTYGET(JumpTable,true) PROPERTYGET(AddrConst,true) PROPERTYGET(ImmConst,true) PROPERTYGET(ZeroImm,true) PROPERTYGET(FixupOffset,true) PROPERTYGET(FixupSize,true) PROPERTYGET(JumpAddr,true) PROPERTYGET(Error,true) PROPERTYGET(Warnings,true) PROPERTYGET(OPType1,true) PROPERTYGET(OPType2,true) PROPERTYGET(OPType3,true) PROPERTYGET(OPSize1,true) PROPERTYGET(OPSize2,true) PROPERTYGET(OPSize3,true) PROPERTYGET(OPSeg1,true) PROPERTYGET(OPSeg2,true) PROPERTYGET(OPSeg3,true) PROPERTYGET(OPConst1,true) PROPERTYGET(OPConst2,true) PROPERTYGET(OPConst3,true) PROPERTYGET(OPEaxScale1,true) PROPERTYGET(OPEcxScale1,true) PROPERTYGET(OPEdxScale1,true) PROPERTYGET(OPEbxScale1,true) PROPERTYGET(OPEspScale1,true) PROPERTYGET(OPEbpScale1,true) PROPERTYGET(OPEsiScale1,true) PROPERTYGET(OPEdiScale1,true) PROPERTYGET(OPEaxScale2,true) PROPERTYGET(OPEcxScale2,true) PROPERTYGET(OPEdxScale2,true) PROPERTYGET(OPEbxScale2,true) PROPERTYGET(OPEspScale2,true) PROPERTYGET(OPEbpScale2,true) PROPERTYGET(OPEsiScale2,true) PROPERTYGET(OPEdiScale2,true) PROPERTYGET(OPEaxScale3,true) PROPERTYGET(OPEcxScale3,true) PROPERTYGET(OPEdxScale3,true) PROPERTYGET(OPEbxScale3,true) PROPERTYGET(OPEspScale3,true) PROPERTYGET(OPEbpScale3,true) PROPERTYGET(OPEsiScale3,true) PROPERTYGET(OPEdiScale3,true) END_INTF() public: IntegerFields() { fptr=NULL; } void Init(IntValuePtr _fptr) { fptr=_fptr; } private: IntValuePtr fptr; }; //t_disasm结构中字符串成员的封装,这个类提供基本值,以及由基本值参与字符串搜索Like
//而得到的条件表达式,这样得到的条件表达式可以直接传给FindDisasm2方法用作条件,也可以经由前一个类的AND,OR,NOT操作
//合成新的条件表达式后再用于FindDisasm2方法。
class StringFields : public IDispatch { public: virtual IDispatch* __stdcall Like(BSTR str) { if(!fptr) return NULL; StringLikeCondition* p=new StringLikeCondition(fptr,CString(str)); Condition* ptr=Condition::CreateDispatch(); ptr->Init(p); p->Release(); return ptr; } DefStrFieldMember(Dump)
DefStrFieldMember(Disasm) DefStrFieldMember(Comment) DefStrFieldMember(OPComment1) DefStrFieldMember(OPComment2) DefStrFieldMember(OPComment3) public:
BEGIN_INTF(StringFields) METHOD(Like) PROPERTYGET(Dump,true) PROPERTYGET(Disasm,true) PROPERTYGET(Comment,true) PROPERTYGET(OPComment1,true) PROPERTYGET(OPComment2,true) PROPERTYGET(OPComment3,true) END_INTF() public: StringFields() { fptr=NULL; } void Init(StrValuePtr _fptr) { fptr=_fptr; } private: StrValuePtr fptr; }; 反汇编代码搜索方法的实现:
virtual IDispatch* __stdcall FindDisasm2(DWORD addr,IDispatch* condition)
{ //判断参数是否条件表达式,因为没有定义新接口,所以不能用QueryInterface来确定。
BOOL r=DispatchDriver::GetProperty(condition,L"IsCondition",Type2Type<BOOL>()); if(!r) return NULL; Condition* pv=static_cast<Condition*>(condition); ConditionExpression* pCE=pv->GetCondition(); pCE->AddRef();//引用计数加1 DWORD endaddr;
int ct=0; int size = 0; t_memory* tmem = Findmemory(addr); char cmd[MAXCMDSIZE]={0}; do { addr = Disassembleforward(0, tmem->base, tmem->size, addr, 1, 1); endaddr = Disassembleforward(0, tmem->base, tmem->size, addr, 1, 1); size = Readcommand(addr, cmd); if(addr == tmem->base + tmem->size)
size = 0; if(size) { t_disasm dasm; ::memset(&dasm,0,sizeof(t_disasm)); DWORD dsize=0; unsigned char* bytes=Finddecode(addr,&dsize); ::Disasm((uchar*)cmd,size,addr,bytes,&dasm,DISASM_CODE,NULL); //应用LAMBDA表达式执行计算来得到判断
if((*pCE)(&dasm)) { pCE->Release();//引用计数减1 DisasmInfo* p=DisasmInfo::CreateDispatch();
p->Init(&dasm); return p; } } if(ct % EXTERN_EVENT_INTERVAL == 0)
{ dlg.DoMessageLoopOnce(); } ct++; } while(size != 0); pCE->Release();//引用计数减1 return NULL; } June 22 由于关联倒致的继承关系的失效近来做项目时发现一个继承关系失效的情形,感觉应该还是比较常见的。
我们的项目中有一个水利上要用的点、线、面的概念,它们是在传统的几何所说的点、线、面的基础上扩充了相关的水利方面的属性,从概念上粗略看,带有水利属性的点也是一个几何意义上的点,带有水利属性的线也是一个几何意义上的线,带有水利属性的面也是一个几何意义上的面,似乎是很合适的继承关系,所以一开始我也就这么认为了,结果在细化时遇到了麻烦,麻烦出在研究点、线、面的关系的时候。
我们首先按继承考虑,所以有一个几何意义上的点、线、面层次,也有一个水利意义上的点、线、面层次,后者分别是前者的子类,现在来看横向的关系,在几何意义上线由点组成,面由线组成,所以存在一个组合关系,我们为几何意义层次的点、线、面加上这些关系:
几何意义层面的类:
class Point
{
}
class Line
{
Point[2] points;
}
class Plane
{
Line[] lines;
}
水利意义层面的类:
class SubPoint : Point
{
}
class SubLine : Line
{
}
class SubPlane : Plane
{
}
到现在为止,还是一切正常,接下来我们考虑水利意义层面的类的应用场景时,发现水利意义上的SubLine需要知道水利意义上SubPoint,水利意义上的SubPlane需要知道水利意义上的SubLine,当然我们已经从类的继承里得到了一些相互关系,SubLine能够了解Point,SubPlane能够了解Line,但是,仅仅知道这些是不够的,SubLine需要了解的是SubPoint的水利属性而不是Point的几何属性,SubPlane需要了解的是SubLine的水利属性而不是Line的几何属性,这样,我们得到新的水利层面的实现:
class SubPoint : Point
{
}
class SubLine : Line
{
SubPoint[2] subPoints;
}
class SubPlane : Plane
{
SubLine[] subLines;
}
好了,现在问题出现了,我们发觉SubLine的实例有两个属性,一个是points,一个是subPoints,显然对SubLine来说,这两个集合属性里的元素是相同的,也就是说有冗余,SubPlane也有同样的问题,当然,还有另一个办法,那就是仍然使用先前的水利层面的实现,不添加subLines属性,在使用时使用向下转型,向下转型?这可不是好现象。
现在我们再回过头来看Point,Line,Plane,SubPoint,SubLine,SubPlane的关系:
Point --------------------<> Line --------------------<> Plane
/ \ / \ / \ |_| |_| |_|
| | |
| | |
| | |
| | |
| | |
SubPoint--------------<>SubLine-------------------<>SubPlane
呵呵,现在根源出来了,我们看SubLine依然 is-a Line吗?因为我们并没有SubLine对Point的关联,所以这个继承是有问题的,也就是说由于Line与Point的关联关系在SubLine类中不存在,SubLine不再 is-a Line了。SubPlane的情况与此类似。我们直观得到的水利意义的点、线、面是几何意义的点、线、面这个概念原来是不准确的!
发现这个问题后我查了一下设计模式,看看它里面两个层次的类的横向关系是什么样子的,结果发现,它们的父类层次的关系与子类层次的关系一定是反向的,也就是说如果SA的父类A依赖了SB的父类B,那么SA一定不会再有对SB的依赖,通常情况(许多模式中出现这样的关系)是SB有一个对SA的依赖,这样子父类层次的依赖与子类层次的依赖方向相反,最终在子类层次形成一个环,父类层次的依赖利用抽象可以得到子类层次的具体实现(十分有趣的是,《设计模式》里这种关联出现的几率十分之高!)。(题外话:有一本讲UML的书里说设计的一个核心关注就是如何减少依赖,所以绘图时用到 ------> 这个时一定要小心)
原因找到了,那么如何解决这个问题呢,很显然的一点,我们需要将继承关系去掉,这样子几何层面的点、线、面与水利层面的点、线、面互相独立了,这种绝对的分离显然感觉不是太好,因为它们实际上确实是有关的,至少在代码上会有许多相似的地方。我想到的一个解决方案(不知道有没有更好的)是我们重新定义水利层面的点、线、面,将其几何特征与水利特征分组,这样子,一个水利层面的点变成一个几何层面的点再加上水利特征数据,线与面也如此,也就是将前面的继承概念变成组合概念,结果变成六个类:Point,Line,Plane,PointData,LineData,PlaneData
现在的类关系是这样的:
Point ---------------------<> Line ---------------------<> Plane
/|\ /|\ /|\
| | |
| | |
| | |
| | |
| | |
| | |
\|/ \|/ \|/
PointData LineData PlaneData
现在只有一个几何层面的点、线、面概念了,而水利层面则是附在几何层面点、线、面上数据,实际情形水利层面可能有多种这样数据,这样一个Point类会关联多个PointData类,这里恰好能用上参数化类型,所以结果Point,Line,Plane三个类变成了Point<PointDataT,LineDataT,PlaneDataT>,Line<PointDataT,LineDataT,PlaneDataT>,
Plane<PointDataT,LineDataT,PlaneDataT>。
.NET中泛型的限制最近用.net 2.0做一个项目,里面较多的用到了泛型,使用一段时间后发现.net里泛型的限制挺大的,在MSDN里有c# genetic与C++ template的比较:
“C# 泛型和 C++ 模板都是用于提供参数化类型支持的语言功能。然而,这两者之间存在许多差异。在语法层面上,C# 泛型是实现参数化类型的更简单方法,不具有 C++ 模板的复杂性。此外,C# 并不尝试提供 C++ 模板所提供的所有功能。在实现层面,主要区别在于,C# 泛型类型替换是在运行时执行的,从而为实例化的对象保留了泛型类型信息。 以下是 C# 泛型和 C++ 模板之间的主要差异:
从中可以看到:
1、.net泛型参数在定义的类或方法中仅可访问泛型参数约束所指明的类或接口的方法,即泛型类对泛型参数的使用是基于特定的接口或抽象类来访问泛型参数实例,这限定在了一种纯OO运行时多态的语义下,使得泛型实例的泛型参数类型只能是泛型参数约束类型的子类型。尽管默认泛型参数是Object这个所有类的基类,但因为Object的接口限制,这种情况泛型类几乎什么也干不了,除了作为泛型参数实例的容器之外。
2、没有显式专有化与局部专有化,也就是说没有编译时类型选择的能力了(也就是没有编译时多态了!),而这些特性可以说是C++模板成功的主要功臣,也是STL,LOKI,BOOST等库需要的基础特征,也就是说,这才是真正的泛型所具有的特性。
3、不能用泛型参数作泛型类型的基类,我们知道ATL中大量使用这个特性,这是编译时多态的另一种表现,这也尚失了。
4、泛型参数是基于实例访问的,所以任何静态类接口的方法都不能被使用(实际上没有静态类接口这个语言元素,但我们常常需要这样的语义,比如工厂方法模式的实现,我感觉这个概念是指类的一组静态方法,这些方法在不同的类中均有相同的签名),而且泛型参数实例仅有一种缺省构造约束,只能用无参数构造实例化泛型参数,这限制了泛型参数实例的创建。
回头看一下,1、2、3、4其实是一脉相承的,亦即.net泛型是纯面向对象环境里的参数化类型,并且它仅提供动态多态的支持,所以.net泛型似乎不能称为泛型。
May 22 为.NET 2.0的WebBrowser控件提供一个通用的将.NET对象暴露给DHTML的扩展对象2.0提供的WebBrowser控件对IE的封装十分酷,特别是其ObjectForScripting特性,只要将一个.net里的对象实例赋给它,就可以在DHTML中用window.external访问该.net对象了(有一个限制是该.net对象必须使用ComVisible属性修饰成COM可见的),如果用IE作界面,DHTML可以认为是UI与控制器,.NET中则实现对象模型,如果可以在DHTML中自由的访问.NET里的对象,那会让人很舒服.
我想到的方法是在external上提供一个CreateObject方法,直接由脚本来创建.net里的对象实例,但是有一个问题,.net里有许多对象并不是COM可见的,这样的对象实例无法返回给DHTML,所以必须对这样的对象作些处理.
对非COM可见的对象,要让DHTML来访问,必须作一个代理,代理对象设成COM可见的,然后代理对象具有原对象一样的访问接口.因为目标客户是javascript脚本,它访问对象是通过IDispatch[Ex]接口来实现的,所以我们的代理对象实际上并不需要与原对象有完全一致的接口,这样,有三种可能的方式:
1、直接修原.NET对象,让它变成COM可见的;
2、在.NET中实现一个对象,它实现IDispatch接口,实现的功能是访问原.net对象的属性与方法;
3、利用动态装配与反射,动态的生成一个代理类,接口与原.net类相同,并设置为COM可见的。
方法1我不知道如何做,因为.net框架没有提供对现有类的修改的方式,如果是cracker的方式,我想可能会比较复杂并且不通用(更重要的是,我不知道怎么弄,谁要是知道,请告诉我,谢了)。
方法2理论上是绝对可行的,但是我打算用C#来写,也就是说企图用托管代码实现IDispatch接口,实验失败,MSDN上好象是说,CLR总是会替.net对象实现IUnknown与IDispatch接口;如果是用C++,我想IDispatch实现成非托管的,然后其实现功能利用C++互操作调.NET的相关实现,然后将非托管IDispatch实例返回并使用自己的定制Marshal应该是可行的(没有试验过,只是想法)。
现在只剩下方法3了,我试验了这种方法,可行。
不多说了,直接看代码(试验性的,其中的定制Marshal是试验用的,现在的实现与.NET默认的Marshal是一样的,所以是可以去掉的)
public class MyMarshal : ICustomMarshaler
{ #region ICustomMarshaler 成员 public void CleanUpManagedData(object ManagedObj)
{} public void CleanUpNativeData(IntPtr pNativeData)
{} public int GetNativeDataSize()
{ return -1; } public IntPtr MarshalManagedToNative(object o)
{ IntPtr i = Marshal.GetIDispatchForObject(o); return i; } public object MarshalNativeToManaged(IntPtr pNativeData)
{ throw new Exception("The method or operation is not implemented."); } #endregion
static public ICustomMarshaler GetInstance(string pstrCookie)
{ return ins; } static private MyMarshal ins = new MyMarshal();
} [ComVisible(true)] public class External { [method: ComVisible(false)] static private object WrapAsComVisible(Type t, object o) { Attribute[] attrs = Attribute.GetCustomAttributes(t); foreach (Attribute a in attrs) { if (a is ComVisibleAttribute) { ComVisibleAttribute cva = a as ComVisibleAttribute; if (cva.Value) return o; } } AssemblyName an = new AssemblyName("__" + t.Namespace + "_" + t.Name + "_" + t.GetHashCode()); AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); ModuleBuilder mob = ab.DefineDynamicModule("_" + t.Name + "_" + t.GetHashCode()); TypeBuilder tb = mob.DefineType(t.Name + "_" + t.GetHashCode(), TypeAttributes.Public); Type[] cts = new Type[] { typeof(bool) }; ConstructorInfo ci = typeof(ComVisibleAttribute).GetConstructor(cts); CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { true }); tb.SetCustomAttribute(cab); tb.DefineDefaultConstructor(MethodAttributes.Public); FieldBuilder innerFieldInfo = tb.DefineField("innerObj", t, FieldAttributes.Public); MemberInfo[] memberInfos = t.GetMembers();
foreach (MemberInfo memberInfo in memberInfos) { switch (memberInfo.MemberType) { case MemberTypes.Field: { FieldInfo fi = memberInfo as FieldInfo; PropertyBuilder pb = tb.DefineProperty(fi.Name, System.Reflection.PropertyAttributes.None, fi.FieldType, null); if (true) { MethodBuilder getMB = tb.DefineMethod("get_" + fi.Name, MethodAttributes.Public | MethodAttributes.SpecialName, fi.FieldType, null); ILGenerator ilg = getMB.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); ilg.Emit(OpCodes.Ldfld, fi); ilg.Emit(OpCodes.Ret); pb.SetGetMethod(getMB); } if (true) { Type[] tis = new Type[] { fi.FieldType }; MethodBuilder setMB = tb.DefineMethod("set_" + fi.Name, MethodAttributes.Public | MethodAttributes.SpecialName, typeof(void), tis); ILGenerator ilg = setMB.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); ilg.Emit(OpCodes.Ldarg_1); ilg.Emit(OpCodes.Stfld, fi); ilg.Emit(OpCodes.Ret); pb.SetSetMethod(setMB); } } break; case MemberTypes.Property: { PropertyInfo pi = memberInfo as PropertyInfo; ParameterInfo[] paras = pi.GetIndexParameters(); Type[] tis0 = new Type[paras.Length]; for (int i = 0; i < tis0.Length; i++) { tis0[i] = paras[i].ParameterType; } PropertyBuilder pb = tb.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.None, pi.PropertyType, tis0); if (pi.CanRead) { if (paras.Length > 0) { Type[] tis = new Type[paras.Length]; for (int i = 0; i < tis.Length; i++) { tis[i] = paras[i].ParameterType; } MethodBuilder getMB = tb.DefineMethod("get_Item", MethodAttributes.Public | MethodAttributes.SpecialName, pi.PropertyType, tis); ILGenerator ilg = getMB.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); for (int i = 0; i < tis.Length; i++) { switch (i) { case 0: { ilg.Emit(OpCodes.Ldarg_1); } break; case 1: { ilg.Emit(OpCodes.Ldarg_2); } break; case 2: { ilg.Emit(OpCodes.Ldarg_3); } break; default: { ilg.Emit(OpCodes.Ldarg_S, i + 1); } break; } } ilg.EmitCall(OpCodes.Call, pi.GetGetMethod(), tis); ilg.Emit(OpCodes.Ret); pb.SetGetMethod(getMB); } else { MethodBuilder getMB = tb.DefineMethod("get_" + pi.Name, MethodAttributes.Public | MethodAttributes.SpecialName, pi.PropertyType, null); ILGenerator ilg = getMB.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); ilg.EmitCall(OpCodes.Call, pi.GetGetMethod(), null); ilg.Emit(OpCodes.Ret); pb.SetGetMethod(getMB); } } if (pi.CanWrite) { if (paras.Length > 0) { Type[] tis = new Type[paras.Length + 1]; for (int i = 0; i < tis.Length; i++) { if (i < paras.Length) tis[i] = paras[i].ParameterType; else tis[i] = pi.PropertyType; } MethodBuilder setMB = tb.DefineMethod("set_Item", MethodAttributes.Public | MethodAttributes.SpecialName, typeof(void), tis); ILGenerator ilg = setMB.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); for (int i = 0; i < tis.Length; i++) { switch (i) { case 0: { ilg.Emit(OpCodes.Ldarg_1); } break; case 1: { ilg.Emit(OpCodes.Ldarg_2); } break; case 2: { ilg.Emit(OpCodes.Ldarg_3); } break; default: { ilg.Emit(OpCodes.Ldarg_S, i + 1); } break; } } ilg.EmitCall(OpCodes.Call, pi.GetSetMethod(), tis); ilg.Emit(OpCodes.Ret); pb.SetSetMethod(setMB); } else { Type[] tis = new Type[] { pi.PropertyType }; MethodBuilder setMB = tb.DefineMethod("set_" + pi.Name, MethodAttributes.Public | MethodAttributes.SpecialName, typeof(void), tis); ILGenerator ilg = setMB.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); ilg.Emit(OpCodes.Ldarg_1); ilg.EmitCall(OpCodes.Call, pi.GetSetMethod(), tis); ilg.Emit(OpCodes.Ret); pb.SetSetMethod(setMB); } } } break; case MemberTypes.Method: { MethodInfo mi = memberInfo as MethodInfo; MethodBuilder mb = tb.DefineMethod(mi.Name, MethodAttributes.Public); ParameterInfo[] paras = mi.GetParameters(); Type[] tis = new Type[paras.Length]; for (int i = 0; i < tis.Length; i++) { tis[i] = paras[i].ParameterType; } mb.SetReturnType(mi.ReturnType); mb.SetParameters(tis); ILGenerator ilg = mb.GetILGenerator(); ilg.Emit(OpCodes.Ldarg_0); ilg.Emit(OpCodes.Ldfld, innerFieldInfo); for (int i = 0; i < tis.Length; i++) { switch (i) { case 0: { ilg.Emit(OpCodes.Ldarg_1); } break; case 1: { ilg.Emit(OpCodes.Ldarg_2); } break; case 2: { ilg.Emit(OpCodes.Ldarg_3); } break; default: { ilg.Emit(OpCodes.Ldarg_S, i + 1); } break; } } ilg.EmitCall(OpCodes.Call, t.GetMethod(mi.Name), tis); ilg.Emit(OpCodes.Ret); } break; } } Type tt = tb.CreateType(); object oo = Activator.CreateInstance(tt); tt.InvokeMember("innerObj", BindingFlags.SetField, null, oo, new object[] { o }); return oo; } [method: ComVisible(false)] static private object CreateObjectFromFileX(string aname, string tname,params object[] args) { Assembly a = Assembly.LoadFile(aname); Type t = a.GetType(tname); object o=Activator.CreateInstance(t, args); return WrapAsComVisible(t,o); } [method: ComVisible(false)] static private object CreateObjectX(string tname, params object[] args) { Type t = Type.GetType(tname); object o = Activator.CreateInstance(t, args); return WrapAsComVisible(t,o); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile0(string aname,string tname) { return CreateObjectFromFileX(aname,tname); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject0(string tname) { return CreateObjectX(tname); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile1(string aname, string tname, object arg1) { return CreateObjectFromFileX(aname, tname, arg1); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject1(string tname, object arg1) { return CreateObjectX(tname, arg1); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile2(string aname, string tname, object arg1, object arg2) { return CreateObjectFromFileX(aname, tname, arg1, arg2); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject2(string tname, object arg1, object arg2) { return CreateObjectX(tname, arg1, arg2); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile3(string aname, string tname, object arg1, object arg2, object arg3) { return CreateObjectFromFileX(aname, tname, arg1, arg2, arg3); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject3(string tname, object arg1, object arg2, object arg3) { return CreateObjectX(tname, arg1, arg2, arg3); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile4(string aname, string tname, object arg1, object arg2, object arg3, object arg4) { return CreateObjectFromFileX(aname, tname, arg1, arg2, arg3, arg4); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject4(string tname, object arg1, object arg2, object arg3, object arg4) { return CreateObjectX(tname, arg1, arg2, arg3, arg4); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile5(string aname, string tname, object arg1, object arg2, object arg3, object arg4, object arg5) { return CreateObjectFromFileX(aname, tname, arg1, arg2, arg3, arg4, arg5); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject5(string tname, object arg1, object arg2, object arg3, object arg4, object arg5) { return CreateObjectX(tname, arg1, arg2, arg3, arg4, arg5); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile6(string aname, string tname, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6) { return CreateObjectFromFileX(aname, tname, arg1, arg2, arg3, arg4, arg5, arg6); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject6(string tname, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6) { return CreateObjectX(tname, arg1, arg2, arg3, arg4, arg5, arg6); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile7(string aname, string tname, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7) { return CreateObjectFromFileX(aname, tname, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject7(string tname, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7) { return CreateObjectX(tname, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObjectFromFile8(string aname, string tname, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8) { return CreateObjectFromFileX(aname, tname, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshal))] public object CreateObject8(string tname, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8) { return CreateObjectX(tname, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } } 用法很简单,实例化一个External并将它赋给WebBrowser控件实例的ObjectForScripting,然后在DHTML里用window.external.CreateObject系列方法来创建.net里的对象(包括当前EXE中实现的与.net框架库及dll的),可以看出,相当于向DHTML暴露了整个.net framework,呵呵。 April 13 慎对偶然重用我写过一个数据库录入/查询的通用组件,一般数据库的录入都可以用这个组件很轻松的解决,假设有一个项目,数据库涉及用户表,权限表,模块表,业务分类等等,现在我要写一个管理这些数据的程序了,因为有了数据库录入的组件,我现在有两种方案:
1、建立一个窗体,用上数据库录入组件,然后给它传入要管理的数据库表的参数,用一个窗体解决全部数据库的操作;
2、对每一类表单建立一个窗体,每个窗体上放上那个组件,代码除数据库表名外其它基本相同。
初看一下,方案一重用了代码,方案二有重复代码,似乎方案一更合适一些,但是这个结论是从表面简单得出的,我们进一步的考察前面提到的数据,是用户、权限、模块与业务分类,按通常的经验我们会发现,这些数据各自是有其特点的,也许一开始,我们只是需要简单的管理,也就是把它们当成是同样的数据管理(这实质上是一种抽象,没错的),而我们有管理数据的通用组件,在这个抽象级别,我们发现二者特别符合,用一句官话:“问题域与解空间同构”,这也是我们第一感觉方案一比方案二合适的理由。
但是问题恰恰出在这里,抽象其实经常骗人,因为抽象说白了是求同存异,在追求抽象的道路上我们很容易忘记现实,也就是我们实际要解决的问题,换句话说,我们有时会忘记现实的抽象级别,这话其实是说得好听了一点,真实的情况往往是我们没有了解现实的抽象级别。
回到前面的问题,因为一开始我们没有理解这些数据在其业务环境里的真实抽象级别,我们理想化的用一般数据这个更抽象一级的概念来表述了,所以我们会轻信方案1(而且因为多数人想偷懒,方案二要写更多的代码,虽然是复制[而且复制代码有太多的软件工程书籍说这样不好]),然而随着开发的深入,我们会发现美好的感觉开始变味了(有时开发过程中可能自始至终都意识不到,反而会陶醉在重用营造的良好感觉中,直到最后用户当头棒喝),我们重用的那一个窗体无法应对针对每类数据的特定的管理要求,于是我们开始对传入的参数switch case,好了,已经糟糕透了,先前优美的代码现在变得难看,而且开始出现BUG了。
反观方案二,一开始我们复制了代码,费了点劲,而且感觉不是那么良好,但随着开发的深入,我们发现我们每次都只修改与特定数据相关的那一块,没有因为哪个地方的改动影响别的地方,所以BUG比较少,改起来也很容易。
现在找找原因,我们用结构化时代最著名的关于耦合/内聚的概念来分析一下,我们知道,内聚有一种情况很烦人,那就是偶然内聚,我们的方案1在某种程度与此相似,因为我们是刚开始一个项目,对项目缺少深入了解,而偶然(这种偶然往往带有随意性,比如为了偷懒,呵呵)做出的重用选择,所以是偶然重用。
再从面向对象的角度来看,分层抽象、差异式编程,越多的对象,越小的对象,越优秀的设计(其实是因为概念精炼,对问题的更深入的理解,抓住了主要矛盾,发现了固有的差异性),方案1想一个对象解决问题,方案2感觉到某种差异存在于问题之中(有两种情况会这样,一种是初学,完全没有重用的概念,一种是比较有经验,知道这里变故多,呵呵,否定之否定的螺旋式发展规律出现了),所以选择了更多的对象(后来会表现得更小,开始可不小)。
最后想起在以前公司的最后一个感悟:实现软件重用至少有两个级别,一个是技术级别的,一个是业务级别的,技术级别的抽象程度往往比业务级别高(就像前面的数据与针对业务的数据一样);如果我们技术很差,但是对业务有成熟的理解,换句话,我们的代码很差,但是因为我们的系统能解决业务上的实际问题,并且因为我们对业务的理解有其通用性,这样,系统仍然可以重用在相似业务的环境,而另一方面,如果我们技术很强,但是对业务的理解很差,那样我们可以打造很不错的更高级的重用组件,但是很可惜,我们通常无法解决用户的问题。当然,更好的情形是,在技术级别,我们有优秀的重用组件,因此,一开始我们不用费劲的重写每一块(方案2开始就只需要简单的拷贝一点点代码),而因为我们有对业务的良好理解,我们知道哪些东西是不能合并的,必须分隔开来,从而制止了走向纯粹的抽象。这么说来,技术让系统很美丽,业务让系统很实际。
April 10 从EXE/DLL文件中基于VTBL提取C++类及继承关系图今天完成了IdaHTML的一个比较大的脚本了,接下来该好好干活了,过一阵再做这些不务正业的事,呵呵。
<html xmlns:v>
<head> <title>分析.rdata段的VTBL并据此构造类继承图</title> <STYLE> v\:*{behavior:url(#default#VML);} </STYLE> </head> <body scroll="no" leftmargin="0" topmargin="0" rightmargin="0" bottommargin="0"> <table style="width:100%;height:100%"> <tr> <td colspan="2"> <div style="width:100%;height:100%;overflow:scroll;" onmouseup="zoomGraph()"> <v:group id="flowGraph" style="position:relative;width:100%;height:100%" coordsize="500,300"> </v:group> </div> </td> </tr> <tr style="height:80px"> <td width="80px"> <select id="caList" style="width:100%;height:100%;" size="2" ondblclick="drawGraph()"> </select> </td> <td> <textarea id="scpArea" style="width:100%;height:100%" WRAP="off"> </textarea> </td> </tr> <tr style="height:16px"> <td colspan="2"> <input type="button" value="Run" onclick="eval(scpArea.value)"> <input type="button" value="Analysis" onclick="startAnalysis()"> <input type="button" value="LoadGraph" onclick="loadTxtGraph()"> </td> </tr> </table> <script src="res://IdaHTML.plw/const.js"></script> <script> function ClassInfo() { this.level=0; this.index=0; this.funcs=new Array(); this.subClasses=new Object(); this.addFunc=function(f) { this.funcs.push(f); } this.addSubClass=function(c) { this.subClasses[c]=true; } this.delSubClass=function(c) { delete this.subClasses[c]; } this.after=new Object();//被依赖类 this.addAfter=function(c) { this.after[c]=true; } this.haveAfter=function(c) { if(this.after[c]) return true; return false; } } function FuncInfo() { this.classes=new Array(); this.refCount=0; this.callFuncs=new Array(); this.addClass=function(c) { this.classes.push(c); this.refCount++; } this.addCallFunc=function(f) { this.callFuncs.push(f); } } var classes=new Object(); var funcs=new Object(); //继承图相关的数据 var maxLevel=0,maxIndex=0;//当前图的最大层及最大索引,也就是图在二维平面上的高与宽的度量 var layerIndex=null;//图上每一层的当前最大索引,一个数组 var dx=0,dy=0; var txtGraphs=new Object(); var curW=500,curH=300; </script> <script> function window.onload() { scpArea.value=""; } function getRoundRect(x,y) { return document.createElement("<v:roundrect style='position:relative;z-index:1;LEFT:"+(x-32)+";TOP:"+(y-9)+";width:64;height:18;' fillcolor='yellow'/>"); } function getLine(x1,y1,x2,y2,rx,ry) { if(Math.abs((y2-y1)/(x2-x1))>ry/rx) { x1-=(x2-x1)*ry/(y2-y1); y1-=ry; x2+=(x2-x1)*ry/(y2-y1); y2+=ry; } else if(x1<x2) { x1+=rx; y1+=(y2-y1)*rx/(x2-x1); x2-=rx; y2-=(y2-y1)*rx/(x2-x1); } else { x1-=rx; y1-=(y2-y1)*rx/(x2-x1); x2+=rx; y2+=(y2-y1)*rx/(x2-x1); } x1=Math.floor(x1); y1=Math.floor(y1); x2=Math.floor(x2); y2=Math.floor(y2); return document.createElement("<v:line style='position:relative;z-index:1;' from='"+x1+","+y1+"' to='"+x2+","+y2+"' />"); } function getStroke() { return document.createElement("<v:stroke dashstyle='solid' endarrow='None' opacity='0.5' />"); } function getText(x,y,text) { var r=document.createElement("<v:roundrect style='position:relative;z-index:1;left:"+(x-30)+";top:"+(y-7)+";width:60;height:14;font-size:14px' stroked='f' />"); var f=document.createElement("<v:fill type='frame' opacity='0.5' />"); r.appendChild(f); var t=document.createElement("<v:textbox inset='1,1,1,1' />"); t.innerText=text; r.appendChild(t); return r; } function getX(p) { var lvl=classes[p].level; var maxIx=layerIndex[lvl]; var m=Math.floor(maxIx/2); return Math.floor((maxIndex+1)/2*dx+(classes[p].index-m)*dx); } function getY(p) { return Math.floor(dy*classes[p].level)+10; } function zoomGraph() { if(!event.altKey)return; if(event.button==1) { curW/=2; curH/=2; } else if(event.button==2) { curW*=2; curH*=2; } else { return; } flowGraph.coordSize=""+curW+","+curH; } function window.drawGraph() { if(caList.selectedIndex<0) return; var firstClass=window.external.HexToInt(caList.options[caList.selectedIndex].value); var w=500; var h=300; //初始化流程绘制相关的数据 maxLevel=0; maxIndex=0; layerIndex=new Array(); flowGraph.innerHTML=""; scpArea.value=txtGraphs[firstClass]; //广度优先遍历确定节点层次与层序号 var line=new Array();//当前层 line.push(firstClass); layerIndex.push(0);//第一层就一个节点,即首过程结点 maxLevel=0; for(;;) { var tline=new Array();//下一层 for(var i=0;i<line.length;i++) { var a=line[i]; if(classes[a]) { var ns=classes[a].subClasses; var pix=classes[a].index; var ct=0; for(var j in ns)ct++; var m=Math.floor(ct/2); var ix=0; for(var j in ns) { var tx=pix+ix-m; if(maxIndex<tx) maxIndex=tx; ix++; tline.push(j); var cx=tline.length-1; classes[j].index=(cx>tx ? cx : tx); classes[j].level=maxLevel+1; } } } if(tline.length==0) { break; } else { if(maxIndex<tline.length-1) maxIndex=tline.length-1; layerIndex.push(maxIndex);//压入新发现的一层的最大层索引 maxLevel++; } line=tline; } // dx=Math.floor(w/(maxIndex+2)); dy=Math.floor(h/(maxLevel+2)); //再次广度优先遍历绘制流程图 line=new Array();//当前层 line.push(firstClass); //绘制初始节点 flowGraph.appendChild(getRoundRect(getX(firstClass),getY(firstClass))); flowGraph.appendChild(getText(getX(firstClass),getY(firstClass),window.external.IntToHex(firstClass))); for(;;) { var tline=new Array();//下一层 for(var i=0;i<line.length;i++) { var a=line[i]; if(classes[a]) { var ns=classes[a].subClasses; for(var j in ns) { tline.push(j); //绘制节点 flowGraph.appendChild(getRoundRect(getX(j),getY(j))); flowGraph.appendChild(getText(getX(j),getY(j),window.external.IntToHex(j))); //绘制连线 var l=getLine(getX(j),getY(j),getX(a),getY(a),20,9); l.appendChild(getStroke()); flowGraph.appendChild(l); } } } if(tline.length==0) { break; } line=tline; } } function compare(a,b) { var al=classes[a].funcs.length; var bl=classes[b].funcs.length; if(al<bl) return -1; else if(al==bl) { if(classes[a].haveAfter(b)) return 1; else if(classes[b].haveAfter(a)) return -1; else return 0; } else return 1; } function decideSubClass(carray,ix) { var len=carray.length; if(ix>=len-1) return; var c=carray[0]; var c1=carray[ix]; for(var ii=ix+1;ii<len;ii++) { if(carray[ii]==0) continue;//空项目表明已经被添加为子类了 var c2=carray[ii]; if(classes[c1].haveAfter(c2)) { classes[c].delSubClass(c2); classes[c1].addSubClass(c2); decideSubClass(carray,ii); carray[ii]=0; } } } function iterateGraph(fp,c0,c,lvl) { c=parseInt(""+c,10); for(var i=0;i<lvl;i++) { app.IdcApi.writestr(fp,"\t"); txtGraphs[c0]+="\t"; } var info=app.IdcApi.form("%8.8X\r\n",c); app.IdcApi.writestr(fp,info); txtGraphs[c0]+=info; for(var sc in classes[c].subClasses) { iterateGraph(fp,c0,sc,lvl+1); } } function startAnalysis() { var path=app.IdcApi.AskFile(1,"classes.txt","请指定一个文件用于存储类分析的结果:"); if(!path) return; var a=app.IdcApi.FirstSeg(); var s=app.IdcApi.SegName(a); while(s!=".rdata") { a=app.IdcApi.NextSeg(a); s=app.IdcApi.SegName(a); } var e=app.IdcApi.NextSeg(a); app.IdcApi.Message("找到.rdata段:%8.8X-%8.8X,现在开始分析可能的VTBL...\r\n",a,e); var fp=app.IdcApi.fopen(path,"w"); app.IdcApi.writestr(fp,".classmember--------------------------------------------------\r\n"); var curClass=null; for(var i=a;i<e;) { s=app.IdcApi.GetDisasm(i); if(app.IdcApi.strstr(s,"dd offset")>=0) { //取出VTBL中的一项,查看是否函数 var v=app.IdcApi.Dword(i); var f=app.IdcApi.GetFlags(v); //查看是否有名称,有名称的表明有被引用,可能是VTBL的开始 var label=app.IdcApi.Name(i); if(typeof(label)=="string" && label.length>0)//如果是RTTI或MFC动态类,则第一项可能不是函数 { curClass=i; classes[curClass]=new ClassInfo(); app.IdcApi.writestr(fp,app.IdcApi.form("class:%8.8X\r\n",curClass)); classes[curClass].addFunc(v); if(!funcs[v]) { funcs[v]=new FuncInfo(); } funcs[v].addClass(curClass); app.IdcApi.writestr(fp,app.IdcApi.form("=>member:%8.8X\r\n",v)); } else if((f & MS_CLS)==FF_CODE && curClass) { classes[curClass].addFunc(v); if(!funcs[v]) { funcs[v]=new FuncInfo(); } funcs[v].addClass(curClass); app.IdcApi.writestr(fp,app.IdcApi.form("=>member:%8.8X\r\n",v)); } else//不是VTBL成员 { curClass=null; } } i=app.IdcApi.ItemEnd(i); } //删除只有一个虚函数的类(通常这可能不是类) var nullClasses=new Array(); for(var c in classes) { if(classes[c].funcs.length<=1) nullClasses.push(c); } for(var i=0;i<nullClasses.length;i++) { delete classes[nullClasses[i]]; } app.IdcApi.writestr(fp,".classarray---------------------------------------------------\r\n"); app.IdcApi.Message("VTBL分析完毕,现在分析可能的类簇...\r\n"); //分析类簇 var classesArray=new Array(); var accessed=new Object(); for(var c in classes) { c=parseInt(""+c,10); if(!accessed[c])//发现一个新的类簇,并构造出此类簇 { accessed[c]=true; app.IdcApi.writestr(fp,app.IdcApi.form("class Array:\r\n")); app.IdcApi.writestr(fp,app.IdcApi.form("=>%8.8X\t(起始类)\r\n",c)); var curClasses=new Array(); curClasses.push(c); classesArray.push(curClasses); var queue=new Array(); queue.push(c); while(queue.length>0) { var cc=queue[0]; for(var i=0;i<classes[cc].funcs.length;i++) { var f=classes[cc].funcs[i]; for(var ii=0;ii<funcs[f].classes.length;ii++) { var fc=funcs[f].classes[ii]; if(!accessed[fc] && classes[fc]) { app.IdcApi.writestr(fp,app.IdcApi.form("=>%8.8X\t(共用:%8.8X[%8.8X])\r\n",fc,cc,f)); accessed[fc]=true; curClasses.push(fc); queue.push(fc); } } } queue.shift(); } } } app.IdcApi.writestr(fp,".callvfunc----------------------------------------------------\r\n"); app.IdcApi.Message("类簇分析完毕,现在开始分析虚函数调用关系...\r\n"); var fcount=0; for(var f in funcs) { /* if(fcount>=100) { break; } fcount++; */ f=parseInt(""+f,10); var st=f; var ed=app.IdcApi.FindFuncEnd(f); for(var i=st;i<ed;) { for(var x=app.IdcApi.Rfirst(i);x!=BADADDR;x=app.IdcApi.Rnext(i,x)) { var xt=app.IdcApi.XrefType(); if(xt == fl_CN && funcs[x]) { funcs[f].addCallFunc(x); app.IdcApi.writestr(fp,app.IdcApi.form("%8.8X=>%8.8X\r\n",f,x)); } } i=app.IdcApi.ItemEnd(i); } } app.IdcApi.Message("函数调用关系分析完毕,现在据此构造类的依赖关系...\r\n"); for(var f in funcs) { for(var i=0;i<funcs[f].callFuncs.length;i++) { var cf=funcs[f].callFuncs[i]; for(var ii=0;ii<funcs[cf].classes.length;ii++) { var cc=funcs[cf].classes[ii]; for(var iii=0;iii<funcs[f].classes.length;iii++) { var c=funcs[f].classes[iii]; classes[cc].addAfter(c); } } } } app.IdcApi.writestr(fp,".classorder---------------------------------------------------\r\n"); app.IdcApi.Message("类簇分析完毕,挑选VTBL尺寸最小的类作基类...\r\n"); //对每个类簇的类按VTBL的大小排序 for(var i=0;i<classesArray.length;i++) { var ca=classesArray[i]; ca=ca.sort(compare); classesArray[i]=ca; app.IdcApi.writestr(fp,"class inherit order:\r\n"); for(var ii=0;ii<ca.length;ii++) { app.IdcApi.writestr(fp,app.IdcApi.form("=>%8.8X size:%d\r\n",ca[ii],classes[ca[ii]].funcs.length)); } } //每簇类以最小尺寸的类作基类 for(var i=0;i<classesArray.length;i++) { var ca=classesArray[i]; for(var ii=1;ii<ca.length;ii++) { classes[ca[0]].addSubClass(ca[ii]); } } app.IdcApi.Message("类簇排序完毕,依据依赖细化继承关系...\r\n"); for(var i=0;i<classesArray.length;i++) { var ca=classesArray[i]; decideSubClass(ca,1); } app.IdcApi.writestr(fp,".graph--------------------------------------------------------\r\n"); app.IdcApi.Message("分析完毕,现在输出继承关系描述...\r\n"); caList.size=classesArray.length; for(var i=0;i<classesArray.length;i++) { var ca=classesArray[i]; var c=ca[0]; if(ca.length>=2) { var opt=document.createElement("option"); opt.text=window.external.IntToHex(c); opt.value=window.external.IntToHex(c); caList.add(opt); } txtGraphs[c]=""; iterateGraph(fp,c,c,0); } app.IdcApi.Message("全部分析完成!\r\n"); app.IdcApi.fclose(fp); } function loadClass(c,addr,lines,ix,lvl) { for(var ii=ix;ii<lines.length;) { var line=lines[ii]; for(var i=0;i<line.length;i++) { if(line.charAt(i)!="\t") break; } if(i<=lvl) return ii; else { var ca=window.external.HexToInt(line.substr(i)); classes[ca]=new ClassInfo(); classes[addr].addSubClass(ca); txtGraphs[c]+=line; ii=loadClass(c,ca,lines,ii+1,i); } } } function loadTxtGraph() { //目前只装入绘制类继承图相关的信息 var path=app.IdcApi.AskFile(0,"classes.txt","请指定存储了类分析的结果的文本文件"); if(!path) return; classes=new Object();//清空类信息 txtGraphs=new Object(); while(caList.options.length>0) caList.options.remove(0); app.IdcApi.Message("装入开始...\r\n"); var content=window.external.Setup.ReadTxtFile(path);//将文本文件内容读到字符串中 var lines=content.split("\r\n"); for(var i=lines.length-1;i>=0;i--) { if(lines[i].indexOf(".graph")==0) break; } for(var j=i+1;j<lines.length;) { var addr=window.external.HexToInt(lines[j]); classes[addr]=new ClassInfo(); txtGraphs[addr]=lines[j]; var nj=loadClass(addr,addr,lines,j+1,0); if(nj>j+1) { var opt=document.createElement("option"); opt.text=lines[j]; opt.value=lines[j]; caList.add(opt); } j=nj; } app.IdcApi.Message("装入完成!\r\n"); } </script> </body> </html> 主要分析思路: 1、在.rdata段中查找可能的VTBL,并构造相应的类与成员函数的对应关系; 2、VTBL有公共函数的认为是同一簇的类(不一定正确); 3、分析虚函数间的调用关系,据此构造类的依赖; 4、对2中得到的同一簇的类按VTBL大小与依赖关系排序; 5、根据4中顺序与依赖关系构造继承图(只是参考,不完全正确); 6、用VML绘制类继承图(双击分析结果列表中的某行即绘制)。 April 07 如何让程序运行在多个版本OS并依版本使用不同的API想想自从使用vc7.1以后越来越大手大脚了,不单是写出的程序通常不能用vc6编译(二者对C++标准的支持相差太大了!),而且越来越习惯使用新OS的API了,这次写调试程序的插件,因为打算拿出来让大家用,才意识到了这个问题(汗).呵呵,不多说了,直接拿出代码来:
//自win98以来新增加的API的适应性处理(按默认win98 SDK编译需要),部分是旧的SDK不支持,有一些则是旧系统不支持
//,这里提供首次调用动态装入功能. #define OS_NEWAPI_DEFVAL 0xffffffff class OS_NewApi { typedef HWND (WINAPI*GetAncestorPtr)(HWND,UINT); typedef UINT (WINAPI *GetWindowModuleFileNameAPtr)(HWND,LPSTR,UINT); typedef HANDLE (WINAPI*OpenThreadPtr)(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwThreadId); typedef DWORD (WINAPI*GetProcessIdPtr)(HANDLE Process); public: static inline HWND GetAncestor(HWND hwnd,UINT flag) { static GetAncestorPtr fptr=(GetAncestorPtr)OS_NEWAPI_DEFVAL; if((DWORD)fptr==OS_NEWAPI_DEFVAL) { fptr=NULL; HMODULE hModule=::GetModuleHandle("User32.dll"); if(!hModule) return NULL; fptr=(GetAncestorPtr)::GetProcAddress(hModule,"GetAncestor"); } if(!fptr) return NULL; return (*fptr)(hwnd,flag); } static inline UINT GetWindowModuleFileName(HWND hwnd,LPTSTR pFileName,UINT num) { static GetWindowModuleFileNameAPtr fptr=(GetWindowModuleFileNameAPtr)OS_NEWAPI_DEFVAL; if((DWORD)fptr==OS_NEWAPI_DEFVAL) { fptr=NULL; HMODULE hModule=::GetModuleHandle("User32.dll"); if(!hModule) return NULL; fptr=(GetWindowModuleFileNameAPtr)::GetProcAddress(hModule,"GetWindowModuleFileNameA"); } if(!fptr) return NULL; return (*fptr)(hwnd,pFileName,num); } static inline HANDLE OpenThread(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwThreadId) { static OpenThreadPtr fptr=(OpenThreadPtr)OS_NEWAPI_DEFVAL; if((DWORD)fptr==OS_NEWAPI_DEFVAL) { fptr=NULL; HMODULE hModule=::GetModuleHandle("Kernel32.dll"); if(!hModule) return NULL; fptr=(OpenThreadPtr)::GetProcAddress(hModule,"OpenThread"); } if(!fptr) return NULL; return (*fptr)(dwDesiredAccess,bInheritHandle,dwThreadId); } static inline DWORD GetProcessId(HANDLE process) { static GetProcessIdPtr fptr=(GetProcessIdPtr)OS_NEWAPI_DEFVAL; if((DWORD)fptr==OS_NEWAPI_DEFVAL) { fptr=NULL; HMODULE hModule=::GetModuleHandle("Kernel32.dll"); if(!hModule) return NULL; fptr=(GetProcessIdPtr)::GetProcAddress(hModule,"GetProcessId"); } if(!fptr) return NULL; return (*fptr)(process); } }; 解决思路小结:
1、在一个类中提供静态方法来代理实际的API;
2、每个API都是在首次使用时定位,之后则使用先前的定位值,(我这里只用到user32.dll与kernel32.dll这两个基本上所有程序都会用的动态库,所以没有做首次使用时动态装入,而是直接用了GetModuleHandle,如果要动态装入,则需要提供一个方法供程序退出时释放那些库,另外,我只用到极少的API,所以是在每个API调用时读库模块句柄,如果是很多的话,显然需要有一个统一的管理类较好);
3、对于无法定位的API与模块,应返回相应API失败时的返回值(暂时没想到别的好办法,抛出异常对API使用方显示太烦了);
4、如果乐意,用这种方法可以调用程序所需的全部API,这样程序就不需要在IMPORT表中显示那么多的API名称/序号了,再加上对API名称的加密,这可以是一种程序保护的技术。
April 04 idapro 4.9插件IdaHTMLIDC脚本语言用着不太舒服,我研究了一下idapro 4.9的SDK,偶然发现居然可以很方便的在C++中调IDC语言的内部函数,所以就写了一个用DHTML作脚本的插件.核心代码如下:(就是实现一个IDispatch接口,然后在GetIDsOfNames与Invoke方法的实现中调用IDC内部函数,想了一下许多脚本语言的接口都可以用这样的方法写成IDispatch接口,从而可以在javascript中像自己内建的对象一样访问[语法上与内建对象访问完全一样])
class IdaApi : public IDispatch { typedef IdaApi ComObj; typedef std::map<CString,DISPID> NAME_IDS; IdaApi():m_cRef(0) {} public: static inline ComObj* CreateDispatch() { ComObj* p=new ComObj(); p->AddRef(); return p; } STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{ if (NULL == ppv) return E_POINTER; *ppv = NULL; HRESULT hr = S_OK; if ((IID_IUnknown == riid) || (IID_IDispatch == riid)) *ppv = static_cast<IDispatch*>(this); if(*ppv!=NULL)
{ reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return hr; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef(void) { return ++m_cRef; } STDMETHODIMP_(ULONG) Release(void) { if (0 != --m_cRef) return m_cRef; delete this; return 0; } STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) { if (NULL == pctinfo) return E_POINTER; *pctinfo = 0; return S_OK; } STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { if (NULL == ppTInfo) return E_POINTER; *ppTInfo = NULL; return E_FAIL; } STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { if (IID_NULL != riid) return DISP_E_UNKNOWNINTERFACE; if (NULL == rgDispId) return E_POINTER; if (NULL == rgszNames) return E_POINTER; if (cNames != 1) return E_INVALIDARG; CString name(*rgszNames); NAME_IDS::iterator it=name_ids.find(name); if(it!=name_ids.end()) { *rgDispId=it->second; return S_OK; } else { *rgDispId = 0; return DISP_E_MEMBERNOTFOUND; } } STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { if (IID_NULL != riid) return DISP_E_UNKNOWNINTERFACE; if (NULL == pDispParams) return E_POINTER; extfun_t* pfun=(extfun_t*)dispIdMember; if(pfun) { //确定函数参数个数与有否可变参数 bool haveWildArg=false; for(int ai=0;pfun->args[ai];ai++) { if(pfun->args[ai]==VT_WILD) { haveWildArg=true; break; } } //初始化返回值变量 value_t res; res.vtype=VT_LONG; if(haveWildArg) res.num=pDispParams->cArgs; else res.num=0; //分配参数空间并初始化各参数 CString* strargs=new CString[pDispParams->cArgs]; value_t* args=new value_t[pDispParams->cArgs]; for(int i=pDispParams->cArgs-1;i>=0;i--) { VARIANT& var=pDispParams->rgvarg[i]; int ix=pDispParams->cArgs-i-1; if(ix<ai)//对已知类型的参数按IDC内部函数定义类型转换赋值 { switch(pfun->args[ix]) { case VT_STR: { if(var.vt==VT_BSTR) { strargs[ix]=CString(var.bstrVal); } else { CComVariant v=var; v.ChangeType(VT_BSTR); strargs[ix]=CString(v.bstrVal); } args[ix].vtype=VT_STR; args[ix].str=(LPSTR)(LPCSTR)strargs[ix]; } break; case VT_FLOAT: { //因为不知道IDC是如何表示浮点数的,这里将浮点数先转为字符串再调IDC API转为浮点数 CComVariant v=var; v.ChangeType(VT_BSTR); strargs[ix]=CString(v.bstrVal); args[ix].vtype=VT_STR; args[ix].str=(LPSTR)(LPCSTR)strargs[ix]; VarFloat(&args[ix]); } break; default: { CComVariant v=var; v.ChangeType(VT_I4); args[ix].vtype=VT_LONG; args[ix].num=(sval_t)v.lVal; } } } else if(haveWildArg)//可变参数按传入类型赋值 { switch(var.vt) { case VT_BSTR: { strargs[ix]=CString(var.bstrVal); args[ix].vtype=VT_STR; args[ix].str=(LPSTR)(LPCSTR)strargs[ix]; } break; case VT_R4: case VT_R8: { //因为不知道IDC是如何表示浮点数的,这里将浮点数先转为字符串再调IDC API转为浮点数 CComVariant v=var; v.ChangeType(VT_BSTR); strargs[ix]=CString(v.bstrVal); args[ix].vtype=VT_STR; args[ix].str=(LPSTR)(LPCSTR)strargs[ix]; VarFloat(&args[ix]); } break; default: { CComVariant v=var; v.ChangeType(VT_I4); args[ix].vtype=VT_LONG; args[ix].num=(sval_t)v.lVal; } } } else//多余的参数忽略 { break; } } //调用IDC内部函数 error_t r=eOS; try { r=(*(pfun->fp))(args,&res); } catch(...) {} //释放参数空间 delete[] args; delete[] strargs; //处理函数返回值 switch(res.vtype) { case VT_STR: { CComVariant(res.str).Detach(pVarResult); VarFree(&res);//释放字符串 } break; case VT_FLOAT: { VarString(&res);//先转为字符串 double v=::atof(res.str);//再转为double CComVariant(v).Detach(pVarResult); VarFree(&res);//释放前面转换为字符串时idapro为字符串分配的空间 } break; default: { VarLong(&res); CComVariant((int)res.num).Detach(pVarResult); } } if(r==eOk) { return S_OK; } else { return E_INVALIDARG; } } return DISP_E_MEMBERNOTFOUND; } public: void ResetBuildIn(void) { name_ids.clear(); for(int fi=0;fi<IDCFuncs.qnty;fi++) { name_ids[CString(IDCFuncs.f[fi].name)]=(DISPID)&IDCFuncs.f[fi]; } } private: unsigned int m_cRef; NAME_IDS name_ids; }; 写完插件后我重写了之前用IDC提取VTBL的脚本,呵呵,几乎与原来IDC脚本一模一样(都是c系语言的缘故了,呵呵)
var s,a,e,fp,i,name,lastIsNull;
a=app.FirstSeg(); s=app.SegName(a); while(s!=".rdata") { a=app.NextSeg(a); s=app.SegName(a); } e=app.NextSeg(a); app.Message("找到.rdata段:%8.8X-%8.8X,现在开始将其中可能的VTBL写入文件...\r\n",a,e); lastIsNull=1; fp=app.fopen("c:\\vtbl.txt","w"); for(i=a;i<e;) { s=app.GetDisasm(i); if(app.strstr(s,"dd offset")>=0) { name=app.Name(i); if(name!="") { app.writestr(fp,app.form("@ %s\r\n",s)); } else { app.writestr(fp,app.form(" %s\r\n",s)); } lastIsNull=0; } else if(!lastIsNull) { app.writestr(fp,"\r\n"); lastIsNull=1; } i=app.ItemEnd(i);/*app.FindData(i,1); */ } app.fclose(fp); app.Message("文件构造完毕!\r\n"); 插件放在偶的临时空间上了,就是这里 April 03 在idapro 4.9中生成指定过程及被该过程调用的过程代码的脚本#include "idc.idc"
static ElementExist(arrayid,size,val)
{ auto i,v; for(i=GetFirstIndex(AR_LONG,arrayid);i>=0;i=GetNextIndex(AR_LONG,arrayid,i)) { v=GetArrayElement(AR_LONG,arrayid,i); if(v==val) return 1; } /* for(i=0;i<size;i++) { v=GetArrayElement(AR_LONG,arrayid,i); if(v==val) return 1; } */ return 0; } static GenFuncIns(st,arrayid,size) { auto start,end,i,ins,x,xt; start=st; end=FindFuncEnd(start); for(i=start;i<end;) { ins=GetDisasm(i); for(x=Rfirst(i);x!=BADADDR;x=Rnext(i,x)) { xt=XrefType(); if(xt == fl_CN && !ElementExist(arrayid,size,x)) { SetArrayLong(arrayid,size,x); size++; } } i=ItemEnd(i);/*FindCode(i,1);*/ Message(form("%s\r\n",ins)); } return size; } static main() { auto arrayid,size,pos,st; st=ScreenEA(); /*st=ChooseFunction("请指定一个函数地址:");*/ if(st==BADADDR) { Warning("您需要选中一个函数起始地址!"); return; } arrayid=CreateArray("gen_func_ins"); if(arrayid<0) { arrayid=GetArrayId("gen_func_ins"); } pos=0; SetArrayLong(arrayid,pos,st); size=1; for(pos=0;pos<size;pos++) { st=GetArrayElement(AR_LONG,arrayid,pos); Message(form("proc:%8.8x\r\n",st)); size=GenFuncIns(st,arrayid,size); Message("\r\n"); } DeleteArray(arrayid); } 生成的代码显示idapro的消息区,所以执行前先应将它清空,执行后拷出即可。 在idapro 4.9中提取可执行文件类vtbl的脚本auto s,a,e,fp,i,name,lastIsNull;
a=FirstSeg(); s=SegName(a); while(s!=".rdata") { a=NextSeg(a); s=SegName(a); } e=NextSeg(a); Message("找到.rdata段:%8.8X-%8.8X,现在开始将其中可能的VTBL写入文件...\r\n",a,e); lastIsNull=1; fp=fopen("c:\\vtbl.txt","w"); for(i=a;i<e;) { s=GetDisasm(i); if(strstr(s,"dd offset")>=0) { name=Name(i); if(name!="") { writestr(fp,form("@ %s\r\n",s)); } else { writestr(fp,form(" %s\r\n",s)); } lastIsNull=0; } else if(!lastIsNull) { writestr(fp,"\r\n"); lastIsNull=1; } i=FindData(i,1); } fclose(fp); Message("文件构造完毕!\r\n"); 提出出来的只是可能的VTBL,每个VTBL的开始以@标注,接下来需要做的就是比较各VTBL的尺寸及其中函数的被引用次数以确定类的继承关系了.大致上,有公共函数的VTBL的类可能是一个簇的,VTBL尺寸越小的越可能是基类,同样尺寸的则函数被引用次数越多的越可能是基类.(IDC脚本语言的功能有点弱,不方便做上述分析,只能转用别的脚本了)
March 30 OllyHTML--Ollydbg调试程序的插件呵呵,今天将近来抽空写的一个小程序放在邻居给我的免费的空间上了,是一个插件,目的是为Ollydbg调试程序提供DHTML脚本引擎.我把我写这个插件的目的作为例子放在帮助上面了,例子给出的功能有两个:
1、获取与特定UI操作相关的过程入口地址,比如你想知道你点了一个按钮后会多执行哪些过程(通常应该就是执行对应该按钮功能的过程了,呵呵)。
2、绘制指定过程入口列表的动态时序流程,这个功能实际是接着1的扩展,1中可能得到好多个过程,将它们用这个功能绘制一个流程,通常流程入口过程应该就是关键过程了(这个当然不一定的,呵呵)。
我的临时空间(就在邻居家的服务器上,可以从这里下载这个插件):http://dreaman.haolinju.net/ March 28 ms javascript里对象调用的开销近来为Ollydbg写了一个使用DHTML作脚本的插件,在使用时发现对象调用特别的慢,于是作了个小测试:
//使用对象属性来保存数组
function test1() { window.p1=new Array(); window.p2=new Array(); for(var i=0;i<1000;i++) { window.p1.push(i); window.p2.push(i+1000);//有意让第二个数组与第一个数组无重叠,这样后面会有1000*1000次循环 } alert("1"); var d1=new Date(); for(var i=0;i<window.p1.length;i++) { for(var j=0;j<window.p2.length;j++) { if(window.p1[i]==window.p2[j]) break; } } var d2=new Date(); alert("1="+(d2-d1)); } //直接使用数组 function test2() { var p1=new Array(); var p2=new Array(); for(var i=0;i<1000;i++) { p1.push(i); p2.push(i+1000); } alert("2"); var d1=new Date(); for(var i=0;i<p1.length;i++) { for(var j=0;j<p2.length;j++) { if(p1[i]==p2[j]) break; } } var d2=new Date(); alert("2="+(d2-d1)); } //使用数组与一个COM集合对象,COM对象成员访问可能有一定开销 function test3() { var p1=window.external.CreateSet();//这是我写的一个扩充方法,用于创建一个STL集合对象 var p2=new Array(); //分配百万个空间,这个循环很费时,看来内存分配确实对性能影响奇大 for(var i=0;i<1000000;i++) { p1.Insert(i); p2.push(i+1000000); } alert("3"); var d1=new Date(); for(var i=0;i<p2.length;i++) { if(p1.Exist(p2[i])) break; } var d2=new Date(); alert("3="+(d2-d1)); } //使用数组与JavaScript语言内建对象,内建对象可认为是Hash表,而且可用[]直接访问,没有对象成员访问的开销 function test4() { var p1=new Object(); var p2=new Array(); //分配百万个空间,这个循环很费时,看来内存分配确实对性能影响奇大 for(var i=0;i<1000000;i++) { p1[i]=true; p2.push(i+1000000); } alert("4"); var d1=new Date(); for(var i=0;i<p2.length;i++) { if(p1[p2[i]]) break; } var d2=new Date(); alert("4="+(d2-d1)); } //使用两个COM对象代替数组,以比较内建对象访问与COM对象访问及直接数组访问的开销 function test5() { var p1=window.external.CreateVector();//这是我写的扩充方法,用于创建一个STL向量对象 var p2=window.external.CreateVector(); for(var i=0;i<1000;i++) { p1.Push(i); p2.Push(i+1000); } alert("5"); var d1=new Date(); for(var i=0;i<p1.Length;i++) { for(var j=0;j<p2.Length;j++) { if(p1.Get(i)==p2.Get(j)) break; } } var d2=new Date(); alert("5="+(d2-d1)); } test1(); test2(); test3(); test4(); test5(); 在我的电脑上,test1显示的时间:48570ms,test2显示的时间:2243ms,test3显示的时间:11859ms,test4显示的时间是3976ms,test5显示的时间是15422ms.
呵呵,可以很明显的看到对象成员访问的开销.
分析:(js内建对象是IDispatchEx接口,COM对象是IDispatch接口,数组[]操作不太确定如何实现的)
1.test1与test2的比较可以看出,IDispatchEx接口成员访问开销比较大,估计没有进行循环前成员名到DISPID的转换之后用DISPID调用的优化;
2.test3与test4的比较可以看出,IDispatch接口成员访问的开销约为数组访问的3倍,这里不好断定IDispatch接口有否进行BSTR转为DISPID的优化;
3.test1与test5的比较可以看出,IDispatchEx接口成员访问比IDispatch接口成员访问的开销大,也许IDispatch有做BSTR转DISPID的优化;
4.test2与test5的比较可以看出,IDispatch接口成员访问约为数组访问开销的7倍,这可能是COM调用的开销(估计是Invoke的开销而不是BSTR转DISPID的开销).
这么看来,在javascript脚本里要尽量少用对象,内建对象访问除了比直接变量/函数多一层COM Invoke外,还有成员通过字符串查找来匹配(IDISPATCH的调用方式)以及BSTR字符串的分配与回收,比较奇怪的是,微软实现javascript时为什么没有进行IDispatchEx调用的优化呢?只需要简单的将循环中的方法先求DISPID然后再调用就会快很多了,从MS实现.net上语言的方式差不多能猜测javascript的解析是采用自顶向下的语法分析的,极有可能是采取手写的递归下降分析,而且顺便就连执行也一块简单处理了,呵呵,这当然是我在瞎想,其实微软还是考虑了优化的,我曾经手写的类似javascript语言费了很大劲才能达到ms JS的执行效率,不过,我手写的那个就是没有代码优化的,我是直接执行三元式中间代码的(所以还是有可能ms没有加代码优化,不过他们显然在解释程序上考虑过一些性能优化,IDispatch的调用似乎就有这样的优化). March 14 回想我的虚荣心显然我是一个从小相信书本,相信党,相信国家的人,至少在我的内心深处,能够得到国家机构的认可,是一件很荣耀的事,比如教育部门的认可,比如国家专业管理机构的认可,所以一旦有这样的机会,我是不愿意放弃的,甚至从来不考虑是否值得,因为我相信党,相信国家.然而很遗憾,我没能一开始就得到很完整的教育,大学毕业就工作了,而且还不是本行,是搞计算机软件,也就是写程序.所以我的潜意识里一直向往更深的教育,同时一直希望得到一个计算机专业的认可,我想这也许是我的虚荣心在作怪吧.
一个非计算机系的毕业生好象很难直接找到计算机方面的工作,特别是在98年刚毕业的时候,所以我一开始没法从事纯的软件开发工作,而只能打打擦边球,比如做做自动控制系统的组态(自动控制里的翻译,其实是config),做做进销存系统的管理员,可是我是如此渴望进入真正的软件行业(看来确实好象似乎是我有问题,这是干嘛,为了正名?),所以想办法接近真正的软件业,我的做法便是先考张国家专业管理机构认可的证书,其实就是软考了,99年的软考不知道是什么原因推迟到2000年才举行,这样我其实是2000年考的99年的高级程序员,老天还是挺照顾的,一次通过,我想这么说我可以算半个软件专业了吧,所以2001年兴冲冲跑到北京.为什么是2001年呢?下面办事比较慢,证书等了一年才拿到,而且让人把性别写错了,汗,因为照片上穿的是红毛衣,市人事局的人就果断的写了"女",想当时我汗如雨下的将"女"改成"男",真是可怜天下考者心啊,机关的人说得可轻松了,说"要不你就先不拿,等着给你换个新的吧,大约一年就可以了.",可是我如何能再等一年啊.(其实我到北京找工作,发觉这个证书好象没有任何用,因为面试的老总根本没看那个证,晕倒,我可怜的虚荣)
我以前基本上是属于做有兴趣的事不问回报的人,所以在北京的第一家公司一做就是四年,而且后两年多一直没有涨工资,"付出总有回报"嘛,应该不用主动要吧?(这真是个巨大的错误啊,必须承认,社会其实是与思想或政治书里教的完全相反的方式运行着的).这四年的前两年还是比较幸福的,可能是我暂时放下了虚荣心吧,那段时间为工作而工作,别的什么都不太注意,包括最熟悉的人的工资我都不知道的.(这种员工真好,都不用刻意去瞒他了)
后来我的虚荣再次出来闹事了,我觉得还是需要些什么来证明一下自己,从专业方面看,我仍然选择了软考,而且当年有个很诱人的文件,那就是软考资格与水平合一了,而先前我考的高程是水平(因为毕业不满四年,不到评工程师资格的时候).在北京工作与在中小城市工作还是有很大差别的,那就是你不大可能有太多的空闲时间来学习了,即使有,也是在公交车上看书,这是我到北京头两年的习惯,后来在公交车上基本上是睡觉了(不知道为什么!).这样子我虽然报了名,但并没有太多时间来针对考试大纲学习,就这样糊涂的参加了考试,呵呵,这次老天依旧十分意外的照顾,我又过了.北京拿证比其他地方确实快许多,大约三个多月就下来了,因为北京市有个引进人才的文件,按新标准,这次软考表明具备相应技术资格,我便托公司人事打听,前后折腾了近半年,居然真的蒙过去了,我便办了人才引进.(为什么一定要引进呢?不是说了户口无关紧要吗?我想这也是我的虚荣心的缘故)
除了专业方面,我一直遗憾的是自己的教育经历,真的十分渴望有个更高的文凭,04年的时候,有一次公司老总说让我们几个人参加北航的EMBA(当时确实是这么宣传的,但后来录取通知写的却是软件工程硕士)的考试,说要是过了公司出钱让我们去学,不过要和公司签一个学完后再服务三年的合同.当时我们便去考了,结果上了,看起来老天对我太照顾了(其实我发现,只要是花钱学习的考试,机会真的很大的,我们当时是20人取了10个,真是很惊人的高比率).
好了,时间到了现在了,为什么要回想我的这些虚荣心的历程呢,原因在于,我发觉,我的虚荣建立的基础是如此的不牢固,反而甚至成为了一个圈套了.
首先说系分,2004年开始的新政策的确给了考试者很大的诱惑(比如用于评职称或如我用于人才引进),然而与之相伴的,2004年开始证书有效期是三年,过了三年需要登记,登记必须要出示接受继续教育的证明(每年不少于40学时,三年不少于180学时),OK,我知道,这便是国外所谓的动态管理,在ISO,CMMI,PMP等等管理方面的认证里十分普遍并且已证明是行之有效的保证从业者质量的方法.问题是我们国家的规定往往比较怪异,比如这里的学时数的取得,一是参加指定的培训机构的培训(要交纳不菲的费用的),二是被国家级会议选用的论文,三是在某级别以上核心期刊发表论文的.在我看来,唯有三是比较合理的大众化的,然而为了这个证书,我必须要写论文吗?我真的不知道如何是好了,也许只有写了.而且培训大纲里的好几门课都是我正好在北航学习的科目,但北航当然不在指定的培训机构之列.
接下来说北航的在职学习,不能说什么也没学到,实际上必须承认,在这里还是比较系统的学习了项目管理的知识的(虽然好象自学也可以的,呵呵,教育的功能有时确实不是很强).不过实话实说,现在的高校在工程硕士或MBA的培养方面的问题十分显然,学校往往不把这些当作他们教育的根本,所以通常不太重视,而另一方面这些方向往往是学校社会化合作办学的试验地,师资质量很值得怀疑.(算了,这个就不多说了,还得继续在此学习呢,呵呵) 另外一方面是我为此付出的代价,前面说过,公司当时答应替我们出钱的(当时的学费是8万,后来交的时候变成了7万,到我必须自己出钱的时候降到了5.8万) ,然而需要签一个合同,学完后再在公司服务三年,否则要赔偿,这份合同的怪异之处在于,越是接近合同期满,赔率越高(学习期间是150%,学完后是250%),而且如果因为失误或怠工被公司解聘也要赔偿,我在开始时很犹豫,领导说"这还用考虑吗,要是有人替我掏钱我早签了,而且我还会害你吗?"(领导自己确实签了一份,他的学费是25万,与我们不同的是,他是自己跟自己签),我想也是啊,他总不会害我吧,那就签吧.然而签了之后,我发觉什么都改变了,原来很少对我的挑剔出现了,更加忍无可忍的是,经常在我需要上课的时间要求加班(而合同里有一条,如果拿不到学位是要退回全部学费的)以及隔几天就问我上学觉得值吗还让我把招生的材料给他们备案(说将来不成可能得与学校打官司,晕倒).我受不了了,大约一个月后,我说,我想我还是自己掏学费吧,出乎意料,领导十分爽快的答应了(在给财务钱的时候,财务更是高兴,当时好象公司财务方面有点紧张,希望这不是我的好意的假想),这很让我糊涂,真的不知道当初是怎么回事了.之后不久,我终于离开了我到北京后服务过的第一家公司,也是目前为止在北京唯一服务过的公司.
现在又过去快一年了,我的许多事情都变得很乱,也许,真的一切都是因为我的虚荣吧?
再接下来,我如何是好呢?我应该一如既往的保持这种虚荣吗?还是放弃?
[晕,看来我的思路太不一致了,写着写着就不知所云,以后得改一改了.] |
|
|