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,
- MAKE.EXE
- BCC32.EXE
- BRCC32.EXE
- TLINK32.EXE
- TLIB.EXE
- MAKERSP.EXE
- REGSRVR.EXE
- The INCLUDE directory
- The LIB directory
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,
- TD32.EXE
- IMPORT32.LIB
- Use this one instead of the one in C++Builder. See below if you don't have BC++5.02
- GUIDGEN.EXE
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,
- MIDL.EXE 3.0
- Win32SDK IDL files
- Normally the interface descriptions you will write will need to include one or more of these files.
- RPCNDR.H
- an updated version (defines __declspec(uuid))
- IMPORT32.LIB
- from BC++ 5.02. Be sure to use this one instead of the one in C++Builder !
- CPP32.EXE
- an updated version (Properly handles uuid's. See BCTOOLS.HLP)
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:
- MFC
- ATL
The newest MS class library, Active Template Library is now in version 2.1 available from MS website HTTP://www.microsoft.com/visualc/prodinfo/atlinst.htm. As the name suggests, these are template classes and this characteristic enables an ATL ActiveX to be smaller than controls built with MFC. ATL is shipped as source code which you include in your application. ATL also makes a DLL available (ATL.DLL) which contains code that may be shared across components. However, this DLL is not necessary.
- Control Frameworks
This is found in the ActiveX SDK, which is available from MS website http://www.microsoft.com/msdownload/sbndownload/sbnaxsdk/sbnaxsdk.htm. All sample controls in this SDK link a static library CTLFW32.LIB the source for which is also provided. I have built this library and all ActiveX SDK sample controls with Borland C++Builder.
COMPILING TECHNIQUES
A few general issues arise when building these samples which were originally built with MSVC++.
- 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".
- 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,
- IMPORT32.LIB
contains all OLE GUIDs
- INET.LIB
contains all the import records of the internet API in HLINK.DLL, URLMON.DLL, MSCONF.DLL, WEBPOST.DLL and WININET.DLL.
- OLE2W32.LIB
contains all the import records for OLE in OLE32.DLL, OLEAUT32.DLL, OLEPRO32.DLL and OLEDLG.DLL
- RPCEXTRA
contains the import records from RPCDCE4.DLL
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,
- Generate some GUIDs for your type library (LIBID), component class (CLSID), interfaces (IIDs and dispIDs)
- 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.
- Create a .REG file for your component.
- Implement DllRegisterServer, DllCanUnloadNow, IClassFactory, IUnknown for your object.
- Implement necessary OLE and custom interface methods.
- Compile and link your source to a DLL renaming it with an OCX extension
- Register your ActiveX control.
- 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
- "Inside OLE", Kraig Brockschmidt, MS Press 1995, Second Edition
- 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;
};