|
Introduction
This article will describe how to utilise Windows Forms controls outside of
.NET. In a recent MSDN magazine article on .NET Interop available here,
various ways of exposing .NET objects to 'legacy' environments are discussed,
including the exposure of Windows Forms controls as ActiveX controls.
The problem is that the goalposts have
moved since the article was written as Beta 2 is now available, and
unfortunately this support has been removed - see this posting
on the .NET list at http://discuss.develop.com.
The following image shows a control, written purely within .NET, hosted
within an ActiveX control container - in this instance tstcon32.exe.

As Beta1 supported this facility, and being somewhat inquisitive, I decided
to see if I could find a way to expose controls anyway. The attached project
creates the 'Prisoner' control, which won't set the world on fire but does show
the main things you need to do in order to get a .NET control up & running
within VB6.
CAVEAT: As this support has been dropped from Beta2 of .NET, don't
blame me if it fries your PC or toasts the cat.
Now that's out of the way, how's it done?.
Writing the control
- Create a new control project from within Visual Studio
- my examples are all in C# but VB.NET could also be used.
- Add controls etc to the form, put in the code etc.
- Add in the following using clauses...
using System.Runtime.InteropServices;
using System.Text;
using System.Reflection;
using Microsoft.Win32;
- Attribute your class so that it gets a ProgID. This
isn't strictly necessary as one will be generated, but it's almost always best
to be explicit.
[ProgId("Prisoner.PrisonerControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
This assigns the ProgID, and also defines that the interface exposed should
be 'AutoDual' - this crufts up a default interface for you from all public,
non-static members of the class. If this isn't what you want, use one of the
other options.
- Update the project properties so that your assembly is
registered for COM interop.

If you're using VB.NET, you also need a strong named assembly. Curiously in
C# you don't - and it seems to be a feature of the environment rather than a
feature of the compiler or CLR.
- Add the following two methods into your class.
[ComRegisterFunction()]
public static void RegisterClass ( string key )
{
StringBuilder sb = new StringBuilder ( key ) ;
sb.Replace(@"HKEY_CLASSES_ROOT\","") ;
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
RegistryKey ctrl = k.CreateSubKey ( "Control" ) ;
ctrl.Close ( ) ;
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
inprocServer32.SetValue ( "CodeBase" , Assembly.GetExecutingAssembly().CodeBase ) ;
inprocServer32.Close ( ) ;
k.Close ( ) ;
}
The RegisterClass function is attributed with ComRegisterFunction - this static
method will be called when the assembly is registered for COM Interop. All I do
here is add the 'Control' keyword to the registry, plus add in the
CodeBase entry.
CodeBase is interesting - not only for .NET controls. It defines a URL path
to where the code can be found, which could be an assembly on disk as in this
instance, or a remote assembly on a web server somewhere. When the runtime
attempts to create the control, it will probe this URL and download the control
as necessary. This is very useful when testing .NET components, as the usual
caveat of residing in the same directory (etc) as the .EXE does not apply.
[ComUnregisterFunction()]
public static void UnregisterClass ( string key )
{
StringBuilder sb = new StringBuilder ( key ) ;
sb.Replace(@"HKEY_CLASSES_ROOT\","") ;
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
k.DeleteSubKey ( "Control" , false ) ;
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
k.DeleteSubKey ( "CodeBase" , false ) ;
k.Close ( ) ;
}
The second function will remove the registry entries added when (if) the
class is unregistered - it's always a good suggestion to tidy up as you go.
Now
you are ready to compile & test your control.
Testing the ControlFor
this example I have chosen tstcon32.exe, which is available with the
installation of .NET. The main reason I've used this rather than VB6 is that I
don't have VB6 anymore. Inserting the ControlFirst up you need to
insert your control, so to do that choose Edit -> Insert New Control,
and choose your control from the dropdown...

This will result in a display as shown at the top of the article, if you're
following along with my example code.
Testing Methods
The example control only includes one method, 'Question'. To test this within
TstCon32, choose Control -> InvokeMethods from the menu, and select
the method you want to call. Note that because I defined the interface as
AutoDual, I get gazillions of methods. If you implement an interface and expose
this as the default interface then the list of methods will be more manageable.

Click on the 'Invoke' button will execute the method, which in this instance
displays the obligatory message box.
Wrap Up
Dropping support for creating ActiveX controls from Windows Forms controls is
a pain, and one decision I wish Microsoft had not made.
This article presents one way of exposing .NET controls as ActiveX controls,
and seems to work OK. Having said that, I've not exhaustively tested this and
who knows what bugs might be lurking in there. I haven't delved into events yet,
nor property change notifications, so there's some fun to be had there if you
like that sort of thing.
The .NET framework truly is the best thing since sliced bread, but the lack
of support for creating ActiveX controls from Windows Forms controls is inconvenient. There are many applications out there
(ours included) which can be extended with ActiveX controls. It would be nice given
the rest of the support in the framework to be able to expose Windows Forms
controls to ActiveX containers, and maybe someday the support will be available.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 144 (Total in Forum: 144) (Refresh) | FirstPrevNext |
|
|
 |
|
|
Hi~ How can I get any Acitvated and Deactivated event in my Windows.Forms.UserControl object same as normal Windows.Forms.Form object? I had tried many ways, but it's no work. Please help me to solve it!
Best regards, =============== Eric Hu
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I've just noticed that Microsoft have officially added support for embedding .NET forms and controls into VB6 with the new Interop Forms Toolkit 2.0[^]. You may also want to check out the videos online on MSDN[^].
Please feel free to completely ignore all of the above article!
Hooray!
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
This line:
k.DeleteSubKey ( "CodeBase" , false ) ;
Should Be:
inprocServer32.DeleteSubKey ( "CodeBase" , false ) ;
Write 100 lines of code, and include one bug.
Wayne Berry
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Excellent proof-of-concept. But a lot of the issues people are having are related to the lack of support in your C# library for the ActiveX specification, which is officially called the OCX 96 Technical Specification for "OLE Controls 96"; OLE/OCXs got renamed at the March 1996 PDC conference to "ActiveX". The version 1.1 guideline, OLE Control and Control Container Guidelines v1.1, specifies all the required and optional interfaces that must be implemented (although technically, any OLE/COM component that implements IUnknown is an ActiveX control). Thus, in this C# project you will have to implement quite a few more interfaces to get it to play "nicely" within the vast majority of host containers (many are quite trivial, others are a bear, especially the region clipping, resizing, and other similar cooperative UI rendering operations). Again, this is not a bash, dude. It's a good start, and the hard part you got working; bridging .NET and C++. Now you have to do all the nitty-gritty work of impementing the rest of the interfaces to make if a full fledged ActiveX control. If you're serious, then I would suggest creating a temporary project: create an ATL COM server and create an ATL lite control - this will show you the minimum required interfaces for the deprecated OLE Control 1.0 functionality. Then you can trace into and look at the ATL implementations for the aforementioned required interfaces (e.g. IOleInPlaceObjectImpl). Rinse, repeat, for a full control to get the updated "OLE Controls 96" (ActiveX) specification. Click here to view some ActiveX history, and here for the official Microsoft OLE Control and Control Container Guidelines v1.1.
S. Sean Stagner
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Yes... indeed it seems there is still much to be done... I've created the control as described in this article, but as I compile it I get several warning messages like the one bellow:
warning : Type library exporter warning processing 'myActiveXControl.myControl.get_Anchor(#0), myActiveXControl'. Warning: Non COM visible value type 'System.Windows.Forms.AnchorStyles' is being referenced either from the type currently being exported or from one of its base types
I could use the "Microsoft ActiveX Control Pad" to create a HTML and insert the ActiveX Control in it. It worked fine.  But... when I tried to use the same control in my Visual Studio Project or other applications I got an error...  I guess it might be 'cause it doesn't follow all OLE Control and Control Container Guidelines. I used VCONTAIN.EXE and VCONTROL.EXE to test it against the guidelines and there are a few that should be Mandatory and are not implemented. 
The question is: how can I add those missing methods and/or properties to the proposed ActiveX Control? If I fix it, will it work fine with other Visual Studio projects?
LEo
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
I compiled this code in VS2005, but when I try to insert it as a control into vb6 at design time I get message: "prisoner.dll was not registerable as an ActiveX component"
It inserts fine into tstcon32.exe. Any thoughts?
regards, Jeltz
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hello, you have to do following things to use .net control in vb 1. Create .Net control 2.Copy Register and UnRegister functions from the above article 3.Make ComVisible - True for the Control class 4. RegisterForComInterop - true ( set in Build properties) 4.make tlbExp for .net control
Now Open vb and open components window In the list, you will see your control name
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
If the system running web browser does not have .NET Framework, can we direct it to automatically download and install the same?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I have an ASSERT when the Container application written in MFC exists:
ASSERT(m_dwRef <= 1);
When the control is placed as static on the dialog it doesn't happen. I made the following steps:
- place the control in the dialog ( Insert ActiveX Control ) - assign a variable -> it creates a CWnd wrapper class - remove all functions which are not accepted ( or just leave the first functions GetClsID and 2 Create and DECLARE DYNCREATE ) - add the line to the OnInitDialog m_axCtrl.Create(_T("Prisoner"),WS_VISIBLE|WS_CHILD,CRect(0,0,200,200),this,1286); where m_axCtrl is the symbol assigned to the static control -remove DDX_Control(pDX, IDC_PRISONERCONTROL1, m_axCtrl); from DoDataExchange - handle ON_WM_DESTROY() to destroy the control : void CprisonerContainerDlg::OnDestroy() { m_axCtrl.DestroyWindow(); CDialog::OnDestroy(); }
Build and run, it correctly placed on the dialog but assert when exit.
This way works well for any another activex control. I have already replaced the COMRegister and unRegister functions to the correct one described above.
Can somebody help me? I have the same problem with any ActiveX control written in .NET. I can post the demo application if somebody needs it, contact me at domo@freemail.hu
Best Regards, Domo
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
I am having the exact same issue. When the control is added to the dialog via the resource editor, no problems. When the control is created dynamically during runtime, the ASSERT is hit and the control is never released.
Someone please help!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
After several mails I got the answer from MS:
Issue: =====
Hosting a System.Windows.Forms.UserControl form as an ActiveX control in native MFC causes the following debug assert in CCmdTarget::~CCmdTarget:
ASSERT(m_dwRef <= 1);
It doesn't matter if m_axCtrl.DestroyWindow() has been called before. Moreover, when the control is placed at design-time on the dialog (Insert ActiveX Control), the problem doesn't occur.
Customer doesn't want to use /clr option.
Objective: ========
ENVIRONMENT ----------------------- Visual Studio 2005
Assessment: ========== SUMMARY of TROUBLESHOOTING
-----------------------------------------------------
* Debugging has clearly shown that those instances of CCmdTarget that assert are referenced by managed objects through COM Interop, even when these native objects are already about to be deleted due to MFC shutdown.
* Calling IDisposable::Dispose on the managed object didn't help.
* Found KB Article that explains this is not supported scenario: Description of the supportability of Winforms controls in unmanaged applications
* This unsupported quasi-solution is to call CoEEShutDown to force shutdown of the CLR. As actually visible in the debugger, calling CoEEShutDown forces the release of the problematic references, along with all managed and unmanaged resources. Therefore the correct reference count is restored, and no native objects assert.
Rgds, Domo
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Domo,
I have a memory leak in my program and I am considering CoEEShutDown as a solution. But I have been unsuccessful in locating it. I tried mscorcfg.h , but there was no such function. Please let me know where to find "CoEEShutDown" and an example to use it.
DomoG wrote: * This unsupported quasi-solution is to call CoEEShutDown to force shutdown of the CLR. As actually visible in the debugger, calling CoEEShutDown forces the release of the problematic references, along with all managed and unmanaged resources. Therefore the correct reference count is restored, and no native objects assert.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
If you give me your e-mail address I will send you the example I got from MS ( send it to domo@freemail.hu).
Rgds, Domo
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi Domo,
Thank you for details of this post. Can you send me the information related to calling CoEEShutDown. Also do you have any more insight into how this reference counting can be stabilized in C# control?
Do let me know if I too can be of any help.
Thanks in advance, Suchit
http://www.Suchit-Tiwari.Org
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi I'm new with C# so please bear with me.
I downloaded your project and ran it in Microsoft Visual C# 2005. It worked great and I got it as an ActiveX. The problem: Microsoft Visual C# recognized the project as a class library, not a Windows Form. Is there a way for me to convert my written Windows Forms into a class library ?
|
| Sign In·View Thread·PermaLink | 1.50/5 (5 votes) |
|
|
|
 |
|
|
I am able to get the control working in tstcon32 but not in IE. It doesnt throw me any error in fusion logs too. I've Visual Studio 2003, Windows Server 2003, and IE 6. Can any one help me?
|
| Sign In·View Thread·PermaLink | 2.67/5 (5 votes) |
|
|
|
 |
|
|
Hallo, I tried your code, and everything works fine with tstcont32.
I have problems when I try to insert the prisonercontrol to an ocx in visual studio. First time I insert the control I get an error that the initialization does not work well. Then when I try to add a variable to it I get the following error from the Add Member Variable Wizard: The Exdenter Provided failed to return an Extender for this object. So I don't get a wrapper class for it, and I don't know how I can invoke its method.
Please help.
Thanks in advance,
Andrea
|
| Sign In·View Thread·PermaLink | 1.50/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
Yes. See the following code to register the class for COM. See also the link http://www.ondotnet.com/pub/a/dotnet/2003/01/20/winformshosting.html
using System.Runtime.InteropServices; // COM attributes using Microsoft.Win32; // RegistryKey … [ComRegisterFunction] static void ComRegister(Type t) { string keyName = @"CLSID\" + t.GUID.ToString("B"); using( RegistryKey key = Registry.ClassesRoot.OpenSubKey(keyName, true) ) { key.CreateSubKey("Control").Close(); using( RegistryKey subkey = key.CreateSubKey("MiscStatus") ) { subkey.SetValue("", "131457"); } using( RegistryKey subkey = key.CreateSubKey("TypeLib") ) { Guid libid = Marshal.GetTypeLibGuidForAssembly(t.Assembly); subkey.SetValue("", libid.ToString("B")); } using( RegistryKey subkey = key.CreateSubKey("Version") ) { Version ver = t.Assembly.GetName().Version; string version = string.Format("{0}.{1}", ver.Major, ver.Minor); if( version == "0.0" ) version = "1.0"; subkey.SetValue("", version); } } }
[ComUnregisterFunction] static void ComUnregister(Type t) { // Delete entire CLSID\{clsid} subtree string keyName = @"CLSID\" + t.GUID.ToString("B"); Registry.ClassesRoot.DeleteSubKeyTree(keyName); }
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
I've run your code on MSVC 2003 and run tstcont32.exe It all works fine but the tstcon32 process does not end
please have a look
I've used version 7.1.3088 and .NET frame version 1.1.4322 SP1
thanks
yaniv
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Same problem here. It seams that the tstcon32.exe process still remains running, even if its window is gone. Other applications react with access violation, if I try to reactive a once deativated control.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|