Borland C++ Builder 4.0 provides excellent support for using and building COM/ActiveX components. The COM support in C++Builder 4.0 is a major improvement over version 3.0. Its ActiveX Wizard contains several options to create a COM-based component. Of course, C++Builder also gives you the ability to import ActiveX controls.
All this sounds good, and it is. But somewhere has to be a catch. After all, this ease of use cannot come for free. In this case, the price comes in the form of the size of the final DLL or OCX. The code generated by C++Builder drags in a lot of the VCL library—code that a lean COM object doesn’t need.
Borland doesn't make a secret out of the fact that VCL-less COM was one of the design goals for C++Builder 4.0. For various reasons, though, this feature was dropped from the final product. But don't despair. A fair amount of this design goal has made it into the C++Builder 4.0 code base. In this article I will show how an Automation control can be tweaked to remove reliance on the VCL. To do this, you only need to make a small number of changes to the source files C++Builder generates when it creates a COM object.
To begin, you will create a simple Automation Server. Select File|New from the main menu. Select the ActiveX tab in the Object Repository and double-click the ActiveX Library icon. C++Builder will create a project for you that contains two source files. I saved the project with the name VCLLESSCOM. Using that name results in the IDE generating files called VCLLESSCOM.CPP and VCLLESSCOM_ATL.CPP.
Next you need to give this object some functionality. This is accomplished by adding an automation object to the ActiveX library. Again select File|New from the main menu. This time double-click the Automation Object icon on the ActiveX page of the Object Repository. In the New Automation Object dialog, fill in the CoClass name. Predictably, I chose the name "VCLLess." I also opted to generate events support code.
When you close the New Automation Object dialog, C++Builder will add two more files to the project. The files are VCLLESSCOM.TLB (the type library) and VCLLESSIMPl.CPP (the implementation of the IVCLLess interface). The Type Library Editor is displayed, ready for you to add properties and methods to your automation object.
This article does not cover the subject of developing COM objects, so you will just define a simple property. To do this, right click on the IVCLLess interface in the Type Library Editor, select New|Property and give it the name SimpleProperty. After you add the property, you should save the project. You will be prompted to select a name for the interface implementation file. You can save the implementation with the default name C++Builder provides.
Before you build the server you need to set the project options. You don't want the COM server DLL to depend on any other DLLs or packages. Select Project|Options from the main menu and click on the Linker tab. Uncheck the Use dynamic RTL check box. You don't need an import library either so you can uncheck the Generate import library check box. Now select the Packages tab. Uncheck the Build with runtime packages option. Finally, go to the Directories/Conditionals page and set the intermediate directory to DEBUG. Close the Project Options dialog and save the project with the new project options.
Now build the project by selecting Project|Make VCLLessCom from the main menu. When the build is finished, start Windows Explorer and navigate to the directory where your newly born COM server resides. You should find that the DLL is a whopping 400K!
400K for a COM object that does nothing is a bit extreme to say the least. Don't despair, though, because by the time we are done, over 300K of this bloat will be gone. What follows is a set of steps that you can use to remove the VCL from your COM objects.
The first step requires adding a defined symbol to the project. Open the Project Options dialog and click on the Directories/Conditionals tab. Add the _NO_VCL define to the Conditional defines edit box. Close the Project Options dialog.
The project source files must be modified to eliminate dependence on the VCL. Switch to the main project file (VCLSLESSCOM.CPP). The first three lines look like this:
#include <vcl.h>
#pragma hdrstop
#include <atl\atlvcl.h>
Change these lines so that they read:
#include <windows.h>
#pragma hdrstop
#include <atl\atlmod.h>
#include <condefs.h>
The line that includes CONDEFS.H is required for C++Builder’ s "USE" macros (USERES, USEUNIT, and so on). Normally CONDEFS.H is included by VCL.H. You are no longer including VCL.H so you must explicitly include CONDEFS.H.
In the VCLLESSCOM_ATL.CPP file, insert the following above the line that includes VCLLESSCOM_ATL.H:
#undef USING_ATLVCL
#define USING_ATL
The first few lines in VCLLESSCOM_ATL.CPP should now look like this:
#include <vcl.h>
#pragma hdrstop
#undef USING_ATLVCL
#define USING_ATL
#include "VCLLessCOM_ATL.h"
Now it’s time to do some precision surgery. You will start with the project make file.
Select Project|View Makefile from the main menu. The VCLLESSCOM.BPR file will be displayed in the Code Editor. This is where most of the changes will take place. Do the following:
1. Remove VCL40.LIB from the SPARELIBS line.
2. Remove sysinit.obj from the ALLOBJ line.
3. Remove $(LIBFILES) from the ALLLIB line.
4. On the ALLLIB line, change cp32mt.lib to cw32mt.lib.
After making the above changes, save the project.
Next big change involves the Unlock() function of the TATLModule template in ATLMOD.H. You can open this file from VCLLESSCOM_ATL.CPP by right-clicking the mouse and selecting Open Source/Header file. You will be brought into the VCLLESSCOM_ATL.H file. While here, you should fix a rather nasty bug. Locate the line that reads:
extern CComModule _Module;
Change the line so that it reads:
extern CComModule &_Module;
The code generator "forgot" to add the reference operator to the declaration of CComModule.
Locate the line that includes ATLVCL.H. Click on the filename and press Ctrl-Enter on the keyboard, or right-click on the filename and select Open file at cursor. In the ATLVCL.H file, find the line that includes ATLMOD.H. Open ATLMOD.H and locate the TATLModule<T>::Unlock() method. Modify it so that it looks like this:
template <class T>
LONG TATLModule<T>::Unlock()
{
LONG result = T::Unlock();
if ((result == 0) && m_bExe)
{
#ifndef _NO_VCL
TSysCharSet DelimSet;
DelimSet << '/' << '-';
if (FindCmdLineSwitch(
"AUTOMATION", DelimSet, true))
::PostThreadMessage(
m_ThreadID, WM_QUIT, 0, 0);
#endif
}
return result;
}
What I've added is the #ifndef _NO_VCL and #endif lines. (The function body was formatted to fit the Journal’s column width, so it will look slightly different here than in the original source.) The problem is that the code inside the #ifndef/#endif block is implemented in the VCL. I have no desire to rewrite the FindCmdLineSwitch() function in C. At any rate, this code is only needed for executables using the COM object. For those applications, the VCL is already used and the _NO_VCL macro will not be defined. Save ATLMOD.H after making the changes.
Next you will make a change to ATLWIN.CPP. The easiest way to find the line you will modify is to compile the project. When you compile, the compiler will produce an error message in ATLWIN.CPP on line 296. Here is that line:
for(i = 0; i < m_nEntries; i++)
The compiler error is generated because Microsoft does not adhere to the C++ standard regarding the scope of variables in for loops. In this case, a previous for loop declares the variable i. The second for loop attempts to reference that same variable. The C++Builder compiler correctly reports the second reference to i as being undefined because it is not in scope at that point. To fix this error, simply add int before the variable i:
for(int i = 0; i < m_nEntries; i++)
Save ATLWIN.CPP after making this change.
The last thing to change is the stubs that the IDE generates for the property access functions in VCLLESSIMPL.CPP. The problem here is that the IDE generates code like this:
catch(Exception &e)
{
return Error(
e.Message.c_str(), IID_IVCLLess);
}
Exception is a VCL class and, obviously, we have removed reliance on the VCL. This is a problem in the catch statement itself, and in the return statement within the catch block. Change the catch blocks in the property access methods so that they look like this:
catch(...)
{
return E_FAIL;
}
This code simply catches all exceptions and returns E_FAIL if an exception occurs.
At this point you are ready to build the DLL again and see the results of your work. Build the project again, making sure you do a full build and not just a make. You will see a few warning messages coming from the ATL files, but they are benign and nothing to worry about.
Now take a look at the file size of VCLLESSCOM.DLL. You will see that it is now only about 95K in size. Wow! That’s quite a savings!
Once you know what is required, it's rather easy to remove the VCL from a COM server. The best part is that all the features of RAD COM development are still at your disposal. The Type Library Editor will still work and the IDE will modify your source as you add properties and methods to the COM server. You may have noticed that there are still quite a few places where VCL.H is included. You can change those to include WINDOWS.H if you like. Look in the VCLLESSCOM_ATL.CPP, VCLLESSCOM_TLB.CPP and VCLLESSIMPL.CPP files for references to VCL.H. This change is not required to have a COM object stripped of the VCL, but will probably result in faster compile times. Also, by getting rid of VCL.H, you are guaranteed to hit compiler errors if by some chance you accidentally use a VCL function.
C++Builder is a complex and powerful piece of software. Though some corners had to be cut on the COM side of the product, enough hooks where put in place that a determined hacker can get rid of the fat that a default C++Builder COM object contains. Keep in mind that if you create an ActiveForm or create a new ActiveX control derived from a VCL component, you are still bound to the VCL. That's not necessarily a bad thing, because the VCL makes developing those kinds of objects a breeze. However, when you need a lean and mean COM server, the approach described in this article is the way to go.