C++CLI--5委托、事件、反射和混合非托管代码

C++CLI--5委托、事件、反射和混合非托管代码

委托

C++中是有函数指针的,例如:long (*pf)(int,int),声明了一个函数指针,要求获取两个int并返回一个long的任意函数。在C#中是有委托概念的,其实原理就是将函数的执行委托给一个中间对象,和C++中的函数指针功能类似,但更强大(例如多播委托)。其使用与C#中使用委托基本相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//定义委托
delegate double NumbericOP(double);

ref class OPs
{
public:
static double Square(double d)
{
return d * d;
}

double Circle(double r)
{
return Math::PI * r * r;
}
};
int main(array<System::String^>^ args)
{
//委托赋予静态方法
NumbericOP^ op = gcnew NumbericOP(&OPs::Square);
double area = op->Invoke(3);
double area1 = op(5);
//委托赋予非静态方法
OPs^ ops = gcnew OPs();
NumbericOP^ op1 = gcnew NumbericOP(ops,&OPs::Circle);
double area2 = op1(3);
//多播委托
NumbericOP^ op2;
op2 += op1;
op2 += op;
double area3= op2(2);
}

事件

事件是基于委托的,只不过事件的触发必须在定义事件的类中,在外面只能调用+=和-=进行操作。其使用和C#中基本相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
delegate void MyEventHandler(String^);

ref class EvtSrc
{
public:
event MyEventHandler^ OnMyEvent;
void Raise(String^ msg)
{
OnMyEvent(msg);
}
};

ref class EvtRcv
{
public:
EvtRcv(EvtSrc^ s)
{
if (s == nullptr)
{
throw gcnew ArgumentNullException("句柄无效");
}
src = s;
src->OnMyEvent += gcnew MyEventHandler(this,&EvtRcv::EventMethod);
}
void EventMethod(String^ msg)
{
Console::WriteLine(msg);
}
private:
EvtSrc^ src;

};

int main(array<System::String^>^ args)
{

EvtSrc^ src = gcnew EvtSrc();
EvtRcv^ rcv = gcnew EvtRcv(src);

src->Raise("调用啦");

Console::WriteLine("程序结束");
}

image-20240322161104921

反射

在C#中,反射是经常使用的功能之一,在C++/CLI中也支持反射,使用方法和C#使用反射的方法基本一致。

混合非托管代码

使用C++/CLI一般都是作为非托管代码和托管代码的中介,所以混合非托管代码在实际开发中经常用到。

混合类

1
2
3
4
ref class ManagedClass
{
UnManagedClas* puc;//使用*而不是^说明这是非托管代码
}

不能直接这样做

1
2
3
4
ref class ManagedClass
{
UnManagedClas puc;//错误
}

并且,不能在非托管代码中直接使用托管代码,因为在标准C++中无法识别^,也没有gc

1
2
3
4
class UnManagedClass
{
ManagedClas^ puc;//错误
}

GCHandle类型

GCHandle类型可以实现托管类型作为非托管类型的一部分使用。使用静态GCHandle::Alloc方法创建句柄,使用句柄的Free方法释放它。将托管对象的指针传给非托管代码的步骤如下:

  1. 创建一个GCHandle对象来引用你的对象,GCHandle可以和整数互换。
  2. 将GCHandle传给非托管代码
  3. 非托管对象在不需你的对象时,调用Free释放对象

官方提供了gcroot辅助模板类,以避免亲自和Alloc和free打交道。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "gcroot.h"
using namespace System;
using namespace System::Runtime::InteropServices;

ref class MyClass
{
public:
int val;
MyClass(int n) :val(n) {}

};

class UClass
{
public:
//mc是一个gcroot变量,里面包装了引用MyClass句柄的GCHandle,gcroot对象创建时自动创建GCHandle,销毁时自动释放GCHandle
gcroot<MyClass^> mc;
UClass(gcroot<MyClass^>pmc) :mc(pmc) {}
int getValue()
{
return mc->val;
}
};

int main(array<System::String^>^ args)
{

MyClass^ pm = gcnew MyClass(3);
UClass uc(pm); //一旦uc离开作用域,则gcroot会被销毁,释放GCHandle,进而释放托管对象
int v= uc.getValue();
Console::WriteLine(v);
Console::WriteLine("程序结束");
}

固定

因为有了gc的存在,非托管对象引用托管对象会出现错误,因为托管对象会发生移动,对象内部的成员也会跟着移动。如果要将托管对象的指针传给非托管参数,则需要将托管对象的指针进行固定。可根据需要固定部分或者整个托管对象的指针,但是如果固定了成员指针则整个对象也会被固定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//非托管函数
void someFun(int* o)
{
int n = *o;
}
int main(array<System::String^>^ args)
{
array<int>^ arr1 = gcnew array<int>(3);
//创建固定指针
pin_ptr<int> pin = &arr1[0];
someFun(pin);//固定指针能隐式转为int*
pin = nullptr;//释放对象,让指针可以自由移动
Console::WriteLine("程序结束");
}

拆装箱

和C#一样,装箱就是将值类型转成引用类型,拆箱就是将引用类型转为值类型。

C++CLI--5委托、事件、反射和混合非托管代码

https://bubuweiying.site/C-CLI-5委托、事件、反射和混合非托管代码/

作者

步步为营

发布于

2024-03-22

更新于

2025-03-15

许可协议