Thursday, 18 February 2010

Use .NET Controls in Delphi VCL Application

In the last couple of days I was occupied with a at the fist glance trivial task. I had to evaluate a possibility of using a .NET control for presentation of chemical formulas in Delphi. "So what's the matter, there is a .NET import wizard in Delphi" was my first thought. That would have been very easy, almost unbelievable, if worked at first attempt. The problem was, that the assembly was not compiled COM visible and thus not usable in Delphi. So here are the steps I had to make to achieve the desired result:

  • Wrap the assembly to make it COM visible. Create a new Windows Forms Control LibraryVisual Studio. You get a default UserControl1 class automatically created. Inherit in this class from the control you have to wrap. Now add following attributes to your class (guid and progid are your custom values):
[ComVisible(true)]
[Guid("f5df04c4-2bdf-4e5c-ba0d-c3464eabf9be")]
[ProgId("WindowsFormsControlLibrary1.UserControl1")]

  • Add register / unregister functions. Here comes a bit of magic. You have to expose your controls to COM and register them. Otherwise you'll get a Component in Delphi and not a visual Control you expect. Add these two methods RegisterClass and UnregisterClass to your class:

/// 
/// Register the class as a control and set it's CodeBase entry
/// 
///
/// The registry key of the control

[ComRegisterFunction()]
public static void RegisterClass(string key)
{
     // Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
     StringBuilder sb = new StringBuilder(key);
     sb.Replace(@"HKEY_CLASSES_ROOT\", "");

     // Open the CLSID{guid} key for write access
     RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);

     // And create the 'Control' key - this allows it to show up in
     // the ActiveX control container
     RegistryKey ctrl = k.CreateSubKey("Control");
     ctrl.Close();

     // Next create the CodeBase entry - needed if not string named and GACced.
     RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);
     inprocServer32.SetValue("CodeBase", Assembly.GetExecutingAssembly().CodeBase);
     inprocServer32.Close();

     // Finally close the main key
     k.Close();
}

/// 
/// 
/// Called to unregister the control
/// 
///
/// Tke registry key        

[ComUnregisterFunction()]
public static void UnregisterClass(string key)
{
     StringBuilder sb = new StringBuilder(key);
     sb.Replace(@"HKEY_CLASSES_ROOT\", "");

     // Open HKCR\CLSID{guid} for write access
     RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);

     // Delete the 'Control' key, but don't throw an exception if it does not exist
     k.DeleteSubKey("Control", false);

     // Next open up InprocServer32
     RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);

     // And delete the CodeBase key, again not throwing if missing
     k.DeleteSubKey("CodeBase", false);

     // Finally close the main key
     k.Close();
}

I found these methods in Embarcadero forum: https://forums.embarcadero.com/thread.jspa?threadID=14363
  • Do not forget to set the COM visibility for your assembly in the project properties:
  • Compile... Now you can Import the Assembly via the Delphi Import Wizard and install the Components in a Delphi package.

Events were exposed automatically. To expose the properties additional work is required, like defining an interface and set the DispId attributes (s. the Embarcadero forum link above for more details)

No comments: