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

  1. is marked with the STA attribute
  2. initializes COM as STA
  3. 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>