|
By Reg Charney
Last month, I published an article critiquing an MSDN sample. I
also proposed a solution. After presenting a more expanded version
of that article at our monthly ACCU meeting, Al Bauer, sent me this
solution which I believe has a lot of value, so I have presented
here. [I have made some minor changes to fit this page format—Ed.]
“Here is my [Al’s] version of the sample program of
the ACCU meeting of Oct. 10. I had some trouble finding enough
include files to get all types defined; hence my own ULONG
and using char instead
of TCHAR.
Generally, I have substituted declarations and data for
algorithms.
Feedback? Specifically, I was interested in your (Reg) comment
that a nested if (like mine)
compiles to less efficient object code than using goto's.
|
#include <stdlib.h>
#include <stdio.h>
#include "toolhelp.h"
#include <commdlg.h>
typedef unsigned long ULONG;
BOOL OpenTheFile (void)
{
typedef enum {ekOK, ekFDia, ekOpen, ekFSiz,
ekFZro, ekGAlo,
ekRdFi, ekRdZr, ekProc} terrKind;
typedef char* terrMsgs[ekProc+1];
terrKind errKind = ekOK;
static terrMsgs errMsgs = { "No
Error", "File Not Fnd",
"Cant Open","Invalid FSize",
"File Empty", "Alloc Error",
"File Read Err", "No Bytes Read",
"Process Err" };
HANDLE hFile = (HANDLE)-1;
ULONG ulCount, ulFilSiz;
OPENFILENAME
Ofn;
char szFile[MAX_PATH+1] = "\0";
char *lpBuf = NULL;
BOOL bRet =
FALSE;
if
(!GetOpenFileName(&Ofn))
errKind=ekFDia;
else if ((hFile=CreateFile(…))==(HANDLE)-1)
errKind=ekOpen;
else if ((ulFilSiz=GetFileSize(...))==0xFF)
errKind=ekFSiz;
else if (ulFilSiz
== 0)
errKind=ekFZro;
else if ((lpBuf=GlobalAlloc(...))==NULL)
errKind=ekGAlo;
else if (!ReadFile(hFile, ..., &ulCount))
errKind=ekRdFi;
else if (ulCount==0)
errKind=ekRdZr;
else if (!ProcessTheFile(lpBuf))
errKind=ekProc;
if
(lpBuf) GlobalFree(lpBuf);
if (hFile!=(HANDLE)-1) CloseHandle(hFile);
if (errKind!=ekOK)
MessageBox(NULL, errMsgs[errKind]);
else bRet = TRUE;
return
bRet;
}
Listing #1 -
Alternate Solution |
I replied to AL with these comments:
“Hi Al,
This is a very nice solution. It is one “level” above mine in
that after analyzing the problem in total, it made imminent sense to
rewrite it this way. I only see two potential problem areas. One,
keeping the enums and error messages in sync; and two, what happens
when more convoluted logic is needed at each stage.
Consider this example case. Assume that we want to process files
of zero size and do further extensive processing. That means that we
need to bypass the allocation, ReadFile and subsequent (possibly
redundant, as Brian Grimshaw mentioned) test, but execute the
routine ProcessTheFile and the further extensive processing. My type
of solution would have had an extra goto that branches to the if
containing the invocation of ProcessTheFile. Your code would become
something like that shown in Listing #2. Note the need to duplicate
the “further processing” code.
Alternatively, you could create a new function containing all the
code in “extensive further processing” and invoke it in the two
place where it is needed. While this solution could lead to a
cleaner result, often creating a new function out of fragments of
existing code is difficult because the old code assumes the
availability of local data. This local data needs to be passed to
any new function, often by reference or pointer.
Still, your solution still stands up very well. Nicely done and
thanks!”
if
(!GetOpenFileName(&Ofn)) errKind=ekFDia;
else if ((hFile=CreateFile(…))==(HANDLE)-1) errKind=ekOpen;
else if ((ulFileSize=GetFileSize(…))==0xFF) errKind=ekFSiz;
else if (ulFileSize == 0)
{ // braces needed
if (!ProcessTheFile(lpBuf))
errKind=ekProc;
else
{
//
further extensive processing - duplicate of what
//
appears below
}
}
else if ((lpBuf=GlobalAlloc(...))==NULL)
errKind=ekGAlo;
else if (!ReadFile(hFile,…,&ulBytesRead))
errKind=ekRdFi;
else if (ulBytesRead
==0)
errKind=ekRdZr;
else if (!ProcessTheFile(lpBuf))
errKind=ekProc;
else
{
// further
extensive processing
}
Listing #2 -
Response to Alternate Solution |
And finally, Al’s reply:
“Thanks for taking the time for dialog;
The sync issue: Yes; this is error prone. You may guess I'm an
old Pascal devotee'. The weaker C syntax checking lets you declare:
typedef char *tmsgs[4];
static tmsgs msgs={ "zero", "one" };
so msgs[2] and msgs[3]
remain uninitialized. The mind reels.
C/C++ must make up in discipline what the Pascal compiler helps
you do. Nevertheless, a hack programmer can find ways around even
Pascal. Sigh.
More convoluted logic: Yes. The tough decision; do you write very
loosely structured code that can be easily modified, to even much
different design. the best structure to represent the current
design, but risk painting yourself into a corner? - the moderate
approach; probably best.
Whether C, Pascal, or whatever, I was much impressed by Niklaus
Wirth’s “Data Structures + Algorithms = Programs” I like to
(as in this case) tailor the data (i.e. the error msgs)
to simplify the logic. Generally I find the code is smaller and
faster.
Of course, the next step to many is OOP, and it truly does
advance the cause started by “structured programming”. Oh, well,
if I go much further, I'll be getting dangerously close to religion.
I liked your “contract” approach. I came to about the same
thing.
Write function header comments.
Carefully name the function;
- a void function is a verb describing what it does. (includes
boolean returns denoting success/failure)
- a function returning a value is a noun describing the value
returned. Mostly here I don't return values in pointer parms of
value returning functions. I declare these pointer parms CONST.”
3. Design the return value, parms for all data flows. i.e.: Write
the function header (prototype) Avoid globals whenever possible.
4. Now (LAST) code the function body. If you MUST
"break" the contract, update the header comments
IMMEDIATELY.
I rewrote this example to correct for these errors. Can you see
any other errors in this modified example.
If I were writing this code from scratch, using OO techniques
would simplify the code and reduce the chances of errors.
By Reg. Charney
We are very lucky here in the Valley. We probably have more
money, opportunities and leading edge product development here than
anywhere else in the world. Amidst these riches and abundance, we
tend to forget how they came about and the debts we owe to those
that came before. We also tend to assume that today’s successes
were the direct result of our brilliance and hard work. A lot of
that is true—but not all of it.
Much of what we are doing here is the result of R&D and
social programs started 20 or 30 years ago. The Internet,
lasers/masers, and fiber optics, relational databases, Equal
Employment Opportunity are some, just to name a few. More important,
these projects were all originally government funded. Government
funding tends to be long term and in the areas of pure research.
Company funded R&D tends to be short term (driven by the quarter
to quarter results) and more application specific. Without pure
research, applied research is not possible and without eventual
applications, the monies for long term research is not available.
There needs to be a balance. As an editor and observer of the
Valley, my concern is that with all the sound and fury during this
election year, we are forgetting our foundations. Doing so puts our
own and our children's future at risk. Please keep this in mind when
you go out to vote this November 7.
I just returned from Toronto where the C++ Standards Committee
was holding their semi-annual meeting.
We are continuing to clarifying the more obscure corners of the
language. Typical was making sure that the terms template function
(a function) and function template (a template) are used
consistently throughout the standard. We are also considering
opening up the standards process to extensions, particularly in the
library area.
By Reg. Charney
We have all had occasion to use global variables. For example, in
Windows programming, they are used extensively. However, they suffer
from a number of major problems.
They pollute the namespace—it is easy to accidentally have
two variables with the same name in different translation units
communicate with each other at run-time because the linker thought
they were the same variable.
Within the same module, local variables can accidentally hide
global variables, or global variables can be used when really a
local variable by the same name was omitted by accident.
Lastly, a change in one part of the program can effect another
part of the program unexpectedly when the programmer does not
realize that the variable is shared by other routines.
First, we need to recognize that there is always at least one
global: main() or one of its
variants in every program. There are also other globals if you use
namespaces—their namespace names. The main idea is avoid possible
name clashes. I am going to present three ways of dealing with
globals.
The old way—define all global variables individually at file
scope.
Enclose globals inside a namespace, like the standard library
does with namespace std.
Enclose all variables to be used as globals inside a class
definition that is instantiated at global scope.
Listing #3 shows these three methods.
Method 1 has no saving graces. It just happens to be the most
common method.
Method 2 reduces the namespace pollution problem to adding just
one name. It also has the added advantage that it can be extended
without recompiling preexisting modules.
Method 3 has scope, compile-time access control and potentially
runtime control. It can also be used with the other two methods. Its
main disadvantage is that you must remember to instantiate the class
at the appropriate place.
Please note that a namespace is not a scope or storage type. It
is simply a name qualification scheme.
|
struct
X { int i; };
// Method
1 - global scope
int v1;
X x1;
void f1(void)
{
v1 = 1; // ::v1
x1.i = 1; // ::x1.i
}
// Method
2 - namespace
namespace MyGlobalNS
{
int v2;
X x2;
}
namespace mgn = MyGlobalNS;
void f2a(void) // qualified
{
mgn::v2 = 1;
mgn::x2.i = 2;
}
using namespace mgn;
void f2b(void)
{
v2 = 1; // mgn::v2
x2.i = 2; // mgn::x2.I
}
// Method
3 - global class
struct MyGlobalClass
{
int v3;
X x3;
};
MyGlobalClass mgc;
void main(void)
{
mgc.v3 = 1;
mgc.x3.i = 3;
}
Listing #3 - Global Methods
|
By Reg. Charney
Last month, you may have noticed that there was a slight up-tick
in the number of Software Engineering jobs in demand, particularly
here in the Valley. This month proves that that was no fluke. There
is still a continued demand for both IT skills in general and
software engineering in particular. However, it does look like it is
leveling off. The demand now for software engineers appears to be
increasing at the same rate as for all IT positions.

(Note: Chart #1 is normalized for the number of jobs in January
of this year. That is, the number of openings in January 2000 has
been taken as 1.00.)
The demand for all the major computer languages have shown an
interesting set of trends since the start of this year. Chart #2
shows the demand for today’s most popular computer languages. The
demand peaked in the April-May time frame when demand overall for
language skills was at its highest. Since then, demand for language
skills has decline until last month when things turned around.
Demand for the Internet related languages, XML and Perl have
increased while the demand for Java and C/C++ has leveled off. My
conclusion is that the desire to connect systems together using
shared data definitions and universal scripting languages like Perl
is driving this demand. Visual Basic is the anomaly. Demand for it
increased, probably because of new prototyping being done.

|