Path to this page:
Home » Tips
CSDA
Tips on
Alpha Five &
Product Tips
|
 |
Last Updated 08
April 2010
Any
database program, Alpha Five included, is very complex in what it does
and how it does it. Database programs push the hardware, software and
networks much harder than almost every other typical programs people
use, like Microsoft Word, Excel or browsers. So, while developing in
Alpha Five is typically much faster, there are
still things that are very useful to know when developing a database
program so that it runs efficiently, with few errors, and is easy to
maintain.
Below
are tips on a variety of topics (many of which apply to any database)
that you should be aware of as you develop an application in Alpha
Five. Re-read each tip very carefully for it's meaning and
guidelines. And if this is just TMI (Too Much Information) for
you to absorb, we provide consulting, mentoring and training to help
you through these issues.
If
any of these tips are particularly useful to you, or make a big
difference in your Alpha Five application, or you believe any
aspect of what is stated is incorrect, we'd love to hear your comments!
CSDA
Code Utility specific tips
- Speed up starting an A5
Desktop with a Database shortcut
- Menus are too
complicated or too large
- Customize Menus
for a specific database
- Restoring messed
up button files
- Lost
customized button
- Accurate
Timing of code
- Quick Backup to send
files
- Modeless
Interactive Window
Alpha Five General Tips & Information
The Short List
-
(What to do
without the details!) Updated
23 Dec 2009
Otherwise, click below:
- Use functions,
Not scripts
- Choosing the right
& fast function
- Variable usage,
dimensioning and deletion Updated 30 Mar 2010
- Field Rule Recalcs
- Delimiter
characters for CSV Updated 26 Dec 2009
- Files over 2 Gigabytes
- Why Network
Optimization (shadowing) does not necessarily improve speed
- Lightning Query Optimization
(LQO)
- Record Locking 101
(and how to speed up Reports/Appends/Updates)
- Coding with
XBasic versus Action Scripts Updated
21 Mar 2010
- AEX Compiled libraries
Library Numbers
- Coding Tips Updated
07 Mar 2010
- Isolate Code that
depends on tricks Updated 03 Jan 2010
- Using Active-X objects tips Updated 27 Feb 2010
- Bitwise Logical operations (Binary) with numbers Updated 08 Apr 2010
Links
to Other
Alpha Five Tips
- AIMS
Web site - Alpha Five naming conventions
- AlphaToGo
- various Alpha Five Articles
- LearnAlpha.com
- various Alpha Five articles
- Proctor
& Peake - various Alpha Five tips
- Marcel
Kollenaar's tips on using DLL's with Alpha Five
- Dan
Blank's Tips
- QODBC - QuickBook interface to Alpha Five tip
CSDA Code Utility Tips
To speed up starting,
create a Desktop Restore shortcut for each specific database
you use.
When you start with the A5vX Desktop Restore, or the A5vX Last
Desktop
restore desktop icon shortcuts, it starts a special database and then
starts the actual database. Just start your database, then
select
the CSDA Code Utility "Create Shortcut" button.
Press OK,
and a new A5 Desktop Restore specific to the database will be placed on
your desktop.
Menus are too complicated or
too
large or have features I never use
Simply edit the dialog box menu for the appropriate dialog type
(Rolldown, Rollup, Minimize, Horizontal dock or Vertical dock) and
remove buttons you want. Do this by pressing the
CSDA Code
Utility "Help" button. At the bottom are various
edit
buttons for the dialog boxes. Just press one you want to
edit,
scroll down to the button lines (near the bottom) and "comment out" any
button you don't want to use by simply putting a single quote at the
beginning of the line to eliminate the button. If you made a
mistake, removing the single quote will restore the button.
After
you made desired changes save the file and exit. Then use any
utility's restart buttons to restart the utility with the new menus.
In a similar fashion you can also add buttons (both predefined and ones
that run your own code) to the menus. Just read the comments
at
the top of the button files and copy similar buttons as a guide and
modify.
Can I
customize Menus for a specific database?
Yes, you can. This is sometimes referred to as "skinning".
Simply select the "Edit A5 Buttons Loc" on the Utility's help
button, and when done, save to the database directory as a file name
"databasename.abt" (Be careful, as Notepad will try to save
it to
"databasename.abt.txt". Rename it if this happens).
This
file has lines that point to where the various other button files are
located, and they could be anywhere in the system, but typically should
be in the default Alpha Five "application" folder, or the database
directory.
Restoring Button files
If you have a problem with the CSDA Code Utility button files that are
not operating properly after changing them, you can restore
the
original ones by either
- Reinstall the utility
- Delete the button files (locations are described in the
documentation)
- Edit the files, delete all text, and save. They
will be restored to their original values at the next restart
Lost
customized button
You lost your customized button code for the CSDA Code Utility
after reinstalling or
updating the utility. Because the button files are subject
to
change from version to version (although they haven't in a long time),
the new versions always use the current default definitions.
However, all of
your old button files are renamed with a date at the end of the file
name.
You can either edit the new ones copying in your older changes, copy
the old ones to the newer ones (with the risk of a format change), or
use a button Location file skin to redirect it to some custom named
files that will not be overwritten.
Quick Backup to send files
When you need to send database files to others (or post on the message
board), here is a quick way with the CSDA Code Utility:
- Make sure the Zip
Backup? checkbox is checked, then
- Press Save
DB++ Othr
- Select additional
files that you want included
This will copy your database, dictionaries and web components, compact
(Pack) them, and then zip them together into a file of your choosing.
Accurate Timing of code
To test code speed, using the CSDA Code Utility, select a code editor
tab and press the Code timing
button and that function or script speed will be tested. It
is
generally best not to have any dialog boxes pop up in the code that
require user responses, as the user response time will be included in
the timing results.
If you are on the Interactive Window (IW) tab, just highlight (select)
some code, and press the Code timing button, and that code will be
timed. It is often best to initialize all needed variables in
the
IW, and then test the key code separately.
Results may vary up to
about 3%, depending upon what windows and Alpha is doing at the same
time. If you run the tests at different times where the Windows
environment and available memory has changed, your results may vary
Running different tests right after each other yields a
more accurate comparative timing value.
Modeless Interactive Window
Sometimes you need an interactive window in a modeless dialog, that you
can run code without changing the context of the primary window.
By using the Code Edit button to edit any existing code, you
get
a modeless script editor and an interactive window mode on the 2nd tab.
This allows you to probe variables and run test code without
changing the context of the other windows.
Alpha Five General Tips & Info
The Short List
(What to do without the details!) (
Email
Comments)
Updated
23 Dec 2009
- Use functions, Not scripts
- Choose Alpha Built-in functions that are written in C, vs
XBasic for speed.
- Dimension all variables in code as local where possible.
If
dimensioning Shared, Global or Addin, make sure variable name is
totally unique to your use (and doesn't conflict with other code of
yours or Alpha's built-in Code)
- Don't delete variables other than those created
within the current
code, and make sure the variable being deleted is dimensioned local
- Never do Field Rule Recalcs
- You can use any Delimiter characters you want between ASCII
1 and
ASCII 255, but in general use ones that have few conflicts between
Ascii 3 and Ascii 31, when you still want to use all the printable
characters and common control characters like CR, LF, Tab, Bell, Escape
- Separate memo fields into a linked table with just the link
and memo
field. For large Memo fields getting close to 2
Gigabytes,
place each memo field into a separate linked table.
- Network Optimization (shadowing) does not necessarily
improve
speed. Use if layouts are reloaded many times and
causes the
network to be max'ed out.
- Design your queries to use Lightning Query Optimization
(LQO)
- Never lock your tables longer than required, eliminate
non-running
totals if possible in reports and check off "Don't Lock Table"
in reports unless really required.
- Never use Wide Area Networks (Internet) to access
non-client-server
databases. Use a remote viewing program like VNC, PC
Anywhere,
GoToMyPC, TeamViewer or a Citrix Server (which creates virtual local
computers) that operate on a computer that is on a Local Area Network.
- Change Action Scripts to XBasic after perfecting them, to
forestall unexpected changes to their regeneration.
- Isolate Code that depends on code tricks or side-effects
into a
function that can be changed if the trick no longer works in a future
version of Alpha Five.
Use a function, not
a script (
Email
Comments)
Updated
23 Dec 2009
When you create code in Alpha, particularly using Alpha Five
Genies, or Action Scripts, you are creating code as a script.
It
may be embedded on a form, or available as a Global Script, but the
result is the same.
Move
the code
off the form (layouts)
The 1st point is to change your code to a global script or
function,
rather than embedding it on a form (or other layout - I'll refer to
them all as forms for this tip).
When you
place the code directly on the form, you typically have access to all
the variables and fields
available on the form. In other words it typically "inherits"
the
variable space of the object it is embedded in. In
addition, "parentform"
is an alias to the current form's name. If you
change the
name of the form and use the alias as a pointer in the code, the code
still works. The
code
is being executed in the context of the form, so has access to all of
those properties, as well as all variables that the form has access to.
However, if you move the code off the form and make it a
global script or function, you may lose the context of the form (which
can be totally eliminated by passing the form's pointer).
Recent testing in A5V5 and up seems that this is not the
case.
Parentform and Parent do seem to work in the context of the
function. However local variables on the form are
accessible
only if you pass the pointer to the function, or retrieve the form's
local variable space by using the parentform or parent alias to
retrieve the pointer to the form's local variable space.
You get these advantages with Global Scripts & Functions;
- Reduce
- You can use the code multiple times from many places, say on Form_A
and Form_B
- Reuse
- It is easier to
grab sections of code to reuse in other code without opening up layouts
in design mode and then finding the needed code
- Recycle
- It is easy to export/import the code and bring it to other
applications where you need to do similar code
- Fix Once
- You only have
to fix it in one place if you have to make a change to correct
something, than to search and change multiple forms (or layouts)
- Find
references - It is easier to search the global code
looking for code, and to change all occurrences.
- Faster to edit
- Each
time a correction is required with code embedded in the form,
you have
to change to design mode, open up the object's event code, edit the
code, save the code, save the form, and retest. In the global
code, for most cases, you just need to change the code, resave and
retest.
- Layout
Corruption easier to recover-
If the form or layout ever gets corrupted (doesn't happen for most,
but some do experience this regularly for reasons unknown), the form
will be easier to recreate, as the code is stored elsewhere
- Protection
- Global
code (except action scripts) can be protected easily by passwords and
can be also be encrypted when compiled into an AEX. The
protection for layouts and their code is much less effective.
Using Script_Play_Local("script_name") allows the global script code to
inherit the space from where it is used, so all local variables of the
form are also available (and changeable) within the script, which
simplifies usage for
beginners.
But this can be also be bad -- VERY BAD! Even if you use
SCRIPT_PLAY("script_name"), you still have access to other
global or shared level variable names that have been defined by Alpha,
or yourself, that you did not know
were being used. This can cause your's or Alpha's code portions to
fail, because of different uses of the same name. This makes debugging
a problem very hard, if not close to impossible. (Note:
Anything
listed here is just one example. The problem exists
everywhere,
100's of cases could be cited, and can fail even with you own
code, if you don't dimension as suggested.)
Don't believe it? Try
this in the interactive window!
(A5v6 through A5v10's December 21, 2009 build).
- just one simple example out of 100's that are known)
dim shared woy as n
woy=-5
?woy
= -5
?week(date())
= 16
?woy
= 16
Obviously, Alpha Five's
code does not
dimension the "woy" variable name. Here's another example (I use the
"delete woy" to clear any previous usage here for illustration)
delete woy
dim shared woy as L
woy=.t.
?woy
= .T.
?week(date())
ERROR: Variable type mismatch
Use
functions,
Not scripts!
I've seen people use
100's of scripts to accomplish the same as 1 simple function could do.
The problem with the function is that, in general, if you
don't
use a passed pointer (e.g. local_variables() etc.),
the function does not know the context of the code that called it.
No local variables from the calling code (where the function
is referenced) are available.
Normally this is good for debugging, and the context is not needed. If
it does need a context, e.g. it prints a report of an
ID that
is in the
current layout, then you can pass the ID value as a
parameter. If
you need many variables, you can pass a pointer to the local variables,
a pointer to the layout, or to just your own pointer with lots
of property values.
To help isolate
name usage, change the script code to a global function. A global
function can be created as simple as
FUNCTION script_name as V
( )
' original script code here
END FUNCTION
Then just use script_name() instead of SCRIPT_PLAY("script_name")
wherever you need that code to be placed. Also, make sure
each and every variable used is
dimensioned (prior to the code's 1st use) with DIM (or is in the
function parameter definition line).
See this tip for a discussion of
variable dimensioning.
If you do need the context of the form (layout), in a function, pass a
pointer to the context a parameter, such as
FUNCTION script_name as V
(layoutpointer as P)
' in the use of the function, pass the layout pointer, as in
' script_name(parentform.this)
with layoutpointer
' original script code here, has access and can
change all of layout's variables
' (which can be bad, so use this method
less)
end with
' Or prefix layout' variable name with pointer
(better method)
variablename=layoutpointer.layouts_variable_name
END FUNCTION
However, the function templates shown above are really not the best way
to use a function, but will allow you to quickly move your script code
into functions. The next step is eliminate passing the
local_variables() pointer, and instead pass just
the parameters
you need, combined with a returned value. Here is an example;
FUNCTION SumNumbers as N
(Number1 as N, Number2 as N)
' To use this function, use a line similar to
' sum_of_the_numbers = SumNumbers(1.5,
3.4)
' or put any valid numeric value or numeric expression in the
parameters, such as
' sum_of_the_numbers =
SumNumbers(numeric_variable1 *1.5, numeric_variable2)
' All of these are dimensioned in the function definition line, so they
are already DIM'ed
SumNumbers = Number1 + Number2
END FUNCTION
The key advantages to a Function over a script are
- Faster
overhead - The overhead is less for an XBasic function
call by a
factor of 15 over a script play.
There is no good reason for this, and the reverse is
typically
the case in most computer languages, but it happens to be true in Alpha
Five (and could change in the future, but not likely). It
does not matter much, unless your code's main
execution
time is very small compared to the overhead of the
call, and it is used
many times.
- Easier debug
- Variable usage and changes for locally dimensioned variables have
no effect outside the operation of the function. This makes
problems a lot easier to debug.
- Reuse Code
- It is easier to reuse similar code. If parameters
are
used with the function (and normally should be), it can meet the
requirements of many usages of the code, while adapting to the context
of those values
- Easier
Maintenance - By having the same basic code used
repeatedly, it is easier to
maintain and correct bugs in the code, without having to repair all
code in each use.
- Synchronous
Operation (Minimizes timing issues) - Functions tend to operate in a synchronous
manner, meaning the
main code waits for the function to complete before continuing.
This tends to solve many race and timing problems people
experience (except for User Interface(UI) displays, which are a different type of timing issue)
Note that any code including functions can start other asynchronous
threads/sessions, that once started, run independently.
The most common way that this is done is by opening another
Form or Browse layout.
- Expression
usage - Global functions can be used in expressions,
anywhere within Alpha Five.
- Simplified
calls to code:
In a script call you might do this
variable_A="1"
variable_B=2
script_play_local("script_name)
result=variable_C
In a function, it would look like this
result=Function_Name("1",2)
- Simplifying complex
problems - changes a complex problem to a
simple call, separating the usage from the implementation
E.g. To find the distance between 2 zip codes can be
simply a function call like
mileage=Global_Distance(zipcode_1,zipcode_2)
simplifying the actual
flow of code and
yielding greater comprehension of what the code is doing.
What
the function does inside may be complex, but using it is
simple.
My development library has over 1000 finished functions I have
developed
over time for many purposes, and exactly 1 finished script (the
AUTOEXEC
script). 'Nuff said? :?)
Choosing the right Alpha
Five Function (
Email
Comments)
Alpha Five has over 10,000 functions and methods. Methods are
basically functions associated with an object like forms, browses,
email etc, e.g. form.view(form_name), and can be considered to be a
function. How do you
choose one function (or method) over another? The
main
criteria
are
- It has some capability that another one doesn't have, or
- It is clearer as to what you are trying to do so that
others
viewing the code (including yourself 6 months down the road) could
understand it, or
- It is more modifiable for future changes, or
- It is faster
All of the above are important in selection of a function, but the
one that you can have the most control of is speed. No matter how big
databases are, they will grow over
time to a point where speed will become important. No one usually
cares if something takes 2 sec or 5 sec, and sometimes 1 min
or 5
minutes, but running a report/operation/etc. that takes 10 minutes vs 1
hour is significant (especially if you need an update every 15
minutes!) (Note: No matter how long something takes, once it
exceeds a second or 2, you should display the status or at least a
warning that the process will take a time to complete)
In terms of speed, Alpha Five functions (and methods) seem to
fall
into 4 basic groups
- Key Functions - Provided by Alpha, written in C or those
that are
in optimized function libraries that run very fast for what they do
- Key Xbasic Functions - Those written in XBasic code,
but the
majority of the operational code's time is spent using those functions
of #1. They are typically slower than those written using techniques of
#1 and could be user-written. These could also include XBasic
wrapper code (See #4 below for definition) calling Windows
API,
OLE or Active X functions
- Xbasic functions - Those written in XBasic, where most of
the
time is spent in Xbasic code - typically much slower than #1 or #2,
could be
user-written
- Wrapper functions - These are XBasic functions that call
other
functions of type 1, 2 or 3 and just rearrange or simplify the input
parameters and then return values from a function for easier use
How can you tell the difference?
One way is to use the
CSDA Code Utility's
accurate timer to test samples of code or entire functions and scripts.
This utility basically loops code many times to arrive at an accurate
timing value.
Those in #1 or #2 are generally faster by a factor of 3
to 10 than the equivalent in #3 and #4. Type #4 tends to be just a
little
slower than the equivalent of type #3.
Also, those in type #1 and #2 typically have simpler types of
parameters and inputs than those of #3 and #4. Also they may
have
less error checking of parameter values (e.g. a numeric parameter less
than 0 may fail) and less error error trapping than #3 or #4.
Occasionally, Alpha Software changes a function of type #3 or #4 to a
type #1
because they need it for internal uses or because enough developers
seem to need that speed, so newer versions and releases may be faster.
Re-testing is sometimes required to ascertain this. For code
that
operates on multi-Alpha Five versions, you can occasionally benefit by
using version dependent code.
So in general, stick to types #1 and #2 if it makes no difference. Use
type #3 and #4 if speed is not important. If you need a type
#3 or
#4 and speed is important, either
- Write an equivalent function for your use that might be
faster (I've done this many times), or
- Create a function specifically tailored to the usage
without any bells and whistles (features), or
- Embed the equivalent code in each and every place you need
to use that code that needs the speed.
The last affects readability, but can speed things quite a bit,
particularly where the function's main code execution approaches the
speed of the overhead of the function call.
A new feature (Called Code Count) to be released in an upcoming CSDA
Code Utility update will do counts of various scripts and function
usages in your databases, and will indicate if the code is user XBasic,
Alpha Five XBasic or Alpha Five internal code, and whether
there is any documentation on that function, a useful starting
point.
Variable Usage,
Dimensioning & Deletion (
Email
Comments)
Updated 30 Mar 2010
Alpha Software's webhelp also has some additional information
here:
Variable
Scope
Basic guideline rules for variable usage are:
Dimensioning styles
There are 2 styles typically used for dimensioning variables.
They are
- Dimension all variables at the very top of the code.
This
guarantees that all dimensioning occurs before 1st usage.
There
is a very small overhead with dimensioning.
- Dimension all variables just prior to first usage.
This
eliminates unneeded overhead of dimensioning, and makes sections of
code more independent (for copying to other code). However, the
variable may not be dimensioned in the path the code took, so if not
executed prior to the first usage, it is a potential coding
error waiting to happen.
Dimension
as Local
If a variable is not DIM'd, and you don't reference an
explicit variable space
(addin, global, shared and local), the current most limited space that
is DIM'd is used for references and assignments.
If not DIM'd at any
level, then there is implicit DIM as a local variable (or at the space
as defined by the WITH/END WITH command directive) upon 1st use.
Note
that as of Jan 1, 2010 an explicit "local" DIM, as in "DIM LOCAL
varname AS type" does not work. So to dimension at the local variable
space (or at the space
as defined by the WITH/END WITH command directive), you must do a
"DIM varname AS type".
In a similar way DELETE LOCAL varname does nothing, but DELETE varname
deletes the lowest level space defined
DIM GLOBAL, DIM SHARED, DELETE GLOBAL and DELETE SHARED do work though
If
you use a property pointer, it will use the scope of that pointer.
So
you could force a local DIM or delete by doing something similar to
(substituting the variable name for varname and the type below);
dim localptr as P
localptr=local_variables()
DIM localptr.varname as type
' for each DIM needed at the local level
DELETE localptr.varname
' for
each DELETE needed at the local level
You could do similar things at other levels with
- SHARED (use session_variables() instead of local_variables)
- GLOBAL (use global_variables() instead of local_variables)
- ADDIN (use addin.variables() instead of local_variables -
notice the period, not underscore),
Cautions
using SCRIPT_PLAY_LOCAL()
Dimensioning variables as local helps eliminate un-intended
side
effects, however, if you create scripts (action or XBasic) and use
SCRIPT_PLAY_LOCAL() to execute it, it operates in the same variable
space as the place it was invoked from.
For your code, you know what variables you are using and can avoid
naming reuse.
However, Alpha Five
uses many cases of
SCRIPT_PLAY_LOCAL() in it's code generated by Genies and Action
Scripts, so there is a potential (although relatively small
chance) of
their being a conflict of names between what you use and what Alpha
generates at the local level.
Xdialog Variable Spaces (Updated 30 Mar 2010)
XDialogs inherit the local variable space from the code they were
started from. This means any variables dimensioned locally prior
to starting the XDialog will be available in the XDialog. And of
course, as before, any references to variables that are not found
dimensioned locally, will be checked in the shared variables and then
the global variables.
Some of the controls in XDialogs, e.g. ListView objects, use an Active
X Control (another program essentially). They are implemented in
ways that require some variables to be dimensioned at the shared
(session) level, as those Listview events are running in a different
variable space and must have access and set some values from
the Xdialog variable spaces.
Minimize
Variable Name collisions with code not your own
If you need to prevent conflicts (collisions) with creation
and deletion of variables with the same
name at different scopes (particularly at the shared, global or
addin scopes), by code that is not your own (e.g. Alpha Five
built-in functions, code generated by action scripts and genies) create
a uniquely named variable
space pointer (possibly with a GUID or time stamp in the name), and
reasonably long to be unique to a maximum length of 23 characters
(Alpha can use 24, but has had some places that only saw the 1st 23)
and
then use that pointer to create property values (variables)
for all the ones required at that scope.
That's what is done in the
CSDA Code Utility. Version
2.69 and after, creates 1 global and 1 addin pointers and no
other top level variables in
the entire program. The name is chosen to be long and not common to
eliminate possible conflicts with other programs. Many
variables that are needed by many aspects of the program are stored
under those pointers as properties.
Why you shouldn't delete a
variable, nor have to
The reason for not deleting a variable, and for that matter using a
variable from any other session (also referenced as the words process
or thread in Alpha Five - a layout like a form or browse always runs in
it's own session), is that it has an independent
purpose and timing.
In most cases, when you create a variable, you intend
on using it and for that particular variable type. Its use should
have a determined purpose, and does not need to be redefined to
another type. It would be better to create another variable of
the type
needed, e.g. tmpC, tmpN, tmpL etc. for Character, Numeric, Logical etc.
E.g. two forms create a global variable "tmp". One set's it to "text",
the other deletes it and sets it to the number 2. This will cause an
unintended side effect (read as failure) in the other form. Just
changing the value alone will cause the other form to fail what it was
doing, unless you intend it for use as an inter-thread communications.
In that case usage must done in a way that allows for proper
operation, and that one process does not receive a partial message (A
whole other subject to discuss!)
When a session finishes, all the variables at the session (shared)
scope and
local scope are
released (deleted). Similarly, when a function or
normal
script terminates, the local variables for it are automatically
deleted.
For Global or Addin variables, they persist. Global variables
remain until you close the database, Addin variables remain until you
close Alpha Five (even if you open another database). If you
must delete the global or addin variable, try to do it in the
same
session that created it (and the same applies to variable writes,
although it is generally OK to read from elsewhere). If not,
try
to make sure, by program design, any other session
that
writes or deletes it, is not executing at the same time.
If you need to change variable types, e.g Character and Numeric, the
only real
place I've seen a true need for this, is in a function's
return value
where the return value is type A (can be any type).
E.g. MAX() function works with numbers, characters,
dates, and the return value is the same type as the inputs.
You might start with an initial value of -1 to indicate
error, but for some cases, you might be returning other types. If the
function code works in a way that you only need to set the return
variable once for any particular exit, then you can set it for the 1st
time for that exit path, and not have to delete the previous usage at
all, as there wouldn't be any
Issues
with Action scripts using DELETE
Alpha Five action scripts don't assume any knowledge of other code
that's
been used previously (including the same action script action used on
another line) and thus deletes the variable to make sure there is no
conflict. This carries the risk of deleting another variable at a more
"global" scope that might have an identical name (unlikely for Alpha
script naming, but it could happen) and being currently run.
It
could also delete a variable that you named that had the same name.
Making sure your code dimensions everything as local
eliminates this issue. If a variable can not be
dimensioned as local, make sure it has a unique name.
Special Cases for using DELETE
You might be able to come up with some code examples that do need to
delete variables, but those truly requiring it are going to be very
unique in their purpose.
One example of this is the Code operations of the
CSDA Code Utility.
This has the ability to run other people's code for testing purposes
and can't know what the potential return values types are going to be.
Thus,
there is exactly 1 delete of a variable in the code. And since it's a
"local" variable, the code that created it is the same as the one that
is deleting it, a safe operation.
Referencing
Variables at other levels
Even if variables with the same name are defined at more than 1 level,
you can reference these higher scoped variables, and test for their
presence.
' They can also be
referenced by creating a pointer
dim localptr as p
dim shareptr as p
dim globalptr as p
localptr=local_variables()
shareptr=session_variables()
globalptr=global_variables()
value1=localptr.varname
value2=shareptr.varname
value3=globalptr.varname
' They can also be referenced directly by prefixing the function on the
property name
' This will not work with A5 directives (commands) like DIM, DELETE
value1=local_variables().varname
value2=session_variables().varname
value3=global_variables().varname
If you want to test for a scope of a variable, these examples will
return logical values
logicalvalue1=eval_valid("global_variables().varname")
logicalvalue2=eval_valid("session_variables().varname")
logicalvalue3=eval_valid("local_variables().varname")
Don't use Field Rule recalcs (
Email
Comments)
The recalc of field rules
applies to all field rules, not just specific ones of interest. It's an
all or nothing proposition (for the most part).
Suppose you have a field rule with a field with a default value of
DATE() holding the record create date (some of these might be
blank from records imported). Another field has a calculated
field of DATE() holding the date record was last modified (by a human,
not a program).
If you do a recalc of the field rules, the blank create dates might be
set to the current date, and the last modified date will be
changed to the current date, not what was necessarily intended.
The field rules are intended as a set of rules to be applied while
entering and changing data by human input (exception probably being
auto-increment). The rules are applied to the record based upon the
current values of the database at the time of entry or change.
If you have any field rule that depends upon date or time, or the
current state of other records at the time, then a recalc of field
rules totally invalidates the conditions that they were when they were
created originally.
If you must perform an operation like the field rules, then run a
global update or other operations that take in consideration of what
must be done to get the correct results.
In the above example you, would not global update the create date, and
may or
may not global update the last modified date, depending upon intended
results.
Also, using variables in a field rule is generally a bad idea.
If
the variable is undefined or uninitialized, the field rule will not
work. The typical way to initialize a variable is
via an
Autoexec script or in a form or browse layout's initialization event
code. This does not happen if you open the table without the
autoexec, or open a form (e.g. the default form) where there is no
initialization event code.
So, to sum up,
- Field rules are primarily for human input to tables/sets
only
- Do not use field rule recalc except to test development
copies
- Use update operation or other code for other changes
similar to field rules
- Don't use variables in Field Rules unless they handle the
case when the variable is undefined or uninitialized
If you look at the descriptions of the general method and table method
of recalc and this
link,
it is highly probably that they are using one of those functions to
perform the recalc. Based upon the documentation of the link, it
implies a wide variety of effects besides calc fields. Without testing
intensively, it might be hard to prove that it doesn't affect other
rules, although proving it does affect other rules means finding just 1
or more, which might come out in testing quite quickly.
Based upon the documentation of the functions and my link and that
there seem to be no other functions applicable, I am guessing that
recalc of calc fields is really recalc of field rules. This also agrees
with historical history of the recalc process as well.
Delimiter characters
for CSV (character separated values)
(
Email
Comments)
Updated
26 Dec 2009
Normal choices for CSV lists are comma, Carriage Return/Line Feed, or
Tabs. Vertical bar ("|"), space, colon and semi-colons are
also
used often. But whatever character (or character string) you
choose, there is a chance
the user could have text with that character.
But since Alpha can have any value from ascii 1 to ascii 255
(chr(1) -> chr(255)) in a text string, use a chr(x) as the
delimiter, where x is a
value between 1 and 31 with the exceptions of 7 (beep), 8 (backspace),
9 (tab), 10
(line feed), 12 (Form Feed), 13 (Carriage return), and 27 (escape).
Alpha Five also uses values 1 (Start of Header), 2 (Start of Text), and
11 (Vertical Tab)
in some of their code, so avoid these as well. The value 28
is a
good choice, as it is not common to find a binary "00011100"
in
text or binary files. In most cases, set a variable name,
such as
chr28 to the value, as in
chr28=CHR(28)
or
field_separator=chr(28)
What to do if your database
files are 2 Gigabytes or larger (
Email
Comments)
If you have a database data file (*.dbf) or memo field (*.fpt) that is
approaching the 2 gigabyte maximum allowed size for these formats, you
must get all files under the 2 Gigabyte limit.
The solution is to segment each memo field of the table into separate
tables with links back to the main table holding all the other
non-memo fields. If you are exceeding 2 Gigabytes with the main dbf
file, then separate fields into more than 1 table with a link.
Alternatively, use an SQL database if you are able to.
Why Network Optimization
(shadowing) does not necessarily improve speed
(
Email
Comments)
Network Optimization makes a copy of all layouts (such as forms,
browses,
reports), operations (like exports) and user code to a faster location
(typically a local drive) in attempt to minimize network traffic of
these. Data is still accessed from the "server".
Only if more than one system's access of layouts, operations or code
interferes with the network traffic of another, does the network
optimization become useful from a global network performance of all
systems. Alpha Five caches the code to some degree, so any
local
increase in speed is seen only for those applications that
open forms, browses and other layouts
repeatedly. Operations
and code entries seldom cause a cache miss, and thus their reloading
does not happen often during normal (not-development) operation.
Lightning Query Optimization
(LQO) (
Email
Comments)
Lightning Query Optimization (LQO) is a process of Alpha Five
that happens automatically whenever you apply any filter or
query
to a table (except for the filter inside an index definition). It
speeds up query time by creating a dynamic range to an index.
Back in Alpha Four, (and actually is still doable in Alpha Five), you
might do a search (query) by setting a range that reduced the number of
records that needed to be searched.
E.g. If you wanted only people whose last name began with S, your range
for an index whose expression was
LastName+FirstName
would be "S" through "Sz". Alpha would only access those "S" records.
In order to be more generic than a range, LQO takes a part of the
expression of the filter (e.g
Between(LastName,"S","Sz)
and looks to see if there is an index that uses the field of that part
of the expression at the beginning of the index expression. It then
tries to set a range that restricts the range of records that need to
be accessed, and then applies the filter to only that record. So if
your filter expression was
(FirstName="John") .and.
Between(LastName,"S","Sz)
then ideally it would search the records only starting with "S".
Unfortunately, LQO isn't super intelligent. You need to help it a bit
to find a matching index. It is best that the beginning part of the
filter expression that you want to match to an index rearrange the
filter terms to either start with a matching equality (or >= or
<=) statement or use a Between() with constants for the 2nd and
3rd
arguments, as in
Between(LastName,"S","Sz)
.and. (FirstName="John")
or
LastName="Smith" .and.
FirstName="John"
So for a last name search, Alpha Five would be just as happy with index
expressions of
LastName
LastName+FirstName
LastName+FirstName+Zipcode
It may not work with things like
trim(lastname)+trim(firstname)
LQO may also not work with query expressions that use expressions that
are complicated inside a between() like
between(trim(lastname),"S","Sz")
between(lastname,BeginValueVariable,EndValueVariable)
But it may work with
between(lastname+firstname,"S","Sz")
Different versions of Alpha Five may be better than others in using LQO
for more complicated expressions, but in generally, make sure it
happens by keeping the expressions simple.
LQO will not help (and could slow it down a bit) if the range of
selected records is a large percentage of total records. This is not
typical, but happens if you create filter and index expressions
incorrectly or reference a large portion of the table.
When you get the LQO right, your speed of accessing records for large
tables will increase a lot. That's the only way to really tell if LQO
is happening (unfortunately it would be nice if Alpha had an LQO
evaluator which would show you when it would actually happen)
Record Locking 101
(
Email
Comments)
In databases that are not "True" client-server databases, which
includes Alpha Five's DBF file format, as well as Filemaker Pro, and
Microsoft's Access (typical usage) and many others, there is a process
that happens with each access of data that is referred to as "Locking".
Since each client computer is actually writing to a portion
of a
shared file someplace on a network, this needs to occur so that data is
not used that is only partially changed.
Imagine that data was in the process of being written to the shared
file and you read the data at a point when only part of the
data
had been written. This would give you a mix of the old and
new
data.
To prevent this, writing and reading of the data must be
performed
in "Atomic" operations (one that can not be interrupted during the
process). This includes parent records and every child record
that
is linked with referential integrity (referential integrity does not
actually require this child locking, but is the way that it is
implemented in Alpha Five). Locks are performed by the
operating system, so each one has an associated time that it takes that
can add up. Since it is atomic, every access by
other users
and processes
must wait
while this happens. This atomic
operation generally goes something like this;
- Request and wait for a file lock on all relevant data files
of
the database (typically, data, indexes, memo). Some allow you
to
cancel this step if waiting
- After receiving a file lock, request a
record lock for each
of
those files for the needed records. There
can be read record locks and write record locks for
the
respective needs,
or they may be identical depending on the operating system.
Reads are typically faster than write locks, as the
writes
have to read the original sector(s) on the server hard drive, modify
the data on the record part, and then write back the sectors.
- Release the file lock while keeping the record lock.
The record data can now be used or changed. When done working
with the specific records, the reverse process must be done to release
the locks. For a read, the data is generally read quickly and
released.
For a write, it depends on the database program. Some release
it
after a quick read, then later repeat the process to write the final
changed data. If more than 1 process tries to write the same
data, the last one written generally is one. Others, such as
Alpha Five, hold the write lock on the record forever until the changed
data is
saved.
The release process goes something like this;
- Request and wait for a file lock on all relevant data
files of the database (typically, data, indexes, memo). Some
allow you
to cancel this step if waiting
- After receiving a file lock,
release the record locks.
- Release the file locks
These atomic locks and releases are sent by the operating
system
to the computer where the data is stored. It is fastest if
it's
the local drive (microseconds), a bit longer on a Local Area Network
(LAN), and can be seconds over a Wide Area Network (WAN) (such as the
Internet).
Usage on a WAN will slow a database to being
unusable.
However, there is a solution. Using a remote program
like
PCAnywhere, VNC, or a Citrix server, they move the programs operation
to the LAN or local computer, and essentially just send an image and
allow control of the program remotely over the Internet.
In some cases, the database program may actually hold the file locks
for multiple accesses of record data, say 50 at a time. While
the
file lock is asserted, anything can be done. Then releasing
the
file lock so other processes can use it. This might be done
for
generating a report, or appending data etc. to lessen the impact of
many record accesses, as the lock process has a fairly high overhead
compared to reading and writing records. Some database
programs
may allow you to explicitly control this to "Batch" process
data,
however, Alpha Five does not. In Alpha Five, you can open
tables
with exclusive use if no one else is using it, but this would cause
other processes to fail that tried to share the data.
In SQL and other True client server databases, the same lock
processes basically happens, except now 1
computer controls
the entire database. No file locks or file unlocks are needed, and the
computer manages what amounts to the equivalent of a record lock.
Any requests to read and write data go to the server, who
honors
the request to the data and sends back the data read, or updates the
database with the new data. This gets rid of the time
required to
get and release locks over a network, speeding access to the data.
However this comes at the cost of requiring more work for the server,
that was previously being done at each computer. The server can allow
multiple workstations to open the same record for change, and when the
data is presented back, can manage which of the workstations'
data
will be accepted for changing, and/or flag differences between the
multiple data being written, and manage those differences.
There is one other advantage of a True client server database.
In a non-True client server database if a workstation crashes during
the lock/unlock process, it can stop every computer using the database.
If it crashes while a record write lock is present, no one
can
change that record. In most cases, these locks are released
after
the computer is rebooted, and in some systems (but not Alpha Five) may
have some automatic time-out. But in a True client server
database, the workstation crashing just loses the data it was working
with, and the server continues normal operation.
Alpha
Five specific locking info tips
Disclaimer:
Many of the items
listed below could change between versions (or even patched updates) of
Alpha Five. They are guidelines and should be verified for
your
usage.
Normally, for most normal record accesses in a shared environment, A5
locks (arbitrate might be a better word here) records (and indexes) on
a record-by-record basis. If a record change causes a change to a
record, or when a new record is entered, A5 performs a very short
indivisible reading/writing of the tables and indexes that for that
short time (typically milliseconds on a LAN), you effectively
have exclusive
control of the database "files" (tables/indexes), but not of the
individual records locked by other users/processes changing another
record.
If the table is being used by another process/user, you can perform all
the normal single record type XBasic commands.
Same thing is true for the more global operations like append, copy,
crosstab, export, intersect, join, post, subtract, summarize and
update. The most interesting cases most would use are the append and
update. In these, the table's indexes are potentially modified.
Shared
vs Sole
Use during Appends and Updates
A5 has 2 different modes of operation for this kind of case. If the
table is in use by another, it will update the index as each record is
modified. No problem sharing here.
The 2nd way, if it determines it has sole use (still could be in share
mode though), will do the operation and then rebuild the indexes. This
temporarily locks (for what could be a long time, mind you) the tables
from all other users. They will get a locking error with an automatic
retry. As soon as A5 finishes the re-index (and it only rebuilds
indexes that use fields that have been modified in the append or
update), the file locks are released and the other users go on their
merry (although a little miffed for someone locking them out for
potentially a long time!) way.
Why do it this way you say? If the majority of records are going to be
modified, it is much faster to update the indexes at the end of the
operation. This can backfire when the number of changed
records is small compared
to the total size of the table. Assuming you are the sole user, where
the break even point is, I've never actually tested, but I suspect it's
something about where you change about 40% of the records.
To force Appending/Updating to use the shared method, you can
not
count on other users also using the table. To force this,
open
the table in a different session. It is easiest to just open
a
form (which can be hidden) while doing the operation, and
close
the form afterwards.
Some code that does it
frm=form.load("tablename")
update.run_silent("updateoperationname")
frm.close()
Tests on a
35000 record file had an update take about 11 seconds in the above
method, versus 19 seconds for the index rebuilds happening at the end
(index expressions are very complex for this table) when the table is
available exclusively.
Measuring code speed for short
time events is difficult, and the profiler functions of A5 can give
wildly
inaccurate values to the execution speed differences in
many cases. The only way to get an accurate time result is to
run
short time code pieces many times over, and make sure the loop overhead
is not significant. The second part can be difficult to do.
The accurate speed timing button of the CSDA Code Utility for Alpha
Five does just that.
Is
the overhead
of an Update operation slower than parsing through the records?
For large numbers of records where either the index rebuilds don't take
long, or using the method shown above to update indexes on the fly,
normally the answer is yes. For under 10 records, it is probably faster
with code, with 10 to 100 records probably being the cross over point.
It's even possible to use both methods, depending upon # of records to
process, if the value changes dynamically.
Force LQO where possible
Depending
upon your options selected in operations, the 1st thing it might do is
perform a query
of the source or destination tables. Make sure your filters will invoke
LQO (Lightning Query Optimization), otherwise you will be doing a slow
query if the query results do not include almost every record of the
table. Each read of the record for a query requires record
locks.
Accessing less records (by using LQO) will reduce
locking/unlocking requests on the network.
Open
and Close
of Tables
The Opening and closing of a table, either explicitly by code, or
implied by the code being executed is a fairly long process over a
network. (typically 25 milliseconds). For some code, e.g
lookupc(), lookupn() and similar, they actually are much faster if the
table is already open (at least in more recent versions of A5).
This implies that they are using an already opened table, to
save
time. If you are performing a lot of these operations, it is
best
to open the table before the lookups, and close after all are done.
To check which functions might use an already opened table, try the
code with a table open, and without, and measure the speed.
Of course, you should always close tables the code
has opened on the way out for cleanliness.
Report
Locking
Alpha Five locks reports for the whole process while generating the
report, unless you take specific steps to not have this happen.
A
report that either doesn't uncheck lock table on the report properties,
or
uses summary values that are not of the running type, or has widows and
orphan turned on anywhere will lock the
table, sometimes for a relatively long time. Having the Lock
tables checked in the report properties (and this is the
default),
using summary values that are not running values, placing summary
values at tops of groups as opposed to bottom, sorting/breaking groups
differently from the sort of the entire report, using widows
or orphans, using page x of y, are just a few things
that slow the application down for everyone, by locking the table(s).
A report needs to be locked when it is required to make multiple passes
through the table for calculations that summarize items. It
guarantees that a pass 1
though the data to get some values needed to format the report is
identical to pass 2 where it actually creates the report.
Some of
these double passes go all the way to the end, some just to
the end of the current page or group.
E.g. to
compute percentages, you need to know the total 1st. Let's
say you don't lock the table. You pre-process the total of some
number. Then as you process each record's value, you divide by that
total times 100 to get the percentage. If some record that is part of
the total is changed, or a new record is added, the actual total might
change, but the total number that A5 computed will not change. It's
possible you could have percentages totaling more or less than 100%
Instead, use calculated fields (on reports) that use functions like
(which I'll call running summary values)
RUN_TOTAL, RUN_AVERAGE, RUN_COUNT, RUN_MAXIMUM, RUN_MINIMUM,
RUN_STDDEV, RUN_VARIANCE
instead of
TOTAL, AVERAGE, COUNT, MAXIMUM, MINIMUM, STDDEV, VARIANCE
PAGE_TOTAL, PAGE_AVERAGE, PAGE_COUNT, PAGE_MAXIMUM, PAGE_MINIMUM
PAGECOUNT
Following the above means that they must also be
used in the footers,
and can't be placed in headers, as it does not exist prior to the
header being generated. Page x of y will also cause 2 passes as
the y can't be determined until
running through the data 1st generating a quasi-internal report.
Group Keep/Balance column height/Keep with detail/Balance column height
(widows) Report Group Properties, Keep together on page (Orphans)
Report Region Properties all force multiple passes and hence locking
through the data.
As you might surmise, the running versions are much faster, and should
always be used unless you must have the summary prior to when all the
records are processed for the printing pass..
Simplify
Sets
Use of a set that is overly complex for the report's needs is another
bad idea, as it requires unneeded locks. Create a set
tailored
for the usage by the report. Turn off referential integrity
for
that set and set any children to read only where possible.
Sometimes it makes sense to copy the needed table records to a local
drive (could also be any place, but the local drive is fastest
normally) and run the report there (this requires a fair amount of
code). The copy goes very fast, and then only the local user
is
impacted by the report (and does not cause any locking of the shared
tables over the network)
The
general rule is never lock shared tables, ANYTIME, ANYHOW!
Xbasic
versus Action Script Coding (
Email
Comments)
Updated
21 Mar 2010
One should be familiar with the strengths & weaknesses of any
tool
they use, and XBasic or Action Script coding in Alpha Five is no
different. Below should give you some of the advantages
and disadvantages of Action Scripting and how to "bullet-proof" your
code a bit more.
What
is an
Action Script?
Action Script coding contains individual Action Items. Each
Action Item allows specifying in a menu or dialog driven
sequence,
a number of values and settings that generate an underlying XBasic
code.
The Action item's underlying XBasic code is regenerated each
time you save the Action Script.
Action Script Advantages
- Can be faster to create what you need, especially for the
non-trivial action items.
- Easier for non-programmers or those with less experience to
create something that they couldn't
otherwise create on their own.
- Easy to change a setting and regenerate new code
- There are some techniques and functions buried in Action
Scripting
that are not documented elsewhere, but are worth looking
at and using
them to generate code (especially XDialog)
Action Script Disadvantages
- Many settings or combination of settings mean that all the
possible choices could never be tested in their generation of
XBasic code. This means that uncommon selections may never
been
encountered by anyone, nor validated to it's working.
Here is a thread from the message board that indicates a bug in the
code generation of Action Scripts - Re:
Variable Type "H"
- It is possible to select settings of an action line that
make
no sense, and may generate inoperable Xbasic code. Newer
users
tend to select these out of ignorance or via a "try anything to see if
it works" approach.
- Generates a lot of code to determine the context of the
current
action line, which is repeated for each action line, slowing the code
down
- Many initialization and completion code, e.g. Opening a
table/Closing the table, are repeated for each action line.
This
puts more overhead to the network, Hard drive and other resources, and
slows things down as well.
- When you edit code in XBasic, it's code is always the same
each time. This is not true for action
script code. Each time an action script is opened for editing and
subsequently saved
(whether changed or not), the underlying XBasic code for all action lines
are regenerated.
With a new Alpha Five patch or version number, the same Action Script
settings could potentially generate different code. In most
cases
this is fine, as it should be accomplishing the same as before.
But occasionally, it results in problems even though no real
changes to the code was intended by you. Essentially, this
means
that each time you save in a changed Alpha Five patch or version, you
should retest for proper operation.
- Only so many development dollars can probably go to
correcting
Action Script code generation errors, but the underlying Xbasic code
functions used must work for both Action Script and Xbasic coding.
- Action Scripts are script code, not functions.
See the discussion of the advantages of functions over scripts
Real-Life
Action
Script example vs XBasic
This is the XBasic code that an action script line to set a form field
to an expression value generates:
'Set 'Value' property of
'TimeStamp' in Form 'TestForm' .
DIM form_name as c
if
is_object(topparent.this) then
form_name = topparent.name()+".this"
else
form_name = ""
end if
DELETE expression_result
expression_result
= eval("now()",form_name)
parentform:TimeStamp.value = expression_result
This is the same exact functional XBasic code without the Action Script
un-needed code
parentform:TimeStamp.value=NOW()
In
the Action Script, the actual code has the disadvantages of
being
slower, more complex, deleting a variable named
"expression_result",
that could have potentially been a global and/or shared variable prior
to
that point that would now not exist. If both global and
shared levels existed before the delete, now the global will still
exist. The
"expression_result
= " line will now assign a new value to the global variable
"expression_result" since
it was not explicitly dimensioned at a lower level. Either the
global variable will change it's value, or possibly
cause an execution error as the object type (type T for time here) does
not match the current type of the global variable.
Recommendations
Most users of Alpha Five probably use Action
Scripting more than XBasic coding. It is useful, particularly to
non-programmers, but even
as a 1st step for programmers. However, I would
encourage moving to XBasic coding as your personal abilities
allow.
Use Action Scripting as a tool to learn Xbasic. Use them to
show
the way or at least to point in a direction to go.
Then convert the Action Script to XBasic
code, take out
the extraneous coding and redundant sequences and you almost have
"normal" code. The difference
will be faster, more reliable code, over the long haul.
In the long-term, the more Xbasic you know, the better off you will be.
It will allow you to use the debugger more effectively and generate
faster, cleaner code. Inevitably you will end up needing something
Action Scripts can not address. Action Scripts can do
many
things, but it doesn't do everything that XBasic coding can.
Some
more comments of
Action Script Code Generation
The moment you save an action script being edited (even with no
modifications), the action script dialog settings are evaluated and new
code is generated. So let's say you created in release "X", but are now
editing in release "Y". Saving the Action Script will generate the code
per the new releases' Action Script generating code. So even if you did
not change anything, a small edit of an Action Script line
followed by a save, can result in potentially different (and untested)
code!
A similar thing could occur in the way a function works in an Xbasic
piece of code operates, but at least with XBasic code, your code would
not have changed! Also, it is much less likely that a function would
change in a bad way in what it does, versus the code
that Action
Scripting might generate.
So, one could argue that the prudent thing to do would either change
the code to XBasic so that it will not change, or make sure the
underlying Xbasic code you start with, is the code you end with (except
for your changes) when you edit in a new release.
Despite the surface appearance that they are the same, they are
actually radically different. A function (Alpha's or a user's UDF has
code that is syntactically compiled without errors otherwise
it
would never be able to be used) is tested for what it is.
But Action Scripting can generate myriads of combinations for
the
settings of a single action script line that would generate code that
was never validated. In probably 99.9% of the cases this is
fine
and works as expected. But not always (see this thread
Re:
Variable Type "H" )
Some Action Script lines are very simple and straightforward. These
seldom have issues, as there is almost a 1 to 1 correspondence to the
XBasic code they generate. Others actions generate code from many
selections of menus and settings. These generate a string of code that
needs to be compiled (which Alpha does when you save it) without
errors, and more importantly makes sense for all the settings. While,
in general, each setting (or multiple settings) of an action script
builds a portion of the code, there is no way every combination of
possible codes could ever be fully tested. Some parts can be very
straightforward to generate (and to test - this is why it generally
works) and other parts depend on multiple settings which can be a
nightmare to generate and can create extremely complex code.
In most cases, there is a very specific code generation sequence where
you take setting "A", generate it's code, take setting "B" append it's
generated code and so forth. But then there are cases where "B" implies
what "A" generates, and where it needs to be located in the code, and
so forth. It can get so intertwined, that it's impossible to totally
predict what it will or could generate and these are where the typical
failures tend to be.
AEX
Library Numbers (
Email
Comments)
The CSDA Code Utility has an AEX library compiling utility called
LibMake that simplifies the process of making a desired library.
But whether you make an AEX library with LibMake or Alpha
Five's
built in tools, you still need to select a Library number.
Depending on how the libraries are loaded, the library
numbers
may or may not be important.
Alpha Five currently uses the
following library number assignments:
| Library # |
Purpose |
| 0 |
Alpha5.AEX, Startup.AEX |
| 1 |
System.AEX |
| 2 |
modern.AEX |
| 3 |
classic.AEX |
| 100 |
Temporary for addin_manager explorer, and
default compiled number |
| 125 |
used in loading aex files |
| 126 |
used for aex with database name |
| 127 |
used for
Addin_Library.Aex (which combines all installed library AEX's and
ignores their library number) |
| 200 |
used by web
server to load AEX libraries to a web page |
| 60-63 |
for AIMS
products |
| 64 |
for CSDA Code
Utility and for AlphaSports.AEX |
Coding
tips (
Email
Comments)
Updated
07 Mar 2010
Isolate
Code that depends on tricks (
Email
Comments)
Updated
03 Jan 2010
Any code that uses special tricks or depends on an odd or undocumented
way that Alpha operates, or is Alpha Five version-specific, should
first tried to be avoided.
These
could lead to future support issues with the code.
However, if you still think you need to use this, place the
code
inside a function. It should contain a lot of comments in the
code documenting the trick or usage so that anyone (including
you
in the future) understands what is happening.
By placing it inside a function, you can update it easily as needed in
the future to account for slight variations between Alpha Five versions
or patches.
An example is the code
""+numbervalue
This code converts the numbervalue to a string, keeping all decimals,
but without trailing decimal zeros
and with no leading spaces padding. There are other ways to do it, but
they can
be more complicated. Since it is not well documented, it might change
(although unlikely) in a
future version.
Placing code like this in a function will allow
replacement with a working version should it prove necessary.
Here is a version specific example that returns the current EXE name,
should it be renamed. It is the typical way
many might write
version specific code.
FUNCTION get_exe_name AS C
( )
IF version()>=8
get_exe_name=a5.get_exe_name()
ELSE
get_exe_name="Alpha5.exe"
END IF
END FUNCTION
The above works fine, however, there are times when specific builds
(patches) of Alpha work or don't work. You could process each
case as needed, but this can get to be very hard to check each
build. In this case, and in any cases where you can test the operation
of the "trick", you can dynamically test it and use some alternative
value or method when it doesn't work. For the above, rewrite
it
this way, and now it is not version-specific, but whether the method
works or not-specific.
FUNCTION get_exe_name AS C
( )
IF eval_valid("a5.get_exe_name()")
get_exe_name=a5.get_exe_name()
ELSE
get_exe_name="Alpha5.exe"
END IF
END FUNCTION
If the test requires a lot of time to process, continue storing the
final result or whether the trick works so that you need to only do the
test once
Using Active-X Objects tips (
Email
Comments)
Updated
27 Feb 2010
Please note the following:
1. Xbasis code using activeX controls can only reference the Active X
properties (variables) with one pointer level (one dot). This is
due to an issue with the Alpha Five XBasic Interpreter and Visual Basic
Interface. To solve it, set the parent pointers of a variable to
a temporary variable pointer, and then use that pointer to reference.
E.g.
objectpointer.subpointer.propertyvalue=-1
should be replaced with code similar to this
dim temp_pointer as p
temp_pointer=objectpointer.subpointer
temp_pointer.propertyvalue=-1
2. Active X Arrays referenced from Alpha Five start at base of 1 for
the 1st array element vs the Active X control bases it at a base of 0.
Hence add 1 to the array element when using in Alpha Five Xbasic.
Also, reference to arrays in XBasic are always addressed with [ ]
square brackets. E.g. the
lines in visual basic
objectpointer.subpointer(5).propertyvalue=-1
objectpointer.arrayvalue(9)="test string"
should be replaced with Xbasic code similar to this (taking in to account both issues)
temp_pointer=objectpointer.subpointer[6]
temp_pointer.propertyvalue=-1
objectpointer.arrayvalue[10]="test string"
Bitwise Logical Operations (Binary) with numbers (
Email
Comments)
Updated
08 April 2010
When interfacing to other programs using Active X controls or similar,
they often use numeric values to act like having many logical flags,
one per binary bit. Each binary bit (base 2 arithmetic) can have
a value of 1 or 0.
However Alpha Five only operates with decimal numbers in it's
expressions. So any time you have values in other formats,
typically either binary (base 2), octal (base 8) or hexadecimal
(sometimes called just hex - base 16)
So if you had a hexadecimal number 23, typically shown in other
languages and programs as &H23 or 0x23, this is equivalent to the
following
| Value |
Base |
Valid digits for base |
| 23 |
hexadecimal |
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F |
| 00100011 |
binary |
0,1 |
| 043 |
octal |
0,1,2,3,4,5,6,7 |
| 35 |
decimal |
0,1,2,3,4,5,6,7,8,9 |
To convert a hex number from some other language to decimal in Alpha,
Alpha needs for you to give the number portion as a string, so for the
above, you might use
numeric_var=hex_to_dec("23")
' Translate Hex string &H23 to decimal number
Alpha Five does not have any binary or octal functions to convert those formats.
Once a numeric value, Alpha Five logical operators,
.AND.
.OR.
.XOR.
will all work on each bit of the binary equivalent as if you performed
the logical operation on each bit position of each side. So
if doing a bitwise .OR. , an example might be
| Expression |
Result |
Base |
Valid digits for base |
| 23 .OR. A1 |
A3 |
hexadecimal |
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F |
| 00100011 .OR. 10100001 |
10100011 |
binary |
0,1 |
| 043 .OR. 121 |
243 |
octal |
0,1,2,3,4,5,6,7 |
| 35 .OR. 161 |
163 |
decimal |
0,1,2,3,4,5,6,7,8,9 |
Since Alpha Five's expression parser
understands only decimal numbers, you must convert them all to decimal numbers if not already in that format, e.g.
hex_to_dec("23") .OR. hex_to_dec("A1")
or
35 .OR. 161
Inverting the Bits (Bitwise NOT)
As it turns out, to invert the bits (change 1's to 0's and 0's to 1's),
one might expect he .NOT. operator to work on the bits, but it does
work. The easiest way to invert the bits is to use .XOR.
with the value on 1 side and a decimal equivalent of all binary
1's (for the needed width) for the other side. So to invert
hexadecimal 23, for a width of 16 bits, we would use an expression like
hex_to_dec("23") .XOR. ((2^16)-1)
which simplifies to
35 .XOR. 65535
and yields
| Expression |
Result |
Base |
Valid digits for base |
| 23 .XOR. FFFF |
FFDC
|
hexadecimal |
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F |
| 00100011 .XOR. 1111111111111111 |
1111111111011100 |
binary |
0,1 |
| 043 .XOR. 121 |
377,334
|
octal |
0,1,2,3,4,5,6,7 |
| 35 .XOR. 65535 |
65500 |
decimal |
0,1,2,3,4,5,6,7,8,9 |
Accessing specific Bits
To operate on a particular binary bit in "n" (where n is
the bit position starting at 0 on the right most bit) in a long word of
binary width "w" (typically values, 8, 16, 32, 64 which correspond to 1, 2, 4 or 8 bytes of information), the following
expressions should be helpful
numeric_var=numeric_var .OR. (2^n) ' Set a bit at position n in a binary
representation
numeric_var=numeric_var .XOR. (2^n) ' Invert bit at position n in a
binary representation
numeric_var=numeric_var .AND. ((2^w-1) .xor. 2^n) ' Clear bit at
position n in a binary representation
numeric_var=numeric_var*2 'Shift bits left 1 position, and zero-fill
lowest bit
Test Bit n for a 1
IF((numeric_var .AND. (2^n))>0,.T.,.F.) ' True if bit n is set to a 1, else false
These are not the most efficient ways to do these,
as in most cases the numbers n and w are known at the time of writing
the expression, e.g. 2^2 is just the constant 4. And likewise
((2^8-1)
.xor. 2^2)
is equal to
255 .xor. 4
which is equal to
251
In addition, you can do multiple bits simultaneously by combining individual bit expressions together
Blob bitwise operations
Using Blob functions, there are blob operations OR, AND, XOR and
NOT that will work on each byte of the blob in a bitwise manner as
well.