Manipulate managed and native objects in C++ to show the registry in a WPF TreeView
Last time, we looked at how easy it is to add managed code to your existing C++ application. (Call managed code from your C++ code)
The sample below shows a more substantial C++ program which
- Liberally intermixes both native and managed objects in C++ code for demo purposes.
- Reads the registry recursively using native C++ code to call Windows API
- Stores strings in both a native STL vector and a managed List<String> to demonstrate using these techniques
- Also stores strings in a TreeViewItem
- Uses Windows Presentation Foundation (WPF) to display the registry data in a TreeView
- Subscribes to a WPF event Window->OnLoaded
We’ll start by creating a new C++ project.
File->New->Project->C++ General->Empty Project CppClr
Source->Files->Right-Click->Add New C++ File “CppClr”
Paste in the code below
Now change the project type to be Windows (not Console) and set the new entry point:
// Project->Properties
// set Linker->System->SubSystem Windows (/SUBSYSTEM:WINDOWS)
// Set Linker->Advance->entry point = mymain
Because we’re using WPF, we must set our main thread to be Single Threaded Apartment (STA). To do this for a C++/CLI project, we need to define our own entry point that gets called before the default entry point. One way to do this is to define a custom entry point “MyMain” that
- is marked with the STA attribute
- initializes COM as STA
- then calls mainCRTStartup to initialize the C Runtime library and calls our “main” program
If we comment out the STA attribute line, we get exceptions like:
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationCore.dll Additional information: The calling thread must be STA, because many UI components require this.
<code>
/*
File->New->Project->C++ General->Empty Project CppClr
Source->Files->Right-Click->Add New C++ File
// Project->Properties
// set Linker->System->SubSystem Windows (/SUBSYSTEM:WINDOWS)
// Set Linker->Advance->entry point = mymain
*/
// CppClr.cpp : main project file.
//#include "stdafx.h"
#include "objbase.h"
#include "atlbase.h"
#include "string"
#include "vector"
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Controls;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;
using namespace std;
extern "C" void mainCRTStartup();
public ref class MyWindow : Window
{
String ^_strReg;
int _nTotal;
public:
MyWindow()
{
_strReg = gcnew String(
L"Software\\Microsoft\\VisualStudio"
);
Title = _strReg;
Height = 500;
Width = 500;
// subscribe to managed event
this->Loaded +=
gcnew RoutedEventHandler(
this,
&MyWindow::OnLoaded);
}
void OnLoaded(Object ^Sender, RoutedEventArgs ^e)
{
// create a new treeview
auto tv = gcnew TreeView();
// get the content
List<String^>^ lst = GetRegKeys(_strReg, tv);
// add content to the form
this->Content = tv;
// set the title
this->Title =
String::Format(
"# reg keys {0} {1}",
_nTotal,
_strReg);
}
//GetRegKeys is a native method that
// gets the reg keys, adds them to the passed in
// ItemsControl
// both TreeView and TreeViewItem are ItemsControls
// The parameters are both managed
// Even though not used, return a generic List<String>
// to demonstrate usage.
List<String^>^ GetRegKeys(
// a managed string
String ^ strReg,
ItemsControl ^itemsControl
)
{
// create a vector. Can't mix native/managed
// (can't have a vector of a System.String)
vector<wstring> vecKeys;
// create a generic list<string>
auto lst = gcnew List<String ^>();
// convert the Managed string to an IntPtr
IntPtr wstrReg =
Marshal::StringToHGlobalUni(strReg);
// CRegKey is an ATL class,
// with a dtor to close the key
CRegKey regkey;
DWORD dwResult = regkey.Open(
HKEY_CURRENT_USER,
(WCHAR *) wstrReg.ToPointer(), //IntPtr to WCHAR
KEY_READ);
// don't leak this guy
Marshal::FreeHGlobal(wstrReg);
if (ERROR_SUCCESS == dwResult)
{
for (int nIndex = 0; ; nIndex++)
{
wstring strKey(200, L'\0'); // native string
DWORD nLen = strKey.length();
// enumerate the key
if (ERROR_SUCCESS !=
regkey.EnumKey(nIndex, &strKey[0], &nLen))
{
break;
}
// add the native string to the native vector
vecKeys.push_back(strKey);
// create a managed string and add it to the list
String ^ str = gcnew String(strKey.data());
lst->Add(str);
//create & store item in WPF TreeViewItem
auto tvItem = gcnew TreeViewItem();
itemsControl->Items->Add(tvItem);
auto childLst = GetRegKeys(
strReg + "\\" + str,
tvItem
); // recur
// set the text
tvItem->Header = strReg+"\\"+ str;
tvItem->IsExpanded = true;
}
}
_nTotal += lst->Count;
return lst;
}
};
// we need a new entry point
// so we can set the STAThread attribute on
// the main thread
[System::STAThread]
int mymain() //the new entry point so we can set STAThread
{
//If we need COM, Init COM as single model apartment
//HRESULT hr = CoInitializeEx(0,COINIT_APARTMENTTHREADED);
//Initialize the CRT,
// which also calls our "main" program
mainCRTStartup();
//uninit
//CoUninitialize();
return 0;
}
// this main is called from CRuntime mainCRTStartup
int main()
{
Console::WriteLine(L"Hello World");
// create a new WPF Window
auto win = gcnew MyWindow();
// show it modally
win->ShowDialog();
return 0;
}
</code>