??xml version="1.0" encoding="utf-8" standalone="yes"?> 一个函数在~译时被分配一个入口地址Q将q个入口地址UCؓ函数的指针,?span lang="EN-US"> 以用一个指针变量指向该函数指针Q然后通过该变量来调用函数?span lang="EN-US"> 有关说明Q?span lang="EN-US"> 1、函数指针的声明格式Q?span lang="EN-US"> 函数q回值类型(Q指针变量名Q(参数cd列表Q?span lang="EN-US"> 或者是Q?span lang="EN-US"> typedef 函数q回值类型 Q*指针变量名)Q参数类型列表) 2、一个函数指针只能指向一U类型的函数Q即h相同的返回值和相同的参 数的函数 Q、关于函数指针的加减q算没有意义 函数指针数组定义Q?span lang="EN-US"> 函数定义Q?span lang="EN-US"> void fun1(void *p); void fun2(void *p); void fun3(void *p); 函数指针数组定义Q?span lang="EN-US"> void(*fun[3])(void*);//typedef void(*pfun)(void*);pfun fun[3]; 指针赋| fun[0] = fun1; fun[1] = fun2; fun[2] = fun3; 函数调用Q?span lang="EN-US"> fun[0](&a); //int a; fun[1](&b); //int b; fun[3](&c); //int c; 一个指向成员函数的指针包括成员函数的返回类型,?em>::W号的类名称Q函数参数表。虽然这一语法看似复杂Q其实它和普通的指针是一L。指向外部函数的指针可如下声明: void (*pf)(char *, const char *); 相应指向c?span lang="EN-US">A的成员函数的指针如下表示Q?span lang="EN-US"> 以上pmf是指向类A的一个成员函数的指针Q传递两个变?em>char *?const char *Q没有返回倹{注意星号前面的A::W号Q这和前面的声明是一致的?span lang="EN-US"> 你可以?span lang="EN-US">typedef来隐藏一些指向成员函数的复杂指针。例如,下面的代码定义了一个类A中的成员函数的指?em>PMAQƈ传?em>char *?em>const char *参数?span lang="EN-US"> 使用typedef特别有用Q尤其是对于指向成员函数的数l指针?span lang="EN-US"> ?/font> voidcd的指?span lang="EN-US"> void含义Q?span lang="EN-US"> void?#8220;无类?#8221;Q?span lang="EN-US">void*则ؓ无类型指针,void*可以指向Mcd的数据?span lang="EN-US"> void aQ?span lang="EN-US">//此变量没有Q何实际意义,无法~译通过“illegal use of type” void 的作用: 1、对E序q回的限?span lang="EN-US"> 2、对函数参数的限?span lang="EN-US"> 我们知道Q如何指?span lang="EN-US">p1?span lang="EN-US">p2的类型相同,那么我们可以直接?span lang="EN-US">p1?span lang="EN-US">p2间赋|如果不同Q必M用强制类型{换?span lang="EN-US"> 如:float *p1; int *p2; 若:p1 = p2; ~译出错Q?#8220;can not covert from int* to float*” 必须为:p1 = (float*)p2; ?span lang="EN-US">void*不同QQ何类型的指针都可以直接赋为它Q不需要强制类型{换: 如:void *p1; int *p2; 可作Q?span lang="EN-US">p1 =p2; 无类型可以包ҎcdQ有cd不能包容无类型: 必须为:p2 = (int*)p1; viod ?span lang="EN-US"> void*使用规则ȝQ?span lang="EN-US"> ?/font> 如果函数没有q回|那么应声明ؓvoidcd ?/span>C语言中,凡不加返回值类型限定的函数Q就会被~译器作回整型值处理。但是许多程序员却误以ؓ其ؓvoidcd. 因此Qؓ了避免乱,我们在编?/span>C/C++E序Ӟ对于M函数都必M个不漏地指定其类型。如果函数没有返回|一定要声明?/span>voidcd。这既是E序良好可读性的需要,也是~程规范性的要求。另外,加上voidcd声明后,也可以发挥代码的“自注?/span>”作用。代码的“自注?/span>”即代码能自己注释自己?/span> ?/span> 如果函数无参敎ͼ那么应声明其参数?/span>void ?/span> 心使用void指针cd 按照ANSI的标准,不能?/span>void指针q行法操作Q即下列操作是不合法的: void *pvoid; pvoid ++; //ansi错误 pvoid += 1; //ansi 错误 ansi标准之所以这栯定,是因为它坚持Q进行算法操作的指针必须是确定知道其指向数据cd的大的?/span> ?/span>GUNQ?/span>GUN’s Not UnixQ则不这么认为,它指?/span>void*的算法操作与char*一致。因此在GUN~译器中上述语句是正的?/span> 在实际的E序中,Zq合ansi标准Qƈ提高E序的可UL性,我们可以q样实现同样功能的代码: void *pvoid; (char*)pvoid++; (char*)pvoid += 1; ?如果函数的参数可以是Lcd指针Q那么应声明其参Cؓvoid * 典型的如内存操作函数memcpy?/span>memset的函数原型分别ؓQ?/span> void* memcpy(void *dest, const void *src, size_t len); void* memset(void *buffer,int c, size_t num); q样QQ何类型的指针都可以传?/span>memcpy?/span>memset中,q也真实CC内存操作函数的意义,因ؓ它操作的对象仅仅是一片内存,而不论内存是什cd?/span> ?void不能代表一个真实的变量 void a; //错误 function(void a); //错误 ?/font> this指针 《深入浅?span lang="EN-US">MFC》中解释Q?span lang="EN-US"> 定义c?span lang="EN-US">CRectQ定义两个对?span lang="EN-US">rect1?span lang="EN-US">rect2Q各有自qm_color成员变量Q但rect1.setcolor?span lang="EN-US">rect2.setcolor却都是通往唯一?span lang="EN-US">CRect::setcolor成员函数Q那?span lang="EN-US">CRect::setcolor如何处理不同对象?span lang="EN-US">m_colorQ答案是׃个隐藏参敎ͼ名ؓthis指针。当你调用: rect1.setcolro(2); rect2.setcolor(3); Ӟ~译器实际上Z做出来一下的代码Q?span lang="EN-US"> CRect::setcolor(2,(CRect*)&rect1); CRect::setcolor(3,(CRect*)&rect2); 多出来的参数Q就是所谓的this指针?span lang="EN-US"> class CRect { …… public: void setcolor(int color){m_color = color}; }; 被编译后Q其实ؓQ?span lang="EN-US"> class CRect { …… public: void setcolor(int color,(CRect*)this){this->m_color = color}; };
作者:成晓?br> 旉Q?001q?0?1?11:35:38-12:35:00)
内容Q完成插花问题的“动态规划法”法及注?br>*/
#include "stdafx.h"
#include "string.h"
#define MAX(A,B) ((A) > (B) ? (A):(B))
//--------------------鲜花问题--------------------
#define F 100
#define V 100
/*
插花问题描述:
f束鲜花插入v个花瓶中,使达到最徍的视觉效果,
问题相关U定及插p?
鲜花被编号ؓ1--f,q被编号ؓ1--v,q按从到
大顺序排?一只花瓶只能插一支花,鲜花i插入qj中的
视觉效果效果值已?~号的鲜花所攑օ的花瓶编号也?nbsp;
问题求解思\:
qj(1<=j<=v)中插入鲜q可能~号为[1..j](~号
的鲜花所攑օ的花瓶编号也?;
设数lp[i][j]表示鲜花i插入qj的好看程?数组
q[i][j]表示[1..i]束鲜花插入[1..j]个花瓶所能得到的最?br> 好看E度,初始化q[0][0] = 0;q[0][j]=0(1<=j<=v),则q[f][v]
是问题的?
特别?j束鲜花插入到前面的j只花瓶中,所得到的好?br> E度是q[j][j] = p[1][1]+p[2][2]+...+[j][j].现将插花q?br> E按q排列序划分成不同阶D?则在Wj阶段,Wi束鲜?br> 若放入第j可?最大好看程度是q[i-1][j-1]+p[i][j];Wi束鲜
p攑օ前j-1个花瓶中的某一?所得的好看E度是q[i][j-1],
那么在第j阶段,插入Wi束鲜花所能得到的最大好看程度ؓ:
q[i][j] = MAX(q[i-1][j-1]+p[i][j],q[i][j-1]),要q[i][j]
最?应q[i-1][j-1]和q[i][j-1]也最?br>*/
//初始化函?br>void Initialize(int *f,int *v,int p[][V])
{
int i,j;
printf("输入鲜花数量及花瓶个?");
scanf("%d%d",f,v);
printf("序输入各鲜花插入各q的好看程?\n");
for(i=1;i<=*f;i++)
for(j=1;j<=*v;j++)
p[i][j] = i+j;
//scanf("%d",&p[i][j]);
}
//鲜花问题处理函数
int Sove(int p[][V],int f,int v,int *way)
{
int i,j,newv,q[F][V];
q[0][0] = 0;
/*讄v个花瓶分别被插入v束鲜花时各号q对应?初始)最大好看程?/
for(j=1;j<=v;j++)
{
q[0][j] = 0;
/*讄Wj束鲜花放入第j可瓶中的最大好看程?/
q[j][j] = q[j-1][j-1]+p[j][j];
}
for(j=1;j<=v;j++)
for(i=1;i<j;i++)
q[i][j] = MAX(q[i-1][j-1]+p[i][j],q[i][j-1]);
newv = v;
for(i=f;i>0;i--)
{
while(q[i-1][newv-1]+p[i][newv] < q[i][newv])
newv--;
//定鲜花i插在qnewv?q准备考虑前一只花瓶
way[i] = newv--;
}
return(q[f][v]);
}
//--------------------鲜花问题--------------------
//--------------------最长子串问?-------------------
#define N 100
char a[N],b[N],str[N];
//计算两个序列最长公共子序列的长?br>int Get_LongSubStr_Len(char *a,char *b,int c[][N])
{
int m=strlen(a),n=strlen(b),//两个序列的长?br> i,j;//循环变量
for(i=0;i<=m;i++) c[i][0] = 0;
for(i=1;i<=n;i++) c[0][i] = 0;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
if(a[i-1]==b[j-1])
c[i][j] = c[i-1][j-1]+1;
else
c[i][j] = MAX(c[i-1][j],c[i][j-1]);
/*
if(c[i-1][j]>=c[i][j-1])
c[i][j] = c[i-1][j];
else
c[i][j] = c[i][j-1];
*/
return(c[m][n]);
}
//构造最长公共子序列
char *Build_LongSubStr(char s[],char *a,char *b)
{
int i=strlen(a),j=strlen(b),
k,c[N][N];
k = Get_LongSubStr_Len(a,b,c);
s[k] = '\0';
while(k>0)
{
if(c[i][j]==c[i-1][j])
i--;
else
{
if(c[i][j]==c[i][j-1])
j--;
else
{
s[--k]=a[i-1];
i--;
j--;
}
}
}
return(s);
}
//--------------------最长子串问?-------------------
int main(int argc, char* argv[])
{
int i,f,v,p[F][V],way[F];
//-----------------------------------
/*
Initialize(&f,&v,p);
printf("最大好看程度ؓ%d\n",Sove(p,f,v,way));
printf("插有鲜花的花瓶是:\n");
for(i=1;i<=f;i++)
printf("%4d",way[i]);
*/
//-----------------------------------
printf("输入两个字符?长度<%d):\n",N);
scanf("%s%s",a,b);
printf("两个串的最长公共子序列?%s\n",Build_LongSubStr(str,a,b));
//-----------------------------------
printf("\n\n应用E序正在q行......\n");
return 0;
}
]]>
]]>
和成员函数指针不同,你不隑֏现委托的用处。最重要的,使用委托可以很容易地实现一?Subject/Observer设计模式的改q版[GoF, p. 293]。ObserverQ观察者)模式昄在GUI中有很多的应用,但我发现它对应用E序核心的设计也有很大的作用。委托也可用来实现策略(StrategyQ[GoF, p. 315]和状态(StateQ[GoF, p. 305]模式?br>
现在Q我来说明一个事实,委托和成员函数指针相比ƈ不仅仅是好用Q而且比成员函数指针简单得多!既然所有的.NET语言都实C委托Q你可能会猜惛_此高层的概念在汇~代码中q不好实现。但事实q不是这P委托的实现确实是一个底层的概念Q而且像普通的函数调用一L单(q且很高效)。一个C++委托只需要包含一个this 指针和一个简单的函数指针够了。当你徏立一个委托时Q你提供q个委托一个this指针Qƈ向它指明需要调用哪一个函数。编译器可以在徏立委托时计算整this指针需要的偏移量。这样在使用委托的时候,~译器就什么事情都不用做了。这一Ҏ好的是,~译器可以在~译时就可以完成全部q些工作Q这L话,委托的处理对~译器来说可以说是微不道的工作了。在x86pȝ下将委托处理成的汇编代码应该是q么单:
mov ecx, [this]
call [pfunc]
但是Q在标准C++中却不能生成如此高效的代码?BorlandZ解决委托的问题在它的C++~译器中加入了一个新的关键字Q__closureQ?用来通过z的语法生成优化的代码。GNU~译器也对语aq行了扩展,但和Borland的编译器不兼宏V如果你使用了这两种语言扩展中的一U,你就会限制自己只使用一个厂家的~译器。而如果你仍然遵@标准C++的规则,你仍然可以实现委托,但实现的委托׃会是那么高效了?br>
有趣的是Q在C#和其?NET语言中,执行一个委托的旉要比一个函数调用慢8倍(参见http://msdn.microsoft.com/library/en- us/dndotnet/html/fastmanagedcode.aspQ。我猜测q可能是垃圾攉?NET安全查的需要。最q,微Y?#8220;l一事g模型Qunified event modelQ?#8221;加入到Visual C++中,随着q个模型的加入,增加了__event?__raise、__hook、__unhook、event_source和event_receiver{一些关键字。坦白地_我对加入的这些特性很反感Q因是完全不W合标准的,q些语法是丑陋的Q因为它们ɘq种C++不像C++Qƈ且会生成一堆执行效率极低的代码?br>
解决q个问题的推动力Q对高效委托Qfast delegateQ的q切需?br>
使用标准C++实现委托有一个过度臃肿的症状。大多数的实现方法用的是同一U思\。这些方法的基本观点是将成员函数指针看成委托K�但这L指针只能被一个单独的cM用。ؓ了避免这U局限,你需要间接地使用另一U思\Q你可以使用模版为每一个类建立一?#8220;成员函数调用器(member function invokerQ?#8221;。委托包含了this指针和一个指向调用器QinvokerQ的指针Qƈ且需要在堆上为成员函数调用器分配I间?br>
对于q种Ҏ已经有很多种实现Q包括在CodeProject上的实现Ҏ。各U实现在复杂性上、语法(比如Q有的和C#的语法很接近Q上、一般性上有所不同。最h威的一个实现是boost::function。最q,它已l被采用作ؓ下一个发布的C++标准版本中的一部分[Sutter1]。希望它能够被广泛地使用?br>
像传统的委托实现方法一P我同样发觉这U方法ƈ不十分另人满意。虽然它提供了大家所期望的功能,但是会淆一个潜在的问题Qh们缺乏对一个语a的底层的构造?“成员函数调用?#8221;的代码对几乎所有的c都是一LQ在所有^C都出现这U情冉|令h沮的。毕竟,堆被用上了。但在一些应用场合下Q这U新的方法仍然无法被接受?br>
我做的一个项目是L事g模拟器,它的核心是一个事件调度程序,用来调用被模拟的对象的成员函数。大多数成员函数非常单:它们只改变对象的内部状态,有时在事仉列(event queueQ中d来要发生的事gQ在q种情况下最适合使用委托。但是,每一个委托只被调用(invokedQ一ơ。一开始,我用了boost:: functionQ但我发现程序运行时Q给委托所分配的内存空间占用了整个E序I间的三分之一q要多!“我要真正的委托!”我在内心呼喊着Q?#8220;真正的委托只需要仅仅两行汇~指令啊Q?#8221;
我ƈ不能L能够得到我想要的Q但后来我很q运。我在这儿展C的代码Q代码下载链接见译者注Q几乎在所有编译环境中都生了优化的汇~代码。最重要的是Q调用一个含有单个目标的委托Qsingle-target delegateQ的速度几乎同调用一个普通函C样快。实现这L代码q没有用C么高q东西Q唯一的遗憑ְ是,Z实现目标Q我的代码和标准C++ 的规则有些偏R我使用了一些有x员函数指针的未公开知识才它能够这样工作。如果你很细心,而且不在意在数情况下的一些编译器相关Qcompiler-specificQ的代码Q那么高性能的委托机制在MC++~译器下都是可行的?br>
诀H:Q何类型的成员函数指针转化Z个标准的形式
我的代码的核心是一个能够将Mcȝ指针和Q何成员函数指针分别{换ؓ一个通用cȝ指针和一个通用成员函数的指针的cR由于C++没有“通用成员函数Qgeneic member functionQ?#8221;的类型,所以我把所有类型的成员函数都{化ؓ一个在代码中未定义的CGenericClasscȝ成员函数?br>
大多数编译器Ҏ有的成员函数指针q等地对待,不管他们属于哪个cR所以对q些~译器来_可以使用reinterpret_cast一个特定的成员函数指针转化Z个通用成员函数指针。事实上Q假如编译器不可以,那么q个~译器是不符合标准的。对于一些接q标准(almost-compliantQ的~译器,比如Digital MarsQ成员函数指针的reinterpret_cast转换一般会涉及C些额外的Ҏ代码Q当q行转化的成员函数的cM间没有Q何关联时Q编译器会出错。对q些~译器,我们使用一个名为horrible_cast的内联函敎ͼ在函C使用了一个union来避免C++的类型检查)。用这U方法看来是不可避免的�Kboost::function也用Cq种Ҏ?br>
对于其他的一些编译器Q如Visual C++, Intel C++和Borland C++Q,我们必须多重(multiple-Q承和虚拟Qvirtual-Q承类的成员函数指针{化ؓ单一Qsingle-Q承类的函数指针。ؓ了实现这个目的,我y妙地使用了模板ƈ利用了一个奇妙的戏法。注意,q个戏法的用是因ؓq些~译器ƈ不是完全W合标准的,但是使用q个戏法得到了回报:它ɘq些~译器生了优化的代码?br>
既然我们知道~译器是怎样在内部存储成员函数指针的Qƈ且我们知道在问题中应该怎样为成员函数指针调整this指针Q我们的代码在设|委托时可以自己调整this指针。对单一l承cȝ函数指针Q则不需要进行调_对多重承,则只需要一ơ加法就可完成调_对虚拟?..有些麻烦了。但是这样做是管用的Qƈ且在大多数情况下Q所有的工作都在~译时完成!
q是最后一个诀H。我们怎样区分不同的承类型?q没有官方的Ҏ来让我们区分一个类是多重承的q是其他cd的ѝ但是有一Uy妙的ҎQ你可以查看我在前面l出了一个列表(见中)——对MSVCQ每U承方式生的成员函数指针的大是不同的。所以,我们可以Z成员函数指针的大用模版!比如对多重承类型来_q只是个单的计算。而在定unknown_inheritanceQ?6字节Q类型的时候,也会采用cM的计方法?br>
对于微Y和英特尔的编译器中采用不标准12字节的虚拟承类型的指针的情况,我引发了一个编译时错误Qcompile-time errorQ,因ؓ需要一个特定的q行环境QworkaroundQ。如果你在MSVC中用虚拟承,要在声明cM前?FASTDELEGATEDECLARE宏。而这个类必须使用unknown_inheritanceQ未知承类型)指针Q这相当于一个假定的 __unknown_inheritance关键字)。例如:
class CDerivedClass : virtual public CBaseClass1, virtual public CBaseClass2 {
// : (etc)
};
q个宏和一些常数的声明是在一个隐藏的命名I间中实现的Q这样在其他~译器中使用时也是安全的。MSVCQ?.0或更新版本)的另一U方法是在工E中使用/vmg~译器选项。而Inter的编译器?vmg~译器选项不v作用Q所以你必须在虚拟承类中用宏。我的这个代码是因ؓ~译器的bug才可以正运行,你可以查看代码来了解更多l节。而在遵从标准的编译器中不需要注意这么多Q况且在M情况下都不会妨碍FASTDELEGATEDECLARE宏的使用?br>
一旦你类的对象指针和成员函数指针转化为标准Ş式,实现单一目标的委托(single-target delegateQ就比较Ҏ了(虽然做v来感觉冗长乏呻I。你只要为每一U具有不同参数的函数制作相应的模板类p了。实现其他类型的委托的代码也大都与此怼Q只是对参数E做修改|了?br>
q种用非标准方式转换实现的委托还有一个好处,是委托对象之间可以用等式比较。目前实现的大多数委托无法做到这一点,qɘq些委托不能胜Q一些特定的dQ比如实现多播委托(multi-cast delegatesQ?[Sutter3]?br>
静态函C为委托目标(delegate targetQ?br>
理论上,一个简单的非成员函敎ͼnon-member functionQ,或者一个静态成员函敎ͼstatic member functionQ可以被作ؓ委托目标Qdelegate targetQ。这可以通过静态函数{换ؓ一个成员函数来实现。我有两U方法实现这一点,两种Ҏ都是通过使委托指向调用这个静态函数的“调用器(invokerQ?#8221;的成员函数的Ҏ来实现的?br>
]]>
引子
标准C++中没有真正的面向对象的函数指针。这一点对C++来说是不q的Q因为面向对象的指针Q也叫做“闭包QclosureQ?#8221;?#8220;委托QdelegateQ?#8221;Q在一些语a中已l证明了它宝늚价倹{在Delphi (Object Pascal)中,面向对象的函数指针是Borland可视化组建库QVCLQVisual Component LibraryQ的基础。而在目前QC#?#8220;委托”的概忉|流行,q也正显C出C#q种语言的成功。在很多应用E序中,“委托”化了松耦合对象的设计模式[GoF]。这U特性无疑在标准C++中也会生很大的作用?br>
很遗憾,C++中没?#8220;委托”Q它只提供了成员函数指针Qmember function pointersQ。很多程序员从没有用q函数指针,q是有特定的原因的。因为函数指针自w有很多奇怪的语法规则Q比?#8220;->*”?#8220;.*”操作W)Q而且很难扑ֈ它们的准含义,q且你会扑ֈ更好的办法以避免使用函数指针。更h讽刺意味的是Q事实上Q编译器的编写者如果实?#8220;委托”的话会比他费劲地实现成员函数指针要容易地多!
在这文章中Q我要揭开成员函数指针?#8220;秘的盖?#8221;。在D地重q成员函数指针的语法和特性之后,我会向读者解释成员函数指针在一些常用的~译器中是怎样实现的,然后我会向大家展C编译器怎样有效地实?#8220;委托”。最后我会利用这些精q知识向你展示在C++~译器上实现优化而可靠的“委托”的技术。比如,在Visual C++(6.0, .NET, and .NET 2003)中对单一目标委托Qsingle-target delegateQ的调用Q编译器仅仅生成两行汇编代码Q?br>
函数指针
下面我们复习一下函数指针。在C和C++语言中,一个命名ؓmy_func_ptr的函数指针指向一个以一个int和一个char*为参数的函数Q这个函数返回一个Q点|声明如下Q?br>
//Z便于理解Q我强烈推荐你用typedef关键字?br>
//如果不这L话,当函数指针作Z个函数的参数传递的时候,
// E序会变得晦涩难懂?br>
// q样的话Q声明应如下所C:
MyFuncPtrType my_func_ptr;
应注意,Ҏ一个函数的参数l合Q函数指针的cd应该是不同的。在Microsoft Visual C++Q以下称MSVCQ中Q对三种不同的调用方式有不同的类型:__cdecl, __stdcall, 和__fastcall。如果你的函数指针指向一个型如float some_func(int, char *)的函敎ͼq样做就可以了:
//当你惌用它所指向的函数时Q你可以q样写:
(*my_func_ptr)(7, "Arbitrary String");
你可以将一U类型的函数指针转换成另一U函数指针类型,但你不可以将一个函数指针指向一个void *型的数据指针。其他的转换操作׃用详叙了。一个函数指针可以被讄?来表明它是一个空指针。所有的比较q算W(==, !=, <, >, <=, >=Q都可以使用Q可以?#8220;==0”或通过一个显式的布尔转换来测试指针是否ؓI(nullQ?br>
在C语言中,函数指针通常用来像qsort一样将函数作ؓ参数Q或者作为Windowspȝ函数的回调函数等{。函数指针还有很多其他的应用。函数指针的实现很简单:它们只是“代码指针Qcode pointerQ?#8221;Q它们体现在汇编语言中是用来保存子程序代码的首地址。而这U函数指针的存在只是Z保证使用了正的调用规范?br>
成员函数指针
在C++E序中,很多函数是成员函敎ͼ卌些函数是某个cM的一部分。你不可以像一个普通的函数指针那样指向一个成员函敎ͼ正确的做法应该是Q你必须使用一个成员函数指针。一个成员函数的指针指向cM的一个成员函敎ͼq和以前有相同的参数Q声明如下:
//对于使用const关键字修饰的成员函数Q声明如下:
float (SomeClass::*my_const_memfunc_ptr)(int, char *) const;
注意使用了特D的q算W(::*Q,?#8220;SomeClass”是声明中的一部分。成员函数指针有一个可怕的限制Q它们只能指向一个特定的cM的成员函数。对每一U参数的l合Q需要有不同的成员函数指针类型,而且ҎU用const修饰的函数和不同cM的函敎ͼ也要有不同的函数指针cd。在MSVC中,对下面这四种调用方式都有一U不同的调用cdQ__cdecl, __stdcall, __fastcall, ?__thiscall。(__thiscall是缺省的方式Q有的是,在Q何官Ҏ档中从没有对__thiscall关键字的详细描述Q但是它l常在错误信息中出现。如果你昑ּC用它Q你会看?#8220;它被保留作ؓ以后使用Qit is reserved for future useQ?#8221;的错误提C。)如果你用了成员函数指针Q你最好用typedef以防止淆?br>
函数指针指向型如float SomeClass::some_member_func(int, char *)的函敎ͼ你可以这样写Q?br>
很多~译器(比如MSVCQ会让你L“&”Q而其他一些编译器Q比如GNU G++Q则需要添?#8220;&”Q所以在手写E序的时候我把它M。若要调用成员函数指针,你需要先建立SomeClass的一个实例,q用特D操作符“->*”Q这个操作符的优先较低Q你需要将光当地放入圆括号内?br>
(x->*my_memfunc_ptr)(6, "Another Arbitrary Parameter");
//如果cd栈上Q你也可以?#8220;.*”q算W?/span>
SomeClass y;
(y.*my_memfunc_ptr)(15, "Different parameters this time");
不要怪我使用如此奇怪的语法——看hC++的设计者对标点W号有着p的感情!C++相对于C增加了三U特D运符来支持成员指针?#8220;::*”用于指针的声明,?#8220;->*”?#8220;.*”用来调用指针指向的函数。这Lh对一个语a模糊而又很少使用的部分的q分x是多余的。(你当然可以重?#8220;->*”q些q算W,但这不是本文所要涉及的范围。)
一个成员函数指针可以被讄?Qƈ可以使用“==”?#8220;!=”比较q算W,但只能限定在同一个类中的成员函数的指针之间进行这L比较。Q何成员函数指针都可以?做比较以判断它是否ؓI。与函数指针不同Q不{运符Q?lt;, >, <=, >=Q对成员函数指针是不可用的?br>
成员函数指针的怪异之处
成员函数指针有时表现得很奇怪。首先,你不可以用一个成员函数指针指向一个静态成员函敎ͼ你必M用普通的函数指针才行Q在q里“成员函数指针”会生误解,它实际上应该?#8220;非静态成员函数指?#8221;才对Q。其ơ,当用类的承时Q会出现一些比较奇怪的情况。比如,下面的代码在MSVC下会~译成功Q注意代码注释)Q?br>
class SomeClass {
public:
virtual void some_member_func(int x, char *p) {
printf("In SomeClass"); };
};
class DerivedClass : public SomeClass {
public:
// 如果你把下一行的注释销掉,带有 line (*)的那一行会出现错误
// virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); };
};
int main() {
//声明SomeClass的成员函数指?/span>
typedef void (SomeClass::*SomeClassMFP)(int, char *);
SomeClassMFP my_memfunc_ptr;
my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*)
return 0;
}
奇怪的是,&DerivedClass::some_member_func是一个SomeClasscȝ成员函数指针Q而不是DerivedClasscȝ成员函数指针Q(一些编译器E微有些不同Q比如,对于Digital Mars C++Q在上面的例子中Q?amp;DerivedClass::some_member_func会被认ؓ没有定义。)但是Q如果在DerivedClasscM重写QoverrideQ了some_member_func函数Q代码就无法通过~译Q因为现在的&DerivedClass::some_member_func已成为DerivedClasscM的成员函数指针!
成员函数指针之间的类型{换是一个讨v来非常模p的话题。在C++的标准化的过E中Q在涉及l承的类的成员函数指针时Q对于将成员函数指针转化为基cȝ成员函数指针q是转化为子cL员函数指针的问题和是否可以将一个类的成员函数指针{化ؓ另一个不相关的类的成员函数指针的问题Qh们曾有过很激烈的争论。然而不q的是,在标准委员会做出军_之前Q不同的~译器生产商已经Ҏ自己对这些问题的不同的回{实C自己的编译器。根据标准(W?.2.10/9节)Q你可以使用reinterpret_cast在一个成员函数指针中保存一个与本来的类不相关的cȝ成员函数。有x员函数指针{换的问题的最l结果也没有定下来。你现在所能做的还是像以前那样——将成员函数指针转化为本cȝ成员函数的指针。在文章的后面我会l讨个问题,因ؓq正是各个编译器对这样一个标准没有达成共识的一个话题?br>
在一些编译器中,在基cd子类的成员函数指针之间的转换时常有怪事发生。当涉及到多重承时Q用reinterpret_cast子c{换成基类ӞҎ一特定~译器来说有可能通过~译Q而也有可能通不q编译,q取决于在子cȝ基类列表中的基类的顺序!下面是一个例子:
class Derived2: public Base2, public Base1 // 情况 (b)
typedef void (Derived::* Derived_mfp)();
typedef void (Derived2::* Derived2_mfp)();
typedef void (Base1::* Base1mfp) ();
typedef void (Base2::* Base2mfp) ();
Derived_mfp x;
对于情况(a)Qstatic_cast<Base1mfp> (x) 是合法的Q而static_cast<Base2mfp> (x) 则是错误的。然而情?b)却与之相反。你只可以安全地子cȝ成员函数指针转化为第一个基cȝ成员函数指针Q如果你要实验一下,MSVC会发出C4407可告,而Digital Mars C++会出现编译错误。如果用reinterpret_cast代替static_castQ这两个~译器都会发生错误,但是两种~译器对此有着不同的原因。但是一些编译器Ҏl节|之不理Q大家可要小心了Q?br>
标准C++中另一条有的规则是:你可以在cd义之前声明它的成员函数指针。这对一些编译器会有一些无法预料的副作用。我待会讨论q个问题Q现在你只要知道要尽可能得避免这U情况就是了?br>
需要值得注意的是Q就像成员函数指针,标准C++中同h供了成员数据指针Qmember data pointerQ。它们具有相同的操作W,而且有一些实现原则也是相同的。它们用在stl::stable_sort的一些实现方案中Q而对此很多其他的应用我就不再提及了?br>
成员函数指针的?br>
现在你可能会觉得成员函数指针是有些奇异。但它可以用来做什么呢Q对此我在网上做了非常广泛的调查。最后我ȝZ用成员函数指针的两点原因Q?br>
* 用来做例子给
* C++初学者看Q帮助它们学习语法;或?Z实现“委托Q?br> delegateQ?#8221;Q?
成员函数指针在STL和Boost库的单行函数适配器(one-line function adaptorQ中的用是微不道的,而且允许你将成员函数和标准算法合用。但是它们最重要的应用是在不同类型的应用E序框架中,比如它们形成了MFC消息pȝ的核心?br>
当你使用MFC的消息映宏Q比如ON_COMMANDQ时Q你会组装一个包含消息ID和成员函数指针(型如QCCmdTarget::*成员函数指针Q的序列。这是MFCcdȝ承CCmdTarget才可以处理消息的原因之一。但是,各种不同的消息处理函数具有不同的参数列表Q比如OnDraw处理函数的第一个参数的cd为CDC *Q,所以序列中必须包含各种不同cd的成员函数指针。MFC是怎样做到q一点的呢?MFC利用了一个可怕的~译器漏z(hackQ,它将所有可能出现的成员函数指针攑ֈ一个庞大的联合QunionQ中Q从而避免了通常需要进行的C++cd匚w查。(看一下afximpl.h和cmdtarg.cpp中名为MessageMapFunctions的unionQ你׃发现q一恐怖的事实。)因ؓMFC有如此重要的一部分代码Q所以事实是Q所有的~译器都个漏z开了绿灯。(但是Q在后面我们会看刎ͼ如果一些类用到了多重承,q个漏洞在MSVC中就不会起作用,q正是在使用MFC时只能必M用单一l承的原因。)
在boost::function中有cM的漏z(但不是太严重Q。看h如果你想做Q何有x员函数指针的比较有趣的事Q你必d好与q个语言的漏z进行挑战的准备。要是你惛_定C++的成员函数指针设计有~陷的观点,看来是很隄?br>
在写q篇文章中,我有一炚w要指明:“允许成员函数指针之间q行转换QcastQ,而不允许在{换完成后调用其中的函?#8221;Q把q个规则U_C++的标准中是可W的。首先,很多行的编译器对这U{换不支持Q所以,转换是标准要求的Q但不是可移植的Q。其ơ,所有的~译器,如果转换成功Q调用{换后的成员函数指针时仍然可以实现你预期的功能Q那~译器就没有所谓的“undefined behaviorQ未定义的行为)”q类错误出现的必要了Q调用(InvocationQ是可行的,但这不是标准Q)。第三,允许转换而不允许调用是完全没有用处的Q只有{换和调用都可行,才能方便而有效地实现委托Q从而ɘq种语言受益?br>
Z让你信q一h争议的论断,考虑一下在一个文件中只有下面的一D代码,q段代码是合法的Q?br>
typedef void (SomeClass::* SomeClassFunction)(void);
void Invoke(SomeClass *pClass, SomeClassFunction funcptr)
{
(pClass->*funcptr)();
};
注意到编译器必须生成汇编代码来调用成员函数指针,其实~译器对SomeClasscM无所知。显Ӟ除非链接器进行了一些极端精l的优化措施Q否则代码会忽视cȝ实际定义而能够正地q行。而这造成的直接后果是Q你可以“安全?#8221;调用从完全不同的其他cM转换q来的成员函数指针?br>
释我的断a的另一半——{ 换ƈ不能按照标准所说的方式q行Q我需要在l节上讨论编译器是怎样实现成员函数指针的。我同时会解释ؓ什么用成员函数指针的规则h如此严格的限制。获 得详l论q成员函数指针的文不是太容易,q且大家寚w误的a论已l习以ؓ怺Q所以,我仔l检查了一pd~译器生成的汇编代码……
]]>
刚遇到这U语法时也许会让你止步不前。但你会发现Q用恰当的cd定义之后Q复杂的语法是可以简化的。本文引g了解成员函数指针的声明,赋值和调用回叫函数?
成员函数指针的声?/strong>
一个成员函数指针包括成员函数的q回cdQ后?:操作W类名,指针名和函数的参数。初看上去,语法有点复杂。其实可以把它理解ؓ一个指向原函数的指针,格式是:函数q回cdQ类名,::操作W,指针星号Q指针名Q函数参数?
一个指向外部函数的指针声明为:
void (*pf)(char *, const char *);
void strcpy(char * dest, const char * source);
pf=strcpy;
一个指向类A成员函数的指针声明ؓQ?
void (A::*pmf)(char *, const char *);
声明的解释是Qpmf是一个指向A成员函数的指针,q回无类型|函数带有二个参数Q参数的cd分别是char * ?const char *。除了在星号前增加A:: Q与声明外部函数指针的方法一栗?
赋?/strong>
l成员指针赋值的Ҏ是将函数名通过指针W号&赋予指针名。如下所C:
{
public:
void strcpy(char *, const char *);
void strcat(char *, const char *);
};
pmf = &A::strcpy;
有些老的~译器可以通过没有&L赋值方式,但标准C++强制要求加上&受?
使用cd定义
可以用类型定义来隐藏复杂的成员指针语法。例如,下面的语句定义了PMA是一个指向A成员函数的指针,函数q回无类型|函数参数cd为char * ?const char *Q?
typedef void(A::*PMA)(char *, const char *);
PMA pmf= &A::strcat; // pmf是PMFcd(cA成员指针)的变?
下文会看C用类型定义特别有利于声明成员指针数组?
通过成员指针调用成员函数
可以在不必知道函数名的情况下Q通过成员指针调用对象的成员函数。例如,函数dispatcher有一个变量pmfQ通过它调用类成员函数Q不它调用
的是strcpy()函数q是strcat()函数。指向外部原函数的指针和指向cL员函数的指针是有很大区别的。后者必L向被调函数的宿主对象。因
此,除了要有成员指针外,q要有合法对象或对象指针?
CD例做q一步说明。假设A有二个实例,成员函数指针支持多态性。这样在成员指针调用虚成员函数时是动态处理的(x谓后联编 - 译注)。注意,不可调用构造和析构函数。示例如下:
A *p= &a1; //创徏指向A的指?br>
//创徏指向成员的指针ƈ初始?/font>
void (A::*pmf)(char *, const char *) = &A::strcpy;
//要将成员函数l定到pmfQ必d义呼叫的对象?br>
//可以?号引|
void dispatcher(A a, void (A::*pmf)(char *, const char *))
{
char str[4];
(a.*pmf)(str, “abc”); //成员函数绑定到pmf
}
//或用A的指针表达方式指向成员指针:
void dispatcher(A * p, void (A::*pmf)(char *, const char *))
{
char str[4]; (p->*pmf)(str, “abc”);
}
//函数的调用方法ؓQ?/font>
dispatcher(a, pmf); // .* 方式
dispatcher(&a, pmf); // ->* 方式
高使用技?
以上是成员函数的基本知识。现在介l它的高U用技巧?
成员指针数组
在下例,声明了一个含有二个成员指针的数组Qƈ分配cȝ成员函数地址l成员指针:
PMA pmf[2]= {&A::strcpy, &A::strcat};
也就?br> void (A::*PMA[2])(char *, const char *)= {&A::strcpy, &A::strcat};
q样的数l在菜单驱动应用中很有用。选择菜单后Q应用将调用相应的回叫函敎ͼ如下所C:
int main()
{
MENU_OPTIONS option; char str[4];
//从外部资源读取选项
switch (option)
{
case COPY:
(pa->*pmf[COPY])(str, “abc”);
break;
case CONCAT:
(pa->*pmf[CONCAT])(str, “abc”);
break;
//…
}
}
Const cd的成员函?/strong>
成员指针的类型应该与成员函数cd一致。上面例子中的pmf 可以指向A的Q意函敎ͼ只要该函C是constcd。如下所C,如果touppercase()的地址分配lpmfQ将D~译出错Q因为touppercase() 的类型是const?
{
public:
void strpcy(char *, const char *);
void strcat(char *, const char *);
void touppercase(char *, const char*) const;
};
pmf=&A::touppercase; //出错Q类型不匚w
//解决的方法是声明一个constcd的成员指针:
void (A::pcmf)(char *, const char *) const;
pcmf=&A::touppercase; // 现在可以?nbsp;
有些差劲的编译器允许一个非constcd的成员指针指向constcd的成员函数。这在标准C++是不允许的?
]]> 声明一个指向成员函数的指针
void strcpy(char * dest, const char * source);
pf=strcpy;void (A::*pmf)(char *, const char *);
赋?span lang="EN-US">
Zl一个指向成员函数的指针赋|可以采用成员函数名ƈ再其前面加一?span lang="EN-US">&的方?/span>
使用typedef
typedef void(A::*PMA)(char *, const char *);
PMA pmf= &A::strcat; // use a typedef to define a pointer to member
]]>
pDC = this->GetDC();
::BeginPath(pDC->m_hDC);
//讄为透明模式
::SetBkMode(pDC->m_hDC, TRANSPARENT);
//
RECT rect;
this->GetClientRect(&rect);
/*三角?br> int TopCenterPoint=rect.left + (rect.right - rect.left) /2;
pDC->MoveTo(TopCenterPoint, rect.top);
pDC->LineTo(rect.left, rect.bottom - GLOBAL_OVERLEN);
pDC->LineTo(rect.right, rect.bottom - GLOBAL_OVERLEN);
pDC->LineTo(TopCenterPoint,rect.top);
*/
/*比较奇怪的矩Ş
pDC->MoveTo(rect.left, rect.top);
pDC->LineTo(rect.right, rect.top);
pDC->LineTo(rect.right, rect.bottom - GLOBAL_OVERLEN);
pDC->LineTo(rect.left + (rect.right - rect.left) / 2, rect.bottom - GLOBAL_OVERLEN);
pDC->LineTo(rect.left + (rect.right - rect.left) / 2, rect.bottom);
pDC->LineTo(rect.left + (rect.right - rect.left) / 2 - GLOBAL_OVERWIDTH, rect.bottom - GLOBAL_OVERLEN);
pDC->LineTo(rect.left, rect.bottom - GLOBAL_OVERLEN);
pDC->LineTo(rect.left, rect.top);
*/
//
::EndPath(pDC->m_hDC);
hRgn = ::PathToRegion(pDC->m_hDC);
this->SetWindowRgn(hRgn, TRUE);
=============================================================
0. 盘文g数据存储方式
在介l各U操作文件方式之前,需要先介绍盘上文件数据的l织方式?/span>
实际上,文g是在计算机内存中以二q制表示的数据在外部存储介质上的另一U存攑Ş式?/span>文g通常分ؓ二进制文件和文本文g?/span>二进制文件是包含?/span>ASCII及扩?/span>ASCII字符中编写的数据或程序指令的文g。一般是可执行文?/span>(Exe)、图形、图像、声音等文g。而文本文?/span>(通常也成?/span>ASCII文g)Q它的每一字节存放的是可表CZؓ一个字W的ASCII代码的文件。这里把文g区分Zq制文g和文本文Ӟ但实际上它们都是以二q制数据的方式来存储的。文本文仉所存储的每一个字节都可以转化Z?/span>可读的字W。譬?/span>’a’,’b’Q?/span>但在内存中ƈ不会存储'a', 'b'Q而是存储它们?/span>ASCII码:61?/span>62?/span>
1. C语言Ҏ件操作的支持
?/span>C语言中,对于文g的操作都是利?/span>FILEl构体来q行的。包括文件的打开、文件的d、文件的关闭、文件指针的定位{?/span>
(1) 文g的打开
文g的打开需要用fopen函数。该函数?/span>2个参敎ͼq回gؓ指向之前定义?/span>FILEl构体指针。语法定义ؓ
FILE* fopen(const char* filename, const char* mod);
参数1Q表C打开文g的完整\径,例如: “C:\\Test\\Zero_Test.txt”.
参数2Q打开文g的方式。取gؓ下表所C?/span>
文g打开模式 |
意义 |
r/rb |
取而打开。如果文件不存在或不能找刎ͼ函数调用p|?/span> |
w/wb |
为写入而打开一个空文g。如果给定的文g已经存在Q则清空其内宏V?/span> |
a/ab |
为写入而打开一个文件。如果文件存在,则在文gNd新数据,在写新数据之前不会移除原有的EOF标志。如果文件不存在Q则新徏一个空文g以待写入?/span> |
r+/rb+ |
打开文g用于写入操作和读取操作,文g必须存在?/span> |
w+/wb+ |
为写入操作和d操作打开一个空文g。如果文件已l存在,则清I其内容?/span> |
a+/ab+ |
打开文g用于d操作和添加操作。ƈ且添加操作在d新数据之前会U除该文件中已有?/span>EOF标志Q然后当写入操作完成之后再恢?/span>EOF标志。如果指定文件不存在Q那么首先将新徏一个文件?/span> |
在上表中Q没有带b的模式表C打开的是文本文gQ而带b的模式表C打开的是二进制文件?/span>
通常在定义结?/span>FILE时会其初始化ؓNULL。在打开文g函?/span>fopen的返回D值给它。之后需要判断文件是否成功打开Q可采用如下方式Q?/span>
if ((pFile = fopen(“C:\\Test\\Zero_Test.txt”, “w”) == NULL)
Print(“Opening File Error.”); // FILE* pFile = NULL;
else
正常写入数据到文?/span>…
Q?/span>2Q文件的关闭
在用完一个文件之后应该关闭它Q以防止它再被误用?#8220;关闭”是使文件指针变量不再指向该文gQ其脱钩。函C用方?/span>:
fclose(pFile);
需要注意的?/span>fclose的参数必L有效的文件指针变量,否则q行时会挂掉?/span>
应该L在文件数据操作完成后关闭文g的习惯,如果不关闭文件将会丢失数据。同Ӟ在向文g写数据时Q是现将数据输出到缓冲区Q待~冲区充满后才正式输出给文g。如果程序运行结束而缓冲区q未充满Q则~冲Z的数据将会丢失。因此利用函?/span>fclose来关闭文件可以避免这个问题?/span>
当然Q还有另外一个函?/span>fflushQ也可以用来缓冲区里的数据输出到文件中。函数调用方?/span>:
fflush(pFile);
Q?/span>3Q文件的d
当涉及到大量数据的读写时Q可以采用函?/span>fread?/span>fwrite?/span>q两个函数通常用于二进制文件的d?/span>函数调用方式如下Q?/span>
fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);
buffer是一个指针,是用来存放数据的变量指针Q例如内|类型变量的指针Q结构体变量指针{?/span>
size是要d的字节数?/span>特别要注意的是,此处q不是数l的大小Q结构体内变量的个数{。最好采?/span>sizeof操作W来求得buffer的内存字节数?/span>
count?/span>size大小的重复次数?/span>
fp是文件指针?/span>
对于q样的结构体Q?/span>
typedef struct {
int clr_sel; // color mode的选择
int fixval_sel; // fixed value的选择
float thres_val; // threshold value的确?/span>
float thres_scrlbar_pos; // threshold scroll bar位置的确?/span>
int method_sel; // analysis method的选择
int usediff_sel; // use difference of MSA/TSE方式的选择
int usescat_sel; // use MSA/TSE of scatter signal 方式的选择
int remxtalk_sel; // remove crosstalk 方式的选择
float timewin_scrlbar_pos; // time window scroll bar位置的确?/span>
int timewin_sel; // time window模式的选择
int freq_sel; // frequency selection模式的选择
int path_sel; // path selection模式的选择
} IMG_SETTINGS;
IMG_SETTINGS m_img_settings;
文gd操作Ӟ可采用如下方式:
fwrite(&m_img_settings, sizeof(m_img_settings), 1, pwFile);
fread(&m_img_settings, sizeof(m_img_settings), 1, prFile);
====================================================
如果惌写特定格式的文gQ可采用fprintf?/span>fscanf函数?/span>q两个函数通常是针Ҏ本文件进行操作?/span>
函数调用方式如下Q?/span>
fprintf(pFile, “%d…”, i, j, k …);
fscanf(pFile, “%d…”, i j, k…);
格式化时可以规定d数据的精度,cdQ以及数据之间的分割W等?/span>
例如Q?/span>
fprintf(pFile, " %d %d %d %d %d %d %d %d \n", sigSize, samp_points, samp_rate,40, avrag_num, 0, 0, 0);
fprintf(pFile, "%d %.2f %s %d %d %d %d \n", vpathdef[i].frequency1/1000,
vpathdef[i].amplitude, sig_type.c_str(), psnset->sensorArray[vpathdef[i].actuator-1].channel-tol,
psnset->sensorArray[vpathdef[i].sensor-1].channel-tol, vpathdef[i].gain, 30);
特别需要注意的是,如果写入的数据的cd相同Ӟ不可Z便,格式化的字W串写成如此形式(.., ”%d”,i,j,k…); // i,j,k…同整型类型?/span>
如果q样操作的话Q读写的数据便只有数?/span>i了?/span>
最后针对一个面试题来对C语言操作文g的方式作一个ȝ.
面试题:l你一个整敎ͼ例如12345Q将q个整数保存到文件中Q要求在以记事本打开该文件时Q显C的?/span>:12345?/span>
l出三种代码Q?/span>
Q?Q?span>
代码1FILE* pwFile = NULL;
pwFile = fopen(“c:\\Test.txt”, “w”); // create and open file with text mode.
int i = 12345; // However, write number with binary mode.
fwrite(&i, 4, 1, pwFile); // sizeof(int) = 4.
fclose(fwFile);
========================
Q?Q?span>
代码2FILE* pwFile = NULL;
pwFile = fopen(“c:\\Test.txt”, “w”);
char ch[5] = {1+48, 2+48, 3+48, 4+48, 5+48 };
fwrite(ch, 1, 5, pwFile); // sizeof(char) = 1, 5*sizeof(char) = 5
fclose(pwFile);
=======================
Q?Q?span>
代码3FILE* pwFile = NULL;
pwFile = fopen(“c:\\Test.txt”, “w”);
int i = 12345;
char ch[5];
itoa(i, ch, 10); // transform int number to string
fwrite(ch, 1, 5, fwFile);
fclose(pFile);
==============================
Q?Q?span> 代码4
FILE* pwFile = NULL;
pwFile = fopen(“c:\\Test.txt”, “w”);
int i = 12345;
fprintf(pwFile, “%d”, i); // write number with specified format.
fclose(pwFile);
==============================
?/span>4U代码在vc中进行编译运行,可以发现只有方式Q?/span>1Q不满题目要求?br>
依次是视cR文类、框架类Q最后才是应用程序类?/span>
2. Windows消息的分c?/span>
实际上,菜单命o也是一U消息。在Windows中,消息分ؓ以下3U:
Q?Q?span> 标准消息
除了WM_COMMAND之外Q所有以WM_开头的消息都是标准消息。从CWndz的类Q都可以接收到该cL息?/span>
Q?Q?span> 命o消息
来自菜单、加速键或工h按钮的消息。这cL息都是以WM_COMMAND形式呈现。在MFC中,通过菜单的标识(ID)来区分不同的命o消息Q在SDK中,通过消息?/span>wParam参数来标识。从CCmdTargetz的类Q都可以接收到这cL息?/span>
Q?Q?span> 通告消息
由控件生的消息Q例如按钮的单击、列表框的选择{都会生这cL息,目的是ؓ了向其父H口通知事g的发生。这cL息也是以WM_COMMAND形式呈现的。从CCmdTargetz的类Q都可以接收到这cL息?/span>
׃CWnd是从CCmdTargetz的,故从CWndz的类Q它们既可以接收标准消息Q也可以接收命o消息和通告消息。而对于那些从CCmdTargetz的类Q则只能接收命o消息和通告消息Q不能接收标准消息?/span>
3. 菜单命o消息路由的具体过E?/span>
当点L个菜单项Ӟ最先接收到q个菜单命o消息的是框架cR框架类把接收到的q个消息交给它的子窗口,卌c,pc首先进行处理。视c首先根据命令消息映机制查找自w是否对此消息进行了响应Q如果响应了Q就调用响应函数对这个消息进行处理,消息路由q程l束Q如果视cL有对此命令消息做处响应,׃由文类Q文档类同样查找自n是否对这个菜单命令进行了响应Q如果响应了Q就由文档类的命令消息响应函数进行处理,路由q程l束。如果文类也未做出响应Q就把这个命令消息交q给视类Q后者又把该消息交还l框架类。框架类查看自己是否对这个命令消息进行了响应Q如果它也没有作处响应,把q个菜单命o消息交换l应用程序类Q由后者来q行处理?/span>
4. 菜单操作
要想获得某个菜单资源Q需使用下面的函数调用:
CMenu* GetMenu() const ;
该函数调用者是CWnd的对象。返回gؓCMenucd象的指针?/span>
通过获得的菜单指针便可以获得其上的某个菜单栏的指针了。调用方式ؓQ?/span>
CMenu* GetSubMenu(int nPos) const;
调用者是菜单资源的类对象指针Q或者是某个菜单栏的对象指针?/span>
如果某个菜单栏下面还有子菜单,通过可以通过GetSubMenu函数来获得其子菜单项的操作指针。子菜单的索引都是?/span>0开始的Q同时分割栏也是要占据烦引值的?/span>
5. CASEQ?/span>
在资源编辑器中编?/span>Popup Menu资源: IDR_SENSOR_DRAW_MENU?/span>
Grids Style子菜单中的各个选项是相互排斥的。要求选择其中的一个时Q需在其菜单前面标志选择?/span>”·”Q其它都Zؓ选中。也是?/span>Style中只能选中一个?/span>
可采用两U方式:
Q?/span>1Q映消息:ON_WM_CONTEXTMENU()
void ..::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
Q?/span>2Q映消息:ON_WM_RBUTTONDOWN()
void ..::OnRightBtnDown(.. ..)
响应函数实现代码Q?/span>
{
CMenu menu;
menu.LoadMenu(IDR_SENSOR_DRAW_MENU); // 装蝲菜单资源
CMenu* pMenu = menu.GetSubMenu(0); // 获得Sensor Menu菜单栏的对象指针
CMenu* pSubMenu = pMenu->GetSubMenu(0); // 获得Grids Style子菜单项的对象指?/span>
// 以此cLQ如果是Edit Structure字菜单项的对象指针,参数应该?/span>1
int idxsel = 0;
switch (m_GridStyle) {
case GRIDSOFF:
idxsel = 0;
break;
case GRIDS10X:
idxsel = 2; // separator is one resource, so take it into account.
break;
case GRIDS20X:
idxsel = 3;
break;
case GRIDS30X:
idxsel = 4;
break;
}
// Grids Style子菜单项又有N个子菜单,Ҏ索引g|来讄其状态?/span>
pSubMenu->CheckMenuRadioItem(0, 4, idxsel, MF_BYPOSITION);
// 如果?/span>WM_RBUTTONDOWN响应Q则需要调用函?/span>
ClientToScreen(&point); // 客户坐标{化ؓ屏幕坐标?/span>
// 以下函数的参?/span>x,y是屏q坐标倹{?/span>
pMenu->TrackPopupMenu(TPM_RIGHTBUTTON,point.x,point.y,this,NULL);
}
Author: Yevgeny Menaker
下蝲代码
原文出处Q?a target=_blank>Five Steps to Writing Windows Services in C
摘要
Windows 服务被设计用于需要在后台q行的应用程序以及实现没有用户交互的d。ؓ了学习这U控制台应用E序的基知识QCQ不是C++Q是最佳选择。本文将建立q实C个简单的服务E序Q其功能是查询系l中可用物理内存数量Q然后将l果写入一个文本文件。最后,你可以用所学知识编写自q Windows 服务?br> 当初我写W一?NT 服务Ӟ我到 MSDN 上找例子。在那里我找C一?Nigel Thompson 写的文章Q?#8220;Creating a Simple Win32 Service in C++”Q这文章附带一?C++ 例子。虽然这文章很好地解释了服务的开发过E,但是Q我仍然感觉~少我需要的重要信息。我想理解通过什么框Ӟ调用什么函敎ͼ以及何时调用Q但 C++ 在这斚w没有让我L多少。面向对象的Ҏ固然方便Q但׃用类对底?Win32 函数调用q行了封装,它不利于学习服务E序的基本知识。这是Z么我觉得 C 更加适合于编写初U服务程序或者实现简单后CQ务的服务。在你对服务E序有了充分透彻的理解之后,?C++ ~写才能游刃有余。当我离开原来的工作岗位,不得不向另一个h转移我的知识的时候,利用我用 C 所写的例子非常容易解?NT 服务之所以然?br> 服务是一个运行在后台q实现勿需用户交互的Q务的控制台程序。Windows NT/2000/XP 操作pȝ提供为服务程序提供专门的支持。h们可以用服务控制面板来配|安装好的服务程序,也就?Windows 2000/XP 控制面板|理工具中的“服务”Q或?#8220;开?#8221;|“q行”对话框中输入 services.msc /s——译者注Q。可以将服务配置成操作系l启动时自动启动Q这样你׃必每ơ再重启pȝ后还要手动启动服务?br> 本文首先解释如何创Z个定期查询可用物理内存ƈ结果写入某个文本文件的服务。然后指g完成生成Q安装和实现服务的整个过E?br>
W一步:d数和全局定义
首先Q包含所需的头文g。例子要调用 Win32 函数Qwindows.hQ和盘文g写入Qstdio.hQ:
#include <windows.h> #include <stdio.h>
接着Q定义两个常量:
#define SLEEP_TIME 5000 #define LOGFILE "C:\\MyServices\\memstatus.txt"
SLEEP_TIME 指定两次q箋查询可用内存之间的毫U间隔。在W二步中~写服务工作循环的时候要使用该常量?br>LOGFILE 定义日志文g的\径,你将会用 WriteToLog 函数内存查询的l果输出到该文gQWriteToLog 函数定义如下Q?/p>
int WriteToLog(char* str) { FILE* log; log = fopen(LOGFILE, "a+"); if (log == NULL) return -1; fprintf(log, "%s\n", str); fclose(log); return 0; }
声明几个全局变量Q以便在E序的多个函C间共享它们倹{此外,做一个函数的前向定义Q?/p>
SERVICE_STATUS ServiceStatus; SERVICE_STATUS_HANDLE hStatus; void ServiceMain(int argc, char** argv); void ControlHandler(DWORD request); int InitService();
现在Q准备工作已l就l,你可以开始编码了。服务程序控制台E序的一个子集。因此,开始你可以定义一?main 函数Q它是程序的入口炏V对于服务程序来_main 的代码o人惊讶地短,因ؓ它只创徏分派表ƈ启动控制分派机?/p>
void main() { SERVICE_TABLE_ENTRY ServiceTable[2]; ServiceTable[0].lpServiceName = "MemoryStatus"; ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain; ServiceTable[1].lpServiceName = NULL; ServiceTable[1].lpServiceProc = NULL; // 启动服务的控制分z机U程 StartServiceCtrlDispatcher(ServiceTable); }
一个程序可能包含若q个服务。每一个服务都必须列于专门的分z表中(为此该程序定义了一?ServiceTable l构数组Q。这个表中的每一w要在 SERVICE_TABLE_ENTRY l构之中。它有两个域Q?/p>
分派表的最后一必L服务名和服务d数域?NULL 指针Q文本例子程序中只宿M个服务,所以服务名的定义是可选的?br> 服务控制理器(SCMQServices Control ManagerQ是一个管理系l所有服务的q程。当 SCM 启动某个服务Ӟ它等待某个进E的ȝE来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这把调用q程的主U程转换为控制分z֙。该分派器启动一个新U程Q该U程q行分派表中每个服务?ServiceMain 函数Q本文例子中只有一个服务)分派器还监视E序中所有服务的执行情况。然后分z֙控制请求从 SCM 传给服务?br>
注意Q如?StartServiceCtrlDispatcher 函数30U没有被调用Q便会报错,Z避免q种情况Q我们必d ServiceMain 函数中(参见本文例子Q或在非d数的单独U程中初始化服务分派表。本文所描述的服务不需要防范这L情况?br>
分派表中所有的服务执行完之后(例如Q用户通过“服务”控制面板E序停止它们Q,或者发生错误时。StartServiceCtrlDispatcher 调用q回。然后主q程l止?br>
W二步:ServiceMain 函数
Listing 1 展示?ServiceMain 的代码。该函数是服务的入口炏V它q行在一个单独的U程当中Q这个线E是由控制分z֙创徏的。ServiceMain 应该可能早早ؓ服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函敎ͼ服务名和指向 ControlHandlerfunction 的指针?br> 它指C控制分z֙调用 ControlHandler 函数处理 SCM 控制h。注册完控制处理器之后,获得状态句柄(hStatusQ。通过调用 SetServiceStatus 函数Q用 hStatus ?SCM 报告服务的状态?br>Listing 1 展示了如何指定服务特征和其当前状态来初始?ServiceStatus l构QServiceStatus l构的每个域都有其用途:
调用 SetServiceStatus 函数?SCM 报告服务的状态时。要提供 hStatus 句柄?ServiceStatus l构。注?ServiceStatus 一个全局变量Q所以你可以跨多个函C用它。ServiceMain 函数中,你给l构的几个域赋|它们在服务运行的整个q程中都保持不变Q比如:dwServiceType?br> 在报告了服务状态之后,你可以调?InitService 函数来完成初始化。这个函数只是添加一个说明性字W串到日志文件。如下面代码所C:
// 服务初始? int InitService() { int result; result = WriteToLog("Monitoring started."); return(result); }
?ServiceMain 中,?InitService 函数的返回倹{如果初始化有错Q因为有可能写日志文件失败)Q则服务状态置为终止ƈ退?ServiceMainQ?/p>
error = InitService(); if (error) { // 初始化失败,l止服务 ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ServiceStatus); // 退?ServiceMain return; }
如果初始化成功,则向 SCM 报告状态:
// ?SCM 报告q行状? ServiceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus (hStatus, &ServiceStatus);
接着Q启动工作@环。每五秒钟查询一个可用物理内存ƈ结果写入日志文件?br>
?Listing 1 所C,循环一直到服务的状态ؓ SERVICE_RUNNING 或日志文件写入出错ؓ止。状态可能在 ControlHandler 函数响应 SCM 控制h时修攏V?br>
W三步:处理控制h
在第二步中,你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各U?Windows 消息的窗口回调函数非常类伹{它?SCM 发送了什么请求ƈ采取相应行动?br> 每次你调?SetServiceStatus 函数的时候,必须指定服务接收 STOP ?SHUTDOWN h?a target=_blank>Listing 2 C了如何在 ControlHandler 函数中处理它们?br> STOP h?SCM l止服务的时候发送的。例如,如果用户?#8220;服务”控制面板中手动终止服务。SHUTDOWN h是关闭机器时Q由 SCM 发送给所有运行中服务的请求。两U情늚处理方式相同Q?/p>
׃ ServiceStatus l构对于整个E序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务l止后停止。其它的控制h如:PAUSE ?CONTINUE 在本文的例子没有处理?br> 控制处理器函数必L告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus?br>
图一 昄 MemoryStatus 服务的服务控刉?br>
W四步:安装和配|服?/strong>
E序~好了,之~译?exe 文g。本文例子创建的文g?MemoryStatus.exeQ将它拷贝到 C:\MyServices 文g夏Vؓ了在机器上安装这个服务,需要用 SC.EXE 可执行文Ӟ它是 Win32 Platform SDK 中附带的一个工兗(译者注QVisaul Studio .NET 2003 IDE 环境中也有这个工P具体存放位置在:C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin\winntQ。用这个实用工具可以安装和U除服务。其它控制操作将通过服务控制面板来完成。以下是用命令行安装 MemoryStatus 服务的方法:
sc create MemoryStatus binpath= c:\MyServices\MemoryStatus.exe
发出此创建命令。指定服务名和二q制文g的\径(注意 binpath= 和\径之间的那个I格Q。安装成功后Q便可以用服务控刉板来控制q个服务Q参见图一Q。用控制面板的工h启动和终止这个服务?br>
图二 MemoryStatus 服务的属性窗?br>
MemoryStatus 的启动类型是手动Q也是说根据需要来启动q个服务。右键单击该服务Q然后选择上下文菜单中?#8220;属?#8221;菜单,此时昄该服务的属性窗口。在q里可以修改启动cd以及其它讄。你q可以从“常规”标签中启?停止服务。以下是从系l中U除服务的方法:
sc delete MemoryStatus
指定 “delete” 选项和服务名。此服务被标记为删除,下次襉K重启后Q该服务被完全U除?br>
W五步:试服务
从服务控刉板启?MemoryStatus 服务。如果初始化不出错,表示启动成功。过一会儿服务停止。检查一?C:\MyServices 文g夹中 memstatus.txt 文g的服务输出。在我的机器上输出是q样的:
Monitoring started. 273469440 273379328 273133568 273084416 Monitoring stopped.
Z试 MemoryStatus 服务在出错情况下的行为,可以?memstatus.txt 文g讄成只诅R这样一来,服务应该无法启动?br> L只读属性,启动服务Q在文件设成只诅R服务将停止执行Q因为此时日志文件写入失败。如果你更新服务控制面板的内容,会发现服务状态是已经停止?br>
开发更大更好的服务E序
理解 Win32 服务的基本概念,使你能更好地?C++ 来设计包装类。包装类隐藏了对底层 Win32 函数的调用ƈ提供了一U舒适的通用接口。修?MemoryStatus E序代码Q创建满己需要的服务Qؓ了实现比本文例子所C的更复杂的Q务,你可以创建多U程的服务,作业划分成几个工作者线Eƈ?ServiceMain 函数中监视它们的执行?br>
l合ACE大略的看?/span>POSA1Q?/span> POSA2?/span>
对设计模式的理解q是很肤, H然x入地学习设计模式Q?/span> Ҏ一U模?/span>
都辅助相关的code来理解, q把设计模式?/span>MFCQ?/span> ACEQ?/span> cppunit{框架联pv来,
更好地理解和应用q些~程的框Ӟq有log4plus{)
今天ȝ一下简单的单g模式Q?/span>
Singleton(单g)——对象创建型模式
保证一个类仅有一个实例,q提供一个访问它的全局讉K炏V?span lang=EN-US>
1·典型Singletonc?/span>Q?span lang=EN-US>
客户仅通过Instance成员函数讉Kq个单g。变?/span>_instance初始化ؓ0Q而静态成员函?/span>Instanceq回该变量倹{注意:构造器是保护型的,保证了仅有一个实例被创徏?span lang=EN-US>
q种方式的实现对于线E来说ƈ不是安全的,因ؓ在多U程的环境下有可能得?/span>Singletoncȝ多个实例。如果同时有两个U程d断(_instance == nullQ,q且得到的结果ؓ真,q时两个U程都会创徏c?/span>Singleton的实例,q样p背了Singleton模式的原则。实际上在上qC码中Q有可能在计出表达式的g前,对象实例已经被创建,但是内存模型q不能保证对象实例在W二个线E创Z前被发现?/span>
该实现方式主要有两个优点Q?#183;׃实例是在 Instance 属性方法内部创建的Q因此类可以使用附加功能Q例如,对子c进行实例化Q,即它可能引入不惌的依赖性?#183; 直到对象要求产生一个实例才执行实例化;q种ҎUCؓ“惰性实例化”。惰性实例化避免了在应用E序启动时实例化不必要的 singleton
2·U程安全?/span>singleton
q种方式的实现对于线E来说是安全的。我们首先创Z一个进E辅助对象,U程在进入时先对辅助对象加锁然后再检对象是否被创徏Q这样可以确保只有一个实例被创徏Q因为在同一个时d了锁的那部分E序只有一个线E可以进入。这U情况下Q对象实例由最先进入的那个U程创徏Q后来的U程在进入时Q?/span>_instence == nullQؓ假,不会再去创徏对象实例了。但是这U实现方式增加了额外的开销Q损׃性能?/span>
3Q双重锁?/span>
q种实现方式对多U程来说是安全的Q同时线E不是每ơ都加锁Q只有判断对象实例没有被创徏时它才加锁,有了我们上面W一部分的里面的分析Q我们知道,加锁后还得再q行对象是否已被创徏的判断。它解决了线Eƈ发问题,同时避免在每?/span> Instance 属性方法的调用中都出现独占锁定。它q允许您实例化延迟到第一ơ访问对象时发生。实际上Q应用程序很需要这U类型的实现。大多数情况下我们会用静态初始化。这U方式仍然有很多~点Q无法实现gq初始化?br>
实现要点
·Singleton模式是限制而不是改q类的创建?/span>
·SingletoncM的实例构造器可以讄?/span>Protected以允许子cL生?/span>
·Singleton模式一般不要支?/span>Icloneable接口Q因可能D多个对象实例Q与Singleton模式的初衯背?/span>
· Singleton模式一般不要支持序列化Q这也有可能D多个对象实例Q这也与Singleton模式的初衯背?/span>
· Singleton只考虑了对象创建的理Q没有考虑到销毁的理Q就支持垃圾回收的^台和对象的开销来讲Q我们一般没必要对其销毁进行特D的理?/span>
·理解和扩?/span>Singleton模式的核心是“如何控制用户使用new对一个类的构造器的Q意调?/span>”?/span>
ȝQ?/span> Singleton设计模式是一个非常有用的机制Q可用于在面向对象的应用E序中提供单个访问点?/span>
应该注意的几?/span>:
1,我们不应该来序列化和反序列化用单件模式实现的对象,否则多次反序列化则可以创建多的实?/span>,q与单g模式是相矛盾?/span>.
2,我们不应该克隆用单g模式实现的对?/span>,否则多次克隆则可以创建多的实?/span>,q与单g模式是相矛盾?/span>.
3,在多U程环境中用单件模式时要小?/span>.
关于ACE中单件模?/span>ACE_Singleton的设计和应用可以参考一博友的文章, 感觉写的很好
http://blog.csdn.net/joise/archive/2006/09/29/1305849.aspx
参考网站:http://www.oonumerics.org/blitz/
Blitz++ 是一个高效率的数D函数库Q它的设计目的是希望建立一套既具像C++ 一h便,同时又比Fortran速度更快的数D环境。通常Q用C++所写出的数值程序,?Fortran?0%左右Q因此Blitz++正是要改掉这个缺炏V方法是利用C++的template技术,E序执行甚至可以比Fortran更快。Blitz++目前仍在发展中,对于常见的SVDQFFTsQQMRES{常见的U性代数方法ƈ不提供,不过使用者可以很Ҏ地利用Blitz++所提供的函数来构徏?
POOMA
POOMA是一个免费的高性能的C++库,用于处理q行式科学计。POOMA的面向对象设计方便了快速的E序开发,对ƈ行机器进行了优化以达到最高的效率Q方便在工业和研I环境中使用?
MTL
Matrix Template Library(MTL)是一个高性能的泛型组件库Q提供了各种格式矩阵的大量线性代数方面的功能。在某些应用使用高性能~译器的情况下,比如Intel的编译器Q从产生的汇~代码可以看出其与手写几乎没有两L效能?
CGAL
参考网站:www.cgal.org
Computational Geometry Algorithms Library的目的是把在计算几何斚w的大部分重要的解x案和Ҏ以C++库的形式提供l工业和学术界的用户?
Thread U程Q当U程l束ӞU程对象卌Ȁ发。当U程q在q行Ӟ则对象处于未Ȁ发状态?/span>
Process q程Q当q程l束Ӟq程对象卌Ȁ发。当q程q在q行Ӟ则对象处于未Ȁ发状态?/span>
Change Notification Q当一个特定的盘子目录中发生一件特别的变化Ӟ此对象即被激发。此对象pȝ FindFirstChangeNotification() 产生
Console Input Q当 console H口的输入缓冲区中有数据可用Ӟ此对象将处于Ȁ发状态?/span> CreateFile Q)?/span> GetStdFile Q)两函数可以获?/span> console handle ?/span>
Event Q?/span> Event 对象的状态直接受控于应用E序所使用的三?/span> Win32 函数Q?/span> SetEvent Q)Q?/span> PulseEvent Q)Q?/span> ResetEvent Q)?/span> CreateEvent Q)?/span> OpenEvent Q)都可以传回一?/span> event object handle ?/span> Event 对象的状态也可以被操作系l设定——如果用于“ overlapped ”操作时?/span>
Mutex Q如?/span> mutex 没有被Q何线E拥有,他就是处于激发状态。一旦一个等?/span> mutex 的函数返回了Q?/span> mutex 也就自动重置为未Ȁ发状态?/span> CreateMutex Q)?/span> OpenMutex Q)都可以获得一?/span> Mutext ?/span> handle ?/span>
Semaphore Q?/span> Semaphore 有点?/span> mutex Q但他有个计数器Q可以约束其拥有者(U程Q的个数。当计数器内容大?/span> 0 Ӟ semaphore 处于Ȁ发状态,当计数器内容{于 0 Ӟ semaphore 处于未激发状态?/span> CreateSemaphore Q)?/span> OpenSemaphore Q)可以传回一?/span> semaphore handle ?/span>
?到N(100000)中Q意拿掉两个数Q把剩下?9998个数序打ؕQƈ且放入数lA中。要求只扫描一遍数l,把这两个数找出来。可以用最C过5个局部变量,不能用数l变量,q且不能改变原数l的倹{?br />
在函数的声明中用数组的引用定义,׃怕数l退化了。如?br /> for_each( int (&int_ref)[10] )
{
for( int i=0; i<10; ++i )
std::cout << int_ref[i] << std::endl;
}
int main( int argc, char* argv[] )
{
int int_array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
for_each( int_array );
return 0;
}
在上面的代码中,如果你传入不?0个尺寸的数组Q是~译通不q的。代码的安全性提高了。 ?
惌定义一个数l引用类型,Ҏ如下
typedef cd?(&数组引用cd?[N];
实例
typedef int (&Array_Ref)[10];
Array_Ref是一个数l的引用cd了?br />
我们需要做的是通过我们自己~写的应用程序去拦截别h写好的应用程序消息,实际上这是在两个q程之间q行的,隑ֺ在q里Q如果是同一个进E什么都好办Q只要将pȝ响应WINDOWS消息的处理函C改ؓ我们自己~写的函数就可以Q但现在不能q么做,因ؓ两个q程有各自的q程地址I间Q理Z你没有办法直接去讉K别的q程的地址I间Q那么怎么办来Q办法还是很多的Q这里仅仅介l通过HOOK来达到目的?/p>
需要拦截别的应用程序的消息Q需要利用将自己~写的DLL注入到别人的DLL地址I间中才可以辑ֈ拦截别h消息的目的。只有将我们的DLL插入到别的应用程序的地址I间中才能够对别的应用程序进行操作,HOOK帮助我们完成了这些工作,我们只需要用HOOK来拦截指定的消息Qƈ提供必要的处理函数就行了。我们这里介l拦截在MSN聊天对话框上的鼠标消息,对应的HOOKcd是WH_MOUSE?/p>
首先我们要徏立一个用来HOOK的DLL。这个DLL的徏立和普通的DLL建立没有什么具体的区别Q不q我们这里提供的Ҏ有写不同。这里用隐式导入DLL的方法。代码如下:
头文?/p>
#pragma once
#ifndef MSNHOOK_API
#define MSNHOOK_API __declspec(dllimport)
#endif
MSNHOOK_API BOOL WINAPI SetMsnHook(DWORD dwThreadId);//安装MSN钩子函数
MSNHOOK_API void WINAPI GetText(int &x,int &y,char ** ptext);//安装MSN钩子函数
MSNHOOK_API HWND WINAPI GetMyHwnd();//安装MSN钩子函数
==================================================
DLL 的CPP文g
#include "stdafx.h"
#include "MSNHook.h"
#include <stdio.h>
// 下面几句的含义是告诉~译器将各变量放入它自己的数据共享节?/p>
#pragma data_seg("Shared")
HHOOK g_hhook = NULL;
DWORD g_dwThreadIdMsn = 0;
POINT MouseLoc={0,0};
char text[256]={0};
HWND g_Hwnd = NULL;
#pragma data_seg()
//告诉~译器设|共享节的访问方式ؓQ读Q写Q共?/p>
#pragma comment(linker,"/section:Shared,rws")
HINSTANCE g_hinstDll = NULL;
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hinstDll = (HINSTANCE)hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT WINAPI GetMsgProc(int nCode,WPARAM wParam, LPARAM lParam);
BOOL WINAPI SetMsnHook(DWORD dwThreadId)
{
OutputDebugString("SetMsnHook");
BOOL fOK = FALSE;
if(dwThreadId != 0)
{
OutputDebugString("SetMsnHook dwThreadId != 0");
g_dwThreadIdMsn = GetCurrentThreadId();
//安装WM_MOUSE钩子和处理函数GetMsgProc
g_hhook = SetWindowsHookEx(WH_MOUSE,GetMsgProc,g_hinstDll,dwThreadId);
fOK = (g_hhook != NULL);
if(fOK)
{
fOK = PostThreadMessage(dwThreadId,WM_NULL,0,0);
}
else
{
fOK = UnhookWindowsHookEx(g_hhook);
g_hhook = NULL;
}
}
return(fOK);
}
LRESULT WINAPI GetMsgProc(int nCode,WPARAM wParam, LPARAM lParam)
{
char temp[20];
sprintf(temp,"%dn",nCode);
OutputDebugString("temp");
if (nCode==HC_ACTION)
{
MOUSEHOOKSTRUCT *l=(MOUSEHOOKSTRUCT *)lParam;
MouseLoc=l->pt; //送鼠标位|?br />
//char text[256] = "";
HWND hWnd = WindowFromPoint(l->pt);
if(hWnd)
{
//GetWindowText(hWnd,text,256);
SendMessage(hWnd,WM_GETTEXT,256,(LPARAM)(LPCTSTR)text);
// strcpy(text,"123455555");
SendMessage(hWnd,WM_SETTEXT,256,(LPARAM)(LPCTSTR)text);
g_Hwnd = hWnd;
}
//SendMessage(WindowFromPoint(l->pt),WM_GETTEXT,256,(LPARAM)(LPCTSTR)psw);
}
return(CallNextHookEx(g_hhook,nCode,wParam,lParam));
}
void WINAPI GetText(int &x,int &y,char ** ptext)
{
x = MouseLoc.x;
y = MouseLoc.y;
*ptext = text;
}
HWND WINAPI GetMyHwnd()
{
return g_Hwnd;
}
上面是处理钩子的DLL代码Q下面我们要让这个DLL起作用还需要一个启动部分,通过q个启动部分我们才能让我们的钩子函数真正的注入到pȝ其他函数中。我们这里用个对话框的E序Q程序非常简单:一个按钮用来启动钩子,一个用来停止,一个TIMER用来h昄Q还有一个EDITBOX用来接受信息?/p>
E序如下Q?/p>
//包含DLL函数导出的头文g
#include "MSNHook.h"
//隐式导入
#pragma comment(lib,"MSNHook.lib")
//声明导入函数
__declspec(dllimport) BOOL WINAPI SetMsnHook(DWORD dwThreadId);
__declspec(dllimport) void WINAPI GetText(int &x,int &y,char ** ptext);
__declspec(dllimport) HWND WINAPI GetMyHwnd();//安装MSN钩子函数
void CTestMSNHookDlg::OnBnClickedOk()
{
//通过SPY++可以看到MSN聊天对话框窗口类是IMWindowClassQ通过q个得到该窗口句?br />CWnd *pMsnWin = FindWindow(TEXT("IMWindowClass"),NULL);
if(pMsnWin == NULL) return ;
//通过H口句柄得到对应的线E的ID
SetMsnHook(GetWindowThreadProcessId(pMsnWin->GetSafeHwnd(),NULL));
MSG msg;
GetMessage(&msg,NULL,0,0);
SetTimer(101,100,NULL);
}
void CTestMSNHookDlg::OnTimer(UINT_PTR nIDEvent)
{
//h消息
char * pText = NULL;
int x = 0,y = 0;
GetText(x,y,&pText);
if(x ==0 && y ==0) return ;
m_Edit.Format("%d:%d:%s",x,y,pText);
//m_Edit = pText;
UpdateData(FALSE);
HWND hWnd = GetMyHwnd();
CWnd * pWnd = CWnd::FromHandle(hWnd);
pWnd->GetWindowText(m_Edit);
CDialog::OnTimer(nIDEvent);
}
void CTestMSNHookDlg::OnBnClickedButton1()
{
//关闭
KillTimer(101);
SetMsnHook(0);
OnCancel();
}
好了Q基本上p些了。这里有个问题,我本惛_到MSN用户聊天时输入的聊天信息Q这里通过WM_GETTEXT消息的不刎ͼ如果有知道的朋友告诉一声?/p>