Direct-To-COM Compiler

How to Develop new COM Objects


This text gives an overview of the typical way in which new OLE objects are developed. It summarizes the most important aspects of what is detailed in the BlackBox user's guide. BlackBox is the integrated development environment (IDE) for the Direct-To-COM Component Pascal compiler with Safer OLE technology.

In a first step, a subsystem name for the project is chosen, and a suitable directory created (with its Code, Mod, and Sym directories). This directory must be in the BlackBox directory itself, i.e. at the same location where the System, Std, etc. directories are. Note that there are no environment variables or path name lists in BlackBox. File lookup is static and determined by module names, not by configurable search paths or similar constructs. It can be convenient to provide a subsystem for private software, like test modules or personal tool commands, e.g. a subsystem Priv.

It is helpful to set up a text document which contains the names of all the subsystem's modules, starting with the one lowest in the module hierarchy. Such a tool text can be used as input to the Dev->OpenModuleList command. This command opens the source file of the module whose name is selected. This is convenient since it can almost eliminate the need to navigate through the directory hierarchy. As a mnemonic aid, the keyboard equivalent for Dev->OpenModuleList is the digit "0", while the keyboard equivalent of the File->Open... command is the letter "O". Dev->OpenModuleList even allows to open several files at the same time, if the selection includes several module names.

Now you are ready to create the module sources, which should be placed in your subsystem's Mod directory. In order to take advantage of autoindentation, you should use tab characters for indentation. You can directly compile the module on which you are working with the Dev->Compile command. This command compiles, but does not yet load, the module. The command Dev->CompileModuleList takes a list of module names and compiles them all.

In order to load the module, you can execute one of its exported parameterless procedures, either by executing the Dev->Execute menu command on the command's name (e.g. on Dialog.Beep), or you can augment your tool text with commanders, e.g.  Dialog.Beep (execute Tools->InsertCommander). Once loaded, a module remains loaded, unless you explicitly unload it. Unloading can be done by executing Unload when the source code is focused, or by selecting a module name and calling Dev->UnloadModuleList. This command also works on a sequence of module names, similar to Dev->CompileModuleList. Note that modules must be unloaded from top to bottom, since only modules which are not imported anymore may be unloaded. The loaded modules are displayed in the correct order by the command Info->Loaded Moules. This command opens a text which lists all loaded modules. The command Dev->Unload Module List may directly be called on a selection in this text. In this text, top-level modules, i.e. modules which are not imported by other modules, have a clients count of 0. For the unloading of top-level modules, there is a shortcut if you use a commander: ctrl-click on the commander first unloads the old version of the module, then loads the new version, and then calls the procedure.

In order to get quick access to a module's definition or documentation, you can use the commands Info->Interface, or Info->Documentation, respectively. Info->Interface calls the browser to construct a definition text out of a module's symbol file. Note that in this text you can select further module names (or names of imported constants, variables, types, or procedures) and get the interface of those by calling the same command again. If there is a documentation file available for a module, you can execute Info->Documentation after selecting the module's name. This command opens the documentation text. If you have selected not only the module name (e.g. Math) but an object of this module (e.g. Math.Sin), the command tries to find the first occurence of the object's name which is written in boldface. The name is selected, and the window scrolls to the selection's position. In the rare cases where this first occurence is not the one you need, you may call Text->FindAgain to search for the next occurence.

In order to test a module, you can use module Out to write debugging output into the Log window. In order to set breakpoints, you can introduce HALT statements and recompile the module. The trap text which is opened when the HALT is reached gives you information about the call chain (stack) as it has been at the moment the command was interrupted. Note that at no point in time the normal BlackBox environment is left; debugging takes place completely inside BlackBox.

It is recommended to check preconditions and important invariants systematically in all programs, using the ASSERT statement. This debugging strategy is called static debugging, since error conditions are specified statically (even though they can be checked only at run-time). In an object-oriented environment, where the control flow tends to become too convoluted to follow step-by-step, the systematic application of assertions proves to keep debugging times shorter than any interactive debugging ever could.

There is a global menu configuration text. It can be opened with command Info->Menus, edited, and then made the current configuration by calling Info->UpdateMenus.
A menu item may not only have an action command, but also a guard command. Guards mainly determine whether a menu item is currently enabled or disabled.

Distribution of a COM object basically consists of distributing a linked EXE file (application) or a linked DLL (e.g. a control).

How does the dynamic loading facility of BlackBox, as described above, relate to COM binaries? Basically, dynamic loading of individual modules is convenient during development, so you don't need a separate linking step every time before trying out a new version of your code. Only before distributing the code you need to link it into an EXE or DLL.

For example, if you develop an OLE object as one Component Pascal module, you typically load the module by executing its command that registers the object with OLE. Then you switch to some other application which is an OLE client (i.e. a container), such as MS Word. There you can insert the new object and try it out. If you detect some erroneous behavior, close the document and return to BlackBox, i.e. to the OLE server. Unload the module, correct the error in the Component Pascal module(s), recompile, and then continue again with loading the new version of the module.
When working in this way, in principle you can use all BlackBox features for debugging, e.g. the Out module which writes into the Log text, or the symbolic debugger of BlackBox. Only when you're completely satisfied with the object's behavior, you link it together into a DLL.

Unfortunately, for historical reasons ActiveX controls are treated somewhat differently than ordinary OLE objects. For example, controls require the existence of a DLL. For you, this means that you need to link the program into a DLL after each modification and compilation of the sources. Furthermore, for testing you need a program which is a control container; normal OLE containers are not sufficient.
