Hi everybody,
Hope this guide explains the steps necessary to expose a .NET control to COM, making it accessible from environments like Visual FoxPro/VO/VB6, etc. The example focuses on creating a custom button control.
When exposing a control via COM, interfaces play an important role, especially when using
ClassInterfaceType.None. This type of class interface means you must define the methods yourself and explicitly expose them.
a) Define Event Interface
This interface (
IControlEvents) will handle the events that your control will raise. For example, a
MyButtonClick event can be defined like this:
Code: Select all
[Guid("ea567180-887c-48f0-8867-c51aaef20750")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
INTERFACE IControlEvents
[DispId(1)]
METHOD MyButtonClick() AS VOID
END INTERFACE
[Guid]: Assigns a unique identifier for the interface.
[InterfaceType]: InterfaceIsIDispatch allows the interface to be accessed via late binding, which is useful in environments like Visual FoxPro.
[DispId]: Assigns a dispatch ID to the method for COM.
b) Define Method Interface
If you are using
ClassInterfaceType.None, you need to define the methods manually using an interface like
ICustomButton. This interface ensures that any method you want to expose is explicitly declared. Here is an example for a
Button_Click method:
Code: Select all
INTERFACE ICustomButton
METHOD Button_Click(sender AS OBJECT, e AS EventArgs) AS VOID
END INTERFACE
Obligatory for ClassInterfaceType.None: When using ClassInterfaceType.None, no automatic interface is generated. This means you must explicitly declare any methods you want to expose through COM.
Now that the interfaces are defined, you can implement your custom control. The control class will inherit from
UserControl (or any other Windows Forms control) and implement the
ICustomButton interface.
Code: Select all
[Guid("6e3a579a-59a4-42c9-b9c7-3b162f5c0005")]
[ComSourceInterfaces(typeof(IControlEvents))]
[ProgId("ActiveX.MySample.MyCustomButton")]
[ClassInterface(ClassInterfaceType.None)]
PARTIAL CLASS MyCustomButton INHERIT System.Windows.Forms.UserControl ;
IMPLEMENTS ICustomButton
[ComSourceInterfaces]: Specifies which interfaces (like
IControlEvents) will be used to trigger events accessible via COM.
[ProgId]: The Programmatic Identifier that COM clients like Visual FoxPro will use to instantiate the control.
[ClassInterface(ClassInterfaceType.None)]: Disables automatic generation of COM interfaces for the class. This is why you must define your own interfaces, like ICustomButton, to expose the necessary methods.
In this step, you will implement the event handler and expose the necessary methods in your control.
a) Implement Button Click Event
The Button_Click method, defined in the ICustomButton interface, is implemented as follows:
Code: Select all
METHOD Button_Click(sender AS System.Object , e AS System.EventArgs) AS VOID
IF MyButtonClick != NULL
MyButtonClick()
ENDIF
END METHOD
This method triggers the MyButtonClick event whenever the button is clicked.
b) Declare the Event
To make the event accessible via COM, define a delegate and an event:
Code: Select all
DELEGATE MyButtonClickEvent AS VOID
EVENT MyButtonClick AS MyButtonClickEvent
Once the control is implemented, it must be registered in the Windows registry so that COM clients like FoxPro can find and use it.
a) Register the Control
This is done using the ComRegisterFunction and ComUnregisterFunction attributes. These methods modify the Windows registry to register or unregister the control.
Code: Select all
[ComRegisterFunction()]
PUBLIC STATIC METHOD RegisterClass(tcKey AS STRING) AS VOID
VAR loSb := StringBuilder{tcKey}
loSb:Replace("HKEY_CLASSES_ROOT\", "")
VAR k := Registry.ClassesRoot:OpenSubKey(loSb:ToString(), TRUE)
VAR ctrl := k:CreateSubKey("Control")
ctrl:Close()
VAR inprocServer32 := k:OpenSubKey("InprocServer32", TRUE)
inprocServer32:SetValue("CodeBase", Assembly.GetExecutingAssembly():CodeBase)
inprocServer32:Close()
k:Close()
END METHOD
[ComUnregisterFunction()]
PUBLIC STATIC METHOD UnregisterClass(tcKey AS STRING) AS VOID
VAR loSb := StringBuilder{tcKey}
loSb:Replace("HKEY_CLASSES_ROOT\", "")
VAR k := Registry.ClassesRoot:OpenSubKey(loSb:ToString(), TRUE)
k:DeleteSubKey("Control", FALSE)
k:OpenSubKey("InprocServer32", TRUE)
k:DeleteSubKey("CodeBase", FALSE)
k:Close()
END METHOD
The RegisterClass method adds the control to the Windows registry so it appears in COM.
The UnregisterClass method removes it from the registry.
1. Don't forget to add the ComVisible(true) attribute in the AssemblyInfo.prg file.
2. You have to convert your compiled assembly in the COM equivalent by using the Assembly Registration Tool (RegAsm)
3. Use your own guids, I've built a plugin that adds an option in the Tools menu (Generate Guid) that injects the guid in your editor caret current position. @Chris where can we share all the plugin we build?