Creating ActiveX Controls with C++Builder

by George Cross, Borland C++ Developer Support , Borland Conference '97

INTRODUCTION

CAUTION: This is a non-visual discussion. If you want to a RAD, visual, ActiveX design tool, buy Delphi 3.0. Delphi 3.0 includes a new version of VCL containing a suite of classes which fully support RAD, visual development of ActiveX controls. It is most likely a future version of C++Builder will be released which also has such capability. If you want to build ActiveX controls with C++Builder 1.0 however, you should be prepared to shell out to your favorite editor and start cranking out a makefile. This discussion will show all the steps and components necessary to build an ActiveX control unaided by experts, wizards, or RAD design tools. We will be using only the comparatively lightweight Active Template Library (ATL) or the Control Framework which ships with the ActiveX SDK. We will cover compiling IDL files using MIDL 3.0, and we will outline which headers, libraries and dll's are necessary to create an ActiveX control. Finally, we will build and register our OCX and dig through the registry to find our OCX and debug it using the legendary Turbo Debugger32.

TOOLS

The parts of C++Builder I use in this discussion are, All of these are also found in Borland C++5.x. I also make use of the following found only on Borland C++5.02, Finally, I make use of a number of items which can only be found on the latest Win32SDK. I have packaged these items into the C++Builder ActiveX Update available on this CD, which contains the following, This update will be incorporated into the first patch for C++Builder which will be available by Borland Conference '97.

CLASS LIBRARIES

There are several class libraries available to assist one in creating ActiveX controls. Aside from VCL 3.0 which comes with Delphi 3.0 as mentioned above, Microsoft offers three:

COMPILING TECHNIQUES

A few general issues arise when building these samples which were originally built with MSVC++.
  1. Microsoft code often uses anonymous structures and unions which are illegal in ANSI C++. tagVARIANT is one significant structure which uses anonymous structures and unions.

    Introduced in Borland C++5.0, BCC32.EXE has a -VF switch (called the MFC Compatability option) which forces the compiler to accept anonymous structures and unions. If you wanted to modify the code to be ANSI C++ compliant, you could define NONAMELESSUNION and change all instances of, for example,

      VARIANT v;
      v.vt = VT_EMPTY;
      v.lVal = 0;
    
    to,
    
      VARIANT v;
      v.n1.n2.vt = VT_EMPTY;
      v.n1.n2.n3.lVal = 0; 
    

    If you don't use this switch when building these samples, your output messages will be proliferated by error messages such as "vt is not a member of structure tagVARIANT".

  2. For DLL's, which is what ActiveX controls are, MSVC++ uses the function name "DllMain" as the module's main entrypoint. Borland C++ uses the function name "DllEntryPoint". Be sure to change "DllMain" to "DllEntryPoint" when compiling a DLL written for MSVC++.

IMPORT AND STATIC LIBRARIES

"Unresolved external" error messages from the linker are common. Knowing which import and static libraries to link into your ActiveX code can alleviate this. In contrast to MSVC++ which has many .LIB files linked both through project makefiles and '#pragma comment' directives in header files, Borland C++5 and C++Builder expose the API's for ActiveX development in the following libraries only, So when building an ActiveX control, one will link the C runtime library, one might link a class library such as MFC or CTLFW32.LIB, and one will link one or more of the libraries listed above.

Just in case there is any doubt, .LIB and .OBJ files generated with MSVC++ can not be linked by Borland's linker at this time. Similarily, the MSVC++ linker can not link .LIB and .OBJ files generated by Borland's TLIB and BCC32.

BUILD PROCEDURE

Whichever library you use, the basic steps to building an ActiveX control are,
  1. Generate some GUIDs for your type library (LIBID), component class (CLSID), interfaces (IIDs and dispIDs)
  2. Create the interface definition in the Object Description Language and save it to an .ODL file. Compile this with MKTYPLIB or MIDL. MIDL 3.0+ is best. No INITGUIDS macro since GUID instantiations are now split out to a .C file. Also if you are going to build a local or remote OLE server, you can specify your custom interfaces in .IDL and have the marshalling code generated.
  3. Create a .REG file for your component.
  4. Implement DllRegisterServer, DllCanUnloadNow, IClassFactory, IUnknown for your object.
  5. Implement necessary OLE and custom interface methods.
  6. Compile and link your source to a DLL renaming it with an OCX extension
  7. Register your ActiveX control.
  8. Test and debug.
ActiveX Control and Control Container Guidelines in the OLE Reference which comes with C++Builder provides a good outline of what OLE interfaces you might want to implement.

In the least, you will likely have IDataObject for Uniform Data Transfer, IViewObject2, IOleCache, IOleCacheControl to display your data, IPersist* for Structured Storage, IOleObject for OLE Document Embedding, and IDispatch for Automation. Other basic interfaces you might want are IConnectionPointContainer, IConnectionPoint for outgoing interfaces and events, and IOleControl for keyboard and mouse handling withing the container, IOleInplaceObject, IOleInplaceActiveObject for In-place Activation, ISpecifyPropertyPages for those property pages you see controls have. IPerProperyBrowsing would be nice to have if you want a control's stock properties to be displayed in a container-supplied property sheet, likely along with ambient and extended properties as in the BCB IDE.

WHAT IS AN ACTIVEX CONTROL ?

There seems to be some contention and vagueness about this issue. If you are one aspiring to marketing hype, you might perfusely advocate an ActiveX control is nothing less than a device to cure world hunger and all other ailments known to the human race. For the purposes of this discussion, let it be clear it is nothing more than a DLL. It exports some functions and implements some COM interfaces. It has been renamed with the .OCX extention. It is an in-process OLE server, which may support automation, events, persistence.

Make no mistake about it. ActiveX requires on OLE. OLE requires COM.

THE MISSION

Initially I said I would discuss ATL and Control Frameworks. However, my fussy nature dictates I know what is happening at the API level first. There are more discussions about how to use some vendor's class library than the API anyway these days. Since I'm a beginner, I just wanted to adopt a straight forward, old fashioned approach to creating an in-process OLE automation and document server with events. I've used strictly C++ Builder command-line tools, the help and Brockschmidt. Hopefully, through this, I can impart some of the solid foundation in OLE I feel I have acquired.

Borland C++, in any current flavor, can build OCX's from any of Microsoft's class libraries: MFC, Control Frameworks, or ATL. For examples of MFC, see BC502. For examples of Control Frameworks see my website. For examples of ATL, wait for the BCB3 beta. But we've been building Microsoft code for sometime now. Just no wizards.

I have not finished the control I set out to build - I've only got as far as data transfer and persistence. My control is called "George's Bitmap Control". It simply . . .

Please come and visit http://apsoft.com/~gcross/homebrew.html to see a number of the ActiveX SDK Samples, Brockschmidt Samples built with C++Builder 1.0.


References

  1. "Inside OLE", Kraig Brockschmidt, MS Press 1995, Second Edition
  2. Ole Programmer's Reference, MS Help file in Borland C++Builder, chapter titled "ActiveX Controls"

A template is somewhat like a macro. As with a macro, invoking a template causes it to expand (with appropriate parameter substitution) to code you have written. However, a template goes further than this to allow the creation of new classes based on types that you pass as parameters. These new classes implement type-safe ways of performing the operation expressed in your template code. Template libraries such as ATL differ from traditional C++ class libraries in that they are typically supplied only as source code (or as source code with a little, supporting run time) and are not inherently or necessarily hierarchical in nature. Rather than deriving from a class to get the functionality you desire, you instantiate a class from a template.
// from OAIDL.H
#if (__STDC__ && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION)
#define __VARIANT_NAME_1 n1
#define __VARIANT_NAME_2 n2
#define __VARIANT_NAME_3 n3
#else
#define __tagVARIANT
#define __VARIANT_NAME_1
#define __VARIANT_NAME_2
#define __VARIANT_NAME_3
#endif
typedef /* [wire_marshal] */ struct tagVARIANT VARIANT;

struct  tagVARIANT
    {
    union 
        {
        struct  __tagVARIANT
            {
            VARTYPE vt;
            WORD wReserved1;
            WORD wReserved2;
            WORD wReserved3;
            union
                {
                LONG lVal;
                BYTE bVal;
                SHORT iVal;
                FLOAT fltVal;
                DOUBLE dblVal;
                VARIANT_BOOL boolVal;
#if !defined (__BORLANDC__)
                _VARIANT_BOOL bool;
#endif
                SCODE scode;
                CY cyVal;
                DATE date;
                BSTR bstrVal;
                IUnknown __RPC_FAR *punkVal;
                IDispatch __RPC_FAR *pdispVal;
                SAFEARRAY __RPC_FAR *parray;
                BYTE __RPC_FAR *pbVal;
                SHORT __RPC_FAR *piVal;
                LONG __RPC_FAR *plVal;
                FLOAT __RPC_FAR *pfltVal;
                DOUBLE __RPC_FAR *pdblVal;
                VARIANT_BOOL __RPC_FAR *pboolVal;
                _VARIANT_BOOL __RPC_FAR *pbool;
                SCODE __RPC_FAR *pscode;
                CY __RPC_FAR *pcyVal;
                DATE __RPC_FAR *pdate;
                BSTR __RPC_FAR *pbstrVal;
                IUnknown __RPC_FAR *__RPC_FAR *ppunkVal;
                IDispatch __RPC_FAR *__RPC_FAR *ppdispVal;
                SAFEARRAY __RPC_FAR *__RPC_FAR *pparray;
                VARIANT __RPC_FAR *pvarVal;
                PVOID byref;
                CHAR cVal;
                USHORT uiVal;
                ULONG ulVal;
                INT intVal;
                UINT uintVal;
                DECIMAL __RPC_FAR *pdecVal;
                CHAR __RPC_FAR *pcVal;
                USHORT __RPC_FAR *puiVal;
                ULONG __RPC_FAR *pulVal;
                INT __RPC_FAR *pintVal;
                UINT __RPC_FAR *puintVal;
                }	__VARIANT_NAME_3;
            }	__VARIANT_NAME_2;
        DECIMAL decVal;
        }	__VARIANT_NAME_1;
    };