![]() |
|
Published 1998-06-01 Printer-friendly version
| Publisher's Note |
| At several points in this article, the template code is much too long to fit in a normal width document. We've used continuation characters ( the rule character | ) to show where the code is continued. This code will not work as shown in this article. You should use the enclosed templates instead. We hope this message helps eliminate the confusion that could be caused. |
I've heard a number of individuals comment lately that the new ABC templates prevent them from organizing their APPs in a particular fashion. Specifically, they have a utility APP that uses its own dictionary, and they need to be able to call procedures and functions in that APP from other APPs using different dictionaries.
This whole scenario is simply a complex example of multi-APP development, which I believe many developers still do not fully understand; hence, this article. I hope the following example will dispel any confusion you may have had regarding this or any other multi-APP system.
Our example system will contain the following modules:
ABC - ABC support libraries, without a dictionary
UTIL - Utility module, using UTIL.DCT
BASE - Main file module for your system, using MAIN.DCT.
DLL - One of many DLL modules in your system, using MAIN.DCT
MAIN - The main EXE module for your system, using MAIN.DCT.
Each of these modules is dependent upon all the modules leading up to it. For example, ABC references only itself, while MAIN references ABC, UTIL, BASE and DLL.
Just to clear up some confusion before it happens, lets look at the different destination types:
EXE - This is an executable module. Your user can run it directly. It will always have a "First Procedure" specified in the Application Properties window.
DLL - This stands for "Dynamic Link Library". In reality, though, it's more like an EXE than a LIB. It's a stand-alone module that contains procedures that can be called at run-time by another DLL or EXE. If you specify a "First Procedure" it will never be called, unless you explicitly call it from another APP.
LIB - This module will normally be a "handle" into a DLL. (You can create a LIB that contains the executable code itself, but this is not a normal practice for Windows development.)
All DLLs will have an accompanying LIB. You'll notice that a LIB is always much smaller than its corresponding DLL. When you are including a DLL in your APP as an external module, you specify the LIB name, not the DLL name.
One thing that you must understand about using DLLs is that they are "resolved" or "complete". That means that any external references that they contain must be resolved when the DLL is created. This differs from a LIB, which can have a multitude of unresolved externals that are not clarified until the LIB is included in a calling EXE or DLL.
Given this constraint, you must always compile DLLs in the proper order, and they must always call down and never call up. Contrastingly, LIBs can call anywhere anytime.
You may feel that this makes LIBs a better option for your development, because it allows you to proceed without planning. Don't make this mistake! If your system is large enough to split it into multiple APPs, then planning and organization are crucial attributes of your project management.
The other factor is that the actual code is contained in stand-alone (i.e.: non-DLL) LIBs. This means that every EXE that refers to them gets a copy of the code, making all of your EXEs very large.
Your best bet is to always use DLLs.
There are two basic concepts to consider when plugging all of our example modules together:
The first is very simple to answer. The ABC support libraries (and all other template global data) are located in ABC.APP. From all other modules, the template globals and ABCs are external. This is controlled via a checkbox on the first tab of the Global settings. Look for the setting "Generate template globals and ABC's as EXTERNAL". In ABC.APP this will be OFF, while all other modules will have it ON.
After turning this ON, you can specify that the ABC source module is a DLL or a LIB. I would strongly suggest that you always use DLLs, as this is the Windows standard. There are certainly a few situations where you might want to work with LIBs instead of DLLs, but these are few and far between.
The second question above is a little stranger. In our example, there are four answers:
ABC - There are no data files here. This is just ABC support code.
UTIL - This module has its own dictionary, UTIL.DCT, so it has the data files defined internally. They are not, however, exported for other modules to see.
BASE - This module defines the data files internally for all of the rest of the modules using MAIN.DCT to see.
DLL and MAIN - These two modules reference the data files from MAIN.DCT externally.
There are a number of settings that control the location of data files. These are all found on the "File Control" tab of the Global settings. The settings are as follows:
Generate all file declarations - When this is ON, if a particular file is not explicitly used in the procedures of this APP, the file will still be included during generation. If the data files are defined internally in this module, and you want other APPs to be able to access any of the files in the dictionary via this APP, then you turn this ON.
External - This setting is within the "File Attributes" box. It determines whether the data files are declared in another APP. Depending on this setting, the following settings will appear and disappear. If it's set to "None External", then the files are internal to this APP. If it's set to "All External", then the files are defined somewhere else (usually another APP).
Export all file declarations - This switch should be ON if you want to define all of your files in this APP, making them accessible from the rest of your APPs. By the way, this switch is only visible if your APP is defined as a DLL.
Declaring Module - This setting in the External Files box will almost always be blank. It is only used when the files are declared in the Module memory of another APP. Considering the only option with Generator is to place them in Global memory, it is very unlikely that this setting will be used. In other words, ignore it!
All files are declared in another .APP - If you see it, then you should probably turn it ON. There are very few situations where this would not be so.
Now that we understand how to control code generation, let's try applying our newfound knowledge with our example APPs:
This module will contain the ABC support code. Because it doesn't actually access data, it doesn't need a dictionary. (You may have to change your application options to make it possible to create an APP without a dictionary. See the Settings pulldown menu.)
Of course, the "Global External" setting will be OFF, and the data file settings are not important, because there are no data files.
This is the "external" module that contains some procedures and functions that you want to include in many systems. However, this APP uses its own dictionary, which can cause troubles if not handled properly.
First, specify that the "Destination Type" is DLL. This can be done when you create the APP, or later through the Application Properties pulldown.
Next, the "Global External" setting will be ON, because the ABCs are already defined in ABC.DLL. For this to work, we must also add ABC.LIB to our Modules list. There are two ways to do this:
If your external module contains procedure that you wish to call from this APP, then use #2. In fact, I always use #2 for consistency purposes. That way I always know where to find my external modules.
In either case, you will specify that the module is called ABC.LIB, not ABC.DLL. This is because the linker needs to tell UTIL.DLL how to call procedures and functions in ABC.DLL at runtime, and this information is contained in ABC.LIB. Think of the LIB as an index into the DLL.
Finally, we have to set the data file options. In this case the module uses its own dictionary, and this is the only place that the dictionary is referenced. Therefore, the External setting will be "None External". Also we don't want the files from UTIL.DCT to be visible to our other APPs, so the "Export all file declarations" must be set to OFF.
This module contains the file definitions for MAIN.DCT. All other APPs in the system will reference the files externally, and the linker will find those external files here.
In most ways, this module uses the same settings as UTIL.APP. The primary different is that "Export all file declarations" is ON. (Of course, the dictionary is also different.)
Don't forget that ABC.LIB must be included as an external module. Also, if BASE.DLL contains calls to procedures in UTIL.DLL, then UTIL.LIB must also be included as an external module.
This represents the bulk of the modules that comprise your system. It references the data files and ABCs externally, while exporting its procedures and functions to be called from other DLLs and EXEs.
The "Global External" switch will be ON, the External setting will be "All External", and the "All files in another APP" will be ON.
As far as external modules go, ABC.LIB and BASE.LIB are required. If this APP calls procedures in UTIL.DLL, then you should include UTIL.LIB as well.
This is the entrance module of your system. It will normally contain the main menu, and possibly a number of other procedures. It uses exactly the same settings as DLL.APP, except that the target is an EXE rather than a DLL.
Although it is beyond the scope of this article, I want to touch briefly upon the concept of procedure prototypes. First of all, when you create a DLL, not all procedures in the DLL will be called from other APPs. Many of them are used internally by the DLL to perform its operations. Any procedures that do not need to be called externally should specify that they are not exported. (You set this on the Procedure Properties window.) This makes the EXP file much smaller, causing your program to load quicker for your users.
For those procedures that are exported, we need to declare the prototype (i.e.: calling conventions) for the procedure to be referenced from other APPs. There are two basic ways to achieve this:
#EXTENSION(mhPrototypeExporter,|
'Mike Hanson''s Prototype Exporter')|
,APPLICATION
#BOXED('Mike Hanson''s Prototype Exporter')
#PROMPT('Include Filename:',@S13),%IncludeFilename,|
REQ, DEFAULT(UPPER(SLICE(%Program, 1,|
INSTRING('.', %Program, 1, 1)-1)) & '.INC')
#ENDBOXED
#!----------
#AT(%CustomGlobalDeclarations),WHERE(%ProgramExtension='DLL')
#CREATE(%BuildInclude)
#FOR(%Procedure),WHERE(%ProcedureExported)
#INSERT(%MakeDeclr(ABC), 24, %OOPConstruct, %Procedure,|
%ProcedureType & %Prototype)
#IF(%ProcedureDescription)
#INSERT(%MakeDeclr(ABC), 55, %OOPConstruct, |
%OOPConstruct, '!'& %ProcedureDescription)
#ENDIF
%OOPConstruct
#ENDFOR
#CLOSE(%BuildInclude)
#REPLACE(%IncludeFilename, %BuildInclude)
#REMOVE(%BuildInclude)
#ENDAT
This is a rather complex subject, or a rather simple subject, depending on how you look at it. When your APP gets too big (I like to keep my APPs around 20 procedures or less), then you need to break it up. It's much harder to do this if your APP already contains 100 procedures, than it is to keep it organized from the start. (So much so that it's often easier to leave larger APPs big, and to move the occasional procedure into another APP as the need occurs).
I tend to organize my APPs according to entity. For example, I will have an APP for handling most aspects of employee maintenance, another for customers, another for sales (including products), and a miscellaneous APP for extraneous support procedures. If I happen to have a multitude of sales reports, then I would stuff them together into a single APP (or possibly two or three APPs). Alternatively, an APP might have only two procedures (a Browse and a Form); there's really no penalty for doing this. The size of the APP is completely up to you, and experience will help you to find your "sweet spot".
You'll occasionally run into a problem where the one APP sits on top of another APP, but the bottom APP needs to access a procedure in the top APP. (Recall the earlier discussion regarding resolved references in DLLs.) In this case, split out a subset of the procedures from the top APP into a new APP, then compile this new APP below the bottom APP.
For example, CUSTOMER.APP contains a Browse to list Invoices for the current Customer. This Browse calls UpdateInvoice, which is in SALES.APP. SALES.APP is compiled before (i.e.: below) CUSTOMER.APP. This is fine so far.
The problem is that UpdateInvoice in SALES.APP needs to call SelectCustomer, which is defined above in CUSTOMER.APP. Since we're not allowed to call up, our only solution is to create a new APP called CUST2.APP, which will contain SelectCustomer and possibly a few other support procedures.
Don't make the mistake of expecting SelectCustomer to call UpdateCustomer, which calls BrowseCustomerInvoice, which calls UpdateInvoice, etc. If you need this much calling depth in these procedures, then you'll have to combine SALES.APP and CUSTOMER.APP into a single larger APP. These types of organizational issues will always be tricky and frustrating.
Regarding naming conventions, you must ensure that the first five letters of the APP names are unique. For example, don't call one APP SALES, and another SALES2. The generator will trounce the various source files. This is why my above example used CUST2, which is different from CUSTOmer.
Of course, compiling all of these APPs is a bit of a hassle, as a change in the dictionary means that you have to recompile all of the APPs. To save time, I use Compile Manager 2, a free utility from Gordon Smith (who now works for TopSpeed in the UK). You can find it at ireland.iol.ie/~schmoo. This utility will automatically compile all the APPs in your system, letting you go for a coffee while the work's being done. By the way, Gordon has also got a great Class Browser for perusing the ABC classes.
Another benefit of keeping your DLLs small is that you can initially develop and test them as EXEs, then recompile them as DLLs once you're comfortable with their functionality. This will save you copious amounts of time normally spent compiling and linking a single large APP.
As you can see, there's nothing that hard about multi-APP development. It's simply comprised of a bunch of settings, modules, prototypes, and templates, each of which adds another piece to the puzzle. When you're done, you have a large, well-organized system that can be easily understood and maintained.
|
Posted on Wednesday, August 01, 2001 by Shane Peterson I tried you example of building an ABC.dll without file definitions and creating a seperate app for the file definitions(global.dll). When I try to compile my global.app, I get errors on all my variable filenames that I have in my dictionary. The variable filenames are also declared in the dictionary under the global flag. The error I get is: Attribute variable must be global.
|
To add a comment to this article you must log in.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239