Partager via


Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net

Creating an ActiveX control is a good exercise in understanding how one works. It also helps to have full control over its source code for learning and testing purposes. A customer asked about migrating legacy ActiveX controls over to .Net. Many controls can be used without any changes in .Net. (See Using ActiveX Controls with Windows Forms in Visual Studio .NET)

Here are some steps to create an ActiveX control using C++ ATL (Active Template Library) and a few wizards. We’ll add a method and an event, and if you’re really ambitious, we’ll add some GDIPlus calls to draw text and a picture.

Start Visual Studio 2005 (2003 works too, but the steps may be a little different). Now create a new project: choose File->New Project->C++->ATL->ATL Project. Call it “MyCtrl”

In the ATL Project Wizard’s Application Settings, choose “Attributed” and Server Type: DLL. Then choose Finish.

Now we’ll add a control to the project: Choose Project->Add Class->ATL->ATL Control to start the ATL Control Wizard.

Enter the short name "TestCtrl". Note that the ProgId is "MyCtrl.TestCtrl". In Options, choose Connection Points for event support, then Finish the wizard.

Now you can host the control in a client, even though it has no functionality yet. Hit F5 to go, allow it to build. When it asks for an Executable to run for debugging, point it to a host like “c:\program files\....\VFP9.EXE”, VB6.exe, or Excel.exe

Note that the output is MyCtrl.DLL. The extension doesn’t matter: if you want to use the traditional OCX extension, change Project->Properties->Configuration->Linker->General->Output file.

To use the control in VB6, right click on the toolbox and select Components->Controls. Add "MyCtrl 1.0 Type Library"

To use the control in Fox, create a new form, put on an ActiveX Control, then choose "CTestCtrl Object"

In VB.Net 2005, right click on the ToolBox Common Controls, choose "Add Toolbox Items", COM, "CTestCtrl Object"

Depending on which client you choose, you can have VS automatically start the client with F5 and load the control on a form by loading a prior project or executing some code. Choose Project->Properties, Configuration Properties->Debugging. Set the Command Arguments to point to some code to execute or a project.

For Fox, I have command arguments set to "-t t" which means to suppress the Log screen (run VFP9 /? to see other options) and run a program called "t" which contains these lines

CLEAR ALL

MODIFY FORM t nowait

RETURN

You can also view the control on a web page. Right click on TestCtrl.htm in Solution explorer and choose to view in a browser. Note all the security warnings!

Now that you see how easy it is to make a control that does nothing, let’s add some functionality.

Let’s add a method: In Class View (View->Class View), expand MyCtrl and right click on ITestCtrl, choose Add->New Method to start the "Add Method Wizard"

We'll add a method that takes a string parameter and returns an integer: a signature like this:

            Function Foobar(bstrString as string) as Integer

In the wizard, set the Method Name to "Foobar"

Now we'll add the first parameter: it's an IN parameter, so check "In". Set the Parameter type to "BSTR" and the name to "bstrString", then choose "Add" to add that parameter.

All COM method calls return HRESULTs, with S_OK (zero) being success. Thus, to get a return value for a method call, we need to add another parameter that's passed by reference, and it's marked by "retval" in the dialog. IOW, if the method has N parameters, the COM method signature will have N+1, with the last parameter being the return value.

However, you'll see the "retval" checkbox disabled until you choose a parameter type that's by reference, such as "LONG *". The "*" indicates it's a pointer, so it's by reference. So Choose "LONG *" and then click on the newly enabled "retval" checkbox. Give it a name "nRetval", then choose "Add".

Now you have 2 parameters for the method.

Similarly, right click on ITestCtrl, choose Add->New Property called "MyString" (this actually adds 2 methods: a get and a set)

In TestCtrl.CPP, put some code in the Method Foobar. To open TestCtrl.CPP, click on CTestCtrl in Class View, and in the bottom pane of Class View, dbl-click on Foobar.

      ::MessageBox(0,bstrString,L"From Foobar",0);

      *nRetval=10;

      return S_OK;

Set a breakpoint on the MessageBox line and on the CTestCtrl::get_MyString and CTestCtrl::set_MyString methods.

Hit F5 to go, or test your control in a client. Call the Foobar method, play with the properties of the control. At breakpoints, examine the call stacks and expand the “this” pointer in the CTestCtrl object to see all the COM Interfaces CTestCtrl implements (I copied them from the debug window, pasted into a new VS C++ file, then used the VS Editor Column Select with Ctrl-Alt drag to select columns):

ATL::CStockPropImpl<CTestCtrl,ITestCtrl,&_GUID_4d088eed_554d_4df0_a5e9_

ATL::IPersistStreamInitImpl<CTestCtrl> {...} ATL::IPersistStreamInit

ATL::IOleControlImpl<CTestCtrl> {...} ATL::IOleControlImpl<CTestCtrl>

ATL::IOleObjectImpl<CTestCtrl> {...} ATL::IOleObjectImpl<CTestCtrl>

ATL::IOleInPlaceActiveObjectImpl<CTestCtrl> {...} ATL::IOleInPlaceAct

ATL::IViewObjectExImpl<CTestCtrl> {...} ATL::IViewObjectExImpl<CTes

ATL::IOleInPlaceObjectWindowlessImpl<CTestCtrl> {...} ATL::IOleInPlac

ATL::IPersistStorageImpl<CTestCtrl> {...} ATL::IPersistStorageImpl<CT

ATL::ISpecifyPropertyPagesImpl<CTestCtrl> {...} ATL::ISpecifyProper

ATL::IQuickActivateImpl<CTestCtrl> {...} ATL::IQuickActivateImpl<CTe

ATL::IDataObjectImpl<CTestCtrl> {...} ATL::IDataObjectImpl<CTestCtrl>

ATL::CComControl<CTestCtrl,ATL::CWindowImpl<CTestCtrl,ATL::CWindow,ATL:

ATL::IConnectionPointContainerImpl<CTestCtrl> {...} ATL::IConnectio

ATL::IPropertyNotifySinkCP<CTestCtrl,ATL::CComDynamicUnkArray> {...}

ATL::CComCoClass<CTestCtrl,&_GUID_b2ab8776_ec21_46a2_acf8_fcb9bd1e97bc>

ATL::CComObjectRootEx<ATL::CComSingleThreadModel> {...} ATL::CComOb

ISupportErrorInfo {...} ISupportErrorInfo

ATL::IConnectionPointImpl<CTestCtrl,&_GUID_6ea2a8a2_c809_4dbd_a938_c792

ATL::IProvideClassInfo2Impl<&_GUID_b2ab8776_ec21_46a2_acf8_fcb9bd1e97bc

Also, you'll see that without any implementation on the property get and set, setting and getting the property from the client does nothing.

Let's add some code to respond to mouse clicks on the control. First we need to write a click handler. From Class View, click on CTestCtrl. Near the top of the Properties window (not the Class View window), click on the Messages icon. Choose WM_LBUTTONDOWN, add code. This creates an OnLButtonDown method for you to fill out. I want the code to raise an event in the client. That means we need to modify the ITestCtrlEvents interface, add the definition of a method that the client will optionally implement.

From Class View, click on the ITestCtrlEvents interface (which was added because we chose Connection Point support above) and add a method as we did for the ITestCtrl interface.

Let's call it MyEvent with a single BSTR parameter bstrEventString and no return value. This will be implemented by the client (Fox or Excel) and will be called from our control.

Now add some code to fire the event in the client in your OnLButtonDown handler in TestCtrl.cpp:

LRESULT CTestCtrl::OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)

{

      CComBSTR bstr=L"it was clicked";

      MyEvent(bstr); // Fire the event: call the client code, if it exists

      return 0;

}

Now hit F5 and click on the control to see the event get fired. In the Fox client, add code to the Olecontrol1.MyEvent snippet.

*** ActiveX Control Event ***

MESSAGEBOX(bstreventstring+" From Fox event code")

?PROGRAM(),bstreventstring

For more fun, let’s override the OnDraw method and add some GDIPlus calls.

Add these 2 lines after the other #includes as the top of testctrl.h:

#include "gdiplus.h"

using namespace Gdiplus;

Then rename the OnDraw method (in the same file: TestCtrl.h) to be OnDrawOld, and add this code. Be sure to point to a jpg file on your machine and note that backslash is an escape character and needs to be doubled.

(For production code, we’d call GdiplusShutdown and cache the Image.)

Notice how the ActiveX control determines the difference between design time and run time.

HRESULT OnDraw(ATL_DRAWINFO& di)

{

      BOOL fRunMode=0;

      GetAmbientUserMode(fRunMode);

      static Gdiplus::GdiplusStartupInput gdiplusStartupInput;

      static ULONG_PTR gdiplusToken=0;

      if (gdiplusToken == 0) {

            Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput,NULL);// note: call GdiplusShutdown!!!

      }

      Gdiplus::Graphics MyGraphics(di.hdcDraw);

      Gdiplus::Image MyImage(L"d:\\kids.jpg",0);// some jpg: backslash needs to be doubled. Cache this!

      RectF rect(

            (REAL)di.prcBounds->left,

            (REAL)di.prcBounds->top,

            (REAL)(di.prcBounds->right - di.prcBounds->left),

            (REAL)(di.prcBounds->bottom - di.prcBounds->top));

      MyGraphics.DrawImage(&MyImage,rect);

      if (!fRunMode) // if design mode, draw some text

      {

            Gdiplus::Font MyFont(L"Arial",16);

            Gdiplus::StringFormat sf;

            PointF pf(rect.X,rect.Y);

            SolidBrush MyBrush(Color(128,0,128,255));

            MyGraphics.DrawString(L"Design Time!",12,&MyFont,pf,&MyBrush);

      }

      return S_OK;

}

Now we’ll have to link with the GDIPlus library, so go to Project->Properties, Configuration Properties->Linker->input->Additional Dependencies and add Gdiplus.lib.

Hit F5 and have fun!

Below is sample Fox client code that doesn’t use the Fox form designer, so it will not be in design mode:

PUBLIC ox

ox=CREATEOBJECT("MyForm")

ox.visible=1

DEFINE CLASS MyForm as Form

          ADD OBJECT OC as olecontrol WITH ;

                   oleClass="MyCtrl.TestCtrl",;

                   height=200,width=300

          left=200

          AllowOutput=.f.

          PROCEDURE oc.MyEvent(bstreventstring as String)

                   MESSAGEBOX(bstreventstring+" From Fox event code")

                   ?PROGRAM(),bstreventstring

                   ?this.foobar("Call the Foobar method")

ENDDEFINE

When you try it in a VB.Net application, try this code:

    Private Sub AxCTestCtrl1_MyEvent(ByVal sender As System.Object, ByVal e As AxMyCtrl._ITestCtrlEvents_MyEventEvent) Handles AxCTestCtrl1.MyEvent

        MsgBox(e.bstrEventString)

    End Sub

See also ATL Tutorial

You can also create a .Net Usercontrol as an ActiveX control: see: Create a .Net UserControl that calls a web service that acts as an ActiveX control to use in Excel, VB6, Foxpro

Comments

  • Anonymous
    February 13, 2007
    Hi, This is a helpful, fully explained and easy to understand document for a rookie like me in this filed. Thank you Calvin for your help. With the help of this I am felling like Activex Control with ATL is a very simple task, wich was almost next to impossible to me a few hours ago. I wonder why other documents are not as simple and down to earth as this one. Thanks a lot again. R Deval.

  • Anonymous
    April 19, 2007
    great tutorial, helped me a lot! thank you!! j.m.b.

  • Anonymous
    April 25, 2007
    Excellent tutorial, saved my day with pouring over MS(IT) document

  • Anonymous
    July 02, 2007
    Hi Calvin,  I am new to ATL/COM .  This tutorial helped me to understand how easy to build a control. Can you tell me how I can create an Image Viewer ( For Windows and Web based applications) using ATL/COM Thanks and Regards RGP

  • Anonymous
    July 02, 2007
    Hi Calvin,  One more query ; Can you tell how I can test the control using Active x Control Test container.( I couldnt see the control while opening thru Edit-> Insert NewControl). Thanks and Regards RGP

  • Anonymous
    October 15, 2007
    Hi Calvin I'm trying to make my pwn Frame activex control - to act as a container for buttons etc. How do I make my control a container in its own right? Thanks Rory

  • Anonymous
    October 15, 2007
    Well, darn... is it so simple as making the property 'ControlContainer' true??? Rory

  • Anonymous
    November 08, 2007
    I'm getting stuck on the event handling part  (I'm working in Excel).  The OnLButtonDown method gets called when the control is clicked, as expected; however, I can't figure out how/where to implement the MyEvent method in Excel.  When I choose "view code" in design mode, it only shows GotFocus and LostFocus methods in the VB editor window.  I tried to write code like this: Private Sub CTestCtrl1_MyEvent(theString)    MsgBox("blah") End Sub But this does nothing.  Any ideas?  I'm sure this is a trivial goof on my part.

  • Anonymous
    January 11, 2008
    Hi Calvin, I have a requirement. I need to use a .Net Winform user control from a ATL. In turns I want to host user control as an ActiveX control. How can I achieve this. Kindly help me with a sample code. Thanks you very much. Saikat

  • Anonymous
    January 11, 2008
    The comment has been removed

  • Anonymous
    January 13, 2008
    Hi Calvin, Thanks for your reply. See, I need to create the ActiveX control using ATL (VC++) only. In that control I need to host my .NET user control. So, when the client application (written in most likely in VC++) will host the ATL activeX control, the user should be able to see and access the .NET control. I am very new to VC++. Kindly help me in this. Thanks in advance. saikat

  • Anonymous
    February 23, 2008
    Hallo Calvin, I'm new to ATL. I try your sample using VS 2008, it's built without error and work in test container. But when I try it in VFP9 SP2 it's able to be put on form but the form cannot be saved with the following error: OLE error code 0x80004005: Unspecified Error. Please help me Regards Tarno Nona

  • Anonymous
    February 23, 2008
    Hi Calvin, I read your article on http://blogs.msdn.com/vsdata/archive/2004/03/18/92346.aspx, and when I try using your suggested code: ox=CREATEOBJECT("form") ox.addobject("oc","olecontrol","the control’s progid")   ox.oc.visible=.t. ox.show(1) it's working. Oh btw, my os is XP SP2. Regards Tarno Nona

  • Anonymous
    May 28, 2008
    How fast is interop code? If you’re in one kind of code and your calling another, what is the cost of

  • Anonymous
    June 11, 2008
    Plz, tell me a way. I have made a control in ATL(VC++). I want to know in my control whether any dll entered by user is registered or not. thanks in advance

  • Anonymous
    July 11, 2008
    Hi Calvin, I want to know about the Task Sequencer in VC++ using ATL and COM. Will you please reply me ??

  • Anonymous
    September 25, 2008
    The comment has been removed

  • Anonymous
    January 14, 2009
    You can also see http://www.microsoft.com/msj/0299/atl3Activex/atl3Activex.aspx.

  • Anonymous
    January 21, 2009
    PingBack from http://www.keyongtech.com/477529-invisible-activex-control-created-using

  • Anonymous
    May 29, 2009
    PingBack from http://paidsurveyshub.info/story.php?title=calvin-hsia-s-weblog-create-an-activex-control-using-atl-that-you-can

  • Anonymous
    June 08, 2009
    PingBack from http://quickdietsite.info/story.php?id=4482