Look Ma! No project defines! (DCL)
The DCL is all about classes. And there are fundamentally two ways to use a class: you can compile the class into the application itself, or you can compile it into a library (usually a DLL) so the class can be used in a precompiled form.
The disadvantage of the first approach is that if you need to make a change to the class, say a bug fix to one of the methods, you need to recompile every app that uses that class. The advantage is it's harder to GPF your app using this technique.
The disadvantage of the second approach is that you have to add some extra information to the class definition, and you have to add some extra project defines to each and every APP (or hand coded project) that uses the class DLL, and if you get any of this wrong your app will almost surely GPF. The advantage is that if you make a change to the class that doesn't affect any of is existing exports, you don't need to recompile all the apps that use that class. You just drop in the new DLL.
An ABC example
Here's a typical ABC class header:
StepClass CLASS,MODULE('ABBROWSE.CLW'),TYPE| ,LINK('ABBROWSE.CLW',_ABCLinkMode_),DLL(_ABCDllMode_)
Note the LINK
and DLL
attributes. These are the little critters that cause so much grief. They're there because a DLL that exports a class has to handle that class differently than a DLL or EXE that uses the exported class.
The help has this to say about LINK:
LINK | Names a file to add to the link list for the current project. |
Linkfile | A string constant naming an file (without an extension .OBJ is assumed) to link into the project. Normally, this would be the same as the parameter to the MODULE attribute (which by default is a source file), but may explicitly name a .LIB or .OBJ file. |
Flag | A numeric constant, equate, or Project system define which specifies the attribute as active or not. If the flag is zero or omitted, the attribute is not active, just as if it were not present. If the flag is any value other than zero, the attribute is active. |
Let's say you're creating a hand coded DLL that will contain all your generic classes (easy to do, I assure you - just wait a bit). In that case you'll want the LINK
attribute to be active, because you want the class source to be compiled and linked.
If you're using that class in another app, one that will make use of the DLL with the class source compiled in, you want LINK
to be inactive, because your app is going to make calls directly into the generic class DLL. You don't want to link in the source.
This is why you'll see _ABCLinkMode_
defined as true
(1) in an app that exports the ABC classes (typically also a data DLL), and defined as 0 in an app that makes use of that base class/data DLL.
But it's not enough to take care of the linking differences. There's also the DLL
flag, about which the help says this:
DLL | Declares a variable, FILE, QUEUE, GROUP, or CLASS defined externally in a .DLL. |
flag | A numeric constant, equate, or Project system define which specifies the attribute as active or not. If the flag is zero, the attribute is not active, just as if it were not present. If the flag is any value other than zero, the attribute is active. |
The help also notes that "The DLL attribute is required for 32-bit applications because .DLLs are relocatable in a 32-bit flat address space, which requires one extra dereference by the compiler to address the variable."
This translates loosely as "If you're using a class exported from a DLL, and you don't have an active DLL attribute on the class, your app is gonna blow chunks."
The problem I have is I can never seem to keep _ABCLinkMode_
and _ABCDllMode_
straight in my head. Objectively I know that a DLL that exports the ABC classes needs the defines
_ABCDllMode_=>0;_ABCLinkMode_=>1
while a DLL or EXE that uses that DLL with the exports needs the defines
_ABCDllMode_=>1;_ABCLinkMode_=>0
but it doesn't matter. It's only two variables, each with just two states, but my eyes glaze over anyway.
What complicates this further is that in my own code I don't want to use _ABCLinkMode_
and_ABCDllMode_
, because my classes are potentially useful in different applications (not just APP files). If I have two different applications, each made up of a data DLL, non-data DLLs, and one or more EXES, and I add a new class or method to my class library (which happens all the time), I now have to update the exports for each of those data DLLs. I don't want to do this - I want a single point of maintenance for my classes.
Third party vendors have the same problem - they don't want you to have to export their classes from your data DLL (the one that typically exports ABC classes). So they set up their own equates, which have to have their own project defines to ensure that their classes are handled correctly. I've seen apps with numerous pairs of DLL and LINK mode defines as needed by the various third party products.
This, not to put too fine a point on it, is nuts. In most circumstances there's absolutely no need for any project defines in any app except the one that compiles and exports the class(es).
I've only touched on two uses of the LINK
and DLL
attributes - linking classes into a DLL, and using classes exported from such a DLL. There are other possibilities such as linking in classes in an OBJ or LIB, but these aren't very common. I strongly suspect that almost everyone who reuses classes does so by exporting from one DLL and then using those exported classes in other DLLs and EXEs. This is the scenario I'm addressing.
A better way
The DCL uses what I like to think is a much better way. It's certainly a lot simpler and less error-prone.
All DCL classes use a DCL-specific set of LINK and DLL attributes. For example:
DCL_System_String CLASS,TYPE,MODULE('DCL_System_String.CLW')| ,LINK('DCL_System_String.CLW',_DCL_Classes_LinkMode_)| ,DLL(_DCL_Classes_DllMode_)
Nothing unusual there. But wait! All DCL class INC files also have this statement at the top:
include('DCL_IncludeInAllClassHeaderFiles.inc'),once
That file looks like this:
OMIT('***',_Compile_DCL_Class_Source_) _DCL_Classes_LinkMode_ equate(0) _DCL_Classes_DllMode_ equate(1) *** COMPILE('***',_Compile_DCL_Class_Source_) _DCL_Classes_LinkMode_ equate(1) _DCL_Classes_DllMode_ equate(0) ***
The key point is that in the Clarion project system if a variable is undefined it is assumed to have a value of zero. If _Compile_DCL_Class_Source_ is never set, then the first pair of equates applies; if _Compile_DCL_Class_Source_ is set to true then the second set applies.
Now there's just one equate that needs to be added to a project, and it only needs to be added if you actually want to compile the classes; otherwise it's assumed that you're using the classes as compiled into the DCL DLL.
The easiest way
It's still a hassle typing in LINK and DLL attributes when creating a class for the first time. Or it used to be. I now use John Hickey's brilliant ClarionLive! Class Creator, part of the ClarionLive utilities pack. Just set up a set of INC and CLW base class files, and Clive (as I like to call him) will do the rest. Fantastic!
Summary
For years I've found the DLL
and LINK
attributes to be a huge pain, and on more than one occasion I've either forgotten to add them or added them incorrectly, with predictably ugly results.
The technique I've described in this article eliminates the need to set project defines to use exported classes. It takes a little more setup (which I've now completely automated using Clive), but only requires a single compilation symbol and that only for the DLL that exports the class. I can use my exported classes in any app without being concerned at all about whether I have the correct project defines set up.