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:
cli | The 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::XML | Helper and tool classes for reading, writing, and parsing XML data. |
cli::XML::Streaming | Namespace with helper classes and templates for streaming
XML. |
System | The 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::Collections | The System.Collections namespace contains interfaces and classes that define various collections of objects, such
as lists, queues, bit arrays, hash tables, and dictionaries. |
System::ComponentModel | The System.ComponentModel namespace provides classes that are used to implement the run-time and
design-time behavior of components and controls. |
System::Diagnostics | The System.Diagnostics namespace provides classes that allow you to interact with system processes, event logs, and debugging traces. |
System::IO | The 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::Ports | The 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::Net | The System.Net namespace provides a simple programming interface for many of the protocols used on networks today. |
System::Net::Sockets | The 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::Resources | The System.Resources namespace provides classes and interfaces that allow developers to create, store, and manage
various culture-specific resources used in an application. |
System::Runtime | The
System.Runtime namespace
contains advanced types that support diverse namespaces such as System , the Runtime namespaces, and the
Security namespaces. |
System::Runtime::InteropServices | The System.Runtime.InteropServices namespace provides a wide variety of members that support platform invoke services. |
System::Security | The System.Security namespace provides the underlying structure of the common language runtime security system,
including base classes for permissions. |
System::Security::AccessControl | The System.Security.AccessControl
namespace provides programming elements that control access to and audit security-related actions on securable objects. |
System::Threading | The 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 :)
#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();
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)
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);
}
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];
lock(ddl)
{
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
}
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.