作者:Changjiang 来源:CSDN博客 酷勤网收集 2007-12-10
Visual C++ 2005(Visual C++ 8.0)中的C++支持C++/CLI,C++/CLI同时支持C++语言特性和.NET编程语言特性,可以实现.NET CLI不支持而C++支持的语言特性,例如多重继承等,还支持混合Assembly,可以在同一Assembly中混合使用CIL代码和本机代码。
C++/CLI的功能要比C#强的多,更适合作为分析.NET Framework内核的编程语言,笔者试编写了一个C++/CLI程序,试图通过反汇编编译生成的.NET Assembly可执行文件,分析C++/CLI中类的本质。
笔者编写的这个程序是一个试验性的复数和实数计算程序,下面是C++主程序(TypesSample1_CPP.cpp):
// TypesSample1_CPP.cpp: 主项目文件。
#include "stdafx.h"
using namespace System;
//Complex 类,.NET CTS值类型。
value class Complex
{
public:
double Re;
double Im;
Complex(double Re,double Im);
};
Complex::Complex(double Re,double Im)
{
this->Re=Re;
this->Im=Im;
}
//ComplexMath 类型,.NET CTS引用类型。
ref class ComplexMath
{
public:
Complex Add(Complex OpNum1,Complex OpNum2);
Complex Minus(Complex OpNum1,Complex OpNum2);
};
Complex ComplexMath::Add(Complex OpNum1,Complex OpNum2)
{
Complex Result1;
Result1.Re=OpNum1.Re+OpNum2.Re;
Result1.Im=OpNum1.Im+OpNum2.Im;
return Result1;
}
Complex ComplexMath::Minus(Complex OpNum1,Complex OpNum2)
{
Complex Result1;
Result1.Re=OpNum1.Re-OpNum2.Re;
Result1.Im=OpNum1.Im-OpNum2.Im;
return Result1;
}
//DisplayComplex类,C++ 类。
class DisplayComplex
{
public:
void Display(Complex C);
};
void DisplayComplex::Display(Complex C)
{
String ^ReStr,^ImStr;
ReStr=C.Re.ToString();
ImStr=C.Im.ToString();
Console::WriteLine(ReStr+"+"+ImStr+"i");
}
//RealMath类,C++ 类,编译成本机代码。
#pragma unmanaged
class RealMath
{
public:
double Add(double OpNum1,double OpNum2);
double Minus(double OpNum1,double OpNum2);
};
double RealMath::Add(double OpNum1,double OpNum2)
{
return OpNum1+OpNum2;
}
double RealMath::Minus(double OpNum1,double OpNum2)
{
return OpNum1-OpNum2;
}
#pragma managed
//主函数
int main(array<System::String ^> ^args)
{
//C++/CLI中,.NET CTS值类型变量直接定义。
Complex C1(2,2),C2(1,1);
Complex C3,C4;
//C++/CLI中,.NET CTS引用类型的对象通常使用gcnew 关键字创建。
ComplexMath ^CM1=gcnew ComplexMath();
//C++/CLI中,C++ 类的对象仍然使用new 关键字创建。
DisplayComplex *DC1=new DisplayComplex();
RealMath *RM1=new RealMath();
C3=CM1->Add(C1,C2);
C4=CM1->Minus(C1,C2);
DC1->Display(C3);
DC1->Display(C4);
Console::WriteLine(RM1->Add(2,1));
Console::WriteLine(RM1->Minus(2,1));
//C++/CLI中,使用new 关键字创建的C++ 类的对象,仍然需要使用delete关键字删除。
delete DC1;
delete RM1;
return 0;
}
程序中Complex类使用value class关键字定义,属于.NET CTS值类型(Value Types);ComplexMath类使用ref class关键字定义,属于.NET CTS引用类型(Reference Types);DisplayComplex和RealMath类则使用class关键字定义,属于C++类,其中RealMath类的定义和实现还使用了#pragma unmanaged和#pragma managed编译指示,说明RealMath类编译成本机代码,而不编译成CIL代码,以实现混合Assembly。
程序编译运行后,得到了预期结果:
3+3i
1+1i
3
1
将编译后生成的可执行文件(Assembly)TypesSample1_CPP.exe使用IDA Pro 4.8反汇编(不要使用ildasm.exe反汇编),注意使用.NET Executable文件格式反汇编,检查生成的IL汇编语言程序,可以看到下列IL汇编语言代码(去掉了部分注释,还有一部分注释是笔者加的,下同):
……
//Complex类,从System.ValueType类派生,显然是值类型。
.class private sequential sealed ansi Complex extends [mscorlib]System.ValueType
{
.field public float64 Re
.field public float64 Im
.method public hidebysig specialname void .ctor(float64 Re, float64 Im)
{
ldarg.0
ldarg.1
stfld float64 Complex::Re
ldarg.0
ldarg.2
stfld float64 Complex::Im
ret
}
}
//ComplexMath类,从System.Object类派生,显然是引用类型。
.class private auto ansi ComplexMath extends [mscorlib]System.Object
{
.method public hidebysig value class Complex Add(value class Complex OpNum1, value class Complex OpNum2)
{
.locals (value class Complex V0)
ldloca.s 0
initobj Complex
ldloca.s 0
ldarga.s 1
ldfld float64 Complex::Re
ldarga.s 2
ldfld float64 Complex::Re
add
stfld float64 Complex::Re
ldloca.s 0
ldarga.s 1
ldfld float64 Complex::Im
ldarga.s 2
ldfld float64 Complex::Im
add
stfld float64 Complex::Im
ldloc.0
ret
}
.method public hidebysig value class Complex Minus(value class Complex OpNum1, value class Complex OpNum2)
{
.locals (value class Complex V0)
ldloca.s 0
initobj Complex
ldloca.s 0
ldarga.s 1
ldfld float64 Complex::Re
ldarga.s 2
ldfld float64 Complex::Re
sub
stfld float64 Complex::Re
ldloca.s 0
ldarga.s 1
ldfld float64 Complex::Im
ldarga.s 2
ldfld float64 Complex::Im
sub
stfld float64 Complex::Im
ldloc.0
ret
}
.method public hidebysig specialname void .ctor()
{
ldarg.0
call void [mscorlib]System.Object::.ctor()
ret
}
}
//DisplayComplex类,C++类,编译后变成了值类型,而且方法成员消失了。
.class private sequential sealed ansi DisplayComplex extends [mscorlib]System.ValueType
{
}
//RealMath类,C++类,编译后变成了值类型,而且方法成员消失了。
.class private sequential sealed ansi RealMath extends [mscorlib]System.ValueType
{
}
……
DisplayComplex类和RealMath类这两个C++类,方法成员为什么消失了?进一步检查的IL汇编语言程序,还可以看到下列IL汇编语言代码:
……
.module TypesSample1_CPP.exe
……
//DisplayComplex类的Display方法,注意这是一个全局方法(函数),但是仍然使用CIL代码实现。
.method assembly static void DisplayComplex.Display(modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst) value class DisplayComplex* C, value class Complex)
{
.locals (class System.String V0,
float64 V1,
float64 V2)
ldarga.s 1
ldfld float64 Complex::Re
stloc.2
ldloca.s 2
call class System.String [mscorlib]System.Double::ToString()
ldarga.s 1
ldfld float64 Complex::Im
stloc.1
ldloca.s 1
call class System.String [mscorlib]System.Double::ToString()
stloc.0
ldstr "+"
call class System.String [mscorlib]System.String::Concat(class System.String, class System.String)
ldloc.0
call class System.String [mscorlib]System.String::Concat(class System.String, class System.String)
ldstr "i"
call class System.String [mscorlib]System.String::Concat(class System.String, class System.String)
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}
//main函数(主函数),注意这是一个全局方法。
.method assembly static int32 main(class System.String[] args)
{
……
}
……
//RealMath类的Add方法,注意这是一个全局方法,使用PInvoke(平台调用)调用本机代码实现。
.method public static pinvokeimpl(/* No map */) modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) float64 RealMath.Add(modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst) value class RealMath*, float64, float64) native unmanaged
{
}
//RealMath类的Minus方法,注意这是一个全局方法,使用PInvoke(平台调用)调用本机代码实现。
.method public static pinvokeimpl(/* No map */) modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) float64 RealMath.Minus(modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst) value class RealMath*, float64, float64) native unmanaged
{
}
……
读者读到这里可能会感到有些奇怪:不是说.NET编程语言(例如C#)源程序完全由类构成吗?那么编译后生成的可执行文件反汇编后,就不应该还有全局方法(函数),为什么反汇编结果中竟然出现了全局方法呢?
实际上,.NET CLI的第2部分“元数据的定义和语义”中已经明确说明:字段和方法都可以是全局的,也就是不在任何一个类型中定义,而在模块(Module)中直接定义的字段和方法。
虽然C#等.NET编程语言不一定支持全局字段和全局方法,但是C++/CLI是支持全局字段和全局方法的,C++/CLI中的C++全局变量和全局函数,就是使用全局字段和全局方法实现的。
其实,说.NET编程语言源程序中完全没有全局字段和全局方法,这种说法本身就是不太合理的,类型中的静态成员,包括静态字段和静态方法,本质上不也就相当于加了类型名“名字空间”——相当于“前缀”的全局字段和全局方法吗?
C++语言特性明显不同于C#,C++/CLI仍然支持C++语言特性。例如:在C#中,使用struct关键字定义的结构属于.NET CTS值类型,使用class关键字定义的类属于.NET CTS引用类型,但是在C++/CLI中,struct关键字和class关键字是等价的,区别只是使用struct关键字定义的类中,成员默认是public的,而使用class关键字定义的类中,成员默认是private的。
回到上述反汇编结果,从反汇编结果中,可以明显看出以下几点:
1、C++/CLI中直接使用class或者struct关键字定义类,并不是直接定义.NET CTS中的类型。C++/CLI中定义.NET CTS中的类型,自定义值类型必须使用value class或者value struct关键字定义,自定义引用类型必须使用ref class或者ref struct关键字定义。
2、对于C++/CLI中的C++类,也就是直接使用class或者struct关键字定义的类,其实现方法,包括类的对象的实现方法,仍然与传统C++实现类和对象的方法相似:对象的属性(字段或者成员变量)和方法(成员函数)分离,对象设计成包含对象属性的数据结构,方法设计成针对对象属性的过程(函数),将包含对象属性的数据结构的引用(指针)作为方法过程的参数,让方法过程针对特定对象的属性进行操作,这样包含对象属性的数据结构和方法过程在逻辑上就构成通常意义上的对象。所以C++类编译后,实际上对应的.NET CTS类型中只包含字段成员,方法成员变成了全局方法。因为传统C++中,类都相当于值类型,C++对象变量直接存储C++对象本身,所以C++/CLI中的C++类编译后对应的.NET CTS类型也都是值类型。
3、对于混合Assembly中编译成本机代码的类,实际上只有方法的实现编译成了本机代码,通过平台调用(PInvoke)进行调用,如同C++/CLI或者C#调用本机代码DLL一样。
可以看出,C++/CLI中,除非一个类被明确定义成.NET CTS中的类型,C++类和对象的实现在本质上仍然和传统C++实现类和对象的方法相似,这就是为什么C++/CLI可以实现.NET CLI不支持而C++支持的语言特性,例如多重继承等的本质原因。C++/CLI的本质可以借用一句话来说明:
“.NET的归.NET,C++的还归C++!”
关于C++中类和对象的实现方法,还可以参见《深度探索C++对象模型》一书(《Inside The C++ Object Model》一书的中译本)。
来自:http://blog.csdn.net/Changjiang/archive/2006/11/27/1415972.aspx

