分享
 
 
 

实例解析C++/CLI之代理与事件

王朝c/c++·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

在C++/CLI中,代理是对函数进行包装的对象;而事件是一种为客户程序提供通知的类机制。

在前几篇文章中,已经多次演示了假如让一个句柄在不同的时间,被引用至不同的对象,从而以更抽象的方法来解决程序中的问题,但是,也能使用代理通过函数来达到同样的效果;代理是包装了函数的一个对象,且对实例函数而言,也能通过特定的实例,与这些函数发生联系。一旦一个代理包装了一个或多个函数,你就能通过代理来调用这些函数,而无须事先了解包装了哪些函数。

请看例1中的代码,在标号1中,定义一个代理类型Del,由于使用了上下文要害字delegate,所以有点像函数的声明,但与函数声明不同的是,此处声明的是一个代理类型Del的实例,其可包装进任意接受一个int类型作为参数并返回一个int值类型的函数(任意有效的参数列表及返回类型组合都是答应的)。一旦定义了某种代理类型,它只能被用于包装具有同样类型的函数;代理类型可被定义在源文件中或命名空间的范围内,也能定义在类中,并可有public或private访问控制属性。

例1:

using namespace System;

ref strUCt A

{

static int Square(int i)

{

return i * i;

}

};

ref struct B

{

int Cube(int i)

{

return i * i * i;

}

};

/*1*/

delegate int Del(int value);

int main()

{

/*2*/ Del^ d = gcnew Del(&A::Square);

/*3*/ Console::WriteLine("d(10) result = {0}", d(10));

/*4*/ B^ b = gcnew B;

/*5*/ d = gcnew Del(b, &B::Cube);

/*6*/ Console::WriteLine("d(10) result = {0}", d(10));

}

静态函数A::Square与实例函数B::Cube对Del来说,都具有相同的参数类型及返回类型,因此它们能被包装进同类型的代理中。注重,即使两个函数均为public,当考虑它们与Del的兼容性时,它们的可访问性也是不相关的,这样的函数也能被定义在相同或不同的类中,主要由程序员来选择。

一旦定义了某种代理类型,就可创建此类型实例的句柄,并进行初始化或赋值操作,如标号2中所示的静态函数A::Square,及标号5中所示的实例函数B::Cube。(此处只是出于演示的目的,否则把Cube做成实例函数没有任何好处。)

创建一个代理实例涉及到调用一个构造函数,假如是在包装一个静态函数,只需传递进一个指向成员函数的指针;而对实例函数而言,必须传递两个参数:一个实例的句柄及指向实例成员函数的指针。

在初始化代理实例之后,就能间接地调用它们包装的函数了,用法与直接调用原函数一样,只不过现在用的是代理实例名,如标号3与6,由包装函数返回的值也是像直接调用函数时那样获得。假如一个代理实例的值为nullptr,此时再试图调用被包装的函数,会导致System::NullReferenceException类型异常。

以下是输出:

d(10) result = 100

d(10) result = 1000

传递与返回代理

有时,把包装好的函数传递给另一个函数,会非常有用,接受一方的函数并不知道会传递过来哪个函数,并且它也无须关心,只需简单地通过包装好的代理,间接调用此函数就行了。

下面以集合中元素排序来说明,大多数时候,集合中元素排序所依据的规则,只在对某对元素进行比较的方法上存在区别。假如在运行时提供进行比较的函数,一个排序过程就能用相应定义的比较函数排出任意的顺序,请看例2。

例2:

using namespace System;

ref struct StrCompare

{

static int CompareExact(String^ s1, String^ s2)

{

Console::WriteLine("Comparing {0} and {1} ""using CompareExact", s1, s2);

// ...

return 0;

}

static int CompareIgnoreCase(String^ s1, String^ s2)

{

Console::WriteLine("Comparing {0} and {1}" "using CompareIgnoreCase", s1, s2);

// ...

return 0;

}

};

delegate int Compare(String^ s1, String^ s2);

/*1*/

Compare^ FindComparisonMethod()

{

// ...

}

void Sort(Compare^ compare)

{

int result;

/*3*/ result = compare("Hello", "Hello");

/*4*/ result = compare("Hello", "HELLO");

/*5*/ result = compare("Hello", "Hell");

}

int main()

{

/*6*/ Sort(gcnew Compare(&StrCompare::CompareIgnoreCase));

/*7*/ Sort(FindComparisonMethod());

/*8*/ FindComparisonMethod()("Red", "RED");

}

Compare代理类型可对任意接受两个String^参数并返回一个int结果的函数进行包装,在此,有两个函数为StrCompare::CompareExact和StrCompare::CompareIgnoreCase。

在标号6中,创建了一个Compare代理类型的实例,用它来包装StrCompare::CompareIgnoreCase,并把此代理句柄传递给Sort函数,其将会利用比较函数进一步进行处理。

正如大家所看到的,Sort可接受一个代理类型的参数--而此参数可像其他函数参数一样,可为传值、传址、传引用。

在标号7中,调用了FindComparisonMethod函数,其返回一个Del代理类型,接着在标号7及8中调用了包装过的函数。此处要重点说一下标号8:首先,FindComparisonMethod函数是被调用来获取代理实例--其常用于调用底层函数;其次,这两个函数的调用操作符都有同等的优先级,所以它们从左至右调用。

FindComparisonMethod函数中也用了一些逻辑用于确定到底需要包装哪个函数,此处就未作具体说明了。

代理类型的兼容性

一个代理类型只与它自身相兼容,与其他任何代理类型都不兼容,即使其他类型的包装函数均为同一类型。请看例3,非常明显,代理类型D1与函数A::M1与A::M2兼容,代理类型D2也与这些函数兼容,然而,这两个代理类型在标号5、6、8、9中并不能互换使用。

例3:

delegate void D1();

delegate void D2();

public struct A

{

static void M1() { /* ... */ }

static void M2() { /* ... */ }

};

void X(D1^ m) { /* ... */ }

void Y(D2^ n) { /* ... */ }

int main()

{

D1^ d1;

/*1*/ d1 = gcnew D1(&A::M1); //兼容

/*2*/ d1 = gcnew D1(&A::M2); //兼容

D2^ d2;

/*3*/ d2 = gcnew D2(&A::M1); //兼容

/*4*/ d2 = gcnew D2(&A::M2); //兼容

/*5*/ d1 = d2; //不兼容

/*6*/ d2 = d1; //不兼容

/*7*/ X(d1); //兼容

/*8*/ X(d2); //不兼容

/*9*/ Y(d1); //不兼容

/*10*/ Y(d2); //兼容

}

代理类型的合并

一个代理实例实际上能包装多个函数,在这种情况下,被包装的函数集被维护在一个调用列表中,当合并两个代理实例时,它们的调用列表也以指定的顺序连接起来,并产生一个新的列表,而现有的两个列表并没有发生改变。当从调用列表中移除一个或多个函数时,也会产生一个新的列表,且原始列表不会发生变化。请看例4中的代码,每个函数调用后的输出都写在相应函数后。

例4:

using namespace System;

delegate void D(int x);

ref struct Actions

{

static void F1(int i)

{

Console::WriteLine("Actions::F1: {0}", i);

}

static void F2(int i)

{

Console::WriteLine("Actions::F2: {0}", i);

}

void F3(int i)

{

Console::WriteLine("instance of Actions::F3: {0}", i);

}

};

int main()

{

/*1*/ D^ cd1 = gcnew D(&Actions::F1); //包含F1的调用列表

cd1(10);

Actions::F1: 10

/*2*/ D^ cd2 = gcnew D(&Actions::F2); //包含F2的调用列表

cd2(15);

Actions::F2: 15

/*3*/ D^ cd3 = cd1 + cd2; //包含F1 + F2的调用列表

cd3(20);

Actions::F1: 20

Actions::F2: 20

/*4*/ cd3 += cd1; //包含F1 + F2 + F1的调用列表

cd3(25);

Actions::F1: 25

Actions::F2: 25

Actions::F1: 25

Actions^ t = gcnew Actions();

D^ cd4 = gcnew D(t, &Actions::F3);

/*5*/ cd3 += cd4; //包含F1 + F2 + F1 + t->F3的调用列表

cd3(30);

Actions::F1: 30

Actions::F2: 30

Actions::F1: 30

instance of Actions::F3: 30

/*6*/ cd3 -= cd1; //移除最右边的F1

cd3(35); //调用F1、F2,t->F3

Actions::F1: 35

Actions::F2: 35

instance of Actions::F3: 35

/*7*/ cd3 -= cd4; //移除t->F3

cd3(40); //调用F1、F2

/*8*/ cd3 -= cd1; //移除F1

cd3(45); //调用F2

/*9*/ cd3 -= cd2; //移除F2,调用列表现在为空

/*10*/Console::WriteLine("cd3 = {0}",

(cd3 == nullptr ? "null" : "not null"));

}

Actions::F1: 40

Actions::F2: 40

Actions::F2: 45

cd3 = null

代理可通过 + 和 += 操作符来合并,如标号3、4中所示。两个单入口列表会连接成一个新的双入口列表,以先左操作数,后右操作数的顺序。新的列表被cd3引用,而现有的两个列表并未改变。在此要注重的是,不能合并不同类型的代理。

正如在标号4中所见,同一个函数可在一个调用列表中包装多次;而在标号5中,也说明了一个调用列表能同时包含类与实例函数。代理可通过 - 或 -= 操作符移除,如标号6中所示。

当同一个函数在调用列表中出现多次时,一个对它的移除请求会导致最右边的项被移除。在标号6中,这产生了一个新的三入口列表,其被cd3引用,且前一个列表保持不变(因为先前被cd3引用的列表现在引用计数为零,所以会被垃圾回收)。

当一个调用列表中的最后一项被移除时,代理将为nullptr值,此处没有空调用列表的概念,因为,根本就没有列表了。

例5中演示了另一个代理合并与移除的例子,正如标号3a与3b中所示,两个多入口调用列表是以先左操作数,后右操作数的顺序连接的。

假如想移除一个多入口列表,只有当此列表为整个列表中严格连续的子集时,操作才会成功。例如,在标号4b中,你可以移除F1和F2,因为它们是相邻的,对标号5b中的两个F2及标号6b中的F1、F2来说,道理也是一样的。但是,在标号7b中,列表中有两个连续的F1,所以操作失败,而结果列表则是最开始的列表,它包含有4个入口。

例5:

using namespace System;

delegate void D(int x);

void F1(int i) { Console::WriteLine("F1: {0}", i); }

void F2(int i) { Console::WriteLine("F2: {0}", i); }

int main()

{

D^ cd1 = gcnew D(&F1);

D^ cd2 = gcnew D(&F2);

/*1*/ D^ list1 = cd1 + cd2; // F1 + F2

/*2*/ D^ list2 = cd2 + cd1; // F2 + F1

D^ cd3 = nullptr;

/*3a*/ cd3 = list2 + list1; // F2 + F1 + F1 + F2

cd3(10);

/*3b*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1

cd3(20);

/*4a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1

/*4b*/ cd3 -= cd1 + cd2; // F2 + F1

cd3(30);

/*5a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1

/*5b*/ cd3 -= cd2 + cd2; // F1 + F1

cd3(40);

/*6a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1

/*6b*/ cd3 -= cd2 + cd1; // F1 + F2

cd3(50);

/*7a*/ cd3 = list1 + list2; // F1 + F2 + F2 + F1

/*7b*/ cd3 -= cd1 + cd1; // F1 + F2 + F2 + F1

cd3(60);

}

System::Delegate

代理类型的定义,会隐式地创建一个对应的类(class)类型,并且所有的代理类型均从类库System::Delegate继续而来。要定义一个这样的类,唯一的方法就是通过delegate上下文要害字。代理类为隐式的sealed,因此它们不能被用作基类。另外,一个非代理类也不能从System::Delegate继续。

例6演示了几个Delegate函数的用法:

例6:

using namespace System;

delegate void D(int x);

ref class Test

{

String^ objName;

public:

Test(String^ objName)

{

this->objName = objName;

}

void M(int i)

{

Console::WriteLine("Object {0}: {1}", objName, i);

}

};

void ProcessList(D^ del, int value, Object^ objToExclude);

int main()

{

/*1*/ Test^ t1 = gcnew Test("t1");

D^ cd1 = gcnew D(t1, &Test::M);

/*2*/ Test^ t2 = gcnew Test("t2");

D^ cd2 = gcnew D(t2, &Test::M);

/*3*/ Test^ t3 = gcnew Test("t3");

D^ cd3 = gcnew D(t3, &Test::M);

/*4*/ D^ list = cd1 + cd2 + cd3 + cd2;

/*5a*/ ProcessList(list, 100, nullptr);

/*5b*/ ProcessList(list, 200, t1);

/*5c*/ ProcessList(list, 300, t2);

/*6a*/ D^ cd4 = cd1 + cd2;

/*6b*/ D^ cd5 = (D^)cd4->Clone();

/*6c*/ ProcessList(cd4, 5, nullptr);

/*6d*/ ProcessList(cd5, 6, nullptr);

}

void ProcessList(D^ del, int value, Object^ objToExclude)

{

/*7*/ if (del == nullptr)

{

return;

}

/*8*/ else if (objToExclude == nullptr)

{

del(value);

}

else

{

/*9*/ array<Delegate^>^ delegateList = del->GetInvocationList();

for each (Delegate^ d in delegateList)

{

/*10*/ if (d->Target != objToExclude)

{

/*11*/ ((D^)d)(value);

}

}

}

}

实例函数Test::M与代理类型D相兼容,当调用时,这个函数只是识别出它调用的对象,并带有一个整数参数。

在标号1、2、3中,定义了三个Test类型的对象,并把它们各自与实例函数Test:M包装在单独的代理类型D中。接着,在标号4中,创建了一个四入口的调用列表。

倘若传递进来的调用列表不为空,ProcessList函数将调用在列表中除了特定对象以外的所有函数,例如,在标号5a中,没有排除任何入口,因此所有的函数都会被调用;在标号5b中,t1被排除在外,而标号5c中,与对象t2有关的两个入口都被排除了,结果输出如下:

Object t1: 100

Object t2: 100

Object t3: 100

Object t2: 100

Object t2: 200

Object t3: 200

Object t2: 200

Object t1: 300

Object t3: 300

在标号6b中,调用了Clone创建了代理cd4的一个副本,这个函数返回一个Object^,因此,要把它转换成D^类型。当原始及克隆的代理在标号6c、6d中调用时,结果输出如下:

Object t1: 5

Object t2: 5

Object t1: 6

Object t2: 6

关于函数ProcessList,假如参数中的代理实例为nullptr,即没有调用列表,那它将直接返回;假如排除的对象为nullptr,那么列表中所有的函数都将被调用;假如存在要排除的对象,就要像标号8中那样把调用列表当作代理数组取出,接着,在标号9中逐个排查不相符的入口,最后,在标号10中调用余下的这些函数。尽管在调用列表中每个入口都是Del类型,但GetInvocationList返回一个基类Delegate数组,所以在调用每个代理实例之前,需像标号10那样先转换成类型D。

事件

在C++/CLI中,事件是一种当某种重要事情发生时,为客户程序提供通知的机制。鼠标单击就是事件的一个典型例子,在事件发生之前,有关的客户程序必须先注册它们感爱好的事件,如,当检测到鼠标单击时,这些程序就会接到通知。

通过添加或删除一个或多个感爱好的事件,事件列表可在运行时增长或缩减,请看例7中Server类型的定义,在标号1中,Server类定义了代理类型NewMsgEventHandler(一般约定在用于事件处理时,代理类型添加EventHandler的后缀名),接着,在标号2中,定义了一个名为ProcessNewMsg的公共事件(event在此为一个上下文要害字)。一个事件必须有一个代理类型,实际上,像这样的一个事件已经是一个代理实例了,而且因为它被默认初始化为nullptr,所以它没有调用列表。

例7:

using namespace System;

public ref struct Server

{

/*1*/ delegate void NewMsgEventHandler(String^ msg);

/*2*/ static event NewMsgEventHandler^ ProcessNewMsg;

/*3*/ static void Broadcast(String^ msg)

{

if (ProcessNewMsg != nullptr)

{

ProcessNewMsg(msg);

}

}

};

当通过一条消息调用时,函数Broadcast将调用包装在ProcessNewMsg调用列表中所有的函数。

Client类定义在例8中,一个Client的类型实例无论何时被创建,它都会通过向为Server::ProcessNewMsg维护的代理列表中添加一个实例函数(它关联到实例变量),来注册它所感爱好的新Server消息,而这是通过 += 操作符来完成,如标号5中所示。只要这个入口一直保持在通知列表中,无论何时一个新消息送达Server,注册的函数都会被调用。

例8:

using namespace System;

public ref class Client

{

String^ clientName;

/*4*/ void ProcessNewMsg(String^ msg)

{

Console::WriteLine("Client {0} received message {1}", clientName, msg);

}

public:

Client(String^ clientName)

{

this->clientName = clientName;

/*5*/ Server::ProcessNewMsg += gcnew Server::NewMsgEventHandler(this, &Client::ProcessNewMsg);

}

/*6*/ ~Client()

{

Server::ProcessNewMsg -= gcnew Server::NewMsgEventHandler(this, &Client::ProcessNewMsg);

}

};

要从通知列表中移除一个入口,可使用 -= 操作符,如标号6定义的析构函数中那样。

例9:

using namespace System;

int main()

{

Server::Broadcast("Message 1");

Client^ c1 = gcnew Client("A");

Server::Broadcast("Message 2");

Client^ c2 = gcnew Client("B");

Server::Broadcast("Message 3");

Client^ c3 = gcnew Client("C");

Server::Broadcast("Message 4");

c1->~Client();

Server::Broadcast("Message 5");

c2->~Client();

Server::Broadcast("Message 6");

c3->~Client();

Server::Broadcast("Message 7");

}

例9是主程序,一开始,没有注册任何函数,所以当发送第一个消息时,不会获得任何通知。然而,一旦构造了c1,通知列表就包含了此对象的一个入口,而接下来c2与c3的构造使这个列表增长到3个入口。在这些对象消失时(通过显式调用析构函数),入口数也相应地减少了,直到最后,一个也不剩,因此当最后一条消息发出时,没有任何对象在监听。以下是输出:

Client A received message Message 2

Client A received message Message 3

Client B received message Message 3

Client A received message Message 4

Client B received message Message 4

Client C received message Message 4

Client B received message Message 5

Client C received message Message 5

Client C received message Message 6

尽管3个对象均为同一类型,但这并不是必须的,只要定义的函数可与NewMsgEventHandler兼容,就能使用任意的类型。

上述例子中使用的事件只不过是微不足道的一个示例,另外要说明一点,与以前文章中说过的属性一样,此种类型的事件均以private属性自动备份,且自动生成添加(add)与移除(remove)存取程序,为自定义这些存取程序,就必须提供这些函数的定义,如例10中所示,名称add与remove在此为上下文要害字。

例10:

public ref struct Server

{

// ...

static event NewMsgEventHandler^ ProcessNewMsg {

void add(NewMsgEventHandler^ n) { /* ... */ }

void remove(NewMsgEventHandler^ n) { /* ... */ }

}

// ...

};

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有