An introduction to Visual Cadd API programming with PB/DLL
By Bob Benson        

This the third in a series of articles dedicated to exploring the Visual Cadd API with PowerBASIC PB/DLL.  

Using multiple source files with PB/DLL

As you can see, although the programming is pretty simple so far, it does not take long for our programs to start getting quite long.  PB/DLL has an #INCLUDE metastatement (compiler instruction) that tells PB/DLL to read a text file from disk.

#INCLUDE "filename"

filename is a string constant that follows DOS file-naming conventions.  You can not use long filenames for filename.  They are not supported in PB/DLL source code, though they may be used for other purposes at run-time.  PB/DLL is a 16-bit application and therefor it requires the eight character file length restriction of DOS.  It does create true 32-bit applications, so you can use long filenames within your applications, if you desire.

When PB/DLL encounters an #INCLUDE metastatement, it reads filename from disk and continues compilation with the source code in filename.  When the end of filename is reached, compilation continues with the statement immediately following the #INCLUDE in the original source file.  The result is the same as if the contents of the included file were physically present in the original text.  This allows you to break large source files into more manageable chunks.  These files are often referred to as include files with PB/DLL and have the extension .INC.  You can include .BAS or any extension name that you desire, as long as the file is a text file and contains valid PB/DLL source code.

When working with API applications such as Windows and Visual Cadd, all of the procedure declarations, variable type definitions and equates, etc. are generally found in these separate include files.  When used for this purpose, these separate files that are included with your source code are often referred to as header files or headers.

For example, to include this file for the Windows API, we would use the following statement:  

#INCLUDE "WIN32API.INC"

As many other APIs often make use of variable types that have been previously defined in the Windows API, this file should be included prior to any other API include (header) files.  The Visual Cadd API documentation contains these files for each language supported.  These are added by using the following code:

#INCLUDE "WIN32API.INC"
#INCLUDE "VCTYPE32.INC" 
#INCLUDE "VCMAIN32.INC"
#INCLUDE "VCTOOL32.INC"
#INCLUDE "VCDLG32.INC"
#INCLUDE "VCTRAN32.INC"  
#INCLUDE "VCLINK32.INC"

Win32API.inc is optional and is not required until we start making calls to the Windows API.  VCType32.inc for PB/DLL can detect if Win32API.inc has been loaded.  If so, it will avoid making duplicate definitions of Win32API.inc variable types, etc. that will be used with Visual Cadd, otherwise the proper definitions will be made within VCType32.inc.  This is possible by using a feature of PB/DLL called "conditional compilation".

VCType32.inc should be listed first, as it may contain definitions required by the Visual Cadd include files that follow.  In the 3.0.1 API for PB/DLL, all procedure declarations are made directly to the native Visual Cadd DLL that actually contains the appropriate procedure.  In the Visual Basic 3.0.1 API, the declarations for ALL Visual Cadd procedures are made in the VCLink32.bas file.

The 3.0.1 API include files for PB/DLL declare the procedures as indicated by the appropriate file name.  VCMain32.inc is for VCMain32.dll, VCTool32.inc is for VCTool.dll, etc.  The only procedures declared in VCLink32.inc for VCLink32.dll are the nine new `BP' (by pointer) calls added for version 3.0.1.  With this version of the API, the new BP calls are actually coded in VCLink32.dll.  It is anticipated that the next API release will have these new BP calls coded in VCMain32.dll.  This is more consistent with previous practice and will eliminate the need of using VCLink32.dll with PB/DLL.  In testing the 3.0.1 API, it has been found that a number of Visual Cadd procedures declared in VCLink32.bas for Visual Basic, have not been properly exported from the native dll, VCMain32.dll, etc.  This means that a dll that uses VCLink.32.bas to declare these `missing' procedures, will link properly when loaded, BUT will not perform as expected when called.  The procedure in VCLink32.dll will call the appropriate procedure in VCMain32.dll, etc. and it will NOT be found.  These missing calls are listed in the 3.0.1 API documentation for PB/DLL.


Impact of using multiple source files in your program

If you would look at the Windows and Visual Cadd API include files (headers), you will find thousands of lines of code that are procedure declarations, variable definitions and equates (constants), etc.  Most of these will probably never be used by your program, so you may ask if all this extra baggage is really necessary and if it will add extra bulk to your program?

PB/DLL is smart enough to determine which portions of this additional source code is required to be compiled into your application.  A procedure declaration is only included if you make a call to that particular procedure, a variable type definition is only included if you use it, etc.  This means that with PB/DLL, the compiled size of your dll or exe is directly related to your code and is not adversely affected by including all of these extra declarations.

What you will notice is that it does take longer to compile.  This is due to not only the many extra lines of source code involved, but the extra number of disk files that have to be opened and read.  For example, our sample program here is now approaching 400 lines.  The compile time is 0.1 seconds on a 233Mhz computer.  When we include Win32API.inc this jumps to 15,800 lines at 1.0 seconds.  Adding the Visual Cadd files further increases this to 19,000 lines at 1.4 seconds.  As you can see, the Win32API.inc has the most impact.  We don't need it for most of our projects here and I often copy the required declarations to my source code, if only a few are required.  After VCType32.inc, the Visual Cadd files can be selectively included as well.  Most of the procedures we will be using are found in VCMain32.inc.  If you do the math, you will see that PB/DLL is compiling at over 800 lines per minute, making it one of the fastest compilers available.  You may find the slight delay well worth the simplicity of just including the appropriate files.                   
   
I have had DOS Basic programmers say that they sure miss the type and RUN of the good old days with interpretive Basic.  As the source code for these programs remained in memory, everything was done in one file.  Using all the tricks to create the smallest possible code generally resulted in a program that was hard to read and almost impossible to maintain.  With a compiler, we are free to use longer and more meaningful variable names, include plenty of remarks to help us when look back at the code at some later time,  have code that can be included under certain circumstances but ignored with others (conditional compilation), develop and test separate modules of code that can be used with any number of applications, etc.  Using multiple source files is just the first step realizing the benefits of modular programming.  

An introduction to Structured Programming

For most applications, good programmers use an organized approach to programming called structured programming. The original Basic didn't really support this kind of programming. However, PB/DLL, with its control structures and more advanced functions and procedures, is very well suited to structured programming style.

Structured programming is based on the theory that modularization makes for better programs. Modularization means grouping statements together (making modules) that have some relation to each other. In other words, you break up your program into logical functional sections. This makes it easier to write, debug, and understand the program.

Ideally, modules should be no more than a page long. This seemingly arbitrary constraint makes it easier to absorb the entire module at one glance. It's easier to understand a series of ten single-page modules than it is a single ten-page program.

For some projects, after this initial breakup, you're ready to write the program. More complicated problems might require you to break the modules into subsidiary pieces. The process continues until you have refined the material enough that you can write the code that corresponds to your ideas. This entire process is often described in books as top-down design, since you start with a general description and work toward a more specific one.

Once you have the logical organization in hand, you can start to design the overall structure of your program. For short, simple programs, these steps may only take a few minutes. For complex programs, it could take months.

To summarize the steps of structured programming (also known as top-down programming or top-down design):

1. Plan your program on paper. Ask yourself the following questions:

What is the overall purpose of the program?
What kind of input will it need?
How will it process that input?
What kind of output will the program produce? To where ?
How should the input and output look?
How can the program be broken up into discrete processes (modules?)
How will those modules fit into the main program, how will they communicate?
Can those modules be broken up into even smaller functional segments?

2. Next, write your main program. Don't worry about writing the individual modules that you separated out earlier. Instead, write stubs: Dummy statements that allow the main program to continue. This allows you to test the logic of your main program.

3. Finally (and this step will actually be several steps), write the modules one at a time.  Test and debug each module thoroughly before proceeding to the next.  If you've broken your module into even smaller processes, write the code for those processes first, test and debug each process, then put them together to build your module.

Using Modular Programming in our examples

If look at our simple examples so far, you can see that we have actually been creating "modules" at the most basic level.  Our one liners merely GET, SET or TOGGLE a specific setting within the Visual Cadd environment.  We have not included any procedural code or used any specific variables other than iError% which we have defined as GLOBAL (can be seen by all procedures in our application).  This would allow a separate module for error handling to be added later, if desired.

By EXPORTing these procedures, we have made them available to be used as a custom command within the Visual Cadd UI (user-interface).  If we continue this exercise to the fullest extent, we would have a support module for Visual Cadd that could change all the settings available in the API independently of the dialog boxes (including some settings not currently available in the Visual Cadd UI). 

If you look at the definitions for our procedures, you will see that actually two different procedure names are used.  The first is the name used within our application and the second (after the ALIAS) is the name (case specific) that is actually EXPORTed.  It would be possible to use another layer of modular programming here to better suite the overall scheme of things in the design of a major application.  What if we wanted our main application to be cadd independent and we had a second cadd program that could be accessed from our application as well?  By using modular programming, we could have a separate support module for that cadd program as well.  Our main application could use the same procedure name to retrieve data from a drawing and would not know or care that Visual Cadd or the second cadd program actually produced the drawing.  We would merely have to `flip a switch' and have the appropriate support module (DLL) loaded into memory.  Of course this is an over simplification, but demonstrates the power available with modular-programming in a more modern environment.
Expanding Our Sample DLL To Include More Features

In this example, we will use the #INCLUDE metastatement (compiler instruction) to also use a second source file called "Sample03.inc".  This file will contain all of the previously described source code.  This will put the previous examples "out of sight" so that we can focus on just the additional procedures being added.  The only line that has been moved to our current "module", is the #COMPILE instruction that specifies the desired name and path for our sample DLL.  Although "remarked out", the include statements for Win32API and the Visual Cadd API are shown as they would be used when desired.  This would allow the Visual Cadd procedure declarations to be "out of sight" as well, but with these examples, we will continue to show the appropriate declarations, variable definitions and equates.

One area that is quite cumbersome in the Visual Cadd UI, is the display of the various dimension elements.  This example will show how to provide an additional method to turn "AllDimPartsOn" and to show, hide or toggle the dimension line, left arrow, right arrow, left extension, right extension and dimension text.  These procedures are "exported" from our DLL and can easily be used in a user-defined custom command that can be used as a menu entry or called with a 2-letter shortcut from the keyboard.

Two additional procedures are included to show how easily we can overcome some of the limitations of the Visual Cadd scripts by using the API procedures.  "ToggleHandlePts" will not only toggle the handle points, but will change the "Symbol Snap" setting to match at the same time.  "ToggleDisplayUnits" will toggle both display units and dimension units between meters and fractional feet & inches.

Additional equates (constants) are introduced to show the values internally by Visual Cadd for the dimension items and display units.  If you have downloaded the Visual Cadd 3.0.1 API Help file, you may see that the "magic numbers" for several of the dimension items in the documentaion do not agree with those shown here.  The values for "Decimal Feet" and "Decimal Feet and Inches" have been turned around, as have the values for "Fractional Feet" and "Fractional Feet and Inches".  The values shown in this example are from the VCType32.inc file and should work as expected.

Many of the Visual Cadd API procedures will return various types of values that we will use in our programs.  In this example, the global variable iTemp% is used for this purpose in the "ToggleHandlePts" and "ToggleDisplayUnits" procedures.


'============================================================
'  SAMPLE03.BAS by Bob Benson
'  using PowerBASIC DLL Compiler
'============================================================
#COMPILE DLL "Sample03.dll" 'specify desired name and path
#INCLUDE "Sample03.inc"

'#INCLUDE "WIN32API.INC"
'#INCLUDE "VCType32.INC"
'#INCLUDE "VCMAIN32.INC"
'#INCLUDE "VCLINK32.INC"
'#INCLUDE "VCTOOL32.INC"
'#INCLUDE "VCDLG32.INC"
'#INCLUDE "VCTRAN32.INC"

'--- Declare Visual Cadd API calls here ---------------------
DECLARE FUNCTION VCGetDimItemShow LIB "VCMAIN32.DLL" _
          ALIAS "VCGetDimItemShow" (iError AS INTEGER, _
          BYVAL i AS INTEGER) AS INTEGER

DECLARE SUB VCSetDimItemShow LIB "VCMAIN32.DLL" _
     ALIAS "VCSetDimItemShow" (iError AS INTEGER, _
     BYVAL i AS INTEGER, BYVAL tf AS INTEGER)

DECLARE SUB VCSetAllDimPartsOn LIB "VCMAIN32.DLL" _
     ALIAS "VCSetAllDimPartsOn" (iError AS INTEGER)

DECLARE FUNCTION VCGetSymSnap LIB "VCMAIN32.DLL" _
          ALIAS "VCGetSymSnap" (iError AS INTEGER) AS INTEGER

DECLARE SUB VCSetSymSnap LIB "VCMAIN32.DLL" _
     ALIAS "VCSetSymSnap" (iError AS INTEGER, _
     BYVAL tf AS INTEGER)

DECLARE FUNCTION VCGetDisplayDistFormat LIB "VCMAIN32.DLL" _
          ALIAS "VCGetDisplayDistFormat" (iError AS INTEGER) _
          AS INTEGER

DECLARE SUB VCSetDisplayDistFormat LIB "VCMAIN32.DLL" _
     ALIAS "VCSetDisplayDistFormat" (iError AS INTEGER, _
     BYVAL iF_ AS INTEGER)

DECLARE SUB VCSetDimDistFormat LIB "VCMAIN32.DLL" _
     ALIAS "VCSetDimDistFormat" (iError AS INTEGER, _
     BYVAL iF_ AS INTEGER)

'--- Define Equates and Variable usage here -----------------
' Dimension items
%DIMLINE       = 0
%DIMLEFTARROW  = 1
%DIMRIGHTARROW = 2
%DIMLEFTEXT    = 3
%DIMRIGHTEXT   = 4
%DIMTEXT       = 5

' Display Unit
%InDec    = 0  'Decimal Inches
%FtInDec  = 1  'Decimal Feet & Inches - Feet in API docs
%FtDec    = 2  'Decimal Feet - Feet & Inches in API docs
%InFrac   = 3  'Fractional Inches
%FtInFrac = 4  'Fractional Feet & Inches - Feet in API docs
%FtFrac   = 5  'Fractional Feet - Feet & Inches in API docs
%Mil      = 6  'Millimeter
%Cen      = 7  'Centimeter
%Met      = 8  'Meter
%AngDeg   = 9  'Decimal Degrees               DimAngleFormat
%AngDMS   = 10 'Degrees Minutes Seconds       DimAngleFormat
%Mile     = 20 'Mile - not in API docs
%Kilo     = 21 'Kilometer - not in API docs

GLOBAL iTemp AS INTEGER
'------------------------------------------------------------
SUB MyAllDimPartsOn ALIAS "AllDimPartsOn" () EXPORT
'------------------------------------------------------------
CALL VCSetAllDimPartsOn (iError%)
END SUB

'------------------------------------------------------------
SUB MyHideDimLine ALIAS "HideDimLine" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLINE, %OFF)
END SUB

'------------------------------------------------------------
SUB MyShowDimLine ALIAS "ShowDimLine" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLINE, %ON)
END SUB

'------------------------------------------------------------
SUB MyToggleDimLine ALIAS "ToggleDimLine" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLINE, _
     1 - VCGetDimItemShow (iError%, %DIMLINE))
END SUB

'------------------------------------------------------------
SUB MyHideDimLtArrow ALIAS "HideDimLtArrow" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLEFTARROW, %OFF)
END SUB

'------------------------------------------------------------
SUB MyShowDimLtArrow ALIAS "ShowDimLtArrow" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLEFTARROW, %ON)
END SUB

'------------------------------------------------------------
SUB MyToggleDimLtArrow ALIAS "ToggleDimLtArrow" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLEFTARROW, _
     1 - VCGetDimItemShow (iError%, %DIMLEFTARROW))
END SUB

'------------------------------------------------------------
SUB MyHideDimRtArrow ALIAS "HideDimRtArrow" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMRIGHTARROW, %OFF)
END SUB

'------------------------------------------------------------
SUB MyShowDimRtArrow ALIAS "ShowDimRtArrow" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMRIGHTARROW, %ON)
END SUB

'------------------------------------------------------------
SUB MyToggleDimRtArrow ALIAS "ToggleDimRtArrow" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMRIGHTARROW, _
     1 - VCGetDimItemShow (iError%, %DIMRIGHTARROW))
END SUB

'------------------------------------------------------------
SUB MyHideDimLtExt ALIAS "HideDimLtExt" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLEFTEXT, %OFF)
END SUB

'------------------------------------------------------------
SUB MyShowDimLtExt ALIAS "ShowDimLtExt" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLEFTEXT, %ON)
END SUB

'------------------------------------------------------------
SUB MyToggleDimLtExt ALIAS "ToggleDimLtExt" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMLEFTEXT, _
     1 - VCGetDimItemShow (iError%, %DIMLEFTEXT))
END SUB

'------------------------------------------------------------
SUB MyHideDimRtExt ALIAS "HideDimRtExt" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMRIGHTEXT, %OFF)
END SUB




'------------------------------------------------------------
SUB MyShowDimRtExt ALIAS "ShowDimRtExt" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMRIGHTEXT, %ON)
END SUB

'------------------------------------------------------------
SUB MyToggleDimRtExt ALIAS "ToggleDimRtExt" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMRIGHTEXT, _
     1 - VCGetDimItemShow (iError%, %DIMRIGHTEXT))
END SUB

'------------------------------------------------------------
SUB MyHideDimText ALIAS "HideDimText" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMTEXT, %OFF)
END SUB

'------------------------------------------------------------
SUB MyShowDimText ALIAS "ShowDimText" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMTEXT, %ON)
END SUB

'------------------------------------------------------------
SUB MyToggleDimText ALIAS "ToggleDimText" () EXPORT
'------------------------------------------------------------
CALL VCSetDimItemShow (iError%, %DIMTEXT, _
     1 - VCGetDimItemShow (iError%, %DIMTEXT))
END SUB

'------------------------------------------------------------
SUB MyToggleHandlePts ALIAS "ToggleHandlePts" () EXPORT
'------------------------------------------------------------
iTemp% = 1 - VCGetHandlePt(iError%)  'calculate new setting
CALL VCSetHandlePt (iError%, iTemp%) 'toggle Handle Points
CALL VCSetSymSnap (iError%, iTemp%)  'set SymSnap to match
END SUB

'------------------------------------------------------------
SUB MyToggleDisplayUnits ALIAS "ToggleDisplayUnits" () EXPORT
'------------------------------------------------------------
iTemp% = VCGetDisplayDistFormat(iError%) 'get current setting
IF iTemp% = %Met THEN           'if current setting is Meters
   iTemp% = %FtInFrac           'change to Fractional Ft & In
ELSE
   iTemp% = %Met          'anything else is changed to Meters
END IF
CALL VCSetDisplayDistFormat (iError%, iTemp%)    'set display
CALL VCSetDimDistFormat (iError%, iTemp%)     'set dimensions
END SUB
                           

