Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC9.0

SystemFramework library

5.00/5 (4 votes)
24 Nov 2011CPOL6 min read 35.7K   1.1K  
SystemFramework defines interfaces, classes, and types to support a native runtime system with its own garbage collector, delegates, etc. The design of SystemFramework classes is similar to those of the .NET Framework.

Introduction

Standard native C++ does not have a true object-oriented garbage collector, object-oriented function pointers (delegates), truly object-oriented exception handling, and object-oriented multithreading system. But the CLR C++, in fact .NET Framework, has all of these things. A few years ago I felt a serious lack of all these features when writing my applications with C++ on platforms where the .NET Framework wasn't supported. Fortunately, I found this article on CodeProject: FastDelegate.aspx.

After studying the content of the FastDelegate article and many more articles about implementing garbage collector, I decided to write my own C++ native system framework which wouldl allow almost everything which is clever on the .NET Framework and the C# syntax. It was a very stressful way to put all things alive, but thankfully there was the "Partial Specialization of Class Templates" which helped me a lot.

Well, here you have the SystemFramework, which contains Smart Pointers, Delegates and Multicast Delegates, Enumeration classes, and Mark-and-Sweep Garbage Collector so you don't need to call free or delete to free allocated memory anymore. I also put support for creating my own DLL like assemblies using the same SystemFramework DLL. I added a very simple XML parser as well. All classes are documented within the code and there is possibilities to generate a help file using the Doxygen tool which is available at http://www.stack.nl/~dimitri/doxygen/.

Summary of supported .NET namespaces which are completely or partially implemented:

cliThe cli namespace consists of the internal structures, classes, and templates which support the Runtime Framework infrastructure and is not intended to be used directly from your code.
cli::XMLHelper and tool classes for reading, writing, and parsing XML data.
cli::XML::StreamingNamespace with helper classes and templates for streaming XML.
SystemThe System namespace contains structures and classes which are designed according to the .NET Framework system data types. These basics are the core of the runtime system and gives the ability to write a runtime application in .NET like style with almost all supported features.
System::CollectionsThe System.Collections namespace contains interfaces and classes that define various collections of objects, such as lists, queues, bit arrays, hash tables, and dictionaries.
System::ComponentModelThe System.ComponentModel namespace provides classes that are used to implement the run-time and design-time behavior of components and controls.
System::DiagnosticsThe System.Diagnostics namespace provides classes that allow you to interact with system processes, event logs, and debugging traces.
System::IOThe System.IO namespace contains types that allow reading and writing to files and data streams, and types that provide basic file and directory support.
System::IO::PortsThe System.IO.Ports namespace contains classes for controlling serial ports. The most important class, System.IO.Ports.SerialPort, provides a framework for synchronous and event-driven I/O, access to pin and break states, and access to serial driver properties.
System::NetThe System.Net namespace provides a simple programming interface for many of the protocols used on networks today.
System::Net::SocketsThe System.Net.Sockets namespace provides a managed implementation of the Windows Sockets (Winsock) interface for developers who need to tightly control access to the network.
System::ResourcesThe System.Resources namespace provides classes and interfaces that allow developers to create, store, and manage various culture-specific resources used in an application.
System::RuntimeThe System.Runtime namespace contains advanced types that support diverse namespaces such as System, the Runtime namespaces, and the Security namespaces.
System::Runtime::InteropServicesThe System.Runtime.InteropServices namespace provides a wide variety of members that support platform invoke services.
System::SecurityThe System.Security namespace provides the underlying structure of the common language runtime security system, including base classes for permissions.
System::Security::AccessControlThe System.Security.AccessControl namespace provides programming elements that control access to and audit security-related actions on securable objects.
System::ThreadingThe System.Threading namespace provides classes and interfaces that enable multithreaded programming.

Limitations

To use the library properly, it is necessary to use special macros and keywords and respect these limitations and instructions:

  • In project settings, set the /Zp1 compiler directive. [C/C++->Code Generation->Struct Member Alignment->1 Byte (/Zp1)]
  • Static constructors are not supported directly (you must create your own initialization of static members).
  • Attributes are not supported.
  • Destructors are the mechanism for performing cleanup operations. Destructors provide appropriate safeguards, such as automatically calling the base type's destructor. Such as in C# code, they present the object's Finalize method.
  • When two objects keep full references (gcptr not gcmember) to each other, it must be guaranteed that they will drop their references before leaving them for GC. (You can implement the System.IDisposable interface or simply, in code, set the reference to nullptr before the application will leave the objects.)
  • When an object implements the System.IDisposable interface, it is recommended to override the protected destructor (finalizer method) and add the call to the Dispose method. The same is true when the Dispose method has been overridden in later classes.
  • All reference type arguments must be declared with the gcptr macro or gcmember as a class member declaration.
  • Value types as reference or out parameter must be declared using the gcref and gcout macros.
  • Properties must be defined using the appropriate macros.
  • Events must be declared using the event macro.
  • Indexers must be defined using the appropriate macros and are limited for one parameter only (only one-dimensional indexers are supported).
  • Delegates must be defined using the appropriate delegate macro.
  • Default indexers must be called in certain terms: this->default[index] (cannot be called as this[index] only by arrays).
  • Every interface class must be derived from the System.Object class using public virtual inheritance and must have a protected default constructor.
  • Deriving from interfaces must always be declared with public virtual.
  • Deriving from the System.Object class must always be declared with public virtual.
  • Every class used in the framework must be derived from the System.Object (Remarks: Stuctures are not classes, but value objects).
  • Exception handling must use the following macros: throw, try, catch, and end_catch.
  • Throwing exception using the throw(exception) statement should be enclosed in bracelets {} to avoid compilation errors when used in an if-else statement.
  • finally statement is not supported.
  • Try to avoid throwing an exception inside a constructor.
  • Array types must be defined as gcptr(array<type, dim>) when it supports one-, two-, or three-dimensional arrays only.
  • Try to avoid implementing a structure (value type) object which holds any reference type, there cannot be solved cyclic references because the structure is not derived from System.Object.

Known bugs

An unhandled exception raised within a constructor can ruin the garbage collector consistency, but there is no simple way to block this unpleasant effect with the current C++ syntax and the operator new semantics.

Using the code

A brief description of how to use the library code is almost impossible, therefore I placed the example project within the library solution whose snippet is shown below. There are some macros and special keywords which help to follow the C#-like syntax and allow to define "managed" classes, members, and many more. Some constructs are quite complicated, but everything is deducible, studying the source code is the key :)

C++
// Example.cpp : Defines the entry point for the console application.
//

#include "Framework.h"

using namespace System;
using namespace System::Collections;
using namespace System::Threading;

MAIN_FUNCTION_IMPL(_tmain)

delegate(System::Guid, ExecutingHandler, (gcptr(System::Object)));
delegate(System::Guid, ExecuteDelegate, ());

extern void DelegatesDemo();
extern void SocketsDemo();
extern void SerialDemo();
extern void EventsDemo();

// --- DemoLunched enum ---

struct DemoLunched : cli::TEnum<DemoLunched, true>
{
public:
    enum States
    {
        Halt = 0,
        Running
    };

    ENUM_DECL(DemoLunched, States, Halt);
};

ENUM_BEGIN_STRINGS(DemoLunched)
{
    _T("Halt"),
    _T("Running")
}
ENUM_END_STRINGS

ENUM_BEGIN_VALUES(DemoLunched)
{
    DemoLunched::Halt,
    DemoLunched::Running
}
ENUM_END_VALUES

ENUM_IMPL(DemoLunched)

// --- DelegatesDemoLuncher class --- 

class DelegatesDemoLuncher : public virtual System::IDisposable
{
public:
    event(ExecutingHandler, DelegatesDemoLuncher, Executing);

    PROPERTYGETSET_DECL(DemoLunched, State);

private:
    gcmember(System::Object) _self;

public:
    DelegatesDemoLuncher();
    override ~DelegatesDemoLuncher();
    virtual System::Guid Execute(gcptr(System::Object) obj);
    override void Dispose();
    System::Guid OnExecuting();
};

PROPERTYGETSET_IMPL(DemoLunched, State, DelegatesDemoLuncher)

DelegatesDemoLuncher::DelegatesDemoLuncher()
    : _self(this),
        Executing(this),
        PROPERTYGETSET_INIT(DemoLunched, State, DelegatesDemoLuncher)
{
    this->State = DemoLunched::Halt;

    this->Executing += gcnew ExecutingHandler(this, &DelegatesDemoLuncher::Execute);

    _self = this;
}

DelegatesDemoLuncher::~DelegatesDemoLuncher()
{
}

System::Guid DelegatesDemoLuncher::Execute(gcptr(System::Object) obj)
{
    this->State = DemoLunched::Parse(_T("Running"));

    Console::WriteLine(obj);
    Console::WriteLine();
    DelegatesDemo();
    return Guid::NewGuid();
}

void DelegatesDemoLuncher::Dispose()
{
    this->State = DemoLunched::Halt;

    this->Executing -= gcnew ExecutingHandler(this, &DelegatesDemoLuncher::Execute);

    Console::WriteLine(_T("DelegatesDemoLuncher disposed."));
}

System::Guid DelegatesDemoLuncher::OnExecuting()
{
    return this->Executing(this);
}

// --- Helper functions ---

static void WriteChar(Char c)
{
    Console::Write(c);
}

void Execution()
{
    Console::WriteLine(_T("Directory list of \"C:\\Program Files\":\n"));
    gcptr(array<gcptr(System::String)>) dirs =
          System::IO::Directory::GetDirectories(_T("C:\\Program Files"));
    for (int i=0; i<dirs->Length; i++)
        Console::WriteLine(dirs[i]);

    Console::Write(_T("Reading Test.xml..."));
    gcptr(System::IO::FileStream) fs =
          gcnew System::IO::FileStream(_T("Test.xml"), System::IO::FileMode::Open, 
          System::IO::FileAccess::Read, System::IO::FileShare::Read, 4000, true);
    gcptr(array<Byte>) ba = gcnew array<Byte>(1000);
    gcptr(System::IAsyncResult) ares = fs->BeginRead(ba, 0, 700, nullptr, nullptr);
    ares->AsyncWaitHandle->WaitOne();
    fs->EndRead(ares);
    int read = fs->Read(ba, 700, 100);
    ba[700+read] = fs->ReadByte();
    Byte ba700 = ba[700+read];
    read = fs->Read(ba, 801, 100);
    ba[801+read] = 0;
    fs->Close();
    Console::WriteLine(_T("Done\n"));

    gcptr(System::String) xmlData = gcnew System::String((const char *)&(ba[0]));
    cli::XML::StringXMLReader reader(cli::StringBufferAccessor::GetString(xmlData));
    cli::XML::XMLStreamArchive ar(&reader, cli::XML::XMLStreamArchive::eRead);

    ar.SkipHeader();
    ar.Read();
    cli::XML::XMLString str = ar.GetName();

    gcptr(IDisposable) disposableObj = gcnew DelegatesDemoLuncher();
    gcptr(DelegatesDemoLuncher) ddl = safe_cast<DelegatesDemoLuncher>(disposableObj);

    gcptr(array<gcptr(DelegatesDemoLuncher)>) ddla = 
                   gcnew array<gcptr(DelegatesDemoLuncher)>(1);
    ddla[0] = ddl;

    gcptr(ExecuteDelegate) mcd;
    mcd += gcnew ExecuteDelegate(ddla[0], &DelegatesDemoLuncher::OnExecuting);

    System::Guid g = mcd();
    gcptr(array<System::Byte>) byteArray = g.ToByteArray();
    Array::Sort(byteArray);
    Array::Reverse(byteArray);
    gcptr(System::String) s = System::Guid(byteArray).ToString();
    gcptr(array<gcptr(System::String)>) ss = s->Split(_T('-'));
    s = String::Join(_T("-"), ss);
    s = String::Concat(ddl->ToString(), _T(" - {"), s, _T("}"));

    try
    {
        gcptr(System::Object) co = s[0];

        // The lock of object will be released when the exception has been raised 
        // within the locked part of code
        lock(ddl)
        {
            // Index out of range exception will be trown.
            Char c = s[1000];
        }
        end_lock
    }
    catch(IndexOutOfRangeException, ex)
    {
        Console::WriteLine(_T("\nException: {0}"), ex->Message);
        Console::Write(_T(" -> "));
        Console::WriteLine(ex->StackTrace);
        Console::WriteLine();
    }
    end_catch
}

// --- Main function ---

void Main(gcptr(array<gcptr(System::String)>) args)
{
    Console::WriteLine(_T("Total memory at beginning is: {0}.\n"), 
                       GC::GetTotalMemory(false));
    Console::WriteLine(String::Empty);
    
    gcptr(System::Threading::Thread) t =
        gcnew System::Threading::Thread(gcnew System::Threading::ThreadStart(Execution));
    t->Start();
    t->Join();
    
    Console::WriteLine(_T("\nTotal memory before collection is: {0}."), 
                       GC::GetTotalMemory(false));
    Console::WriteLine(_T("Total memory after collection is: {0}."), 
                       GC::GetTotalMemory(true));
}

History

  • 1.0 - 23 Nov 2011: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)