vc++中各种字符串。

2008-12-31 发表在 程序开发 | 查看 24 次 | 评论

CString ,BSTR ,LPCTSTR之间关系和区别

CString是一个动态TCHAR数组,BSTR是一种专有格式的字符串(需要用系统提供的函数来操纵,LPCTSTR只是一个常量的TCHAR指针。

CString 是一个完全独立的类,动态的TCHAR数组,封装了 + 等操作符和字符串操作方法。
typedef OLECHAR FAR* BSTR;
typedef const char * LPCTSTR;

vc++中各种字符串的表示法

首先char* 是指向ANSI字符数组的指针,其中每个字符占据8位(有效数据是除掉最高位的其他7位),这里保持了与传统的C,C++的兼容。

LP的含义是长指针(long pointer)。LPSTR是一个指向以‘/0’结尾的ANSI字符数组的指针,与char*可以互换使用,在win32中较多地使用LPSTR。
而LPCSTR中增加的‘C’的含义是“CONSTANT”(常量),表明这种数据类型的实例不能被使用它的API函数改变,除此之外,它与LPSTR是等同的。
1.LP表示长指针,在win16下有长指针(LP)和短指针(P)的区别,而在win32下是没有区别的,都是32位.所以这里的LP和P是等价的.
2.C表示const
3.T是什么东西呢,我们知道TCHAR在采用Unicode方式编译时是wchar_t,在普通时编译成char.

为了满足程序代码国际化的需要,业界推出了Unicode标准,它提供了一种简单和一致的表达字符串的方法,所有字符中的字节都是16位的值,其数量也可以满足差不多世界上所有书面语言字符的编码需求,开发程序时使用Unicode(类型为wchar_t)是一种被鼓励的做法。

LPWSTR与LPCWSTR由此产生,它们的含义类似于LPSTR与LPCSTR,只是字符数据是16位的wchar_t而不是char。

然后为了实现两种编码的通用,提出了TCHAR的定义:
如果定义_UNICODE,声明如下:
typedef wchar_t TCHAR;
如果没有定义_UNICODE,则声明如下:
typedef char TCHAR;

LPTSTR和LPCTSTR中的含义就是每个字符是这样的TCHAR。

CString类中的字符就是被声明为TCHAR类型的,它提供了一个封装好的类供用户方便地使用。

LPCTSTR:
#ifdef _UNICODE
typedef const wchar_t * LPCTSTR;
#else
typedef const char * LPCTSTR;
#endif

VC常用数据类型使用转换详解

先定义一些常见类型变量借以说明
int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;

一、其它数据类型转换为字符串

短整型(int)
itoa(i,temp,10);//将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); //按二进制方式转换
长整型(long)
ltoa(l,temp,10);

二、从其它包含字符串的变量中获取指向该字符串的指针

CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);

三、字符串转换为其它数据类型
strcpy(temp,"123");

短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);

四、其它数据类型转换到CString

使用CString的成员函数Format来转换,例如:

整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;

五、BSTR、_bstr_t与CComBSTR

CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");//使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);

六、VARIANT 、_variant_t 与 COleVariant

VARIANT的结构可以参考头文件VC98/Include/OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4; //指明整型数据
va.lVal=a; //赋值

对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:

unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF

_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;

COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;

七、其它

对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
LPARAM lParam;
WORD loValue = LOWORD(lParam);//取低16位
WORD hiValue = HIWORD(lParam);//取高16位
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);//取低8位
BYTE hiValue = HIBYTE(wValue);//取高8位

如何将CString类型的变量赋给char*类型的变量
1、GetBuffer函数:
使用CString::GetBuffer函数。
char *p;
CString str="hello";
p=str.GetBuffer(str.GetLength());
str.ReleaseBuffer();

将CString转换成char * 时
CString str("aaaaaaa");
strcpy(str.GetBuffer(10),"aa");
str.ReleaseBuffer();
当我们需要字符数组时调用GetBuffer(int n),其中n为我们需要的字符数组的长度.使用完成后一定要马上调用ReleaseBuffer();
还有很重要的一点就是,在能使用const char *的地方,就不要使用char *

2、memcpy:
CString mCS=_T("cxl");
char mch[20];
memcpy(mch,mCS,20);

3、用LPCTSTR强制转换: 尽量不使用
char *ch;
CString str;
ch=(LPSTR)(LPCTSTR)str;

CString str = "good";
char *tmp;
sprintf(tmp,"%s",(LPTSTR)(LPCTSTR)str);

4、
CString Msg;
Msg=Msg+"abc";
LPTSTR lpsz;
lpsz = new TCHAR[Msg.GetLength()+1];
_tcscpy(lpsz, Msg);
char * psz;
strcpy(psz,lpsz);

CString类向const char *转换
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
或者如下:
strncpy(a,str,sizeof(a));
以上两种用法都是正确地. 因为strncpy的第二个参数类型为const char *.所以编译器会自动将CString类转换成const char *.

CString转LPCTSTR (const char *)
CString cStr;
const char *lpctStr=(LPCTSTR)cStr;

LPCTSTR转CString
LPCTSTR lpctStr;
CString cStr=lpctStr;

将char*类型的变量赋给CString型的变量
可以直接赋值,如:
CString myString = "This is a test";
也可以利用构造函数,如:
CString s1("Tom");

将CString类型的变量赋给char []类型(字符串)的变量
1、sprintf()函数
CString str = "good";
char tmp[200] ;
sprintf(tmp, "%s",(LPCSTR)str);
(LPCSTR)str这种强制转换相当于(LPTSTR)(LPCTSTR)str
CString类的变量需要转换为(char*)的时,使用(LPTSTR)(LPCTSTR)str

然而,LPCTSTR是const char *,也就是说,得到的字符串是不可写的!将其强制转换成LPTSTR去掉const,是极为危险的!
一不留神就会完蛋!要得到char *,应该用GetBuffer()或GetBufferSetLength(),用完后再调用ReleaseBuffer()。

2、strcpy()函数
CString str;
char c[256];
strcpy(c, str);

char mychar[1024];
CString source="Hello";
strcpy((char*)&mychar,(LPCTSTR)source);

关于CString的使用
1、指定 CString 形参
对于大多数需要字符串参数的函数,最好将函数原型中的形参指定为一个指向字符 (LPCTSTR) 而非 CString 的 const 指针。
当将形参指定为指向字符的 const 指针时,可将指针传递到 TCHAR 数组(如字符串 ["hi there"])或传递到 CString 对象。
CString 对象将自动转换成 LPCTSTR。任何能够使用 LPCTSTR 的地方也能够使用 CString 对象。

2、如果某个形参将不会被修改,则也将该参数指定为常数字符串引用(即 const CString&)。如果函数要修改该字符串,
则删除 const 修饰符。如果需要默认为空值,则将其初始化为空字符串 [""],如下所示:
void AddCustomer( const CString& name, const CString& address, const CString& comment = "" );

3、对于大多数函数结果,按值返回 CString 对象即可。

串的基本运算
对于串的基本运算,很多高级语言均提供了相应的运算符或标准的库函数来实现。
为叙述方便,先定义几个相关的变量:
char s1[20]="dir/bin/appl",s2[20]="file.asm",s3[30],*p;
int result;
下面以C语言中串运算介绍串的基本运算
1、求串长
int strlen(char *s);//求串s的长度
【例】printf("%d",strlen(s1)); //输出s1的串长12

2、串复制
char *strcpy(char *to,*from);//将from串复制到to串中,并返回to开始处指针
【例】strcpy(s3,s1); //s3="dir/bin/appl",s1串不变

3、联接
char *strcat(char *to,char *from);//将from串复制到to串的末尾,
//并返回to串开始处的指针
【例】strcat(s3,"/"); //s3="dir/bin/appl/"
strcat(s3,s2);//s3="dir/bin/appl/file.asm"

4、串比较
int strcmp(char *s1,char *s2);//比较s1和s2的大小,
//当s1s2和s1=s2时,分别返回小于0、大于0和等于0的值
【例】result=strcmp("baker","Baker"); //result>0
result=strcmp("12","12");//result=0
result=strcmp("Joe","joseph")//result<0

5、字符定位
char *strchr(char *s,char c);//找c在字符串s中第一次出现的位置,
//若找到,则返回该位置,否则返回NULL
【例】p=strchr(s2,'.'); //p指向"file"之后的位置
     if(p) strcpy(p,".cpp");//s2="file.cpp"

注意:
 ①上述操作是最基本的,其中后 4个操作还有变种形式:strncpy,strncath和strnchr。
 ②其它的串操作见C的。在不同的高级语言中,对串运算的种类及符号都不尽相同
 ③其余的串操作一般可由这些基本操作组合而成

【例】求子串的操作可如下实现:
void substr(char *sub,char *s,int pos,int len){
//s和sub是字符数组,用sub返回串s的第pos个字符起长度为len的子串
//其中0<=pos<=strlen(s)-1,且数组sub至少可容纳len+1个字符。
if (pos<0||pos>strlen(s)-1||len<0)
Error("parameter error!");
strncpy(sub,&s[pos],len); //从s[pos]起复制至多len个字符到sub

让人心疼的12句话。

2008-12-21 发表在 记录思考 | 查看 34 次 | 评论

1 有些事,我们明知道是错的,也要去坚持,因为不甘心;有些人,我们明知道是爱的,也要去放弃,因为没结局;有时候,我们明知道没路了,却还在前行,因为习惯了。
2、以为蒙上了眼睛,就可以看不见这个世界;以为捂住了耳朵,就可以听不到所有的烦恼;以为脚步停了下来,心就可以不再远行;以为我需要的爱情,只是一个拥抱。
3、那些已经犯过的错误,有一些是因为来不及,有一些是因为刻意躲避,更多的时候是茫然地站到了一边。我们就这样错了一次又一次,却从不晓得从中汲取教训,做一些反省。
4、你不知道我在想你,是因为你不爱我,我明明知道你不想我,却还爱你,是因为我太傻。也许有时候,逃避不是因为害怕去面对什么,而是在等待什么。
5、天空没有翅膀的痕迹,但鸟儿已经飞过;心里没有被刀子割过,但疼痛却那么清晰。这些胸口里最柔软的地方,被爱人伤害过的伤口,远比那些肢体所受的伤害来得犀利,而且只有时间,才能够治愈。
6、很多人,因为寂寞而错爱了一人,但更多的人,因为错爱一人,而寂寞一生。我们可以彼此相爱,却注定了无法相守。不是我不够爱你,只是我不敢肯定,这爱,是不是最正确的。
7、如果背叛是一种勇气,那么接受背叛则需要一种更大的勇气。前者只需要有足够的勇敢就可以,又或许只是一时冲动,而后者考验的却是宽容的程度,绝非冲动那么简单,需要的唯有时间。
8、生命无法用来证明爱情,就像我们无法证明自己可以不再相信爱情。在这个城市里,诚如劳力士是物质的奢侈品,爱情则是精神上的奢侈品。可是生命脆弱无比,根本没办法承受那么多的奢侈。
9、人最大的困难是认识自己,最容易的也是认识自己。很多时候,我们认不清自己,只因为我们把自己放在了一个错误的位置,给了自己一个错觉。所以,不怕前路坎坷,只怕从一开始就走错了方向。
10、生活在一个城市里,或者爱一个人,又或者做某件事,时间久了,就会觉得厌倦,就会有一种想要逃离的冲动。也许不是厌倦了这个城市、爱的人、坚持的事,只是给不了自己坚持下去的勇气。
11、多少次又多少次,回忆把生活划成一个圈,而我们在原地转了无数次,无法解脱。总是希望回到最初相识的地点,如果能够再一次选择的话,以为可以爱得更单纯。
12、如果你明明知道这个故事的结局,你或者选择说出来,或者装作不知道,万不要欲言又止。有时候留给别人的伤害,选择沉默比选择坦白要痛多了。

西伯利亚遇闪电,杀毒手轻松灭渔夫。

2008-12-18 发表在 工作那点事 | 查看 27 次 | 评论

我们在上一篇博文中已经领略到了这个来自西伯利亚的渔夫的厉害。

这个渔夫不但能修改dns,而且使用了rootkit技术隐藏保护了自己,甚至使用icesword都无法将其删除。而且市面上常见的日志扫描软件都无法监测到它的行踪。

 

而我们的闪电杀毒手却可以轻松几步就将其搞定。

闪电杀毒手强杀模块单机版下载:

http://support.trendmicro.com.cn/Anti-Virus/temp/myfileclean.rar


步骤如下:

1,  打开注册表编辑器,找到键值:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

在注册表编辑器的右边找到system项,然后复制其的值,本例中是kdmrv.exe。(注:该病毒的文件名是随机的:fn + 三位英文随机字母 .exe

 


2,  打开闪电杀毒手的强杀单机版,选择“手动输入删除”,输入c:\windows\system32\ 加上第一步中的文件名

3,  点击“删除文件”,就可以轻松将病毒删掉。

 

怎么样,简单吧。这就是我们的闪电杀毒手的厉害!

接下来你就可以打开网络连接修改DNS了,点击确定,修改成功!

 

请大家支持我们的闪电杀毒手,我们会越做越好~

小心西伯利亚渔夫的渔网,一招让你成为漏网之鱼

2008-12-17 发表在 工作那点事 | 查看 27 次 | 评论

http://blog.sina.com.cn/s/blog_59acc8e20100bhwi.html

西伯利亚渔夫?漏网之鱼?

最近一段时间出现一种病毒,名为“西伯利亚渔夫”,该病毒使用了Rootkit技术,正常情况下会看不到文件,更险恶的是,该病毒会修改系统的DNS

什么是DNS

大家都知道,当我们在上网的时候,通常输入的是如:www.trendmicro.com.cn 这样子的网址,其实这就是一个域名,而我们计算机网络上的计算机彼此之间只能用IP地址才能相互识别。DNS是指:域名服务器(Domain Name Server)。域名和IP之间的转换工作称为 域名解析,域名解析需要由专门的域名解析服务器来完成,DNS就是进行域名解析的服务器。

我们在一台电脑上,可以从两个地方获得一个域名所对应的IP,第一个是hosts文件,该文件的目录为C:\WINDOWS\system32\drivers\etc\hosts,可以文本文件打开编辑;第二个就是网络连接属性里的dns设置:

 

 

通常情况下,我们通过dns服务器来解析域名的次数更多,hosts文件大多只用来过滤部分特殊的网址。所以,如果dns被恶意修改了,后果则是不堪设想的。

 

例如,病毒将上图中的dns改为一台恶意的dns服务器的地址:192.168.0.2,该台服务器上也有域名和IP的关联数据库,不过在关联之前,它先做了一些其他的操作,比如链接到一个恶意的网站;或者,这个关联的数据库本身就是错误的,假设www.xxxxxxx.com本应对应的IP192.168.0.3,但是恶意的服务器上存储的却是192.168.0.4,所以只要在0.4上放置一个与0.3看上去一模一样的网站,访问者就很难发现问题,但是不幸的是,0.4的网站上通常都包含病毒。

 

“西伯利亚渔夫”就干了这种勾当。

该病毒会将自身以随机命名的方式拷贝一份放在system32目录,并通过HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon项的system键值来随系统一起启动。

 

中了该病毒后,DNS会被强制设为:

 

并且无法修改,就算选择“自动获得DNS服务器地址”,重新打开后仍恢复原样。

 

分析一番之后,我们发现,该病毒对注册表的以下键值做了修改:

 

所以,只要我们将该键值锁住,病毒就无法修改了!

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces下会有几个子项,对应着你机器里的网卡的设置。一一点开,根据右边的IP地址,找到本台机器上网时对应的网卡。

找到后,右击左边的项,选择权限,再选择高级:

 

然后对“everyone”用户的“设置数值”设为“拒绝”:

 

保存。这样,病毒无法就修改你的DNS信息了。

重新运行病毒,OK,修改失败!

 

但是这样做,也有以下几个问题:

1·必须在干净的系统下做这样的设置,如果中毒后再做,由于“西伯利亚渔夫”使用了HOOK技术,所以我们做不到“修改为正确的dns后再设置权限”。因为你刚将dns改为正确的值,病毒就将它改回去了。

2·由于无法对NameServer这个单独的项做权限设置,所以,你网卡下的所有项都被锁住,所以,此类设置只适合“IPDNS不经常改动”的机器,比如公司局域网。而个人ADSL用户由于IP是动态获得的,所以不适合使用此方法。

 

DNS欺骗的病毒虽然不多,但是一旦中招,后果会很严重,而且很难发觉。所以,用这样的方法来预防,还是挺不错的。当然,如果你使用了趋势的云计算,我们的云安全就有这用的功能来保护你不受“假冒网站”的攻击。

小谈IE7 0Day漏洞攻击步骤及原理。

2008-12-14 发表在 工作那点事 | 查看 33 次 | 评论

http://blog.sina.com.cn/s/blog_59acc8e20100bgyb.html

最近这事儿闹得沸沸扬扬的,不过似乎中招的呼声远没有漏洞本身的呼声大。这也说明了一个现象:虽然杀软很难做到对未知病毒的100%的防御,但是,现在已经做到在一个未知病毒出来后,极迅速地做出反应,以保证更多的用户不受病毒的攻击,正如我们趋势科技的云安全,在0day出现后7分钟就做出了反应,厉害吧~

等等,0day?什么是0day?

网络安全意思上的0day就是指一些没有公布补丁的漏洞,或者是还没有被漏洞发现者公布出来的漏洞利用工具,由于这种漏洞的利用程序对网络安全都具有巨大威胁,因此0day也成为黑客的最爱。

0day泛指所有在官方发布该作品之前或者当天,主要涵盖了影视、软件、游戏、音乐、资料等方面,由一些特别小组非法发布的数码内容。

下面我们就简单谈谈此次IE7的0day。

—————————危险的网页源码分割线—————————

---------------------------危险的网页源码分割线---------------------------

照例,攻击的原理还是利用缓冲区溢出,导致被插入堆中的shellcode运行。
关键代码是:SRC=http://rਊr.book.com src=http://www.google.com
由于 SRC 字符串当中的 rਊr 是非正常字符,导致对象分配失败,而失败后的内存指针没有被释放,继续被利用,而这个指针被病毒制造者人为指向了一段堆地址,而如果这段地址被 ShellCode 覆盖的话,调用这个指针就会导致有溢出。

出问题的IE组件便是MSHTML.DLL。
mshtml.dll会对这个SRC:http://rਊr.book.com(即http://rਊr.book.com)作如下解析:
r把十进制的114转成0x0072
ਊ把十进制的2570转成0x0a0a
刚好它们拼在一起就是一个可利用的堆地址:0x0a0a0072,再通过spray函数,分配大量的内存,可使shellcode填充到这个地址空间去。

再看一下spray函数:
spray先对参数做unescap和replace的简单解密,然后开始分配大量内存。
由于Windows 操作系统堆分配的特点,以下三句 ?)(gAUZcV
aaablk=(0x0a0a0a0a-0x100000)/heapBlockSize; nF;H 0Hi9
zzchuck=new Array(); ev $i7@
for(i=0;i 保证了从堆开始的地址直到0x0a0a0a0a 都会被溢出代码淹没,所以最终通过 0x0a0a0072 跳到了溢出代码。 Ve IZ[WnB

所以我们归纳一下该0day的步骤便是:
1·先定义好shellcode的命令,用replace和escape加密;
2·然后用spray解密,并分配大量内存,将shellcode填充至堆;
3·判断浏览器版本,如果是7,则利用rਊr和mshtml.dll的漏洞,导致溢出,并成功运行至第二步shellcode填充处。

下面你又不禁要问,mshtml.dll是怎么导致溢出的呢?那是因为微软的工程师也和小AV一样,会犯指针不赋null的错误~
CRecordInstance里面有一个Binding数组,里面都是CXfer *指针(通过CRecordInstance::AddBinding添加的)。CRecordInstance::RemoveBinding的时候释放了CXfer对象指针,减小了数组大小,但被释放的指针还留在数组里面。
CRecordInstance::TransferToDestination,遍历CXfer *数组,逐个调用CXfer::TransferFromSrc。问题是某个CXfer *元素在TransferFromSrc的时候竟然会调用CRecordInstance::RemoveBinding把后面的CXfer *给释放了。等遍历到后面的CXfer *,这个指针早已不再指向CXfer对象了,而被再次分配作它用,恰好指向了用户可控的网址(rਊr),于是跳到 了Spray Heap中去执行。

关于这个漏洞的patch,网上已经众说纷纭,小AV就不再赘言,但是需要提醒的是,有些厂商提供的补丁是无效的,所以还请各位多加小心。当然,如果你是趋势科技的用户,你会受到我们的“云师兄”的保护:)

具体请看:http://cn.trendmicro.com/cn/about/news/pr/article/20081212071622.html

杀毒手开发小记。

2008-12-12 发表在 工作那点事 | 查看 28 次 | 评论

http://blog.sina.com.cn/s/blog_59acc8e20100bgdk.html

说起来还真有点自嘲,我这个国内某知名高校的软件工程专业的小本,居然时第一次开发团队VC并行项目。不过自嘲归自嘲,自从做了杀毒手,嘿,不但MFC深入浅出了,也能自己搞VC皮肤了。

其实杀毒手的目标定位跟它的名字有点冲突。一开始杀毒手的主要需求是为了帮助趋势的企业版客户做一些强力杀毒的功能,但是又考虑将来push solution的需要,所以附带了一些其他的功能,以便在后续版本做成一个“发布病毒解决方案的平台”。所以,这个杀毒手的名字,其实并不是十分贴切的,当然,这也是处于“一名惊人”的需要

我的模块是负责开发UI和各模块的通信数据。
杀毒手的架构是类似插件的设计,每个模块是个dll,继承自某一个接口,然后通过主程序Load进来。虽然我这是第一次做团队VC项目,但是据我的先前考察,发现这样的做法是切实可行而且是比较好的并行开发模式。

当然这便带来了一些困难。比如我的UI模块。如果是个人开发,我只需做一个skin库就可以。但是,杀毒手的做法是,每个功能模块通过UI的dll来动态生成控件。所以UI模块要和功能模块在事先进行充分的沟通、约定。

以前在学校都是个人做一些VC的中小型工具,所以界面美化都是用一些第三方的控件库。所以精力都集中在程序的逻辑上,而从来没有考虑过界面的产生、通信、消息循环已经销毁的机制。于是,只好硬着头皮上了。

先从“如何创建不规则窗体”开始。加了两天小班,研究了如何通过单色图来定制窗口形状,也阅读了通过定制Crect来控制窗口的外形,终于成功做出一个“奇形怪状”的窗口,但是却十分喜欢。接着,着手“通过dll动态创建控件,并让控件响应事件,然后传回给调用窗体”,加了3天班,对“dll的导出函数”,“MFC的动态绑定消息”有了深入的了解。接下来,开始美化界面,从“最简单的界面贴图”,到“重写MFC的窗体绘制函数”,再到“重绘各个控件”,这真是一个艰难的历程,不过都被我一个个搞定,更重要的是,我对MFC产生窗体的函数和过程有了了解,熟悉了常用控件的使用方法。

经过2个多星期的奋斗,一个友好的界面终于诞生。本来以为以及大功告成。可是新的问题又来了:内存泄露。因为控件是功能模块通过调用UI模块开发的函数动态创建出来的,所以我的模块里有了大量的new,但是为了保证这些控件一直显示在界面上,所以我没有对应的delete。但是由于控件类型功能模块是保密的,所以功能模块也无法delete。那到底是谁来delete?只能是UI模块了。没办法,只能重构代码。对类进行重新设计,将所有控件进行“树形”设计。在父窗口的类中加入子控件的List,那么在父窗口析构的时候,就会遍历这个list,然后对list里的每个子控件进行递归析构。所以,只要最顶层的主窗体销毁,那么其下的所有子窗口也就会销毁了。哦也,Memory Leak也被成功搞定。

UI模块与功能模块的数据的通信是个繁琐的过程。因为通信的数据不固定,所以很难做得“简单而又全面”,我目前的做法只能是有一份类似的SDK文档,其中约定了UI模块与功能模块的数据约定,类似name-value的数据结构。对于这个数据结构,我们曾讨论使用简单的string,到复杂的xml结构,后来都被否定,因为string的格式松散,xml的读写效率低,所以最后自己设计了一个类来存储这些数据,能解决格式松散和读写的问题,但是实例化的时候,要写比较大量的“简单”代码。如果你有什么好方法,请千万不要吝啬告诉我。

整个杀毒手的净开发时间大概在一个月左右。我在c++,vb,asp,asp.net,flash,photoshop,vc#,web中游荡了一圈之后,发现还是钟爱c++,难道这就是传说中的轮回?貌似我在杀毒手之前最近的一个VC项目还是大二做的,不禁又要自嘲一下了。通过杀毒手的开发过程,我觉得自己对MFC、C++的认识又真的上了一个台阶,我想,大概是因为又过了几年,项目的经验丰富了,所以对语言的认识也就在一个新的高度了。还记得大一时最讨厌C++的指针了,而我现在却是那么的喜欢它。真所谓温故而知新,不亦悦乎。

最后要略略带过的是,杀毒手的开发团队只有5个人,无法在一个月的时间内,做出一个类似“集合了杀毒、免疫、扫描等综合功能的产品”。所以,在杀毒手发布后,我们也看到一些业内人士的详细评测,我们很感谢他们做的无偿劳动,但是毕竟我们的主要定位还是帮助企业客户端强力杀毒。当然,我们会在后续版本里不断完善,将其真正作为一个发布杀毒方案的平台。请大家到时支持我们的杀毒手,支持我们的趋势!

谢幕,台下想起热烈的掌声,偶尔还听到阵阵“Jason,我爱你”的美女尖叫声。哦也