c++仿C# event
来源:互联网 发布:关键字怎么优化 编辑:程序博客网 时间:2024/04/30 05:02
本文章为farproc.com站长袁晓辉(海洋)的原创文章,作者保留所有权利。如果转载,请务必注明来自www.farproc.com!!
作为一个C++程序员,学习C#应该不是一件很困难的事情,因为C#的许多特性都是从C++“继承”(不精确的说法)来的。但是C#作为一门全新的编程语言,它必然有自己的新特性,而这些C++中并不存在的新特性正是我们从C++转向C#的过程中必须认真重新学习的东西。“事件(event)”是C#的一个比较简单的新特性,我们今天就从“事件”开始,看一看C#的事件到底是怎么回事。
C#的事件和Windows窗口编程中提到的“事件”、“消息”、“事件驱动”等在概念上是很类似的。我们在一个窗口上移动鼠标指针,系统就会产生WM_MOUSEMOVE消息(在vb中就是激发mousemove事件),只要我们告诉系统一个函数指针,系统就会通过这个回调函数通知我们,这是Windows窗口编程中的“事件”。C#中的事件的外延更广,任何一个对象都可以拥有事件,客户可以“定制”该对象的事件,当该对象的内部状态发生特定的改变时,就会通过定制事件时指定的函数代理(delegate)调用这个函数通知客户。当客户不在需要事件通知时,可以“撤消”对该事件的定制。
Now is the show time!模仿秀现在开始!
一、C#版
我们先来看一个C#中一个“事件”的最简单例子:
在VS.NET中新建一个C#的Console Application,项目名称为“eventtest”。为该项目新加一个类MyClass,对应的源文件为MyClass.cs代码如下:
using System;
namespace eventtest
{
//定义EventHandler函数代理
public delegate void EventHandler();
/// <summary>
///
/// </summary>
public class MyClass
{
//构造函数
public MyClass(){}
//声明一个事件
public event EventHandler AEvent;
//激发事件
public void FireEvent()
{
if(AEvent != null)
{
//直接把event当做函数调用
AEvent();
}
}
}
}
public event EventHandler AEvent;就是给MyClass定义了一个事件(通过event关键字),其事件处理函数(通知函数)的原型由EventHandler函数代理(类似C++中的函数指针)指定。
FireEvent()成员函数用于激发该事件,如果客户定制了该event,当本类对象的FireEvent()公开方法被调用时,客户应该可以得到通知。在Main函数里写如下代码:
using System;
namespace eventtest
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void
{
MyClass Obj = new MyClass();
Obj.AEvent += new EventHandler(MyEventHandler);//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= new EventHandler(MyEventHandler);//撤消事件
Obj.FireEvent();//这里将不会引发事件
Console.WriteLine("结束!");
Console.ReadLine();
}
//事件处理函数
public static void MyEventHandler()
{
Console.WriteLine("This is a event!");
}
}
}
首先写一个具有适当形式的事件处理(通知)函数 MyEventHandler,然后通过Obj.AEvent += new EventHandler(MyEventHandler)来定制事件。通过“-=”来撤消事件定制。
运行程序我们可以发现,当客户(Class1)定制了Obj的AEvent事件后,在Obj的FireEvent()成员函数被调用时,客户可以在MyEventHandler函数中得到通知(在这里只是简单地输出一个文本)。而当客户撤消该事件的定制后,就不会再得到该事件通知。
二、C++版
下面我们在C++中模拟该机制:
由于C++不支持event关键字,我们就必须自己写代码。在这里我通过模板类的手段来实现,因为该手段实现的效果和C#比较类似。
在VC6中新建一个win32 console app,命名为“cppevent“。新建一个.h头文件,命名为“event.h”,代码如下:
//event.h
template <typename Handler>
class event
{
private:
Handler m_Handler;
protected:
//模拟C# event 的add/remove访问器
//如果要重新实现add/remove请在派生类中重写这两个函数
virtual void add(const Handler value){m_Handler = value;};
virtual void remove(const Handler value){if(value == m_Handler)m_Handler = NULL;};
public:
//构造函数
event():m_Handler(NULL){}
//+= 操作符
event& operator += (const Handler value)
{
add(value);
return *this;
}
//-=操作符
event& operator -= (const Handler value)
{
remove(value);
return *this;
}
//PFN_EVENT_HANDLE 操作符
operator Handler()
{
return m_Handler;
}
};
为了能够在定义是指定事件处理函数的原型,我使用了template,为了能和C#一样用+=和-=来定制和撤消事件,我重载了这两个操作符(C#不支持操作符重载),为了能像C#一样直接把event当做函数调用,我有重载了Handler自定义转换操作符,可惜的是,这一点模拟得不是很像,在调用时还必须来一次强制转换才可以:(,具体参看后面的代码:
C++版的MyClass如下:
//MyClass.h
#include "event.h"
//定义EventHandler的函数指针类型
typedef void(*EventHandler)();
class MyClass
{
public:
//构造函数
MyClass(){};
//声明一个事件
event<EventHandler> AEvent;
//激发事件
void FireEvent()
{
if(AEvent != NULL)
{
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)();
};
}
};
和C#版的MyClass比较一下你就会发现代码非常接近,当然,C#是在语言级直接支持event关键字的,而C++不支持,用模板类代替,所以声明事件的代码有些不一样。还有就是FireEvent()中C++不能把event对象直接当做函数来调用,多了强制类型转换。
C++版的客户代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
//向前声明
void MyEventHandler();
int main(int argc, char* argv[])
{
MyClass Obj;
Obj.AEvent += MyEventHandler;//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= MyEventHandler;//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
void MyEventHandler()
{
printf("This is a event!/n");
}
我们可以看到,可C#版的客户代码相比,核心部分是非常接近的,我们已经可以和C#一样用“+=”和“-=”来定制事件和撤消事件定制,并在Obj的FireEvent()被调用时收到事件通知,输出文本。
鉴于篇幅的原因,我们没有仔细比较两个版本的event的add和remove访问器/成员函数,其实二者也是非常类似的,你可以自己试试。C++版的event的add和remove均为virtual的,你可以从event类继承出来一个MyEvent类,然后重新实现这两个函数,就可以定制自己的add和remove了。这和C#的add/remove访问器的也是非常相像的。
三、总结
通过这场“模仿show”我们可以从更深的层次理解C#的event机制,更重要的是我们用自己所熟悉的东西(C++,模板类)来模仿并解释了我们目前还不太熟悉的东西(C#,event)。
其实,C#的delegate就是C++的函数指针,C# event的核心机制就是C++中的模板(定义event时表现出来)和运算符重载(+=、-=和直接把event当做函数调用)的结合体。C#把C++中容易出错的部分用“新特性”封装了起来,把这部分工作从programmer身上转移到了compiler身上,让我们把更多的精力集中到业务逻辑的处理上。
<完>
袁晓辉
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=444284
- c++仿C# event
- C++:event
- [C#-1] C#中event的关键字存在的意义
- C# 自定义EVENT
- event handling in c#
- C#事件(event)解析
- C#事件(event)解析
- C#事件(event)解析
- C# Event--Anonymous Methods
- C#事件(event)解析
- C#事件(event)解析
- C#中的Event
- C#事件(event)解析
- C#事件(event)解析
- C#事件(event)解析
- C#中的Event
- C#事件(event)解析
- C#事件(event)解析
- 行路问题
- 请各位大侠进来帮忙 安全问题 欲跳楼
- 我的第一次出差
- 用C++访问DBF文件的方法
- SQL 语法参考手册(SQL)
- c++仿C# event
- CBase.cs
- ASP.NET程序中常用的三十三种代码
- 圣诞快乐
- Pattern-Oriented Software Architecture
- (Win)Upack 0.39final by dwing
- 求矩阵的最大路径的问题
- WinCE实验教程之二集成开发环境
- Apex的ORIGlN版本 规格
我曾尝试过,C++中成员函数指针不能和void*相互强转,而且typedef中不能含有模板,所以使用(成员)函数指针不能解决这个问题。可以看看我的C++ event的实现:http://blog.csdn.net/zhuweisky/archive/2005/07/01/409474.aspx
大家多多交流一下!
另外,你的实现我大概看了一下,不错!!
----------------------------------------
就用成员函数的指针也可以实现。
我有一个最简单的: http://blog.csdn.net/loveghb/archive/2005/04/25/362524.aspx
---
1、为VC6的cppevent工程添加一个Thunk.h,代码如下:
//Thunk.h
#ifndef AUX_THUNK_H
#define AUX_THUNK_H
#pragma pack(push, 1)
template<typename T>
class CAuxThunk
{
private:
BYTE m_mov;
DWORD m_this;
BYTE m_jmp;
int m_relproc;
public:
typedef void(T::*TMFP)();
//构造函数
CAuxThunk():m_mov(0), m_this(0), m_jmp(0), m_relproc(0){};
CAuxThunk(TMFP method, const T* pThis)
{
CreateThunk(method, pThis);
};
//创建Thunk
void CreateThunk(TMFP method, const T* pThis)
{
m_mov = 0xB9;
m_this = (DWORD)pThis;
m_jmp = 0xE9;
void* p = &method;
m_relproc = *((int*)&method) - int(this + 1);
FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
};
//取得用于回调函数指针
FARPROC GetThunk()const
{return (FARPROC)this;}
//取得回调函数指针的LONG形式
operator LONG()
{return (LONG)this;};
};
#pragma pack(pop)
#endif
通过这个CAuxThunk模板类我们就可以把类的成员函数指针作为CALLBACK函数使用了。
---
2、将MyClass.h中EventHandler的原型定义改为:
//定义EventHandler的函数指针类型
typedef void(CALLBACK *EventHandler)(int n);
注意,由于这个CAuxThunk只能把成员函数转换为CALLBACK调用方式的函数指针,所以这里加了CALLBACK关键字。(可以通过修改Thunk.h的代码从而不用在这里添加CALLBACK,只是我的汇编不好,不会:( )
为了验证参数传递,特地加了一个参数int n,所以需要修改FireEvent中的激发事件的代码,也添加一个参数:
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)(10);
---
3、修改cppevent.cpp中的代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
#include "Thunk.h"
class Class1
{
public:
int m_nTemp;
Class1():m_nTemp(0){};
//这个成员函数将被作为event的处理函数
void MyEventHandler(int n)
{
m_nTemp = n;
printf("This is a event! m_nTemp = %d/n", m_nTemp);
}
};
typedef CAuxThunk<Class1> Class1Thunk;
int main(int argc, char* argv[])
{
//声明一个Class1的实例O,并把O.MyEventHandler创建为一个Thunk
Class1 O;
Class1Thunk MyEvnetHandlerThunk((Class1Thunk::TMFP)O.MyEventHandler, &O);
MyClass Obj;
Obj.AEvent += (EventHandler)MyEvnetHandlerThunk.GetThunk();//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= (EventHandler)MyEvnetHandlerThunk.GetThunk();//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
我们运行发现,当Obj.FireEvent()被调用时,对象O的成员函数MyEventHandler被调用,并将接收到的参数10也打印了出来。
注意:如果无法编译请在stdafx.h中添加#include<wtypes.h>
本文章为farproc.com站长袁晓辉(海洋)的原创文章,作者保留所有权利。如果转载,请务必注明来自www.farproc.com!!
作为一个C++程序员,学习C#应该不是一件很困难的事情,因为C#的许多特性都是从C++“继承”(不精确的说法)来的。但是C#作为一门全新的编程语言,它必然有自己的新特性,而这些C++中并不存在的新特性正是我们从C++转向C#的过程中必须认真重新学习的东西。“事件(event)”是C#的一个比较简单的新特性,我们今天就从“事件”开始,看一看C#的事件到底是怎么回事。
C#的事件和Windows窗口编程中提到的“事件”、“消息”、“事件驱动”等在概念上是很类似的。我们在一个窗口上移动鼠标指针,系统就会产生WM_MOUSEMOVE消息(在vb中就是激发mousemove事件),只要我们告诉系统一个函数指针,系统就会通过这个回调函数通知我们,这是Windows窗口编程中的“事件”。C#中的事件的外延更广,任何一个对象都可以拥有事件,客户可以“定制”该对象的事件,当该对象的内部状态发生特定的改变时,就会通过定制事件时指定的函数代理(delegate)调用这个函数通知客户。当客户不在需要事件通知时,可以“撤消”对该事件的定制。
Now is the show time!模仿秀现在开始!
一、C#版
我们先来看一个C#中一个“事件”的最简单例子:
在VS.NET中新建一个C#的Console Application,项目名称为“eventtest”。为该项目新加一个类MyClass,对应的源文件为MyClass.cs代码如下:
using System;
namespace eventtest
{
//定义EventHandler函数代理
public delegate void EventHandler();
/// <summary>
///
/// </summary>
public class MyClass
{
//构造函数
public MyClass(){}
//声明一个事件
public event EventHandler AEvent;
//激发事件
public void FireEvent()
{
if(AEvent != null)
{
//直接把event当做函数调用
AEvent();
}
}
}
}
public event EventHandler AEvent;就是给MyClass定义了一个事件(通过event关键字),其事件处理函数(通知函数)的原型由EventHandler函数代理(类似C++中的函数指针)指定。
FireEvent()成员函数用于激发该事件,如果客户定制了该event,当本类对象的FireEvent()公开方法被调用时,客户应该可以得到通知。在Main函数里写如下代码:
using System;
namespace eventtest
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static voidMain (string[] args)
{
MyClass Obj = new MyClass();
Obj.AEvent += new EventHandler(MyEventHandler);//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= new EventHandler(MyEventHandler);//撤消事件
Obj.FireEvent();//这里将不会引发事件
Console.WriteLine("结束!");
Console.ReadLine();
}
//事件处理函数
public static void MyEventHandler()
{
Console.WriteLine("This is a event!");
}
}
}
首先写一个具有适当形式的事件处理(通知)函数 MyEventHandler,然后通过Obj.AEvent += new EventHandler(MyEventHandler)来定制事件。通过“-=”来撤消事件定制。
运行程序我们可以发现,当客户(Class1)定制了Obj的AEvent事件后,在Obj的FireEvent()成员函数被调用时,客户可以在MyEventHandler函数中得到通知(在这里只是简单地输出一个文本)。而当客户撤消该事件的定制后,就不会再得到该事件通知。
二、C++版
下面我们在C++中模拟该机制:
由于C++不支持event关键字,我们就必须自己写代码。在这里我通过模板类的手段来实现,因为该手段实现的效果和C#比较类似。
在VC6中新建一个win32 console app,命名为“cppevent“。新建一个.h头文件,命名为“event.h”,代码如下:
//event.h
template <typename Handler>
class event
{
private:
Handler m_Handler;
protected:
//模拟C# event 的add/remove访问器
//如果要重新实现add/remove请在派生类中重写这两个函数
virtual void add(const Handler value){m_Handler = value;};
virtual void remove(const Handler value){if(value == m_Handler)m_Handler = NULL;};
public:
//构造函数
event():m_Handler(NULL){}
//+= 操作符
event& operator += (const Handler value)
{
add(value);
return *this;
}
//-=操作符
event& operator -= (const Handler value)
{
remove(value);
return *this;
}
//PFN_EVENT_HANDLE 操作符
operator Handler()
{
return m_Handler;
}
};
为了能够在定义是指定事件处理函数的原型,我使用了template,为了能和C#一样用+=和-=来定制和撤消事件,我重载了这两个操作符(C#不支持操作符重载),为了能像C#一样直接把event当做函数调用,我有重载了Handler自定义转换操作符,可惜的是,这一点模拟得不是很像,在调用时还必须来一次强制转换才可以:(,具体参看后面的代码:
C++版的MyClass如下:
//MyClass.h
#include "event.h"
//定义EventHandler的函数指针类型
typedef void(*EventHandler)();
class MyClass
{
public:
//构造函数
MyClass(){};
//声明一个事件
event<EventHandler> AEvent;
//激发事件
void FireEvent()
{
if(AEvent != NULL)
{
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)();
};
}
};
和C#版的MyClass比较一下你就会发现代码非常接近,当然,C#是在语言级直接支持event关键字的,而C++不支持,用模板类代替,所以声明事件的代码有些不一样。还有就是FireEvent()中C++不能把event对象直接当做函数来调用,多了强制类型转换。
C++版的客户代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
//向前声明
void MyEventHandler();
int main(int argc, char* argv[])
{
MyClass Obj;
Obj.AEvent += MyEventHandler;//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= MyEventHandler;//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
void MyEventHandler()
{
printf("This is a event!/n");
}
我们可以看到,可C#版的客户代码相比,核心部分是非常接近的,我们已经可以和C#一样用“+=”和“-=”来定制事件和撤消事件定制,并在Obj的FireEvent()被调用时收到事件通知,输出文本。
鉴于篇幅的原因,我们没有仔细比较两个版本的event的add和remove访问器/成员函数,其实二者也是非常类似的,你可以自己试试。C++版的event的add和remove均为virtual的,你可以从event类继承出来一个MyEvent类,然后重新实现这两个函数,就可以定制自己的add和remove了。这和C#的add/remove访问器的也是非常相像的。
三、总结
通过这场“模仿show”我们可以从更深的层次理解C#的event机制,更重要的是我们用自己所熟悉的东西(C++,模板类)来模仿并解释了我们目前还不太熟悉的东西(C#,event)。
其实,C#的delegate就是C++的函数指针,C# event的核心机制就是C++中的模板(定义event时表现出来)和运算符重载(+=、-=和直接把event当做函数调用)的结合体。C#把C++中容易出错的部分用“新特性”封装了起来,把这部分工作从programmer身上转移到了compiler身上,让我们把更多的精力集中到业务逻辑的处理上。
<完>
袁晓辉
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=444284
我曾尝试过,C++中成员函数指针不能和void*相互强转,而且typedef中不能含有模板,所以使用(成员)函数指针不能解决这个问题。可以看看我的C++ event的实现:http://blog.csdn.net/zhuweisky/archive/2005/07/01/409474.aspx
大家多多交流一下!
另外,你的实现我大概看了一下,不错!!
----------------------------------------
就用成员函数的指针也可以实现。
我有一个最简单的: http://blog.csdn.net/loveghb/archive/2005/04/25/362524.aspx
---
1、为VC6的cppevent工程添加一个Thunk.h,代码如下:
//Thunk.h
#ifndef AUX_THUNK_H
#define AUX_THUNK_H
#pragma pack(push, 1)
template<typename T>
class CAuxThunk
{
private:
BYTE m_mov;
DWORD m_this;
BYTE m_jmp;
int m_relproc;
public:
typedef void(T::*TMFP)();
//构造函数
CAuxThunk():m_mov(0), m_this(0), m_jmp(0), m_relproc(0){};
CAuxThunk(TMFP method, const T* pThis)
{
CreateThunk(method, pThis);
};
//创建Thunk
void CreateThunk(TMFP method, const T* pThis)
{
m_mov = 0xB9;
m_this = (DWORD)pThis;
m_jmp = 0xE9;
void* p = &method;
m_relproc = *((int*)&method) - int(this + 1);
FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
};
//取得用于回调函数指针
FARPROC GetThunk()const
{return (FARPROC)this;}
//取得回调函数指针的LONG形式
operator LONG()
{return (LONG)this;};
};
#pragma pack(pop)
#endif
通过这个CAuxThunk模板类我们就可以把类的成员函数指针作为CALLBACK函数使用了。
---
2、将MyClass.h中EventHandler的原型定义改为:
//定义EventHandler的函数指针类型
typedef void(CALLBACK *EventHandler)(int n);
注意,由于这个CAuxThunk只能把成员函数转换为CALLBACK调用方式的函数指针,所以这里加了CALLBACK关键字。(可以通过修改Thunk.h的代码从而不用在这里添加CALLBACK,只是我的汇编不好,不会:( )
为了验证参数传递,特地加了一个参数int n,所以需要修改FireEvent中的激发事件的代码,也添加一个参数:
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)(10);
---
3、修改cppevent.cpp中的代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
#include "Thunk.h"
class Class1
{
public:
int m_nTemp;
Class1():m_nTemp(0){};
//这个成员函数将被作为event的处理函数
void MyEventHandler(int n)
{
m_nTemp = n;
printf("This is a event! m_nTemp = %d/n", m_nTemp);
}
};
typedef CAuxThunk<Class1> Class1Thunk;
int main(int argc, char* argv[])
{
//声明一个Class1的实例O,并把O.MyEventHandler创建为一个Thunk
Class1 O;
Class1Thunk MyEvnetHandlerThunk((Class1Thunk::TMFP)O.MyEventHandler, &O);
MyClass Obj;
Obj.AEvent += (EventHandler)MyEvnetHandlerThunk.GetThunk();//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= (EventHandler)MyEvnetHandlerThunk.GetThunk();//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
我们运行发现,当Obj.FireEvent()被调用时,对象O的成员函数MyEventHandler被调用,并将接收到的参数10也打印了出来。
注意:如果无法编译请在stdafx.h中添加#include<wtypes.h>
本文章为farproc.com站长袁晓辉(海洋)的原创文章,作者保留所有权利。如果转载,请务必注明来自www.farproc.com!!
作为一个C++程序员,学习C#应该不是一件很困难的事情,因为C#的许多特性都是从C++“继承”(不精确的说法)来的。但是C#作为一门全新的编程语言,它必然有自己的新特性,而这些C++中并不存在的新特性正是我们从C++转向C#的过程中必须认真重新学习的东西。“事件(event)”是C#的一个比较简单的新特性,我们今天就从“事件”开始,看一看C#的事件到底是怎么回事。
C#的事件和Windows窗口编程中提到的“事件”、“消息”、“事件驱动”等在概念上是很类似的。我们在一个窗口上移动鼠标指针,系统就会产生WM_MOUSEMOVE消息(在vb中就是激发mousemove事件),只要我们告诉系统一个函数指针,系统就会通过这个回调函数通知我们,这是Windows窗口编程中的“事件”。C#中的事件的外延更广,任何一个对象都可以拥有事件,客户可以“定制”该对象的事件,当该对象的内部状态发生特定的改变时,就会通过定制事件时指定的函数代理(delegate)调用这个函数通知客户。当客户不在需要事件通知时,可以“撤消”对该事件的定制。
Now is the show time!模仿秀现在开始!
一、C#版
我们先来看一个C#中一个“事件”的最简单例子:
在VS.NET中新建一个C#的Console Application,项目名称为“eventtest”。为该项目新加一个类MyClass,对应的源文件为MyClass.cs代码如下:
using System;
namespace eventtest
{
//定义EventHandler函数代理
public delegate void EventHandler();
/// <summary>
///
/// </summary>
public class MyClass
{
//构造函数
public MyClass(){}
//声明一个事件
public event EventHandler AEvent;
//激发事件
public void FireEvent()
{
if(AEvent != null)
{
//直接把event当做函数调用
AEvent();
}
}
}
}
public event EventHandler AEvent;就是给MyClass定义了一个事件(通过event关键字),其事件处理函数(通知函数)的原型由EventHandler函数代理(类似C++中的函数指针)指定。
FireEvent()成员函数用于激发该事件,如果客户定制了该event,当本类对象的FireEvent()公开方法被调用时,客户应该可以得到通知。在Main函数里写如下代码:
using System;
namespace eventtest
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static voidMain (string[] args)
{
MyClass Obj = new MyClass();
Obj.AEvent += new EventHandler(MyEventHandler);//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= new EventHandler(MyEventHandler);//撤消事件
Obj.FireEvent();//这里将不会引发事件
Console.WriteLine("结束!");
Console.ReadLine();
}
//事件处理函数
public static void MyEventHandler()
{
Console.WriteLine("This is a event!");
}
}
}
首先写一个具有适当形式的事件处理(通知)函数 MyEventHandler,然后通过Obj.AEvent += new EventHandler(MyEventHandler)来定制事件。通过“-=”来撤消事件定制。
运行程序我们可以发现,当客户(Class1)定制了Obj的AEvent事件后,在Obj的FireEvent()成员函数被调用时,客户可以在MyEventHandler函数中得到通知(在这里只是简单地输出一个文本)。而当客户撤消该事件的定制后,就不会再得到该事件通知。
二、C++版
下面我们在C++中模拟该机制:
由于C++不支持event关键字,我们就必须自己写代码。在这里我通过模板类的手段来实现,因为该手段实现的效果和C#比较类似。
在VC6中新建一个win32 console app,命名为“cppevent“。新建一个.h头文件,命名为“event.h”,代码如下:
//event.h
template <typename Handler>
class event
{
private:
Handler m_Handler;
protected:
//模拟C# event 的add/remove访问器
//如果要重新实现add/remove请在派生类中重写这两个函数
virtual void add(const Handler value){m_Handler = value;};
virtual void remove(const Handler value){if(value == m_Handler)m_Handler = NULL;};
public:
//构造函数
event():m_Handler(NULL){}
//+= 操作符
event& operator += (const Handler value)
{
add(value);
return *this;
}
//-=操作符
event& operator -= (const Handler value)
{
remove(value);
return *this;
}
//PFN_EVENT_HANDLE 操作符
operator Handler()
{
return m_Handler;
}
};
为了能够在定义是指定事件处理函数的原型,我使用了template,为了能和C#一样用+=和-=来定制和撤消事件,我重载了这两个操作符(C#不支持操作符重载),为了能像C#一样直接把event当做函数调用,我有重载了Handler自定义转换操作符,可惜的是,这一点模拟得不是很像,在调用时还必须来一次强制转换才可以:(,具体参看后面的代码:
C++版的MyClass如下:
//MyClass.h
#include "event.h"
//定义EventHandler的函数指针类型
typedef void(*EventHandler)();
class MyClass
{
public:
//构造函数
MyClass(){};
//声明一个事件
event<EventHandler> AEvent;
//激发事件
void FireEvent()
{
if(AEvent != NULL)
{
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)();
};
}
};
和C#版的MyClass比较一下你就会发现代码非常接近,当然,C#是在语言级直接支持event关键字的,而C++不支持,用模板类代替,所以声明事件的代码有些不一样。还有就是FireEvent()中C++不能把event对象直接当做函数来调用,多了强制类型转换。
C++版的客户代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
//向前声明
void MyEventHandler();
int main(int argc, char* argv[])
{
MyClass Obj;
Obj.AEvent += MyEventHandler;//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= MyEventHandler;//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
void MyEventHandler()
{
printf("This is a event!/n");
}
我们可以看到,可C#版的客户代码相比,核心部分是非常接近的,我们已经可以和C#一样用“+=”和“-=”来定制事件和撤消事件定制,并在Obj的FireEvent()被调用时收到事件通知,输出文本。
鉴于篇幅的原因,我们没有仔细比较两个版本的event的add和remove访问器/成员函数,其实二者也是非常类似的,你可以自己试试。C++版的event的add和remove均为virtual的,你可以从event类继承出来一个MyEvent类,然后重新实现这两个函数,就可以定制自己的add和remove了。这和C#的add/remove访问器的也是非常相像的。
三、总结
通过这场“模仿show”我们可以从更深的层次理解C#的event机制,更重要的是我们用自己所熟悉的东西(C++,模板类)来模仿并解释了我们目前还不太熟悉的东西(C#,event)。
其实,C#的delegate就是C++的函数指针,C# event的核心机制就是C++中的模板(定义event时表现出来)和运算符重载(+=、-=和直接把event当做函数调用)的结合体。C#把C++中容易出错的部分用“新特性”封装了起来,把这部分工作从programmer身上转移到了compiler身上,让我们把更多的精力集中到业务逻辑的处理上。
<完>
袁晓辉
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=444284
我曾尝试过,C++中成员函数指针不能和void*相互强转,而且typedef中不能含有模板,所以使用(成员)函数指针不能解决这个问题。可以看看我的C++ event的实现:http://blog.csdn.net/zhuweisky/archive/2005/07/01/409474.aspx
大家多多交流一下!
另外,你的实现我大概看了一下,不错!!
----------------------------------------
就用成员函数的指针也可以实现。
我有一个最简单的: http://blog.csdn.net/loveghb/archive/2005/04/25/362524.aspx
---
1、为VC6的cppevent工程添加一个Thunk.h,代码如下:
//Thunk.h
#ifndef AUX_THUNK_H
#define AUX_THUNK_H
#pragma pack(push, 1)
template<typename T>
class CAuxThunk
{
private:
BYTE m_mov;
DWORD m_this;
BYTE m_jmp;
int m_relproc;
public:
typedef void(T::*TMFP)();
//构造函数
CAuxThunk():m_mov(0), m_this(0), m_jmp(0), m_relproc(0){};
CAuxThunk(TMFP method, const T* pThis)
{
CreateThunk(method, pThis);
};
//创建Thunk
void CreateThunk(TMFP method, const T* pThis)
{
m_mov = 0xB9;
m_this = (DWORD)pThis;
m_jmp = 0xE9;
void* p = &method;
m_relproc = *((int*)&method) - int(this + 1);
FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
};
//取得用于回调函数指针
FARPROC GetThunk()const
{return (FARPROC)this;}
//取得回调函数指针的LONG形式
operator LONG()
{return (LONG)this;};
};
#pragma pack(pop)
#endif
通过这个CAuxThunk模板类我们就可以把类的成员函数指针作为CALLBACK函数使用了。
---
2、将MyClass.h中EventHandler的原型定义改为:
//定义EventHandler的函数指针类型
typedef void(CALLBACK *EventHandler)(int n);
注意,由于这个CAuxThunk只能把成员函数转换为CALLBACK调用方式的函数指针,所以这里加了CALLBACK关键字。(可以通过修改Thunk.h的代码从而不用在这里添加CALLBACK,只是我的汇编不好,不会:( )
为了验证参数传递,特地加了一个参数int n,所以需要修改FireEvent中的激发事件的代码,也添加一个参数:
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)(10);
---
3、修改cppevent.cpp中的代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
#include "Thunk.h"
class Class1
{
public:
int m_nTemp;
Class1():m_nTemp(0){};
//这个成员函数将被作为event的处理函数
void MyEventHandler(int n)
{
m_nTemp = n;
printf("This is a event! m_nTemp = %d/n", m_nTemp);
}
};
typedef CAuxThunk<Class1> Class1Thunk;
int main(int argc, char* argv[])
{
//声明一个Class1的实例O,并把O.MyEventHandler创建为一个Thunk
Class1 O;
Class1Thunk MyEvnetHandlerThunk((Class1Thunk::TMFP)O.MyEventHandler, &O);
MyClass Obj;
Obj.AEvent += (EventHandler)MyEvnetHandlerThunk.GetThunk();//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= (EventHandler)MyEvnetHandlerThunk.GetThunk();//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
我们运行发现,当Obj.FireEvent()被调用时,对象O的成员函数MyEventHandler被调用,并将接收到的参数10也打印了出来。
注意:如果无法编译请在stdafx.h中添加#include<wtypes.h>
本文章为farproc.com站长袁晓辉(海洋)的原创文章,作者保留所有权利。如果转载,请务必注明来自www.farproc.com!!
作为一个C++程序员,学习C#应该不是一件很困难的事情,因为C#的许多特性都是从C++“继承”(不精确的说法)来的。但是C#作为一门全新的编程语言,它必然有自己的新特性,而这些C++中并不存在的新特性正是我们从C++转向C#的过程中必须认真重新学习的东西。“事件(event)”是C#的一个比较简单的新特性,我们今天就从“事件”开始,看一看C#的事件到底是怎么回事。
C#的事件和Windows窗口编程中提到的“事件”、“消息”、“事件驱动”等在概念上是很类似的。我们在一个窗口上移动鼠标指针,系统就会产生WM_MOUSEMOVE消息(在vb中就是激发mousemove事件),只要我们告诉系统一个函数指针,系统就会通过这个回调函数通知我们,这是Windows窗口编程中的“事件”。C#中的事件的外延更广,任何一个对象都可以拥有事件,客户可以“定制”该对象的事件,当该对象的内部状态发生特定的改变时,就会通过定制事件时指定的函数代理(delegate)调用这个函数通知客户。当客户不在需要事件通知时,可以“撤消”对该事件的定制。
Now is the show time!模仿秀现在开始!
一、C#版
我们先来看一个C#中一个“事件”的最简单例子:
在VS.NET中新建一个C#的Console Application,项目名称为“eventtest”。为该项目新加一个类MyClass,对应的源文件为MyClass.cs代码如下:
using System;
namespace eventtest
{
//定义EventHandler函数代理
public delegate void EventHandler();
/// <summary>
///
/// </summary>
public class MyClass
{
//构造函数
public MyClass(){}
//声明一个事件
public event EventHandler AEvent;
//激发事件
public void FireEvent()
{
if(AEvent != null)
{
//直接把event当做函数调用
AEvent();
}
}
}
}
public event EventHandler AEvent;就是给MyClass定义了一个事件(通过event关键字),其事件处理函数(通知函数)的原型由EventHandler函数代理(类似C++中的函数指针)指定。
FireEvent()成员函数用于激发该事件,如果客户定制了该event,当本类对象的FireEvent()公开方法被调用时,客户应该可以得到通知。在Main函数里写如下代码:
using System;
namespace eventtest
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static voidMain (string[] args)
{
MyClass Obj = new MyClass();
Obj.AEvent += new EventHandler(MyEventHandler);//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= new EventHandler(MyEventHandler);//撤消事件
Obj.FireEvent();//这里将不会引发事件
Console.WriteLine("结束!");
Console.ReadLine();
}
//事件处理函数
public static void MyEventHandler()
{
Console.WriteLine("This is a event!");
}
}
}
首先写一个具有适当形式的事件处理(通知)函数 MyEventHandler,然后通过Obj.AEvent += new EventHandler(MyEventHandler)来定制事件。通过“-=”来撤消事件定制。
运行程序我们可以发现,当客户(Class1)定制了Obj的AEvent事件后,在Obj的FireEvent()成员函数被调用时,客户可以在MyEventHandler函数中得到通知(在这里只是简单地输出一个文本)。而当客户撤消该事件的定制后,就不会再得到该事件通知。
二、C++版
下面我们在C++中模拟该机制:
由于C++不支持event关键字,我们就必须自己写代码。在这里我通过模板类的手段来实现,因为该手段实现的效果和C#比较类似。
在VC6中新建一个win32 console app,命名为“cppevent“。新建一个.h头文件,命名为“event.h”,代码如下:
//event.h
template <typename Handler>
class event
{
private:
Handler m_Handler;
protected:
//模拟C# event 的add/remove访问器
//如果要重新实现add/remove请在派生类中重写这两个函数
virtual void add(const Handler value){m_Handler = value;};
virtual void remove(const Handler value){if(value == m_Handler)m_Handler = NULL;};
public:
//构造函数
event():m_Handler(NULL){}
//+= 操作符
event& operator += (const Handler value)
{
add(value);
return *this;
}
//-=操作符
event& operator -= (const Handler value)
{
remove(value);
return *this;
}
//PFN_EVENT_HANDLE 操作符
operator Handler()
{
return m_Handler;
}
};
为了能够在定义是指定事件处理函数的原型,我使用了template,为了能和C#一样用+=和-=来定制和撤消事件,我重载了这两个操作符(C#不支持操作符重载),为了能像C#一样直接把event当做函数调用,我有重载了Handler自定义转换操作符,可惜的是,这一点模拟得不是很像,在调用时还必须来一次强制转换才可以:(,具体参看后面的代码:
C++版的MyClass如下:
//MyClass.h
#include "event.h"
//定义EventHandler的函数指针类型
typedef void(*EventHandler)();
class MyClass
{
public:
//构造函数
MyClass(){};
//声明一个事件
event<EventHandler> AEvent;
//激发事件
void FireEvent()
{
if(AEvent != NULL)
{
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)();
};
}
};
和C#版的MyClass比较一下你就会发现代码非常接近,当然,C#是在语言级直接支持event关键字的,而C++不支持,用模板类代替,所以声明事件的代码有些不一样。还有就是FireEvent()中C++不能把event对象直接当做函数来调用,多了强制类型转换。
C++版的客户代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
//向前声明
void MyEventHandler();
int main(int argc, char* argv[])
{
MyClass Obj;
Obj.AEvent += MyEventHandler;//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= MyEventHandler;//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
void MyEventHandler()
{
printf("This is a event!/n");
}
我们可以看到,可C#版的客户代码相比,核心部分是非常接近的,我们已经可以和C#一样用“+=”和“-=”来定制事件和撤消事件定制,并在Obj的FireEvent()被调用时收到事件通知,输出文本。
鉴于篇幅的原因,我们没有仔细比较两个版本的event的add和remove访问器/成员函数,其实二者也是非常类似的,你可以自己试试。C++版的event的add和remove均为virtual的,你可以从event类继承出来一个MyEvent类,然后重新实现这两个函数,就可以定制自己的add和remove了。这和C#的add/remove访问器的也是非常相像的。
三、总结
通过这场“模仿show”我们可以从更深的层次理解C#的event机制,更重要的是我们用自己所熟悉的东西(C++,模板类)来模仿并解释了我们目前还不太熟悉的东西(C#,event)。
其实,C#的delegate就是C++的函数指针,C# event的核心机制就是C++中的模板(定义event时表现出来)和运算符重载(+=、-=和直接把event当做函数调用)的结合体。C#把C++中容易出错的部分用“新特性”封装了起来,把这部分工作从programmer身上转移到了compiler身上,让我们把更多的精力集中到业务逻辑的处理上。
<完>
袁晓辉
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=444284
我曾尝试过,C++中成员函数指针不能和void*相互强转,而且typedef中不能含有模板,所以使用(成员)函数指针不能解决这个问题。可以看看我的C++ event的实现:http://blog.csdn.net/zhuweisky/archive/2005/07/01/409474.aspx
大家多多交流一下!
另外,你的实现我大概看了一下,不错!!
----------------------------------------
就用成员函数的指针也可以实现。
我有一个最简单的: http://blog.csdn.net/loveghb/archive/2005/04/25/362524.aspx
---
1、为VC6的cppevent工程添加一个Thunk.h,代码如下:
//Thunk.h
#ifndef AUX_THUNK_H
#define AUX_THUNK_H
#pragma pack(push, 1)
template<typename T>
class CAuxThunk
{
private:
BYTE m_mov;
DWORD m_this;
BYTE m_jmp;
int m_relproc;
public:
typedef void(T::*TMFP)();
//构造函数
CAuxThunk():m_mov(0), m_this(0), m_jmp(0), m_relproc(0){};
CAuxThunk(TMFP method, const T* pThis)
{
CreateThunk(method, pThis);
};
//创建Thunk
void CreateThunk(TMFP method, const T* pThis)
{
m_mov = 0xB9;
m_this = (DWORD)pThis;
m_jmp = 0xE9;
void* p = &method;
m_relproc = *((int*)&method) - int(this + 1);
FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
};
//取得用于回调函数指针
FARPROC GetThunk()const
{return (FARPROC)this;}
//取得回调函数指针的LONG形式
operator LONG()
{return (LONG)this;};
};
#pragma pack(pop)
#endif
通过这个CAuxThunk模板类我们就可以把类的成员函数指针作为CALLBACK函数使用了。
---
2、将MyClass.h中EventHandler的原型定义改为:
//定义EventHandler的函数指针类型
typedef void(CALLBACK *EventHandler)(int n);
注意,由于这个CAuxThunk只能把成员函数转换为CALLBACK调用方式的函数指针,所以这里加了CALLBACK关键字。(可以通过修改Thunk.h的代码从而不用在这里添加CALLBACK,只是我的汇编不好,不会:( )
为了验证参数传递,特地加了一个参数int n,所以需要修改FireEvent中的激发事件的代码,也添加一个参数:
//C++中必须用EventHandler进行强制类型转换
((EventHandler)AEvent)(10);
---
3、修改cppevent.cpp中的代码如下:
// cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
#include "Thunk.h"
class Class1
{
public:
int m_nTemp;
Class1():m_nTemp(0){};
//这个成员函数将被作为event的处理函数
void MyEventHandler(int n)
{
m_nTemp = n;
printf("This is a event! m_nTemp = %d/n", m_nTemp);
}
};
typedef CAuxThunk<Class1> Class1Thunk;
int main(int argc, char* argv[])
{
//声明一个Class1的实例O,并把O.MyEventHandler创建为一个Thunk
Class1 O;
Class1Thunk MyEvnetHandlerThunk((Class1Thunk::TMFP)O.MyEventHandler, &O);
MyClass Obj;
Obj.AEvent += (EventHandler)MyEvnetHandlerThunk.GetThunk();//定制事件
Obj.FireEvent();//这行将导致MyEventHandler被调用
Obj.AEvent -= (EventHandler)MyEvnetHandlerThunk.GetThunk();//撤消事件
Obj.FireEvent();//这个将不会引发事件
printf("结束!/n");
char n;
scanf("%c", &n);
return 0;
}
我们运行发现,当Obj.FireEvent()被调用时,对象O的成员函数MyEventHandler被调用,并将接收到的参数10也打印了出来。
注意:如果无法编译请在stdafx.h中添加#include<wtypes.h>