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

  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>