Introduction
ACF (Another C++ Framework) is a C++ framework designed to bring the power and RAD of the .NET framework to standard C++. The overall vision of version 0.3 is to deliver a more robust and ready to use Corlib.
1. Type system
Basic RTTI support
ACF 0.3 supports basic RTTI through Type
, Object::GetType()
, and macro TYPEOF(T)
. The implementation is based on standard C++ RTTI, namely type_info
and typeid
. Currently, there is no plan to support advanced reflection functionalities in the .NET framework (e.g., calling a method at runtime).
The following sample code shows how to use GetType
and TYPEOF
.
ObjectPtr obj = str(L"hello");
bool equals = (obj->GetType() == TYPEOF(String));
Following is the equivalent code in C#:
object obj = "hello";
bool equals = (obj.GetType() == typeof(string));
Value types boxing/unboxing unification
In ACF 0.3, all value types are boxed to type Boxed<T>
(e.g.: Boxed<Int32>
, Boxed<DateTime>
) through template specialization for consistency purpose. For example, the following code checks whether an object is int
, and if so, reads its value:
ObjectPtr obj = box(10);
...
Boxed<Int32>* intObj = dynamic_cast<Boxed<Int32>*>(obj.Pointer);
if (intObj != null) {
int n = intObj->Value;
}
Of course, the example can also be written as follows, if try
/catch
is preferred and you don't need to change the value of the object:
ObjectPtr obj = box(10);
...
try {
int n = unbox<int>(obj);
}
catch (...) {
}
2. Base types parsing and formatting
Base types parsing and formatting are updated in ACF 0.3 (however, currently there is no support for cultures). For example, the following code shows how to format integers using various formats:
Int32 n = 123456789;
Console::WriteLine(n.ToString(L"C"));
Console::WriteLine(n.ToString(L"E"));
Console::WriteLine(n.ToString(L"P"));
Console::WriteLine(n.ToString(L"N"));
Console::WriteLine(n.ToString(L"F"));
The following function formats file sizes as shown in Windows Explorer (e.g., "3,012 KB"):
StringPtr FormatFileSize(int64 size) {
Int64 kb = size / 1024;
if (size % 1024 != 0)
kb++;
return kb.ToString(L"N0") + str(L" KB");
}
3. Arrays and collections
Multi-dimensional arrays
ACF 0.3 supports multi-dimensional arrays. The array class definition is as follows:
template <typename T, int R>
class Array : ... {
Array(int (&lengths)[R]);
T GetValue(int (&indices)[R]);
void SetValue(InArgType value, int (&indices)[R]);
...
};
template <typename T>
class Array<T, 1> : ... {
Array(int length);
T GetValue(int index);
void SetValue(InArgType value, int index);
static int BinarySearch(Array<T, 1>* array, InArgType value,
IComparer<T>* comparer = null);
static void Sort(Array<T, 1>* array,
IComparer<T>* comparer = null);
...
};
template <typename T>
class Array<T, 2> : ... {
Array(int length1, int length2);
T GetValue(int index1, int index2);
void SetValue(InArgType value, int index1, int index2);
...
};
...
As you can see, the implementation uses template partial specialization for various array ranks (especially for single dimensional arrays). This is because certain methods, such as BinarySearch
, work only on single dimensional arrays. In C#, if you call BinarySearch
on a two dimensional array, it compiles OK but will throw a RankException
at runtime. In ACF, this will be a compile-time error.
The following sample code shows how to use three dimensional arrays:
RefPtr<Array<int, 3> > array3D = new Array<int, 3>(10, 20, 30);
array3D->SetValue(100, 0, 0, 0);
Following is the equivalent code in C#:
int[,,,] array3D = new int[10, 20, 30];
array3D[0, 0, 0] = 100;
Comparing and sorting (esp. case insensitive)
ACF 0.3 supports comparing interfaces and classes like IComparer<T>
, Comparer<T>
, and StringComparer
(which are introduced in .NET 2.0). For example, the following sample code sorts a string array using case insensitive invariant culture:
RefPtr<Array<StringPtr> > array =
Array<StringPtr>::Build(str(L"hello"), str(L"world"));
Array<StringPtr>::Sort(array,
StringComparer::get_InvariantCultureIgnoreCase());
Following is the equivalent code in C#:
string[] array = new string[] { "hello", "world" };
Array.Sort(array, StringComparer.InvariantCultureIgnoreCase);
The following sample code shows how to use case insensitive hash table:
RefPtr<Dictionary<StringPtr, int> > dict =
new Dictionary<StringPtr, int>(StringComparer::get_InvariantCultureIgnoreCase());
dict->Item[str(L"Test")] = 10;
int n = dict->Item[str(L"test")];
Following is the equivalent code in C#:
Dictionary<string, int> dict =
new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
dict["Test"] = 10;
int n = dict["test"];
4. Registry support
ACF 0.3 provides support for accessing Windows registry (under namespace Acf::Microsoft::Win32
). The RegViewer sample application shows how to use the registry classes.
For example, the following code shows how to respond to TVN_ITEMEXPANDING
notification and expand the tree view node (exception handling code omitted):
void CRegViewerDlg::OnTvnItemexpandingTree1(NMHDR *pNMHDR, LRESULT *pResult) {
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
*pResult = 0;
if (pNMTreeView->action == TVE_EXPAND &&
(pNMTreeView->itemNew.state & TVIS_EXPANDEDONCE) == 0) {
RegistryKey* parentKey =
reinterpret_cast<RegistryKey*>(pNMTreeView->itemNew.lParam);
RefPtr<Array<StringPtr> > subKeyNames = parentKey->GetSubKeyNames();
Array<StringPtr>::Sort(subKeyNames,
StringComparer::get_InvariantCultureIgnoreCase());
FOREACH (StringPtr, keyName, subKeyNames) {
RegistryKeyPtr subKey = parentKey->OpenSubKey(keyName);
InsertTreeNode(subKey, keyName,
pNMTreeView->itemNew.hItem, TVI_LAST);
}
}
}
The following code adds keys to the tree view:
HTREEITEM CRegViewerDlg::InsertTreeNode(RegistryKey* key, String* name,
HTREEITEM hParent, HTREEITEM hInsertAfter) {
key->AddRef();
CW2T nameT(name->GetCString());
TVINSERTSTRUCT tvi;
memset(&tvi, 0, sizeof(tvi));
tvi.hParent = hParent;
tvi.hInsertAfter = hInsertAfter;
tvi.item.mask = TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM;
tvi.item.pszText = nameT;
tvi.item.lParam = reinterpret_cast<LPARAM>(key);
tvi.item.cChildren = key->SubKeyCount;
return this->_treeCtrl.InsertItem(&tvi);
}
As you can see, we should call AddRef
when adding the key to the tree view to comply with our reference counting rules. We should release it in the TVN_DELETEITEM
notification:
void CRegViewerDlg::OnTvnDeleteitemTree1(NMHDR* pNMHDR, LRESULT* pResult) {
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
*pResult = 0;
RegistryKey* key =
reinterpret_cast<RegistryKey*>(pNMTreeView->itemOld.lParam);
key->Release();
}
5. Other minor updates and clean-up
For example, Directory::GetLogicalDrives
.