ACF 简介(另一个 C++ 框架)






4.86/5 (33投票s)
2004 年 4 月 25 日
11分钟阅读

140009

753
本文介绍了ACF,一个将.NET框架引入标准C++的C++框架。
最新版本可以在这里找到(ACF主页)。
摘要
本文介绍了ACF,一个将.NET框架引入标准C++的C++框架。本文假定读者熟悉C++和.NET Framework。
目录
- ACF是什么
- 为什么选择ACF
- 入门
- 基本类型系统
- 异常
- 数组和集合
- 委托和事件
- 字符串和文本
- I/O
- 多线程
- 历史
- 联系方式
ACF是什么
ACF(Another C++ Framework),是一个C++框架,旨在将.NET框架的强大功能和RAD(快速应用开发)能力引入标准C++。ACF通过在标准C++中实现.NET框架的特性和API来实现这一点,同时还利用C++的独有特性,如模板。
为什么选择ACF
.NET框架无疑是微软未来的开发平台,而C#是该平台最佳的编程语言。然而,今天仍然有成千上万的现有C++项目和开发者。这些项目不太可能被完全重写为.NET,而开发者正面临着在C#和.NET框架的RAD和优势,以及C++的性能和控制之间艰难抉择的困境。显然,在未来迁移到.NET之前,C++社区现在需要一座桥梁。
微软的解决方案是Managed C++。MC++试图将C++引入.NET,结果是C++代码可以编译成IL,并且托管代码可以轻松地与原生代码互操作。然而,MC++可能并非适用于许多项目和开发者的好选择。首先,C++几乎是性能和控制的代名词,但JIT和GC非常慢,而且运行时对于许多应用程序来说也太庞大。其次,C++本身已经非常复杂且难以学习和使用,MC++更是增加了其复杂性。
ACF试图从另一个角度解决这个问题——将.NET引入C++。通过在标准C++中实现.NET框架,ACF可以帮助现有的C++项目和开发者
- ACF可以用于现有或新的C++项目(ACF可以与MFC/ATL/STL等无缝集成),并且所有代码都编译成机器码。
- 开发者可以利用现有的C++技能,同时使用与.NET框架非常接近的概念和API。他们的新技能可以在C#、C++和MC++之间复用(例如,他们总是使用
String::Format
来构建格式化字符串)。 - ACF还有助于在C++和C#之间移植代码。
入门
目前,ACF是在Visual C++ 2003下开发的,最好在继续之前获取它(如果您仍在使用Visual C++ 6.0,请将其丢弃,因为它不够符合标准,并且其模板支持非常有限)。第一步是构建ACF(如果之前没有完成)。例如,要构建Corlib,请在Visual Studio中打开Corlib.sln(位于“{...}\Acf\src\Corlib”下,其中“{...}”是包含ACF的目录)并进行构建。与其他C++库一样,在使用ACF构建应用程序之前,您需要在开发环境中设置包含目录和库目录。Corlib的包含目录是“{...}\Acf\src\Corlib”,库目录是“{...}\Acf\lib”。
"Hello World"程序可以这样写:
#include <AcfCorlib.h>
int main() {
Acf::Console::WriteLine(L"hello, world");
}
要编译此程序,您需要启用“将wchar_t视为内置类型”和“启用运行时类型信息”编译器选项,并链接到多线程CRT。这是所有ACF项目必需的。
ACF遵循.NET框架的设计,被组织成多个命名空间。Acf
命名空间对应于.NET框架中的System
命名空间。AcfCorlib.h是Corlib的头文件。在我们编写更多有用的应用程序之前,让我们先了解一下ACF的类型系统。
基本类型系统
C++类型系统和C#/CLI类型系统之间存在一些显著差异,这影响了ACF类型系统的实现。
C++没有统一的类型系统。基本类型是“魔术”的;类型不需要继承自一个共同的根;并且允许多重继承。C++还有一个非常灵活的内存模型,开发者控制如何以及何时分配/释放内存。
C#/CLI拥有统一的类型系统。所有类型都直接或间接继承自System.Object
;不允许多重继承,但一个类型可以实现多个接口。类型可以是值类型或引用类型。值类型的实例直接包含数据;引用类型的实例分配在GC堆上并自动回收。值类型和引用类型之间的转换称为装箱/拆箱,这由编译器和运行时处理。
ACF试图在C++上模拟C#/CLI类型系统,同时保留性能。结果是一个混合类型系统。在ACF中,类型包括引用类型和其他类型。引用类型分配在堆上,并通过引用计数进行管理(类似于COM)。引用类型必须继承自一个单一的根——Acf::Object
。多重继承仅用于实现接口,并且所有引用接口都必须继承自Acf::InterfaceBase
。其他类型包括基本类型(例如int
、float
)、enum
类型以及其他C++结构体和类。下图显示了基本对象模型(绿色类是值类型,蓝色类是引用类型)。
在ACF中,没有自动装箱/拆箱,因为它需要编译器和运行时支持。开发者必须单独定义值类和引用类,并处理装箱/拆箱请求(在C#/CLI中,只有一个类型,并且装箱/拆箱是自动处理的)。例如,Int32
是一个值类型,没有基类。它有一个实例int
字段,并提供了用于解析和格式化方法。Int32Object
引用类型有一个实例Int32
字段,并继承自Acf::Object
并实现了IFormattable
等接口。(ACF使用X和XObject的命名约定来区分值类型及其对应的引用类型,另一个例子是Acf::Guid
和Acf::GuidObject
。)
示例
int main() {
int i = 123;
ObjectPtr o = box(i); // boxing
int j = unbox<int>(o); // unboxing
}
展示了如何使用装箱和拆箱。box
和unbox<T>
函数是Acf
命名空间下的模板函数。box
的默认实现返回一个Acf::Boxed<T>
的实例,它继承自Acf::Object
并充当对象框。box
和unbox<T>
函数应该为具有单独值类和引用类定义的自定义类型(例如Int32
和Int32Object
)重载。
在ACF中,所有引用类型都通过运算符new
在堆上分配。new
运算符被重载,以便在内存分配失败时抛出Acf::OutOfMemoryException
。成功分配后,返回的内存会被清零,因此没有随机位。如前所述,ACF使用引用计数来管理对象生命周期(所有接口都与它们的拥有对象共享相同的引用计数)。持有对象或接口时,应调用AddRef
;完成后,应调用Release
。对象的初始引用计数为0,当其引用计数返回到0时会被释放。智能指针类RefPtr<T>
被设计用于自动执行AddRef/Release
作业。作为一种常见的设计理念,RefPtr<T>
被视为强引用,而原始C++指针被视为弱引用。我们需要区分强引用和弱引用,因为引用计数方法在处理循环引用时存在问题。例如,如果对象A对对象B有强引用,而对象B对对象A有强引用,那么这两个对象就形成了循环引用,两者都永远不会被释放。这个问题可以通过让B持有对A的弱引用(或反之,取决于场景)来解决。
以下示例展示了如何定义和使用引用类型。
#include <AcfCorlib.h>
using namespace Acf;
class Person : public Object {
public:
StringPtr Name;
int Age;
Person() {
}
protected:
virtual ~Person() {
}
};
int main() {
StringPtr name = new String(L"Foo");
RefPtr<Person> person = new Person();
person->Name = name;
person->Age = 25;
Console::WriteLine(L"Name: {0}, Age: {1}",
person->Name, str(person->Age));
}
类Person
是一个引用类型,继承自Acf::Object
。它有两个实例字段,Name
(string
)和Age
(int
)。StringPtr
是RefPtr<String>
的typedef
。析构函数是保护的,因此此类型不能在栈上分配(引用类型应始终在堆上分配)。
在main函数中,第一条语句定义并分配了一个新的字符串实例。接下来,分配了一个新的Person
实例并修改了其字段。然后,使用Console::WriteLine
来格式化并打印该人的姓名和年龄(格式字符串由.NET框架定义)。
异常
ACF使用异常处理作为其基本的错误引发和处理机制。在.NET框架中,异常也是引用类型并由GC收集。在ACF中,异常是值类型。这是因为在C++中,抛出和捕获异常的首选方式是throw MyException()
和catch (const MyException& e)
。Acf::Exception
是所有异常的基类。常见的异常类包括ArgumentNullException
、IOException
、FormatException
等。
数组和集合
ACF中的数组和集合与.NET框架中的不同,因为C++提供了良好的模板和模板特化支持(尤其是偏特化)。
Array<T>
是ACF中的数组类(目前不支持多维数组)。实际的类声明是Array<T, Tr>
,其中Tr
是集合特征类(大多数类都有默认实现)。集合特征控制如何传递参数、如何清除元素、如何比较两个元素以及如何生成哈希码。此技术被所有ACF集合类和接口使用。
这里有两个数组示例:
RefPtr<Array<int> > array1 = Array<int>::Build(3, 1, 2, 5, 4);
Array<int>::Sort(array1);
RefPtr<Array<StringPtr> > array2 = new Array<StringPtr>(2);
array2->Item[0] = str(L"hello");
array2->Item[1] = str(L"world");
第一个示例构建了一个包含5个元素的int
数组并对其进行排序。第二个示例创建了一个包含两个元素的字符串数组并设置了元素。Item[0]
语法是Visual C++语言扩展,表示一个索引属性。在编译时,它会被翻译成array1->set_Item(0, str(L"hello"))
。
ACF中的其他集合类和接口(在Acf::Collections
命名空间下)与.NET框架中的非常相似,只是它们是基于模板的。这里有一些例子:
RefPtr<List<int> > list = new List<int>();
list->Add(10);
list->Add(20);
RefPtr<Dictionary<StringPtr, int> > map = new Dictionary<StringPtr, int>();
map->Item[str(L"s1")] = 1;
map->Item[str(L"s2")] = 10;
C#有一个foreach
语句来遍历数组和集合,许多开发者非常喜欢它。ACF定义了一个FOREACH
宏来模拟这个特性。例如,给定一个存储int
的数组,我们可以像这样遍历数组:
FOREACH (int, n, array) {
Console::WriteLine(n);
}
委托和事件
委托和事件是C#和.NET框架中如此吸引人的特性,ACF当然不能错过它们。
ACF中的委托定义与C#/CLI有很大不同。例如,C#中的EventHandler
委托定义如下:
delegate void EventHandler(object sender, EventArgs e);
在ACF中,它看起来像这样:
typedef Delegate2<void, Object*, EventArgs*> EventHandler;
这里DelegateN<R, P1, P2, ..., Pn>
是ACF中的委托类,其中N
是函数参数的数量,R
是函数的返回类型。要调用一个委托,请调用实例方法Invoke
并传入函数参数。
在C#中,定义和使用事件非常容易,但在ACF中,由于没有编译器支持,它有点复杂。要定义一个事件,请使用ACF_DECLARE_EVENT
宏。
以下是一个关于如何在ACF中使用委托和事件的示例:
typedef Delegate2<void, Object*, EventArgs*> EventHandler;
typedef RefPtr<EventHandler> EventHandlerPtr;
class Button : public Object {
public:
ACF_DECLARE_EVENT(EventHandler, Click)
protected:
void OnClick(EventArgs* e) {
if (Click != null)
Click->Invoke(this, e);
}
};
class Form1 : public Object {
private:
RefPtr<Button> _button;
public:
Form1() {
this->_button = new Button();
EventHandlerPtr h1 = new EventHandler(this, Form1::ButtonClickHandler);
this->_button->add_Click(h1);
EventHandlerPtr h2 = new EventHandler(Form1::StaticButtonClickHandler);
this->_button->add_Click(h2);
}
private:
void ButtonClickHandler(Object* sender, EventArgs* e) {
std::cout << "Button clicked!" << std::endl;
}
static void StaticButtonClickHandler(Object* sender, EventArgs* e) {
std::cout << "Static: Button clicked!" << std::endl;
}
};
ACF_DECLARE_EVENT
宏实际上生成了以下代码:
class Button : public Object {
private:
RefPtr<EventHandler> Click;
public:
void add_Click(EventHandler* h) {
LOCK (this)
this->Click = EventHandler::Combine(this->Click, h);
}
void remove_Click(EventHandler* h) {
LOCK (this)
this->Click = EventHandler::Remove(this->Click, h);
}
};
在Form1
的构造函数中,创建了两个委托,一个绑定到一个静态方法,另一个绑定到一个实例方法。对于实例方法,委托还需要一个指向对象的引用(这里是Form1
)。由于Form1
对Button
有强引用,而Button
对Click
有强引用,所以如果Click
对Form1
有强引用,就会形成循环引用。然而,在其他情况下,委托可能确实需要对对象的强引用。因此,委托类的构造函数被重载为支持T*
和RefPtr<T>
。如果您在构造委托时传递一个T*
(通常使用this
指针),委托将持有对对象的弱引用。否则,它将持有强引用。
字符串和文本
String
和StringBuilder
类提供了基本的字符串操作。Acf::Text
命名空间也提供了用于编码和解码文本的类,就像.NET框架一样。这是一个简单的例子:
StringPtr s = String::Format(L"Text: {0} {1}", s1, obj);
EncodingPtr enc = Encoding::get_Default();
RefPtr<Array<byte> > bytes = enc->GetBytes(s);
str
函数将数值和C风格字符串转换为String
。
int a = 10;
float b = 15.2;
const char* c = "hello";
StringPtr s = String::Format(L"{0}{1}{2}", str(a), str(b), str(c));
I/O
Acf::IO
命名空间包含用于读写流和文件的类(目前ACF不支持异步文件I/O)。
例如,要从文件中读取文本,您可以编写如下代码:
StringPtr path = new String(L"C:\in.txt");
StreamReaderPtr reader = new StreamReader(path);
StringPtr text = reader->ReadToEnd();
ACF支持基本流,包括FileStream
和MemoryStream
,以及读写器,包括BinaryReader/BinaryWriter
、TextReader/TextWriter
、StreamReader/StreamWriter
和StringReader/StringWriter
。
ACF还通过Path
、File
和Directory
类支持简单的文件系统管理。
多线程
ACF为编写多线程应用程序提供了基本支持。以下代码展示了如何创建和启动工作线程:
static void WorkerThreadProc() {
...
}
class MyObject : public Object {
public:
void WorkerThreadProc() {
...
}
};
int main()
{
ThreadPtr thread1 = new Thread(WorkerThreadProc);
thread1->Start();
RefPtr<MyObject> obj = new MyObject();
ThreadStartPtr start = new ThreadStart(obj, MyObject::WorkerThreadProc);
ThreadPtr thread2 = new Thread(start);
thread2->Start();
...
}
为了同步数据访问,请使用LOCK
宏或Interlocked
、Monitor
、AutoResetEvent
、ManualResetEvent
或Mutex
类。
历史
- 2004年5月25日,版本0.2。
- 2004年4月25日,版本0.1。
联系方式
您的反馈和建议是使该框架变得更好的关键因素,请随时发送给Yingle Jia(yljia@msn.com)。您也可以访问他的博客:http://blogs.wwwcoder.com/yljia/。