Click here to Skip to main content
Click here to Skip to main content

Introduction to ACF (Another C++ Framework)

By , 27 May 2004
 

The latest version can be found here (ACF homepage).

Abstract

This article provides an introduction to ACF, a C++ framework which brings the .NET framework to standard C++. Knowledge of C++ and the .NET Framework is assumed.

Contents

  • What is ACF
  • Why ACF
  • Getting started
  • Basic type system
  • Exceptions
  • Arrays and collections
  • Delegates and events
  • Strings and text
  • I/O
  • Threading
  • History
  • Contact

What is ACF

ACF (Another C++ Framework), is a C++ framework designed to bring the power and RAD of the .NET framework to standard C++. ACF does this by implementing the features and APIs of the .NET framework in standard C++, and at the same time takes advantage of C++ unique features such as templates.

Why ACF

The .NET framework is definitely the future development platform of Microsoft, and C# is the best programming language for that platform. However, today there are thousands of existing C++ projects and developers. These projects are not likely to be totally rewritten for .NET, and the developers are facing the headache choice between the RAD and benefits of C# and the .NET framework, and the performance and control of C++. Apparently, the C++ community need a bridge now before moving to .NET in the future.

The solution from Microsoft is Managed C++. MC++ tries to bring C++ to .NET, the result is that C++ code can be compiled to IL, and managed code can easily interop with native code. However, MC++ may not be a good choice for many projects and developers. First, C++ is almost the symbol of performance and control, but JIT and GC are very slow, and the runtime is also too huge for many applications. Second, C++ is already very complex and hard to learn and use, MC++ adds to it.

ACF tries to solve this problem from another side - bring .NET to C++. By implementing the .NET framework in standard C++, ACF helps existing C++ projects and developers:

  1. ACF can be used in existing or new C++ projects (ACF works seamlessly with MFC/ATL/STL, etc), and everything compiles to machine code.
  2. Developers can use existing C++ skills, and at the same time use concepts and APIs that are very close to the .NET framework. Their new skills are reusable across C#, C++ and MC++ (for example, they always use String::Format to build formatted strings).
  3. ACF also helps when porting code between C++ and C#.

Getting started

Currently, ACF is developed under Visual C++ 2003, you'd better get it before we move on (if you are still using Visual C++ 6.0, throw it away since it is not very standards conformant and its template support is very limited). The first step will be to build ACF if not done before. For example, to build the Corlib, open Corlib.sln (under "{...}\Acf\src\Corlib", where {...} is the directory which contains ACF) with Visual Studio and build it. Like using other C++ libraries, you need to setup the include and library directories in your development environment before using ACF in your applications. The include directory for Corlib is "{...}\Acf\src\Corlib", library directory is "{...}\Acf\lib".

The "hello world" program can be written as follows:

#include <AcfCorlib.h>
int main() {
    Acf::Console::WriteLine(L"hello, world");
}

To compile this program, you need to turn on "treat wchar_t as built-in type" and "enable runtime type info" compiler options, and link to the multithreaded CRT. This is required for all ACF projects.

ACF is organized into a number of namespaces, following the design of the .NET framework. The Acf namespace corresponds to the System namespace in the .NET framework. AcfCorlib.h is the header file for the Corlib. Before we can write more useful applications, let's look into the type system of ACF.

Basic type system

There are some significant differences between the C++ type system and the C#/CLI type system, this affects how ACF type system is implemented.

C++ does not have a unified type system. Primitive types are "magic"; types are not required to inherit from a common root; and MI is allowed. C++ also has a very flexible memory model, the developers control how and when to allocate/free memory.

C#/CLI has an unified type system. All types directly or indirectly inherit from System.Object; MI is not allowed, but a type can implement multiple interfaces. Types can be value types or reference types. Instances of value types directly contain data; instances of reference types are allocated on the GC heap and are automatically reclaimed. The translation between value types and reference types is called boxing/unboxing, which is handled by the compiler and the runtime.

ACF tries to emulate the C#/CLI type system on C++ and at the same time retain performance. The result is a hybrid type system. In ACF, types include reference types and other types. Reference types are allocated on the heap, and managed using reference count (like COM). Reference types are required to inherit from a single root - Acf::Object. MI is only used to implement interfaces, and all reference interfaces are required to inherit from Acf::InterfaceBase. Other types include the primitive types (e.g. int, float), enum types and other C++ structures and classes. The following diagram shows the basic object model (green classes are value types, blue classes are reference types):

In ACF, there is no automatically boxing/unboxing since it requires compiler and runtime support. Developers have to define the value class and reference class separately and handle the boxing/unboxing requests (in C#/CLI, there is only one type, and boxing/unboxing are automatically handled). For example, Int32 is a value type and does not have a base. It has an instance int field and provides methods for parsing and formatting. The Int32Object reference type has an instance Int32 field and inherits from Acf::Object and implements interfaces such as IFormattable. (ACF uses X and XObject naming conversion to distinguish value types and their corresponding reference types, another example is Acf::Guid and Acf::GuidObject.)

The example:

int main() {
    int i = 123;
    ObjectPtr o = box(i); // boxing
    int j = unbox<int>(o); // unboxing
}

shows how boxing and unboxing are used. The box and unbox<T> functions are template functions under Acf namespace. The default implementation of box returns an instance of Acf::Boxed<T> which inherits from Acf::Object and acts as an object box. The box and unbox<T> functions should be overloaded for user-defined types that have separate value class and reference class definitions (for example, Int32 and Int32Object).

In ACF, all reference types are allocated on the heap via operator new. The new operator is overloaded so that it throws Acf::OutOfMemoryException when memory allocation fails. On successful allocation, the returned memory is cleared so there are no random bits. As stated, ACF uses reference count to manage object lifetime (all interfaces share the same reference count with their owner objects). When holding an object or an interface, you should call AddRef; when you are done, you should call Release. An object has an initial reference count 0 and is freed when its reference count goes back to 0. A smart pointer class, RefPtr<T>, is designed to do the AddRef/Release jobs automatically. As a common design philosophy, RefPtr<T> is treated as strong reference, and raw C++ pointer is treated as weak reference. We need to distinguish strong reference and weak reference because the reference count approach has problem dealing with cyclic reference. For example, if object A has a strong reference to object B, and object B has a strong reference back to object A, then these two objects form a cyclic reference and both will never be freed. This problem can be solved by letting B hold a weak reference to A (or reverse, depending on the scenario).

The following example shows how to define and use reference types:

#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));
}

The class Person is a reference type and inherits from Acf::Object. It has two instance fields, Name (string) and Age (int). StringPtr is a typedef of RefPtr<String>. The destructor is protected so this type cannot be allocated on stack (reference types shall always be allocated on the heap).

In the main function, the first statement defines and allocates a new string instance. Next, a new Person instance is allocated and its fields are changed. Then, Console::WriteLine is used to format and print the person's name and age (the format string is defined by the .NET framework).

Exceptions

ACF uses exceptional handling as its basic error raising and handling mechanism. In the .NET framework, exceptions are also reference types and collected by GC. In ACF, exceptions are value types. This is because in C++, the preferred way to throw and catch exceptions is throw MyException() and catch (const MyException& e). Acf::Exception is the base class for all exceptions. Common exception classes include ArgumentNullException, IOException, FormatException, etc.

Arrays and collections

Arrays and collections in ACF are different from the .NET framework ones, because C++ provides good template and template specialization support (especially partial specialization).

Array<T> is the array class in ACF (currently multidimensional arrays are not supported). The actual class declaration is Array<T, Tr>, where Tr is the collection traits class (there are default implementations for most classes). The collection traits control how to pass parameters, how to clear elements, how to compare two elements, and how to generate hash code. This technique is used by all ACF collection classes and interfaces.

Here are two array examples:

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");

The first example builds an int array with 5 elements and sorts it. The second example creates a string array with two elements and sets the elements. The Item[0] syntax is a Visual C++ language extension which represents an indexed property. It is translated into array1->set_Item(0, str(L"hello")) at compile time.

The other collection classes and interfaces in ACF (under namespace Acf::Collections) are just like the .NET framework ones, except they are template based. Here are some examples:

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# has a foreach statement to enumerate over arrays and collections and many developers like it very much. ACF defines a FOREACH macro to emulate this feature. For example, given an array which stores int, we can enumerate the array as follows:

FOREACH (int, n, array) {
    Console::WriteLine(n);
}

Delegates and events

Delegates and events are so attractive features of C# and the .NET framework that ACF definitely cannot miss them.

The delegate definition in ACF is quite different from C#/CLI. For example, the EventHandler delegate definition in C# is as follows:

delegate void EventHandler(object sender, EventArgs e);

In ACF it is like:

typedef Delegate2<void, Object*, EventArgs*>   EventHandler;

Here DelegateN<R, P1, P2, ..., Pn> is the delegate class in ACF, where N is the number of the function parameters and R is the function return type. To invoke a delegate, call the instance method Invoke with the function parameters.

In C#, it is very easy to define and consume events, but in ACF, it's a little complex since there is no compiler support. To define an event, use the ACF_DECLARE_EVENT macro.

Here is an example on how to use delegates and events in 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;
    }
};

The ACF_DECLARE_EVENT macro actually generates the following code:

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);
    }
};

In Form1's constructor, two delegates are created, one attaches to a static method and another attaches to an instance method. For the instance method, the delegate also needs a reference to the object (here it is Form1). Since Form1 has a strong reference to Button, and Button has a strong reference to Click, so, if Click has a strong reference to Form1, then a cyclic reference is formed. However, in other cases, the delegates may really need strong references to the objects. So, the constructors of the delegate classes are overloaded for both T* and RefPtr<T>. If you pass a T* when constructing a delegate (typically using the this pointer), the delegate will hold a weak reference to the object. Otherwise, it'll hold a strong reference.

Strings and text

The String and StringBuilder classes provide basic string manipulations. The Acf::Text namespace also provides classes for encoding and decoding text, as the .NET framework. Here is a simple example:

StringPtr s = String::Format(L"Text: {0} {1}", s1, obj);

EncodingPtr enc = Encoding::get_Default();
RefPtr<Array<byte> > bytes = enc->GetBytes(s);

The str function converts numerical values and C-style strings to 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

The Acf::IO namespace contains classes for reading and writing streams and files (currently ACF does not support asynchronous file I/O).

For example, to read text from a file, you can write code as follows:

StringPtr path = new String(L"C:\in.txt");
StreamReaderPtr reader = new StreamReader(path);
StringPtr text = reader->ReadToEnd();

ACF supports basic streams including FileStream and MemoryStream, and readers/writers including BinaryReader/BinaryWriter, TextReader/TextWriter, StreamReader/StreamWriter and StringReader/StringWriter.

ACF also supports simple file system management via Path, File and Directory classes.

Threading

ACF provides basic support for writing multithreaded applications. The following code shows how to create and start worker threads:

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();

    ...
}

To synchronize data access, use the LOCK macro or the Interlocked, Monitor, AutoResetEvent, ManualResetEvent or Mutex classes.

History

  • 5/25/2004, version 0.2.
  • 4/25/2004, version 0.1.

Contact

Your feedback and suggestions are key factors to make this framework better, please feel free to send them to Yingle Jia (yljia@msn.com). You can also visit his weblog at http://blogs.wwwcoder.com/yljia/.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Yingle Jia
Web Developer
China China
Member
Yingle Jia is a software engineer located in Beijing, China. He currently works at IBM CSDL (China Software Development Lab). His interests include C++/COM/C#/.NET/XML, etc. He likes coding and writing.
 
He is the creator of ACF (Another C++ Framework) project. See http://acfproj.sourceforge.net/.
 
He also has a blog at http://blogs.wwwcoder.com/yljia/

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralExcelent workmemberCrym6911 Sep '08 - 4:19 
Greetings!!!
You are made good work. I'm trying to make it like you. But I rely was happy when read your article. And I think if you wont to make it more better, it will be grate if you can use some GC staff. For ex.: libgc. I was implemented some objects with helping of libgc. About performance: libgc support operator delete and other staff.
GeneralSuggestions [modified]memberVincent_RICHOMME13 Jun '06 - 10:01 
Hi,
 
rencently I have started a project : porting .NET classes into native C++. I have progressed and now I have found your code. I am happy to see that I have done almost like you, however I have implemented classes related to Process. You might be interested int that code. I will send it to you if you like.
I would like to compile your code under Visual 2005, unfortunately MS has changed some bahaviors and it's more C++ compliant.
 

GeneralSuperbmemberAmit Krishna Joshi10 Nov '04 - 20:37 
I am highly impressed. Do post further developments on this topic.
 
Amit Krishna Joshi aka crossthelimit
http://www.akjoshi.com.np
 
Imagine possible.
Imagine impossible.
Imagine impossible possible.
GeneralWindows CEmemberbalham5 Jul '04 - 17:03 
Although Windows CE.NET contains the .NET framework, there is no managed C++ support.
Only C# and VB are supported ( so far).
 
So, you could plug ACF to Windows CE C++ developers, as an interim until managed C++ comes along (if it does)
GeneralGreat,but...memberucc80128 May '04 - 16:50 
Great idea,but it is too simple and too hard to use.
Regards
----------------------------------------------------------------
XD++ MFC Library Full source Kits -- http://www.ucancode.net

GeneralInteresting, but....memberRui A. Rebelo30 Apr '04 - 5:39 
What about the other open source .NET frameworks which are around: dotGNU, Rotor, Mono,...
 
ACF seems to be in a very early stage of development . Why not adopt one of those alternatives above?
 
Rui A. Rebelo
 
Computers are useless, they can only provide answers.
Pablo Picasso
GeneralSeems backing into SmallTalk age...sussAnonymous28 Apr '04 - 17:10 
single root inheritance, huge class tree, virtual functions everywhere... it looks like another OWL/MFC/VCL/AWT.
 
anyway, we can mix native codes with .Net IL, so why don't just write UI in C#/VB.Net/J# or any language supported by .Net if you like
WinForm/WebForm things?
 
I think a good C++ UI framework should be something like WTL, not SmallTalk.
GeneralRe: Seems backing into SmallTalk age...sussYingle Jia28 Apr '04 - 18:34 
Hi,
 
On some aspect you are right. But what if your clients don't have .NET installed? What if their computers have only or less than 128M memories? .NET is definitely the future, ACF is just designed to be a bridge for current C++ projects and developers (say 3~5 years). I think to implement the concepts and APIs in .NET framework is very important since so you don't have to learn many different frameworks and libraries.
 
As to WTL, I think it's too hard to use for normal developers. A good framework should be consistent, robust and easy to use.

GeneralRe: Seems backing into SmallTalk age...memberpinhopro13 May '04 - 6:33 
What is Normal Developer ? Is a developer that think WTL is complicated ?
 
I do not think so. I think that best word to define this developer is Mediocre developer.
 
Sorry, But WTL is robust, and easy to use.
 
Rodrigo Pinho
Siemens Mobile
PC Software Tools
rpinho@mobiledeveloperspace.de
GeneralExcellent, but some issues.memberTomM26 Apr '04 - 17:18 
Seems like a very good idea, but I have a couple of questions.
 
As I'm sure you know, the dotNet framework was designed to be cross-platform. I take it that the code you've written so far is for Windows. Are you using the Win32 API extensively or are you trying to make your code portable?
 
Also, do you have any plans for what to do about Windows Forms and the other proprietary API's? I guess it isn't really legal or safe to implement these without MS permission. Would you recommend that people use your library with other C++ GUI libraries?
 
Thanks for your work,
Tom.
GeneralRe: Excellent, but some issues.sussYingle Jia26 Apr '04 - 18:32 
Hi,
 
Portability is an issue which will be addressed in the future - you know, I work only on Win32 platform. The BCL, System, and XML components will be portable. Sure I cann't do all these stuff all by my hands, this is why I set up a project on Sourceforge. I believe that the C++ community can make it a real framework.
 
There is a plan to implement windows forms and other windows client technologies in version 2.0. I guess it should be OK because the Mono open source project includes those stuff. Currently I would recommend that you use MFC or other GUI llibraries to create the user interface part (this is what I'm doingSmile | :) ).
 
- Yingle

GeneralRe: Excellent, but some issues.memberAnthony_Yio26 Apr '04 - 21:00 
the dotNet framework was designed to be cross-platform
 
There is already a more established cross platform programming language that already been deployed in many platform that named after a coffee bean.
 

 
Sonork 100.41263:Anthony_Yio
 
Life is about experiencing ...

GeneralRe: Excellent, but some issues.memberKevin McFarlane27 Apr '04 - 1:46 
By "cross-platform" they mean portable - in the same way that C, C++, Eiffel, Perl, Python, etc., are portable.
 
Kevin
GeneralCool!memberStoyan Damov26 Apr '04 - 11:53 
Nice job!
 
If not for anything else, you got my five for the effort.
Keep up the good work!
 
Stoyan
 
VB.NET coders need "IntelliSense", C# coders only need their "CommonSense" and "IntelliGence".
That's the difference Wink | ;-)
Robert Fuchs
GeneralBlaze++ .NETsussAnonymous26 Apr '04 - 7:47 
similar .NET like C++ class library with a RAD IDE
 
http://www.capitolsoft.com/Articles/RadVC_NET_Screens.htm[^]
GeneralRe: Blaze++ .NETmemberdog_spawn26 Apr '04 - 8:39 
The library by "captiolsoft" seems very different from the code presented in this article. Maybe I have not looked at it in enough detail, but it looks like an MFC "wrapper". Not very useful...
GeneralRe: Blaze++ .NETsussAnonymous27 Apr '04 - 11:15 
Sounds like your a retard
GeneralRe: Blaze++ .NETmemberAnna-Jayne Metcalfe27 May '04 - 23:34 
If you two want to argue, please take it to the Soapbox[^]. That's what it's there for.
 
A tip: Don't call anyone gay or retarded unless you want to get flamed. The first isn't an insult anyway (unless you happen to be a homophobe or a 14 year old script kiddie of course) and the second is just insensitive.
 

 
Anna Rose | [Rose]
 
Homepage | Tears and Laughter
 
"Be yourself - not what others think you should be"
- Marcia Graesch
 
"Anna's just a sexy-looking lesbian tart"
- A friend, trying to wind me up. It didn't work.
 
Trouble with resource IDs? Try the Resource ID Organiser Visual C++ Add-In

GeneralNit-picky Criticism of Reference Counting...memberCap'n Code25 Apr '04 - 18:35 
One day a student came to Moon and said: "I understand how to make a better garbage collector. We must keep a reference count of the pointers to each object."
 
Moon patiently told the student the following story:
 
"One day a student came to Moon and said: `I understand how to make a better garbage collector..."
 
Thus spake the master programmer:
"It is time for you to leave."

GeneralRe: Nit-picky Criticism of Reference Counting...sussYingle Jia25 Apr '04 - 19:25 
Hi, buddy
Every experienced C++ programmer knows there is no silver-bullet to manage memory in C++. Reference counting may not work well on certain cases (as I stated in the article), but it does work for most cases. In ACF I'm definitely not trying to make a automatic garbage collector.

GeneralRe: Nit-picky Criticism of Reference Counting...sussAnonymous26 Apr '04 - 2:26 
I know. That's why I call it nit-picky criticism. Reference counting will do you good in most cases, or at least an autoptr<>.
GeneralRe: Nit-picky Criticism of Reference Counting...sussAnonymous26 Apr '04 - 2:28 
Reference counting will do you good in most cases. But someone using the reference object must be aware of its shortcomings.
GeneralRe: Nit-picky Criticism of Reference Counting...sussYingle Jia26 Apr '04 - 5:08 
Yep, you are right.Smile | :)

GeneralAn ImprovementmemberGilad Novik25 Apr '04 - 1:16 
First, your framework looks pretty cool.
 
As for events, you can override operator+= to make the event handlers similar to .NET.
 
Whoa! The internet is even on computers now!
GeneralExcellent workmemberdog_spawn25 Apr '04 - 4:25 
Gilad Novik wrote:
First, your framework looks pretty cool
 
I agree Smile | :)
 
I enjoyed reading through your code, Yingle Jia. Very refreshing to see someone has put in the effort to use namespaces and exceptions. Well done also for using smart pointers.
 
I look forward to seeing how this develops! Smile | :)
 
My personal view about writing new frameworks is it's always a good idea. I know people complain about re-inventing the wheel and such, but investigating the algorithms (for example utf8) and designing the objects (like streams) is a great learning experience.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 28 May 2004
Article Copyright 2004 by Yingle Jia
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid