.NET Control C++/CLI Treasure Trove

Maybe this article should have been called, “.NET Controls - Things that trip you up” or “.NET Controls - Making your life easier”. Regardless, this is a small compendium of issues that recently arose whilst I was writing an Interop layer over some Win32 Windows and COM Objects using C++/CLI.

Essentially, the mini-project was to convert some Win32 windows that we use regularly for inhouse purposes (eg a super-fast grid that can handle oodles of updates/sec). Some of these Win32 windows also have COM counterparts and these needed to be made more friendly.

In Win32 you create a window by calling CreateWindowEx() specifying the Window that you want to create. There are also many articles that can be found that use P/Invoke to do just that.

The code-happy fellows at Microsoft have also recently released WPF as part of the .NET Framework v3.0. In that they define a class HwndHostthat apparently provides a similar result. If you are interested in that, you can check out an intro here.

Our clients however, are reluctant to apply the most recent update of the .NET framework, meaning that we needed to be .NET 2.0 compatible (even though v3.0 is v2.0 with some extra functionality) . Thankfully, using C++/CLI one doesn’t need to worry about P/Invoke at all and since WPF is actually an extension onto .NET 2.0 we should be able to re-invent what WPF is doing by ourselves.

Getting started with C++/CLI

For those not yet ready to unleash the beast of writing for .NET in C++ here’s a quick primer:

  1. You need Visual Studio 2005 at least
  2. Forget the phrase ‘Managed Code’. This now refers to the recently deprecated functionality provided with prior versions of Visual Studio. If you do Google searches that contain ‘managed’ you generally get articles referring to older instances of .NET.
  3. You now use C++/CLI to specify that you want to write for .NET.
  4. Using C++/CLI, the compiler makes Interop just work (which the clever fellows at MS have coined IJW - ‘It Just Works‘), meaning that with a few exceptions, you can relax your brain and let the compiler deal with converting to/from native and managed code.
  5. Boxing is the term used to move across the native/managed barrier.

That should be enough words for the time being. Now lets look at writing a .NET control that uses an existing Win32 control.

Step1. UserControl or not UserControl

You may initially think to start Visual Studio and create yourself aSystem.Windows.Forms.UserControl, because that’s what you traditionally do when creating a .NET control. However, what we are actually doing is creating an Interop between Win32 and .NET architecture. So, we only need to use a System.Windows.Forms.Control rather than aSystem.Windows.Forms.UserControl. Have a look at the Class Browser for both of the classes and you’ll see what I mean: most of the meat that goes into creating a UserControl is actually presented in the Control class.

Step 2. System.Windows.Forms.Control initialises in a manner similar to a COM component (ActiveX object).

That is, once your control’s constructor has been called, you will get a slew of other things happening to your control before it actually gets created with Control::OnCreateControl(). Let me elaborate:

  • OnResize() will be called before OnCreateControl() is.
  • Property setters will be called before OnCreateControl() is. If you have a ‘ForeColor’ property defined on your control, then the ForeColor’s value will be set before OnCreateControl() is.

This of course is blatantly obvious if you look at what happens to a control when it is added to a Form. If you create a simple Windows Forms project and drop a button onto the form you will see the following inInitializeComponent() in Form1.Designer.cs:

this.button1 = new System.Windows.Forms.Button();
this.button1.Location = new System.Drawing.Point(98, 263);
this.button1.Size = new System.Drawing.Size(75, 23);

Knowing that InitializeComponent() has been called from your Form1’s constructor, you can see how OnCreateControl() hasn’t (and can’t) be called until later in the processing loop.

Fixing Step 2.

Simple. Simply create your Win32 control in your constructor.

This of course raises the immediate issue of ‘How do I get the Form’s parent window handle’. Again, the Form hasn’t actually been created yet either (because when the Control’s constructor is called, we are still in the Form’s constructor), meaning that trying to get the Form’s window handle is pointless.

The solution then is to do the following:

Use C++ Interop and call GetDesktopWindow() (a Win32 function that returns the HWND of the Desktop) to return a valid window handle. (We do this rather than using NULL since many Win32 controls do not like being instantiated without a valid parent). Essentially, you’ll have a call that looks like:

m_hWnd = CreateWindowEx(exStyle, “MyWin32Control”, …, GetDesktopWindow(), …);

Just make sure that you don’t specify WS_VISIBLE at this point, since it (potentially) can cause a flicker on your desktop.

“Cool!” I hear you say, “but that’s in the wrong place!”. Yes, it is, but we will fix it later.

Now, override OnCreateControl() and and add the re-parenting code there:

virtual void OnCreateControl(void) override
{
Control::OnCreateControl();
HWND hWndNewParent = (HWND)(Handle.ToPointer());
::SetParent(m_hWnd, hWndNewParent);
}

Easy, no?

In my next articles, I will touch upon the following topics:

  • Native/Managed pointers and how to beat the Circular Reference demon.
  • Attributes. Getting the Visual Studio Designer to do what you want (and not what you don’t!)
blog comments powered by Disqus