Introduction
When you design wrappers around unmanaged classes, it might come up that you need to wrap MFC collections, e.g.,
CStringList
s.
One way to (partially) do so is to wrap them as IEnumerable<T>
.
Background
CStringList
uses a POSITION
structure to iterate through the list. Basically, a "kinda-thin" wrapper layer
that can store this position internally
and provide the outside world with a nice IEnumerable
interface.
#pragma once
using namespace System;
using namespace System::Collections;
namespace MFCWrapper
{
public ref class CStringListWrapper : Generic::IEnumerable<String^>
{
public:
virtual Generic::IEnumerator<String^>^ GetEnumerator() sealed =
Generic::IEnumerable<String^>::GetEnumerator
{
return gcnew StringListEnumerator(this);
}
virtual System::Collections::IEnumerator^ GetEnumeratorBase() sealed =
System::Collections::IEnumerable::GetEnumerator
{
return GetEnumerator();
}
CStringList * GetUnmanagedPointer()
{
return m_pStringList;
}
void SetUnmanagedPointer(CStringList * pList)
{
if(m_pStringList) {
delete m_pStringList;
m_pStringList = NULL;
}
m_pStringList = pList;
}
void AddHead(String ^ str)
{
if(str == nullptr)
return;
m_pStringList->AddHead((CString) str);
}
void AddTail(String ^ str)
{
if(str == nullptr)
return;
m_pStringList->AddTail((CString) str);
}
bool Remove(String ^ str)
{
if(str == nullptr)
return false;
CString strNative = (CString) str;
POSITION pos = m_pStringList->Find(strNative);
if(pos)
{
m_pStringList->RemoveAt(pos);
return true;
}
return false;
}
int RemoveAll(String ^ str)
{
if(str == nullptr)
return 0;
int numRemoved = 0;
CString strNative = (CString) str;
POSITION pos = m_pStringList->Find(strNative);
while(pos)
{
m_pStringList->RemoveAt(pos);
++numRemoved;
pos = m_pStringList->Find(strNative);
}
return numRemoved;
}
void Clear()
{
m_pStringList->RemoveAll();
}
property int Count
{
int get()
{
return m_pStringList->GetCount();
}
}
CStringListWrapper(){ SetUnmanagedPointer(new CStringList()); }
internal:
CStringListWrapper(CStringList * pList){ SetUnmanagedPointer(pList); }
!CStringListWrapper(void) { if(m_pStringList){ delete m_pStringList; m_pStringList = NULL; } }
virtual ~CStringListWrapper(void){ this->!CStringListWrapper(); }
private:
ref struct StringListEnumerator : public Generic::IEnumerator<String^>
{
public:
StringListEnumerator(CStringListWrapper ^ wrapper)
{
m_wrapper = wrapper;
m_pos = m_wrapper->GetUnmanagedPointer()->GetHeadPosition();
m_element = nullptr;
}
property String ^ Current
{
virtual String^ get()
{
return m_element;
}
};
property Object^ CurrentBase
{
virtual Object^ get() sealed = System::Collections::IEnumerator::Current::get
{
return Current;
}
};
virtual bool MoveNext()
{
if(m_pos == NULL)
return false;
POSITION curPos = m_pos;
CString strCurrent = m_wrapper->GetUnmanagedPointer()->GetNext(curPos);
m_element = gcnew String (strCurrent);
m_pos = curPos;
return true;
}
virtual void Reset()
{
m_pos = m_wrapper->GetUnmanagedPointer()->GetHeadPosition();
}
virtual ~StringListEnumerator()
{
}
private:
String ^ m_element; POSITION m_pos; CStringListWrapper ^ m_wrapper;
};
CStringList * m_pStringList; };
}
Using the code
You can then iterate through the list, add/remove elements, etc., as this C# sample program illustrates:
using System.Diagnostics;
using MFCWrapper;
namespace MfcWrapperTest
{
class Program
{
static void Main(string[] args)
{
CStringListWrapper wrapper = new CStringListWrapper();
DumpList(wrapper);
wrapper.AddHead("two");
wrapper.AddTail("three");
wrapper.AddHead("one");
DumpList(wrapper);
wrapper.AddTail("three");
wrapper.AddTail("three");
wrapper.AddTail("three");
wrapper.RemoveAll("three");
wrapper.Remove("two");
DumpList(wrapper);
wrapper.RemoveAll("one");
wrapper.AddHead("five");
wrapper.AddTail("six");
wrapper.AddHead("four");
DumpList(wrapper);
wrapper.Clear();
DumpList(wrapper);
}
static void DumpList(CStringListWrapper myStringListWrapper)
{
foreach (string s in myStringListWrapper)
Trace.WriteLine(s);
}
}
}
Points of Interest
Similar wrappers can be done with:
CPtrList
, especially if the type of the pointers inside the list is known, i.e., the pointers themselves can be wrapped. In this case you could wrap to an
IEnumerable<T>
.CObList
. Again, if the runtime classes of the objects inside the List are known, the List can be wrapped as
IEnumerable<T>
.CMapStringToString
, CMapStringToPtr
, etc.: The code can be adapted to wrap
CMap
-classes to IDictionary<TKey, TValue>
.
For list classes, it might be suitable to instead wrap to IList
<T>
, which basically means additionally implementing
ICollection<T>
.