Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Integrate C# Window in C++ Project

0.00/5 (No votes)
10 Oct 2009 1  
This article describes how to integrate C# Window Form in C++ Project
Sample Image - maximum width is 600 pixels

Introduction

This article describes how to integrate C# Forms (also called Windows Forms) in C++ Window. Our C++ Window is built with pure Win32 API and doesn't require MFC.

Background

In general, Windows programming in C# is much easier than in C++, especially when we can't use MFC library in C++. Therefore many programmers prefer to build their projects in C# environment. But sometimes it is necessary to program in C++ environment. For example: when you want to develop an add-on to C++ application. We contend with this problem while developing a plug-in for, well known, Notepad++ application. Notepad++ is written in C++ and uses pure Win32 API and STL which ensures a higher execution speed and smaller program size. BUT, it also makes it difficult to design and develop UI. So, we took a challenge and decided to build our plug-in in C#. How we succeeded to integrate our .NET plug-in in Windows that builds with pure Win32 API? I hope that this article will help you understand.

We have built a small example for this article. If you prefer to view the whole source code of our plug-in for Notepad++, visit our plug-in homepage.

Creating ActiveX Control with C#

Basics

This part of the solution is based on the article that was posted by Morgan Skinner (Exposing Windows Forms Controls as ActiveX controls). Although Skinner provides his solution for Beta version of Visual Studio 8, his example works fine in the release version of VS8 (with small changes). Here is a list of changes we have made to Skinner's solution:

  1. Set ClassInterface to ClassInterfaceType.None (In this way, we expose only specific methods to COM). More information in the following section.
  2. The project should be set as visible to COM. This can be done via: Project Properties --> Application Tab --> Assembly Information --> Make assembly COM visible

  3. You should also register the project for COM Interop (Pay attention that in release version of VS8, Build Properties Window has a different design that in the beta version). When this feature enabled, Visual Studio will automatically register .NET ActiveX control after successful compilation.

  4. In Skinner's article, there is a little mistake in the ComUnregisterFunction() function. This is the correct function:
    /// <summary>
    /// Unregister ActiveX DLL function
    /// </summary>
    /// <param name="i_Key"></param>
    [ComUnregisterFunction()]
    public static void UnregisterClass(string i_Key)
    {
    	// strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
    	StringBuilder sb = new StringBuilder(i_Key);
    	sb.Replace(@"HKEY_CLASSES_ROOT\", "");
    
    	// open HKCR\CLSID\{guid} for write access
    	RegistryKey registerKey = 
    		Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);
    
    	// delete the 'Control' key, 
    	// but don't throw an exception if it does not exist
    	registerKey.DeleteSubKey("Control", false);
    
    	// next open up InprocServer32
    	RegistryKey inprocServer32 = 
    		registerKey.OpenSubKey("InprocServer32", true);
    
    	// and delete the CodeBase key, again not throwing if missing
    	inprocServer32.DeleteSubKey("CodeBase", false);
    
    	// finally close the main key
    	registerKey.Close();
    }	

Exposing Specific Methods to COM

For more accurate programming, we expose only specific methods to COM. Every external application that will use our control will receive access to necessary methods only.

The best way to expose specific methods is by creating an interface that contains all the relevant methods. Then, special attribute should be added to this interface. Form Class should implement this interface:

/// <summary>
/// COM Interface - enables to run c# code from c++
/// </summary>
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICSSExplorerInterface
{
	void setButtonCaption(String strNewCaption);
	void setAdapterDllPtr(IntPtr i_AdapterDllPtr);
}

Using Microsoft Message System

We use Microsoft Message System to communicate with our container window, and with other windows in VC++ project. We don't deal with events, because it's more complicated and it isn't necessary for our solution.

We added this code to our MyDotNetActiveX class to allow message transmit:

private static uint DOT_NET_BUTTON_PRESSED = 0x0400;

private void btnOK_Click(object sender, EventArgs e)
{
   SendMessage(m_AdapterDllPtr.ToInt32(), 
	DOT_NET_BUTTON_PRESSED, IntPtr.Zero, IntPtr.Zero);
}

#region MAPPING_OF_USER32_DLL_SECTION

[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(
	int hwnd, uint wMsg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
	int hwnd, uint wMsg, int wParam, string lParam);

[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
	int hwnd, uint wMsg, int wParam, out int lParam);

[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int GetNbFiles(
	int hwnd, uint wMsg, int wParam, int lParam);


[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int GetFileNames(
	int hwnd, uint wMsg,
	[MarshalAs(UnmanagedType.LPArray)]IntPtr[] wParam,
	int lParam);

[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
	int hwnd, uint wMsg, int wParam, StringBuilder lParam);

#endregion	

On initialization, we suppose that our container window will send us its handle (hwnd) for communication.

Compiling the Project

Now we are ready to compile and test the control. Visual Studio automatically registers our ActiveX control after successful compilation. You can see registered data via freeware RegDllView application.

Testing the Control on ActiveX Control Test Container

Before we jump to the next step in the article, this is a good time to test our control in third-party applications. For the test, we use ActiveX Control Test Container (tstcon32.exe). This application is available with the installation of Visual Studio.

  1. Insert the Control via Edit menu -->Insert New Control

  2. Now choose Control menu -->Invoke Methods
  3. Choose setButtonCaption from Method Name combo-box
  4. Type "Hello" in Parameter Value text-box & press Invoke button

  5. Here is the result of our test:

Adding C# ActiveX Control to C++ Window

Using ATL Control Containment

Any window can host ActiveX control by using Active Template Library (ATL).
In this part of the guide, we will:

  1. Create C++ Win32 Application Project
  2. Insert our ActiveX control into C++ window
  3. Send commands to the ActiveX
  4. Receive message from our ActiveX

Creating C++ Win32 Application Project

  1. Create new Win32 Project and name it CPP_Container:

  2. Use default settings and press OK:

Inserting C# ActiveX Control into C++ Window

  1. Add the following code at the beginning of CPP_Container.cpp:
    #define DOT_NET_BUTTON_PRESSED  0x0400
    
    HWND                                            _hAtl;
    HWND                                            _hSelf;
    IUnknown*                                       _pUnk;
    DotNetActiveX::ICSSExplorerInterfacePtr         _pDotNetCOMPtr;
    
    HINSTANCE _hWebLib = ::LoadLibrary(TEXT("ATL.DLL")); 
  2. When Visual Studio compiled our C# project, it created DotNetActiveX.tlb file. This file contains all public methods and structures in the project. We will import this data via the following command:
    // import C# control function and structures
    #import "DotNetActiveX.tlb" named_guids raw_interfaces_only 
  3. Add the following function to CPP_Container.cpp file. This function inserts ATL container to the Window and loads our C# ActiveX:
    void loadActiveX(LPCTSTR strActiveXName)
    {
        //Initialize ATL control containment code.
        BOOL (WINAPI *m_AtlAxWinInit)();
        m_AtlAxWinInit = (BOOL (WINAPI *)(void))::GetProcAddress
    				(_hWebLib, "AtlAxWinInit");
        m_AtlAxWinInit();
    
        // Get the dimensions of the main window's client 
        // area, and enumerate the child windows. Pass the 
        // dimensions to the child windows during enumeration. 
        RECT rcClient; 
    
        GetClientRect(_hSelf, &rcClient); 
    
        _hAtl = ::CreateWindowEx(
        		WS_EX_CLIENTEDGE,\
        		TEXT("AtlAxWin"),\
        		strActiveXName,\
        		WS_CHILD | WS_VISIBLE | /*WS_CLIPCHILDREN | */WS_EX_RTLREADING,\
        		0, 0, rcClient.right, rcClient.bottom,\
        		_hSelf,\
        		NULL,\
        		NULL,\
        		NULL);
    
        if (!_hAtl)
        {
        	MessageBox( NULL, TEXT("Can not load AtlAxWin!"), 
    				szTitle, MB_OK | MB_ICONSTOP);
        	throw int(106901);
        }
    
        HRESULT (WINAPI *m_AtlAxGetControl) (HWND h, IUnknown** pp);
        m_AtlAxGetControl = (HRESULT (WINAPI *) 
    		(HWND, IUnknown**))::GetProcAddress(_hWebLib, "AtlAxGetControl");
    
        m_AtlAxGetControl(_hAtl, &_pUnk);
    
        _pUnk->QueryInterface(__uuidof(DotNetActiveX::ICSSExplorerInterface),
    					(LPVOID *) &_pDotNetCOMPtr);
    
        if (_pDotNetCOMPtr != NULL)
        {
        	_pDotNetCOMPtr->setAdapterDllPtr((long) _hSelf);
        }
        else
        {
        	// Get the dimensions of the main window's client 
        	// area, and enumerate the child windows. Pass the 
        	// dimensions to the child windows during enumeration. 
        	RECT rcClient; 
        	GetClientRect(_hSelf, &rcClient);
    
        	::DestroyWindow(_hAtl);
        	_hAtl = ::CreateWindowEx(
        				WS_EX_CLIENTEDGE,\
        				TEXT("AtlAxWin"),\
        				TEXT("MSHTML:""Please register ActiveX 
    				    control before using this plugin."""),\
        				WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | 
    					WS_EX_RTLREADING,\
        				0, 0, rcClient.right, rcClient.bottom,\
        				_hSelf,\
        				NULL,\
        				NULL,\
        				NULL);
        }
    }  
  4. For accurate programming, add the following code in WM_DESTORY case in WndProc function:
    _pDotNetCOMPtr->Release();
    ::DestroyWindow(_hAtl);
    _pUnk->Release(); 
    ::FreeLibrary(_hWebLib); 
  5. Finally add a call to loadActiveX function in _tWinMain function:
    loadActiveX(TEXT("DotNetActiveX.MyDotNetActiveX")); 

Sending Commands to C# ActiveX Control

After TLB file insertion, C++ file is familiar with all the methods that we exposed in C# project. Now, we can simply call the relevant method:

char *strHelloWorld = "Hello World!";
_bstr_t bstrHelloWorld(strHelloWorld);
_pDotNetCOMPtr->setButtonCaption(bstrHelloWorld);

This will change button caption to "Hello World!"

Receiving Messages from C# ActiveX Control

Messages from C# control arrive via Microsoft Message System. We already sent the handle of our Window to the C# control (in loadActiveX function). So now, we only need to add some code in WndProc function. WndProc is the function that takes care of every message that arrives to the Window. So we will simply add additional case switch to this function:

case DOT_NET_BUTTON_PRESSED:
    MessageBox(NULL, TEXT("Message from C# arrived: Button Pressed!!"), 
	szTitle, MB_OK | MB_ICONINFORMATION);
    break;

Now, you can press the button in the C# ActiveX control and look at the results:

Conclusion

We hope that this article will help others to develop combined C# & C++ projects. You can check our CSSExplorer plug-in for Notepad++. All the ideas that are mentioned in this article are implemented in our plug-in.

References

  1. C# Programming Guide - Example COM Class (C# Programming Guide)
  2. How to add ATL control containment support to any window in Visual C++
  3. Exposing Windows Forms Controls as ActiveX controls

History

  • 10th October, 2009: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here