Programming With Lcc-Win32 Tutorial
Programming With Lcc-Win32 Tutorial
lcc-win32
Thanks to the many people that sent me those bug reports that allowed
me to improve the software. To all that sent me messages of
encouragement.
Program organization 2
Hello 3
Program input 4
What are “function parameters” ? 5
Console mode programs and windows programs 6
An overview of the compilation process 6
Technical notes 7
The run time environment 8
An overview of the standard libraries 10
The “stdheaders.h” include file 10
Windows specific headers 11
Passing arguments to a program 11
Iteration constructs 14
Basic types 15
Declarations and definitions 17
Variable declaration 17
Types 19
Function declaration 19
Function definitions 21
Variable definition 21
Statement syntax 21
Errors and warnings 21
Input and output 23
Predefined devices 24
The typical sequence of operations 24
Examples 25
Other input/output functions 30
File buffering 31
Commenting the source code 32
Describing a function 33
Describing a file 34
An overview of the whole language 35
Statements 36
Declarations 39
Pre-processor 41
Windows specific defined symbols 42
Structured exception handling 42
Control-flow 43
Windows specific syntax 43
Extensions of lcc-win32 44
A closer view 45
Identifiers. 45
Constants. 45
Arrays. 47
Function call syntax 47
Functions with variable number of arguments. 47
Assignment. 48
Postfix 48
Conditional operator. 49
struct. 49
union. 49
typedef. 49
register. 49
sizeof. 50
enum. 50
Prototypes. 50
variable length array. 50
const. 51
unsigned. 51
bit fields 51
stdcall. 52
break and continue statements 52
Null statements 53
Comments 53
Switch statement. 53
inline 54
Logical operators 54
Bitwise operators 55
Address-of operator 56
Sequential expressions 56
Casts 57
Selection 57
Indirection 58
Precedence of the different operators. 60
The printf family 62
Conversions 62
The conversion flags 63
The size specification 64
The conversions 65
Scanning values 66
Pointers 68
Operations with pointers 69
Null pointers 71
setjmp and longjmp 71
General usage 71
Register variables and longjmp() 73
Simple programs 75
strchr 75
strlen 75
ispowerOfTwo 76
Write ispowerOfTwo without any loops 77
strlwr 78
paste 79
Using arrays and sorting 83
How to sort arrays 86
Other qsort applications 91
Summary of Arrays and sorting 93
Pointers and references 93
Structures and unions 96
Structures 96
Structure size 99
Defining new types 100
Unions 101
Using structures 103
Fine points of structure use 105
Identifier scope and linkage 105
Top-down analysis 107
Extending a program 110
Improving the design 115
Traditional string representation in C 116
The problems with C-“Strings“ 118
Buffer-overflows 121
A buffer overflow in the C standard document 123
Memory management and memory layout 126
Functions for memory allocation 128
Memory layout under windows 128
Memory management strategies 129
Static buffers 129
Stack based allocation 130
“Arena” based allocation 130
The malloc / free strategy 131
The malloc with no free strategy 132
Automatic freeing (garbage collection). 132
Mixed strategies 133
A debugging implementation of malloc 133
Counting words 135
The organization of the table 136
Memory organization 138
Displaying the results 139
Code review 141
Time and Date functions 141
Using structures (continued) 145
Lists 145
Hash tables 148
A closer look at the pre-processor 150
Preprocessor commands 151
Things to watch when using the preprocessor 154
Using function pointers 156
Advanced C programming with lcc-win32 161
Operator overloading 161
References 162
Generic functions 162
Default arguments 163
Structured exception handling 163
The signal function 170
Numerical programming 173
Floating point formats 174
What can we do with those numbers then? 176
Using the floating point environment 183
The status flags 183
Reinitializing the floating point environment 184
Numerical stability 184
Complex numbers 186
Using the libraries of lcc-win32 187
The regular expressions library. A “grep” clone. 187
Programming with security in mind 191
Always include a ‘default’ in every switch statement 191
Pay attention to strlen and strcpy 191
Do not assume correct input 193
Watch out for trojans 193
Pitfalls of the C language 194
Defining a variable in a header file 194
Confusing = and == 194
Forgetting to close a comment 194
Easily changed block scope. 194
Using the ++ or -- more than once in an expression. 195
Unexpected Operator Precedence 195
Extra Semi-colon in Macros 196
Watch those semicolons! 196
Assuming pointer size is equal to integer size 196
Careful with unsigned numbers 197
Changing constant strings 197
Indefinite order of evaluation 198
A local variable shadows a global one 198
Careful with integer wraparound 198
Problems with integer casting 199
Octal numbers 199
Fun with C 200
Calculating pi 200
Writing poetry 200
Tatoo 200
Bibliography 201
Introduction 205
WinMain 208
Resources 211
The dialog box procedure 215
A more advanced dialog box procedure 218
User interface considerations 220
Libraries 223
Dynamically linked libraries (DLLs) 229
Using a DLL 232
A more formal approach. 235
New syntax 235
Event oriented programming 235
A more advanced window 236
Customizing the wizard generated sample code 243
Making a new menu or modifying the given menu. 243
Adding a dialog box. 243
Drawing the window 244
Initializing or cleaning up 244
Getting mouse input. 244
Getting keyboard input 245
Handling moving/resizing 245
Window controls 246
A more complex example: a "clone" of spy.exe 251
Creating the child windows 251
Moving and resizing the child windows 252
Starting the scanning. 252
Building the window tree. 253
Scanning the window tree 253
Review 254
Filling the status bar 256
Auxiliary procedures 257
Numerical calculations in C. 260
Filling the blanks 265
Using the graphical code generator 274
Customizing controls 278
Processing the WM_CTLCOLORXXX message 278
Using the WM_DRAWITEM message 280
Building custom controls 283
The Registry 286
The structure of the registry 286
Enumerating registry subkeys 287
Rules for using the registry 289
Interesting keys 290
Etc. 291
Clipboard 292
Serial communications. 293
Files 294
File systems 295
Graphics 296
Handles and Objects 296
Inter-Process Communications 296
Mail 297
Multimedia 297
Network 297
Hooks 297
Shell Programming 298
Services 298
Terminal Services 298
Windows 299
Advanced windows techniques 300
Memory mapped files 300
Letting the user browse for a folder: using the shell 303
Retrieving a file from the internet 306
Opening a web site or an HTML file using the default browser 307
Creating a desktop shortcut 307
Error handling under windows 309
Check the return status of any API call. 311
Always check allocations 311
Common errors 312
Dialog will not display when you add a control 312
Some Coding Tips 313
Determining which version of Windows is running 313
Translating the value returned by GetLastError() into a readable string 313
Clearing the screen in text mode 313
Getting a pointer to the stack 314
Disabling the screen saver from a program 314
Drawing a gradient background 315
Capturing and printing the contents of an entire window 315
Centering a dialog box in the screen 318
Determining the number of visible items in a list box 318
Starting a non-modal dialog box 319
Propagating environment variables to the parent environment 319
Restarting the shell under program control 320
Translating client coordinates to screen coordinates 320
Passing an argument to a dialog box procedure 320
Calling printf from a windows application 320
Enabling or disabling a button or control in a dialog box. 320
Making a window class available for all applications in the system. 321
Accessing the disk drive directly without using a file system 321
Retrieving the Last-Write Time 322
Retrieving the file name from a FILE pointer 322
Setting the System Time 324
Getting the list of running processes 324
Changing a File Time to the Current Time 325
Converting from GMT (UTC) time to local time 326
Displaying the amount of disk space for each drive 326
Mounting and unmounting volumes in NTFS 5.0 327
FAQ 329
How do I create a progress report with a Cancel button? 329
How do I show in the screen a print preview? 331
How do I change the color of an edit field? 332
How do I draw a transparent bitmap? 332
How do I draw a gradient background? 334
How do I calculate print margins? 335
How do I calculate the bounding rectangle of a string of text? 336
How do I close an open menu? 337
How do I center a dialog box in the screen? 337
How do I create non-rectangular windows? 337
How do I implement a non-blinking caret? 338
How do I create a title window (splash screen)? 338
How do I append text to an edit control? 341
How do I determine the color settings? 342
How do I spawn a process with redirected stdin and stdout? 343
How to modify the width of the list of a combo box 344
How do I modify environment variables permanently? 345
How do I add a new directory to the PATH variable permanently? 346
How do I add a menu item to the explorer right click menu? 348
How do I translate between dialog units and pixels? 348
I initialize in WM_CREATE but the values of my variable change. Why? 349
How do I translate between client coordinates to screen coordinates? 349
When should I use critical sections and when is a mutex better? 350
Why is my call to CreateFile failing when I use conin$ or conout$? 350
How can I control the task bar under program control? 350
How to erase a file into the recycle bin? 351
Making a timed message box 355
How do I build a “Property-sheet” control? 357
How to tell Windows to run a program during startup ? 358
How do I disable the screen saver? 358
How do I play a sound? 359
How do I simulate a key stroke to send it to another window? 359
How can I encrypt and decrypt a character string? 359
Finding more examples and source code 362
Overview of lcc-win32’s documentation 362
Bibliography 363
Introduction 365
What is a network? 365
Protocols 366
The HTTP Protocol 366
GetHttpUrl 367
Implementation 368
The FTP protocol 371
Implementing the ftp “GET” 371
Querying the network parameters 372
Writing “ping” 376
How does it work? 376
Client/Server programming using sockets 378
Common steps for server and client 378
Server side 379
Client side 380
Sending and receiving data 380
Simplifying sockets programming with lcc-win32 380
1
Chapter
Introduction to C
1
This tutorial to the C language supposes you have the lcc-win32 compiler system installed.
You will need a compiler anyway, and lcc-win32 is free for you to use, so please (if you
haven’t done that yet) download it and install it before continuing. http://www.q-software-
solutions.com
What the C language concerns, this is not a full-fledged introduction to all of C. There are
other, better books that do that (see the bibliography at the end of this book). Even if I try to
explain things from ground up, there isn’t here a description of all the features of the language.
Note too, that this is not just documentation or a reference manual. Functions in the standard
library are explained, of course, but no exhaustive documentation of any of them is provided
in this tutorial.1
But before we start, just a quick answer to the question:
Why learn C?
C has been widely criticized, and many people are quick to show its problems and drawbacks.
But as languages come and go, C stands untouched. The code of lcc-win32 has software that
was written many years ago, by many people, among others by Dennis Ritchie, the creator of
the language itself2. The answer to this question is very simple: if you write software that is
going to stay for some time, do not learn “the language of the day”: learn C.
C doesn’t impose you any point of view. It is not object oriented, but you can do object ori-
ented programming in C if you wish.3 It is not a functional language but you can do functional
programming4 with it if you feel like. Most LISP interpreters and Scheme interpreters/compil-
ers are written in C. You can do list processing in C, surely not so easily like in lisp, but you
can do it. It has all essential features of a general purpose programming language like recur-
sion, procedures as first class data types, and many others that this tutorial will show you.
Many people feel that C lacks the simplicity of Java, or the sophistication of C++ with its tem-
plates and other goodies. True. C is a simple language, without any frills. But it is precisely
1. For an overview of the lcc-win32 documentation see "How to find more information"
2. Dennis Ritchie wrote the pre-processor of the lcc-win32 system.
3. Objective C generates C, as does Eiffel and several other object-oriented languages. C, precisely
because of this lack of a programming model is adapted to express all of them. Even C++ started as a pre-
processor for the C compiler.
4. See the “Illinois FP” language implementations in C, and many other functional programming
languages that are coded in C.
2 C programming with lcc-win32
this lack of features that makes C adapted as a first time introduction into a complex high-level
language that allows you fine control over what your program is doing without any hidden
features. The compiler will not do anything else than what you told it to do. The language
remains transparent, even if some features from Java like the garbage collection are incorpo-
rated into the implementation of C you are going to use.5
As languages come and go, C remains. It was at the heart of the UNIX operating system devel-
opment in the seventies6, it was at the heart of the microcomputer revolution in the eighties,
and as C++, Delphi, Java, and many others came and faded, C remained, true to its own
nature.
5. Lisp and scheme, two list oriented languages featured automatic garbage collection since several
decades. APL and other interpreters offered this feature too. Lcc-win32 offers you the garbage collector
developed by Hans Boehm.
6. And today, the linux kernel is written entirely in C as most operating systems.
7. There is no distinction between functions and procedures in C. A procedure is a function of return
type void.
8. Actually, the startup code calls main. When main returns, the startup code regains control and ends
the program. This is explained in more detail in the technical documentation.
9. Any program, in any computer in any language has two main types of memory at the start:
The code of the program, i.e. the sequence of machine instructions that the program will execute. This
section has an “entry point”, the above mentioned “main” procedure in C, or other procedure that is
used as the entry point
The static data of the program, i.e. the string literals, the numbers or tables that are known when the
program starts. This data area con be further divided into an initialized data section, or just empty,
reserved space that is initialized by the operating system to zero when the program is loaded.
10. There is no way to import selectively some identifiers from another included file. Either you import
all of it or none.
Hello 3
A function has a parameter list, a body, and possibly a return value.11 The body can contain
declarations for local variables, i.e. variables activated when execution reaches the function
body.
The body is a series of expressions separated by semicolons. Each statement can be an arith-
metic operation, an assignment, a function call, or a compound statement, i.e. a statement that
contains another set of statements.
1.2 Hello
To give you an idea of the flavor of C we use the famous example given already by the authors
of the language12. We build here a program that when invoked will put in the screen the mes-
sage “hello”.
#include <stdio.h> (1)
int main(void) (2)
{ (3)
printf("Hello\n"); (4)
return 0; (5)
} (6)
1) Using a feature of the compiler called ‘pre-processor’, you can textually include a whole
file of C source with the “#include” directive. In this example we include from the standard
includes of the compiler the “stdio.h” header file.13
2) We define a function called “main” that returns an integer as its result, and receives no
arguments (void). 14
4) We call the standard function “printf” that formats its arguments in a character string that is
displayed in the screen. A function call in C is written like this:function-name ‘(‘
argument-list ‘)’. In this case the function name is “printf”, and its argument
list is the character string “Hello\n”15. Character strings are enclosed in double quotes.
They are represented in C as an array of characters finished by a zero byte.
11. In C, only one return value is possible. A function, however can return several return values if it
modifies its environment.
12. This example is a classic, and appears already in the tutorial of the C language published by B. W.
Kernighan in 1974, four years before the book “The C programming language” was published. Their example
would still compile today, albeit with some warnings:
main() { printf(“Hello world\n”); }
13. The name of the include file is enclosed within a <> pair. This indicates the compiler that it should
look for this include file in the standard include directory, and not in the current directory. If you want to
include a header file in another directory or in the compilation directory, use the double quotes to enclose the
name of the file, like #include “myfile.h”
14. This is one of the two possible definitions of the “main” function. Later we will see the other one.
15. Character strings can contain sequences of characters that denote graphical characters like new line
(\n) tab (\t), backspace (\b), or others. In this example, the character string is finished by the new line
character \n.
4 C programming with lcc-win32
5) The return statement indicates that control should be returned (hence its name) to the
calling function. Optionally, it is possible to specify a return result, in this case the integer
zero.
The first thing to do is to go to the “File” menu, and select the New-> File item. This will indi-
cate to the IDE that you want to start a new program module. You get prompted with a small
window that asks for the file name. Just enter “hello.c”. You will see than that a blank sheet of
paper opens, where you can enter the text of the program. You should type the program as
shown, and pay attention to avoid any typing mistake. Remember: the machine doesn’t under-
stand anything. If you forget a quote, or any special sign it will not work and the compiler will
spit error messages that can be confusing. Check that you type exactly what you see above.
Once this is done, you can compile, and link-edit your program by just clicking in the compile
menu or pressing F9.17
To run the program, you use the “execute” option in the “Compiler” menu (or you type
Ctrl+F5), or you open a command shell and type the program’s name. Let’s do it the hard way
first.
16. You start wedit by double clicking in its icon, or, if you haven’t an icon for it by going to the “Start”
menu, run, and then type the whole path to the executable. For instance, if you installed lcc-win32 in c:\lcc,
wedit will be in c:\lcc\bin\wedit.exe
Hello 5
The first thing we need to know is the name of the program we want to start. This is easy; we
ask the IDE (Wedit) about it using the “Executable stats” option in the “Utils” menu. We get
the following display.
We see at the first line of the bottom panel, that the program executable is called:
h:\lcc\projects\hello.exe.18
We open a command shell window, and type the command:
C:\>h:\lcc\projects\lcc1\hello.exe
Hello
C:\>
Our program displays the character string “Hello” and then a new line, as we wanted. If we
erase the \n of the character string, press F9 again to recompile and link, the display will be:
C:\>h:\lcc\projects\lcc1\hello.exe
Hello
C:\>
But how did we know that we have to call “printf” to display a string?
Because the documentation of the library told us so… The first thing a beginner to C must do
is to get an overview of the libraries provided already with the system so that he/she doesn’t
waste time rewriting programs that can be already used without any extra effort. Printf is one
of those, but are several thousands of pre-built functions of all types and for all tastes. We
present an overview of them in the next section.
17. If this doesn’t work or you receive warnings, you have an installation problem (unless you made a
typing mistake). Or maybe I have a bug. When writing mail to me do not send messages like: “It doesn’t
work”. Those messages are a nuisance since I can’t possibly know what is wrong if you do not tell me exactly
what is happening. Wedit doesn’t start? Wedit crashes? The computer freezes? The sky has a black color?
Keep in mind that in order to help you I have to reproduce the problem in my setup. This is impossible
without a detailed report that allows me to see what goes wrong.
Wedit will make a default project for you, when you click the “compile” button. This can go wrong if
there is not enough space in the disk to compile, or the installation of lcc-win32 went wrong and Wedit can’t
find the compiler executable, or many other reasons. If you see an error message please do not panic, and try
to correct the error the message is pointing you to.
A common failure happens when you install an older version of Wedit in a directory that has spaces in it.
Even if there is an explicit warning that you should NOT install it there, most people are used to just press
return at those warnings without reading them. Then, lcc-win32 doesn’t work and they complain to me. I have
improved this in later versions, but still problems can arise.
18. For understanding the rest of the output see the technical notes below.
6 C programming with lcc-win32
fn(b)
return b;
}
The fn2 function will always return 7, because function fn1 works with a copy of b, not with b
itself. This is known as passing arguments by value. This rule will not be used for arrays, in
standard C.
When you see a statement like:
printf(“Hello\n”);
it means that the address of the first element is passed to “printf”, not a copy of the whole
character array. This is of course more efficient than making a copy, but there is no free lunch.
The cost is that the array can be modified by the function you are calling. More about this
later.
This program will read your source file, and produce another file called object file,19 that has
the same name as the source file but a..obj extension. C supports the separate compilation
model, i.e. you can compile several source modules producing several object files, and rely in
the link-editor lcclnk.exe to build the executable.
Lcclnk.exe is the link-editor, or linker for short. This program reads different object files,
library files and maybe other files, and produces either an executable file or a dynamically
loaded library, a DLL.
When compiling your hello.c file then, the compiler produced a “hello.obj” file, and from that,
the linker produced a hello.exe executable file. The linker uses several files that are stored in
the \lcc\lib directory to bind the executable to the system DLLs, used by all programs:
kernel32.dll, crtdll.dll, and many others.
The workings of the lcc compiler are described in more detail in the technical documentation.
Here we just tell you the main steps.
• The source file is first pre-processed. The #include directives are resolved, and the text
of the included files is inserted into the source file.20
• The front end of the compiler proper processes the resulting text. Its task is to generate a
series of intermediate code statements.21 The code generator that emits assembler
instructions from it processes these.22
• Eventually the compiler produces an object file with the .obj extension. This file is
passed then (possibly with other object files) to the linker lcclnk that builds the
executable.
Organizing all those steps and typing all those command lines can be boring. To easy this, the
IDE will do all of this with the F9 function key.
2 Size of initialized data. This is the number of bytes that the tables, constants and other
initial data use. In this case we see that besides the character string “hello” we have many
other constants added to the program by the C library.
3 Uninitialized data. This is the part of your program that reserves space for variables that
will be zeroed by the system at the start.
4 Size of image. This the place the whole will take in memory when loaded, including all
the above items. For alignment reasons this is greater than a simple sum of the above parts.
This size must be a multiple of 4096 bytes, a page.
What does this mean?
Program size has lately become a “non-issue”. Machines have grown so enormously, that most
people think that if the program makes 2MB or 20MB it doesn’t matter. This has some justifi-
cation, of course, but it is not the philosophy of lcc-win32 or C in general. Wedit has several
tools to get the size of each function, and it reports summaries for size information about your
program. Remember that smaller programs fit better in the cache of the CPU, and execute
faster since they require less resources.
Header Purpose
assert.h Diagnostics for debugging help.
complex.h Complex numbers definitions. See page 186.
ctype.h Character classification (isalpha, islower, isdigit)
errno.h Error codes set by the library functions
fenv.h Floating point environment. Functions concerning the precision of the
calculations, exception handling, and related items. See page 173.
float.h Characteristics of floating types (float, double, long double, qfloat). See
page 173.
inttypes.h Characteristics of integer types
iso646.h Alternative spellings for some keywords. If you prefer writing the opera-
tor “&&” as “and”, use this header.
limits.h Size of integer types.
locale.h Definitions for the formatting of currency values using local conven-
tions.
math.h Mathematical functions.
setjmp.h Non local jumps, i.e. jumps that can go past function boundaries. See
page 71.
signal.h Signal handling. See page 170.
stdarg.h Definitions concerning functions with variable number of arguments.
stdbool.h Boolean type and values
stddef.h Standard definitions for the types of a pointer difference, or others.
stdint.h Integer types
stdio.h Standard input and output.
stdlib.h Standard library functions.
stddef.h This file defines macros and types that are of general use in a program.
NULL, offsetof, ptrdiff_t, size_t, and several others.
string.h String handling. Here are defined all functions that deal with the standard
representation of strings as used in C. See “Traditional string representa-
tion in C” on page 116.
stdarg.h Functions with variable number of arguments are described here. See
page 47.
time.h Time related functions.See page 141.
wchar.h Extended multibyte/wide character utilities
wctype.h Wide character classification and mapping utilities
23. In the user’s manual there is an exhaustive list of the entire set of header files distributed with lcc-
win32. Please look there for an in-depth view.
Passing arguments to a program 11
#include <assert.h>
#include <complex.h>
...
etc
Instead of including the standard headers in several include statements, you just include the
“stdheaders.h” file and you are done with it. True, there is a very slight performance lost in
compilation time, but it is not really significant.
2) We use a longer definition of the “main” function as before. This one is as standard as the
previous one, but allows us to pass parameters to the program. There are two arguments:
24. Here we will only describe the standard way of passing arguments as specified by the ANSI C
standard, the one lcc-win32 uses. Under the Windows operating system, there is an alternative entry point,
called WinMain, and its arguments are different than those described here. See the Windows programming
section later in this tutorial.
12 C programming with lcc-win32
int argc This is an integer that in C is known as “int”. It contains the number of arguments
passed to the program plus one.
char *argv[] This is an array of pointers to characters25 containing the actual arguments
given. For example, if we call our program from the command line with the arguments
“foo” and “bar”, the argv[ ] array will contain:
argv[0] The name of the program that is running.
argv[1] The first argument, i.e. “foo”.
argv[2] The second argument, i.e. “bar”.
We use a memory location for an integer variable that will hold the current argument to be
printed. This is a local variable, i.e. a variable that can only be used within the enclosing
scope, in this case, the scope of the function “main”.26
3) We use the “for” construct, i.e. an iteration. The “for” statement has the following structure:
• Initialization. Things to be done before the loop starts. In this example, we set the
counter to zero. We do this using the assign statement of C: the “=” sign. The general
form of this statement is
• variable “=” value
• Test. Things to be tested at each iteration, to determine when the loop will end. In this
case we test if the count is still smaller than the number of arguments passed to the
program, the integer argc.
• Increment. Things to be updated at each iteration. In this case we add 1 to the counter
with the post-increment instruction: counter++. This is just a shorthand for writing
counter = counter + 1.
• Note that we start at zero, and we stop when the counter is equal to the upper value of
the loop. Remember that in C, array indexes for an array of size n elements always start
at zero and run until n-1.27
4) We use again printf to print something in the screen. This time, we pass to printf the
following arguments:
25. This means that you receive the machine address of the start of an integer array where are stored the
start addresses of character strings containing the actual arguments. In the first position, for example, we will
find an integer that contains the start position in RAM of a sequence of characters containing the name of the
program. We will see this in more detail when we handle pointers later on.
26. Local variables are declared (as any other variables) with:
<type> identifier;
For instance
int a;
double b;
char c;
Arrays are declared in the same fashion, but followed by their size in square brackets:
int a[23];
double b[45];
char c[890];
Passing arguments to a program 13
"Argument %d = ‘%s’\n"
count
argv[count]
Printf will scan its first argument. It distinguishes directives (introduced with a per-cent
sign %), from normal text that is outputted without any modification. We have in the
character string passed two directives a %d and a %s.
The first one, a %d means that printf will introduce at this position, the character
representation of a number that should also be passed as an argument. Since the next
argument after the string is the integer “count”, its value will be displayed at this point.
The second one, a %s means that a character string should be introduced at this point.
Since the next argument is argv[count], the character string at the position “count” in the
argv[ ] array will be passed to printf that will display it at this point.
5) We finish the scope of the for statement with a closing brace. This means, the iteration
definition ends here.
Now we are ready to run this program. Suppose that we have entered the text of the program in
the file “args.c”. We do the following:
h:\lcc\projects\args> lcc args.c
h:\lcc\projects\args> lcclnk args.obj
We first compile the text file to an object file using the lcc compiler. Then, we link the result-
ing object file to obtain an executable using the linker lcclnk. Now, we can invoke the program
just by typing its name:28
h:\lcc\projects\args> args
Argument 0 = args
We have given no arguments, so only argv[0] is displayed, the name of the program, in this
case “args”. Note that if we write:
h:\lcc\projects\args> args.exe
Argument 0 = args.exe
We can even write:
h:\lcc\projects\args> h:\lcc\projects\args.exe
Argument 0 = h:\lcc\projects\args.exe
But that wasn’t the objective of the program. More interesting is to write:
h:\lcc\projects\args> args foo bar zzz
Argument 0 = args
Argument 1 = foo
Argument 2 = bar
Argument 3 = zzz
The program receives 3 arguments, so argc will have a value of 4. Since our variable count
will run from 0 to argc-1, we will display 4 arguments: the zeroth, the first, the second, etc.
27. An error that happens very often to beginners is to start the loop at 1 and run it until its value is
smaller or equal to the upper value. If you do NOT use the loop variable for indexing an array this will work,
of course, since the number of iterations is the same, but any access to arrays using the loop index (a common
case) will make the program access invalid memory at the end of the loop.
28. The detailed description of what happens when we start a program, what happens when we compile,
how the compiler works, etc., are in the technical documentation of lcc-win32. With newer versions you can
use the compilation driver ‘lc.exe’ that will call the linker automatically.
14 C programming with lcc-win32
1.4.1.1 for
The “for” construct has
1: An initialization part, i.e. code that will be always executed before the loop begins,
2: A test part, i.e. code that will be executed at the start of each iteration to determine if the
loop has reached the end or not, and
3: An increment part, i.e. code that will be executed at the end of each iteration. Normally, the
loop counters are incremented (or decremented) here.
The general form is then:
for(init ; test ; increment) {
statement block
}
Within a for statement, you can declare variables local to the “for” loop. The scope of these
variables is finished when the for statement ends.
#include <stdio.h>
int main(void)
{
for (int i = 0; i< 2;i++) {
printf("outer i is %d\n",i);
for (int i = 0;i<2;i++) {
printf("i=%d\n",i);
}
}
return 0;
}
The output of this program is:
outer i is 0
i=0
i=1
outer i is 1
i=0
i=1
Note that the scope of the identifiers declared within a ‘for’ scope ends just when the for state-
ment ends, and that the ‘for’ statement scope is a new scope. Modify the above example as
follows to demonstrate this:
#include <stdio.h>
int main(void)
{
for (int i = 0; i< 2;i++) {1
printf("outer i is %d\n",i);2
int i = 87;
1.4.1.2 while
The “while” construct is much more simple. It consists of a single test that determines if the
loop body should be executed or not. There is no initialization part, nor increment part.
The general form is:
while (test) {
statement block
}
Any “for” loop can be transformed into a “while” loop by just doing:
init
while (test) {
statement block
increment
}
1.4.1.3 do
The “do” construct is a kind of inverted while. The body of the loop will always be executed
at least once. At the end of each iteration the test is performed. The general form is:
do {
statement block
} while (test);
Using the “break” keyword can stop any loop. This keyword provokes an exit of the block of
the loop and execution continues right afterwards.
The “continue” keyword can be used within any loop construct to provoke a jump to the end
of the statement block. The loop continues normally, only the statements between the continue
keyword and the end of the loop are ignored.
_Complex type they should appear in most C implementations and they do appear in all win-
dows compilers.30
These are the basic types of ANSI-C. Lcc-win32 offers you other types of numbers. To use
them you should include the corresponding header file, they are not “built in” into the com-
piler. They are built using a property of this compiler that allows you to define your own kind
of numbers and their operations. This is called operator overloading and will be explained fur-
ther down.
29. In most compilers the char/short/int/long types are present but their sizes can change from machine to
machine. Some embedded systems compilers do not support floating point. Many compilers do not
implement the recent types _Bool, long long, or long double. Within the windows environment how-
ever, the char/short/int/long/float/double types are identical to this ones in all 32 bit windows compil-
ers I know of.
30. Microsoft Visual C implements "long double" as double, and calls the long long type "__int64".To
remain compatible with this compiler, lcc-win32 accepts __int64 as an equivalent of long long.
Declarations and definitions 17
31. Note that if the function so declared is never used, absolutely no storage will be used. A declaration
doesn’t use any space in the compiled program, unless what is declared is effectively used. If that is the case,
the compiler emits a record for the linker telling it that this object is defined elsewhere.
32. Note that when you do not provide for a declaration, and use this feature: definition is a declaration;
you can only use the defined object after it is defined. A declaration placed at the beginning of the program
module or in a header file frees you from this constraint. You can start using the identifier immediately, even
if its definition comes much later, or even in another module.
18 C programming with lcc-win32
double fn(double f) {
double d = sqrt(f);
// more statements
}
Note that initializing a value with a value unknown at compile time is only possible within a
function scope. Outside a function you can still write:
int a = 7;
or
int a = (1024*1024)/16;
but the values you assign must be compile time constants, i.e. values that the compiler can fig-
ure out when doing its job.
Pointers are declared using an asterisk:
int *a;
This means that a will contain the machine address of some unspecified integer. 33
You can save some typing by declaring several identifiers of the same type in the same decla-
ration like this:
int a,b=7,*c,h;
Note that c is a pointer to an integer, since it has an asterisk at its left side. This notation is
somehow confusing, and forgetting an asterisk is quite common. Use this multiple declara-
tions when all declared identifiers are of the same type and put pointers in separate lines.
The syntax of C declarations has been criticized for being quite obscure. This is true; there is
no point in negating an evident weakness. In his book “Deep C secrets”34 Peter van der Lin-
den writes a simple algorithm to read them. He proposes (chapter 3) the following:
The Precedence Rule for Understanding C Declarations.
Rule 1: Declarations are read by starting with the name and then reading in precedence order.
Rule 2: The precedence, from high to low, is:
2.A : Parentheses grouping together parts of a declaration
2.B: The postfix operators:
2.B.1: Parentheses ( ) indicating a function prototype, and
2.B.2: Square brackets [ ] indicating an array.
2.B.3: The prefix operator: the asterisk denoting "pointer to".
Rule 3: If a const and/or volatile keyword is next to a type specifier e.g. int, long, etc.) it
applies to the type specifier. Otherwise the const and/or volatile keyword applies to the pointer
asterisk on its immediate left.
Using those rules, we can even understand a thing like:
char * const *(*next)(int a, int b);
We start with the variable name, in this case “next”. This is the name of the thing being
declared. We see it is in a parenthesised expression with an asterisk, so we conclude that “next
is a pointer to…” well, something. We go outside the parentheses and we see an asterisk at the
33. Machine addresses are just integers, of course. For instance, if you have a machine with 128MB of
memory, you have 134 217 728 memory locations. They could be numbered from zero up, but Windows uses
a more sophisticated numbering schema called “Virtual memory”.
34. Deep C secrets. Peter van der Linden ISBN 0-13-177429-8
Declarations and definitions 19
left, and a function prototype at the right. Using rule 2.B.1 we continue with the prototype.
“next is a pointer to a function with two arguments”. We then process the asterisk: “next is a
pointer to a function with two arguments returning a pointer to…” Finally we add the char *
const, to get
“next” is a pointer to a function with two arguments returning a pointer to a constant pointer to
char.
Now let’s see this:
char (*j)[20];
Again, we start with “j is a pointer to”. At the right is an expression in brackets, so we apply
2.B.2 to get “j is a pointer to an array of 20”. Yes what? We continue at the left and see ”char”.
Done. “j” is a pointer to an array of 20 chars. Note that we use the declaration in the same form
without the identifier when making a cast:
j = (char (*)[20]) malloc(sizeof(*j));
We see in bold and enclosed in parentheses (a cast) the same as in the declaration but without
the identifier j.
1.5.2 Types
A first, tentative, definition for what a type is, could be “a type is a definition for an algorithm
for understanding a sequence of storage bits”. It gives the meaning of the data stored in mem-
ory. If we say that the object a is an int, it means that the bits stored at that location are to be
understood as a natural number that is built by consecutive additions of powers of two. If we
say that the type of a is a double, it means that the bits are to be understood as the IEEE 754
standard sequences of bits representing a double precision floating point value.
Types can be primitive types (i.e. built-in types) or composite types, i.e. types built from sev-
eral primitive types.
Functions have a type too. The type of a function is determined by the type of its return value,
and all its arguments. The type of a function is its interface with the outside world: its inputs
(arguments) and its outputs (return value).
Types in C can be incomplete, i.e. they can exist as types but nothing is known about them,
neither their size nor their bit-layout. They are useful for encapsulating data into entities that
are known only to certain parts of the program.
Each type can have an associated pointer type: for int we have int pointer, for double we have
double pointer, etc. We can have also pointers that point to an unspecified object. They are
written as void *, i.e. pointers to void.
The programmer can at any time change the type associated with a piece of data by making a
“cast” operation. For instance if you have:
float f = 67.8f;
you can do
double d = (double)f;
The “(double)” means that the type of data in f should be converted into an equvalent data
using the double representation. We will come back to types when we speak again about casts
later.
• The return type of the function, i.e. the kind of result value it produces, if any.
• Its name.
• The types of each argument, if any.
The general form is:
<type> <Name>(<type of arg 1>, ... <type of arg N> ) ;
double sqrt(double) ;
Note that an identifier can be added to the declaration but its presence is optional. We can
write:
double sqrt(double x);
if we want to, but the “x” is not required and will be ignored by the compiler.
Functions can have a variable number of arguments. The function “printf” is an example of a
function that takes several arguments. We declare those functions like this:
int printf(char *, ...);
35. The interface for using functions with a variable number of arguments is described in the standard
header file “stdarg.h”. See “Functions with variable number of arguments.” on page 47.
36. There is a strong commitment, from the part of the compiler writers, to maintain the code that was
written in the language, and to avoid destroying programs that are working. When the standards committee
proposed the prototypes, all C code wasn’t using them yet, so a transition period was set up. Compilers would
accept the old declarations without prototypes and just emit a warning. Some people say that this period
should be over by now (it is more than 10 years that we have prototypes already), but still, new compilers like
lcc-win32 are supporting old style declarations.
Errors and warnings 21
D:\lcc\examples>
We see here a chain of errors, provoked by the first. The compiler tries to arrange things by
skipping text, but this produces more errors since the whole “for” construct is not understood.
Error recovering is quite a difficult undertaking, and lcc-win32 isn’t very good at it. So the
best thing is to look at the first error, and in many cases, the rest of the error messages are just
consequences of it.37
Another type of errors can appear when we forget to include the corresponding header file. If
we erase the #include <stdio.h> line in the args program, the display looks like this:
D:\lcc\examples>lcc args.c
Warning args.c: 7 missing prototype for printf
0 errors, 1 warnings
This is a warning. The printf function will be assumed to return an integer, what, in this case,
is a good assumption. We can link the program and the program works. It is surely NOT a
good practice to do this, however, since all argument checking is not done for unknown func-
tions; an error in argument passing will pass undetected and will provoke a much harder type
of error: a run time error.
In general, it is better to get the error as soon as possible. The later it is discovered, the more
difficult it is to find it, and to track its consequences. Do as much as you can to put the C com-
piler in your side, by using always the corresponding header files, to allow it to check every
function call for correctness.
The compiler gives two types of errors, classified according to their severity: a warning, when
the error isn’t so serious that doesn’t allow the compiler to finish its task, and the hard errors,
where the compiler doesn’t generate an executable file and returns an error code to the calling
environment.
We should keep in mind however that warnings are errors too, and try to get rid from them.
The compiler uses a two level “warning level” variable. In the default state, many warnings
aren’t displayed to avoid cluttering the output. They will be displayed however, if you ask
explicitly to raise the warning level, with the option –A. This compiler option will make the
compiler emit all the warnings it would normally suppress. You call the compiler with lcc -
A <filename>, or set the corresponding button in the IDE, in the compiler configuration
tab.
Errors can appear in later stages of course. The linker can discover that you have used a proce-
dure without giving any definition for it in the program, and will stop with an error. Or it can
discover that you have given two different definitions, maybe contradictory to the same identi-
fier. This will provoke a link time error too.
But the most dreaded form of errors are the errors that happen at execution time, i.e. when the
program is running. Most of these errors are difficult to detect (they pass through the compila-
tion and link phases without any warnings…) and provoke the total failure of the software.
37. You will probably see another display in your computer if you are using a recent version of lcc-
win32. I improved error handling when I was writing this tutorial…
Input and output 23
The C language is not very “forgiving” what programmer errors concerns. Most of them will
provoke the immediate stop of the program with an exception, or return completely nonsense
results. In this case you need a special tool, a debugger, to find them. Lcc-win32 offers you
such a tool, and you can debug your program by just pressing F5 in the IDE.
Summary:
• Syntax errors (missing semi-colons, or similar) are the easiest of all errors to correct.
• The compiler emits two kinds of diagnostic messages: warnings and errors.
• You can rise the compiler error reporting with the –A option.
• The linker can report errors when an identifier is defined twice or when an identifier is
missing a definition.
• The most difficult errors to catch are run time errors, in the form of traps or incorrect
results.
Name Purpose
fopen Opens a file
fclose Closes a file
fprintf Formatted output to a file
fputc Puts a character in a file
putchar Puts a character to stdout
feof Returns true when the current position is at the end of the file
ferror Returns true when there was an error when reading from the device
fputs Puts a string in a file.
fread Reads from a file a specified amount of data into a buffer.
38. An “opaque” structure is a structure whose definition is hidden. Normally, we have just a pointer to it,
but nowhere the actual definition.
24 C programming with lcc-win32
39. The text mode window is often called “Dos window” even if it has nothing to do with the old MSDOS
operating system. It is a window with black background by default, where you can see only text, no
graphics. Most of the examples following use this window. To start it you just go to “Start”, then
“Run” and type the name of the command shell: “cmd.exe”
Input and output 25
FILE *f = fopen(argv[1],"r");
FILE *out = fopen(argv[2],"w");
bytesread = fread(buffer,1,sizeof(buffer),f);
byteswritten = fwrite(buffer,1,sizeof(bufffer),out);
fclose(f);
fclose(out);
return 0;
}
• In this hypothetical program we establish a connection for reading from a device named
in the first argument (argv[1]). We connect to another device named in the second
argument, we read from the first device, we write into the second device, then we close
both.
1.7.3 Examples
For a beginner, it is very important that the basic libraries for reading and writing to a stream,
and the mathematical functions are well known. To make more concrete the general descrip-
tions about input/output from the preceeding sections we present here a compilable, complete
example.
The example is a function that will read a file, counting the number of characters that appear in
the file.
A program is defined by its specifications. In this case, we have a general goal that can be
expressed quickly in one sentence: “Count the number of characters in a file”. Many times, the
specifications aren’t in a written form, and can be even completely ambiguous. What is impor-
tant is that before you embark in a software construction project, at least for you, the specifica-
tions are clear.
#include <stdio.h> (1)
int main(int argc,char *argv[]) (2)
{
int count=0;// chars read (3)
FILE *infile; (4)
int c; (5)
infile = fopen(argv[1],"rb");(6)
c = fgetc(infile); (7)
while (c != EOF) { (8)
count++; (9)
c = fgetc(infile); (10)
}
printf("%d\n",count); (11)
return 0;
}
1) We include the standard header “stdio.h” again. Here is the definition of a FILE structure.
3) We set at the start, the count of the characters read to zero. Note that we do this in the
declaration of the variable. C allows you to define an expression that will be used to
initialize a variable.40
40. There is another construct in this line, a comment. Commentaries are textual remarks left by the
programmer for the benefit of other human readers, and are ignored by the compiler. We will come back to
commentaries in a more formal manner later.
26 C programming with lcc-win32
4) We use the variable “infile” to hold a FILE pointer. Note the declaration for a pointer:
<type> * identifier; the type in this case, is a complex structure (composite type)
called FILE and defined in stdio.h. We do not use any fields of this structure, we just
assign to it, using the functions of the standard library, and so we are not concerned about
the specific layout of it. Note that a pointer is just the machine address of the start of that
structure, not the structure itself. We will discuss pointers extensively later.
6) We start the process of reading characters from a file first by opening it. This operation
establishes a link between the data area of your hard disk, and the FILE variable. We pass
to the function fopen an argument list, separated by commas, containing two things: the
name of the file we wish to open, and the mode that we want to open this file, in our
example in read mode. Note that the mode is passed as a character string, i.e. enclosed in
double quotes.
7) Once opened, we can use the fgetc function to get a character from a file. This function
receives as argument the file we want to read from, in this case the variable “infile”, and
returns an integer containing the character read.
8) We use the while statement to loop reading characters from a file. This statement has the
general form: while (condition) { … statements… }. The loop body will be executed for so
long as the condition holds. We test at each iteration of the loop if our character is not the
special constant EOF (End Of File), defined in stdio.h.
9) We increment the counter of the characters. If we arrive here, it means that the character
wasn’t the last one, so we increase the counter.
10) After counting the character we are done with it, and we read into the same variable a new
character again, using the fgetc function.
11) If we arrive here, it means that we have hit EOF, the end of the file. We print our count in
the screen and exit the program returning zero, i.e. all is OK. By convention, a program
returns zero when no errors happened, and an error code, when something happens that
needs to be reported to the calling environment.
Now we are ready to start our program. We compile it, link it, and we call it with:
h:\lcc\examples> countchars countchars.c
288
We have achieved the first step in the development of a program. We have a version of it that
in some circumstances can fulfil the specifications that we received.
But what happens if we just write
h:\lcc\examples> countchars
Input and output 27
We get the following box that many of you have already seen several times:41
Why?
Well, let’s look at the logic of our program. We assumed (without any test) that argv[1] will
contain the name of the file that we should count the characters of. But if the user doesn’t sup-
ply this parameter, our program will pass a nonsense argument to fopen, with the obvious
result that the program will fail miserably, making a trap, or exception that the system reports.
We return to the editor, and correct the faulty logic. Added code is in bold.
#include <stdio.h>
#include <stdlib.h> (1)
int main(int argc,char *argv[])
{
int count=0;// chars read
FILE *infile;
int c;
1) We need to include <stdlib.h> to get the prototype declaration of the exit() function that
ends the program immediately.
2) We use the conditional statement “if” to test for a given condition. The general form of it is:
if (condition) { … statements… } else { … statements… }.
3) We use the exit function to stop the program immediately. This function receives an integer
argument that will be the result of the program. In our case we return the error code 1. The
result of our program will be then, the integer 1.Note that we do not use the integer constant
1 directly, but rather use the predefined constants EXIT_SUCCESS (defined as 0) or
EXIT_FAILURE (defined as 1) in stdlib.h. In other operating systems or environments, the
41. This is the display under Windows NT. In other systems like Linux for instance, you will get a “Bus
error” message.
28 C programming with lcc-win32
if (argc < 2) {
printf("Usage: countchars <file name>\n");
exit(EXIT_FAILURE);
}
infile = fopen(argv[1],"r");
if (infile == NULL) {
printf("File %s doesn’t exist\n",argv[1]);
exit(EXIT_FAILURE);
}
c = fgetc(infile);
while (c != EOF) {
count++;
c = fgetc(infile);
}
printf("%d\n",count);
return 0;
}
We try again:
H:\lcc\examples> lcc countchars.c
H:\lcc\examples> lcclnk countchars.obj
H:\lcc\examples> countchars sfsfsfsfs
File sfsfsfsfs doesn't exist
H:\lcc\examples>
Well this error checking works. But let’s look again at the logic of this program.
Suppose we have an empty file. Will our program work?
Input and output 29
If we have an empty file, the first fgetc will return EOF. This means the whole while loop will
never be executed and control will pass to our printf statement. Since we took care of initializ-
ing our counter to zero at the start of the program, the program will report correctly the num-
ber of characters in an empty file: zero.
Still, it would be interesting to verify that we are getting the right count for a given file. Well
that’s easy. We count the characters with our program, and then we use the DIR directive of
windows to verify that we get the right count.
H:\lcc\examples>countchars countchars.c
466
H:\lcc\examples>dir countchars.c
if (argc < 2) {
printf("Usage: countchars <file name>\n");
exit(EXIT_FAILURE);
}
infile = fopen(argv[1],"rb");
if (infile == NULL) {
printf("File %s doesn't exist\n",argv[1]);
exit(EXIT_FAILURE);
}
c = fgetc(infile);
while (c != EOF) {
count++;
c = fgetc(infile);
}
fclose(infile);
printf("%d\n",count);
return 0;
}
The skeleton of the commentary above is generated automatically by the IDE. Just right-click
somewhere in your file, and choose “edit description”.
Summary:
• A program is defined by its specifications. In this example, counting the number of
characters in a file.
• A first working version of the specification is developed. Essential parts like error
checking are missing, but the program “works” for its essential function.
• Error checking is added, and test cases are built.
• The program is examined for correctness, and the possibility of memory leaks, unclosed
files, etc., is reviewed. Comments are added to make the purpose of the program clear,
and to allow other people know what it does without being forced to read the program
text.
2) Read the position of the file cursor. This is the size of the file.
Easy isn’t it?
unsigned int FileLength(char *FileName)
{
FILE *f = fopen(FileName,"rb"); // (1)
int result;
if (f == NULL)
return -3;
if (fseek(f,0,SEEK_END)) { // (2)
fclose(f);
return -2;
}
result = ftell(f); // (3)
fclose(f);
return result; // (4)
1) We open the file. Note that we open it in binary mode, because ftell and fseek will NOT
work with files opened in “text” mode, where the sequence \r \n is translated into only one
character.
2) The fseek function will position the file pointer. The first argument is the file descriptor
pointer, the second is the distance from either the beginning, the current position or the end
of the file. In our example we use the position from the end of the file, SEEK_END. If the
function fails for some reason we return -2.
3) We call the ftell function to retrieve the current position. Note that that function returns -1 if
there was an error. We do not test for this result since if that function failed, we return the
error code without any firther processing.
4) Since all this functions return a 32 bit integer, files bigger than 2GB can’t be measured
using this functions. lcc-win32 provides some 64 bit file primitives, and the Windows
operating system provides a full set of 64 bit file primitives.
Note that the buffer is declared global. Be careful about the scope of the buffer you pass to set-
buf. You can use a buffer with local scope, but you must be aware that before leaving the
scope where the buffer is declared the file must be closed. If not, the program will crash when
the fclose function tries to flush the contents of the buffer that is no longer available. The
fclose function will be called automatically at program exit, if any open files exist at that time.
The setvbuf function allows you to change the mode of the buffering (either line, full or
none) and pass a buffer of a different size than BUFSIZ, a constant defined in stdio.h.
What is the proper way of finding an end of file condition while reading from a file ?
Try to read the file, and if it fails, see if the reason it failed was "end of file". You find this
using the feof function.
There are two reasons why a read from a file can fail. The first one is that there is nothing
more to read, the end of the data set has been reached. This means that the current position is
at the end of the file. The second reason is that there is a hardware error that makes any read-
ing impossible: the disk drive has a bad spot and refuses to give any data back, or the device
underlying this file is a network connection and your internet service provider has problems
with the router connected to your machine, or whatever. There are endless reasons why hard-
ware can fail. You can find out the reason for this using the feof and ferror functions.
1) Purpose. This should explain what this function does, and how it does it.
2) Inputs: Here you should explain how the interface of this function is designed: the
arguments of the function and global variables used if any.
42. The IDE of lcc-win32 helps you by automatic the construction of those comments. Just press, “edit
description” in the right mouse button menu.
34 C programming with lcc-win32
3) Outputs. Here you should explain the return value of the function, and any globals that are
left modified.
4) Error handling. Here you should explain the error return, and the behavior of the function in
case of an error occurring within its body.
For the description provided in the screen shot above, the editor produces the following out-
put:
/*---------------------------------------------------------------
Procedure: multiple ID:1
Purpose: Compiles a multiple regular expression
Input: Reads input from standard input
Output: Generates a regexp structure
Errors: Several errors are displayed using the "complain"
function
-----------------------------------------------------------------*/
void multiple(void)
{
This comment will be inserted in the interface the next time you ask for the description of the
function.
/*-----------------------------------------------------------------
Module: d:\lcc\examples\regexp\try.c
Author: ADMINISTRATOR
Project:
State:
Creation Date:
An overview of the whole language 35
1.9.1 Statements
45.9L or
long double constant
4.59e2L
qfloat constant. This is an extension of lcc-win32. 45.9q
character
char enclosed in simple quotes ’a’ or ’8’
constant
string literals enclosed in double quotes "a string"
Access the position “index” of the given array.
Indexes start at zero (see “Within the string, the
Array [index ] Table[45]
following abbreviations are recognized:” on
page 46.)
Table[34][23]
This access the
Access the n dimensional array using the indexes i1,
Array[i1][i2]
i2, … in..See “Arrays.” on page 47. 35th line, 24th
position of
Table
Call the function “fn” and pass it the comma
fn ( args ) separated argument list «args». see “Function call printf(“%d“,5)
syntax” on page 47.
An overview of the whole language 37
1.9.2 Declarations43
43. Lcc-win32 recognizes but doesn’t yet implement the keyword restrict.
An overview of the whole language 41
1.9.3 Pre-processor
__except (integer expression) If the integer expression evaluates to 1 the associated code block
{ exception handler block } will be executed in case of an exception.
1.9.6 Control-flow
if (expression) { block} If the given expression evaluates to something different than zero execute the
else { statements of the following block. Else, execute the statements of the block
block following the else keyword. The else statement is optional. Note that a single
} statement can replace blocks.
while (expression) { If the given expression evaluates to something different than zero, execute the
... statements ... statements in the block, and return to evaluate the controlling expression again.
} Else continue after the block. See “while” on page 15.
do { ... statements ... Execute the statements in the block, and afterwards test if condition is true. If
} while (condition); that is the case, execute the statements again.See “do” on page 15.
Execute unconditionally the expressions in the init statement. Then evaluate the
for(init;test;incr) {
test expression, and if evaluates to true, execute the statements in the block
... statements ...
following the for. At the end of each iteration execute the incr statements and
}
evaluate the test code again. See “for” on page 14.
switch (expression) { Evaluate the given expression. Use the resulting value to test if it matches any of
case int-expr: the integer expressions defined in each of the ‘case’ constructs. If the
statements ... comparison succeeds, execute the statements in sequence beginning with that
break; case statement.
default: If the evaluation of expression produces a value that doesn’t match any of the
statements cases and a “default” case was specified, execute the default case statements in
} sequence. See “Switch statement.” on page 53.
goto label Transfer control unconditionally to the given label.
Within the scope of a for/do/while loop statement, continue with the next
continue iteration of the loop, skipping all statements until the end of the loop.See “break
and continue statements” on page 52.
break Stop the execution of the current do/for/while loop statement.
End the current function and return control to the calling one. The return value of
return expression the function (if any) can be specified in the expression following the return
keyword.
T operator +(T a,
operator opname (args) Redefine one of the operators like +, * or
T b) {
{ others so that instead of doing the normal
... statements
} operation, this function is called instead.
}
Identifier will be a reference to a single
int &ra = a;
object of the given type. References must
type & id = expr; pa will be a reference to an
be initialized immediately after their
integer.
declaration.
Default function arguments. If the argument
int fn(int a,int b=0) is not given in a call, the compiler will fill it
with the specified compile time constant
Generic functions. These functions have
int overloaded fn(int);
several types of arguments but the same
int overloaded fn(double);
name.
A closer view 45
1.11.1 Identifiers.
An identifier is a sequence of non-digit characters (including the underscore _, the lowercase
and uppercase Latin letters, and other characters) and digits. Lowercase and uppercase letters
are distinct. An identifier never starts with a digit. There is no specific limit on the maximum
length of an identifier but lcc-win32 will give up at 255 chars.
Identifiers are the vocabulary of your software. When you create them, give a mnemonic that
speaks about the data stored at that location.
Anonymous identifiers (or counters) are usually the one letters ’i’, or “c” for char, etc. I think
the habit of using i, j, k is quite ancient, maybe inherited from fortran and physics.
1.11.2 Constants.
The default format (without any suffix) is double. This is important since it can be the source
of bugs very difficult to find. For instance:
long double d = 1e800;
The dynamic range of a long double is big enough to accept this value, but since the program-
mer has forgotten the L the number will be read in double precision. Since a double precision
number can’t hold this value, the result is that the initialization will not work at all.
Character string constants that are too long to write in a single line can be entered in two ways:
char *a = "This is a long string that at the end has a backslash \
that allows it to go on in the next line";
Another way, introduced with C99 is:
char *a = "This is a long string written"
"in two lines";
A closer view 47
Note too that character string constants should not be modified by the program. Lcc-win32
stores all character string constants once, even if they appear several times in the program text.
For instance if you write:
char *a = "abc";
char *b = "abc";
Both a and b will point to the SAME string, and if either is modified the other will not retain
the original value.
1.11.3 Arrays.
Here are various examples for using arrays.
int a[45]; // Array of 45 elements
a[0] = 23; // Sets first element to 23;
a[a[0]] = 56; // Sets the 24th element to 56
a[23] += 56; // Adds 56 to the 24th element
Multidimensional arrays are indexed like this:
int tab[2][3];
...
tab[1][2] = 7;
A table of 2 rows and three columns is declared. Then we assign 7 to the second row, third col-
umn. (Remember: arrays indexes start with zero).
Note that when you index a two dimensional array with only one index you obtain a pointer to
the start of the indicated row.
int *p = tab[1];
Now p contains the address of the start of the second column.
va_start(ap,numberOfArgs);
while (n--) {
sum += va_arg(ap,int);
}
va_end(ap);
return sum;
}
We would call this function with
va_add(3,987,876,567);
or
va_add(2,456,789);
1.11.6 Assignment.
An assignment has the left hand side of the equal’s sign that must be a value that can be
assigned to, and the right hand side that can be any expression other than void.
int a = 789; // "a" is assigned 789
array[345] = array{123]+897; //An element of an array is assigned
Struct.field = sqrt(b+9.0); // A field of a structure is assigned
p->field = sqrt(b+9.0);
/* A field of a structure is assigned through a pointer. */
Within an assignment there is the concept of “L-value”, i.e. any assignable object. You can’t,
for instance, write:
5 = 8;
The constant 5 can’t be assigned to. It is not an “L-value”, the “L” comes from the left hand
side of the equals sign of course. In the same vein we speak of LHS and RHS as abbreviations
for left hand side and right hand side of the equals sign in an assignment.
1.11.7 Postfix
These expressions increment or decrement the expression at their left side returning the old
value. For instance:
array[234] = 678;
a = array[234]++;
In this code fragment, the variable a will get assigned 678 and the array element 234 will have
a value of 679 after the expression is executed. In the code fragment:
array[234] = 678;
A closer view 49
a = ++array[234];
The integer a and the array element at the 235th position will both have the value 679.
When applied to pointers, these operators increment or decrement the pointer to point to the
next or previous element. Note that if the size of the object those pointers point to is different
than one, the pointer will be incremented or decremented by a constant different than one too.
NOTE: Only one postfix expression is allowed for a given variable within a statement. For
instance the expression:
i++ = i++;
is illegal C and will never work the same from one compiler to the next, or even within the
same compiler system will change depending whether you turn optimizations on or off, for
instance. The same applies for the decrement operator:
i-- = i--;
is also illegal. Note that this holds even if the expression is much more complicated than this:
i++ = MyTable[i--].Field->table[i++];
is completely illegal C.
1.11.9 struct.
A structure or a union can’t contain another structure that hasn’t been fully specified, but they
can contain a pointer to such a structure since the size of any pointer is always fixed. To build
recursive structures like list you should specify a pointer to the structure, see “Lists” on
page 145.. For a detailed description of this keyword see “Structures” on page 96.
1.11.10 union.
You can store several values in a single memory location or a group of memory locations with
the proviso that they can’t be accessed at the same time of course. This allows you to reduce
the memory requirements of a structure, or to interpret a sequence of bits in a different fash-
ion. For a detailed discussion see “Unions” on page 101.
50 C programming with lcc-win32
1.11.11 typedef.
The typedef keyword defines a name that can be used as a synonym for a type or derived type.
In contrast to the struct, union, and enum declarations, typedef declarations doesn’t introduce
new types — it introduces new names for existing types.
1.11.12 register.
This keyword is a recommendation to the compiler to use a machine register for storing the
values of this type. The compiler is free to follow or not this directive. The type must be either
an integer type or a pointer. If you use this declaration, note that you aren’t allowed to use the
address-of operator since registers do not have addresses. Lcc-win32 tries to honor your rec-
ommendations, but it is better not to use this declaration and leave the register usage to the
compiler.
Registers are the highest part of your machine memory hierarchy. They are the fastest storage
available to the program by the circuit, and in a PC x86 architecture there are just a few of
them available at a time.
After registers there is the level 1 cache, level 2 cache, main memory, then the disk, in order of
decreasing access speed.
1.11.13 sizeof.
The result of sizeof is normally a constant integer known when the compiler is running. For
instance sizeof(int) will yield under lcc-win32 the constant 4. In the case of a variable
length array however, the compiler can't know its size on advance, and it will be forced to gen-
erate code that will evaluate the size of the array when the program is running.
1.11.14 enum.
An enumeration is a sequence of symbols that are assigned integer values by the compiler. The
symbols so defined are equivalent to integers, and can be used for instance in switch state-
ments. The compiler starts assigning values at zero, but you can change the values using the
equals sign. An enumeration like enum { a,b,c}; will provoke that a will be zero, b will
be 1, and c will be 2. You can change this with enum {a=10,b=25,c=76};
1.11.15 Prototypes.
A prototype is a description of the return value and the types of the arguments of a function.
The general form specifies the return value, then the name of the function. Then, enclosed by
parentheses, come a comma-separated list of arguments with their respective types. If the
function doesn’t have any arguments, you should write ‘void’, instead of the argument list. If
the function doesn’t return any value you should specify void as the return type. At each call,
the compiler will check that the type of the actual arguments to the function is a correct one.
The compiler cannot guarantee, however, that the prototypes are consistent across different
compilation units. For instance if in file1.c you declare:
int fn(void);
then, the call
fn();
will be accepted. If you then in file2.c you declare another prototype
void fn(int);
and then you use:
fn(6);
A closer view 51
the compiler cannot see this, and the program will be in error, crashing mysteriously at run
time. This kind of errors can be avoided if you always declare the prototypes in a header file
that will be included by all files that use that function. Do not declare prototypes in a source
file if the function is an external one.
}
The array of integers called “table” has n elements. This “n” is passed to the function as an
argument, so its value can’t be known in advance. The compiler generates code to allocate
space for this array in the stack when this function is entered. The storage used by the array
will be freed automatically when the function exits.
1.11.17 const.
Constant values can’t be modified. The following pair of declarations demonstrates the differ-
ence between a ‘‘variable pointer to a constant value’’ and a ‘‘constant pointer to a variable
value’’.
const int *ptr_to_constant;
int *const constant_ptr;
The contents of any object pointed to by ptr_to_constant shall not be modified through
that pointer, but ptr_to_constant itself may be changed to point to another object. Simi-
larly, the contents of the int pointed to by constant_ptr may be modified, but
constant_ptr itself shall always point to the same location.
1.11.18 unsigned.
Integer types (long long, long, int, short and char) have the most significant bit reserved for the
sign bit. This declaration tells the compiler to ignore the sign bit and use the values from zero
the 2n for the values of that type. For instance, a signed short goes from –32767 to 32767, an
unsigned short goes from zero to 65535 (216). See the standard include file <stdint.h> for the
ranges of signed and unsigned integer types.
With lcc-win32 the size of this structure will be 4 with no special options. With maximum
packing (-Zp1 option) the size will be two.
When you need to leave some space between adjacent bit fields you can use the notation:
unsigned : n;
For example
struct S {
int a:1;
int b:5;
unsigned:10;
int c:7;
};
Between the bit fields a and b we leave 10 unused bits so that c starts at a 16 bit word bound-
ary.
1.11.20 stdcall.
Normally, the compiler generates assembly code that pushes each argument to the stack, exe-
cutes the “call” instruction, and then adds to the stack the size of the pushed arguments to
return the stack pointer to its previous position. The stdcall functions however, return the stack
pointer to its previous position before executing their final return, so this stack adjustment is
not necessary.
The reason for this is a smaller code size, since the many instructions that adjust the stack after
the function call are not needed and replaced by a single instruction at the end of the called
function.
Functions with this type of calling convention will be internally “decorated” by the compiler
by adding the stack size to their name after an “@” sign. For instance a function called fn with
an integer argument will get called fn@4. The purpose of this “decorations” is to force the pre-
vious declaration of a stdcall function so that always we are sure that the correct declarations
was seen, if not, the program doesn’t link.
if (condition == 25)
continue;
doSomethingElse();
}
is equivalent to:
restart:
while (condition != 0) {
doSomething();
if (condition == 25)
goto restart;
doSomethingElse();
}
The advantage of avoiding the goto statement is the absence of a label. Note that in the case of
the “for” statement, execution continues with the increment part.
Remember that the continue statement within a switch statement doesn’t mean that execution
will continue the switch but continue the next enclosing for, while, or do statement.
1) An empty body of an iterative statement (while, do, or for). For instance you can do:
while (*p++)
; /* search the end of the string */
2) A label should appear just before a closing brace. Since labels must be attached to a
statement, the empty statement does that just fine.
1.11.23 Comments
Multi-line comments are introduced with the characters “/” and “*” and finished with the
opposite sequence: “*” followed by “/”. This commentaries can’t be nested. Single line com-
ments are introduced by the sequence “//” and go up to the end of the line. Here are some
examples:
"a//b" Four character string literal
// */ Single line comment, not syntax error
f = g/**//h;Equivalent to f = g/h;
//\
fn();Part of a comment since the last line ended with a "\"
printf("This is a mouse");
break;
default:
printf("Unknown animal");
break;
}
We define an enumeration of symbols, and call another function that asks for an animal type to
the user and returns its code. We dispatch then upon the value of the In this case the integer
expression that controls the switch is just an integer, but it could be any expression. Note that
the parentheses around the switch expression are mandatory. The compiler generates code that
evaluates the expression, and a series of jumps (gotos) to go to the corresponding portions of
the switch. Each of those portions is introduced with a “case” keyword that is followed by an
integer constant. Note that no expressions are allowed in cases, only constants that can be
evaluated by the compiler during compilation.
Cases end normally with the keyword “break” that indicates that this portion of the switch is
finished. Execution continues after the switch. A very important point here is that if you do not
explicitly write the break keyword, execution will continue into the next case. Sometimes this
is what you want, but most often it is not. Beware.
There is a reserved word “default” that contains the case for all other values that do not appear
explicitly in the switch. It is a good practice to always add this keyword to all switch state-
ments and figure out what to do when the input doesn’t match any of the expected values.
If the input value doesn’t match any of the enumerated cases and there is no default statement,
no code will be executed and execution continues after the switch.
Conceptually, the switch statement above is equivalent to:
if (pet == CAT) {
printf("This is a cat");
}
else if (pet == DOG) {
printf("This is a dog");
}
else if (pet == MOUSE) {
printf("This is a mouse");
} else printf("Unknown animal");
Both forms are exactly equivalent, but there are subtle differences:
Switch expressions must be of integer type. The “if” form doesn’t have this limitation.
In the case of a sizeable number of cases, the compiler will optimize the search in a switch
statement to avoid comparisons. This can be quite difficult to do manually with “if”s.
Cases of type other than int, or ranges of values can’t be specified with the switch statement,
contrary to other languages like Pascal that allows a range here. Switch statements can be
nested to any level (i.e. you can write a whole switch within a case statement), but this makes
the code unreadable and is not recommended.
1.11.25 inline
This instructs the compiler to replicate the body of a function at each call site. For instance:
int inline f(int a) { return a+1;}
Then:
int a = f(b)+f(c);
will be equivalent to writing:
A closer view 55
int a = (b+1)+(c+1);
Note that this expansion is realized in the lcc-win32 compiler only when optimizations are
ON. In a normal (debug) setting, the “inline” keyword is ignored. You can control this behav-
ior also, by using the command line option “-fno-inline”.
1) The & (AND) operator yields a 1 bit if both arguments are 1. Otherwise it yields a 0.
56 C programming with lcc-win32
2) The ^ (exclusive or) operator yields 1 if one argument is 1 and the other is zero, i.e. it yields
1 if their arguments are different. Otherwise it yields zero
3) The | (or) operator yields 1 if either of its arguments is a 1. Otherwise it yields a zero.
We can use for those operators the following truth table:
Note that this operators are normal operators, i.e. they evaluate always their operands, unlike
&& or || that use short-circuit evaluation. If we write:
a = 0 && fn(67);
the function call will never be executed. If we write
a = 0&fn(67);
the function call will be executed even if the result is fixed from the start.
The “p” variable will always receive the value 6, and the result of the function call will be dis-
carded.
Do not confuse this usage of the comma with other usages, for example within a function call.
The expression:
fn(c,d=6,78);
is always treated as a function call with three arguments, and not as a function call with a
comma expression. Note too that in the case of a function call the order of evaluation of the
different expressions separated by the comma is undefined, but with the comma operator it is
well defined: always from left to right.
1.11.30 Casts
A cast expression is the transformation of an object from one type to another. For instance, a
common need is to transform double precision numbers into integers. This is specified like
this:
double d;
...
(int)d
In this case, the cast needs to invoke run time code to make the actual transformation. In other
cases there is no code emitted at all. For instance in:
void *p;
...
(char *)p;
Transforming one type of pointer into another needs no code at all at run-time.
You can use a cast expression in another context, to indicate the type of a composite constant
literal. For instance:
typedef struct tagPerson {
char Name[75];
int age;
} Person;
1.11.31 Selection
A structure can have several different fields. The operators . and -> select from a variable of
structure type one of the fields it contains. For instance given:
struct example {
58 C programming with lcc-win32
int amount;
double debt_ratio;
};
struct example Exm;
struct example *pExm;
you can select the field ‘debt_ratio’ using
Exm.debt_ratio
If you have a pointer to a structure instead of the structure itself, you use
Exm->debt_ratio
This leads to an interesting question: Why having two operatos for selection?
It is obvious that the compiler can figure out that a pointer needs to be dereferenced, and could
generate the code as well if we would always write a point, as in many other languages. This
distinction has historial reasons. In a discussion about this in comp.lang.c Chris Torek, one of
the maintainers of the gcc C library wrote:45
The "true need" for separate "." and "->" operators went away sometime around
1978 or 1979, around the time the original K&R white book came out.
Before then, Dennis' early compilers accepted things like this:
struct { char a, b; };
int x 12345; /* yes, no "=" sign */
main() {
printf("%d is made up of the bytes %d and %d\n", x,
(x.a) & 0377, (x.b) & 0377);
}
(in fact, in an even-earlier version of the compiler, the syntax was "struct (" rather
than "struct {". The syntax above is what appeared in V6 Unix. I have read V6
code, but never used the V6 C compiler myself.)
Note that we have taken the "a" and "b" elements of a plain "int", not the "struct"
that contains them. The "." operator works on *any* lvalue, in this early C, and all
the structure member names must be unique -- no other struct can have members
named "a" and "b".
We can (and people did) also write things like:
struct rkreg { unsigned rkcsr, rkdar, rkwc; };
...
/* read disk sector(s) */
0777440->rkdar = addr;
0777440->rkwc = -(bytecount / 2);
0777440->rkcsr = RK_READ | RK_GO;
Note that the "->" operator works on *any* value, not just pointers.
Since this "early C" did not look at the left hand side of the "." and "->" operators,
it really did require different operators to achieve different effects. These odd
aspects of C were fixed even before the very first C book came out, but -- as with
the "wrong" precedence for the bitwise "&" and "|" operators -- the historical bag-
gage went along for the ride.
A closer view 59
1.11.32 Indirection
The * operator is the contrary of the address-of operator above. It expects a pointer and returns
the object the pointer is pointing to. For instance if you have a pointer pint that points to an
integer, the operation *pint will yield the integer value the pointer pint is pointing to.
The result of this operator is invalid if the pointer it is de referencing is not valid. In some
cases, de referencing an invalid pointer will provoke the dreaded window “This program has
performed an invalid operation and will terminate” that windows shows up when a machine
fault is detected. In other cases, you will be unlucky and the de referencing will yield a non-
sense result.
For instance, this program will crash:
int main(void)
{
char *p;
*p = 0;
return 1;
}
We have never assigned to p an object where it should point to. We are using a dangling
pointer. When we follow this program we obtain:
The debugger tells us that a machine exception has occurred, with the code 0xc0000005. This
means that the CPU has detected an invalid memory reference and has called the exception
mechanism of windows, that notified the debugger of the problem. Note the value of the
pointer in the output window: 0xfffa5a5a.
Lcc-win32 follows the philosophy that the sooner you see an error, the better. When it allo-
cates the stack frame of the function, it will write this value to all memory that has not been
explicitly initialized by the program. When you see this value in the debugger you can be
highly confident that this is an unitialized pointer or variable. This will not be done if you turn
on optimizations. In that case the pointer will contain whatever was in there when the compiler
allocated the stack frame.
Note that many other compilers do not do this, and some programs run without crashing out of
sheer luck. Since lcc-win32 catches this error, it looks to the users as if the compiler was
buggy. I have received a lot of complaints because of this.
This kind of problem is one of the most common bugs in C. Forgetting to initialize a pointer is
something that you can never afford to do.
Another error is initializing a pointer within a conditional expression:
char *BuggyFunction(int a)
{
char *result;
if (a > 34) {
result = malloc(a+34);
}
return result;
}
If the argument of this function is less than 35, the pointer returned will be a dangling pointer
since it was never initialized.
Function Prototype
fprintf int fprintf(FILE * stream,const char *fmt, ...);
The printf function is the same as fprintf, with an implicit argument “stdout”, i.e. the standard
output of the program, that in most cases is the console window.
fprintf(stdout,"hello\n"); <---> printf("hello\n");
The value returned by all this functions is EOF (End Of File, usually -1) if an error occurred
during the output operation. Otherwise, all went OK and they return the number of characters
written. For sprintf, the returned count does not include the terminating zero appended to the
output string.
The “fmt” argument is a character string or “control string”. It contains two types of charac-
ters: normal characters that are copied to the output without any change, and conversion spec-
ifications, that instruct the function how to format the next argument. In the example above,
we have just the string “hello\n”, without any conversion specification so that character string
is simply copied to the destination.
There should be always at least as many arguments to this functions as there are conversion
specifications. If you fail to do this with lcc-win32, you will get a warning from the compiler.
Other compilers can be less user friendly, so do not rely on that.
1.12.1 Conversions
A conversion specification begins with a percent sign ( % ) and is made of the following ele-
ments
1) Zero or more flag characters (-, +, 0, #, ‘, or space), which modify the meaning of the
operation.46
2) An optional minimum field with.Note well this. The printf function will not truncate a
field. The specified width is just that: a minimum.
4) An optional size flag, expressed as one of the letters ll, l, L, h, hh, j, q,t, or z.
h n Means short *.
with.
Well now we can pass to the final part.
The printf family 65
d,i Signed integer conversion is used and the argument is by default of type int. If
the h modifier is present, the argument should be a short, if the ll modifier
is present, the argument is a long long.
u Unsigned decimal conversion. Argument should be of type unsigned int
(default), unsigned short (h modifier) or unsigned long long (ll
modifier).
o Unsigned octal conversion is done. Same argument as the u format.
x,X Unsigned hexadecimal conversion. If x is used, the letters will be in lower case,
if X is used, they will be in uppercase. If the # modifier is present, the number
will be prefixed with 0x.
c The argument is printed as a character or a wide character if the l modifier is
present.
s The argument is printed as a string, and should be a pointer to byte sized charac-
ters (default) or wide characters if the l modifier is present. If no precision is
given all characters are used until the zero byte is found. Otherwise, the maxi-
mum number of characters used is the given precision.
p The argument is a pointer and is printed in pointer format. Under lcc-win32 this
is the same as the unsigned format (#u).
n The argument is a pointer to int (default), pointer to short (h modifier) or pointer
to long long (ll modifier). Contrary to all other conversions, this conversion
writes the number of characters written so far in the address pointed by its argu-
ment.
e, E Signed decimal floating point conversion..Argument is of type double
(default), or long double (with the L modifier) or qfloat (with the q
modifier). The result will be displayed in scientific notation with a floating point
part, the letter ‘e’ (for the e format) or the letter E (for the E format), then the
exponent. If the precision is zero, no digits appear after the decimal point, and
no point is shown. If the # flag is given, the point will be printed.
f, F Signed decimal floating point conversion. Argument is of type double (default),
or long double (with the L modifier). If the argument is the special value infinite,
inf will be printed. If the argument is the special value NAN the letters nan
are written.
g, G This is the same as the above but with a more compact representation. Argu-
ments should be floating point. Trailing zeroes after the decimal point are
removed, and if the number is an integer the point disappears. If the # flag is
present, this stripping of zeroes is not performed. The scientific notation (as in
format e) is used if the exponent falls below -4 or is greater than the precision,
that defaults to 6.
% How do you insert a % sign in your output? Well, by using this conversion: %%.
66 C programming with lcc-win32
Name Description
printf(format,...) Writes its output into the standard output
sprintf(string, format,...) Writes its output into a string
snprintf(string,size,format,...) Writes its output into the given string but never
exceeding “size” chars
fprintf(file,format,...) Writes its output into a file
2) For each format directive you must supply a corresponding pointer to a suitable sized
location.
4) The “%f” format means float, to enter a double you should write “%lf”.
5) Scanf ignores new lines and just skips over them. However, if we put a \n in the input
format string, we force the system to skip a new line.
6) Calls to scanf can’t be intermixed with calls to getchar, to the contrary of calls to printf and
putchar.
7) When scanf fails, it stops reading at the position where it finds the first input character that
doesn’t correspond to the expected sequence. If you are expecting a number and the user
makes a mistake and types 465x67, scanf will leave the input pointing to the “x”, that must
be removed by other means. Because of this problem it is always better to read one line of
input and then using sscanf in the line buffer rather than using scanf directly with user
input.
The printf family 67
/* providing a pointer to the wrong type (or a wrong format to the right pointer) */
sscanf("%d", &shortint); /* wrong */
sscanf("%hd", &shortint); /* right */
scanf("%d",&i);
scanf("%c",&c);
printf("%d %c\n",i,c);
}
Assume you type 45\n in response to the first scanf. The 45 is copied into variable n. When
the program encounters the next scanf, the remaining \n is quickly copied into the variable c.
The fix is to put explicitely a \n, like this:
...
scanf("%d\n",&i);
...
Type Format
char %c
short %hd
int %d or %i
long %ld
long long %lld
float %f or %e
double %lf or %le
long double %Lf or %Le
string %s
68 C programming with lcc-win32
1.13 Pointers
Pointers are one of the great ideas in C, but it is one that is difficult to grasp at the beginning.
All objects (integers, structures, any data) reside in RAM. Conceptually memory is organized
in a linear sequence of locations, numbered from 0 upwards. Pointers allow you to pass the
location of the data instead of the data itself.
To make things explicit, suppose you have some structure like this:
#define MAXNAME 128
struct person {
char Name[MAXNAME];
int Age;
bool Sex;
double Weight;
};
Instead of passing all the data to a function that works with this data, you just pass the address
where it starts. What is this address?
We can print it out. Consider this simple program:
1 #include <stdio.h>
2 #include <stdbool.h>
3 #define MAXNAME 128
4 struct person {
5 char Name[MAXNAME];
6 int Age;
7 bool Sex;
8 double Weight;
9 };
10 struct person Joe;
11 int main(void)
12 {
13 printf("0x%x + %d\n",&Joe,sizeof(struct person));
14 }
The address-of operator in line 13 returns the index of the memory location where the “Joe”
structure starts. In my machine this prints:
0x402004 + 144
The memory location 0x402004 (4 202 500 in decimal) contains the start of this data, that goes
up to 0x402094 (4 202 644).
When we write a function that should work with the data stored in that structure, we give it
just the number 4 202 500. That means:
The data starts at 4 202 500.
No copying needed, very efficient.
A pointer then, is an integer that contains the machine address, i.e. the number of the memory
location, where the data starts.
The integer that contains a memory location is not necessarily the same as a normal “int”, that
can be smaller or bigger than an address. In 64 bit systems, for instance, addresses can be 64
bits wide, but “int” can remain at 32 bits. In other systems (Win 32 for instance) a pointer fits
in an integer.
Pointers must be initialized before use by making them point to an object. Before initialization
they contain a NULL value if they are defined at global scope, or an undefined value if they
are local variables. It is always a very bad idea to use an uninitialized pointer.
Pointers 69
Memory locations are dependent on the operating system, the amount of memory installed,
and how the operating system presents memory to the programmer. Never make many
assumptions about memory locations. For instance, the addresses we see now under windows
32 bit could radically change in other context, where they become 64 bit addresses. Anyway,
under Win32 we use virtual memory, so those numbers are virtual addresses, and not really
memory locations inside the circuit board. See the description of virtual memory under win-
dows page 128.
A pointer can store the start address of an object, but nothing says that this object continues to
exist. If the object disappears, the pointers to it contain now invalid addresses, but it is up to
the programmer to take care of this. An object can disappear if, for instance, its address is
passed to the “free” function to release the memory. An object can disappear if its scope (i.e.
the function where it was defined) ends. It is a beginner’s mistake to write:
int *fn(int a)
{
int a;
...
return &a;
}
The “a” variable has a duration defined until the function “fn” exits. After that function exits,
the contents of all those memory locations containing local variables are undefined, and the
function is returning a pointer to memory that will be freed and recycled immediately. Of
course the memory location itself will not disappear, but the contents of it will be reassigned to
something else, maybe another function, maybe another local variable, nobody knows.
A pointer that is not initialized or that points to an object that has disappeared is a “dangling”
pointer and it is the nightmare of any C programmer. The bugs produced by dangling pointers
are very difficult to find, since they depend on whether the pointers destroy or not some other
object. This programs tend to work with small inputs, but will crash mysteriously with com-
plex and big inputs. The reason is that in a more complex environment, object recycling is
done more often, what means that the memory locations referenced by the dangling pointers
are more likely used by another object.
Pointers can be used to retrieve not only a portion of the object (operation “->”) but to
retrieve the whole object using the “*” notation. In the example above the operation “*pJoe”
would yield as its result the whole structure. This operation dereferences the pointer and
retrieves the entire object it is pointing to, making it the exact opposite of the “&” (address-of)
operator.
Only two kinds of arithmetic operations are possible with machine addresses: Addition or sub-
traction of a displacement, or subtraction of two machine addresses. No other arithmetic oper-
ators can be applied to pointers.
1.13.1.2 Subtraction
The subtraction of two pointers means the distance that separates two objects of the same type.
This distance is not expressed in terms of memory locations but in terms of the size of the
objects. For instance the distance between two consecutive objects is always one, and not the
number of memory locations that separates the start addresses of the two objects.
The type of the result is an integer, but it is implementation defined exactly which (short, int,
long, etc). To make things portable, the standard has defined a special typedef, ptrdiff_t
setjmp and longjmp 71
that encapsulates this result type. Under lcc-win32 this is an “int” but under other versions of
lcc-win32 (in 64 bit architectures for instance) it could be something else.
int guard(void)
/* Return 0 if successful; else lonjmp code */
{
int status = setjmp(ErrorEnv);
if (status != 0)
return status; /* error */
process();
return 0;
}
int process(void)
47. This wasn’t the case in MSDOS, for instance. NULL pointers were made to point to a special location
in memory where the compiler left a special value that was checked when the program exited. If the
value at the end of the program wasn’t what it should be, that meant that somewhere in the program, a
NULL pointer was used in an assignment.
72 C programming with lcc-win32
{
int error_code;
...
if (error_happened) longjmp(ErrorEnv,error_code);
...
}
With all respect I have for Harbison and Steele and their excellent book, this example shows
how NOT to use setjmp/longjmp.
The ErrorEnv global variable is left in an undefined state after the function exits with zero.
When you use this facility utmost care must be exercised to avoid executing a longjmp to a
function that has already exited. This will always lead to catastrophic consequences. After this
function exists with zero, the contents of the global ErrorEnv variable are a bomb that will
explode your program if used. Now, the process() function is entirely tied to that variable and
its validity. You can’t call process() from any other place. A better way could be:
#include <setjmp.h>
jmp_buf ErrorEnv;
int guard(void)
/* Return 0 if successful; else longjmp code */
{
jmp_buf pushed_env;
memcpy(push_env,ErrorEnv,sizeof(jmp_buf));
int status = setjmp(ErrorEnv);
if (status == 0)
process();
memcpy(ErrorEnv, pushed_env, sizeof(jmp_buf));
return status;
}
int process(void)
{
int error_code=0;
...
if (error_code) longjmp(ErrorEnv,error_code);
...
}
This way, the contents ErrorEnv are left as they were before, and if you setup in the first lines
of the main() function:
int main(void)
{
if (setjmp(ErrorEnv)) // Do not pass any other code.
return ERROR_FAILURE; // Just a general failure code
...
}
This way the ErrorEnv can be always used without fearing a crash. Note that I used memcpy
and not just the assignment:
pushed_env = ErrorEnv; /* wrong! */
since jmp_buf is declared as an array as the standard states. Arrays can only be copied with
memcpy or a loop assigning each member individually.
Note that this style of programming is sensitive to global variables. Globals will not be
restored to their former values, and, if any of the procedures in the process() function modified
global variables, their contents will be unchanged after the longjmp.
#include <setjmp.h>
setjmp and longjmp 73
jmp_buf ErrorEnv;
double global;
int guard(void)
/* Return 0 if successful; else longjmp code */
{
jmp_buf pushed_env;
memcpy(push_env,ErrorEnv,sizeof(jmp_buf));
int status = setjmp(ErrorEnv);
global = 78.9776;
if (status == 0)
process();
memcpy(ErrorEnv, pushed_env, sizeof(jmp_buf));
// Here the contents of “global” will be either 78.9776
// or 23.87 if the longjmp was taken.
return status;
}
int process(void)
{
int error_code=0;
...
global = 23.87;
if (error_code) longjmp(ErrorEnv,error_code);
...
}
And if you erase a file longjmp will not undelete it. Do not think that longjmp is a time
machine that will go to the past.
Yet another problem to watch is the fact that if any of the global pointers pointed to an address
that was later released, after the longjmp their contents will be wrong.
Any pointers that were allocated with malloc will not be released, and setjmp/longjmp could
be the source of a memory leak. Within lcc-win32 there is an easy way out, since you can use
the garbage collector instead of malloc/free. The garbage collector will detect any unused
memory and will released when doing the gc.
printf("1: %d\n",localVariable);
if (setjmp(jumper) == 0) { // return from longjmp
localVariable++; (2)
printf("2: %d\n",localVariable);
longjmp(jumper,1);
}
localVariable++; (3)
printf("3: %d\n",localVariable);
return 0;
}
74 C programming with lcc-win32
Our “localVariable” starts with the value 1. Then, before calling longjmp, it is incremented. Its
value should be two. At exit, “localVariable” is incremented again at should be three. We
would expect the output:
1: 1
2: 2
3: 3
And this is indeed the output we get if we compile without any optimizations. When we turn
optimizations on however, we get the output:
1: 1
2: 2
3: 2
Why?
Because “localVariable” will be stored in a register. When longjmp returns, it will restore all
registers to the values they had when the setjmp was called, and if localVariable lives in a reg-
ister it will return to the value 1, even if we incremented it before calling longjmp.
The only way to avoid this problem is to force the compiler to allocate localVariable in mem-
ory, using the “volatile” keyword. The declaration should look like this:
int volatile localVariable;
This instructs the compiler to avoid any optimizations with this variable, i.e. it forces allocat-
ing in the stack, and not in a register. This is required by the ANSI C standard. You can’t
assume that local variables behave normally when using longjmp/setjmp.
The setjmp/longjmp functions have been used to implement larger exception handling frame-
works. For an example of such a usage see for example “Exceptions and assertions” in “C
Interfaces and implementations” of David Hanson, Chapter 4.
Simple programs 75
1.15.1 strchr
Find the first occurrence of a given character in a character string. Return a pointer to the char-
acter if found, NULL otherwise.
This problem is solved in the standard library by the strchr function. Let’s write it. The
algorithm is very simple: We examine each character. If it is zero, this is the end of the string,
we are done and we return NULL to indicate that the character is not there. If we find it, we
stop searching and return the pointer to the character position.
char *FindCharInString(char *str, int ch)
{
while (*str != 0 && *str != ch) {
str++;
}
if (*str == ch)
return str;
return NULL;
}
We loop through the characters in the string. We use a while condition requiring that the char-
acter pointed to by our pointer “str” is different than zero and it is different than the character
given. In that case we continue with the next character by incrementing our pointer, i.e. mak-
ing it point to the next char. When the while loop ends, we have either found a character, or we
have arrived at the end of the string. We discriminate between these two cases after the loop.
1.15.2 strlen
Return the length of a given string not including the terminating zero.
This is solved by the strlen function. We just count the chars in the string, stopping when
we find a zero byte.
int strlen(char *str)
76 C programming with lcc-win32
{
char *p = str;
while (*p != 0) {
p++;
}
return p – str;
}
We copy our pointer into a new one that will loop through the string. We test for a zero byte in
the while condition. Note the expression *p != 0. This means “Fetch the value this pointer
is pointing to (*p), and compare it to zero”. If the comparison is true, then we increment the
pointer to the next byte.48
We return the number of characters between our pointer p and the saved pointer to the start of
the string. This pointer arithmetic is quite handy.
How can this program fail? The same problems apply that we discussed in the previous
example, but in an attenuated form: only a wrong answer is returned, not an outright wrong
pointer. The program will only stop at a zero byte.
1.15.3 ispowerOfTwo
Given a positive number, find out if it is a power of two.
Algorithm: A power of two has only one bit set, in binary representation. We count the bits. If
we find a bit count different than one we return 0, if there is only one bit set we return 1.
Implementation: We test the rightmost bit, and we use the shift operator to shift the bits right,
shifting out the bit that we have tested. For instance, if we have the bit pattern 1 0 0 1, shifting
it right by one gives 0 1 0 0: the rightmost bit has disappeared, and at the left we have a new
bit shifted in, that is always zero.
int ispowerOfTwo(unsigned int n)
{
unsigned int bitcount = 0;
while (n != 0) {
if (n & 1) {
bitcount++;
}
n = n >> 1;
}
if (bitcount == 1)
return 1;
return 0;
}
Our condition here is that n must be different49 than zero, i.e. there must be still some bits to
count to go on. We test the rightmost bit with the binary and operation. The number one has
only one bit set, the rightmost one. By the way, one is a power of two50.
48. The expression (*p != 0) could have been written in the form while (*p), using the implicit
test for a non-zero result in any logical expression. Any expression will be considered true if its value is
anything but zero. It is better, however, to make comparisons explicit.
49. Different than is written in C != instead of ≠ . The symbol ≠ wasn’t included in the primitive
typewriters in use when the C language was designed, and we have kept that approximation. It is consistent
with the usage of ! as logical not, i.e. != would mean not equal.
Simple programs 77
Note that the return expression could have also been written like this:
return bitcount == 1;
The intention of the program is clearer with the “if” expression.
How can this program fail? The while loop has only one condition: that n is different than
zero, i.e. that n has some bits set. Since we are shifting out the bits, and shifting in always
zero bits since bitcount is unsigned, in a 32 bit machine like a PC this program will stop
after at most 32 iterations. Running mentally some cases (a good exercise) we see that for
an input of zero, we will never enter the loop, bitcount will be zero, and we will return
0, the correct answer. For an input of 1 we will make only one iteration of the loop. Since 1
& 1 is 1, bitcount will be incremented, and the test will make the routine return 1, the
correct answer. If n is three, we make two passes, and bitcount will be two. This will be
different than 1, and we return zero, the correct answer.
Anh Vu Tran [email protected] made me discover an important bug. If you change
the declaration of “n” from unsigned int to int, without qualification, the above function
will enter an infinite loop if n is negative.
Why?
When shifting signed numbers sign is preserved, so the sign bit will be carried through, pro-
voking that n will become eventually a string of 1 bits, never equal to zero, hence an infinite
loop.
This means that we test if x-1 and x doesn't share bits with the and operator. If they share some
bits, the AND of their bits will yield some non-zero bits. The only case where this will not
happen is when x is a power of two.
Of course, if x is zero (not a power of two) this doesn't hold, so we add an explicit test for zero
with the logical AND operator:
50. For a more detailed discussion, see the section News groups at the end of this tutorial.
78 C programming with lcc-win32
xx && expression.
Negative powers of two (0.5, 0.25, 0.125, etc) could share this same property in a suitable
fraction representation. 0.5 would be 0.1, 0.250 would be 0.01, 0.125 would be 0.001 etc.
This snippet and several others are neatly explained in:
http://www.caam.rice.edu/~dougm/twiddle.
1.15.5 strlwr
Given a string containing upper case and lower case characters, transform it in a string with
only lower case characters. Return a pointer to the start of the given string.51
This is the library function strlwr. In general is not a good idea to replace library functions,
even if they are not part of the standard library (as defined in the C standard) like this one.
We make the transformation in-place, i.e. we transform all the characters of the given string.
This supposes that the user of this program has copied the original string elsewhere if the orig-
inal is needed again.
if (str == NULL)
return NULL;
while (*p) {
*str = tolower(*p);
p++;
}
return str;
}
We include the standard header ctype.h, which contains the definition of several character
classification functions (or macros) like “isupper” that determines if a given character is
upper case, and many others like “isspace”, or “isdigit”. We need to include the stdio.h
header file too, since it contains the definition for NULL.52
The first thing we do is to test if the given pointer is NULL. If it is, we return NULL. Then, we
start our loop that will span the entire string. The construction while(*p) tests if the con-
tents of the character pointer p is different than zero. If this is the case, we transform it into a
lower case one. We increment our pointer to point to the next character, and we restart the
51. This convention is used in the library function. Actually, it is a quite bad interface, since the return
value doesn’t give any new information to the user, besides the expected side effect of transforming the given
string. A better return value would be the number of changed characters, for instance, that would allow the
caller to know if a transformation was done at all, or the length of the string, or several others. But let’s
implement this function as it is specified in the standard library. Many times, you will see that even if it is
obvious that software must be changed, the consequences of a change are so vast that nobody wants to
assume it, and we get stuck with software “for compatibility reasons”. Here is yet another example.
52. If remembering which headers contain which definitions bothers you (as it bothers me) just use the
<stdheaders.h> header file included with lcc-win32. That file is just an include of all standard header
files.
Simple programs 79
loop. When the loop finishes because we hit the zero byte that terminates the string, we stop
and return the saved position of the start of the string.
Note the cast that transforms str from a char * into an unsigned char *. The reason is that it
could exist a bad implementation of the toupper() function, that would index some table using
a signed char. Characters above 128 would be considered negative integers, what would result
in a table being indexed by a negative offset, with bad consequences, as you may imagine.
How can this program fail?
Since we test for NULL, a NULL pointer can’t provoke a trap. Is this a good idea?
Well this depends. This function will not trap with NULL pointers, but then the error will be
detected later when other operations are done with that pointer anyway. Maybe making a trap
when a NULL pointer is passed to us is not that bad, since it will uncover the error sooner
rather than later. There is a big probability that if the user of our function is calling us to trans-
form the string to lower case, is because he/she wants to use it later in a display, or otherwise.
Avoiding a trap here only means that the trap will appear later, probably making error finding
more difficult.
Writing software means making this type of decisions over and over again.
Obviously this program will fail with any incorrect string, i.e. a string that is missing the final
zero byte. The failure behavior of our program is quite awful: in this case, this program will
start destroying all bytes that happen to be in the range of uppercase characters until it hits a
random zero byte. This means that if you pass a non-zero terminated string to this apparently
harmless routine, you activate a randomly firing machine gun that will start destroying your
program’s data in a random fashion. The absence of a zero byte in a string is fatal for any C
program. In a tutorial this can’t be too strongly emphasized!
1.15.6 paste
You have got two text files, and you want to merge them in a single file, separated by tabula-
tions. For instance if you have a file1 with this contents:
line 1
line2
and you got another file2 with this contents
line 10
line 11
you want to obtain a file
line1 line 10
line 2 line 11
Note that both file can be the same.
A solution for this could be the following program:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
We decide arbitrarily that lines longer than 32767 chars will make this program fail.
*/
#define MAXLINELEN 32767
int main(int argc,char *argv[])
{
/*
80 C programming with lcc-win32
We need two FILE pointers, and two line buffers to hold each line from each file. We receive
in argc the number of arguments passed + 1, and in the character array argv[] the names of
the two files
*/
FILE *f1,*f2;
char buf1[MAXLINELEN],buf2[MAXLINELEN];
/*
We test immediately if the correct number of arguments has been given. If not, we exit with a
clear error message.
*/
if (argc < 3) {
fprintf(stderr,"Usage: paste file1 file2\n");
exit(EXIT_FAILURE);
}
/*
We open both files, taking care not to open the same file twice. We test with strcmp if they are
equal.
*/
f1 = fopen(argv[1],"r");
if (strcmp(argv[1],argv[2]))
f2 = fopen(argv[2],"r");
else
f2 = f1;
/*
We read line after line of the first file until we reach the end of the first file.
*/
while(fgets(buf1,MAXLINELEN,f1)) {
char *p = strchr(buf1,'\n');
/*
the fgets function leaves a \n in the input. We erase it if it is there. We use for this the strchr
function, that returns the first occurrence of a character in a string and returns a pointer to it.
If it doesn’t it returns NULL, so we test below before using that pointer
*/
if (p)
*p = 0;
/*
We output the first file line, separated from the next with a single tabulation char.
*/
printf("%s\t",buf1);
/*
If there are still lines to be read from file 2, we read them and we print them after doing the
same treatment as above.
*/
if (f2 != f1 && fgets(buf2,MAXLINELEN,f2)) {
p = strchr(buf2,'\n');
if (p)
*p = 0;
printf("%s\n",buf2);
}
/*
If we are duplicating the same file just print the same line again.
*/
else printf("%s\n",buf1);
}
/*
End of the while loop. When we arrive here the first file has been completely scanned. We
close and shut down.
*/
fclose(f1);
Simple programs 81
if (f1 != f2)
fclose(f2);
return 0;
}
How can this program fail?.
Well, there are obvious bugs in this program. Before reading the answer, try to see if you can
see them. What is important here is that you learn how to spot bugs and that is a matter of log-
ical thinking and a bit of effort.
Solution will be in the next page. But just try to find those bugs yourself.
Before that bug however we see this lines in there:
if (f2 != f1 && fgets(buf2,MAXLINELEN,f2)) {
}
else printf("%s\n",buf1);
If f1 is different from f2 (we have two different files) and file two is shorter than file one, that
if statement will fail after n2 lines, and the else portion will be executed, provoking the dupli-
cation of the contents of the corresponding line of file one.
To test this, we create two test files, file1 and file2. their contents are:
File1:
File 1: line 1
File 1: line 2
File 1: line 3
File 1: line 4
File 1: line 5
File 1: line 6
File 1: line 7
File 1: line 8
File2:
File 2: line 1
File 2: line 2
File 2: line 3
File 2: line 4
The line five of file one was read and since file two is already finished, we repeat it.
Is this a bug or a feature?
We received vague specifications. Nothing was said about what the program should do with
files of different sizes. This can be declared a feature, but of course is better to be aware of it.
82 C programming with lcc-win32
We see that to test hypothesis about the behavior of a program, there is nothing better than test
data, i.e. data that is designed to exercise one part of the program logic.
In many real cases, the logic is surely not so easy to follow as in this example. Building test
data can be done automatically. To build file one and two, this small program will do:
#include <stdio.h>
int main(void)
{
FILE *f = fopen("file1","w");
for (int i =0; i<8;i++)
fprintf(f,"File 1: Line %d\n",i);
fclose(f);
f = fopen("file2","w");
for (int i = 0; i < 5;i++)
fprintf(f,"File 2: Line %d\n",i);
fclose(f);
return 0;
}
This a good example of throw away software, software you write to be executed once. No
error checking, small and simple, so that there is less chance for mistakes.
And now the answer to the other bug above.
Using arrays and sorting 83
One of the first things to notice is that the program tests with strcmp to see if two files are the
same. This means that when the user passes the command line:
paste File1 filE1
our program will believe that they are different when in fact they are not. Windows is not case
sensitive for file names. The right thing to do there is to compare the file names with stricmp,
that ignores the differences between uppercase and lowercase.
But an even greater problem is that we do not test for NULL when opening the files. If any of
the files given in the command line doesn’t exist, the program will crash. Add the necessary
tests before you use it.
Another problem is that we test if we have the right number of arguments (i.e. at least two file
names) but if we have more arguments we simply ignore them. What is the right behavior?
Obviously we could process (and paste) several files at once. Write the necessary changes in
the code above. Note that if you want to do the program really general, you should take into
account the fact that a file could be repeated several times in the input, i.e.
paste file1 file2 file1 file3
Besides, the separator char in our program is now hardwired to the tab character in the code of
the program. Making this an option would allow to replace the tab with a vertical bar, for
instance.
But the problem with such an option is that it supposes that the output will be padded with
blanks for making the vertical bars align. Explain why that option needs a complete rewrite of
our program. What is the hidden assumption above that makes such a change impossible?
Another feature that paste.exe could have, is that column headers are automatically under-
lined. Explain why adding such an option is falling into the featurism that pervades all modern
software. Learn when to stop!
if (argc < 2) {
54. Global variables provoke an undocumented coupling between several, apparently unrelated
procedures or modules. Overuse of them is dangerous, and provokes errors that can be difficult to understand
and get rid of. I learned this by experience in long debugging sessions, and now I use global variables more
sparingly.
Using arrays and sorting 85
But let’s come back to our task. We update the array at each character, within the while loop.
We just use the value of the character (that must be an integer from zero to 256 anyway) to
index the array, incrementing the corresponding position. Note that the expression:
Frequencies[count]++
means
Frequencies[count] = Frequencies[count]+1;
i.e.; the integer at that array position is incremented, and not the count variable!
Then at the end of the while loop we display the results. We only display frequencies when
they are different than zero, i.e. at least one character was read at that position. We test this
with the statement:
if (Frequencies[count] != 0) { ... statements ... }
The printf statement is quite complicated. It uses a new directive %c, meaning character, and
then a width argument, i.e. %3c, meaning a width of three output chars. We knew the %d
directive to print a number, but now it is augmented with a width directive too. Width direc-
tives are quite handy when building tables to get the items of the table aligned with each other
in the output.
The first thing we do is to build a test file, to see if our program is working correctly. We build
a test file containing
ABCDEFGHIJK
And we call:
lcc frequencies.c
lcclnk frequencies.obj
frequencies fexample
and we obtain:
D:\lcc\examples>frequencies fexample
13 chars in file
( 10) = 1
( 13) = 1
A ( 65) = 1
B ( 66) = 1
C ( 67) = 1
D ( 68) = 1
E ( 69) = 1
F ( 70) = 1
G ( 71) = 1
H ( 72) = 1
I ( 73) = 1
J ( 74) = 1
K ( 75) = 1
We see that the characters \r (13) and new line (10) disturb our output. We aren’t interested in
those frequencies anyway, so we could just eliminate them when we update our Frequencies
table. We add the test:
if (c >= ' ')
Frequencies[c]++;
i.e. we ignore all characters with value less than space: \r, \n or whatever. Note that we ignore
tabulations too, since their value is 8.
The output is now more readable:
H:\lcc\examples>frequencies fexample
86 C programming with lcc-win32
13 chars in file
A ( 65) = 1
B ( 66) = 1
C ( 67) = 1
D ( 68) = 1
E ( 69) = 1
F ( 70) = 1
G ( 71) = 1
H ( 72) = 1
I ( 73) = 1
J ( 74) = 1
K ( 75) = 1
We test now our program with itself. We call:
frequencies frequencies.c
758 chars in file
I have organized the data in a table to easy the display.
What is missing obviously, is to print the table in a sorted way, so that the most frequent char-
acters would be printed first. This would make inspecting the table for the most frequent char-
acter easier.
The function qsort doesn’t return an explicit result. It is a void function. Its argument list, is
the following:
Argument 1: is a void *. Void *??? What is that? Well, in C you have void, that means none,
and void *, that means this is a pointer that can point to anything, i.e. a pointer to an
untyped value. We still haven’t really introduced pointers, but for the time being just be
happy with this explanation: qsort needs the start of the array that will sort. This array can
be composed of anything, integers, user defined structures, double precision numbers,
whatever. This "whatever" is precisely the “void *”.
Argument 2 is a size_t. This isn’t a known type, so it must be a type defined before in
stdlib.h. By looking at the headers, and following the embedded include directives, we find:
“stdlib.h” includes “stddef.h”, that defines a “typedef” like this:56
typedef unsigned int size_t;
This means that we define here a new type called “size_t”, that will be actually an unsigned
integer. Typedefs allow us to augment the basic type system with our own types. Mmmm
interesting. We will keep this for later use.
In this example, it means that the size_t n, is the number of elements that will be in the
array.
Argument 3 is also a size_t. This argument contains the size of each element of the array,
i.e. the number of bytes that each element has. This tells qsort the number of bytes to skip at
each increment or decrement of a position. If we pass to qsort an array of 56 double preci-
sion numbers, this argument will be 8, i.e. the size of a double precision number, and the
preceding argument will be 56, i.e. the number of elements in the array.
Argument 4 is a function: int (*f)(const void *)); Well this is quite hard really.
We are in the first pages of this introduction and we already have to cope with gibberish
like this?
We have to use recursion now. We have again to start reading this from left to right, more or
less. We have a function pointer (f) that points to a function that returns an int, and that
takes as arguments a void *, i.e. a pointer to some unspecified object, that can’t be changed
within that function (const).
This is maybe quite difficult to write, but quite a powerful feature. Functions can be passed
as arguments to other functions in C. They are first class objects that can be used to specify
a function to call.
Why does qsort need this?
Well, since the qsort function is completely general, it needs a helper function, that will tell it
when an element of the array is smaller than the other. Since qsort doesn’t have any a priori
knowledge of the types of the elements of the passed array, it needs a helper function that
returns an integer smaller than zero if the first element is smaller than the next one, zero if the
elements are equal, or bigger than zero if the elements are bigger.
Let’s apply this to a smaller example, so that the usage of qsort is clear before we apply it to
our frequencies problem.
#include <stdlib.h>
56. Finding out where is something defined can be quite a difficult task. The easiest way is to use the
IDE of lcc-win32, right click in the identifier, and choose “goto definition”. If that doesn’t work, you can use
“grep” to search in a set of files.
88 C programming with lcc-win32
57. Most compilers do not have the C99 standard implemented. In those compilers you can’t do this and
you will have to declare the loop counter as a normal local variable. Another reason to stick to lcc-win32.
Using arrays and sorting 89
arrays and pointers to the first member are equivalent. This means that in many situations,
arrays “decay” into pointers to the first element, and loose their “array”ness. That is why you
can do in C things with arrays that would never be allowed in another languages. At the end of
this tutorial we will se how we can overcome this problem, and have arrays that are always
normal arrays that can be passed to functions without losing their soul.
At last we are ready to call our famous qsort function. We use the following call expression:
qsort((void*)argv,(size_t)argc,sizeof(char *),compare);
The first argument of qsort is a void *. Since our array argv is a char **, we transform it into
the required type by using a cast expression: (void *)argv.
The second argument is the number of elements in our array. Since we need a size_t and we
have argc, that is an integer variable, we use again a cast expression to transform our int into a
size_t. Note that typedefs are accepted as casts.
The third argument should be the size of each element of our array. We use the built-in pseudo
function sizeof, which returns the size in bytes of its argument. This is a pseudo function,
because there is no such a function actually. The compiler will replace this expression with an
integer that it calculates from its internal tables. We have here an array of char *, so we just tell
the compiler to write that number in there.
The fourth argument is our comparison function. We just write it like that. No casts are
needed, since we were careful to define our comparison function exactly as qsort expects.
To output the already sorted array we use again a “for” loop. Note that the index of the loop is
declared at the initialization of the “for” construct. This is one of the new specifications of the
C99 language standard, that lcc-win32 follows. You can declare variables at any statement,
and within “for” constructs too. Note that the scope of this integer will be only the scope of the
enclosing “for” block. It can’t be used outside this scope.58
Note that we have written the “for” construct without curly braces. This is allowed, and means
that the “for” construct applies only to the next statement, nothing more. The ...
printf("\n");... is NOT part of the for construct.
Ok, now let’s compile this example and make a few tests to see if we got that right.
h:\lcc\examples> sortargs aaa bbb hhh sss ccc nnn
aaa bbb ccc hhh nnn sss
OK, it seems to work. Now we have acquired some experience with qsort, we can apply our
knowledge to our frequencies example. We use cut and paste in the editor to define a new
compare function that will accept integers instead of char **. We build our new comparison
function like this:
int compare( const void *arg1, const void *arg2 )
{
return ( * ( int * ) arg1 - * ( int * ) arg2 );
}
We just return the difference between both numbers. If arg1 is bigger than arg2, this will be a
positive number, if they are equal it will be zero, and if arg1 is smaller than arg2 it will be a
negative number, just as qsort expects.
Right before we display the results then, we add the famous call we have been working so hard
to get to:
58. The compiler emits a record for the linker, telling it to put there the address of the global, if the
argument is a global variable, or will emit the right instructions to access the address of a local using the
frame pointer. This has been working for a while now.
90 C programming with lcc-win32
qsort(Frequencies,256,sizeof(int),compare);
We pass the Frequencies array, its size, the size of each element, and our comparison function.
Here is the new version of our program, for your convenience. New code is in bold:
#include <stdio.h>
#include <stdlib.h>
if (argc < 2) {
...
}
infile = fopen(argv[1],"rb");
if (infile == NULL) {
...
}
c = fgetc(infile);
while (c != EOF) {
...
}
fclose(infile);
printf("%d chars in file\n",count);
qsort(Frequencies,256,sizeof(int),compare);
for (count=0; count<256;count++) {
if (Frequencies[count] != 0) {
printf("%3c (%4d) = %d\n",
count,
count,
Frequencies[count]);
}
}
return 0;
}
We compile, link, and then we write
frequencies frequencies.c
957 chars in file
Using arrays and sorting 91
Well, sorting definitely works (you read this display line by line), but we note with dismay that
double values[N];
int indexes[N];
// This comparison function will use the integer pointer that it receives to index the
// global values array. Then the comparison is done using the double precision values
// found at those positions.
int compare(const void *pp1,const void *pp2)
{
const int *p1 = pp1, *p2 = pp2;
double val1 = values[*p1];
double val2 = values[*p2];
int main(void)
{
int i,r;
// We fill the array of double precision values with a random number for demonstration
// purposes. At the same time we initialize our indexes array with a consecutive
// sequence of 0 <= i <= N
for (i=0; i<N;i++) {
r = rand();
values[i] = (double) r;
indexes[i] = i;
}
// Now we call our qsort function
qsort(indexes,N,sizeof(int),compare);
// We print now the values array, the indexes array, and the sorted vector.
for (i=0; i<N; i++) {
printf("Value %6.0f index %3d %6.0f\n",
values[i],indexes[i],values[indexes[i]]);
}
return 0;
}
Another possibility is to make a structure containing a value and an index, and sort a table of
those structures instead of sorting the values array directly, but this solution is less efficient in
space terms.
Pointers and references 93
2) Assignment from a function or expression that returns a pointer of the same type. In the
frequencies example we initialize our infile pointer with the function fopen, that returns a
pointer to a FILE.
3) Assignment to a specific address. This happens in programs that need to access certain
machine addresses for instance to use them as input/output for special devices. In those
cases you can initialize a pointer to a specific address. Note that this is not possible under
windows, or Linux, or many operating systems where addresses are virtual addresses. More
of this later.
4) You can assign a pointer to point to some object by taking the address of that object. For
instance:
int integer;
int *pinteger = &integer;
Here we make the pointer “pinteger” point to the int “integer” by taking the address of that
integer, using the “&” operator. This operator yields the machine address of its argument.
5) You can access the data the pointer is pointing to by using the “*” operator. When we want
to access the integer “pinteger” is pointing to, we write:
*pinteger = 7;
94 C programming with lcc-win32
This means: “store at the address contained in this pointer pa, the value 8944”.
We can also write:
int m = 698 + *pa;
This means: “add to 698 the contents of the integer whose machine address is contained in the
pointer pa and store the result of the addition in the integer m”
We have a “reference” to a, that in this scope will be called “ra”. Any access to this compiler
maintained pointer is done as we would access the object itself, no special syntax is needed.
For instance we can write:
ra = (ra+78) / 79;
Note that with references the “*” operator is not needed. The compiler will do automatically
this for you.
It is obvious that a question arises now: why do we need references? Why can’t we just use the
objects themselves? Why is all this pointer stuff necessary?
Well this is a very good question. Many languages seem to do quite well without ever using
pointers the way C does.
The main reason for these constructs is efficiency. Imagine you have a huge database table,
and you want to pass it to a routine that will extract some information from it. The best way to
pass that data is just to pass the address where it starts, without having to move or make a copy
of the data itself. Passing an address is just passing a 32-bit number, a very small amount of
data. If we would pass the table itself, we would be forced to copy a huge amount of data into
the called function, what would waste machine resources.
The best of all worlds are references. They must always point to some object, there is no such
a thing as an uninitialized reference. Once initialized, they can’t point to anything else but to
the object they were initialized to, i.e. they can’t be made to point to another object, as normal
pointers can. For instance, in the above expressions, the pointer pa is initialized to point to the
integer “a”, but later in the program, you are allowed to make the “pa” pointer point to
59. This has nothing to do with object oriented programming of course. The word object is used here
with its generic meaning.
60. References aren’t part of the C language standard, and are in this sense an extension of lcc-win32.
They are wildly used in another related language (C++), and the implementation of lcc-win32 is compatible
with the implementation of references of that language.
Pointers and references 95
another, completely unrelated integer. This is not possible with the reference “ra”. It will
always point to the integer “a”.
When passing an argument to a function, if that function expects a reference and you pass it a
reference, the compiler will arrange for you passing only the address of the data pointed to by
the reference.
96 C programming with lcc-win32
1.18.1 Structures
Structures are a contiguous piece of storage that contains several simple types, grouped as a
single object.61 For instance, if we want to handle the two integer positions defined for each
pixel in the screen we could define the following structure:
struct coordinates {
int x;
int y;
};
Structures are introduced with the keyword “struct” followed by their name. Then we open a
scope with the curly braces, and enumerate the fields that form the structure. Fields are
declared as all other declarations are done. Note that a structure declaration is just that, a dec-
laration, and it reserves no actual storage anywhere.
After declaring a structure, we can use this new type to declare variables or other objects of
this type:
struct coordinate Coords = { 23,78};
Here we have declared a variable called Coords, that is a structure of type coordinate, i.e. hav-
ing two fields of integer type called “x” and “y”. In the same statement we initialize the struc-
ture to a concrete point, the point (23,78). The compiler, when processing this declaration, will
assign to the first field the first number, i.e. to the field “x” will be assigned the value 23, and
to the field “y” will be assigned the number 78.
Note that the data that will initialize the structure is enclosed in curly braces.
Structures can be recursive, i.e. they can contain pointers to themselves. This comes handy to
define structures like lists for instance:
struct list {
struct list *Next;
int Data;
};
Here we have defined a structure that in its first field contains a pointer to the same structure,
and in its second field contains an integer. Please note that we are defining a pointer to an iden-
tical structure, not the structure itself, what is impossible. A structure can’t contain itself.
Double linked list can be defined as follows:
struct dl_list {
struct dl_list *Next;
struct dl_list *Previous;
int Data;
};
This list features two pointers: one forward, to the following element in the list, and one back-
ward, to the previous element of the list.
A special declaration that can only be used in structures is the bit-field declaration. You can
specify in a structure a field with a certain number of bits. That number is given as follows:
struct flags {
unsigned HasBeenProcessed:1;
61. The usage of the #pragma pack construct is explained in lcc-win32 user’s manual. Those
explanations will not be repeated here.
Structures and unions 97
unsigned HasBeenPrinted:1;
unsigned Pages:5;
};
This structure has three fields. The first, is a bit-field of length 1, i.e. a Boolean value, the sec-
ond is also a bit-field of type Boolean, and the third is an integer of 5 bits. In that integer you
can only store integers from zero to 31, i.e. from zero to 2 to the 5th power, minus one. In this
case, the programmer decides that the number of pages will never exceed 31, so it can be
safely stored in this small amount of memory.
We access the data stored in a structure with the following notation:
<structure-name> ‘.’ field-name
or
<structure-name ‘->’ field-name
We use the second notation when we have a pointer to a structure, not the structure itself.
When we have the structure itself, or a reference variable, we use the point.
Here are some examples of this notation:
void fn(void)
{
coordinate c;
coordinate *pc;
coordinate &rc = c;
To access the x coordinate from the 4th member of the array we would write:
coordArray[3].x = 89;
Note (again) that in C array indexes start at zero. The fourth element is numbered 3.
Many other structures are possible their number is infinite:
struct customer {
int ID;
char *Name;
char *Address;
double balance;
98 C programming with lcc-win32
time_t lastTransaction;
unsigned hasACar:1;
unsigned mailedAlready:1;
};
This is a consecutive amount of storage where:
• an integer contains the ID of the customer,
• a machine address pointing to the start of the character string with the customer name,
• another address pointing to the start of the name of the place where this customer lives,
• a double precision number containing the current balance,
• a time_t (time type) date of last transaction,
• and other bit fields for storing some flags.
struct mailMessage {
MessageID ID;
time_t date;
char *Sender;
char *Subject;
char *Text;
char *Attachements;
};
This one starts with another type containing the message ID, again a time_t to store the date,
then the addresses of some character strings.
The set of functions that use a certain type are the methods that you use for that type, maybe in
combination with other types. There is no implicit “this” in C. Each argument to a function is
explicit, and there is no predominance of anyone.
A customer can send a mailMessage to the company, and certain functions are possible,
that handle mailMessages from customers. Other mailMessages aren’t from custom-
ers, and are handled differently, depending on the concrete application.
Because that’s the point here: an application is a coherent set of types that performs a certain
task with the computer, for instance, sending automated mailings, or invoices, or sensing the
temperature of the system and acting accordingly in a multi-processing robot, or whatever. It
is up to you actually.
Note that in C there is no provision or compiler support for associating methods in the struc-
ture definitions. You can, of course, make structures like this:
struct customer {
int ID;
char *Name;
char *Address;
double balance;
time_t lastTransaction;
unsigned hasACar:1;
unsigned mailedAlready:1;
bool (*UpdateBalance)(struct customer *Customer,
double newBalance);
};
The new field, is a function pointer that contains the address of a function that returns a Bool-
ean result, and takes a customer and a new balance, and should (eventually) update the balance
field, that isn’t directly accessed by the software, other than trough this procedure pointer.
When the program starts, you assign to each structure in the creation procedure for it, the func-
tion DefaultGetBalance() that takes the right arguments and does hopefully the right thing.
Structures and unions 99
This allows you the flexibility of assigning different functions to a customer for calculating
his/her balance according to data that is known only at runtime. Customers with a long history
of overdraws could be handled differently by the software after all. But this is no longer C, is
the heart of the application.
True, there are other languages that let you specify with greater richness of rules what and how
can be sub classed and inherited. C, allows you to do anything, there are no other rules here,
other the ones you wish to enforce.
You can subclass a structure like this. You can store the current pointer to the procedure some-
where, and put your own procedure instead. When your procedure is called, it can either:
Do some processing before calling the original procedure
Do some processing after the original procedure returns
Do not call the original procedure at all and replace it entirely.
We will show a concrete example of this when we speak about windows sub classing later.
Sub classing allows you to implement dynamic inheritance. This is just an example of the
many ways you can program in C.
But is that flexibility really needed?
Won’t just
bool UpdateBalance(struct customer *pCustomer, double newBalance);
do it too?
Well it depends. Actions of the general procedure could be easy if the algorithm is simple and
not too many special cases are in there. But if not, the former method, even if more compli-
cated at first sight, is essentially simpler because it allows you greater flexibility in small man-
ageable chunks, instead of a monolithic procedure of several hundred lines full of special case
code…
Mixed strategies are possible. You leave for most customers the UpdateBalance field empty
(filled with a NULL pointer), and the global UpdateBalance procedure will use that field to
calculate its results only if there is a procedure there to call. True, this wastes 4 bytes per cus-
tomer in most cases, since the field is mostly empty, but this is a small price to pay, the struc-
ture is probably much bigger anyway.
62. Note that putting structure names in typedefs all uppercase is an old habit that somehow belongs to
the way I learned C, but is in no way required by the language. Personally I find those all-uppercase names
clearer as a way of indicating to the reader that a user defined type and not a variable is used, since I have
never used an all-uppercase name for a variable name. Separating these names by upper/lower case improves
the readability of the program, but this is a matter of personal taste.
100 C programming with lcc-win32
The best thing to do is to always use the sizeof operator when the structure size needs to be
used somewhere in the code. For instance, if you want to allocate a new piece of memory
managed by the memory manager, you call it with the size of the structure.
GC_malloc(sizeof(struct DataPoint)*67);
This will allocate space for 67 structures of type “DataPoint” (as defined above). Note that we
could have written
GC_malloc(804);
since we have:
struct DataPoint {
struct coordinate coords;
int Data;
};
We can add the sizes:
Two integers of 4 bytes for the coordinate member, makes 8 bytes, plus 4 bytes for the Data
member, makes 12, that multiplies 67 to make 804 bytes.
But this is very risky because of two reasons:
Compiler alignment could change the size of the structure
If you add a new member to the structure, the sizeof() specification will continue to work,
since the compiler will correctly recalculate it each time. If you write the 804 however, when
you add a new member to the structure this number has to be recalculated again, making one
more thing that can go wrong in your program.
In general, it is always better to use compiler-calculated constants like sizeof() instead of hard-
wired numbers.
This new name can be used with the sizeof() operator too, and we can write:
GC_malloc(sizeof(COORDINATE));
instead of the old notation. But please keep in mind the following: once you have defined a
typedef, never use the “struct” keyword in front of the typedef keyword, if not, the compiler
will get really confused.
1.18.4 Unions
Unions are similar to structures in that they contain fields. Contrary to structures, unions will
store all their fields in the same place. They have the size of the biggest field in them. Here is
an example:
union intfloat {
int i;
double d;
};
This union has two fields: an integer and a double precision number. The size of an integer is
four in lcc-win32, and the size of a double is eight. The size of this union will be eight bytes,
with the integer and the double precision number starting at the same memory location. The
union can contain either an integer or a double precision number but not the two. If you store
an integer in this union you should access only the integer part, if you store a double, you
should access the double part. Field access syntax is the same as for structures: we use always
the point.
Using the definition above we can write:
int main(void)
{
union intfloat ifl;
union intfloat *pIntfl = &ifl;
pIntfl.i = 2;
pintfl.d = 2.87;
}
First we assign to the integer part of the union an integer, then we assign to the double preci-
sion part a double.
Unions are useful for storing structures that can have several different memory layouts. In
general we have an integer that tells us which kind of data follows, then a union of several
types of data. Suppose the following data structures:
struct fileSource {
char *FileName;
int LastUse;
};
struct networkSource {
int socket;
char *ServerName;
int LastUse;
};
struct windowSource {
WINDOW window;
63. An identifier can also represent a macro or a macro argument, but here we will assume that the pre-
processor already has done its work.
102 C programming with lcc-win32
int LastUse;
};
All of this data structures should represent a source of information. We add the following
defines:
#define ISFILE 1
#define ISNETWORK 2
#define ISWINDOW 3
and now we can define a single information source structure:
struct Source {
int type;
union {
struct fileSource file;
struct networkSource network;
struct windowSource window;
} info;
};
We have an integer at the start of our generic “Source” structure that tells us, which of the fol-
lowing possible types is the correct one. Then, we have a union that describes all of our possi-
ble data sources.
We fill the union by first assigning to it the type of the information that follows, an integer that
must be one of the defined constants above. Then we copy to the union the corresponding
structure. Note that we save a lot of wasted space, since all three structures will be stored
beginning at the same location. Since a data source must be one of the structure types we have
defined, we save wasting memory in fields that would never get used.
Another usage of unions is to give a different interpretation of the same data. For instance, an
MMX register in an x86 compatible processor can be viewed as two integers of 32 bits, 4 inte-
gers of 16 bits, or 8 integers of 8 bits. Lcc-win32 describes this fact with a union:
typedef struct _pW {
char high;
char low;
} _packedWord; // 16 bit integer
if (argc < 2) {
printf("Usage: countchars <file name>\n");
exit(EXIT_FAILURE);
}
infile = fopen(argv[1],"rb");
if (infile == NULL) {
printf("File %s doesn't exist\n",argv[1]);
exit(EXIT_FAILURE);
}
for (int i = 0; i<256; i++) {
Frequencies[i].CharacterValue = i;
}
104 C programming with lcc-win32
c = fgetc(infile);
while (c != EOF) {
count++;
if (c >= ' ')
Frequencies[c].Frequency++;
c = fgetc(infile);
}
fclose(infile);
printf("%d chars in file\n",count);
qsort(Frequencies,256,sizeof(CHARS),compare);
for (count=0; count<256;count++) {
if (Frequencies[count].Frequency != 0) {
printf("%3c (%4d) = %d\n",
Frequencies[count].CharacterValue,
Frequencies[count].CharacterValue,
Frequencies[count].Frequency);
}
}
return 0;
}
We transformed our integer array Frequencies into a CHARS array with very few changes:
just the declaration. Note that the array is still accessed as a normal array would. By the way, it
is a normal array.
We changed our “compare” function too, obviously, since we are now comparing two CHARS
structures, and not just two integers. We have to cast our arguments into pointers to CHARS,
and I decided that using two temporary variables would be clearer than a complicated expres-
sion that would eliminate those.
The initialization of the CharacterValue field is trivially done in a loop, just before we start
counting chars. We assign to each character an integer from 0 to 256 that’s all.
When we print our results, we use that field to get to the name of the character, since our array
that before qsort was neatly ordered by characters, is now ordered by frequency. As before, we
write the character as a letter with the %c directive, and as a number, with the %d directive.
When we call this program with:
frequencies frequencies.c
we obtain at last:
1311 chars in file
We see immediately that the most frequent character is the space with a count of 154, followed
by the letter ‘e’ with a count of 77, then ‘n’ with 60, etc.
Strange, where does “z” appear? Ah yes, in sizeof. And that I? Ah in FILE, ok, seems to be
working.
2) When you have a structure OBJECT, not a pointer, you should use the syntax:
object.field
Beginners easily confuse this.
3) When you have an array of structures, you index it using the normal array notation syntax,
then use the object or the pointer in the array. If you have an array of pointers to structures
you use:
array[index]->field
5) If you are interested in the offset of the field, i.e. the distance in bytes from the beginning of
the structure to the field in question you use the offsetof macro defined in stddef.h:
offsetof(structure or typedef name,member name)
For instance to know the offset of the Frequency field in the structure CHARS above we
would write:
offsetof(CHARS,Frequency)
This would return an integer with the offset in bytes.
64. You see the infinite loop here? Tell me: why is this loop never ending? Look at the code again.
106 C programming with lcc-win32
• an object.
• a function
• a tag or a member of a structure, union or enum
• a typedef
• a label
For each different entity that an identifier designates, the identifier can be used (is visible)
only within a region of a program called its scope. There are four kinds of scopes in C.
The file scope is built from all identifiers declared outside any block or parameter declaration,
it is the outermost scope, where global variables and functions are declared.
A function scope is given only to label identifiers.
The block scope is built from all identifiers that are defined within the block. A block scope
can nest other blocks.
The function prototype scope is the list of parameters of a function. Identifiers declared
within this scope are visible only within it.
Let’s see a concrete example of this:
static int Counter = 780;// file scope
extern void fn(int Counter); // function prototype scope
void function(int newValue, int Counter) // Block scope
{
double d = newValue;
label:
for (int i = 0; i< 10;i++) {
if (i < newValue) {
char msg[45];
int Counter = 78;
sprintf(msg,"i=%d\n",i*Counter); Å
}
if (i == 4)
goto label;65
}
}
At the point indicated by the arrow, the poor “Counter” identifier has had a busy life:
• It was bound to an integer object with file scope
• Then it had another incarnation within the function prototype scope
• Then, it was bound to the variables of the function ‘setCounter’ as a parameter
• That definition was again “shadowed” by a new definition in an inner block, as a local
variable.
The value of “Counter” at the arrow is 78. When that scope is finished its value will be the
value of the parameter called Counter, within the function “function”.
When the function definition finishes, the file scope is again the current scope, and “Counter”
reverts to its value of 780.
65. Yes, but then all initializations are done out of their respective contexts. Some people say this is the
wrong way to go, and that each data type should initialize in a separate init procedure. In this concrete
example and in many situations, making a global init procedure is a correct way of building software. Other
contexts may be different of course.
Top-down analysis 107
The “linkage” of an identifier refers to the visibility to other modules. Basically, all identifiers
that appear at a global scope (file scope) and refer to some object are visible from other mod-
ules, unless you explicitly declare otherwise by using the “static” keyword.
Problems can appear if you first declare an identifier as static, and later on, you define it as
external. For instance:
static void foo(void);
and several hundred lines below you declare:
void foo(void) {
...
}
Which one should the compiler use? static or not static? That is the question…
Lcc-win32 chooses always non-static, to the contrary of Microsoft’s compiler that chooses
always static. Note that the behavior of the compiler is explicitly left undefined in the stan-
dard, so both behaviors are correct.
if (infile == NULL)
return 1;
108 C programming with lcc-win32
But there are good reasons to avoid that. Our global array can become a bottleneck, if we
decide later to process more than one file, and store the results of several files, maybe combin-
ing them and adding up their frequencies.
Another reason to explicitly pass the Frequencies array as a parameter is of course clarity.
The Frequencies array is a parameter of this function, since this function modifies it. Pass-
ing it explicitly to a routine that modifies it makes the software clearer, and this is worth the
few cycles the machine needs to push that address in the stack.
When we write software in today’s powerful micro-processors, it is important to get rid of the
frame of mind of twenty years ago, when saving every cycle of machine time was of utmost
importance. Pushing an extra argument, in this case the address of the Frequencies array,
takes 1 cycle. At a speed of 1400-2500 MHz, this cycle isn’t a high price to pay.
Continuing our analysis of our “main” function, we notice that the next task, is displaying the
output of the frequencies array. This is quite a well-defined task, since it takes the array as
input, and should produce the desired display. We define then, a new function DisplayOut-
put() that will do that. Its single parameter is the same Frequencies array.
void DisplayOutput(CHARS *Frequencies)
{
for (int count=0; count<256;count++) {
if (Frequencies[count].Frequency != 0) {
printf("%3c (%4d) = %d\n",
Frequencies[count].CharacterValue,
Frequencies[count].CharacterValue,
Frequencies[count].Frequency);
}
}
}
Let’s look at our “main() function again:
int main(int argc,char *argv[])
{
int count;
FILE *infile = checkargs(argc,argv);
if (infile == NULL)
return 1;
Initialize();
count = ProcessFile(infile,Frequencies);
fclose(infile);
printf("%d chars in file\n",count);
qsort(Frequencies,256,sizeof(CHARS),compare);
DisplayOutput(Frequencies);
}
Note how much clearer our “main” function is now. Instead of a lot of code without any struc-
ture we find a much smaller procedure that is constructed from smaller and easily understand-
able parts.
Now, suppose that we want to handle several files. With this organization, it is straightforward
to arrange for this in a loop. ProcessFile() receives an open FILE and a Frequencies array, both
can be easily changed now. A modular program is easier to modify than a monolithic one!
We see too that the function that process the files leaves them open. We could streamline more
«main» if we got rid of that in the ProcessFile() function, but I find it personally better that the
same function that opens a file closes it too, so that the reader can see if the fopen/fclose calls
match.
110 C programming with lcc-win32
if (argc < 2) {
printf("Usage: countchars <file name>\n");
}
Extending a program 111
else {
findfirstResult = findfirst(argv[1],&fd);
if (findfirstResult < 0) {
printf("File %s doesn't exist\n",argv[1]);
return NULL;
}
}
infile = malloc(sizeof(STREAM));
infile->Name = argv[1];
memcpy(&infile->FindData, &fd,
sizeof( struct _finddata_t ));
infile->File = fopen(fd.name,"rb");
infile->handle = findfirstResult;
return infile;
}
We store in the local variable findfirstResult the long returned by findfirst. We test then, if
smaller than zero, i.e. if something went wrong. If findfirst failed, this is equivalent to our
former program when it opened a file and tested for NULL.
But now comes an interesting part. If all went well, we ask the system using the built-in mem-
ory allocator “malloc” for a piece of fresh RAM at least of size STREAM. If this call fails,
there is no more memory left. For the time being (see later) we ignore this possibility.
We want to store in there all the parameters we need to use the findfirst/findnext function pair
with easy, and we want to copy the finddata_t into our own structure, and even put the name of
the stream and a FILE pointer into it. To do that, we need memory, and we ask it to the “mal-
loc” allocator.
Once that done, we fill the new STREAM with the data:
• we set its name using the same pointer as argv[1],
• we copy the fd variable into our newly allocated structure, and
• we set the file pointer of our new structure with fopen, so that we can use the stream to
read characters from it.
Another alternative to using the built-in memory allocator would have been to declare a global
variable, call it CurrentStream that would contain all our data. We could have declared some-
where in the global scope something like:
STREAM CurrentStream;
and use always that variable.
This has several drawbacks however, the bigger of it being that global variables make follow-
ing the program quite difficult. They aren’t documented in function calls, they are always
“passed” implicitly, they can’t be used in a multi-threaded context, etc.
Better is to allocate a new STREAM each time we need one. This implies some memory man-
agement, something we will discuss in-depth later on.
Now, we should modify our ProcessFile function, since we are passing to it a STREAM and
not a FILE. This is easily done like this:
int ProcessFile(STREAM *infile,CHARS *Frequencies)
{
int count = 0;
int c = fgetc(infile->file);
while (c != EOF) {
count++;
if (c >= ' ')
Frequencies[c].Frequency++;
112 C programming with lcc-win32
c = fgetc(infile->file);
}
return count;
}
Instead of reading directly from the infile argument, we use the “file” member of it. That’s all.
Note that infile is a pointer, so we use the notation with the arrow, instead of a point to access
the “file” member of the structure.
But there is something wrong with the name of the function. It wrongly implies that we are
processing a FILE instead of a stream. Let’s change it to ProcessStream, and change the name
of the stream argument to instream, to make things clearer:
int ProcessStream(STREAM *instream,CHARS *Frequencies)
{
int count = 0;
int c = fgetc(instream->file);
while (c != EOF) {
count++;
if (c >= ' ')
Frequencies[c].Frequency++;
c = fgetc(instream->file);
}
return count;
}
This looks cleaner.
Now we have to change our “main” function, to make it read all the files that match the given
name.
Our new main procedure looks like this:
int main(int argc,char *argv[])
{
int count=0;
STREAM *infile=checkargs(argc,argv);
if (infile == NULL) {
return(1);
}
Initialize();
do {
count += ProcessStream(infile,Frequencies);
fclose(infile->file);
infile = GetNext(infile);
} while (infile != 0);
printf("%d chars in file\n",count);
qsort(Frequencies,256,sizeof(CHARS),compare);
DisplayOutput(Frequencies);
return 0;
}
We didn’t have to change a lot, thanks to the fact that the complexities of reading and handling
a stream are now hidden in a function, with well-defined parameters. We build a GetNext
function that returns either a valid new stream or NULL, if it fails. It looks like this:
STREAM *GetNext(STREAM *stream)
{
STREAM *result;
struct _finddata_t fd;
long findnextResult = _findnext(stream->handle,&fd);
Extending a program 113
if (findnextResult < 0)
return NULL;
result = malloc(sizeof(STREAM));
memcpy(result->FindData,
&fd,
sizeof(struct _finddata_t));
result->handle = stream->handle;
result->file = fopen(fd.name,"rb");
return result;
}
In the same manner that we allocate RAM for our first STREAM, we allocate now a new one,
and copy into it our “finddata” handle, and we open the file.
We compile, and we get a compiler warning:
D:\lcc\examples>lcc -g2 freq1.c
Warning freq1.c: 44 missing prototype for memcpy
Warning freq1.c: 94 missing prototype for memcpy
0 errors, 2 warnings
Yes, but where is memcpy defined? We look at the documentation using F1 in Wedit, and we
find out that it needs the <string.h> header file. We recompile and we get:
H:\lcc\examples>lcc freq1.c
Error freq1.c: 95 type error in argument 1 to `memcpy'; found
`struct _finddata_t' expected `pointer to void'
1 errors, 0 warnings
Wow, an error. We look into the offending line, and we see:
memcpy(result->FindData,&fd,sizeof(struct _finddata_t));
Well, we are passing it a structure, and the poor function is expecting a pointer !
This is a serious error. We correct it like this:
memcpy(&result->FindData,&fd,sizeof(struct _finddata_t));
We take the address of the destination structure using the address-of operator “&”. We see that
we would have never known of this error until run-time when our program would have
crashed with no apparent reason; a difficult error to find. Note: always use the right header file
to avoid this kind of errors!
Our program now looks like this:
#include <stdio.h> // We need it for using the FILE structure
#include <stdlib.h>// We need it for using malloc
#include <io.h>// We need it for using findfirst/findnext
#include <string.h>// We need it for memcpy
typedef struct tagChars {
int CharacterValue;// The ASCII value of the character
int Frequency;// How many seen so far
} CHARS;
typedef struct tagStream {
char Name;// Input name with possible “*” or “?” chars in it
struct _finddata_t FindData;
long handle;
FILE *file;// An open file
} STREAM;
CHARS Frequencies[256]; // Array of frequencies
int compare(){} // Skipped, it is the same as above
STREAM *checkargs(int argc,char *argv[])
{
STREAM *infile = NULL;
long findfirstResult;
114 C programming with lcc-win32
if (findnextResult < 0)
return NULL;
result = malloc(sizeof(STREAM));
memcpy(&result->FindData,&fd,
sizeof(struct _finddata_t));
result->handle = stream->handle;
result->file = fopen(fd.name,"rb");
result->Name = stream->Name;
return result;
}
Improving the design 115
if (infile == NULL) {
return(1);
}
Initialize();
do {
count += ProcessStream(infile,Frequencies);
fclose(infile->file);
infile = GetNext(infile);
} while (infile != 0);
printf("%d chars in file\n",count);
qsort(Frequencies,256,sizeof(CHARS),compare);
DisplayOutput(Frequencies);
return 0;
}
if (result == NULL) {
fprintf(sdterr,
No more memory left!\nProcessing stops\n);
exit(EXIT_FAILURE);
}
return result;
}
Note that we keep the same signature, i.e. the same type of result and the same type of argu-
ments as the original function we want to replace. This is function sub classing.
Note too, that we use fprintf instead of printf. Fprintf takes an extra argument, a file where the
output should go. We use the predefined file of standard error, instead of the normal output file
stdout, that printf implicitly takes.
Why?
Because it is possible that the user redirects the output to a file instead of letting the output go
directly to the screen. In that case we would write our error messages to that file, and the user
would not see the error message.68
We change all occurrences of malloc by xmalloc, and this error is gone.
We change too, all other error-reporting functions, to take into account stderr.
116 C programming with lcc-win32
But there are other issues. Take for instance our finddata_t structure that we carry around in
each STREAM structure. What’s its use? We do not use it anywhere; just copy it into our
STREAM.
But why we introduced that in the first place?
Well, we didn’t really know much about findfirst, etc, and we thought it could be useful.
So we are stuck with it?
No, not really. Actually, it is very easy to get rid of it. We just change the structure STREAM
like this:
typedef struct tagStream {
char *Name;
long handle;
FILE *file;
} STREAM;
and we take care to erase any references to that member. We eliminate the memcpy calls, and
that’s all. Our program is smaller, uses less memory, and, what is essential, does the same
thing quicker than the older version, since we spare the copying.
It is very important to learn from the beginning that software gains not only with the lines of
code that you write, but also with the lines of code that you eliminate!
l c c - w i n 3 2 0
108 99 99 45 119 105 110 51 50 0
We will have at each of the position of the string array a byte containing a number: the ASCII
equivalent of a letter. The array will be followed by a zero byte. Zero is not an ASCII charac-
ter, and can’t appear in character strings, so it means that the string finishes there.
This design is quite ancient, and dates to the beginning of the C language. It has several flaws,
as you can immediately see:
• There is no way to know the length of a string besides parsing the whole character array
until a zero byte is found.
• Any error where you forget to assign the last terminated byte, or this byte gets
overwritten will have catastrophic consequences.
• There is no way to enforce indexing checks.
The most frequently used function of this library are:
"strlen" that returns an integer containing the length of the string. Example:
68. Some people would say that this is not “Standard C”, since the standard doesn’t explicitly allow for
this. But I would like to point out that the standard explicitly states (page 96 of my edition) that: “An
implementation may accept other forms of constant expressions.”. The implementation lcc-win32 then, is free
to accept the above declaration as a constant expression.
Traditional string representation in C 117
"strcmp" that compares two strings. If the strings are equal it returns zero. If the first is
greater (in the lexicographical sense) than the second it returns a value greater than zero. If
the first string is less than the second it returns some value less than zero. The order for the
strings is based in the ASCII character set.
a == b strcmp(a,b) == 0
a<b strcmp(a,b) < 0
a >= b strcmp(a,b) >= 0
"strcpy" copies one string into another. strcpy(dst,src) copies the src string into
the dst string. This means it will start copying characters from the beginning of the src
location to the dst location until it finds a zero byte in the src string. No checks are ever
done, and it is assumed that the dst string contains sufficient space to hold the src string.
If not, the whole program will be destroyed. One of the most common errors in C program-
ming is forgetting these facts.
"strcat" appends a character string to another. strcat(src,app) will add all the char-
acters of "app" at the end of the "src" string. For instance, if we have the string pointer
that has the characters "lccwin32" as above, and we call the library function str-
cat(str," compiler") we will obtain the following sequence:
l c c w i n 3 2 c o m p i l e r 0
108 99 99 119 105 110 51 50 32 99 111 109 112 105 108 101 114 0
The common operations for strings are defined in the header file <string.h>.
strupr Convert string to upper case strdup Duplicate a string. Uses malloc.
strlwr Convert string to lower case strrev Reverse characters in a string
But this isn’t a good solution. We change the current directory, instead of actually using the
path information. Changing the current directory could have serious consequences in the
working of other functions. If our program would be a part of a bigger software, this solution
would surely provoke more headaches than it solves. So, let’s use our “name” field, that up to
now isn’t being used at all. Instead of passing a name to Fopen, we will pass it a STREAM
structure, and it will be Fopen that will take care of opening the right file. We change it like
this:
FILE *Fopen(STREAM *stream,char *name,char *mode)
{
FILE *result;
char fullname[1024],*p;
p = strrchr(stream->Name,'\\');
if (p == NULL) {
fullname[0] = 0;
}
else {
*p = 0;
strcpy(fullname,stream->Name);
strcat(fullname,"\\");
*p = '\\';
}
strcat(fullname,name);
result = fopen(fullname,mode);
if (result == NULL) {
fprintf(stderr,
"Impossible to open '%s'\n",fullname);
exit(EXIT_FAILURE);
}
return result;
}
We declare a array of characters, with enough characters inside to hold a maximum path, and a
few more. Then, and in the same declaration, we declare a character pointer, p. This pointer
will be set with strrchr. If there isn’t any backslash in the path, we just set the start of our
fullname[ ] to zero. If there is a path, we cut the path component as we did before, and copy
the path component into the fullname variable. The library function strcpy will copy the sec-
ond argument to the first one, including the null character for terminating correctly the string.
We add then a backslash using the strcat function that appends to its first argument the second
string. It does this by copying starting at the terminator for the string, and copying all of its
second argument, including the terminator.
We restore the string, and append to our full path the given name. In our example, we copy
into fullpath the character string “..\src77”, then we add the backslash, and then we add the
rest of the name to build a name like “..\src77\alloc.c”.
This done, we look again into our program. Yes, there are things that could be improved. For
instance, we use the 256 to write the number of elements of the array Frequencies. We could
improve the readability of we devised a macro NELEMNTS, that would make the right calcula-
tions for us.
That macro could be written as follows:
#define NELEMENTS(array) (sizeof(array)/sizeof(array[0]))
This means just that the number of elements in any array, is the size of that array, divided by
the size of each element. Since all elements have the same size, we can take any element to
make the division. Taking array[0] is the best one, since that element is always present.
Buffer-overflows 121
1.26 Buffer-overflows
Let’s come back to the code snippet above:
else {
*p = 0;
strcpy(fullname,stream->Name);
strcat(fullname,"\\");
*p = '\\';
}
strcat(fullname,name);
If the length of stream->Name is bigger than the size of the buffer, a security hole appears: the
program will write over the local variables of the function, and later over the return address,
stored in the stack. when our function is active.
We have a stack layout like this:
local variables of the calling function
arguments to the current function
return address
saved frame pointer
local variables: p is at the lowest address, then fullname, then result.
saved registers
stack pointer is here
An overflow when copying the Name field would destroy the saved frame pointer, and maybe
destroy the return address. The copy starts at the lowest address, the start of the fullname
buffer, then goes on to higher addresses.
After the overflow occurs, the function would continue normally until it executes the return
statement. Depending on the type of overflow, the return address would contain characters
from the Name field, that in most cases would lead to a wrong return address and a program
crash.
But that is not always the case. It could be that a user of our program would notice this style of
programming, and give us a file name that, when overflowing in the copy to the temporary
69. Memory allocation problems plague also other languages like C++ that use a similar schema than C.
122 C programming with lcc-win32
buffer would form a correct return address, that would pass then control to some other routine
that the malicious user prepared for us.
This kinds of exploits can be avoided if we use other functions of the standard library:
else {
*p = 0;
strncpy(fullname,stream->Name,sizeof(fullname));
strncat(fullname,"\\",sizeof(fullname));
*p = '\\';
}
strncat(fullname,name,sizeof(fullname));
Those functions test for overflow conditions and are safer in case of unforeseen input
patterns.
There is a problem with strncpy though. It does NOT terminate the resulting string with a
zero. If we get a Name field with exactly sizeof(fullname) chars, the string will be missing
the trailing zero since strncpy doesn’t add it. One way to cover this possibility is to do:
else {
*p = 0;
fullname[sizeof(fullname)-1] = 0;
strncpy(fullname,stream->Name,sizeof(fullname)-1);
strncat(fullname,"\\",sizeof(fullname)-1);
*p = '\\';
}
strncat(fullname,name,sizeof(fullname)-1);
We finish fullname with a zero, and we copy only up to sizeof(fullname)-1 chars, leaving
our terminating zero intact.
But you should have noted that there is something wrong here. We do initialize the terminating
zero within an else statement. What happens if the execution of the function takes the other
path?
We see the following:
FILE *Fopen(STREAM *stream,char *name,char *mode)
{
FILE *result;
char fullname[1024],*p;
p = strrchr(stream->Name,'\\');
if (p == NULL) {
fullname[0] = 0;
}
else {
*p = 0;
fullname[sizeof(fullname)-1] = 0;
strncpy(fullname,stream->Name,sizeof(fullname)-1);
strncat(fullname,"\\",sizeof(fullname)-1);
*p = '\\';
}
strncat(fullname,name,sizeof(fullname)-1);
result = fopen(fullname,mode);
if (result == NULL) {
fprintf(stderr,
"Impossible to open '%s'\n",fullname);
exit(EXIT_FAILURE);
}
return result;
}
Buffer-overflows 123
If p is NULL, we will initialize the first char of fullname to zero. Then, execution continues at
the strncat call after the else statement, and we will copy at most sizeof(fullname)-1 chars
into it, overwriting the zero and maybe leaving the fullname character array without the termi-
nating zero if the length of the passed buffer is bigger than the size of fullname. That could
lead to a crash in fopen that surely expects a well formed string.
The solution is to finish the fullname buffer in ALL cases.
FILE *Fopen(STREAM *stream,char *name,char *mode)
{
FILE *result;
char fullname[1024],*p;
p = strrchr(stream->Name,'\\');
fullname[sizeof(fullname)-1] = 0;
if (p == NULL) {
fullname[0] = 0;
}
else {
*p = 0;
strncpy(fullname,stream->Name,sizeof(fullname)-1);
strncat(fullname,"\\",sizeof(fullname)-1);
*p = '\\';
}
strncat(fullname,name,sizeof(fullname)-1);
result = fopen(fullname,mode);
if (result == NULL) {
fprintf(stderr,
"Impossible to open '%s'\n",fullname);
exit(EXIT_FAILURE);
}
return result;
}
Never forget to initialize a variable in BOTH cases of an if statement. Bugs such as this are
very difficult to catch later on.
What will happen if the name of the file is bigger than our buffer? This function will fail. The
fopen call will return NULL since the file name has been truncated, and we will show an error
telling that a truncated file name doesn’t exist. Is this a good behavior?
It depends. For a technical user, a long and truncated file name could be an indicator that the
file name is just too long. Better error reporting would be appropriate if required, for instance
at the start of the function a test could produce a clear message like "Name too long".
This is a worst case oversized buffer, since we have already counted some of those digits in the
original calculation, where we have allowed for 3+2+2+2+4 = 13 characters for the digits. A
tighter calculation can be done like this:
Number of characters besides specifications (%d or %s) in the string: 6.
Number of %d specs 5
Total = 6+5*11 = 61 + terminating zero 62.
The correct buffer size for a 32 bit system is 62.
1) The initial data area of the program. Here are stored compile time constants like the
character strings we use, the tables we input as immediate program data, the space we
allocate in fixed size arrays, and other items. This area is further divided into initialized
data, and uninitialized data, that the program loader sets to zero before the program starts.
When you write a declaration like int data = 78; the data variable will be stored in
the initialized data area. When you just write at the global level int data; the variable
will be stored in the uninitialized data area, and its value will be zero at program start.
2) The stack. Here is stored the procedure frame, i.e. the arguments and local variables of each
function. This storage is dynamic: it grows and shrinks when procedures are called and
they return. At any moment we have a stack pointer, stored in a machine register, that
contains the machine address of the topmost position of the stack.
Summary
The definition of the asctime function involves a sprintf call writing into a buffer of size 26. This call will
have undefined behavior if the year being represented falls outside the range [-999, 9999]. Since appli-
cations may have relied on the size of 26, this should not be corrected by allowing the implementation
to generate a longer string. This is a defect because the specification is not self-consistent and does not
restrict the domain of the argument.
3) The heap. Here is the space that we obtain with malloc or equivalent routines. This also a
dynamic data area, it grows when we allocate memory using malloc, and shrinks when
we release the allocated memory with the free() library function.
There is no action needed from your side to manage the initial data area or the stack. The com-
piler takes care of all that.
The program however, manages the heap, i.e. it expects that you keep book exactly and with-
out any errors from each piece of memory you allocate using malloc. This is a very exhausting
undertaking that takes a lot of time and effort to get right. Things can be easy if you always
free the allocated memory before leaving the function where they were allocated, but this is
impossible in general, since there are functions that precisely return newly allocated memory
for other sections of the program to use.
There is no other solution than to keep book in your head of each piece of RAM. Several
errors, all of them fatal, can appear here:
• You allocate memory and forget to free it. This is a memory leak.
• You allocate memory, and you free it, but because of a complicated control flow (many
ifs, whiles and other constructs) you free a piece of memory twice. This corrupts the
whole memory allocation system, and in a few milliseconds all the memory of your
program can be a horrible mess.
• You allocate memory, you free it once, but you forget that you had assigned the memory
pointer to another pointer, or left it in a structure, etc. This is the dangling pointer
problem. A pointer that points to an invalid memory location.
Memory leaks provoke that the RAM space used by the program is always growing, eventu-
ally provoking a crash, if the program runs for enough time for this to become significant. In
short-lived programs, this can have no consequences, and even be declared as a way of mem-
ory management. The lcc compiler for instance, always allocates memory without ever both-
ering to free it, relying upon the windows system to free the memory when the program exits.
Freeing a piece of RAM twice is much more serious than a simple memory leak. It can com-
pletely confuse the malloc() system, and provoke that the next allocated piece of RAM will be
the same as another random piece of memory, a catastrophe in most cases. You write to a vari-
able and without you knowing it, you are writing to another variable at the same time, destroy-
ing all data stored there.
More easy to find, since more or less it always provokes a trap, the dangling pointer problem
can at any moment become the dreaded show stopper bug that crashes the whole program and
makes the user of your program loose all the data he/she was working with.
I would be delighted to tell you how to avoid those bugs, but after more than 10 years working
with the C language, I must confess to you that memory management bugs still plague my pro-
grams, as they plague all other C programmers.71
The basic problem is that the human mind doesn’t work like a machine, and here we are ask-
ing people (i.e. programmers) to be like machines and keep book exactly of all the many small
pieces of RAM a program uses during its lifetime without ever making a mistake.
But there is a solution that I have implemented in lcc-win32. Lcc-win32 comes with an auto-
matic memory manager (also called garbage collector in the literature) written by Hans
Boehm. This automatic memory manager will do what you should do but do not want to do:
take care of all the pieces of RAM for you.
71. This discussion is based upon the article of Randy Kath, published in MSDN.
128 C programming with lcc-win32
Using the automatic memory manager you just allocate memory with GC_malloc instead of
allocating it with malloc. The signature (i.e. the result type and type of arguments) is the same
as malloc, so by just replacing all malloc by GC_malloc in your program you can benefit of
the automatic memory manager without writing any new line of code.
The memory manager works by inspecting regularly your whole heap and stack address space,
and checking if there is anywhere a reference to the memory it manages. If it doesn’t find any
references to a piece of memory it will mark that memory as free and recycle it. It is a very
simple schema, taken to almost perfection by several years of work from the part of the
authors.
To use the memory manager you should add the gc.lib library to your link statement or indi-
cate that library in the IDE in the linker configuration tab.
72. Note that this is a logical view of this address translation process. The actual implementation is much
more sophisticated, since Windows uses the memory manager of the CPU to speed up things. Please read the
original article to get a more in-depth view, including the mechanism of page protection, the working set, and
many other things.
Memory management strategies 129
remaining 12 bits are used to address an individual byte within the page frame. Here is a figure
that visualizes the structure:
We see that a considerable amount of memory is used to… manage memory. To realize the
whole 4GB address space, we would use 4MB of RAM. But this is not as bad as it looks like,
since Windows is smart enough to fill these pages as needed. And anyway, 4MB is not even
0.1% of the total 4GB address space offered by the system.73
Each process has its own page directory. This means that processes are protected from stray
pointers in other programs. A bad pointer can’t address anything outside the process address
space. This is good news, compared to the horrible situation under windows 3.1 or even
MSDOS, where a bad pointer would not only destroy the data of the application where it
belonged, but destroyed data of other applications, making the whole system unstable. But this
means too, that applications can’t share data by sending just pointers around. A pointer is
meaningful only in the application where it was created. Special mechanisms are needed (and
provided by Windows) to allow sharing of data between applications.
73. Since this is stored in a 32 bit integer, the counter will overflow somewhere in year 2038. I hope I
will be around to celebrate that event…
130 C programming with lcc-win32
1 Since the amount of memory allocated to the program is fixed, it is not possible to adapt
memory consumption to the actual needs of the program. The static buffers could be either
over-dimensioned, wasting memory space, or not enough to hold the data needed. Since the
static buffers must be patterned after the biggest possible input, they will be over-
dimensioned for the average case.
2 Unless programming is adapted to this strategy, it is difficult to reuse memory being used
in different buffers to make space for a temporary surge in the space needs of the program.
Advantages:
1 Fewer calls to memory allocation/deallocation routines.
2 No global fragmentation of memory.
Drawbacks:
1 Since the size of the memory that will be needed is not known in advance, once an arena
is full, the strategy fails or needs to be complemented with more sophisticated variations. A
common solution is to make the arena a linked list of blocks, what needs a small processing
overhead.
2 Determining when the moment has come to release all memory is tricky unless the data
processed by the program has a logical structure that adapts itself to this strategy. Since
there is no way of preserving data beyond the frontier where it is released, data that is to be
preserved must be copied into another location.
some data structure, the next time it will be used the program can catastrophically fail or
return invalid results, depending on whether the block was reallocated or not.
5 It can be slow. Malloc/free was a big bottleneck for performance using the Microsoft C
runtime provided by the windows system for windows 95/98, for instance.
2 You are supposed to store the pointers in memory accessible to the collector. If you store
pointers to memory allocated by the collector in files, for instance, or in the “windows extra
bytes” structure maintained by the OS, the collector will not see them and it will consider
the memory they point to as free, releasing them again to the application when new
requests are done.
3 Whenever a full gc is done (a full scan of the stack and the heap), a noticeable stop in
program activity can be perceived by the user. In normal applications this can take a bit less
than a second in large memory pools. The collector tries to improve this by doing small
partial collections each time a call to its allocator function is done.
4 If you have only one reference to a block, the block will be retained. If you have stored
somewhere a pointer to a block no longer needed, it can be very difficult indeed to find it.
5 The garbage collector of lcc-win32 is a conservative one, i.e. if something in the stack
looks like a pointer, it will be assumed that this is a pointer (fail-safe) and the memory
block referenced will be retained. This means that if by chance you are working with
numeric data that contains numbers that can be interpreted as valid memory addresses more
memory will be retained than strictly necessary. The collector provides special APIs for
allocating tables that contain no pointers and whose contents will be ignored by the
collector. Use them to avoid this problems.
ip = (int *) r;
// At the start of the block we write the signature
*ip++ = SIGNATURE;
// Then we write the size of the block in bytes
*ip++ = (int) size;
// We zero the data space
memset(ip, 0, size - 3*sizeof(int));
// We write the magic number at the end of the block, just behind the data section
ip = (int *) (&r[size - sizeof(int)]);
*ip = MAGIC;
// Return a pointer to the start of the data area
return (r + 2 * sizeof(int));
}
void release(void *pp)
{
register int *ip = NULL;
int s;
register char *p = pp;
if (p == NULL) // Freeing NULL is allowed
return;
// The start of the block is two integers before the data.
p -= 2 * sizeof(int);
ip = (int *) p;
if (*ip == SIGNATURE) {
// Overwrite the signature so that this block can’t be freed again
*ip++ = 0;
s = *ip;
ip = (int *) (&p[s - sizeof(int)]);
if (*ip != MAGIC) {
ErorPrintf(“Overwritten block size %d”, s);
return;
}
*ip = 0;
AllocatedMemory -= s;
free(p);
}
else {
/* The block has been overwritten. Complain. */
ErrorPrintf(“Wrong block passed to release”);
}
}
The allocate function adds to the requested size space for 3 integers.
1) The first is a magic number (a signature) that allows the identification of this block as a
block allocated by our allocation system.
2) The second is the size of the block. After this two numbers, the data follows.
3) The data is followed by a third number that is placed at the end of the block. Any memory
overwrite of any block will overwrite probably this number first. Since the “release”
function check for this, we will be able to detect when a block has been overwritten.
At any time, the user can ask for the size of total allocated memory (valid blocks in circula-
tion) by querying the AllocatedMemory variable.
The “release function” accepts NULL (that is ignored). If the pointer passed to it is not NULL,
it will check that it is a valid block, and that the signature is still there, i.e. that no memory
overwrites have happened during the usage of the block.
Counting words 135
2) If the character starts a word, scan the word and store it. Each word is stored once. If it is in
the table already, the count is incremented for that word, otherwise it is entered in the table.
Now, we go to the more difficult task of scanning a word into the computer. The algorithm is
simple: we just keep reading characters until we find a non-word char, that stops our loop. We
use a local array in the stack that will hold until MAXIDLENGTH chars.
#define MAXIDLENGTH 512
int ScanWord(int firstchar,FILE *f)
{
int i = 1, // index for the word buffer
c=0; // Character read
char idbuf[MAXIDLENGTH+1]; // Buffer for the word
1) It should provide a fast access to a word to see if a given sequence of characters is there
already.
Our word table is a sequence of lists of words. Each list is longer or shorter, depending on the
hash function that we use and how good our hash function randomizes the input. If we use a
table of 65535 positions (slots) and a good hash algorithm we divide the access time by 65535,
not bad.
To enter something into our table we hash the word into an integer, and we index the slot in the
table. We then compare the word with each one of the words in the list of words at that slot. If
we found it, we do nothing else than increment the count of the word. If we do not find it, we
add the word at the start of that slot.
Note that this requires that we define a structure to hold each word and its associated count.
Since all the words are in a linked list, we could use the following structure, borrowing from
the linked list representation discussed above:
typedef struct _WordList {
int Count;
struct _WordList *Next;
char Word[];
} WORDLIST;
We have an integer that holds the number of times this word appears in the text, a pointer to
the next word in the list, and an unspecified number of characters just following that pointer.
This is a variable sized structure, since each word can hold more or less characters. Note that
variable sized structures must have only one “flexible” member and it must be at the end of the
definition.
Our “EnterWord” function can look like this:
void EnterWord(char *word)
{
int h = hash(word); // Get the hash code for this word
WORDLIST *wl = WordTable[h]; // Index the list at that slot
while (wl) { // Go through the list
if (!strcmp(wl->Word,word)) {
wl->Count++; // Word is already in the table.
return; // increment the count and return
}
wl = wl->Next; // Go to the next item in the list
}
// Here we have a new word, since it wasn’t in the table.
// Add it to the table now
wl = NewWordList(word);
wl->Next = WordTable[h];
WordTable[h] = wl;
}
138 C programming with lcc-win32
}
mem->used = 0;
memoryused += MEM_ALLOC_SIZE;
mem->size = MEM_ALLOC_SIZE;
}
result = mem->memory+mem->used;
mem->used += siz;
memset(result,siz,0);
memoryused += siz;
return result;
}
We use a static pointer to a MEMORY structure to hold the location of the current memory
chunk being used. Since it is static it will be initialized to NULL automatically by the compiler
and will keep its value from one call to the next. We test before using it, if the chunk has
enough room for the given memory size we want to allocate or if it is NULL, i.e. this is the
very first word we are entering. If either of those if true, we allocate a new chunk and initialize
its fields.74
Otherwise we have some room in our current chunk. We increase our counters and return a
pointer to the position within the “memory” field where this chunk starts. We clean the mem-
ory with zeroes before returning it to the calling function.
Note that we do not keep any trace of the memory we have allocated so it will be impossible to
free it after we use it. This is not so bad because the operating system will free the memory
after this program exists. The downside of this implementation is that we can’t use this pro-
gram within another one that would call our word counting routine. We have a memory leak
“built-in” into our software.
A way out of this is very easy though. We could just convert our mem structures into a linked
list, and free the memory at the end of the program.
We increment this counter when we enter a new word, i.e. in the function NewWordList. 75
We will need a comparison function for the qsort library function too.
int comparewords(const void *w1,const void *w2)
{
WORDLIST *pw1 = *(WORDLIST **)w1,*pw2 = *(WORDLIST **)w2;
74. Note that we allocate MEM_ALLOC_SIZE bytes. If we want to change to more or less bytes, we
just change the #define line and we are done with the change.
140 C programming with lcc-win32
if (pw1->Count == pw2->Count)
return strcmp(pw1->Word,pw2->Word);
return pw1->Count - pw2->Count;
}
Note that we have implemented secondary sort key. If the counts are the same, we sort by
alphabetical order within a same count.
void DoReports(char *filename)
{
int i;
int idx = 0; // Index into the resulting table
while (wl) {
// look at the list at this slot
tab[idx] = wl;
wl = wl->Next;
idx++;
if (idx >= words && wl) {
fprintf(stderr,"program error\n");
exit(EXIT_FAILURE);
}
}
}
// Sort the table
qsort(tab,words,sizeof(WORDLIST *),comparewords);
// Print the results
for (i=0; i< words;i++) {
printf("%s %5d\n",tab[i]->Word,tab[i]->Count);
}
}
We start by printing the name of the file and the number of different words found. Then, we go
through our hash table, adding a pointer to the word list structure at each non-empty slot.
Note that we test for overflow of the allocated table. Since we increment the counter each time
that we add a word, it would be very surprising that the count didn’t match with the number of
items in the table. But it is better to verify this.
After filling our table for the qsort call, we call it, and then we just print the results.
75. Global variables like this should be used with care. Overuse of global variables leads to problems
when the application grows, for instance in multi-threaded applications. When you got a lot of global
variables accessed from many points of the program it becomes impossible to use threads because the
danger that two threads access the same global variable at a time.
Another problem is that our global is not static, but visible through the whole program. If somewhere else
somebody writes a function called “words” we are doomed. In this case and for this example the glo-
bal variable solution is easier, but not as a general solution.
Time and Date functions 141
76. This clock will overflow in something like 2.000 years so be prepared for windows 4.000!
142 C programming with lcc-win32
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
The fields are self-explanatory. The structure “timeb” is defined in the directory include\sys,
as follows:
struct timeb {
time_t time;
unsigned short pad0;
unsigned long lpad0;
unsigned short millitm; // Fraction of a second in ms
unsigned short pad1;
unsigned long lpad1;
// Difference (minutes), moving westward, between UTC and local time
short timezone;
unsigned short pad2;
unsigned long lpad2;
// Nonzero if daylight savings time is currently in effect for the local time zone.
short dstflag;
};
We show here a small program that displays the different time settings.
#include <time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <string.h>
void main()
{
char tmpbuf[128], ampm[] = "AM";
time_t ltime;
struct _timeb tstruct;
struct tm *today, *gmt, xmas = { 0, 0, 12, 25, 11, 93 };
To convert from the ANSI C time format to the Windows time format you can use the follow-
Function Purpose
CompareFileTime Compares two 64-bit file times
DosDateTimeToFileTime Converts MS-DOS date and time values to a 64-bit file time.
FileTimeToDosDateTime Converts a 64-bit file time to MS-DOS date and time values.
Converts a file time based on the Coordinated Universal Time
FileTimeToLocalFileTime
(UTC) to a local file time.
FileTimeToSystemTime Converts a 64-bit file time to system time format
Retrieves the date and time that a file was created, last accessed, and
GetFileTime
last modified.
GetLocalTime Retrieves the current local date and time.
GetSystemTime Retrieves the current system date and time.
Determines whether the system is applying periodic time
GetSystemTimeAdjustment adjustments to its time-of-day clock at each clock interrupt, along
with the value and period of any such adjustments.
Obtains the current system date and time. The information is in
GetSystemTimeAsFileTime
Coordinated Universal Time (UTC) format.
Retrieves the number of milliseconds that have elapsed since the
GetTickCount
system was started. It is limited to the resolution of the system timer.
Retrieves the current time-zone parameters. These parameters
GetTimeZoneInformation control the translations between Coordinated Universal Time (UTC)
and local time.
Converts a local file time to a file time based on the Coordinated
LocalFileTimeToFileTime
Universal Time (UTC).
Sets the date and time that a file was created, last accessed, or last
SetFileTime
modified.
SetLocalTime Sets the current local time and date.
Sets the current system time and date. The system time is expressed
SetSystemTime
in Coordinated Universal Time (UTC).
Tells the system to enable or disable periodic time adjustments to its
SetSystemTimeAdjustment
time of day clock.
Sets the current time-zone parameters. These parameters control
SetTimeZoneInformation
translations from Coordinated Universal Time (UTC) to local time.
SystemTimeToFileTime Converts a system time to a file time.
Converts a Coordinated Universal Time (UTC) to a specified time
SystemTimeToTzSpecificLocalTime
zone's corresponding local time.
ing function:
#include <winbase.h>
#include <winnt.h>
#include <time.h>
UnixTimeToFileTime(t, &ft);
FileTimeToSystemTime(&ft, pst);
}
1.31.1 Lists
Lists are members of a more general type of objects called sequences, i.e. objects that have a
natural order. You can go from a given list member to the next element, or to the previous one.
We have several types of lists, the simplest being the single-linked list, where each member
contains a pointer to the next element, or NULL, if there isn’t any. We can implement this
structure in C like this:
typedef struct _list {
struct _list *Next; // Pointer to next element
void *Data;// Pointer to the data element
} LIST;
We can use a fixed anchor as the head of the list, for instance a global variable containing a
pointer to the list start.
LIST *Root;
We define the following function to add an element to the list:
LIST *Append(LIST **pListRoot, void *data)
{
LIST *rvp = *pListRoot;
if (list == NULL)
return NULL;
if (list == element)
return list;
tmp = list->Next;
list->Next = element;
if (element) {
element->Next = tmp;
}
return list;
}
We test for different error conditions. The first and most obvious is that “list” is NULL. We
just return NULL. If we are asked to insert the same element to itself, i.e. “list” and “element”
are the same object, their addresses are identical, we refuse. This is an error in most cases, but
maybe you would need a circular element list of one element. In that case just eliminate this
test.
Note that Insert(list, NULL); will effectively cut the list at the given element, since
all elements after the given one would be inaccessible.
Using structures (continued) 147
Many other functions are possible and surely necessary. They are not very difficult to write,
the data structure is quite simple.
Double linked lists have two pointers, hence their name: a Next pointer, and a Previous
pointer, that points to the preceding list element.
Our data structure would look like this:
typedef struct _dlList {
struct _dlList *Next;
struct _dlList *Previous;
void *data;
} DLLIST;
Our “Append” function above would look like: (new material in bold)
LIST *AppendDl(DLLIST **pListRoot, void *data)
{
DLLIST *rvp = *pListRoot;
if (list == NULL)
return NULL;
if (list == element)
return list;
tmp = list->Next;
list->Next = element;
if (element) {
element->Next = tmp;
element->Previous = list;
if (tmp)
tmp->Previous = element;
}
return list;
}
Note that we can implement a Previous function with single linked lists too. Given a pointer to
the start of the list and an element of it, we can write a Previous function like this:
148 C programming with lcc-win32
{
int h = (table->hashfn)(str);
LIST *slotp = table->Table[h % HASHELEMENTS];
while (slotp) {
if (!strcmp(str,(char *)slotp->data)) {
return slotp;
}
slotp = slotp->Next;
}
return Append(&table->Table[h % HASHELEMENTS],element);
}
All those casts are necessary because we use our generic list implementation with a void
pointer. If we would modify our list definition to use a char * instead, they wouldn’t be neces-
sary.
We first call the hash function that returns an integer. We use that integer to index the table in
our hash table structure, getting the head of a list of strings that have the same hash code. We
go through the list, to ensure that there isn’t already a string with the same contents. If we find
the string we return it. If we do not find it, we append to that list our string
The great advantage of hash tables over lists is that if our hash function is a good one, i.e. one
that returns a smooth spread for the string values, we will in average need only n/128 compar-
isons, n being the number of elements in the table. This is an improvement over two orders of
magnitude over normal lists.
150 C programming with lcc-win32
while(0)
Note the \ that continues this long line, and the absence of a semicolon at the end of the macro.
An “#undef” statement can undo the definition of a symbol. For instance
#undef PI
will erase from the pre-processor tables the PI definition above. After that statement the iden-
tifier PI will be ignored by the preprocessor and passed through to the compiler.
The second form of pre-processor instructions that is important to know is the
#if (expression)
… program text …
#else
… program text …
#endif
or the pair
#ifdef (symbol)
#else
#endif
When the preprocessor encounters this kind of directives, it evaluates the expression or looks
up in its tables to see if the symbol is defined. If it is, the “if” part evaluates to true, and the
text until the #else or the #endif is copied to the output being prepared to the compiler. If it is
NOT true, then the preprocessor ignores all text until it finds the #else or the #endif. This
allows you to disable big portions of your program just with a simple expression like:
#if 0
…
#endif
This is useful for allowing/disabling portions of your program according to compile time
parameters. For instance, lcc-win32 defines the macro __LCC__. If you want to code some-
thing only for this compiler, you write:
#ifdef __LCC__
… statements …
#endif
Note that there is no way to decide if the expression:
SomeFn(foo);
Is a function call to SomeFn, or is a macro call to SomeFn. The only way to know is to read
the source code. This is widely used. For instance, when you decide to add a parameter to Cre-
ateWindow function, without breaking the millions of lines that call that API with an already
fixed number of parameters you do:
#define CreateWindow(a,b, … ) CreateWindowEx(0,a,b,…)
This means that all calls to CreateWindow API are replaced with a call to another routine that
receives a zero as the new argument’s value.
It is quite instructive to see what the preprocessor produces. You can obtain the output of the
preprocessor by invoking lcc with the –E option. This will create a file with the extension.i
(intermediate file) in the compilation directory. That file contains the output of the preproces-
sor. For instance, if you compile hello.c you will obtain hello.i.
1) Macros
3) Pragma instructions
int fn(int a)
{
// some code
}
If you have the idea of defining a macro like this
#define fn 7987
the definition above will be transformed in
int 7987(int a)
{
}
A closer look at the pre-processor 153
not exactly what you would expect. This can be avoided by #undefining the macros that you
fear could clash with other identifiers in the program.
When expanded, this macro will add a semicolon into the text, with the consequence of a
cascade of syntax errors with apparently no reason.
2) Watch for side effects within macros. A macro invocation is similar to a function call, with
the big difference that the arguments of the function call is evaluated once but in the macro
can be evaluated several times. For instance we have the macro “square”
#define square(a) (a*a)
If we use it like this:
b = square(a++);
After expansion this will be converted into:
b = (a++)*(a++);
and the variable a will be incremented twice.
156 C programming with lcc-win32
1) Build a list or table containing each file found, and return those results to the user.
2) For each file that is found, you call a user provided function that will receive the name of
the file found. The user decides what does he/she want to do with the file.
Note that solution 2 includes solution 1, since the user can write a function that builds the list
or table in the format he wants, instead of in a predefined format. Besides, there are many
options as to what information should be provided to the user. Is he interested in the size of the
file? Or in the date? Who knows. You can’t know it in advance, and the most flexible solution
is the best.
We can implement this by using a function pointer that will be called by the scanning function
each time a file is found. We define
typedef int (*callback)(char *);
This means, “a function pointer called callback that points to a function that returns an int and
receives a char * as its argument”. This function pointer will be passed to our scanning func-
tion and will return non-zero (scanning should be continued) or zero (scanning should stop).
Here is a possible implementation of this concept:
#include <stdio.h>
#include <windows.h>
#include <direct.h>
// Here is our callback definition
typedef int(*callback)(char *);
// This function has two phases. In the first, we scan for normal files and ignore any
// directories that we find. For each file that matches we call the given function pointer.
// The input, the char * “spec” argument should be a character string like “*.c” or “*.h”.
// If several specifications are needed, they should be separated by the ‘;’ semi colon char.
// For instance we can use “*.c;*.h;*.asm” to find all files that match any of those
// file types. The second argument should be a function that will be called at each file
// found.
int ScanFiles(char *spec,callback fn)
{
char *p,*q; // Used to parse the specs
char dir[MAX_PATH]; // Contains the starting directory
char fullname[MAX_PATH];// will be passed to the function
HANDLE hdir;
HANDLE h;
WIN32_FIND_DATA dirdata;
WIN32_FIND_DATA data;
// Get the current directory so that we can always come back to it after calling
// recursively this function in another dir.
memset(dir,0,sizeof(dir));
getcwd(dir,sizeof(dir)-1);
// This variable holds the current specification we are using
q = spec;
// First pass. We scan here only normal files, looping for each of the specifications
// separated by ‘;’
do {
// Find the first specification
Using function pointers 157
p = strchr(q,';');
// Cut the specification at the separator char.
if (p)
*p = 0;
h = FindFirstFile(q,&data);
if (h != INVALID_HANDLE_VALUE) {
do {
if (!(data.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY)) {
// We have found a matching file. Call the user’s function.
sprintf(fullname,"%s\\%s",dir,data.cFileName);
if (!fn(fullname))
return 0;
}
} while (FindNextFile(h,&data));
FindClose(h);
}
// Restore the input specification. It would be surprising for the user of this
// application that we destroyed the character string that was passed to
// this function.
if (p)
*p++ = ';';
// Advance q to the next specification
q = p;
} while (q);
// OK. We have done all the files in this directory. Now look if there are any
// subdirectories in it, and if we found any, recurse.
hdir = FindFirstFile("*.*",&dirdata);
if (hdir != INVALID_HANDLE_VALUE) {
do {
if (dirdata.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY) {
// This is a directory entry. Ignore the “.” and “..” entries.
if (! (dirdata.cFileName[0] == '.' &&
(dirdata.cFileName[1] == 0 ||
dirdata.cFileName[1] == '.'))) {
// We change the current dir to the subdir and recurse
chdir(dirdata.cFileName);
ScanFiles(spec,fn);
// Restore current directory to the former one
chdir(dir);
}
}
} while (FindNextFile(hdir,&dirdata));
FindClose(hdir);
}
return 1;
}
This function above could be used in a program like this:
static int files; // used to count the number of files seen
// This is the callback function. It will print the name of the file and increment a counter.
int printfile(char *fname)
{
printf("%s\n",fname);
files++;
return 1;
}
// main expects a specification, and possibly a starting directory. If no starting directory
// is given, it will default to the current directory.
158 C programming with lcc-win32
// Note that many error checks are absent to simplify the code. No validation is done to the
// result of the chdir function, for instance.
int main(int argc,char *argv[])
{
char spec[MAX_PATH];
char startdir[MAX_PATH];
if (argc == 1) {
printf(“scan files expects a file spec\n”);
return 1;
}
memset(startdir,0,sizeof(startdir));
memset(spec,0,sizeof(spec));
strncpy(spec,argv[1],sizeof(spec)-1);
if (argc > 2) {
strcpy(startdir,argv[2]);
}
if (startdir[0] == 0) {
getcwd(startdir,sizeof(startdir)-1);
chdir(startdir);
}
files = 0;
ScanFiles(spec,printfile);
printf("%d files\n",files);
return 0;
}
What is interesting about this solution, is that we use no intermediate memory to hold the
results. If we have a lot of files, the size of the resulting list or table would be significant. If an
application doesn’t need this, it doesn’t have to pay for the extra overhead.
Using the name of the file, the callback function can query any property of the file like the date
of creation, the size, the owner, etc. Since the user writes that function there is no need to give
several options to filter that information.
One of the big advantages of C is its ability of using function pointers as first class objects that
can be passed around, and used as input for other procedures. Without this feature, this appli-
cation would have been difficult to write and would be a lot less flexible.
Note too that any error in the function pointer argument will provoke a crash, since we do not
test for the validity of the received function pointer.
But, let’s be clear: the biggest drawback is that the user has to write a function in C. Imagine
telling your friends that before they use your program they just write a function in C, compile
it, link it with your stuff, etc. Let’s examine how we put function pointers to good use, assume
the following Problem:
Write a program to print all numbers from 1 to n where n integer > 0 without using any control
flow statements (switch, if, goto, while, for, etc). Numbers can be written in any order.
Solution:
The basic idea is to use a table of function pointers as a decision table. This can be done like
this:
#include <stdio.h>
#include <stdlib.h>
void zero(int a)
{
exit(EXIT_SUCCESS); // This terminates recursion
}
void greaterZero(int a)
{
printf("%d\n",a--);
callbackTable[a>0](a); // recurse with a-1
}
There are many other uses of function tables, for instance in object oriented programming
each object has a function table with the methods it supports.
Since function pointers can be assigned during the run time, this allows to change the code to
be run dynamically, another very useful application. We have come up with a decent solution
let’s see what other might do:
#include <stdio.h>
void print(int n)
{
n && (print(n-1), 1) && printf("%d\n", n);
}
The operator “+” can’t be used for dates. There is nothing that can be assigned to 16/July/1970
+23/December/1980. The operation “+” has no sense for dates. The same applies to multipli-
cation and division.
Subtraction is a legal operation for dates, but the result type is not a date but a dimensionless
number that represents a time interval (in days, hours, etc). The only overloaded operator that
makes sense then is:
int operator-(DATE d1, DATE d2) { ... }
Operator overloading is best done for numbers, and similar objects. This facility allows you to
implement any kind of numbers you would like to design, it is fully general. Just look at
qfloat.h and see what would happen with qfloat being any other number type you want.
Note too, that nothing has changed about C. There are no classes or any other underlying con-
text, and you are free to build the context you wish, as always C has been.
1.34.2 References
References are a special kind of pointers that are always dereferenced when used. When you
declare a reference, you must declare immediately the object they point to. There are no
invalid references since they can’t be assigned. Once a reference is declared and initialized,
you can’t reassign them to another object.
They are safer pointers than normal pointers, since they are guaranteed correct, unless the
object they point to is destroyed, of course. References are initialized with the construct:
int a;
int &pa = a;
The “pa” variable is a reference to an integer (an “int &”), and it is immediately initialized to
point to the integer “a”. Note that you do not have to take the address of “a”, but just put its
name. The compiler takes the address.
Again, here is a short pointer only. A complete description is found in the user manual.
*p = 0;
printf(“This will never be reached\n”);
return 0;
}
There is no way you can catch this trap, and try to recover, or, at least, exit the program with
an error message.
This means that there isn’t any way for you to prevent your program from failing catastrophi-
cally at the smallest error. It suffices to have a bad pointer somewhere and you are doomed. If
you use a third party library you have to trust it 100%, meaning that the slightest problem in
the library will provoke the end of your application.
The whole application is fragile and very difficult to trust. A small error in ten thousands of
lines is almost inevitable, as you may know...
Of course there is already an exception handling mechanism. When a machine trap occurs,
windows displays the well known “This application...” message and shuts down the program.
Couldn’t the system make something better?
Well, it can do something better, and it does. Structured exception handling within lcc-win32
is built using the general exception handling mechanism of windows. It has been around since
the beginning of the win32 API, i.e. 1995.
79. This is an extension of Microsoft, and is not part of the standard language. It is supported by many
compilers under windows, but not by all of them.
164 C programming with lcc-win32
{
char *p = NULL;
__try {
*p = 0;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
printf("Ooops, there was a problem with this program.\n");
printf("Please call the maintenance team at 0-800-XXX\n");
}
return 0;
}
This will print
D:\lcc\mc38\test>test
Ooops, there was a problem with this program.
Please call the maintenance team at 0-800-XXX
If we change it to:
#include <stdio.h>
#include <seh.h>
int main(void)
{
char p[10];
__try {
*p = 0;
printf("No exceptions\n");
}
__except(EXCEPTION_EXECUTE_HANDLER) {
printf("Ooops, there was a problem with this program.\n");
printf("Please call the maintenance team at 0-800-XXX\n");
}
return 0;
}
This will now print:
No exceptions
We have three parts here.
The first one is the protected code block, enclosed by __try { }. This block can contain code
that provokes exceptions. To leave it prematurely you use the __leave expression.
The second part is an integer expression that appears between parentheses after the __except
keyword. This expression should eventually evaluate to one of the three constants defined in
the <seh.h> header file.
EXCEPTION_EXECUTE_HANDLER (1) means that the code in the following expression should be
executed. EXCEPTION_CONTINUE_SEARCH (0) means that the system should go on looking for
another handler. EXCEPTION_CONTINUE_EXECUTION (-1) means that the system should attempt
to go on at the same point in the program where the exception happened.
In this case we decided that any exception would provoke the execution of the block following
the __except().
{
char *p=NULL;
unsigned code;
__try {
*p = 0;
printf("No exceptions\n");
}
__except(code=GetExceptionCode(),EXCEPTION_EXECUTE_HANDLER) {
printf("Ooops, there was a problem with this program.”);
printf("Error code: %#x\n",code);
}
return 0;
}
This will print:
Ooops, there was a problem with this program.
Error code: 0xc0000005
We use a comma expression within the except() to catch the value of the exception code into a
local variable. The comma expression returns EXCEPTION_EXECUTE_HANDLER as its
result, with the side effect of storing the exception code in a local variable.
In other compilers, the GetExceptionCode() function can be called only within an except
expression. Lcc-win32 let’s you call it anywhere you want, it will always return the exception
code of the last exception that was executed, or zero, if there weren’t any.
Another useful function is
void RaiseException(unsigned code,unsigned flags,
unsigned nbArgs,unsigned *args);
We can use it to call the exception handling mechanism in the same way as a real exception
would. Example:
#include <stdio.h>
#include <seh.h>
int main(void)
{
char p[10];
unsigned code;
__try {
*p = 0;
printf("No exceptions\n");
RaiseException(0xdeadbeef,0,0,NULL);
}
__except(code=GetExceptionCode(),EXCEPTION_EXECUTE_HANDLER) {
printf("Ooops, there was a problem with this program.\n");
printf("Error code: %#x\n",code);
}
return 0;
}
This will print:
No exceptions
Ooops, there was a problem with this program.
Error code: 0xceadbeef
Note that the system will change the exception code from 0xdeadbeef to 0xceadbeef.
Why?
The explanation is in the MSDN site: 80
I quote:
166 C programming with lcc-win32
To make sure that you do not define a code that conflicts with an existing exception code, set
the third most significant bit to 1. The four most-significant bits should be set as shown in the
following table.
You can set the first two bits to a setting other than 11 binary if you want, although the error
setting is appropriate for most exceptions. The important thing to remember is to set bits 29
and 28 as shown in the previous table.
End quote.
Note that nowhere will be said that the actual code will be changed by the operating system.
Just recommendations... But anyway. Follow this schema and you (may) be happy with it.
argstable[0] = (unsigned)__func__;
RaiseException(0xe0000001,0,1,argstable);
return 0; // Not reached
}
int main(void)
{
unsigned code;
__try {
fn();
}
__except(code=GetExceptionCode(),EXCEPTION_EXECUTE_HANDLER) {
EXCEPTION_POINTERS *ex = GetExceptionInformation();
printf("Error code: %#x\n",code);
printf("In function %s\n",
80. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore98/html/
_core_raising_software_exceptions.asp.
Advanced C programming with lcc-win32 167
(char *)ex->ExceptionRecord->ExceptionInformation[0]);
}
return 0;
}
This will print:
Error code: 0xe0000001
In function fn
The keyword __func__ will be substituted by the name of the current function by the compiler.
We put that name in the first parameter of the table. We call then RaiseException() with 1
argument and our table. The exception handling code will get the exception information, and
print the first argument.
The function GetExceptionInformation() returns a pointer to a copy of the exception informa-
tion received by the system by the lcc-win32 runtime. The structure EXCEPTION_POINTERS
contains two pointers, the first points to an EXCEPTION_RECORD, and the second to a CONTEXT
structure containing the machine registers at the time of the exception. In the example we use
the exception record to get to the parameters array, and we print the first one.
Nowhere in the shown code there is a test to verify that the exception code is the one we are
expecting. If another exception had happened that has no parameters, or a different set of
parameters we would have crashed.
Overflow: 4
Overflow: 5
Overflow: 6
Overflow: 7
Overflow: 8
Overflow: 9
Overflow: 10
Exception handler C00000FD
Overflow: 0
Overflow: 1
Overflow: 2
Overflow: 3
Overflow: 4
Overflow: 5
Overflow: 6
Overflow: 7
Overflow: 8
Overflow: 9
Overflow: 10
We see that the first time the handler is invoked, but the second time there is no handler
invoked, the program exits abruptly without any further messages.
The reason for this behavior is that if a thread in your application causes an
EXCEPTION_STACK_OVERFLOW, then your thread has left its stack in a damaged state. This is in
contrast to other exceptions such as EXCEPTION_ACCESS_VIOLATION or
EXCEPTION_INT_DIVIDE_BY_ZERO, where the stack is not damaged. This is because the stack
is set to an arbitrarily small value when the program is first loaded, just 4096 bytes, 1 “page”.
The stack then grows on demand to meet the needs of the thread. This is implemented by plac-
ing a page with PAGE_GUARD access at the end of the current stack. When your code causes the
stack pointer to point to an address on this page, an exception occurs. The system then does
the three following things:
1 Remove the PAGE_GUARD protection on the guard page, so that the thread can read
and write data to the memory.
2 Allocate a new guard page that is located one page below the last one.
3 Rerun the instruction that raised the exception.
If a thread in your program grows the stack beyond its limit (1MB with normal programs), the
step 1 above will succeed, but the step 2 will fail. The system generates an exception and the
first try block is executed. This is OK, but the stack has been left without a guard page. Since
the second time that we get a stack overflow there is no more stack left, the program will pro-
voke an access violation when it attempts to use the stack within the code of the exception
handling procedure __except_handler3, that receives the exception from windows. This will
provoke the dreaded double fault exception that terminates immediately the program no ques-
tions asked. Note that not even the “this program has attempted an illegal operation” dialog
box appears.
The correct way of handling stack overflow is then, to do the following:
1 Get the page size from the system.
2 Set the page to guard page again
An example is given below:
#include <intrinsics.h> // For _GetStackPointer()
int main(int argc, char* argv[])
{
for (;;)
Advanced C programming with lcc-win32 169
{
__try {
StackOverflow(0);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
LPBYTE lpPage;
static SYSTEM_INFO si;
static MEMORY_BASIC_INFORMATION mi;
static DWORD dwOldProtect;
__try {
*p ='A';
printf("%s\n",p);
}
__except(EXCEPTION_EXECUTE_HANDLER) {
p = "abc";
__retry;
}
return 0;
}
170 C programming with lcc-win32
The first execution of the __try yields an exception since the pointer p is NULL. Within the
except block we correct this condition by assigning to that pointer a valid value, then we
restart with the __retry. The output of this program is:
Abc
// This function will be called by the signal mechanism when a SIGSEGV is raised.
void traphandler(int s)
{
printf("Illegal address exception\n");
exit(EXIT_FAILURE);
}
int main(void)
{
char *p = NULL;
oldhandler = signal(SIGSEGV,traphandler);
if (oldhandler == SIG_ERR) {
printf("Impossible to establish a signal handler\n");
exit(EXIT_FAILURE);
}
Advanced C programming with lcc-win32 171
*p = 0;
signal(SIGSEGV,oldhandler);
return 0;
}
This code tests if the return value of the signal() call is OK. Besides, before the function exists,
the old handler will be restored.
The software signals that lcc-win32 supports are described in the standard header file <sig-
nal.h>. They include SIGSEGV for illegal addresses, SIGFPE (floating point exceptions),
and others. In general the signal() mechanism is barely supported by the operating system
under Windows.
// This function will call longjmp to reestablish a previous context assumed in the jumpbuffer
// global variable.
void traphandler(int s)
{
psignal(s,”Error”);
longjmp(jumpbuffer,1);
}
int main(void)
{
char *p = NULL;
void (*oldhandler)(int);
oldhandler = signal(SIGSEGV,traphandler);
if (oldhandler == SIG_ERR) {
printf("Impossible to establish a signal handler\n");
exit(EXIT_FAILURE);
}
if (setjmp(jumpbuffer)) {
;
}
else {
*p = 0;
}
signal(SIGSEGV,oldhandler);
printf("Normal exit\n");
return 0;
}
This will print:
172 C programming with lcc-win32
81. The psignal() function is not part of the standard but it exists in many implementation, specially in
UNIX systems.
Numerical programming 173
2 3 4 5 6 7 8
x x x x x x x 9
exp ( x ) = 1 + ----- + ----- + ------ + --------- + --------- + ------------ + --------------- + O ( x )
2 6 24 120 720 5040 40320
We have to stop somewhere. No way out. Here we get tired at the 9th term. And no matter how
much effort we put into this, there will be always a truncation error. In this case the truncation
error can be accurately calculated. Analyzing and estimating the error bounds is the art of
numerical analysis.
Computers use bit patterns to represent any object that can be represented in a computer. In the
section about structures we represented a person by a bit pattern like this:
structure person {
char *Name;
int age;
...
};
A person is surely not a bit pattern. We use the bit pattern to abstract some characteristics of
the person we are interested in. Numbers aren’t different. They can be represented by a bit pat-
tern too. We can use 32 bits, what allows us to represented almost 4294967296 numbers. But
obviously there are more numbers (infinitely more) than that, so any representation will
always fail somewhere.
Any computation involving computer-representable numbers (that map onto the real-line) as
its arguments can potentially produce a result that lies in-between two representable numbers.
In that case, that number is rounded off to one of the grid-points. And this incurs round off
error. Any practical numerical analyst (one who uses a computer to carry out numerical com-
putations) must have a method of bounding, estimating and controlling both the round off
error at each numerical step as well as the total round off error.
What do we want from the representation of numbers then?
2) The range (the span between the smallest and the largest number) should be as wide as
possible
5) The rules should be such that they can be implemented in hard-wired computer logic.
Note that all those requirements are completely contradictory. If we want a dense representa-
tion we have to use more bits. If we increase the range we have to thin the spacing between
numbers, etc.
Bits 0..51 contain the 52 bit fraction f, with bit 0 the least significant and bit 51 the most sig-
nificant; bits 52..62 contain the 11 bit biased exponent e, and bit 63 contains the sign bit s. The
normalized numbers are represented with:
(-1)s × 2e-1023 × 1.f
The bias for the exponent in this case is -1023. The range of this format is from 7fefffff
ffffffff to 00100000 00000000 in decimal from 1.7976931348623157e+308
to 2.2250738585072014e-308. This numbers are defined in float.h as DBL_MAX and
DBL_MIN respectively. The number of significant digits is 15, defined as DBL_DIG.
We have actually two types of NANs: quiet NANs and signalling NANs.
A Quiet NaN, when used as an operand in any floating point operation, quietly (that is without
causing any trap or exception) produces another quiet NaN as the result, which, in turn, propa-
gates. A Quiet NaN has a 1 set in the most significant bit-position in the mantissa field.
A Signaling NaN has no business inside an FPU. Its very presence means a serious error. Sig-
naling NaNs are intended to set off an alarm the moment they are fetched as an operand of a
floating point instruction. FPUs are designed to raise a trap signal when they touch a bit pat-
tern like that.
Quiet NaNs are produced, when you do things like try to divide by zero, or you pass incorrect
arguments to a standard FPU function, for instance taking the square root of -1. Modern FPUs
have the ability to either produce a quiet NaN, or raise a signal of some sort, when they
encounter such operands on such instructions. They can be initialized to do either of the two
options, in case the code runs into these situations. We will see this in more details in the
“exceptions” section further down.
1.35.2.1 Range
OK. We have seen how floating point numbers are stored in memory. To give us an idea of the
range and precision of the different formats let’s try this simple program. It calculates the fac-
torial of its argument, and it is not very efficient, since it will repeat all calculations at each
time.
#include <stdio.h>
#include <math.h>
float factf(float f)
{
float result=1.0;
while (f > 0) {
result *= f;
if (!isfinitef(f))
break;
f--;
}
return result;
}
int main(void)
{
float ff=1.0f,fctf = 1.0;
Numerical programming 177
while (1) {
ff = factf(fctf);
if (!isfinitef(ff))
break;
printf("%10.0f! = %40.21g\n",fctf,ff);
fctf++;
}
printf("Max factorial is %g\n",fctf-1);
return 0;
}
We start with the smallest format, the float format. We test for overflow with the standard
function is_finitef, that returns 1 if its float argument is a valid floating point number, zero oth-
erwise. We know that our fact() function will overflow, and produce a NAN (Not A Num-
ber) after some iterations, and we rely on this to stop the infinite loop. We obtain the following
output:
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178289152
15! = 1307674279936
16! = 20922788478976
17! = 355687414628352
18! = 6402374067290112
19! = 121645096004222980
20! = 2432902298041581600
21! = 51090945235216237000
22! = 1.124000724806013e+021
23! = 2.5852017444594486e+022
24! = 6.204483105838766e+023
25! = 1.5511209926324736e+025
26! = 4.032915733765936e+026
27! = 1.088886923454107e+028
28! = 3.0488839051318128e+029
29! = 8.8417606614675607e+030
30! = 2.6525290930453747e+032
31! = 8.2228384475874814e+033
32! = 2.631308303227994e+035
33! = 8.6833178760213554e+036
34! = 2.952328838437621e+038
Max factorial is 34
This measures the range of numbers stored in the float format, i.e. the capacity of this format
to store big numbers. We modify slightly our program by replacing the float numbers by
double numbers, and we obtain this:
1! = 1
2! = 2
3! = 6
4! = 24
178 C programming with lcc-win32
5! = 120
...
168! = 2.5260757449731988e+302
169! = 4.2690680090047056e+304
170! = 7.257415615308004e+306
Max factorial is 170
The range of double precision floats is much greater. We can go up to 170!, quite a big num-
ber. Even greater (as expected) is the range of long doubles, where we obtain:
1751 = 3.674156538617319512e+4920
1752 = 6.437122255657543772e+4923
1753 = 1.128427531416767423e+4927
1754 = 1.979261890105010059e+4930
Max factorial is 1754
Changing the declarations of the numbers to qfloats (and increasing the printing precision)
increases even more the range:
3207! = 2.68603536247213602472539298328381221236937795484607e+9853
3208! = 8.61680144281061236731906069037446957728096447914618e+9856
3209! = 2.76513158299792550867268657554116728734946150135801e+9860
Max factorial is 3209
The range increases by more than 3,000 orders of magnitude.
1.35.2.2 Precision
What is the precision of those numbers?
We modify the first program as follows:
int main(void)
{
float f=1.0f,fctf = 1.0;
fctf = factf(34.0f);
f = fctf+1.0f; // Add one to fctf
if (fctf != f) { // 1+fctf is equal to fctf ???
printf("OK\n");
}
else
printf("Not ok\n");
return 0;
}
We obtain the factorial of 34. We add to it 1. Then we compare if it is equal or not to the same
number. Against all mathematical expectations, our program prints “Not ok”. In floating
point maths, 1+N = N !!!
Why this?
The density of our format makes the gaps between one number and the next one bigger and
bigger as the magnitude of the numbers increases. At the extreme of the scale, almost at over-
flow, the density of our numbers is extremely thin. We can get an idea of the size of the gaps
by writing this:
int main(void)
{
float f=1.0f,fctf = 1.0;
fctf = factf(34.0f);
f = 1.0f;
while (fctf == (f+fctf)) {
f *= 10.0f;
Numerical programming 179
}
printf("Needs: %e\n",f);
return 0;
}
We get the output:
Needs: 1.000000e+019
We see that the gap between the numbers is huge: 1e19!
What are the results for double precision?
We modify our program and we get:
Needs: 1.000000e+019
What????
Why are the results of double precision identical to the floating point precision? We should
find that the smallest format would yield gaps much wider than the other, more precise format!
Looking at the assembler code generated by our floating point program, we notice:
; while (fctf == (f+fctf)) {
flds -16(%ebp) ; loads fctf in the floating point unit
fadds -4(%ebp) ; adds f to the number stored in the FPU
fcomps -16(%ebp) ; compares the sum with fctf
Looking at the manuals for the pentium processor we see that the addition is done using the
full FPU precision (80 bits) and not in floating point precision. Each number is loaded into the
FPU and automatically converted to a 80 bits precision number. We modify our program to
avoid this:
int main(void)
{
float f,fctf,sum;
fctf = factf(34.0f);
f = 1.0f;
sum = f+fctf;
while (fctf == sum) {
f *= 2.0f;
sum = fctf+f;
}
printf("Needs: %e\n",f);
return 0;
}
Note that this modified program is mathematically equivalent to the previous one. When we
run it, we obtain:
Needs: 1.014120e+031
OK, now we see that the gap between numbers using the float format is much bigger than the
one with double precision.
Note that both versions of the program are mathematically equivalent but numerically
completely different!
Note too that the results differ by 12 orders of magnitude just by modifying slightly the calcu-
lations.
We modify our program for double precision, and now we obtain:
Needs: 3.777893e+022
180 C programming with lcc-win32
The gap using double precision is much smaller (9 orders of magnitude) than with single pre-
cision.82
Using qfloats now, we write:
#include <qfloat.h>
#include <stdio.h>
int main(void)
{
qfloat f=34,fctf;
fctf = factq(f);
f = fctf+1;
if (fctf != f) {
printf("OK\n");
}
else
printf("Not ok\n");
return 0;
}
This prints OK at the first try. Using the extremely precise qfloat representation we obtain
gaps smaller than 1 even when the magnitude of the numbers is 10 34.
This extension of lcc-win32 allows you to use extremely precise representation only in the
places where it is needed, and revert to other formats when that precision is no longer needed.
82. Note that there are as many IEEE754 numbers between 1.0 and 2.0 as there are between 2^56 and
2^57 in double format. 2^57 - 2^56 is quite a big number: 72,057,594,037,927,936.
83. This is an unnamed structure within another structure or union. lcc-win32 and other compilers (like
visual C) allow to access the members of an unnamed union/structure without having to write:
t.u.sign,t.u.e,etc.
Numerical programming 181
int main(void)
{
number t;
t.fl = 178.125;
pfloat(t);
return 0;
}
This will produce the output:
Sign 0, exponent 134 (-127= 7), fraction: 01100100010000000000000
To calculate the fraction we do:
fraction = 01100100001 =
0 * 1/2 +
1 * 1/4 +
1 * 1/8 +
0 * 1/16+
0 * 1/32+
1 * 1/64 +
... +
1 * 1/1024
This is:
0.25+0.125+0.015625+0.0009765625 = 0.3916015625
Then, we add 1 to 0.3916015625 obtaining 1.3916015625.
This number, we multiply it by 2^7 = 128:
1,3916015625 * 128 = 178.125.
1) Round to the nearest grid point. This is the default setting when a program compiled with
lcc-win32 starts executing.
2) Round upwards. Choose always the next higher grid point in the direction of positive
infinity.
3) Round downwards. Choose always the next lower grid point in the direction of negative
infinity.
4) Round to zero. We choose always the next grid point in the direction of zero. If the number
is positive we round down, if it is negative we round up.
This rounding modes are defined in the standard header file fenv.h as:
/* Rounding direction macros */
#define FE_TONEAREST 0
#define FE_DOWNWARD 1
#define FE_UPWARD 2
#define FE_TOWARDZERO 3
You can change the default rounding precision by using the function fesetround(int),
also declared in the same header file.
182 C programming with lcc-win32
The rationale for this “rounding modes” is the following: To know if an algorithm is stable,
change your rounding mode using the same data and run your program in all rounding modes.
Are the results the same? If yes, your program is numerically stable. If not, you got a big prob-
lem and you will have to debug your algorithm.
For a total of N floating point operations you will have a rounding error of:84
4) For round to zero is -N * machine epsilon if the number is positive, N * Machine Epsilon if
the number is negative.
The number you actually obtain will depend of the sequence of the operations.
The machine epsilon is the smallest number that changes the result of an addition operation at
the point where the representation of the numbers is the densest. In IEEE754 representation
this number has an exponent value of the bias, and a fraction of 1. If you add a number smaller
than this to 1.0, the result will be 1.0. For the different representations we have in float.h:
#define FLT_EPSILON 1.19209290e-07F // float
#define DBL_EPSILON 2.2204460492503131e-16 // double
#define LDBL_EPSILON 1.084202172485504434007452e-19L //long double
// qfloat epsilon truncated so that it fits in this page...
#define QFLT_EPSILON 1.09003771904865842969737513593110651 ... E-106
This defines are part of the C99 ANSI standard and should be defined in all compilers that
implement that standard.
When in C you convert a floating point number into an integer, the result is calculated using
rounding towards zero. To see this in action look at this simple program:
#include <stdio.h>
void fn(double a)
{
printf("(int)(%g)=%d (int)(%g)=%d\n",a,(int)a,-a,(int)-a);
}
int main(void) {
for (double d = -1.2; d < 2.0; d += 0.3)
fn(d);
return 0;
}
This leads to the following output (note the lack of precision: 0.3 can’t be exactly represented
in binary):
(int)(-1.5)=-1 (int)(1.5)=1
(int)(-1.2)=-1 (int)(1.2)=1
(int)(-0.9)=0 (int)(0.9)=0
(int)(-0.6)=0 (int)(0.6)=0
(int)(-0.3)=0 (int)(0.3)=0
(int)(1.11022e-016)=0 (int)(-1.11022e-016)=0
(int)(0.3)=0 (int)(-0.3)=0
(int)(0.6)=0 (int)(-0.6)=0
(int)(0.9)=0 (int)(-0.9)=0
(int)(1.2)=1 (int)(-1.2)=-1
(int)(1.5)=1 (int)(-1.5)=-1
(int)(1.8)=1 (int)(-1.8)=-1
To round away from zero you can use:
#define Round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
This will give nonsense results if there is an overflow. A better version would be:
#define Round(x) \
((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
error() :
((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
85. The round functions round their argument to the nearest integer value in floating-point format, round-
ing halfway cases away from zero, regardless of the current rounding direction. Ansi C standard page
232.
86. The C standard, paragraph 7.6.
184 C programming with lcc-win32
The denormal flag means that a loss of precision is certain, the number is too small to be rep-
resented. The stack fault flag means that lcc-win32 generated bad code, since all floating point
operations should balance the floating point stack. If you ever test positive for this flag, do not
hesitate to send me a bug report!
In general, the floating point flags can be queried by the program to know if the last operation
did complete without problems. Here is a small example to show you how this works:
/* This program tests a division by zero */
#include <fenv.h>
#include <stdio.h>
int main(void)
{
double a=1,b=0;
feclearexcept(FE_DIVBYZERO);
a=a/b;
if (fetestexcept(FE_DIVBYZERO)) {
printf("You have divided by zero!\n");
}
return 0;
}
First we clear the flag that we are going to use using feclearexcept. Then, we perform
our operation and query if the flag is set using fetestexcept.
Since we know that the flags are set but not cleared, the expression could be very well be a
much more complicated sequence of operations. The above code would work the same, but we
would lose the possibility of knowing exactly which operation failed. This is in many cases
not very important, we could very well be interested that somewhere there was a serious error,
without bothering to investigate which operation was that it failed.
87. I have this example from the very good book “Numerical Mathematics and Scientific Computation”
by Germund Dahlquist and Åke Björck. Available on line at: http://www.mai.liu.se/~akbjo/NMbook.html
Using the floating point environment 185
I0 = [ln(x+5)]10 = ln 6 - ln 5 = 0.182322
We get:
We see that now we can go up to the 19th iteration with apparently good results. We see too
that the increased precision has only masked a fundamental flaw of the algorithm itself. The
manner the calculation is done produces a five fold increase in the error term at each iteration.
This is an example of a numerically unstable algorithm.
Note that the first notation is not standard (but used in the gcc compiler system), and it applies
only to explicit constants. Please do not assume that if you have a variable “x”, just writing
“xi” will transform it into a complex number!
When in your code you build a complex number using variables, you use the standard nota-
tion:
double complex w;
w = (65.0*angle/2)+45.8*angle*I;
In this example the real part of the number is (65.0*angle/2) and the imaginary part is
45.8*angle. The constant “I” represents 0,1 or csqrt(-1) and is defined in complex.h.
1) The first one is to find all files that match a (possibly) ambiguous file specification like, for
instance “*.c”
2) The second is to apply a regular expression to each line of the set of files. A regular
expression is a program that will be interpreted by a special interpreter at run time. We have
first to compile it, then we apply it to each line.
Here is then, a first approximation of “grep”.
#include <stdio.h>
#include <regexp.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <direct.h>
#include <shlwapi.h>
int main(int argc,char *argv[])
{
regexp *expression;
struct _finddata_t fd;
long h;
int matches;
char *p;
unsigned char path[MAX_PATH*2];
unsigned char currentdir[MAX_PATH];
unsigned char Path[2*MAX_PATH];
88. http://en.wikipedia.org/wiki/Regular_expression
188 C programming with lcc-win32
rather crude algorithm: if the second character of the path is a ‘:’ or the first character is a ‘\\’
we have an absolute path. Note that there can’t be any buffer overflow here since if we have a
path of only one character, the second one will be the terminating zero of the string, that will
be always there.
If it is not an absolute path, we have to handle the cases where we have a path in the form of
“..\..\foo\*.c”. We find the current directory, using the getcwd function, and we add to
it the path part of our specification, for example if we had “..\*.c” we would add to it
“..\”. Then we pass this path to the PathCanonicalize function, that will resolve the embed-
ded .. in any order they appear.
If the path was an absolute path, we assume there are no embedded .. in it, and we just copy
it to the buffer that receives the path.Of course, in a more advanced and robust program you
should check for that possibility, and adding a call to PathCanonicalize is not really a big deal.
4: At last we arrive to the second part of our task. We have a (possibly) ambiguous specifica-
tion like “*.c” and we want to ask the system to find out all files that match. We do that by
calling the function _findfirst, that returns either a valid “search handle”, or -1 if it could
not find any file that matches the specification.
If we found at least one file, we start looping calling _findnext, to get the next file to pro-
cess. We give to the function that will process the file two arguments: the expression that we
compiled just before, and the file name to process. That function returns the number of
matches, that we add to our count.
When there are no more matches we are done, we close the search handle, and exit returning
the number of matches.
Now, having setup the framework, here is the function that process each file.
if (f == NULL)
return 0;
while (fgets(buf,sizeof(buf),f)) {
if (regexec(expression,buf)) {
matches++;
if (!namewritten) {
printf("%s:\n",filename);
namewritten = 1;
}
printf("[%4d] %s",line,buf);
}
line++;
}
fclose(f);
return matches;
}
No secrets here. We open the file, and read each line of it using fgets. To each line we apply
our regular expression, and if there is a match we print that line. We print the file name before
printing any matches to separate the matches from one file from the matches of the next file in
the output. We always write the line number, and we loop until there are no more lines.
190 C programming with lcc-win32
Note that the test for failure to open the file is maybe unnecessary because we are certain that
the file exists, since the name is the result of a _findfirst or a _findnext. It could be however,
that the file exists but it is not accessible, because it is being used by another program in exclu-
sive mode, or that the access rights are such that we can’t read it. It is better to avoid crashes so
a simple test doesn’t make our program much fatter.
Programming with security in mind 191
int i;
memset(buf,0,sizeof(buf));
for (i=0; i<14;i++) {
buf[i] = 'a';
printf("[%-3d] ", i);
func(buf);
}
return 0;
}
Yes, we are sorry for the inconvenience. And no, it doesn’t help to call Microsoft. It is not their
fault this time.
What happened?
Obvious. The printf specification “%10s” will NOT limit the copy to 10 characters. It just
says that the string will be padded at the left with blanks if it is shorter than 10 chars. If not,
sprintf will continue to copy the characters without any limit.
Our function can be made safe very easily, by just using snprintf, instead of its dangerous
cousin sprintf. The snprintf function will take an optional integer argument after the for-
mat string, telling it how many characters it should write at most into its destination buffer.
void func(char *p) {
char buf[10+1];
memset(buf,0,sizeof(buf));
89. Microsoft Security Bulletin MS03-026: Buffer Overrun In RPC Interface Could Allow Code Execu-
tion (823980). Originally posted: July 16, 2003. Revised: September 10, 2003. Cited by Michael Hor-
ward in his excellent series about security in msdn. The URL for one of its articles is: http://
msdn.microsoft.com/library/default.asp?url=/library/en-us/dncode/html/secure09112003.asp
194 C programming with lcc-win32
Golden rule: Never define something in a header file. Header files are for declarations only!
Golden rule: ALWAYS watch out for scopes of “if” or “else” not between curly braces when
adding code.
90. Doing assignment inside the controlling expression of loop or selection statements is not
a good programming practice. These expressions tend to be difficult to read, and problems
such as using = instead of == are more difficult to detect when, in some cases, = is desired.
int main(void)
{
int a,b;
a= 10;
a>10?b=20:b=30; // Error in this line
printf("%d",b);
}
This will provoke an error. If we rewrite the above line like this:
a>10?b=20:(b=30);
the error disappears. Why?
The explanation is that the first line is parsed as:
((a>10)?(b=20):b)=30;
because of the established precedence of the operators. The assignment operator is one of the
weakest binding operators in C, i.e. its precedence is very low. The correct way to write the
expression above is:
b = (a<10)? 20 : 10;
pointers would be 64 bits. This assumption is deeply rooted in many places even under the
windows API, and it will cause problems in the future. Never assume that a pointer is going to
fit in an integer, if possible.
This will terminate after 6 iterations. This loop however, will never terminate:
unsigned int i;
for (i = 5; i >= 0; i --) {
printf("i = %d\n", i);
}
The loop variable i will decrease to zero, but then the decrement operation will produce the
unsigned number 4294967296 that is bigger than zero. The loop goes on forever.Note too that
the common windows type DWORD is unsigned!
// do stuff
free(buf);
return true;
} else {
return false;
}
}
Everything looks normal and perfect in the best of all worlds here. We test if the size if smaller
than a specified limit, and we then allocate the new string. But... what happens if cbSize is
zero???
Our call to malloc will ask for 0-1 bytes, and using 32 bit arithmetic we will get an integer
wrap-around to 0xffffffff, or -1. We are asking then for a string of 4GB. The program
has died in the spot.
93. I got this example from the excellent column of Michael Horward in the msdn security column.
Pitfalls of the C language 199
1.40.1 Calculating pi
This program will print the first few hundred digits of pi. Do not ask me how it works... I
found it in http://paul.rutgers.edu/~rhoads/Code/pi.c
int a=10000,b,c=2800,d,e,f[2801],g;
int main(void){
for(;b-c;)f[b++]=a/5;
for(;d=0,g=c*2;c-=14,printf("%.4d",e+d/a),e=d%a)
for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);
}
1.40.3 Tatoo
This one is in the obfuscated C contest. More of this kind of stuff at the URL:
http://www.ioccc.org
int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\
o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);}
Bibliography 201
1.41 Bibliography
Here are some books about C. I recommend you to read them before you believe what I say
about them.
«C Unleashed»
Richard Heathfield, Lawrence Kirby et al.
Heavy duty book full of interesting stuff like structures, matrix arithmetic, genetic algorithms
and many more. The basics are covered too, with lists, queues, double linked lists, stacks, etc.
«Algorithms in C»
Robert Sedgewick.
I have only the part 5, graph algorithms. For that part (that covers DAGs and many others) I
can say that this is a no-nonsense book, full of useful algorithms. The code is clear and well
presented.
«C a reference manual»
(Fifth edition) Samuel P Harbison Guy L Steele Jr.
If you are a professional that wants to get all the C language described in great detail this book
is for you. It covers the whole grammar and the standard library with each part of it described
in detail.
«The C programming language»
Brian W Kernighan, Dennis Ritchie. (second edition)
This was the first book about C that I got, and it is still a good read. With many exercises, it is
this tutorial in a better rendering...
«A retargetable C compiler: design and implementation»
Chris Fraser and Dave Hanson
This book got me started in this adventure. It is a book about compiler construction and not
really about the C language but if you are interested in knowing how lcc-win32 works this is
surely the place to start.
“C interfaces and implementations”
David R. Hanson
202 C programming with lcc-win32
This is an excellent book about many subjects, like multiple precision arithmetic, lists, sets,
exception handling, and many others. The implementation is in straight C and will compile
without any problems in lcc-win32.
“Safer C”
Les Hatton
As we have seen in the section «Pitfalls of the C language», C is quite ridden with problems.
This book address how to avoid this problems and design and develop you work to avoid get-
ting bitten by them.
C Programming FAQs
Steve Summit
C Programming FAQs contains more than 400 frequently asked questions about C, accompa-
nied by definitive answers. Some of them are distributed with lcc-win32 but the book is more
complete and up-to-date.
The Standard C Library
P.J. Plauger.
This book shows you an implementation (with the source code) of the standard C library done
by somebody that is in the standards committee, and knows what he is speaking about. One of
the best ways of learning C is to read C. This will give you a lot of examples of well written C,
and show you how a big project can be structured.
The C Standard
John Wiley and Sons.
This is the reference book for the language. It contains the complete C standard and the ratio-
nale, explaining some fine points of the standard.
Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade
Crispin Cowan, Perry Wagle, Calton Pu,Steve Beattie, and Jonathan Walpole
Department of Computer Science and Engineering Oregon Graduate Institute of Science &
Technology. URL: http://downloads.securityfocus.com/library/discex00.pdf
Bibliography 203
204 C programming with lcc-win32
Introduction 205
Chapter
Windows Programming
3
3.1 Introduction
OK, up to now we have built a small program that receives all its input from a file. This is
more or less easy, but a normal program will need some input from the user, input that can’t be
passed through command line arguments, or files. At this point, many introductory texts start
explaining scanf, and other standard functions to get input from a command line interface.
This can be OK, but I think a normal program under windows uses the features of windows.94
We will start with the simplest application that uses windows, a dialog box with a single edit
field, that will input a character string, and show it in a message box at exit.
The easiest way to do this is to ask wedit to do it for you. You choose ‘new project’ in the
project menu, give a name and a sources directory, and when the software asks you if it should
generate the application skeleton for you, you answer yes.
You choose a dialog box application, when the main dialog box of the “wizard” appears, since
that is the simplest application that the wizard generates, and will fit our purposes quite well.
But let’s go step by step. First we create a project. The first thing you see is a dialog box, not
very different from the one we are going to build, that asks for a name for the new project. You
enter a name like this:
You press OK, and then we get a more complicated one, that asks quite a lot of questions.
94. note 82
206 C Tutorial
You enter some directory in the second entry field, make sure the “windows executable” at the
The first panel of the wizard is quite impressing, with many buttons, etc. Ignore all but the
type of application panel. There, select a “dialog based” application, like this:
You see, the “Dialog based’ check button at the upper left is checked. Then press the OK but-
ton.
Then we get to different dialogs to configure the compiler. You leave everything with the
default values, by pressing Next at each step. At the end, we obtain our desired program. For
windows standards, this is a very small program: 86 lines only, including the commentaries.
We will study this program in an in-depth manner. But note how short this program actually is.
Many people say that windows programs are impossible huge programs, full of fat. This is just
not true!
But first, we press F9 to compile it. Almost immediately, we will obtain:
208 C Tutorial
Dialog.exe built successfully. Well, this is good news!95 Let’s try it. You execute the program
you just built using Ctrl+F5. When we do this, we see our generated program in action:
Just a dialog box, with the famous OK/Cancel buttons, and nothing more. But this is a start.
We close the dialog, either by pressing the “x” button at the top right corner, or just by using
OK or Cancel, they both do the same thing now, since the dialog box is empty.
We come back to the IDE, and we start reading the generated program in more detail. It has
three functions:
• WinMain
• InitializeApp
• DialogFunc
If we ignore the empty function “InitializeApp”, that is just a hook to allow you to setup things
before the dialog box is shown, only two functions need to be understood. Not a very difficult
undertaking, I hope.
3.1.1 WinMain
Command line programs, those that run in the ill named “msdos window”, use the “main”
function as the entry point. Windows programs, use the WinMain entry point.96
The arguments WinMain receives are a sample of what is waiting for you. They are a mess of
historical accidents that make little sense now. Let’s look at the gory details:
int APIENTRY WinMain(HINSTANCE hinst,
HINSTANCE hinstPrev,
LPSTR lpCmdLine,
int nCmdShow);
This is a function that returns an int, uses the stdcall calling convention97 denoted by APIEN-
TRY, and that receives (from the system) 4 parameters.
hinst, a “HANDLE” to an instance of the program. This will always be 0x400000 in hexadec-
imal, and is never used. But many window functions need it, so better store it away.
95. This has only historical reasons, from the good old days of windows 2.0 or even earlier. You can use
“main” as the entry point, and your program will run as you expect, but traditionally, the entry point is called
WinMain, and we will stick to that for now.
96. A calling convention refers to the way the caller and the called function agrees as to who is going to
adjust the stack after the call. Parameters are passed to functions by pushing them into the system stack.
Normally it is the caller that adjusts the stack after the call returns. With the stdcall calling convention, it is
the called function that does this. It is slightly more efficient, and contributes to keeping the code size small.
97. API means Application Programmer Interface, i.e. entry points into the windows system for use by
the programmers, like you and me.
Introduction 209
lpCmdLine. This one is important. It is actually a pointer to a character string that contains the
command line arguments passed to the program. Note that to the contrary of “main”, there
isn’t an array of character pointers, but just a single character string containing all the com-
mand line.
nCmdShow. This one contains an integer that tells you if the program was called with the
instruction that should remain hidden, or should appear normally, or other instructions that
you should use when creating your main window. We will ignore it for now.
OK OK, now that we know what those strange parameters are used (or not used) for, we can
see what this function does.
int APIENTRY WinMain(HINSTANCE hinst,
HINSTANCE hinstPrev,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASS wc; // A structure of type WNDCLASS
return DialogBox(hinst,
MAKEINTRESOURCE(IDD_MAINDIALOG),
NULL,
(DLGPROC) DialogFunc);
}
We see that the main job of this function is filling the structure wc, a WNDCLASS structure
with data, and then calling the API98 DialogBox. What is it doing?
We need to register a class in the window system. The windows system is object oriented,
since it is derived from the original model of the window and desktop system developed at
Xerox, a system based in SmallTalk, an object oriented language. Note that all windows sys-
tems now in use, maybe with the exception of the X-Window system, are derived from that
original model. The Macintosh copied it from Xerox, and some people say that Microsoft cop-
ied it from the Macintosh. In any case, the concept of a class is central to windows.
A class of windows is a set of window objects that share a common procedure. When some
messages or events that concern this window are detected by the system, a message is sent to
the window procedure of the concerned window. For instance, when you move the mouse over
the surface of a window, the system sends a message called WM_MOUSEMOVE to the win-
dows procedure, informing it of the event.
There are quite a lot of messages, and it would be horrible to be forced to reply to all of them
in all the windows you create. Fortunately, you do not have to. You just treat the messages that
interest you, and pass all the others to the default windows procedure.
There are several types of default procedures, for MDI windows we have MDIDefWindow-
Proc, for normal windows we have DefWindowProc, and for dialog boxes, our case here, we
have the DefDlgProc procedure.
When creating a class of windows, it is our job to tell windows which procedure should call
when something for this window comes up, so we use the class registration structure to inform
it that we want that all messages be passed to the default dialog procedure and we do not want
to bother to treat any of them. We do this with:
wc.lpfnWndProc = DefDlgProc;
As we saw with the qsort example, functions are first class objects in C, and can be passed
around easily. We pass the address of the function to call to windows just by setting this field
of our structure.
This is the most important thing, conceptually, that we do here. Of course there is some other
stuff. Some people like to store data in their windows99. We tell windows that it should reserve
some space, in this case the DLGWINDOWEXTRA constant, that in win.h is #defined as 30. We
put in this structure too, for obscure historical reasons, the hinst handle that we received in
WinMain. We tell the system that the cursor that this window uses is the system cursor, i.e. an
arrow. We do this with the API LoadCursor that returns a handle for the cursor we want. The
brush that will be used to paint this window will be white, and the class name is the character
string “dialog”.
And finally, we just call the RegisterClass API with a pointer to our structure. Windows does
its thing and returns.
The last statement of WinMain, is worth some explanation. Now we have a registered class,
and we call the DialogBox API, with the following parameters:
DialogBox(hinst,
MAKEINTRESOURCE(IDD_MAINDIALOG),
NULL, (DLGPROC) DialogFunc);
The hinst parameter, that many APIs still want, is the one we received from the system as a
parameter to WinMain. Then, we use the MAKEINTRESOURCE macro, to trick the compiler
into making a special pointer from a small integer, IDD_MAINDIALOG that in the header file
generated by the wizard is defined as 100. That header file is called dialogres.h, and is quite
small. We will come to it later.
What is this MAKEINTRESOURCE macro?
Again, history, history. In the prototype of the DialogBox API, the second parameter is actu-
ally a char pointer. In the days of Windows 2.0 however, in the cramped space of MSDOS
with its 640K memory limit, passing a real character string was out of the question, and it was
decided (to save space) that instead of passing the name of the dialog box resource as a real
name, it should be passed as a small integer, in a pointer. The pointer should be a 32 bit pointer
with its upper 16 bits set to zero, and its lower 16 bits indicating a small constant that would be
searched in the resource data area as the “name” of the dialog box template to load.
Because we need to load a template, i.e. a series of instructions to a built-in interpreter that
will create all the necessary small windows that make our dialog box. As you have seen, dia-
log boxes can be quite complicated, full of edit windows to enter data, buttons, trees, what
have you. It would be incredible tedious to write all the dozens of calls to the CreateWin-
dow API, passing it all the coords of the windows to create, the styles, etc.
99. When the IDE asks you if you want to open it as a resource say NO. We want to look at the text of
that file this time.
Introduction 211
To spare you this Herculean task, the designers of the windows system decided that a small
language should be developed, together with a compiler that takes statements in that language
and produce a binary file called resource file.
This resource files are bound to the executable, and loaded by the system from there automat-
ically when using the DialogBox primitive. Among other things then, that procedure needs to
know which dialog template should load to interpret it, and it is this parameter that we pass
with the MAKEINTRESOURCE macro.
Ok, that handles (at least I hope) the second parameter of the DialogBox API. Let’s go on,
because there are still two parameters to go!
The third one is NULL. Actually, it should be the parent window of this dialog box. Normally,
dialog boxes are written within an application, and they have here the window handle of their
parent window. But we are building a stand-alone dialog box, so we left this parameter empty,
i.e. we pass NULL.
The last parameter, is the DialogFunc function that is defined several lines below. The
DefDlgProc needs a procedure to call when something important happens in the dialog box: a
button has been pushed, an edit field receives input, etc.
Ok, this closes the call of the DialogBox API, and we are done with WinMain. It will return
the result of the DialogBox function. We will see later how to set that result within our Dialog
box procedure.
3.1.2 Resources
We mentioned before, that there is a compiler for a small resource language that describes our
dialog boxes. Let’s look at that with a little bit more detail before we go to our dialog proce-
dure.
Open that file that should be called dialog.rc if you gave the project the “dialog” name100, and
look at this lines:
IDD_MAINDIALOG DIALOG 7, 20, 195, 86 (1)
STYLE DS_MODALFRAME|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
(2)
CAPTION "dialog" (3)
FONT 8, "Helv" (4)
BEGIN
DEFPUSHBUTTON "OK", IDOK, 149, 6, 40, 14 (5)
PUSHBUTTON "Cancel", IDCANCEL, 149, 23, 40, 14 (6)
END
We see that all those statements concern the dialog box, its appearance, the position of its child
windows, etc. Let’s go statement by statement:
1) We find here the same identifier IDD_MAINDIALOG, and then the DIALOG statement,
together with some coordinates. Those coordinates are expressed in Dialog Units, not in
pixels. The motivation behind this, is to make dialog boxes that will look similar at all
resolutions and with different screen sizes. The units are based somehow in the size of the
system font, and there are APIs to get from those units into pixels, and from pixels into
those units.
100. You will be prompted for a header file, where are stored the definitions for things like
IDD_MAINDIALOG. Choose the one generated by the wizard. Its name is <project name>res.h, i.e. for a
project named “test” we would have “testres.h”.
212 C Tutorial
2) The STYLE statement tells the interpreter which things should be done when creating the
window. We will see later when we create a real window and not a dialog box window, that
there can be quite a lot of them. In this case the style indicates the appearance
(DS_MODALFRAME), that this window is visible, has a caption, and a system menu.
3) The CAPTION statement indicates just what character string will be shown in the caption.
4) In a similar way, the FONT statement tells the system to use Helv
5) The following statements enumerate the controls of the dialog box, and their descriptions
are enclosed in a BEGIN/END block. We have two of them, a push button that is the
default push button, and a normal pushbutton
6) the Cancel button. Both of them have a certain text associated with them, a set of coords as
all controls, and an ID, that in the case of the OK button is the predefined symbol IDOK,
with the numerical value of 1, and in the case of the Cancel button IDCANCEL (numerical
value 2).
To convert this set of instruction in this language into a binary resource file that windows can
interpret, we use a compiler called a resource compiler. Microsoft’s one is called rc, Borland’s
one is called "brc", and lcc-win32’s one is called lrc. All of them take this resource language
with some minor extensions depending on the compiler, and produce a binary resource file for
the run time interpreter of windows.
The resource compiler of lcc-win32 is explained in detail in the technical documentation, and
we will not repeat that stuff again here. For our purposes it is enough to know that it is compat-
ible with the other ones.
The binary resource files generated by the resource compiler are passed to the linker that con-
verts them into resource specifications to be included in the executable.
Note that actually you do not need to know this language, because the IDE has a resource edi-
tor that can be used to build graphically using drag and drop the dialog box. But the emphasis
here is to introduce you to the system so that you know not only what button should you push,
but why you should push that button too.
But we wanted originally to make a dialog box containing an edit field. We are far away from
our objective yet.
Introduction 213
We come back to Wedit, after closing our text file “dialog.rc”, and we go to the “Design”
menu bar and press “Open/new”. 101The resource editor opens up, and we see the following
display:
Near the top left border you see a button like this:
Controls palette The whole operation of the editor is quite simple: The smaller
window represents all the controls that you can put in a dialog
box: entry fields, buttons, checkboxes, and several others. You
select one button with the mouse, and drag it to your dialog box.
There you drop it at the right position. To add an entry field then,
we just push the edit field icon, the third one from the left in the
upper row of icons, and drag it to our dialog box in the main
wedit window. After doing this, it will look like this:
Our entry field becomes the selected item, hence the red handles
around it. After resizing if necessary, we need to enter its identi-
fier, i.e. the symbolic name that we will use in our program to
refer to it.
We will refer then in our program to this entry field with the name
IDENTRYFIELD, maybe not a very cute name, but at least better
than some bare number. The editor will write a
#define IDENTRYFIELD 101
in the generated header file.
The number 101 is an arbitrary constant, chosen by the editor. We resize the dialog box a bit (I
like dialogs that aren’t bigger than what they should be), and we press the “test” button.
We see a display like this:
We can enter text in the entry field, and pushing Cancel or OK will finish the test mode and
return us to the dialog box editor.
Introduction 215
OK, seems to be working. We save, and close the dialog box editor. We come back to our dia-
log procedure, where we will use this new entry field to get some text from the user.
In this procedure we are interested in only three messages, hence we have only three “cases”
in our switch:
1) WM_INITDIALOG. This message is sent after the window of the dialog box has been
created, but before the dialog is visible in the screen. Here is done the initialization of the
dialog box data structures, or other things. The wizard inserts here a call to a procedure for
handling this message.
2) WM_COMMAND. This message is sent when one of the controls (or child windows if you
want to be exact) has something to notify to the dialog: a button has been pressed, a check
box has been pressed, data has been entered in an entry field, etc. Since we can have several
controls, we use again a switch statement to differentiate between them. Switch statements
can be nested of course.
3) WM_CLOSE. This message arrives when the user has pressed the “close” button in the
system menu, or has typed the Alt+F4 keyboard shortcut to close the dialog.
Now, the whole purpose of this exercise is to input a character string. The text is entered by the
user in our entry field. It is important, from a user’s perspective, that when the dialog box is
displayed, the cursor is at the beginning of the entry field. It could be annoying to click each
time in the entry field to start editing the text. We take care of this by forcing the focus to the
entry field.
Under windows, there is always a single window that has the focus, i.e. receives all the input
from the keyboard and the mouse. We can force a window to have the focus using the SetFo-
cus API.
static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
SetFocus(GetDlgItem(hDlg,IDENTRYFIELD));
return 1;
}
We add this call in the procedure InitializeApp. We test, and… it doesn’t work. We still have
to click in the edit field to start using it. Why?
Because, when we read the documentation of the WM_INITDIALOG message103 it says:
WM_INITDIALOG
hwndFocus = (HWND) wParam; // handle of control to receive focus
lInitParam = lParam; // initialization parameter
Parameters
hwndFocus
Value of wParam. Identifies the control to receive the default keyboard focus. Windows
assigns the default keyboard focus only if the dialog box procedure returns TRUE.
Well, that is it! We have to return FALSE, and our SetFocus API will set the focus to the con-
trol we want. We change that, and… it works! Another bug is gone.104
Note that the SetFocus API wants a window handle. To get to the window handle of a control
in the dialog box we use its ID that we took care of defining in the dialog editor. Basically we
102. You put the cursor under the WM_INITDIALOG identifier and press F1.
103. This example shows you how to get rid of those problems, and the kind of problems you will
encounter when programming under Windows. The only solution in most cases is a detailed reading of the
documentation. Fortunately, Windows comes with a clear documentation that solves most problems.
104. All the notifications messages from edit fields begin with the EN_ prefix, meaning Edit field
Notification.
Introduction 217
give to the SetFocus function the result of calling the API GetDlgItem. This is nice, since we
actually need only one window handle, the window handle of the dialog box that windows
gives to us, to get all other window handles of interest.
Now comes a more difficult problem. When the user presses the OK button, we want to get the
text that he/she entered. How do we do that?
We have two problems in one: the first is to decide when we want to get the text, and the other
is how to get that text.
For the first one the answer is clear. We want to read the text only when the user presses the
OK button. If the Cancel button is pressed, or the window is closed, we surely aren’t interested
in the text, if any. We will read the text then when we handle the message that the OK button
window sends to us when is pressed. We change our dialog procedure like this:
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
ReadText(hwndDlg);
EndDialog(hwndDlg,1);
return 1;
case IDCANCEL:
EndDialog(hwndDlg,0);
return 1;
}
break;
We add a call to a function that will get the text into a buffer. That function looks like this:
static char buffer[1024];
int ReadText(HWND hwnd)
{
memset(buffer,0,sizeof(buffer));
if (GetDlgItemText(hwnd,
IDENTRYFIELD,
buffer,
sizeof(buffer))) {
return 1;
}
return 0;
}
We define a buffer that will not be visible from other modules, hence static. We set a fixed
buffer with a reasonable amount of storage.
Our function cleans the buffer before using it, and then calls one of the workhorses of the dia-
log procedures: the API GetDlgItemText. This versatile procedure will put in the designated
buffer, the text in a control window, in this case the text in the entry field. We again indicate to
the API which control we are interested in by using its numerical ID. Note that GetDlgItem-
Text returns the number of characters read from the control. If there isn’t anything (the user
pressed OK without entering any text), GetDlgItemText returns zero.
The first time that we do that; we will surely will want to verify that the text we are getting is
the one we entered. To do this, we use the API MessageBox that puts up a message in the
screen without forcing us to register a window class, define yet another window procedure,
etc.
We add then to our window procedure, the following lines:
case IDOK:
if (ReadText(hwndDlg)) {
MessageBox(hwndDlg,buffer,
"text entered",MB_OK);
218 C Tutorial
EndDialog(hwndDlg,1);
}
return 1;
MessageBox takes a parent window handle, in this case the handle of the dialog box proce-
dure, a buffer of text, a title of the message box window, and several predefined integer con-
stants, that indicate which buttons it should show. We want just one button called OK, so we
pass that constant.
Note too, that if the user entered no text, we do NOT call the EndDialog API, so the dialog
box will refuse to close, even if we press the OK button. We force the user to enter some text
before closing the dialog. Since we haven’t changed anything in the logic for the Cancel but-
ton, the dialog box will still close when the user presses those buttons. Only the behavior of
the OK button will change.
The EndDialog API takes two parameters: the dialog box window handle that it should
destroy, and a second integer parameter. The dialog box will return these values as the result of
the DialogBox call from WinMain remember?
Since WinMain returns itself this value as its result, the value returned by the DialogBox will
be the return value of the program.
EnableWindow(
GetDlgItem(hwndDlg,IDOK),1);
}
else
EnableWindow(
GetDlgItem(hwndDlg,IDOK),0);
break;
}
break;
}
break;
We add a new case for this message. But we see immediately that this nested switch state-
ments are getting out of hand. We have to split this into a function that will handle this mes-
sage. We change again our dialog box procedure as follows:
case IDCANCEL:
EndDialog(hwndDlg,0);
return 1;
case IDENTRYFIELD:
return EntryFieldMessages(hwndDlg,wParam);
This is much clearer. We put the code for handling the entry field messages in its own proce-
dure, “EntryFieldMessages”. Its code is:
int EntryFieldMessages(HWND hDlg,WPARAM wParam)
{
HWND hIdOk = GetDlgItem(hDlg,IDOK);
switch (HIWORD(wParam)) {
case EN_CHANGE:
if (GetDlgItemText(hDlg,IDENTRYFIELD,
buffer, sizeof(buffer))) {
// There is some text in the entry field. Enable the IDOK button.
EnableWindow(hIdOk,1);
}
else // no text, disable the IDOK button
EnableWindow(hIdOk,0);
break;
}
return 1;
}
Let’s look at this more in detail. Our switch statement uses the HIWORD of the first message
parameter. This message carries different information in the upper 16 bits (the HIWORD) than
in the lower 16 bits (LOWORD). In the lower part of wParam we find the ID of the control
that sent the message, in this case IDENTRYFIELD, and in the higher 16 bits we find which
sub-message of WM_COMMAND the control is sending to us, in this case EN_CHANGE105,
i.e. a change in the text of the edit field.
There are many other notifications this small window is sending to us. When the user leaves
the edit field and sets the focus with the mouse somewhere else we are notified, etc. But all of
those notifications follow the same pattern: they are sub-messages of WM_COMMAND, and
their code is sent in the upper 16 bits of the wParam message parameter.
Continuing the analysis of EntryFieldMessages, we just use our friend GetDlgItemText to get
the length of the text in the edit field. If there is some text, we enable the IDOK button with the
API EnableWindow. If there is NO text we disable the IDOK button with the same API. Since
105. I remember calling the software support when installing some hardware: many of the options of the
installation software were disabled but there was no way of knowing why.
220 C Tutorial
we are getting those notifications each time the user types a character, the reaction of our
IDOK button will be immediate.
But we have still one more thing to do, before we get this working. We have to modify our Ini-
tializeApp procedure to start the dialog box with IDOK disabled, since at the start there is no
text in the entry field.
static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
SetFocus(GetDlgItem(hDlg,IDENTRYFIELD));
// Disable the IDOK button at the start.
EnableWindow(GetDlgItem(hDlg,IDOK),0);
return 1;
}
We recompile, and it works. The OK button starts disabled (grayed), and when we type the
first character it becomes active, just as we wanted. When we select all the text in the entry
field, and then erase it, we observe that the button reverts to the inactive state.
font = GetStockObject(ANSI_FIXED_FONT);
SendDlgItemMessage(hDlg,IDENTRYFIELD,
WM_SETFONT,(WPARAM)font,0);
EnableWindow(GetDlgItem(hDlg,IDOK),0);
SetFocus(GetDlgItem(hDlg,IDENTRYFIELD));
return 1;
}
A HFONT is a font “handle”, i.e. an integer that represents a font for windows. We get that
integer using the GetStockObject API. This function receives an integer code indicating which
106. Now is a good time to read the documentation for that API. It will not be repeated here.
User interface considerations 221
object we are interested in and returns it. There are several types of object we can get from it:
fonts, brushes, pens, etc.107
Yet another point missing in our dialog box is a correct title, or prompt. The title of our dialog
is now just “dialog”. This tells the user nothing at all. A friendlier interface would tell the user
what data the software is expecting from him/her. We could change the title of the dialog to a
more meaningful string.
The program calling our dialog procedure could give this string as a parameter to the dialog
box. Dialog boxes can receive parameters, as any other procedure. They receive them in the
parameters passed to the WM_INITDIALOG message.
A closer look to the documentation of the WM_INITDIALOG message tell us that the lParam
message parameter contains for the WM_INITDIALOG 32 bits of data passed in the last
parameter of an API called DialogBoxParam.
We have to modify our calling sequence to the dialog, and instead of using DialogBox we use
the DialogBoxParam API. Looking into our program, we see that the DialogBox API was
called in our WinMain function (see above). We should modify this call then, but a new prob-
lem appears: where does WinMain know which string to pass to the DialogBoxParam API?
Well, we could decide that this string would be the parameters passed to WinMain in the lpC-
mdLine argument. This is the most flexible way. We modify then the call to DialogBox like
follows:
return DialogBoxParam (hinst,
MAKEINTRESOURCE(IDD_MAINDIALOG),
NULL, (DLGPROC) DialogFunc,
(int)lpCmdLine);
Since our dialog box is now constructed with DialogBoxParam, we receive in the lParam mes-
sage parameter the same pointer that we gave to the DialogBoxParam API. Now, we have just
to set that text in the caption of our dialog box and it’s done. We do that (again) in our initial-
ization procedure by adding:
SetWindowText(hDlg, (char *)lParam);
The SetWindowText API sets the caption text of a window, if that window has a caption bar of
course. To test this, we have to tell Wedit to pass a command line argument to the program
when it calls the debugger or executes the program with Ctrl+F5. We do this by selecting the
“debugger” tab in the configuration of wedit:
107. We introduced that to see if we were really getting a string. Since now we are returning that data to
the calling program, that message should disappear.
222 C Tutorial
The debugger tab is in the upper left corner. When we select it, we arrive at the following tab:
Note the first line “Command line arguments to pass to program”. There, we write the string
that we want shown in the dialog box.
When now we press Ctrl+F5, we see our dialog box like this:
Nice, we can pass our dialog box a “prompt” string. This makes our dialog box more useful as
a general input routine. Remember that the objective of this series of sections was to introduce
you a general routine to input a character string from the user. We are getting nearer.
Still, there is one more problem that we haven’t solved yet. We have a buffer of a limited
length, i.e. 1024 characters. We would like to limit the text that the user can enter in the dialog
box so that we avoid overflowing our buffer. We can do this with the message
EM_SETLIMITTEXT. We have to send this message to the control when we start the dialog
box, so that the limit will be effective before the user has an occasion of overflowing it. We
add then
SendDlgItemMessage(hDlg,IDENTRYFIELD,
EM_SETLIMITTEXT,512,0);
Libraries 223
3.3 Libraries
What we would like is a way of using this dialog box in our applications of course. How could
we do that?
One way would be to call it as an independent program. We could use the facilities for calling
a program within the windows system, and pass our prompt in the command line parameters.
This would work, but the problem of getting the string from the user would be quite compli-
cated to solve. Programs can only return an error code, and in some situations this error code
can only be from zero to 255… We can’t pass pointers just like that from one program to
another; we can’t just pass a pointer to a character string as the result of the program.
Why?
Because windows, as other systems like linux, Solaris, and UNIX in general, uses a protected
virtual address schema. The machine addresses that the program uses are virtual, as if the pro-
gram was the only one running in the machine. It is the operating system and the CPU that
does the translation of those virtual addresses into real RAM locations in the machine you are
using. This means the addresses of each program aren’t meaningful for another program. We
can pass special pointers (shared memory) within windows, but that’s too advanced stuff for
an introductory text, sorry.
But there are many other ways of solving this problem without costly interface development.
What do we want? Let’s get that clear first, worrying about implementation details later.
int GetString(char *prompt, char *buffer,int bufferlen);
This routine would return either true or false, depending if the user has pressed OK or can-
celled the operation. If the user pressed OK, we would find the string that was entered in the
buffer that we pass to GetString. To avoid any overflow problems, we would pass the length of
the character string buffer, so that the GetString routine stops input when we reach that limit.
The C language supports code reuse. You can compile code that is useful in many situations
and build libraries of routines that can be reused over and over again. The advantages are
many:
• The code has to be written once and debugged once.
• The size of our program stays small, since we call a routine instead of repeating the code
all over the place.
Function calls are the mechanism of code reuse since a function can be used in many situa-
tions. Libraries are just a collection of routines that are linked either directly or indirectly with
the main program.
From the standpoint of the user of the library, not the one who is building it, the usage is quite
simple:
1) You have to include the header file of the library to make the definition available to the
compiler.
3) You add the library to the set of files that the linker uses to build your program.
This simplicity of usage makes libraries a good way to reuse and share the code between dif-
ferent applications.
Under windows we have two types of libraries:
224 C Tutorial
Static libraries. These libraries are built in files that normally have the .lib extension and are
linked with the program directly, i.e. they are passed to the linker as arguments. The linker
takes the code of the needed functions from the library and copies the code into the pro-
gram.
Dynamic libraries. These aren’t copied into the main program, but are resolved at load time by
the program loader. When you double-click a program’s icon you activate a system pro-
gram of windows called program loader that goes to the disk, finds the code for the execut-
able you have associated with the icon, and loads it from disk into RAM. When doing this,
the loader finds if the program needs any dynamic libraries, that normally have the .DLL
extension, and reads their code too, linking it dynamically to the program being loaded.
Which one should we use in this application?
Static or not static? That is the question!
Our program needs a class registration before it can call the DialogBoxParam API. If we use
the static library approach, we would have to require that the user of the library calls some ini-
tialization routine before, to allow us to register our class with windows.
But this would complicate the interface. We introduce with this requirement yet another thing
that can go wrong with the program, yet another thing to remember.
A way out of this dilemma would be to take care of doing the registration automatically. We
could setup an integer variable that would start as zero. Before calling our DialogBoxParam
procedure we would test the value of this variable.
If it is zero it means that our class wasn’t registered. We would register our class and set this
variable to one, so that the next call finds a value different than zero and skips the class regis-
tration code.
Libraries 225
We have to tell the IDE to produce a static library now, instead of a normal executable file. We
do this by going into the linker configuration tab, and checking the library radio-button, like
this:
You see the “Static library” radio-button checked. The name of the library is the name of the
project with the .lib extension.
Now we are ready to change our WinMain. We change our WinMain function to be the Get-
String procedure, like this:
static char buffer[1024];
hinst = GetModuleHandle(NULL);
if (classRegistered == 0) {
memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = DefDlgProc;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hinst;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszClassName = "dialog";
RegisterClass(&wc);
classRegistered = 1;
}
226 C Tutorial
result = DialogBoxParam(hinst,
MAKEINTRESOURCE(IDD_MAINDIALOG),
NULL,
(DLGPROC) DialogFunc,
(int)prompt);
if (result == 1) {
strncpy(destbuffer,buffer,bufferlen-1);
destbuffer[bufferlen-1] = 0;
}
return result;
}
We have several things to explain here.
1) We move the declaration of our static buffer that was before further down, to the beginning
of the file, so that we can use this buffer in the GetString procedure to copy its contents into
the destination buffer.
2) We declare our flag for testing if the class has been registered as a static int, i.e. an integer
visible only in this module. We do not need to initialize it to zero, since the C language
guarantees that all non-explicitly initialized static variables will be set to zero when the
program starts.
3) We modify the declarations of local variables in the GetString procedure, adding a result
integer variable, and a HANDLE that will hold the instance of the current module. Before,
we received this as a parameter in the arguments of WinMain, but now we have to get it by
some other means. The solution is to call the GetModuleHandle API, to get this. We
indicate it that we want the handle of the currently running executable by passing it a
NULL parameter.
4) We test then our global flag classRegistered. If it is zero, we haven’t registered the class,
and we do it now. Afterwards, we set the variable to one, so that this piece of code will not
be executed again.
5) We call our DialogBox procedure just like we did before, but now we assign its result to an
integer variable and we test if the result is one (i.e. the user pressed OK). If that is the case,
we copy the string from the temporary buffer to the destination buffer. Note that we use the
strncpy function. This standard library function takes an extra parameter, a maximum
length to copy. We do not want to overflow the destination buffer under any circumstances,
so we only copy a maximum of bufferlen characters minus one, to account for the
terminating zero of the string. We ensure afterwards that the string is zero terminated, and
we return the result.
The rest of the program remains the same, so it is not shown. It is important to remember to
get rid of that MessageBox call however!108
We compile the library, and we want to test it, but… we need a test program. A library is not
an executable by itself.
Besides this, we need a header file that the user of the library will use to get the prototype of
our function. Its contents are very simple:
int GetString(char *prompt, char *destBuffer,int bufferlen);
and that’s all.
108. Well, this is not great deal; we have just to answer YES when it proposes to create the skeleton.
Libraries 227
Now, we have to define a new project that will contain the test code. We do that in the same
way as we have created the other projects: we choose the ‘Create project’ option in the
‘Project’ menu bar, and we name it appropriately “testdialog”.
We do NOT specify a windows application. Since our library should be independent whether
it is called from a windows program or a console application, we should test that now.
Now, when creating the project, we ask the wizard to create a console application.109 We leave
everything by default, but when we arrive at the linker settings dialog, we add our dialog.lib to
the libraries entry field, like this:
Another issue to remember, is the following:
We need a resource file. Since in our library there are no resources, we have to add those
resources to our test program. This is easy to do in this case: we just add the dialog.rc resource
file to the project. The interface for the users of the library however is terrible. All programs
that use our library will be forced to include somehow the resource for the dialog box! Includ-
ing a resource in another resource file is difficult, to say the least.
Wow, this looks like a showstopper actually.
OK. This presents an unexpected and serious trouble for our library project, but we will not
leave things at midway. We finish our test program by changing our “main” function, like this:
extern int APIENTRY GetString(char *prompt,char *buf,int len);
int main(void)
{
char buffer[1024];
if (GetString("Enter a string",buffer,sizeof(buffer))) {
printf("String is %s\n",buffer);
}
else printf("User cancelled!\n");
return 0;
}
109. It could be argued that this could be done with DLLs too: the linker should export all externally
visible symbols. In practice is better only to export symbols that should be visible. This avoids name clashes.
228 C Tutorial
This isn’t that bad, for a start. We have a sophisticated line editor, complete with arrow inter-
face to move around in the text, clipboard support built in, delete and backspace keys already
taken care of, etc. If we would have to write ourselves an equivalent program, it would cost us
probably days of development. To develop the clipboard interface already is quite a challenge.
But we are confronted to the problem of resources. Our static library idea was a dead end. We
have to find something else.
Summary: The C language supports the concept of code reuse in the form of libraries. The
static libraries are combined with the main application at link time (statically). They can’t
contain resources.
Dynamically linked libraries (DLLs) 229
2) We can write the name of the symbol in a special file called definitions file (with the .def
extension) and pass this file to the linker.
Which method you use is a matter of taste. Writing __declspec(dllexport) in the source code is
quite ugly, and may be non-portable to other systems where the conventions for dynamically
linked code may be completely different. A definitions file spares us to hard wire that syntax
in the source code.
The definitions file has its drawbacks too however. We need yet another file to maintain,
another small thing that can go wrong.
For our example we will use the __declspec(dllexport) syntax since we have only one function
to export.
We return to our library project, and reopen it. We go again to the linker configuration tab, that
now is called “librarian” since we are building a static library, and we check the radio-button
corresponding to a DLL project. We answer yes when Wedit says whether it should rebuild the
makefile and there we are. Now we have to make the modifications to our small library.
We have to define a function that will be called when the library is loaded. Traditionally, the
name of this function has been LibMain since the days of Windows 3.0 or even earlier. We
stick to it and define the following function:
int WINAPI LibMain(HINSTANCE hDLLInst, DWORD Reason, LPVOID Reserved)
{
switch (Reason)
{
110. You can change this by pressing the corresponding button in the linker configuration tab, or by
giving the argument –nounderscores to the linker, when building the DLL.
230 C Tutorial
case DLL_PROCESS_ATTACH:
hinst = hDLLInst;
DoRegisterClass();
break;
case DLL_PROCESS_DETACH:
UnregisterClass("dialog",hDLLInst);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
This function, like our dialog function or many other functions under windows, is a callback
function, i.e. a function that is called by the operating system, not directly from our code.
Because of this fact, its interface, the arguments it receives, and the result it returns is fixed.
The operating system will always pass the predefined arguments to it, and expect a well-
defined result.
The arguments that we receive are the following:
1) We receive a HANDLE to the instance of the DLL. Note that we have to pass to several
functions this handle later on, so we will store it away in a global variable.
2) We receive a DWORD (an unsigned long) that contains a numerical code telling us the
reason why we are being called. Each code means a different situation in the life cycle of
the DLL. We have a code telling us that the we were just loaded
(DLL_PROCESS_ATTACH), another to inform us that we are going to be unloaded
(DLL_PROCESS_DETACH), another to inform us that a new thread of execution has been
started (DLL_THREAD_ATTACH) and another to tell us that a thread has finished
(DLL_THREAD_DETACH).
3) The third argument is reserved by the system for future use. It is always zero.
The result of LibMain should be either TRUE, the DLL has been correctly initialized and
loading of the program can continue, or zero meaning a fatal error happened, and the DLL is
unable to load itself.
Note that we return always TRUE, even if our registration failed.
Why?
If our registration failed, this module will not work. The rest of the software could go on run-
ning however, and it would be too drastic to stop the functioning of the whole software
because of a small failure in a routine that could be maybe optional.
Why the registration of our class could fail?
One of the obvious reasons is that the class is already registered, i.e. that our calling program
has already loaded the DLL, and it is loading it again. Since we do not unregister our class,
this would provoke that we try to register the class a second time.
For the time being, we need to handle only the event when the DLL is loaded or unloaded. We
do two things when we receive the DLL_PROCESS_ATACH message: we store away in our
global variable the instance of the DLL, and then we register our string dialog box class. We
could have just done it in the LibMain function, but is clearer to put that code in its own rou-
tine. We write then:
static void DoRegisterClass(void)
Dynamically linked libraries (DLLs) 231
{
WNDCLASS wc;
memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = DefDlgProc;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hinst;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszClassName = "dialog";
RegisterClass(&wc);
}
You see that the code is the same as the code we had originally in WinMain, then in our Get-
String procedure, etc.
To finish LibMain, we handle the message DLL_PROCESS_DETACH unregistering the class
we registered before.
With this, our GetString procedure is simplified: We do not need to test our flag to see if the
class has been registered any more. We can be certain that it was.
int APIENTRY __declspec(dllexport)
GetString(char *prompt, char *destbuffer,int bufferlen)
{
int result;
result = DialogBoxParam(hinst,
MAKEINTRESOURCE(IDD_MAINDIALOG),
NULL,
(DLGPROC) DialogFunc,
(int)prompt);
if (result == 1) {
strncpy(destbuffer,buffer,bufferlen-1);
destbuffer[bufferlen-1] = 0;
}
return result;
}
We compile and link our DLL by pressing F9. Note that when we are building a DLL, lcc-
win32 will generate three files and not only one as with a normal executable.
2) We obtain an import library that allows other programs to be linked with this DLL. The
name will be dialog.lib, but this is not a normal library. It is just a library with almost no
code containing stubs that indicate the program loader that a specific DLL is used by the
program.
3) We obtain a text file called dialog.exp that contains in text form the names that are exported
from the DLL. If, for any reason we wanted to regenerate the import library for the DLL,
we could use this file together with the buildlib utility of lcc-win32 to recreate the import
library. This can be important if you want to modify the names of the exported functions,
establish synonyms for some functions or other advanced stuff.
232 C Tutorial
if (GetString("Enter a string",
buffer,sizeof(buffer))) {
printf("String is %s\n",buffer);
}
else printf("User cancelled\n");
return 0;
}
We compile this in the directory of the project, without bothering to create a project. Suppose
that our project is in
h:\lcc\projects\dialog
and the dll is in
h:\lcc\projects\dialog\lcc
We compile with the command:
lcc testdialog.c
then we link with the command:
lcclnk testdialog.obj lcc\dialog.lib
Perfect! We now type the name of the program to test our dll.
testdialog
but instead of the execution of the program we see a dialog box like this:
This dependency on the DLL is quite disturbing. All programs that use the DLL in this fashion
would need to have the DLL in their startup directory to be able to work at all.
A way to get rid of this is to avoid linking with the DLL import library. Yes you will say, but
how will we use the DLL?
DLLs can be loaded into the program’s address space with the API LoadLibrary. This API will
do what the program loader does when loading a program that contains a reference to a DLL.
If the load succeeds, the API will return us a handle to the library, if not, it will return us an
INVALID_HANDLE as defined in windows.h.
After loading the DLL, we can get the address of any exported function within that DLL just
by calling another windows API: GetProcAddress. This API receives a valid DLL handle, and
a character string containing the name of the function to find, and will return an address that
we can store in a function pointer.
Let’s do this.
#include <windows.h> (1)
#include <stdio.h> (2)
int (APIENTRY *pfnGetString)(char *,char *,int); (3)
We test if the handle received is a correct one. If not we put up some message and we exit the
program.
We are now ready to assign our function pointer. We must cast the return value of GetProcAd-
dress to a function like the one we want. The first part of the statement is just a cast, using the
same construction that the declaration of the function pointer before, with the exception that
we do not include the identifier of course, since this is a cast. But the arguments to GetPro-
cAddress are weird. We do not pass really the name of the function GetString, but a
name _GetString@12. Where does this name come from?
The rest of the program stays the same.
To understand where this weird name comes from, we have to keep in mind the following
facts:
lcc-win32 like many other C compilers, adds always an underscore to the names it generates
for the linker.111
Since our function is declared as _stdcall, windows conventions require that we add to the
name of the function the character ‘@’ followed by the size of the function arguments. Since
our function takes a char pointer (size 4) another char pointer, and an integer (size 4 too), we
have 12 bytes of procedure arguments, hence the 12. Note that all types smaller than an integer
will be automatically be promoted to integers when passing them to a function to keep the
stack always aligned, so that we shouldn’t just take the size of the arguments to make the addi-
tion. All of this can become really complicated if we have structures that are pushed by value
into the stack, or other goodies.
The best thing would be that our DLL would export _GetString@12 as GetString. PERIOD.
Well, this is exactly where our dialog.def file comes handy. Here is a dialog.def that will solve
our problem.
LIBRARY dialog
EXPORTS
_GetString@12=GetString
We have in the first line just the name of the DLL in a LIBRARY statement, and in the second
line two names. The first one is the name as exported by the compiler, and the second one is
the name as it should be visible from outside. By default, both are the same, but now we can
separate them. With these instructions, the linker will put in the export table of the DLL the
character string “GetString”, instead of the compiler-generated name.112
Once this technical problems solved, we see that our interface is much more flexible now. We
could just return FALSE from our interface function if the DLL wasn’t there, and thus disable
some parts of the software, but we wouldn’t be tied to any DLL. If the DLL isn’t found, the
functionality it should fulfill can’t be present but nothing more, no catastrophes.
111. In the documentation of windows, you will find out that in the .def file you can write other kinds of
statements. None of them are supported by lcc-win32 and are silently ignored since they are redundant with
the command line arguments passed to the linker. Do not write anything else in the .def file besides the names
of the exports.
112. The Macintosh works in the same manner, albeit with a much more primitive system.
A more formal approach. 235
we will describe the exact syntax later.113 This message-pump is hidden now from view under
the DefDlgProc procedure, but it is the source of all the messages passed to the dialog proce-
dure that we defined.
A windows program is designed to react to those messages, or events. It will process them in
sequence, until it decides to stop the message pump ending the program.
The general structure is then:
113. Application frameworks like MFC introduce an additional simplifying layer between you and the
operating system. Much has been said and written about them, and here I will not discuss this in much more
detail. Suffice to note that the purpose of lcc-win32 is to let you be always in control of what is going on. You
can do here anything, contrary to a framework, where you can only do what the framework provides for, and
nothing else.
True, an application framework can simplify the coding, and many people use them. It would be feasible
to build such a framework with lcc-win32, but … I will leave this problem “as an exercise to the reader”…
236 C Tutorial
After pressing the “next” button till the dialog boxes disappear, we press F9, compile the
whole, and we run it. We see a single white window, with a status bar at the bottom, a sum-
114. In the data processing field, we have been always recycling very old ideas as “new, just improved”.
Object oriented programming was an idea that came from the old days of Simula in the beginning of the
seventies but was “rediscovered” or rather “reinvented” in the late 80s. Garbage collection was standard in
lisp systems in the seventies, and now has been discovered again by Mr. Bill Gates, in the next century, with
his proposal for the C# language.
115. Remember the basics of Boolean logic: a bit ANDed with another will be one only if both bits are 1.
A bit Ored with another with return 1 only if one or both of them is 1.
A more advanced window 237
mary menu, and nothing else. Well, this is the skeleton of a windows application. Let’s see it
in more detail.
We start as always in the same place. We go to the WinMain function, and we try to figure out
what is it doing. Here it is:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, INT nCmdShow)
{
MSG msg;
HANDLE hAccelTable;
// Saves in this global variable the instance handle, that must be passed as an
// argument to many window functions.
hInst = hInstance;
// If the initialization of the application fails, WinMain exits
if (!InitApplication())
return 0;
// Loads the keyboard accelerators for common menu options
hAccelTable = LoadAccelerators(hInst,MAKEINTRESOURCE(IDACCEL));
// Creates the main window, and exits if it can’t be created
if ((hwndMain = CreateApplWndClassWnd()) == (HWND)0)
return 0;
// Creates the status bar at the bottom of the main window
CreateSBar(hwndMain,"Ready",1);
// Shows the main window
ShowWindow(hwndMain,SW_SHOW);
// Starts the message loop. When the main window post the quit message, GetMessage
// will return NULL
while (GetMessage(&msg,NULL,0,0)) {
if (!TranslateAccelerator(msg.hwnd,hAccelTable,&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Returns the wParam of the WM_QUIT message
return msg.wParam;
}
We have the same schema that we saw before, but this time with some variations. We start the
application (registering the window class, etc.), we load the keyboard accelerators, we create
the window, the status bar, we show our window, and then we enter the message loop until we
receive a WM_QUIT, that breaks it. We return the value of the “wParam” parameter of the last
message received (WM_QUIT of course).
Simple isn’t it?
Now let’s look at it in more detail.
The “InitApplication” procedure initializes the WNDCLASS structure with a little more care
now, since we are not using our DefDialogProc any more, there are a lot of things we have
to do ourselves. Mostly, that procedure uses the standard settings:
static BOOL InitApplication(void)
{
WNDCLASS wc;
memset(&wc,0,sizeof(WNDCLASS));
// The window style
wc.style = CS_HREDRAW|CS_VREDRAW |CS_DBLCLKS ;
wc.lpfnWndProc = (WNDPROC)MainWndProc;
238 C Tutorial
wc.hInstance = hInst;
// The color of the background
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "winexampleWndClass";
// The menu for this class
wc.lpszMenuName = MAKEINTRESOURCE(IDMAINMENU);
// default cursor shape: an arrow.
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
// default icon
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
if (!RegisterClass(&wc))
return 0;
// ---TODO--- Call module specific initialization routines here
return 1;
}
The style of the window used is a combination of integer constants like CS_HREDRAW, and
others, combined using the OR operator, the vertical bar. What does this mean?
This is a standard way of using bit flags in C. If you go to the definition of CS_HREDRAW
(right-click in that name and choose the “Goto definition” option), you will see that the value
is 2. Other constants like CS_DBLCLKS have a value of 8. All those numbers are a power of
two. Well, a power of two by definition will always have a single bit set. All other bits will be
zero. If you OR those numbers with each other, you will obtain a number that has the bits set
that correspond to the different values used. In our case this statement is equivalent to:
wc.style = 2 | 1 | 8;
8 ored with 1 is 1 0 0 1, ored with 2 is 1 0 1 1, what is equal to 11 in decimal notation.
This is a very common way of using flags in C. Now, if you want to know if this window is a
window that answers to double-clicks, you just have to query the corresponding bit in the style
integer to get your answer. You do this with the following construct:
if (wc.style & CS_DBLCLKS) {
}
We test in this if expression, if the value of the “style” integer ANDed with 8 is different than
zero. Since CS_DBLCLKS is a power of two, this AND operation will return the value of that
single bit.116 Note too that 1 is a power of two since 2 to the power of zero is one.
We will return to this at the end of this section.
Coming back to our initialization procedure, there are some new things, besides this style set-
ting. But this is just a matter of reading the windows documentation. No big deal. There are
many introductory books that augment their volume just with the easy way of including a lot
of windows documentation in their text. Here we will make an exception.
But what is important however is that you know how to look in the documentation! Suppose
you want to know what the hell is that CS_DBLCLKS constant, and what does it exactly mean.
You press F1 in that identifier and nothing. It is not in the index.
Well, this constant appears in the context of RegisterClass API. When we look at the docu-
mentation of RegisterClass, we find a pointer to the doc of the WNDCLASS structure. Going
there, we find in the description of the style field, all the CS_* constants, neatly explained.
Note that not all is in the index. You have to have a feeling of where to look. Lcc-win32 comes
with a help file of reasonable size to be downloaded with a standard modem. It is 13MB com-
pressed, and it has the essentials. A more detailed documentation complete with the latest stuff
is in the Software Development Kit (SDK) furnished by Microsoft. It is available at their Web
site, and it has a much more sophisticated help engine.
A more advanced window 239
The WinMain procedure is the right place for initializing other things like CoInitialize() if you
are going to use COM, or WSAStartup() if you are going to use the sockets layer, etc. The
command line parameters are available in the lpCmdLine parameter.
The messages for the main window are handled in the MainWndProc function, that is passed
as the message handling function when registering the window class. The standard version
looks like this: each event is mapped to a case in a big switch of possible events.
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam)
{
switch (msg) {
case WM_SIZE:
// Windows has been resized. Resize any child windows here
SendMessage(hWndStatusbar,msg,wParam,lParam);
InitializeStatusBar(hWndStatusbar,1);
break;
case WM_MENUSELECT:
// The user is browsing the menu. Here you can add code
// to display some text about the menu item being browsed for instance
return MsgMenuSelect(hwnd,msg,wParam,lParam);
// The WM_COMMAND message reports an item of the menu has been selected.
case WM_COMMAND:
HANDLE_WM_COMMAND(hwnd,wParam,lParam,MainWndProc_OnCommand);
break;
// This message reports that the window will be destroyed. Close the application now.
case WM_DESTROY:
PostQuitMessage(0);
break;
// All other messages go to the default procedure provided by Windows
default:
return DefWindowProc(hwnd,msg,wParam,lParam);
116. You may wonder what that variable “msg” stands for. It is a structure of type MSG, that is defined in
windows.h as follows:
typedef struct tagMSG
{ HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt; } MSG, *PMSG, *NPMSG, *LPMSG;
Note that in C you can append as many names to a pointer to a structure as you like, and in windows this
is used a lot. The reason is an historical one. In windows 16 bits there were several types of pointers: near (16
bit pointers), far (long pointers of 32 bits) and generally the near pointers were prefixed with NP, the 32 bit
ones with LP. This was retained for compatibility reasons until today, even if there are only 32 bit pointers
now.
This structure contains then, the following fields:
• hwnd: The handle of the specific window to which the message is directed.
• message: A 16-bit value identifying the message.
• wparam: A 32-bit value identifying the first message parameter. Its meaning depends on the
message being sent.
• lParam: A 32-bit value identifying the second message parameter.
• time: A 32-bit value identifying the time when the event that provoked this message happened.
• pt: This is a POINT structure containing the coordinates of the mouse in the instant the event
happened that provoked this message.
240 C Tutorial
}
return 0;
}
Having all this menues in place is nice but if you are used to use the keyboard something is
lacking. A nice way to use your keyboard:
After initializing the window class, the WinMain function loads the accelerators for the appli-
cation. This table is just a series of keyboard shortcuts that make easy to access the different
menu items without using the mouse and leaving the keyboard. In the resource editor you can
edit them, add/delete/change, etc. To do this you start the resource editor and you press the
“dir” button in the upper right. You will see the following display.
You click in the “Accelerator” tree tab, and you will see the following:
We have here the accelerator called IDM_EXIT that has the value of 300. This is just Ctrl+Q
for quit. The key value is 81, the ASCII value of the letter ‘q’, with a flag indicating that the
control key must be pressed, to avoid quitting just when the user happens to press the letter q
in the keyboard!
A more advanced window 241
Here you can change the accelerator as you want. The flags are explained in the documenta-
tion for the resource editor.
But this was just another digression, we were speaking about WinMain and that statement:
LoadAccelerators… Well, let’s go back to that piece of code again.
After loading the accelerators, the status bar is created at the bottom of the window, and then,
at last, we show the window. Note that the window is initially hidden, and it will be shown
only when all things have been created and are ready to be shown. This avoids screen flicker-
ing, and saves execution time. It would be wasteful to redraw the window before we created
the status bar, since we would have to do that again after it is created.
We will speak about status bar later, since that is not crucial now. What really is important is
the message loop that begins right after we call ShowWindow.
while (GetMessage(&msg,NULL,0,0)) {
if (!TranslateAccelerator(msg.hwnd,hAccelTable,&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
This loop calls the API GetMessage. If that returns TRUE, we call the API to translate the
accelerators. This API will convert a sequence of keystrokes into a WM_COMMAND mes-
sage, as it was sent by a menu, if it finds a correspondence between the keystrokes and the
accelerator table that we loaded just a few lines above. If TranslateAccelerator doesn’t find
any correspondence, we go on by calling TranslateMessage, that looks for sequences of key
pressing and key up messages, and does the dirty work of debouncing the keyboard, handling
repeat sequences, etc. At last, we dispatch our message to the procedure indicated in the win-
dow class.
And that is it. We loop and loop and loop, until we eventually get a WM_QUIT, that provokes
that GetMessage returns FALSE, and the while loop is finished.117
Wow. Finished?
We have hardly started. What is interesting now, is that we have a skeleton to play with. We
will show in the next sections how we add things like a dialog box, etc.
Summary: Windows programming looks intimidating at first. But it is just the looks. Before
we go on, however, as promised, let’s look in more details to how flags are set/unset in C.
Flags are integers where each bit is assigned a predefined meaning. Usually with a pre-
processor define statement, powers of two are assigned a symbolic meaning like in the case of
CS_DBLCLKS above. In a 32-bit integer we can stuff 32 of those. We test those flags with:
117. Never forget this: local variables do NOT retain their value from one call of the function to the next
one!
242 C Tutorial
flag |= CONSTANT;
This last statement needs further explanations. We use the AND operator with the complement
of the constant. Since those constants have only one bit set, the complement of the constant is
an integer with all the bits turned into ones except the bit that we want to unset. We AND that
with our flag integer: since all bits but the one we want to set to zero are one, we effectively
turn off that bit only, leaving all others untouched.
Customizing the wizard generated sample code 243
118. Go to “help”, then click in Win32 API, get to the index and write the name of that function.
119. Again, this is the #defined identifier of the dialog, not the dialog’s title!
120. The registry has been criticized because it represents a single point of failure for the whole system.
That is obviously true, but it provides as a redeeming value, a standard way of storing and retrieving
configuration data and options. It allows your application to use the same interface for storing this data,
instead of having to devise a schema of files for each application. The software is greatly simplified by this,
even if it is risky, as a general principle.
244 C Tutorial
MAKEINTRESOURCE( IDD_ASK_PARAMS),
ghwndMain,ParamsDlgProc);
break;
You give to that API the instance handle of the application, the numerical ID of the dialog
enclosed in the MAKEINTRESOURCE macro, the handle of the parent window, and the name of
the procedure that handles the messages for the dialog. You will need the prototype of the
function in some header file to be able to use the name of it, so it is a good idea to write a “dlg-
boxprotos.h” header file that is included in every source file of the application.
If you want to pass a parameter to the dialog box procedure you can use DialogBoxParam.
Message Meaning
Customizing the wizard generated sample code 245
Message Meaning
WM_ACTIVATE Window is being either activated or desactivated.
WM_CHAR The user has typed a character. Here you receive characters like ‘m’
for instance.
WM_KEYDOWN A key has been pressed. Here you can use keys like the arrows of the
keyboard, Del Insr, etc.
WM_KEYUP A key has been released
Message Meaning
Message Meaning
WM_SIZE Sent after a window has been resized.
WM_SIZING Sent to a window that the user is resizing. By process-
ing this message, an application can monitor the size
and position of the drag rectangle and, if needed,
change its size or position.
WM_WINDOWPOSCHANGED Sent to a window whose size, position, or place in the
Z order has changed as a result of a call to the SetWin-
dowPos function or another window-management
function
WM_WINDOWPOSCHANGING Same as above message but sent before the changes.
WM_GETMINMAXINFO Sent to a window when the size or position of the win-
dow is about to change. You can use this message to
override the window's default maximized size and
position, or its default minimum or maximum tracking
size.
WM_ENTERSIZEMOVE Sent one time to a window after it enters the moving or
sizing modal loop. The window enters the moving or
sizing modal loop when the user clicks the window's
title bar or sizing border, or when the window passes
the WM_SYSCOMMAND message to the DefWin-
dowProc function and the wParam parameter of the
message specifies the SC_MOVE or SC_SIZE value.
The operation is complete when DefWindowProc
returns.
WM_EXITSIZEMOVE Sent one time to a window, after it has exited the mov-
ing or sizing modal loop.
The following controls send a WM_COMMAND message to notify the parent window of an
event:
Control Notifications
Button Notifies the parent window when it has been selected. See the doc-
umentation for the BN_XXX messages
Combobox These controls are a combination of a list box and an edit field, so
the notifications it sends are a combination of both. See the docu-
mentation for CBN_XXX messages.
Edit These controls are used for text input. They notify the parent when
a character has been received or in general when the text has been
modified. See the EN_XXX messages
Listbox These controls display a variable length list. They notify the parent
window when an item has been selected, or clicked.
Scrollbar These controls let the user choose the amount of scrolling that
should be done for the information being displayed. They notify
the parent window when they are used.
Static These controls do not send any notifications (hence they are called
“static”). They are text strings, or lines, rectangles.
Rich edit controls They are used to display text with more than a single font or color.
They have a long list of notifications, you can even specify that
you want to be notified when the user clicks in an URL in the text.
This control uses both the WM_COMMAND interface and the
WM_NOTIFY interface.
The interface using the WM_COMMAND message was described above (See page 251).
Another interface used by many controls is the WM_NOTIFY interface. This message has the
following format:
lResult = SendMessage( // returns LRESULT
(HWND) hWndControl, // handle to parent window
(UINT) WM_NOTIFY, // message ID
(WPARAM) wParam, // = (WPARAM) (int) idCtrl;
(LPARAM) lParam // = (LPARAM) (LPNMHDR) pnmh;
);
This code is executed by the control when it sends a message to the parent window. The
important parameters of this message are:
1 idCtrl This is a numerical constant (an integer) that is used as the ID of this control.
2 pnmh This is a pointer to a structure that contains further information about the message.
It points to a NMHDR structure that contains the following fields:
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
248 C Tutorial
3 This struture can be followed by more data in some of the notifications that a control can
send. In that case this fixed fields will be membbers of a larger structure and they will be
always at the same position.
The controls that use this interface were introduced into windows later (in 1995) than the first
ones, that were present in all the earlier versions of windows. They are the following:
Control Notifications
Control Notifications
Tab controls A tab control is analogous to the dividers in a notebook or the labels
in a file cabinet. By using a tab control, an application can define
multiple pages for the same area of a window or dialog box. Each
page consists of a certain type of information or a group of controls
that the application displays when the user selects the corresponding
tab. The notifications specific to this control use the TCN_XXX pre-
fix.
Trackbar control A trackbar is a window that contains a slider and optional tick marks.
When the user moves the slider, using either the mouse or the direc-
tion keys, the trackbar sends notification messages to indicate the
change. The notifications used are only the NM_CUSTOMDRAW
and the NM_REALEASEDCAPTURE messages.
Tree views A tree-view control is a window that displays a hierarchical list of
items, such as the headings in a document, the entries in an index, or
the files and directories on a disk. Each item consists of a label and
an optional bitmapped image, and each item can have a list of sub-
items associated with it. By clicking an item, the user can expand or
collapse the associated list of subitems. The notifications sent use the
TVN_XXX prefix
Up-down control An up-down control is a pair of arrow buttons that the user can click
to increment or decrement a value, such as a scroll position or a num-
ber displayed in a companion control. The value associated with an
up-down control is called its current position. An up-down control is
most often used with a companion control, which is called a buddy
window.
The documentation for all this controls is very detailed and it would be impossible to repro-
duce here.121 Note that many of the controls not directly supported in the resource editor can
be created “by hand” by just calling the CreateWindow procedure. Most of them are not spe-
cially useful in the context of a dialog box.
To handle the messages with the WM_NOTIFY interface you should add code to handle the
message of interest in the window procedure. Suppose, for instance, that you have a tree con-
trol and you want to be notified when the user has clicked on an an item with the right mouse
button.
case WM_NOTIFY:
LPNMHDR nmhdr = (LPNMHDR)lParam;
switch (nmhdr->code) {
case NM_RCLICK:
TV_HITTESTINFO testInfo;
// The structure testInfo will be filled with the coordinates
// of the item at this particular point.
memset(&testInfo,0,sizeof(TV_HITTESTINFO));
// Get the cursor coordintes
GetCursorPos(&testInfo.pt);
pt1 = testInfo.pt;
// Translate the coordinates into coordinates of the tree window
MapWindowPoints(HWND_DESKTOP,hwndTree,&testInfo.pt,1);
// Now ask the tree view control if there is an item at this position
hti = TreeView_HitTest(hwndTree,&testInfo);
// If nothing is found stop.
if (hti == (HTREEITEM)0) break;
// There is something. Show the context menu using the information returned by
// the control in the item handle hti.
hnewMenu = CreateContextMenu(hti);
// If the creation of the menu did not work stop.
if (hnewMenu == (HMENU)0) break;
menuItem = TrackPopupMenu(hnewMenu,
TPM_RIGHTBUTTON|TPM_TOPALIGN|TPM_RETURNCMD,
pt1.x,pt1.y,
0,hwnd,NULL);
DestroyMenu(hnewMenu);
// Here we would do some action indicated by the menuItem code
break;
}
This code begins with a cast of the lParam message parameter into a pointer to a NMHDR
structure. Then it examines the passed notification code. If it is a right click it fills a
TV_HITTESTINFO structure with the coordinates of the mouse, and translates those into the
coordinates of the tree control that this code supposes in a global variable “hwndTree”. The
control should return either NULL, meaning there is no item at that position, or the handle of
an item that was right-clicked with the mouse. If there was an item, a context menu is created,
shown, and the result is an integer indicating which menu item was chosen, if any.
This is one of the many ways you can interact with the window system. Each control will have
its own interface since they are all different and perform quite different actions.
You can see the above code in action when you click with the right button in an item of the
project workspace window in Wedit.
You use the CreateWindow API with a predefined window class. You pass it as the parent-
window parameter the handle of the window where you want to put the control on. For
instance if you want to create somewhere in your window an edit field, you would write:
hEditWnd = CreateWindow("EDIT", // window class is “edit”
NULL,
WS_CHILD | WS_VISIBLE |
ES_MULTILINE |
WS_VSCROLL | WS_HSCROLL |
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0,
0,
(Rect.right - Rect.left), //Assume a rectangle structure
(Rect.bottom - Rect.top), // that contains the coordinates
hParentWnd,
(HMENU) 14376, // Window identifier
hInst, // Application instance
NULL);
The controls “edit”, “listbox” and others are already predefined when windows starts. You can
use them at any time. Some others however are NOT like that, and you need to call the API
InitCommonControlsEx() before you can use them.
A more complex example: a "clone" of spy.exe 251
switch (msg) {
case WM_CREATE:
hwndTree = CreateTree(hwnd,IDTREEWINDOW);
break;
This is "top down" design. We hide the details of the tree window creation in a function that
returns the window handle of the tree control. We save that handle in a static variable. We
declare it as static, so that we can retrieve its value at any call of MainWndProc.122
Our CreateTree function, looks like this:
static HWND _stdcall CreateTree(HWND hWnd,int ID)
{
return
CreateWindowEx(WS_EX_CLIENTEDGE,
WC_TREEVIEW,"",
WS_VISIBLE|WS_CHILD|WS_BORDER|TVS_HASLINES|
TVS_HASBUTTONS|TVS_DISABLEDRAGDROP,
0,0,0,0,
hWnd,(HMENU)ID,hInst,NULL);
}
This function receives a handle to the parent window, and the numeric ID that the tree control
should have. We call the window creation procedure with a series of parameters that are well
described in the documentation. We use the value of the hInst global as the instance, since
the code generated by Wedit conveniently leaves that variable as a program global for us to
use.
Note that we give the initial dimensions of the control as zero width and zero height. This is
not so catastrophic as it seems, since we are relying in the fact that after the creation message,
the main window procedure will receive a WM_SIZE message, and we will handle the sizing
122. In page 218. Published by St Martin’s Press. 1990. ISBN 0-312-06179-X (pbk)
252 C Tutorial
of the tree control there. This has the advantage that it will work when the user resizes the
main window too.
switch (msg) {
case WM_CREATE:
hwndTree = CreateTree(hwnd,IDTREEWINDOW);
break;
case WM_SIZE:
SendMessage(hWndStatusbar,msg,wParam,lParam);
InitializeStatusBar(hWndStatusbar,1);
GetClientRect(hwnd,&rc);
GetWindowRect(hWndStatusbar,&rcStatus);
rc.bottom -= rcStatus.bottom-rcStatus.top;
MoveWindow(hwndTree,0,0,rc.right,rc.bottom,1);
break;
We ask windows the current size of the main window with GetClientRect. This proce-
dure will fill the rectangle passed to it with the width and height of the client area, i.e. not con-
sidering the borders, title, menu, or other parts of the window. It will give us just the size of the
drawing surface.
We have a status bar at the bottom, and the area of the status bar must be subtracted from the
total area. We query this time using the GetWindowRect function, since we are interested in
the whole surface of the status bar window, not only in the size of its drawing surface. We sub-
tract the height of the window from the height that should have the tree control, and then we
move it to the correct position, i.e. filling the whole drawing surface of the window. And we
are done with drawing.
memset(&TreeCtrlItem,0,sizeof(TreeCtrlItem));
TreeCtrlItem.hParent = TVI_ROOT;
TreeCtrlItem.hInsertAfter = TVI_LAST;
TreeCtrlItem.item.mask = TVIF_TEXT | TVIF_PARAM;
TreeCtrlItem.item.pszText = "Desktop";
hNewNode = TreeView_InsertItem(hTree,&TreeCtrlItem);
Start = GetWindow(Start,GW_CHILD);
Scan(hTree,hNewNode,Start);
return 1;
}
We start at the start, and we ask windows to give us the window handle of the desktop window.
We will need the tree window handle too, so we use "GetDlgItem" with the parent window
of the tree control, and it's ID. This works, even if the parent window is a normal window, and
not a dialog window.
We go on by filling our TV_INSERTSTRUCT with the right values. This is a common inter-
face for many window functions. Instead of passing n parameters, we just fill a structure and
pass a pointer to it to the system. Of course, it is always a good idea to clean the memory space
with zeroes before using it, so we zero it with the "memset" function. Then we fill the fields
we need. We say that this item is the root item, that the insertion should happen after the last
item, that the item will contain the text "Desktop", and that we want to reserve place for a
pointer in the item itself (TVIF_PARAM). Having done that, we use the macro for inserting an
item into the tree.
The root item created, we should then scan the siblings and child windows of the desktop.
Since the desktop is the root of all windows it has no siblings, so we start at its first child. The
GetWindow function, gives us a handle to it.
3.10.6 Review
Let's look at our "BuildTree" function again and ask us:
How could this fail?
We notice immediately several things.
We always add items to the tree at the end, but we never cleanup the tree control. This means
that after a few times the user has clicked in the menu, we will have several times all the win-
dows of the system in our tree. All nodes should be deleted when we start.
The tree control will redraw itself several times when we add items. This is unnecessary and
produces a disturbing blinking in the display. We should hold the window without any redraw-
ing until all changes are done and then redraw once at the end.
We modify the "BuildTree" procedure as follows:
int BuildTree(HWND parent)
{
HWND Start = GetDesktopWindow();
HWND hTree = GetDlgItem(parent,IDTREEWINDOW);
TV_INSERTSTRUCT TreeCtrlItem;
HTREEITEM hNewNode;
SendMessage(hTree,WM_SETREDRAW,0,0);
A more complex example: a "clone" of spy.exe 255
TreeView_DeleteAllItems(hTree);
memset(&TreeCtrlItem,0,sizeof(TreeCtrlItem));
TreeCtrlItem.hParent = TVI_ROOT;
TreeCtrlItem.hInsertAfter = TVI_LAST;
TreeCtrlItem.item.mask = TVIF_TEXT | TVIF_PARAM;
TreeCtrlItem.item.pszText = "Desktop";
hNewNode = TreeView_InsertItem(hTree,&TreeCtrlItem);
Start = GetWindow(Start,GW_CHILD);
Scan(hTree,hNewNode,Start);
TreeView_Expand(hTree,hNewNode,TVE_EXPAND);
SendMessage(hTree,WM_SETREDRAW,1,0);
return 1;
}
We enclose all our drawing to the control within two calls to the SendMessage function, that
tell essentially the tree control not to redraw anything. The third parameter (i.e. the wParam of
the message) is a Boolean flag that indicates whether redrawing should be on or off. This
solves the second problem.
After setting the redraw flag to off, we send a command to the control to erase all items it may
have. This solves our first problem.
Here is the output of the program after we press the "Scan" menu item.
A lot of code is necessary to make this work, but thankfully it is not our code but window's.
The window resizes, redraws, etc., without any code from us.
256 C Tutorial
Using that window handle we call another function that will display the info in the status bar.
We pass all messages to the default window procedure. this is a non-intrusive approach. The
tree control could use our notifications for something. We just need to do an action when this
event happens, but we want to disturb as little as possible the whole environment.
memset(&tvi,0,sizeof(TV_ITEM));
tvi.mask = TVIF_PARAM;
tvi.hItem = hti;
TreeView_GetItem(hwndTree,&tvi);
return (HWND) tvi.lParam;
}
As you can see, it is just a matter of filling a structure and querying the control for the item. we
are interested only in the PARAM part of the item.
More complicated is the procedure for querying the window for information. Here is a simple
approach:
void SetTextInStatusBar(HWND hParent,HWND hwnd)
{
RECT rc;
HANDLE pid;
char info[4096],*pProcessName;
GetWindowRect(hwnd,&rc);
GetWindowThreadProcessId(hwnd,&pid);
pProcessName = PrintProcessNameAndID((ULONG)pid);
wsprintf(info,
"Handle: 0x%x %s, left %d, top %d, right %d, bottom %d,
height %d, width %d, Process: %s",
hwnd,
IsWindowVisible(hwnd)? "Visible" : "Hidden",
rc.left,rc.top,rc.right,rc.bottom,
rc.bottom-rc.top,
rc.right-rc.left,
pProcessName);
UpdateStatusBar(info, 0, 0);
}
The algorithm here is as follows:
Query the window rectangle (in screen coordinates).
We get the process ID associated with this window
We call a subroutine for putting the name of the process executable file given its process ID.
We format everything into a buffer
We call UpdateStatusBar, generated by wedit, with this character string we have built.
The procedure for finding the executable name beginning with a process ID is quite advanced,
and here we just give it like that.
static char * PrintProcessNameAndID( DWORD processID )
{
258 C Tutorial
And now we are done. Each time you click in an item window, the program will display the
associated information in the status bar:
Summary: There are many things that could be improved in this small program. For instance,
it could be useful to have a right mouse menu, or a dialog box with much more information
etc. This is just a blueprint to get you started however.
The whole code for this program is in the appendix 4.
260 C Tutorial
r2 = e[(π+θ) tan φ]
where r1 and r2 correspond to the intertwined spiral arms. The curvature of the galactic
arms is controlled by φ which should be about 0.2 radians for realistic results. In addi-
tion, 0 < θ < 1000 radians. For greater realism, a small amount of random jitter may be
added to the final points.
He is kind enough to provide us with a formal description of the program in some computer
language similar to BASIC. Here it is:
Notes: The program produces a double logarithmic spiral. The purpose of the random number
generator is to add jitter to the distribution of stars.
Variables:
in = curvature of galactic arm (try in = 2)
maxit = maximum iteration number
scale = radial multiplicative scale factor
cut = radial cutoff
f = final cutoff
Code:
loop1: Do i = 0 to maxit;
theta = float(i)/50;
r = scale*exp(theta*tan(in));
if r > cut then leave loop1;
x = r * cos(theta)+50;
y = r * sin(theta)+50;
call rand(randx);
call rand(randy);
PlotDotAt(x+f*randx,y+f*randy);
end
loop2: Do i = 0 to maxit;
123. The resource editor has several editors specialized for each kind of resource. You get a dialog box
editor, a menu editor, a string table editor, an accelerators editor, and an image editor. Each one is called
automatically when clicking in a resource from the menu, obtained with the dir button.
Numerical calculations in C. 261
theta = float(i)/50;
theta2 = (float(i)/50)-3.14;
r = scale*exp(theta2*tan(in));
if r > cut then leave loop2;
x = r * cos(theta)+50;
y = r*sin(theta)+50;
call rand(randx);
call rand(randy);
PlotDotAt(x+f*randx,y+f*randy);
end
This are quite clear specs. Much clearer than other “specs” you will find in your future career
as programmer… So let’s translate this into C. We can start with the following function:
void DrawGalaxy(HDC hDC,double in,
int maxit,double scale,
double cut, double f)
{
double theta, theta2, r, x, y, randx, randy;
for (int i = 0; i <= maxit; i++) { (1)
theta = ((double)i)/CENTER; (2)
r = scale*exp(theta*tan(in)); (3)
if (r > cut) break; (4)
x = r * cos(theta)+CENTER; (5)
y = r * sin(theta)+CENTER; (6)
randx = (double)rand() / (double)RAND_MAX; (7)
randy = (double)rand() / (double)RAND_MAX; (8)
PlotDotAt(hDC,x+f*randx,y+f*randy,RGB(0,0,0));
}
for (int i = 0; i <= maxit; i++) {
theta = ((double)i)/CENTER;
theta2 = ( ((double)i)/CENTER) –3.14;
r = scale * exp(theta2*tan(in)); (9)
if (r > cut) break;
x = r*cos(theta)+CENTER;
y = r*sin(theta)+CENTER;
randx = (double)rand() / (double) RAND_MAX;
randy = (double)rand() / (double) RAND_MAX;
PlotDotAt(hDC,x+f*randx,y+f*randy,RGB(255,0,0)); (10)
}
}
We translate both loops into two for statements. The exit from those loops before they are fin-
ished is done with the help of a break statement. This avoids the necessity of naming loops
when we want to break out from them, what could be quite fastidious in the long term…
I suppose that in the language the author is using, loops go until the variable is equal to the
number of iterations. Maybe this should be replaced by a strictly smaller than… but I do not
think a point more will do any difference.
Note the cast of i (double)i. Note too that I always write 50.0 instead of 50 to avoid
unnecessary conversions from the integer 50 to the floating-point number 50.0. This cast is not
necessary at all, and is there just for “documentation” purposes. All integers when used in a
double precision expression will be automatically converted to double precision by the com-
piler, even if there is no cast.
The functions exp and tan are declared in math.h. Note that it is imperative to include math.h
when you compile this. If you don’t, those functions will be assumed to be external functions
that return an int, the default. this will make the compiler generate code to read an integer
instead of reading a double, what will result in completely nonsensical results.
262 C Tutorial
whole window each time we receive the message, even if we could do better and just repaint
the rectangle that windows passes to us in that parameter. Then, we call the code to draw our
galaxy, and inform windows that we are done with painting.
Well, this finishes the coding. We need to add the
#include <math.h>
#include <time.h>
at the beginning of the file, since we use functions of the math library and the time() function
to seed the srand() function.
We compile and we obtain:
It would look better, if we make a better background, and draw more realistic arms, but for a
start this is enough.
264 C Tutorial
There are many functions for drawing under windows of course. Here is a table that provides
short descriptions of the most useful ones:
Function Purpose
There are many other functions for setting color, working with rectangles, drawing text
(TextOut), etc. Explaining all that is not the point here, and you are invited to read the docu-
mentation.
Summary: C converts integer and other numbers to double precision when used in a double
precision expression. This will be done too when an argument is passed to a function.
When the function expects a double and you pass it an int or even a char, it will be con-
verted to double precision by the compiler.
All functions that return a double result must declare their prototype to the compiler so that
the right code will be generated for them. An unprototyped function returning a double will
surely result in incorrect results!
Opaque data structures are hidden from client code (code that uses them) by providing just
a void pointer to them. This way, the client code is not bound to the internal form of the
Filling the blanks 265
structure and the designers of the system can modify it without affecting any code that uses
them. Most of the windows data structures are used this way: an opaque “HANDLE” is
given that discloses none of the internals of the object it is pointing to.
We arrive at the menu editor124. If we open each branch of the tree in its left side, it looks like
this:
124. This type of interface requires an action from the part of the user to indicate when it is finished
modifying the name and desires to “apply” the changes. Another possibility would be that the resource editor
applies the changes letter by letter as the user types them in, as some other editors do. This has the advantage
of being simpler to use, but the disadvantage of being harder to program and debug. As always, an the
appearance of the user interface is not only dictated by the user comfort, but also by the programming effort
necessary to implement it. You will see this shortly when you are faced with similar decisions.
266 C Tutorial
We have at the left side the tree representing our menu. Each submenu is a branch, and the
items in the branch; the leaves are the items of the submenu. We select the “File” submenu and
press the “insert” key. We obtain a display like this:
A new item is inserted after the currently selected one. The name is “Popup”, and the first item
is “New item”. We can edit those texts in the window at the right: We can change the symbolic
name, and set/unset several options. When we are finished, we press “Apply” to write our
changes to the resource. 125
OK, we change the default names to the traditional “Edit” and “Search”, to obtain this display:
We will name the new item IDM_SEARCH. I am used to name all those constants starting with
IDM_ from ID Menu, to separate them in my mind from IDD_ (ID Dialog).
We can now start drawing the “Search” dialog. Just a simple one: a text to search, and some
buttons to indicating case sensitivity, etc. We close the menu editor, and we start a new dialog.
125. A debugger is a program that starts another program, the “program to be debugged” or “debuggee”,
and can execute it under the control of the user, that directs the controlled execution. All C development
systems offer some debugger, and lcc-win32 is no exception. The debugger is described in more detail in the
user’s manual, and it will not be described here. Suffice to note that you start it with F5 (or Debugger in the
compiler menu), you can single step at the same level with F4 and trace with F8. The debugger shows you in
yellow the line the program will execute next, and marks breakpoints with a special symbol at the left. Other
debuggers may differ from this of course, but the basic operations of all of them are quite similar. Note that
lcc-win32 is binary compatible with the debugger of Microsoft: you can debug your programs using that
debugger too.
To be able to use the debugger you need to compile with the g2 flag on. That flag is normally set by
default. It directs the compiler to generate information for the debugger, to enable it to show source lines and
variable values. The compiler generates a whole description of each module and the structures it uses called
“debug information”. This information is processed by the linker and written to the executable file. If you turn
the debugging flag off the debugger will not work. The best approach is to leave this flag on at all times.
Obviously the executable size will be bigger, since the information uses up space on disk. If you do not want
it, you can instruct the linker to ignore it at link time. In this way, just switching that linker flag on again will
allow you to debug the program.
The debug information generated by lcc-win32 uses the NB09 standard as published by Microsoft and
Intel. This means that the programs compiled with lcc-win32 can be debugged using another debugger that
understands how to use this standard.
Filling the blanks 267
In the “Resources” submenu, we find a “New” item, with several options in it. We choose the
“dialog” option.
The dialog editor builds an empty dialog and we are shown the following parameters dialog:
Even if this quite overwhelming, we are only interested in two things: the title of the dialog
and the symbolic identifier. We leave all other things in their default state. We name the dialog
IDD_SEARCH, and we give it the title “Text search”. After editing it looks like this:
We press the OK button, and we do what we did with the dialog in the DLL, our first example.
The finished dialog should look roughly like this:
An edit field, and two push button for OK and Cancel. The edit field should receive the ID
IDTEXT.
Now comes the interesting part. How to connect all this?
We have to first handle the WM_COMMAND message, so that our main window handles the
menu message when this menu item is pressed. We go to our window procedure MainWnd-
Proc. Here it is:
268 C Tutorial
break;
case IDM_EXIT:
PostMessage(hwnd, WM_CLOSE,0,0);
break;
}
}
When we receive the menu message then, we call our dialog. Since probably we will make
several dialogs in our text editor, it is better to encapsulate the difficulties of calling it within
an own procedure: CallDialog. This procedure receives the numeric identifier of the dialog
resource, the function that will handle the messages for the dialog, and an extra parameter for
the dialog, where it should put the results. We assume that the dialog will return TRUE if the
user pressed OK, FALSE if the user pressed the Cancel button.
If the user pressed OK, we search the text within the text that the editor has loaded in the func-
tion DoSearch.
How will our function CallDialog look like?
Here it is:
int CallDialog(int id,DLGPROC proc,LPARAM parameter)
{
int r = DialogBoxParam(hInst,MAKEINTRESOURCE(id),
hwndMain, proc, parameter);
return r;
}
We could have returned the value of the DialogBoxParam API immediately but I like storing
function return values in local variables. You never know what can happen, and those values
are easily read in the debugger.
We have to write a dialog function, much like the one we wrote for our string DLL above. We
write a rough skeleton, and leave the details for later:
BOOL CALLBACK SearchDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
switch (msg) {
case WM_INITDIALOG:
return TRUE;
case WM_CLOSE:
EndDialog(hwnd,0);
break;
}
return FALSE;
}
This does nothing, it doesn’t even put the text in the received parameter, but what we are inter-
ested in here, is to first ensure the dialog box shows itself. Later we will refine it. I develop
software like this, as you may have noticed: I try to get a working model first, a first approxi-
mation. Then I add more things to the basic design. Here we aren’t so concerned about design
anyway, since all this procedures are very similar to each other.
The other procedure that we need, DoSearchText, is handled similarly:
int DoSearchText(char *txt)
{
MessageBox(NULL,"Text to search:",txt, MB_OK );
return 1;
}
We just show the text to search. Not very interesting but…
270 C Tutorial
We compile, link, the main application window appears, we select the “Search” menu, and…
we see:
What’s wrong?????
Well, we have inverted the parameters to the MessageBox procedure, but that’s surely not
the point. Why is the dammed dialog box not showing?
126
Well, here we need a debugger. We need to know what is happening when we call the dia-
log box. We press F5, and we start a debugging session. The debugger stops at WinMain.
Now, wait a minute, our window procedure that receives the message from the system is
called indirectly from Windows, we can’t just follow the program blindly. If we did that, we
would end up in the main loop, wasting our time.
126.Why didn’t we use the DLL to ask for the string to search? Mostly because I wanted to give you an
overview of the whole process. A good exercise would be to change the program to use the DLL.
Which changes would be necessary? How would you link?
Filling the blanks 271
No, we have to set a breakpoint there. We set a breakpoint when we call the dialog using the
F2 accelerator key. We see that Wedit sets a sign at the left to indicate us that there is a break-
point there. Then we press F5 again to start running the program.
Our program starts running, we go to the menu, select the search item, and Wedit springs into
view. We hit the breakpoint. Well that means at least that we have correctly done things until
here: the message is being received. We enter into the CallDialog procedure using the F8
accelerator. We step, and after going through the DialogBoxParam procedure we see no dialog
and the return result is –1. The debugger display looks like this:
We see the current line highlighted in yellow, and in the lower part we see the values of some
variables. Some are relevant some are not. Luckily the debugger picks up r as the first one. Its
value is –1.
272 C Tutorial
Why –1?
A quick look at the doc of DialogBoxParam tells us “If the function fails, the return value
is -1.”
Ahh, how clear. Yes of course, it failed. But why?
Mystery. There are no error codes other than just general failure. What could be wrong?
Normally, this –1 means that the resource indicated by the integer code could not be found. I
have learned that the hard way, and I am writing this tutorial for you so that you learn it the
easy way. The most common cause of this is that you forgot to correctly give a symbolic name
to your dialog.
We close the debugger, and return to the resource editor. There we open the dialog box proper-
ties dialog box (by double clicking in the dialog box title bar) and we see… that we forgot to
change the name of the dialog to IDM_SEARCH!!! We correct that and we try again.
Filling the blanks 273
The rest is quite trivial most of the wok was done when building the DLL. Actually, the dialog
box is exactly the same.127
127. Newer versions of Wedit check for this. Older ones aren’t so sophisticated so please take care.
274 C Tutorial
This dialog shows you the files that will be generated or used by Wedit. Those that will be
generated have a box at the right, to enable or disable their generation. The others are used if
available, but only for reading. The last item is an additional path to look for bitmaps, icons
and other stuff that goes into resources.
You notice that the first item is disabled. You enable it, and type the full path of a file where
you want Wedit to write the dialog procedures. Notice that by default, the name of this file is
<name of the project>.c. This could provoke that your winexample.c that we worked so hard
to write, could be overwritten.128 Choose another name like “dialogs.c” for instance.
128. Look in the weditreslib folder. The file commmsg.c contains the default procedure and all the
machinery necessary for the run time of the framework.
Using the graphical code generator 275
Now, when you save your work, all the dialog procedures will be written for you. But before,
we have to tell the framework several things.
The first will be the prefix to use for all the procedures that will be used in this dialog box. We
define this in the main properties dialog box obtained when you double click in the dialog title.
“Dlg400” is an automatic generated name, not very convincing. We can put in there a more
meaningful name like “DlgSearch” for instance. We will see shortly where this name is used.
What we want to do now is to specify to the editor where are the events that interest us. For
each of those events we will specify a callback procedure that the code generated by the editor
will call when the event arrives. Basically all frameworks, no matter how sophisticated, boil
down to that: a quick way of specifying where are the events that you want to attach some
code to.
The events we can choose from are in the properties dialog boxes, associated with each ele-
ment of the dialog box. You have the general settings in the properties window associated with
the dialog box (that appears when you double click in the title), and you have the buttons prop-
erties that appear when you right click in a button.
Those dialog boxes have generally a standard or «Properties» part that allows you to change
things like the element’s text, or other simple properties, and a part that is visible only when
you have selected the C code generation. That part is normally labeled “events” and appears at
the right. That label tells us which kind of events the framework supports: window messages.
There are many events that can possibly happen of course, but the framework handles only
those.
We see at the right tab a typical “messages” part: We have some buttons to choose the mes-
sages we want to handle, the function name prefix we want for all callback procedures for this
element, and other things like the font we want to use when the elements are displayed.
We see again that “Dlg400”… but this allows us to explain how those names are generated
actually. The names of the generated functions are built from the prefix of the dialog box, then
the prefix for the element, and then a fixed part that corresponds to the event name. We edit
the prefix again, following this convention.
The “Selected” message is on, so the framework will generate a call to a function called Dlg-
SearchOnOkSeelected().Happily for us, we do not have to type those names our-
selves.
Without changing anything else we close the button properties and save our work. We open
the c source file generated by the editor.
276 C Tutorial
switch(msg)
{
case WM_INITDIALOG:
SetWindowLong(hwnd,DWL_USER,
(DWORD)&WeditDlgParams);
DlgSearchInit(hwnd,wParam,lParam);
/* store the input arguments if any */
SetProp(hwnd,"InputArgument",(HANDLE)lParam);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
DlgSearchOnOKSelected(hwnd);
break;
}
break;
}
return(HandleDefaultMessages(hwnd,msg,wParam,lParam));
}
We have here a normal callback procedure for our dialog. It handles two messages:
WM_INITDIALOG and WM_COMMAND. The callback procedures are in bold type. There are
two of them: The initialization callback called “DlgSearchInit”, and the one we
attached to the OK button above, “DlgSearchOnOkSelected”.
There are more things in there, but for the time being we are interested in just those ones,
because they have to be written by you!
What you want to do when the dialog box has been created but it is not yet visible?
This is the purpose of the first callback. In our text search dialog we could write in the edit
field the last searched word, for instance, to avoid retyping. Or we could fill a combo box with
all the words the user has searched since the application started, or whatever. Important is that
you remember that in that function all initializations for this dialog box should be done,
including memory allocation, populating list boxes, checking check buttons or other chores.
The second callback will be called when the user presses the OK button. What do you want to
do when this event happens? In our example we would of course start the search, or setup a set
of results so that the procedure that called us will know the text to search for, and possibly
other data.
Different controls will react to different events. You may want to handle some of them. For
instance you may want to handle the event when the user presses a key in an edit field, to
check input. You can use the framework to generate the code that will trap that event for you,
and concentrate in a procedure that handles that event.
How would you do this?
Using the graphical code generator 277
You open the properties dialog of the edit control and check the “Update” message. This will
prompt the editor to generate a call to a function of yours that will handle the event. The
details are described in the documentation of the resource editor and will not be repeated here.
What is important to retain are the general principles at work here. The rest is a matter of read-
ing the docs, to find out which events you can handle for each object in the dialog, and writing
the called functions with the help of the windows documentation.
But what happens if there is an event that is not foreseen by the framework? With most frame-
works this is quite a problem, happily not here. You have just to check the button “All” in the
dialog properties and the framework will generate a call to a default procedure (named <pre-
fix>Default) at each message. There you can handle all events that the operating system sends.
I have tried to keep the framework open so that unforeseen events can be still treated correctly.
Another way to customize the framework is to modify the default procedure provided in the
library weditres.lib. The source of that procedure is distributed in the source distributions of
lcc-win32129 and is relatively easy to modify.
129. To find that easily just press F12 and click in its name in the function list.
278 C Tutorial
1) customize the appearance of a control by making some of its parts “owner draw”, i.e.
drawed by yourself.
2) Process some special windows messages that allow you to change the font, the background
color, and other characteristics by modifying the settings of a passed device context (HDC).
In both cases the interface is quite simple: Windows sends a message to the procedure of the
parent window of the control, to ask it to draw a specific part of the control. Some (or all) of
the control behavior is retained, but the appearance can be changed as you like.
If you want to return another brush than white, you should handle the WM_INITDIALOG
message to create the brush, and the WM_NCDESTROY message to destroy the brush you
created to avoid memory leaks. In this case, the above example should be augmented with:
BOOL WINAPI MyDlgProc(HWND hwnd, UINT msg,WPARAM wParam,LPARAM lParam)
{
HDC hDC;
COLORREF oldcolor;
static HBRUSH hBrush;
switch (msg) {
case WM_INITDIALOG:
hBrush = CreateSolidBrush(BK_COLOR);
// other initializations go here
...
case WM_CTLCOLORLISTBOX:
hDC = (HDC)wParam;
color = RGB(192,0,0); // red color
SetTextColor(hDC,color);
return hBrush;
...
case WM_NCDESTROY:
DeleteObject(hBrush);
break;
}
}
We create the brush at the initialization of the list box, we use it when we receive the
WM_CTLCOLORLISTBOX message, and we destroy it when we receive the
WM_NCDESTROY message.
This technique can be used not only with list boxes but with many other controls. We have:
Message Control
WM_CTLCOLORBTN Button
WM_CTLCOLOREDIT Edit field
WM_CTLCOLORDLG Dialog box
WM_CTLCOLORLISTBOX List box
WM_CTLCOLORSCROLLBAR Scroll bar
WM_CTLCOLORSTATIC Static controls (text, etc.)
WM_CTLCOLORLISTBOX + A combo box sends both messages
WM_CTLCOLOREDIT to the parent window
All this messages have the same parameters as in the list box example and the procedure is
exactly the same.
The WM_CTLCOLORSTATIC is interesting because it allows you to add text in any font and
color combination to a window or dialog by just calling SelectObject with a special font or
font size using the passed HDC of this message.
280 C Tutorial
Member Description
unsigned int CtlType This member can be one of the following values.
ODT_BUTTON button
ODT_COMBOBOX combo box
ODT_LISTBOX list box
ODT_LISTVIEW List-view control
ODT_MENU Owner-drawn menu item
ODT_STATIC static control
ODT_TAB Tab control
unsigned int CtlID Specifies the identifier of the combo box, list box, button, or static con-
trol. This member is not used for a menu item.
unsigned int itemID Specifies the menu item identifier for a menu item or the index of the
item in a list box or combo box. For an empty list box or combo box,
this member can be–1. This allows the application to draw only the
focus rectangle at the coordinates specified by the rcItem member even
though there are no items in the control
unsigned int itemAction Specifies the drawing action that windows expect us to perform. This
action is indicated with the following flags:
ODA_DRAWENTIRE The entire control needs to be drawn.
ODA_FOCUS The control has lost or gained the keyboard focus. The
itemState member should be checked to determine whether the control
has the focus.
ODA_SELECT The selection status has changed. The itemState mem-
ber should be checked to determine the new selection state.
unsigned long *itemData Specifies the application-defined value associated with the menu item.
Customizing controls 281
Member Description
unsigned int itemState State of the item after the current drawing action takes place. This can
be a combination of the following values:
ODS_CHECKED This bit is used only in a menu.
ODS_COMBOBOXEDIT The drawing takes place in the edit control
of an owner-drawn combo box.
ODS_DEFAULT The item is the default item.
ODS_DISABLED The item is to be drawn as disabled.
ODS_FOCUS The item has the keyboard focus.
ODS_GRAYED This bit is used only in a menu.
ODS_HOTLIGHT The item is being hot-tracked, that is, the item will
be highlighted when the mouse is on the item.
ODS_INACTIVE The item is inactive and the window associated with
the menu is inactive.
ODS_NOACCEL The control is drawn without the keyboard accelera-
tor cues.
ODS_NOFOCUSRECT The control is drawn without focus indicator
cues.
ODS_SELECTED The menu item's status is selected.
With all this information, we can do whatever we want inside the rcItem rectangle. Here is the
example for our problem of drawing a bitmap in each line of the combo box.
First, in our dialog procedure we handle the message WM_DRAWITEM, of course:
BOOL WINAPI MyDlgProc(HWND hwnd, UINT msg,WPARAM wParam,LPARAM lParam)
{
HDC hDC;
COLORREF oldcolor;
switch (msg) {
case WM_INITDIALOG:
LoadListboxBitmaps(hwnd);
// other initializations can follow
...
case WM_DRAWITEM:
DrawListboxLine(hwnd,(LPDRAWITEMSTRUCT)lParam);
return 1; // We processed this message
...
case WM_NCDESTROY:
DestroyListboxBitmaps(hwnd);
// other cleanup tasks can go here
}
}
Our procedure to draw each line of the dialog box can look like this:
long DrawListboxLine(HWND hwndDlg,LPDRAWITEMSTRUCT lpdis)
{
HBITMAP hbmpPicture,hbmpOld;
int y,state;
HDC hdcMem;
TEXTMETRIC tm;
RECT rcBitmap;
COLORREF oldcolor;
char tchBuffer[200];
switch (lpdis->itemAction) {
case ODA_SELECT:
case ODA_DRAWENTIRE:
// Windows DOES NOT erase the background of owner draw list boxes.
// Erase the background to start with a coherent state.
FillRect(lpdis->hDC,&lpdis->rcItem,
GetStockObject (WHITE_BRUSH) );
// We get the handle of the bitmap to draw from the item-data slot in each
// list box line. The initialization procedure sets this
hbmpPicture =(HBITMAP)SendMessage(lpdis->hwndItem,
LB_GETITEMDATA, lpdis->itemID, (LPARAM) 0);
darker blue for drawing the background of the characters, and black, to draw the space
284 C Tutorial
between each character.130 Our drawing procedure “DoPaint” will make a mapping between
the bitmap colors and the color we want to give to our simulated display.
To draw the characters, we will make a memory display context, where we will store the bit-
map of characters. When we want to draw one char, we will copy from the memory bitmap to
the display window the portion of the bitmap that contains our character, translating the colors
as we go. We have to take care of scrolling if necessary, and many other things. The purpose
here is not to show you how you implement the best ever lcd display in a PC, but to show the
interfaces of a custom control so we will keep the logic to the bare minimum.
12 ColorMap[0].from = SEGM_COLORS[0];
13 ColorMap[0].to = PixelOnColor;
14 ColorMap[1].from = SEGM_COLORS[1];
15 ColorMap[1].to = PixelOffColor;
16 ColorMap[2].from = SEGM_COLORS[2];
17 ColorMap[2].to = PixelBackColor;
18 bmp = CreateMappedBitmap(hInst, ResourceId, 0, ColorMap, 3);
19 dcMemory = CreateCompatibleDC(hDC);
20 oldBitmap = SelectObject(dcMemory,bmp);
21 HBRUSH hbBkBrush = CreateSolidBrush(PixelBackColor);
22 GetClientRect(hwnd,&m_rect);
23 FillRect(hDC, &m_rect, hbBkBrush);
24 x = y = 0;
25 for (int ix = 0; ix < len; ix++){
26 GetCharBmpOffset(&rc, (char)Text[ix],CharacterWidth,
27 CharacterXSpacing,CharacterHeight,CharacterYSpacing);
28 BitBlt(hDC,x, y, (CharacterWidth + CharacterXSpacing),
29 (CharacterHeight+CharacterYSpacing), dcMemory, rc.left,
30 rc.top, SRCCOPY);
31 x += CharacterWidth + CharacterXSpacing;
32 charcount++;
33 if ((charcount == MaxXCharacters) && MaxYCharacters == 1)
34 break;
35 else if ((charcount == MaxXCharacters) && MaxYCharacters > 1)
36 {
37 if (linecount == MaxYCharacters)
38 break;
39 x = charcount = 0;
40 y += CharacterHeight + CharacterYSpacing;
41 linecount++;
42 }
43 }
44 EndPaint(hwnd,&ps);
45 SelectObject(dcMemory,oldBitmap);
46 DeleteDC(dcMemory);
47 DeleteObject(bmp);
48 DeleteObject(hbBkBrush);
}
The first thing our procedure does is to obtain a device context from Windows (2). This device
context is already clipped to the area we should draw. We suppose that the text of the lcd is
stored in the variable “Text”, and we get its length (3).
To make the translation from the colors in the bitmap to the colors of our lcd window we use
the primitive CreateMappedBitmap, that translates a bitmap with a set of source colors into a
bitmap with the colors translated according to a translation table. This table is prepared in lines
12 to 17, then passed as argument to the API.
Now we have to create our memory device context. We make a compatible context in line 19,
and then select into it the bitmap we have just obtained, after color translation. We will use this
memory bitmap to copy bits into our display.
Before that, however, we make a solid brush and fill the whole window with the background
color (lines 21-23).
For each character in our text, we do:
1 Calculate a rectangle with the position in our memory bitmap of the bits of the given
character. Since we assume that all characters have the same width, it is a simple
multiplication of a known constant by the width of the characters. We leave that calculation
to the GetCharBmpOffset function (lines 26-27).
2 Using that rectangle, we copy the bits from our memory bitmap to the display using the
BitBlt API (lines 28-30).
3 The rest of the lines in the loop updates the position of the destination, and take care of
advancing to the next line.
4 Lines 44-48 concern the cleanup. We tell windows that we are finished with drawing
using the EndPaint API (line 44). We deselect our bitmap from the memory device context
(line 45), then we delete the memory DC. Note that we must do things in that order, since a
bitmap can’t be deleted if it is selected in a DC. Then, we delete the background brush we
created.
286 C Tutorial
1 HKEY_CLASSES_ROOT. Registry entries under this key define types (or classes) of
documents and the properties associated with those types. Shell and COM applications use
the information stored under this key. File viewers and user interface extensions store their
OLE class identifiers in HKEY_CLASSES_ROOT, and in-process servers are registered in this
key. To change the settings for the interactive user, store the changes under
HKEY_CURRENT_USER\Software\Classes rather than HKEY_CLASSES_ROOT.
2 HKEY_CURRENT_USER. Registry entries under this key define the preferences of the
current user. These preferences include the settings of environment variables, data about
program groups, colors, printers, network connections, and application preferences. This
key makes it easier to establish the current user's settings; the key maps to the current user's
branch in HKEY_USERS. In HKEY_CURRENT_USER, software vendors store the current user-
specific preferences to be used within their applications. Lcc-win32, for example, creates
the HKEY_CURRENT_USER\Software\lcc key for its applications to use, with each
application creating its own subkey under the lcc key.
3 HKEY_LOCAL_MACHINE. Registry entries under this key define the physical state of
the computer, including data about the bus type, system memory, and installed hardware
and software. It contains subkeys that hold current configuration data, including Plug and
Play information (the Enum branch, which includes a complete list of all hardware that has
ever been on the system), network logon preferences, network security information,
The Registry 287
software-related information (such as server names and the location of the server), and
other system information. Note that you must be the administrator user to be able to modify
this tree.
4 HKEY_USERS. Registry entries subordinate to this key define the default user
configuration for new users on the local computer and the user configuration for the current
user.
5 HKEY_CURRENT_CONFIG. Contains information about the current hardware profile of
the local computer system. The information under HKEY_CURRENT_CONFIG describes only
the differences between the current hardware configuration and the standard configuration.
Information about the standard hardware configuration is stored under the Software and
System keys of HKEY_LOCAL_MACHINE. Actually, this key is an alias for
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Hardware Profiles\Current.
This keys provide you with an entry point into the registry. To open a key, you must supply a
handle to another key in the registry that is already open. The system defines predefined keys
that are always open. Those keys help you navigate in the registry and make it possible to
develop tools that allow a system administrator to manipulate categories of data. Applications
that add data to the registry should always work within the framework of predefined keys, so
administrative tools can find and use the new data.
DWORD i, j;
DWORD retCode, retValue;
CHAR achValue[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME;
CHAR achBuff[80];
SendMessage(GetDlgItem(hDlg, IDL_LISTBOX),
LB_ADDSTRING, 0, (LONG) "..");
SetCursor(LoadCursor(NULL, IDC_WAIT));
for (i = 0, retCode = ERROR_SUCCESS;
retCode == ERROR_SUCCESS; i++)
{
retCode = RegEnumKeyEx(hKey,
i,
achKey,
MAX_PATH,
NULL,
NULL,
NULL,
&ftLastWriteTime);
if (retCode == (DWORD)ERROR_SUCCESS)
{
SendMessage(GetDlgItem(hDlg, IDL_LISTBOX),
LB_ADDSTRING, 0, (LONG) achKey);
}
}
SetCursor(LoadCursor (NULL, IDC_ARROW));
if (cValues)
{
for (j = 0, retValue = ERROR_SUCCESS;
j < cValues; j++)
{
cchValue = MAX_VALUE_NAME;
achValue[0] = '\0';
retValue = RegEnumValue(hKey, j, achValue,
&cchValue,
NULL,
NULL, // &dwType,
NULL, // &bData,
NULL); // &bcData
if (!lstrlen(achValue))
lstrcpy(achValue, "<NO NAME>");
wsprintf(achBuff, "%d) %s ", j, achValue);
SendMessage(GetDlgItem(hDlg,IDL_LISTBOX2),
LB_ADDSTRING, 0, (LONG) achBuff);
}
}
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
Functionality Key
3.17 Etc.
At last count, there was around 17000 APIs in the file \lcc\lib\apilist.txt. This file is used by
the linker to figure out in which library a given API is found. You can look at it to get an
impression of the richness of the windows API. There are functions for many kinds of stuff,
and here I will just try to give a very superficial overview of all that. You are invited to down-
load the SDK documentation (for free) from the Microsoft MSDN site.
Lcc-win32 gives you access to all this:
Clipboard Just that. A common repository for shared data. Quite a few
formats are available, for images, sound, text, etc.
Dynamically linked Yes, I know. It is hot in DLL Hell. But at least you get separate
libraries (DLLs) modules, using binary interfaces that can be replaced one by one.
This can lead to confusion, but it is inherent in the method.
File Systems Journaling file systems, NTFS, FAT32. As you like it.
Graphics Windows are graphical objects. The GDI machinery allows you to
draw simple objects with lines or regions, but you can go to higher
dimensions with DirectX or OpenGl.
Handles and Objects Objects that the system manages (windows, files, threads, and
many others) are described by a numerical identifier. A handle to
the object.
Hooks Install yourself in the middle of the message queue, and hear what
is being passed around: you receive the messages before any other
window receives them.
Inter-Process Com- Client/Server, and many other forms of organizing applications are
munications available. You have all the primitives to do any kind of
architecture. Synchronization, pipes, mailslots, you name it.
Network Yes, TCP/IP. Send data through the wire; develop your own
protocol on top of the basic stuff. You have all the tools in here.
Virtual memory Use those megabytes. They are there anyway. Build huge tables of
data. Use virtual memory, reserve contiguous address space, etc.
3.17.1 Clipboard
The data in the clipboard is tagged with a specific format code. To initiate the data transfer to
or from the clipboard you use OpenClipboard, GetClipboardData allows you to read
it, SetClipboardData to write it, etc. You implement this way the famous Cut, Copy and
Paste commands that are ubiquitous in most windows applications. Predefined data formats
exist for images (CF_BITMAP,CF_METAFILEPICT, CF_TIFF), sound (CF_WAVE,
CF_RIFF), text (CF_TEXT), pen data (CF_PENDATA), a list of files (CF_HDROP) and sev-
eral others.
When you pass a block of data to the clipboard, that memory should be allocated, since win-
dows expects that it can free it when it is no longer used. Since it is the clipboard that should
free the allocated memory, you can’t use the standard allocation functions, you should use the
GlobalAlloc API to get memory to be used by the clipboard.
To transfer a character string to the clipboard then, we could use the following function:
int CopyToClipboard(char *str,HWND hwnd)
{
int len = strlen(str)+1;
HANDLE h;
h = GlobalAlloc(GHND|GMEM_DDESHARE,len);
if (h == (HANDLE)0)
Etc. 293
return 0;
txt = GlobalLock(h);
strcpy(txt,str);
if (OpenClipboard(hwnd)) {
EmptyClipboard();
SetClipboardData(CF_TEXT,h);
CloseClipboard();
return TRUE;
}
return FALSE;
}
We allocate space for our character string, then obtain a pointer from the given handle. We can
then copy the data into this memory block.
We obtain access to the clipboard by opening it giving it the handle of a window that will own
the clipboard. If the operation succeeds, we clear the previous contents, set our data and we are
done.
if (hCom == INVALID_HANDLE_VALUE)
{
294 C Tutorial
if (!fSuccess)
{
// Handle the error.
printf ("GetCommState failed with error %d.\n", GetLastError());
return (2);
}
// Fill in DCB: 57,600 bps, 8 data bits, no parity, and 1 stop bit.
if (!fSuccess)
{
// Handle the error.
printf ("SetCommState failed with error %d.\n", GetLastError());
return (3);
}
3.17.3 Files
Besides the classical functions we have discussed in the examples, Windows offers you more
detailed file control for accessing file properties, using asynchronous file input or output, for
managing directories, controlling file access, locking, etc. In a nutshell, you open a file with
CreateFile, read from it with ReadFile, write to it with WriteFile, close the connection to it
with CloseHandle, and access its properties with GetFileAttributes. Compared with the simple
functions of the standard library those functions are more difficult to use, since they require
more parameters, but they allow you a much finer control. Here is a very small list of some of
the file functions provided. For more information read the associated documentation for the
functions given here.
Etc. 295
One thing you will wonder is how do you get a HANDLE starting with a FILE pointer. We
CreateFile Opens or creates a file. It can open a COM port (serial communica-
tions) or a pipe.
CloseHandle Use it to close a file.
ReadFile or ReadFi- Reads data from a file, starting at the current file pointer. Both syn-
leEx chronous and asynchronous versions exist.
WriteFile or Write- Writes data to a file, starting at the current file pointer. Both syn-
FileEx chronous and asynchronous versions exist.
CopyFile or CopyFi- Copy one file to another.
leEx
MoveFile or Move- Moves a file.
FileEx
DeleteFile Deletes a file.
GetFileAttributes or Reads the file attributes like if its read only, hidden, system, etc.
GetFileAttributesEx
SetFileAttributes or Sets the file attributes
SetFileATtributesEx
GetFileSize Returns the size of a file.
FindFirstFile and Searches for a file matching a certain wildcard.
FindNextFile
discussed above files and used always a FILE structure as specified in <stdio.h>. To obtain a
HANDLE that can be used with the windows file functions you write:
#include <windows.h>
#include <stdio.h>
FILE *fptr;
HANDLE h = (HANDLE)_get_osfhandle(_fileno(fptr));
1) NTFS.NTFS is the preferred file system on Windows. It was designed to address the
requirements of high-performance file servers and server networks as well as desktop
computers, and in doing so, address many of the limitations of the earlier FAT16 and
FAT32 file systems.
296 C Tutorial
2) FAT32 The File Allocation Table (FAT) file system organizes data on fixed disks and
floppy disks. The main advantage of FAT volumes is that they are accessible by MS-DOS,
Windows, and OS/2 systems. FAT is the only file system currently supported for floppy
disks and other removable media. FAT32 is the most recently defined FAT-based file
system format, and it's included with Windows 95 OSR2, Windows 98, and Windows
Millennium Edition. FAT32 uses 32-bit cluster identifiers but reserves the high 4 bits, so in
effect it has 28-bit cluster identifiers.
3) UDF file system. The implementation is compliant with ISO 13346 and supports UDF
versions 1.02 and 1.5. OSTA (Optical Storage Technology Association) defined UDF in
1995 as a format to replace CDFS for magneto-optical storage media, mainly DVD-ROM.
UDF is included in the DVD specification and is more flexible than CDFS.
3.17.5 Graphics
GDI is the lowest level, the basic machinery for drawing. It provides you:
• Bitmap support
• Brush support for painting polygons.
• Clipping that allows you to draw within the context of your window without worrying
that you could overwrite something in your neighbor’s window. Filled shapes, polygons
ellipses, pie rectangle, lines and curves.
• Color management, palettes etc.
• Coordinate spaces, and transforms.
• Text primitives for text layout, fonts, captions and others.
• Printing
But higher levels in such a vast field like graphics are surely possible. Lcc-win32 offers the
standard jpeg library of Intel Corp to read/write and display jpeg files. Under windows you
can do OpenGl, an imaging system by Silicon Graphics, or use DirectX, developed by
Microsoft.
table that can be used to send information to/from one process to another: instead of
sending a string, the processes send the atom id.
• Clipboard. This is the natural way to do inter-process communications under windows:
Copy and Paste.
• Mailslots. A mailslot is a pseudofile; it resides in memory, and you use standard Win32
file functions to access it. Unlike disk files, however, mailslots are temporary. When all
handles to a mailslot are closed, the mailslot and all the data it contains are deleted. A
mailslot server is a process that creates and owns a mailslot. A mailslot client is a
process that writes a message to a mailslot. Any process that has the name of a mailslot
can put a message there. Mailslots can broadcast messages within a domain. If several
processes in a domain each create a mailslot using the same name, the participating
processes receive every message that is addressed to that mailslot and sent to the
domain. Because one process can control both a server mailslot handle and the client
handle retrieved when the mailslot is opened for a write operation, applications can
easily implement a simple message-passing facility within a domain.
• Pipes. Conceptually, a pipe has two ends. A one-way pipe allows the process at one end
to write to the pipe, and allows the process at the other end to read from the pipe. A two-
way (or duplex) pipe allows a process to read and write from its end of the pipe.
• Memory mapped files can be used as a global shared memory buffer.
3.17.8 Mail
The Messaging API (MAPI) allows you to program your messaging application or to include
this functionality into your application in a vendor-independent way so that you can change
the underlying message system without changing your program.
3.17.9 Multimedia
Audio. You can use Mixers, MIDI, and waveform audio using MCI.DirectSound offers a more
advanced sound interface.
Input devices. You can use the joystick, precise timers, and multimedia file input/output.
Video. Use AVI files to store video sequences, or to capture video information using a simple,
message-based interface.
3.17.10 Network
Windows Sockets provides you will all necessary functions to establish connections over a
TCP/IP network. The TCPIP subsystem even supports other protocols than TCPIP itself. But
whole books have been written about this, so here I will only point you to the one I used when
writing network programs: Ralph Davis “Windows NT Network programming”, from Addi-
son Wesley.
For an example of network functions see “Retrieving a file from the internet” page 306.
3.17.11 Hooks
A hook is a mechanism by which a function can intercept events (messages, mouse actions,
keystrokes) before they reach an application. The function can act on events and, in some
cases, modify or discard them. This filter functions receive events, for example, a filter func-
tion might want to receive all keyboard or mouse events. For Windows to call a filter function,
298 C Tutorial
the filter function must be installed—that is, attached—to an entry point into the operating
system, a hook (for example, to a keyboard hook). If a hook has more than one filter function
attached, Windows maintains a chain of those, so several applications can maintain several
hooks simultaneously, each passing (or not) its result to the others in the chain.
3.17.13 Services
A service application conforms to the interface rules of the Service Control Manager (SCM).
A user through the Services control panel applet can start it automatically at system boot, or by
an application that uses the service functions. Services can execute even when no user is
logged on to the system
1) Splash screens. Transmitting a splash screen to a Terminal Services client consumes extra
network bandwidth and forces the user to wait before accessing the application.
3.17.15 Windows
Here is a short overview of the types of controls available to you.
Control Description
Radio buttons Used for choosing one among several possible choices.
Animation
controls
Display AVI files
Used to pack several dialog boxes into the same place, avoiding
Property Sheets
user confusion by displaying fewer items at the same time.
BYTE * pSrc = 0,
* pDst = 0;
char * pszSrcFileName = 0,
* pszDstFileName = 0;
}
/*
Now we create a file mapping for the destination file using the size parameters we got above.
*/
hDstMap = CreateFileMapping (hDstFile, 0,
PAGE_READWRITE,
liSrcFileSize.HighPart,
liSrcFileSize.LowPart, 0);
if (!hDstMap)
{
DEBUG_PRINT("couldn't map destination file\n");
goto DONE;
}
/*
Now that we have the source and destination mapping objects, we build two map views of the
source and destination files, and do the file copy.
To minimize the amount of memory consumed for large files and make it possible to copy files
that couldn't be mapped into our virtual address space entirely (those over 2GB), we limit the
source and destination views to the smaller of the file size or a specified maximum view size
(MAX_VIEW_SIZE--which is 96K).
If the size of file is smaller than the max view size, we'll just map and copy it. Otherwise, we'll
map a portion of the file, copy it, then map the next portion, copy it, etc. until the entire file is
copied.
MAP_SIZE is 32 bits because MapViewOfFile requires a 32-bit value for the size of the view.
This makes sense because a Win32 process's address space is 4GB, of which only 2GB (2^31)
bytes may be used by the process. However, for the sake of making 64-bit arithmetic work below
for file offsets, we need to make sure that all 64 bits of limpest are initialized correctly.
*/
liBytesRemaining.QuadPart = liSrcFileSize.QuadPart;
/* This assignment sets all 64 bits to this value */
liMapSize.QuadPart = MAX_VIEW_SIZE;
do {
/* Now we start our copying loop. The “min” macro returns the smaller of two numbers. */
liMapSize.QuadPart = min(liBytesRemaining.QuadPart,
liMapSize.QuadPart)
liOffset.QuadPart = liSrcFileSize.QuadPart –
liBytesRemaining.QuadPart;
UnmapViewOfFile (pSrc);
UnmapViewOfFile (pDst);
liBytesRemaining.QuadPart -= liMapSize.QuadPart;
}
while (liBytesRemaining.QuadPart > 0);
fResult = SUCCESS;
DONE:
/* We are done, Note the error treatment of this function. We use gotos to reach the end of the
function, and here we cleanup everything. */
Advanced windows techniques 303
if(hDstFile!=INVALID_HANDLE_VALUE) CloseHandle(hDstFile);
if (hSrcMap) CloseHandle(hSrcMap);
if (hSrcFile != INVALID_HANDLE_VALUE)
CloseHandle (hSrcFile);
if (fResult != SUCCESS)
{
printf("copying %s to %s failed.\n",
pszSrcFileName, pszDstFileName);
DeleteFile (pszDstFileName);
}
return (fResult);
}
3.18.2 Letting the user browse for a folder: using the shell
A common task in many programming situations is to let the user find a folder (directory) in
the file system hierarchy. When you want to search for certain item, for instance, or when you
need to allow the user to change the current directory of your application. The windows shell
offers a ready-made set of functions for you, and the resulting function is quite short. Let’s
first see its specification, i.e. what do we want as an interface to this function.
Required is a character string where the result will be written to, and a title for the user inter-
face element. The result should be 1 if the path contains a valid directory, 0 if there was any
error, the user cancelled, whatever.
To be clear about the specifications let’s look at this example:
int main(void)
{
char path[MAX_PATH];
if (BrowseDir("Choose a directory",path)) {
printf("You have choosen %s\n",path);
}
else printf("action cancelled\n");
return 0;
}
How do we write “BrowseDir” in windows?
Here it is:
#include <shlobj.h>
#include <stdio.h>
int r = 0; (4)
if (S_OK != SHGetMalloc(&pMalloc))(5)
return 0;
memset(&browseInfo,0,sizeof(BROWSEINFO));(6)
browseInfo.hwndOwner = GetActiveWindow();(7)
browseInfo.pszDisplayName = result;(8)
browseInfo.lpszTitle = Title;(9)
browseInfo.ulFlags = BIF_NEWDIALOGSTYLE;(10)
ItemIDList = SHBrowseForFolder(&browseInfo);(11)
if (ItemIDList != NULL) {
*result = 0;
if (SHGetPathFromIDList(ItemIDList,result))(12)
{
if (result[0]) r = 1;(13)
pMalloc->lpVtbl->Free(pMalloc,ItemIDList);(14)
}
}
pMalloc->lpVtbl->Release(pMalloc);(15)
return r;
}
Small isn’t it?
Let’s see the gory details.
We need a local variable that will hold a pointer to a shell defined function that will allocate
and release memory. The shell returns us a result that needs memory to exist. We need to free
that memory, and we have to take care of using the same function that the shell uses to allocate
memory. This pointer to an interface (the malloc interface) will be in our local variable pMal-
loc.
The shell needs information about the environment, and some pointers to put the results of the
interaction with the user. We will see more of this when we fill this structure below.
The shell uses a lot of stuff, and we have to take care to avoid filling our brain with unending
details. What is an ITEMLIST? Actually I haven’t even bothered to read the docs about it,
since the only use I found is to pass it around to other shell functions.
The result of the function is initialized to zero, i.e. we will set this result to 1 only and only if
there is a valid path in the buffer.
OK. Here we start. The first thing to do then, is to get the pointer to the shell allocator. If any-
thing goes wrong there, there is absolutely nothing we can do and the best thing is to return
immediately with a FALSE result.
We have to clean up the structure (note that this is a local variable, so its contents are as yet
undefined). We use the primitive memset and set all members of the structure to zero. This is a
common idiom in C: clear all memory before using and assign to it a known value. Since the
default value of many structure members is zero, this easies the initialization of the structure
since we do not have to explicitly set them to NULL or zero.
We start the filling of the relevant members. We need to put the owner window handle in the
hwndOwner member. We get the handle of the current active window using a call to a win-
dows API.
The shell needs place to store the display name of the chosen path. Note that this is not what
we want (the full path) but just the last item in the path, i.e. the last directory. Why this is so?
Because the user could choose a path like “My documents”, and in this case we could detect
that the user has chosen a “standard well known name” for the directory. But we do not use
Advanced windows techniques 305
this feature, and just give the shell some place in our… result variable. Since we overwrite this
data later, this is harmless and avoids a new variable.132
We set the lpszTitle member to the value we passed in. This is a text that will be displayed at
the top of the user interface window that appears, and should remain the user what kind of
folder he/she is looking for.
As flags we just pass BFID_USENEWUI, meaning that we want the shell to use the improved
user interface, with drag and drop, new folder, the possibility of deleting folders, whatever.
And we are done with the filling of the structure! We just have to call our famous
SHBrowseForFolder, and assign the result to ItemIdList. Here is an image of the user
interface display that appears in a windows 2000 machine; in other versions of windows it will
look different. The user interface is quite sophisticated, and it is all at our disposal without
writing any code (well almost!). What is better; even if we had spent some months developing
a similar thing, we would have to maintain it, test it, etc. Note that you can transparently
browse the network, give a symbolic path like “My computer” or other goodies.
If the call worked, i.e. if the user interface returns a valid pointer and not NULL, we should
translate the meaningless stuff we receive into a real path, so we call SHGetPathFro-
mIDList, to do exactly that. We pass it a pointer to the result character string that we receive
as second argument.
If that function returns OK, we verify that the returned path is not empty, and if it is, we set the
result of this function to TRUE.
Now we have to clean-up. We use the COM interface pointer we received from SHGetMal-
loc, and use its only member (lpVtbl) to get into the Free function pointer member. There are
other function pointers in that structure for sure, but we do not use them in this application. We
pass to that Free function a pointer to the interface it gave to us, and then the object to free.
When we are done with a COM interface we have to remember to call a function to release it,
passing it again a pointer to the returned object. We are done now, we return the result and
exit.
How can this program fail?
There are only three API calls here, and all of them are tested for failure. In principle there is
no way this can fail, although it could fail for other reasons: it could provoke a memory leak
(for instance if we had forgotten the call to the Release method at the end), or it could use
resources that are never released (the list of identifiers that we obtain from the system, etc.
int main(void)
{
DWORD dwType = FTP_TRANSFER_TYPE_BINARY;
HANDLE hConnect,hOpen;
char *szUser,*szPass,*szHost,*szFile1,*szFile2;
szFile2 = "README";
if (!FtpGetFile (hConnect,szFile1, szFile2,
FALSE,INTERNET_FLAG_RELOAD, dwType, 0)) {
ErrorOut ("FtpGetFile");
}
else printf("Remote file %s --> local file %s",
szFile1,szFile2);
if (!InternetCloseHandle (hConnect)) {
ErrorOut ("InternetCloseHandle");
}
}
else {
ErrorOut("InternetConnect");
}
InternetCloseHandle(hOpen);
return 0;
}
This program is about 400 bytes of code and 200 bytes of data for character strings. When
linked it is only 4K long.
The whole hard part of networking is hidden behind the windows interface, provided by the
“wininet.lib” library, and the “wininet.h” header file.
Basically, to get a file from a remote machine using the FTP (File Transfer Protocol) you need
to open a connection to the remote server, send it the path of the file to retrieve, and that is it.
The rest is some logic for opening and initializing the wininet library, and taking care of possi-
ble errors.
Please be aware that all this supposes that you have a working TCP/IP network connection and
that the lower layers of the network are working. Besides, note that the paths given here may
change, etc etc. Network programming is covered in more detail in chapter 3.
#include <shobjidl.h>
#include <wchar.h>
int CreateShortCut(const char *file,char *shortcutname,char *args)
{
IShellLink* pLink;
IPersistFile* pPersistFile;
LPMALLOC ShellMalloc;
LPITEMIDLIST DesktopPidl;
char DesktopDir[MAX_PATH];
wchar_t WDesktopDir[MAX_PATH];
BSTR bstr;
// We are going to create a pidl, and it will need to be freed by the shell allocator.
// Get the shell allocator object using API SHGetMalloc function. Return zero if failure.
if(FAILED(SHGetMalloc(&ShellMalloc)))
return 0;
// You do not need to do this if CoInitialize was already done at program startup.
if(SUCCEEDED(CoInitialize(NULL)))
{
if(SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLink, (void **) &pLink)))
{
pLink->SetPath(file);
pLink->SetArguments(args);
pLink->SetShowCmd(SW_SHOW);
if(SUCCEEDED(pLink->QueryInterface(&IID_IPersistFile,
(void **)&pPersistFile)))
{
if (DesktopDir[strlen(DesktopDir)-1] != '\\')
strcat(DesktopDir,"\\");
strcat(DesktopDir,shortcutname);
mbstowcs(WDesktopDir,
DesktopDir,1+strlen(DesktopDir));
bstr = SysAllocString(WDesktopDir);
pPersistFile->Save(bstr, TRUE);
pPersistFile->Release();
SysFreeString(bstr);
Error handling under windows 309
}
pLink->Release();
}
CoUninitialize();
}
return 1;
}
int main(void)
{
CreateShortCut("c:\\program files\\lcc\\bin\\lcc.exe",
"compiler.lnk","lcc-win32 compiler");
}
As shown, this shortcut will produce the output in the desktop, so you will get a new “short-
cut” to an “obj” file... n,ot very clever. This can be solved by adding a line like this:
pLink->SetWorkingDirectory(“c:\\my documents\\lcc\\myproject”);
References:
IShellLink Interface documentation
133.This part has been adapted from an excellent article by Jon Pincus published in MSDN June 2000.
134.Simply looking at this name: “ERROR_SUCCESS” implies that there is a big problem with the peo-
ple that designed that stuff. It is an error code or a success indication?
310 C Tutorial
mean an error code. Note that the value of zero that in the boolean convention above
indicates failure, means the contrary here.
3 Returning a NULL pointer. This failure indication is used in the standard C library.
malloc, realloc, and many others return NULL for failure. As with the boolean convention,
some other means are necessary to distinguish the different kinds of failures.
4 Returning an “impossible” value. Sometimes this value is zero, for instance in the API
GetWindowsDirectory, sometimes is -1 for instance the fgets function in the C library, or
many other values. This convention becomes very difficult to follow (and memorize) when
applied to a large API like the windows API.
5 Throwing a C++ exception. This is very nice in C++, but it will never work with C or
with COM. It is illegal to throw a C++ exception in COM. Besides, the runtimes of
different compilers handle C++ exceptions in a different way, so they are incompatible with
each other. Anyway, if you work with lcc-win32 you will be spared this.
6 Using the RaiseException API. This is the normal way of using structured exception
handling in lcc-win32. It has its drawbacks too, since it doesn’t fit very well with COM, for
instance, and it is (maybe) not compatible with some other compilers. It is compatible with
Microsoft compilers, and maybe Borland, since all of them use the same binary approach to
this problem, imposed by the windows API.
7 Using some other mechanism of structured exception handling. Many other libraries
exists for structured exception handling, sometimes using setjmp/longjmp, sometimes
using the signal() mechanism, or some other conventions. If you find yourself working with
some code that does this, either you use it without any modifications or you just drop it.
Trying to fix/modify this kind of code is best left to very experienced programmers.
8 Using GetLastError() to get information about the error. This would seem evident but
in their infinite wisdom, the windows API designers decided to provide different error
codes for the same failures in the different versions of the operating system. Under
windows 95/98 the error codes would be different than the error codes for the same failure
under windows NT/2000.
When you mix all those error handling strategies in a single program, as you have to do to pro-
gram with the API, the resulting mix can be very entertaining. Consider this:
extern BOOL DoSomething(void); // Uses the boolean strategy
BOOL ok = DoSomething();
if (FAILED(ok)) // WRONG!!!
return;
The macro FAILED tests if the HRESULT is less than zero. Here is the definition:
#define FAILED(S) ((HRESULT)((S)<0))
In the above case the code for failure (0) will not be less than zero, and it will not be detected
as a failure code. Rule:
Do not use the FAILED or SUCCEEDED macros for anything other than HRESULTS!
Since you know that FALSE is defined as zero, FAILED(FALSE) is zero, not really an
expected result.
The opposite problem leads to headaches too. Always use the FAILED/SUCCEEDED mac-
ros with HRESULTS and never write things like this:
HRESULT hr;
hr = ISomething->DoSomething();
if (! hr) // WRONG!!!
return;
Error handling under windows 311
Note too that in their infinite wisdom, the designers of this API decided that S_FALSE is a
success code, NOT a failure code, and it is defined as 1 (one...!!!). This means that
SUCCEEDED(S_FALSE) == TRUE
Nice isn’t it?
Here you are allocating “a” integers in the stack when control reaches the function prologue.
There is no way for you to check if this worked or not, the only failure notification will be a
stack overflow if “a” has the wrong value.
memset(&pdlg, 0, sizeof(PRINTDLG));
pdlg.lStructSize = sizeof(PRINTDLG);
pdlg.Flags = PD_RETURNDEFAULT | PD_RETURNDC;
PrintDlg(&pdlg);
return pdlg.hDC;
}
//
// Create a copy of the current system palette.
//
HPALETTE GetSystemPalette()
{
HDC hDC;
HPALETTE hPal;
HANDLE hLogPal;
LPLOGPALETTE lpLogPal;
// Get a DC for the desktop.
hDC = GetDC(NULL);
// Check to see if you are a running in a palette-based
// video mode.
if (!(GetDeviceCaps(hDC, RASTERCAPS) & RC_PALETTE)) {
ReleaseDC(NULL, hDC);
return NULL;
}
// Allocate memory for the palette.
lpLogPal = GlobalAlloc(GPTR, sizeof(LOGPALETTE) + 256 *
sizeof(PALETTEENTRY));
if (!hLogPal)
return NULL;
// Initialize.
lpLogPal->palVersion = 0x300;
lpLogPal->palNumEntries = 256;
// Copy the current system palette into the logical palette.
GetSystemPaletteEntries(hDC, 0, 256,
(LPPALETTEENTRY) (lpLogPal->palPalEntry));
// Create the palette.
hPal = CreatePalette(lpLogPal);
// Clean up.
GlobalFree(lpLogPal);
ReleaseDC(NULL, hDC);
return hPal;
}
//
// Create a 24-bit-per-pixel surface.
//
HBITMAP Create24BPPDIBSection(HDC hDC, int iWidth, int iHeight)
{
BITMAPINFO bmi;
HBITMAP hbm;
LPBYTE pBits;
// Initialize to 0s.
ZeroMemory(&bmi, sizeof(bmi));
// Initialize the header.
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = iWidth;
bmi.bmiHeader.biHeight = iHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB; // Create the surface.
hbm = CreateDIBSection(hDC,&bmi, DIB_RGB_COLORS,&pBits,NULL, 0);
return (hbm);
Some Coding Tips 317
}
//
// Print the entire contents (including the non-client area) of
// the specified window to the default printer.
BOOL PrintWindowToDC(HWND hWnd)
{
HBITMAP hbm;
HDC hdcPrinter;
HDC hdcMemory;
HDC hdcWindow;
int iWidth;
int iHeight;
DOCINFO di;
RECT rc;
DIBSECTION ds;
HPALETTE hPal;
// Do we have a valid window?
if (!IsWindow(hWnd))
return FALSE;
// Get a HDC for the default printer.
hdcPrinter = GetPrinterDC();
if (!hdcPrinter)
return FALSE;
// Get the HDC for the entire window.
hdcWindow = GetWindowDC(hWnd);
// Get the rectangle bounding the window.
GetWindowRect(hWnd, &rc);
// Adjust coordinates to client area.
OffsetRect(&rc, -rc.left, -rc.top);
// Get the resolution of the printer device.
iWidth = GetDeviceCaps(hdcPrinter, HORZRES);
iHeight = GetDeviceCaps(hdcPrinter, VERTRES);
// Create the intermediate drawing surface at window resolution.
hbm = Create24BPPDIBSection(hdcWindow, rc.right, rc.bottom);
if (!hbm) {
DeleteDC(hdcPrinter);
ReleaseDC(hWnd, hdcWindow);
return FALSE;
}
// Prepare the surface for drawing.
hdcMemory = CreateCompatibleDC(hdcWindow);
SelectObject(hdcMemory, hbm);
// Get the current system palette.
hPal = GetSystemPalette(); // If a palette was returned.
if (hPal) { // Apply the palette to the source DC.
SelectPalette(hdcWindow, hPal, FALSE);
RealizePalette(hdcWindow);
// Apply the palette to the destination DC.
SelectPalette(hdcMemory, hPal, FALSE);
RealizePalette(hdcMemory);
}
// Copy the window contents to the memory surface.
BitBlt(hdcMemory, 0, 0, rc.right, rc.bottom,
hdcWindow, 0, 0, SRCCOPY);
// Prepare the DOCINFO.
ZeroMemory(&di, sizeof(di));
di.cbSize = sizeof(di);
di.lpszDocName = "Window Contents"; // Initialize the print job.
if (StartDoc(hdcPrinter, &di) > 0) {// Prepare to send a page.
if (StartPage(hdcPrinter) > 0) {
318 C Tutorial
AllocConsole();
hCrt = _open_osfhandle((long) GetStdHandle (
STD_OUTPUT_HANDLE),_O_TEXT );
hf = fdopen( hCrt, "w" );
*stdout = *hf;
setvbuf( stdout, NULL, _IONBF, 0 );
printf("Hello world\n");
return 0;
}
hwndControl = GetDlgItem(hwndDlg,IDBUTTON);
Using that window handle, call EnableWindow.
at a sufficient privilege level can define, redefine, or delete Win32 device mappings by calling
the DefineDosDevice() API.
return TRUE;
}
result=6;
}
else if (f == stderr) {
strncpy(buf,"stderr",buflen);
result=6;
}
else {
// Step 2: Convert the FILE pointer into an operating system handle
h = (void *)_get_osfhandle(fileno(f));
// Step 3: Create a mapped view of the file
m = CreateFileMapping(h,NULL,PAGE_READONLY,0,0,NULL);
if (m != INVALID_HANDLE_VALUE) {
pfile = MapViewOfFile(m,FILE_MAP_READ,0,0,0);
if (pfile != NULL) {
// Step 4: Use the API to get the file name from the handle
result = GetMappedFileName(GetCurrentProcess(),
pfile,buf,buflen);
UnmapViewOfFile(pfile);
}
CloseHandle(m);
}
if (result) {
// Step 5: Get the Translation for the device names
char szTemp[1024];
if (GetLogicalDriveStrings(sizeof(szTemp)-1,szTemp)) {
char szName[MAX_PATH];
char szDrive[3] = " :";
BOOL bFound = FALSE;
TCHAR* p = szTemp;
do {
// Copy the drive letter into the template string,
// removing the backslash.
*szDrive = *p;
if (bFound) {
// Reconstruct pszFilename using szTemp as scratch,
// and replace device path with our DOS path.
char szTempFile[MAX_PATH];
sprintf(buf,"%s%s", szDrive, buf+uNameLen);
}
}
}
}
}
324 C Tutorial
return result;
}
int main(void)
{
FILE *f = fopen(argv[1],"r");
char buf[1024];
if (getfilename(f,buf,sizeof(buf))) {
printf("File is %s\n",buf);
}
if (getfilename(stdin,buf,sizeof(buf))) {
printf("Stdin is %s\n",buf);
}
return 0;
}
int main(void)
{
PDH_STATUS pdhStatus = ERROR_SUCCESS;
LPTSTR szCounterListBuffer = NULL;
DWORD dwCounterListSize = 0;
LPTSTR szInstanceListBuffer = NULL;
DWORD dwInstanceListSize = 0;
LPTSTR szThisInstance = NULL;
Some Coding Tips 325
// call the function to determine the required buffer size for the data
pdhStatus = PdhEnumObjectItems(
NULL, // reserved
NULL, // local machine
"Process", // object to enumerate
szCounterListBuffer, // pass in NULL buffers
&dwCounterListSize, // an 0 length to get
szInstanceListBuffer, // required size
&dwInstanceListSize, // of the buffers in chars
PERF_DETAIL_WIZARD, // counter detail level
0);
if (pdhStatus != ERROR_SUCCESS && pdhStatus != PDH_MORE_DATA) {
printf("\nUnable to determine the buffer size required");
return 1;
}
// allocate the buffers and try the call again
// PdhEnum functions will return ERROR_SUCCESS in WIN2K, but
// PDH_MORE_DATA in XP and later.
// In either case, dwCounterListSize and dwInstanceListSize should contain
// the correct buffer size needed.
//
szCounterListBuffer = (LPTSTR)malloc (dwCounterListSize );
szInstanceListBuffer = (LPTSTR)malloc(dwInstanceListSize);
if ((szCounterListBuffer == NULL) ||
(szInstanceListBuffer == NULL)) {
printf ("\nUnable to allocate buffers");
return 1;
}
pdhStatus = PdhEnumObjectItems (NULL, // reserved
NULL, // local machine
"Process", // object to enumerate
szCounterListBuffer, // pass in NULL buffers
&dwCounterListSize, // an 0 length to get
szInstanceListBuffer, // required size
&dwInstanceListSize, // of the buffers in chars
PERF_DETAIL_WIZARD, // counter detail level
0);
if (pdhStatus == ERROR_SUCCESS){
printf ("\nRunning Processes:");
// walk the return instance list
for (szThisInstance = szInstanceListBuffer;
*szThisInstance != 0;
szThisInstance += strlen(szThisInstance) + 1) {
printf ("\n %s", szThisInstance);
}
}
if (szCounterListBuffer != NULL) free (szCounterListBuffer);
if (szInstanceListBuffer != NULL) free (szInstanceListBuffer);
return 0;
}
{
FILETIME ft;
SYSTEMTIME st;
BOOL f;
strcpy(diskname,"C:\\");
printf("%-6s %15s %15s %15s %6s\n","Drive", "Capacity",
"Available","Used","Free");
while (counter != (1+'Z')) {
diskname[0] = counter;
if (GetDiskFreeSpaceEx(diskname, &BytesAvailable,
&capacity,&userFree)) {
percent = 100.0L*(((long double)BytesAvailable)/(long
double)capacity);
used = capacity-BytesAvailable;
/*
printf formats:
%-6s format string in 6 position, left justified (negative width)
%15'lld format 64 bit in 15 positions separating digits in groups (')
%6.2Lf format long double (Lf) in 6 positions with 2 decimals
Some Coding Tips 327
*/
printf("%-6s %15'lld %15'lld %15'lld %6.2Lf%%\n",
diskname,capacity,BytesAvailable,used,percent);
TotalBytesAvailable+=BytesAvailable;
TotalCapacity+=capacity;
TotalUsed+=used;
}
counter++;
}
// Now print the totals
percent = 100.0L*(((long double)TotalBytesAvailable)/(long
double)TotalCapacity);
printf("\n%-6s %15'lld %15'lld %15'lld %6.2Lf%%\n", "Total:",
TotalCapacity,TotalBytesAvailable,TotalUsed,percent);
return 0;
}
The output of this program can look like this:
Drive Capacity Available Used Free
C:\ 6,292,303,872 2,365,452,288 3,926,851,584 37.59%
D:\ 10,487,197,696 3,563,794,432 6,923,403,264 33.98%
E:\ 31,453,437,952 17,499,627,520 13,953,810,432 55.64%
F:\ 15,726,731,264 10,327,638,016 5,399,093,248 65.67%
H:\ 569,366,528 0 569,366,528 0.00%
I:\ 31,790,673,920 27,672,530,944 4,118,142,976 87.05%
3.21.26.1 Mount
#include <windows.h>
#include <stdio.h>
#define BUFSIZE MAX_PATH
int main( int argc, char *argv[] )
{
BOOL bFlag;
char Buf[BUFSIZE]; // temporary buffer for volume name
char buf1[BUFSIZE]; // temporary buffer for volume name
DWORD nl,flags;
char fsname[512];
if( argc != 3 ) {
printf("Usage: mount <directory> <drive>\n");
printf("For example\nmount c:\\mnt\\cdrom g:\\\n");
return( -1 );
}
bFlag = GetVolumeNameForVolumeMountPoint(
argv[2], // input volume mount point or directory
Buf, // output volume name buffer
BUFSIZE // size of volume name buffer
);
if (bFlag != TRUE) {
printf( "Retrieving volume name for %s failed.\n", argv[2] );
return (-2);
}
// Check that the file system supports mounting
bFlag = GetVolumeInformation(argv[2],buf1,BUFSIZE,
NULL,&nl,&flags,fsname,256);
if (0 == (flags & FILE_SUPPORTS_REPARSE_POINTS)) {
printf("File system doesn't support mount points\n");
return (-3);
}
if (!bFlag) {
printf ("Attempt to mount %s at %s failed. Error code %d\n",
argv[2], argv[1],GetLastError());
}
else printf("%s mounted in %s\n",argv[2],argv[1]);
return (!bFlag);
}
3.21.26.2 Umount
Once we have mounted a drive, we can unmount it with the following utility:
#include <windows.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
BOOL bFlag;
char mpath[512];
if (argc != 2)
{
printf("%s unmounts a volume from the volume mount point.\n",
argv[0]);
printf("For example:\numount c:\\mnt\\cdrom\n");
return (-1);
FAQ 329
strncpy(mpath,argv[1],sizeof(mpath)-3);
mpath[sizeof(mpath)-2] = 0;
if (mpath[strlen(mpath)-1] != '\\') {
strcat(mpath,"\\");
}
bFlag = DeleteVolumeMountPoint( mpath );
return (!bFlag);
}
3.22 FAQ
Here are some answer to questions users of lcc-win32 have asked, or questions that I imagine
you will find useful.
default:
return FALSE;
}
.
.
default:
return FALSE;
}
}
The following describes the ProgressYield procedure, which should be called after each unit
of the CPU-intensive subtask is completed. The ProgressYield procedure uses the IsDialog-
Message function (described in the "Microsoft Windows Software Development Kit Refer-
ence Volume 1"). IsDialogMessage will convert keyboard messages into selection commands
for the corresponding dialog box.
void ProgressYield(HWND hwnd)
{
MSG msg;
{
BITMAP bm;
COLORREF cColor;
HBITMAP bmAndBack, bmAndObject, bmAndMem, bmSave;
HBITMAP bmBackOld, bmObjectOld, bmMemOld, bmSaveOld;
HDC hdcMem, hdcBack, hdcObject, hdcTemp, hdcSave;
POINT ptSize;
hdcTemp = CreateCompatibleDC(hdc);
SelectObject(hdcTemp, hBitmap); // Select the bitmap
// to logical points
// Create a bitmap for each DC. DCs are required for a number of
// GDI functions.
// Monochrome DC
bmAndBack = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
// Monochrome DC
bmAndObject = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
// Set the background color of the source DC to the color. contained in the parts of
// the bitmap that should be transparent
cColor = SetBkColor(hdcTemp, cTransparentColor);
// Set the background color of the source DC back to the original color.
SetBkColor(hdcTemp, cColor);
// Place the original bitmap back into the bitmap sent here.
BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcSave, 0, 0, SRCCOPY);
// Determine how large each band should be in order to cover the client with 256
// bands (one for every color intensity level)
fStep = (float)rectClient.bottom / 256.0f;
• Calculate the bottom and right margins. Obtain the total size of the physical page
(including printable and unprintable areas) calling GetDeviceCaps() with the
PHYSICALWIDTH and PHYSICALHEIGHT indices in Windows NT.
• Determine the number of pixels required to yield the desired right and bottom margins
by calling GetDeviceCaps using the LOGPIXELSX and LOGPIXELSY flags.
• Calculate the size of the printable area with GetDeviceCaps() using the HORZRES and
VERTRES flags. The following code fragment illustrates steps a through c:
// Get the size of the printable area
pt.x = GetDeviceCaps(hPrnDC, PHYSICALWIDTH);
pt.y = GetDeviceCaps(hPrnDC, PHYSICALHEIGHT);
xOffsetOfRightMargin = xOffset +
GetDeviceCaps (hPrnDC, HORZRES) -
pt.x -
GetDeviceCaps (hPrnDC, LOGPIXELSX) *
wInchesWeWant;
yOffsetOfBottomMargin = yOffset +
GetDeviceCaps (hPrnDC, VERTRES) -
pt.y -
GetDeviceCaps (hPrnDC, LOGPIXELSY) *
wInchesWeWant;
NOTE: Now, you can clip all output to the rectangle bounded by xOffset, yOffset, xOffsetOf-
RightMargin, and yOffsetOfBottomMargin.
GetWindowRect(hDlg, &rc);
SetWindowPos(hDlg, NULL,
((GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2),
((GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 2),
0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
}
This code centers the dialog horizontally and vertically.
that show through the non-rectangular window. The application need only paint the window as
desired.
For more information on using SetWindowRgn, see the Win32 API documentation.
#define ID_TIMER_CLOSE0x1111
#define ID_TIMER_INIT0x1112
#define ID_TIMER_LOAD0x1113
#define ID_TIMER_DONE0x1114
TCHAR SplashWndClass[28];
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
lstrcpy(SplashWndClass,TEXT("SplashWindow"));
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
return msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
// COMMENTS:
//
// This function and its use is only necessary if you want
// this code to be compatible with Win32 systems prior to the
// 'RegisterClassEx' function that was added to Windows 95. It is
// important to call this function so that the application will
// get 'well formed' small icons associated with it.
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = SplashWndClass;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//
340 C Tutorial
hWnd = CreateWindowEx(WS_EX_TOOLWINDOW,
SplashWndClass,
NULL,
WS_OVERLAPPED,
(rect.right - rect.left - splashwidth)/2,
(rect.bottom - rect.top - splashheight)/2,
splashwidth,
splashheight,
NULL,
NULL,
hInstance,
NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
InflateRect(&rc, -15,-15);
HFONT hFont = CreateFont(-35,-35, 0, 0,0,0,0,
0,0,0,0,0,0,TEXT("Arial"));
HFONT hOldFont = (HFONT) SelectObject(hDC, hFont);
DrawText(hDC, SZ_SPLASH, lstrlen(SZ_SPLASH),
&rc, DT_WORDBREAK);
SelectObject(hDC, hOldFont);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_TIMER:
{
HDC hDC = GetDC(hWnd);
RECT rc = { 0 };
GetClientRect(hWnd, &rc);
KillTimer(hWnd, wParam);
switch (wParam)
{
case ID_TIMER_CLOSE:
DestroyWindow(hWnd);
break;
case ID_TIMER_INIT:
TextOut(hDC, rc.right-200, rc.bottom-20,
SZ_INIT, lstrlen(SZ_INIT));
break;
case ID_TIMER_LOAD:
TextOut(hDC, rc.right-200, rc.bottom-20,
SZ_LOAD, lstrlen(SZ_LOAD));
break;
case ID_TIMER_DONE:
TextOut(hDC, rc.right-200, rc.bottom-20,
SZ_CLOSE, lstrlen(SZ_CLOSE));
break;
}
ReleaseDC(hWnd, hDC);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
tion is made and a caret can be placed at that position. To place a caret at the end of the text in
a Windows edit control and set the focus to the edit control, do the following:
HWND hEdit = GetDlgItem (hDlg, ID_EDIT);
int ndx = GetWindowTextLength (hEdit);
SetFocus (hEdit);
SendMessage (hEdit, EM_SETSEL, (WPARAM)ndx, (LPARAM)ndx);
Once the caret is placed at end in the edit control, you can use the EM_REPLACESEL to
append text to the edit control. An application sends an EM_REPLACESEL message to
replace the current selection in an edit control with the text specified by the lpszReplace
(lParam) parameter. Because there is no current selection, the replacement text is inserted at
the current caret location. This example sets the selection to the end of the edit control and
inserts the text in the buffer:
SendMessage (hEdit, EM_SETSEL, (WPARAM)ndx, (LPARAM)ndx);
SendMessage (hEdit, EM_REPLACESEL, 0, (LPARAM) ((LPSTR)
szBuffer));
Another way to insert text into an edit control is to use the Windows clipboard. If the applica-
tion has the clipboard open or finds it convenient to open the clipboard, and copies the text
into the clipboard, then it can send the WM_PASTE message to the edit control to append text.
Of course, any data that was in the clipboard will be lost.
Before sending the WM_PASTE message, the caret must be placed at the end of the edit con-
trol text using the EM_SETSEL message. Below is "pseudo" code that shows how to imple-
ment this method:
OpenClipBoard () ;
EmptyClipBoard() ;
SetClipBoardData() ;
hDC = GetDC(HWND_DESKTOP);
int BitsPerPixel = GetDeviceCaps(hDC,BITSPIXEL);
int Planes = GetDeviceCaps(hDC,PLANES);
BitsPerPixel *= Planes;
bool UsesPalettes = (bool)
(GetDeviceCaps(hDC, RASTERCAPS) & RC_PALETTE);
if (UsesPalettes) {
printf("System uses palettes\n");
}
switch (BitsPerPixel) {
case 32:
FAQ 343
NOTE: Windows 95 and Windows 98 require an extra step when you redirect the standard
handles of certain child processes.
return FALSE;
if ( bFirst ) {
hwndList = (HWND) lParam ; // HWND is 32 bits.
GetWindowRect ( hwndList, &rectList );
bFirst = FALSE;
}
// Resize listbox window cx by 50 (use your size here).
MoveWindow ( hwndList, rectList.left, rectList.top,
( rectList.right - rectList.left + 50 ),
rectList.bottom - rectList.top, TRUE );
}
// Call original combo box procedure to handle other combo messages.
return CallWindowProc ( lpfnOldComboProc, hWnd, message,
wParam, lParam );
}
For example, on Windows NT, the following code fragment should propagate the changes to
the environment variables used in the Command Prompt:
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
(LPARAM) "Environment", SMTO_ABORTIFHUNG,
5000, &dwReturnValue);
None of the applications that ship with Windows 95, including Program Manager and the
shell, respond to this message. Thus, while this article can technically be implemented on
Windows 95, there is no effect except to notify third-party applications. The only method of
changing global environment variables on Windows 95 is to modify the autoexec.bat file and
reboot.
if (*fname == '"') {
memset(tmpbuf,0,sizeof(tmpbuf));
strncpy(tmpbuf,fname+1,strlen(fname)-2);
p = tmpbuf;
}
else p = fname;
a = GetFileAttributes(p);
if (a > 0 && (a & FILE_ATTRIBUTE_DIRECTORY))
return 1;
return 0;
}
if (RegQueryValueExA(hkey,"path",0,&dwType,buf,&siz) !=
ERROR_SUCCESS) {
// We can’t open this key, probably because we have no
// access to the environment or to the registry
RegCloseKey(hkey);
return ERROR_ACCESS_DENIED;
}
// Check for weird situations
if (siz <= 0) {
// This could be the case when the PATH is just not set!
RegCloseKey(hkey);
return ERROR_BAD_ENVIRONMENT;
}
// Verify that the argument is a directory
if (!IsDirectory(newpath)) {
RegCloseKey(hkey);
return ERROR_BAD_PATHNAME;
}
// Check that this entry is not already in the Path
// If the argument is enclosed by ';' or ';' + EOS
// OR is at the beginning of the path string, return zero
// (no problems) but do not add the entry a second time.
p = buf;
do {
p = stristr(p,newpath);
if (p) {
char *q = p-1;
char *pEnd = p + strlen(newpath);
return ERROR_NOT_ENOUGH_MEMORY;
}
strcat(buf,newpath);
siz = strlen(buf);
if (RegSetValueEx(hkey,"Path",0,REG_EXPAND_SZ,buf,siz) !=
ERROR_SUCCESS) {
RegCloseKey(hkey);
return ERROR_ACCESS_DENIED;
}
RegCloseKey(hkey);
// Send a message to all windows telling them that the
// Path environment variable has changed
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
(LPARAM) "Environment", SMTO_ABORTIFHUNG, 5000,
&dwReturnValue);
return dwReturnValue;
}
For more details on this method, please see the documentation for the MapDialogRect func-
tion in the Microsoft Windows Software Development Kit (SDK).
Use the GetDialogBaseUnits function to retrieve the size of the dialog base units in pixels. A
dialog unit in the x direction is one-fourth of the width that GetDialogBaseUnits returns. A
dialog unit in the y direction is one-eighth of the height that the function returns.
For more details on this method, see the documentation for the GetDialogBaseUnits function
in the Windows documentation of lcc-win32.
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
int x, y;
for (x=0; x < 20; x++)
for (y=0; y < 20; y++)
DrawBitmapNum(hdc,hBitmap,
rect.left+x*16,rect.top+y*16, Pole[x][y], 8 );
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
The programmer initializes the array “Pole” first, when it handles the creation message. And
the programmer expects that the value will stay from one invocation of this function to the
next. This will NOT be the case unless the variable is declared with the “static” keyword. Only
then, values will be preserved from one call to the next, and this becomes correct code. This is
a very common error.
GetClientRect(hwnd, (LPRECT)&rMyRect);
ClientToScreen(hwnd, (LPPOINT)&rMyRect.left);
ClientToScreen(hwnd, (LPPOINT)&rMyRect.right);
350 C Tutorial
3.22.25 How can I control the task bar under program control?
You can control the state of the task bar or query the characteristics using the SHAppBarMes-
sage API. This is a complex API that allows you to change where the tasklbar is localted,
whether it is automatically hidden, etc. For instance, this small program will show you if the
task bar “auto-hide” feature is on or off:
#include<shellapi.h>
#include <stdio.h>
int IsTaskbarAutoHideOn(void)
{
APPBARDATA d;
d.cbSize = sizeof(APPBARDATA);
return SHAppBarMessage(ABM_GETSTATE, &ABData) & ABS_AUTOHIDE;
}
int main(void)
{
printf("Taskbar autohide");
if (IsTaskbarAutoHideOn())
printf(" ON");
FAQ 351
else
printf(" OFF");
printf("\n");
}
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <direct.h>
#include <shellapi.h>
//////////////////
// Send a file to the recycle bin. Args:
// - full pathname of file.
// - bDelete: if TRUE, really delete file (no recycle bin)
//
int Recycle(LPCTSTR pszPath, BOOL bDelete)
{
// Copy pathname to double-NULL-terminated string.
//
char buf[_MAX_PATH + 1]; // allow one more character
SHFILEOPSTRUCT sh;
strcpy(buf, pszPath); // copy caller's path name
buf[strlen(buf)+1]=0; // need two NULLs at end
136.This program is based on a similar program published in MSDN magazine 2001. In the comments for
that program I found the following:
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
352 C Tutorial
#ifdef TEST
// Test program.
#include <gc.h> // for GC_malloc
#include <sys/stat.h> // For stat()
// This list structure holds the names of the files given in
// the command line
typedef struct tagFileList {
struct tagFileList *Next;
char *Name;
} FILELIST;
// pre-declare functions
void usage(void);
void help(void);
char *GetCurrentDir();
char * MakeAbsolute(char *relname);
BOOL confirm(LPCTSTR pFileName);
LPCTSTR GetErrorMsg(int err);
newlist = GC_malloc(sizeof(FILELIST));
newlist->Name = GC_malloc(strlen(name)+1);
strcpy(newlist->Name,name);
if (start) {
newlist->Next = start;
}
return newlist;
}
bDisplayOnly=TRUE;
break;
case 'p':
bPrompt=TRUE;
break;
case 'z':
bZap=TRUE;
break;
case '?':
help();
return 0;
default:
usage();
return 0;
}
} else {
// Got a file name. Make it absolute and add to list.
files = AddToList(files,MakeAbsolute(argv[i]));
}
}
if (files == NULL) {
// No files specified: tell bozo user how to use this command.
usage();
return 0;
}
if (!bDisplayOnly) {
if (!bPrompt || confirm(files->Name)) {
// Finally! Recycle the file. Use CRecycleFile.
int err = Recycle(files->Name,bZap);
if (err==0) {
nDel++;
} else {
// Can't recycle: display error message
fprintf(stderr,"Error %d: %s", err,
GetErrorMsg(err));
}
}
}
} else {
fprintf(stderr,"File not found \"%s\"\n", files->Name);
}
}
if (!bQuiet) {
fprintf(stderr,"%d files recycled\n",nDel);
354 C Tutorial
}
return 0;
}
void usage(void)
{
printf("Usage: RECYCLE [/QNPZ?] file...\n");
}
void help(void)
{
printf("Purpose: Send one or more files to the recycle bin.\n");
printf("Format: RECYCLE [/Q /N /P /Z] file....\n");
printf(" /Q(uiet) no messages\n");
printf(" /N(othing) don't delete, just show files\n");
printf(" /P(rompt) confirm each file\n");
printf(" /Z(ap) really deletesame as del\n");
}
}
else if (relname[0]=='\\') {
// relname begins with \ add drive letter and colon
memmove(relname+2,relname,strlen(relname)+1);
relname[0] = cwd[0];
relname[1] = cwd[1];
absname = relname;
//////////////////
// Get current directory. For some reason unknown to mankind, getcwd
// returns "C:\FOO" (no \ at end) if dir is NOT root; yet it returns "C:\"
// (with \) if cwd is root. Go figure. To make the result consistent for
// appending a file name, GetCurrentDir adds the missing \ if needed.
// Result always has final \.
/////////////////
char *GetCurrentDir(void)
{
static char dir[MAX_PATH];
getcwd(dir, sizeof(dir));
return dir;
}
//////////////////
// Get user confirmation to recycle/delete a file
//////////////////
BOOL confirm(LPCTSTR pFileName)
{
while (TRUE) {
printf("Recycle %s (Y/N/All)? ", pFileName);
char c = getch();
if (c==' ') {
printf("^C\n");
exit(0);
}
printf("\n");
switch (tolower(c)) {
case 'a':
bPrompt=FALSE;
// fall through
case 'y':
return TRUE;
case 'n':
return FALSE;
}
}
}
//////////////////
// Get Windows system error message
//////////////////
LPCTSTR GetErrorMsg(int err)
{
static char buf[BUFSIZ];
buf[0]=0;
void CALLBACK
MessageBoxTimer(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime)
{
PostQuitMessage(0);
}
/*********************************************************************
*
* TimedMessageBox
*
* The same as the standard MessageBox, except it also accepts
* a timeout. If the user does not respond within the
* specified timeout, then the value 0 is returned instead
* of one of the ID* values.
*
*********************************************************************/
/*
* Set a timer to dismiss the message box.
*/
idTimer = SetTimer(NULL,0,dwTimeout, (TIMERPROC)MessageBoxTimer);
/*
* See if there is a WM_QUIT message in the queue. If so,
* then you timed out. Eat the message so you don't quit the
* entire application.
*/
if (PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE)) {
/*
* If you timed out, then return zero.
*/
uiResult = 0;
}
return uiResult;
}
The key to creating a timed message box is exiting the dialog box message loop internal to the
message box. Since the message loop for a message box is part of USER, you cannot modify it
without using hooks and other such methods.
However, all message loops exit when they receive a WM_QUIT message. Furthermore, a
nested message loop, if it receives a WM_QUIT message, must break the loop and then re-
post the quit message so that the next outer layer can process it.
Therefore, you can get the nested message loop to exit by calling PostQuitMessage(). The
nested message loop will clean up and post a new quit message. When the MessageBox
returns, you peek to see if there is a quit message. If so, then it means that the message loop
was abnormally terminated. You also consume the WM_QUIT message instead of re-posting
FAQ 357
it so that the application continues running. Essentially, you have "tricked" the nested mes-
sage loop into thinking that the application is terminating. When it returns, you "eat" the quit
message, effectively canceling the fake quit that you generated.
memset(psp,0,sizeof(psp));
358 C Tutorial
// First we fill the PROPSHEETPAGE array with some items that will be common to all
// pages. This is not necessary, and maybe you will find it useful to fill individually this
// settings.
for (i=0; i<NUMBEROFPAGES;i++) {
psp[i].dwSize = sizeof(PROPSHEETPAGE);
psp[i].dwFlags = PSP_USETITLE|PSP_DEFAULT;
psp[i].hInstance = hInst;
}
// Now we starting filling each page with the unique information it needs. We fill the
// resource ID, the callback procedure and the title.
i = 0;
psp[i].pszTemplate = MAKEINTRESOURCE(IDD_GENERALSETTINGS);
psp[i].pfnDlgProc = EditInfo;
psp[i].pszTitle = "General";
i++;
// Repeat for all pages...
psp[i].pszTemplate = MAKEINTRESOURCE(IDD_COMPILER);
psp[i].pfnDlgProc = CompilerDlg;
psp[i].pszTitle = "Compiler";
i++;
...
// Now that we have filled the property sheet pages we fill the header data. First, we
// set it to zero.
memset(&psh,0,sizeof(PROPSHEETHEADER));
// Then, we set the size field, flags field, and the parent window info.
psh.dwSize = sizeof(PROPSHEETHEADER);
psh.dwFlags = PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW;
psh.hwndParent = hwndOwner;
// Then the hInst (Application instance), the caption and the number of pages this
// property sheet has.
psh.hInstance = hInst;
psh.pszCaption = "Configuration";
psh.nPages = i;
// Now we link the header data with the array of property sheet pages we filled above
psh.ppsp = (LPCPROPSHEETPAGE)&psp;
// Now, we go!
PropertySheet(&psh);
}
case WM_SYSCOMMAND:
if (wParam == SC_SCREENSAVE)
return TRUE; // This disables the screen saver
GetKeyboardState((LPBYTE)&keyState);
if( (bState && !(keyState[VK_NUMLOCK] & 1)) ||
(!bState && (keyState[VK_NUMLOCK] & 1)) )
{
// Simulate a key press
keybd_event( VK_NUMLOCK,
0,
KEYEVENTF_EXTENDEDKEY,
0 );
int main(void)
{
SetNumLock( TRUE );
}
To send key strokes to a window you give the focus to the window or control using the SetFo-
cus API, then you simulate the keyboard events you want.
#include <stdio.h>
int main(void)
{
String s = "Security is our business";
Strencrypt(s);
Strdecrypt(s);
printf("%s\n",s.content);
}
If you want to stay within the C framework only, here is the code for encrypting and decrypt-
ing a string. You should change the interface, of course. For the purposes of this function, a
“String” is a structure with a count and a pointer to the contents.
#include <windows.h>
#include <wincrypt.h>
int Strdecrypt(String &src)
{
HCRYPTMSG hMsg;
unsigned int cbDecoded;
char *pbDecoded;
// this variables should be changed to some parameters if you want to change the
// interface of this function. cbEncodedBlob should contain the length of the blob, and
// pbEncodedBlob should point to the start of it.
unsigned cbEncodedBlob = Strlen(src);
char *pbEncodedBlob = src.content;
StringA result;
// information
&cbDecoded)) // Size of the returned
return 0;
// Clean up.
if(hMsg)
CryptMsgClose(hMsg);
src.capacity = cbDecoded+1;
src.count = cbDecoded;
src.content = pbDecoded;
return 1;
}
int Strencrypt(String &src)
{
HCRYPTMSG hMsg;
BYTE* pbContent; // A byte pointer to the message
DWORD cbContent; // The size of message
DWORD cbEncodedBlob;
BYTE *pbEncodedBlob;
src.capacity = cbEncodedBlob+1;
src.count = cbEncodedBlob;
src.content = pbEncodedBlob;
return 1;
}
6) Manual.chm. This is the user’s manual, where you will find information about how the
system is used, command line options, menu descriptions, how to use the debugger, etc. It
explains how to build a project, how to setup the compiler, each compiler option, all that
with all the details.
7) c-library.chm. This file contains the documentation for all functions in the C runtime
library. It will be automatically invoked by the IDE when you press the F1 function key.
This two files (c-library.chm and manual.chm) are distributed in “manual.exe”.
8) Lcc-win32.doc. This is a technical description for interested users that may want to know
how the system is built, how the programs that build it were designed, the options I had
when writing them, etc.
The documentation of the windows API is distributed in a relatively large file called
win32hlp.exe. This is absolutely essential, unless you know it by heart… When installed, this
file will become Win32.hlp. That file is not complete however. More documentation for the
new features of Win32 can be found in the win32apidoc.exe file, also in the lcc distribution
site. When installed, that file will install:
• Shelldoc.doc. This documents the windows shell, its interfaces, function definitions,
etc.
• Wininet.doc. This documents the TCP/IP subsystem for network programming.
• CommonControls.doc. This explains the new controls added to windows after 1995.
Bibliography 363
Note that Wedit will detect if the documentation is installed, and will allow you to see the doc-
umentation of any function just with pressing the F1 key. This is a nice feature, especially for
beginners. Install a full version if you aren’t an expert. A version without the documentation it
is a pain, since you have to go fishing for that information each time you want to call an API,
not a very exciting perspective.
But if you want to get serious about windows programming, you should download the
Microsoft Software Development Kit (SDK) from the msdn site, and install it in your
machine. Wedit will automatically recognize the msdn library or the SDK if installed and will
call them instead of using the win32.hlp file.
3.25 Bibliography
Here are some books about programming in Windows. I recommend you to read them before
you believe what I say about them.
“Programming Windows”
Charles Petzold
Microsoft Press
This is a very easy to read introduction to the windows API that covers in depth all aspects of
windows programming. You can download an electronic version of this book at the site of the
author: www.cpetzold.com.
“Windows Network Programming”
Ralph Davis
Addison Wesley
This is a very extensive introduction to this side of programming. If you want an in-depth cov-
erage of sockets, net-bios, etc etc, here you will find it.
“Windows System programming Secrets”
Matt Pietreck
IDG Books
“Windows Internals”
Matt Pietreck
Addison Wesley
This books are source books for understanding how windows works and how it is built. Not
easy to read for beginners, but indispensable for more advanced programmers.
364 C Tutorial
Introduction 365
Chapter
Network Programming
3
4.1 Introduction
Network programming under windows started with the windows socket implementation under
windows 3.0. That implementation was a port of the sockets implementation of Unix to win-
dows, that added some support for the asynchronous calls under the weak multitasking of the
16 bit windows implementation.
When windows passed to 32 bits under windows 95, the sockets implementation improved, of
course, since a real multitasking operating system was running.
Based on the windows sockets implementation, Microsoft released first the wininet interface,
later the winhttp interface. The wininet interface is better for people that are concerned with
some other protocols as http of course, and it is a more low level interface than winhttp. Of
course “low level” is here very relative. Having implemented a full FTP protocol under win-
dows 3.1, the wininet interface is kilometer higher than the bare windows sockets interface.
The machines send electrical signals through the medium used by the network, be it coax,
twisted pair, or radio waves. This signals are encoded in precise bit patterns called protocols,
that give meaning to the bit stream. To comunicate with one another, machines follow specific
rules for speech, unlike people. The slightest interference, the slightest error in the sequence
and the machines lose completely the conversation and get stuck. Protocols are the base of all
the network.
Many protocols have been used in the PC, from the early LANMAN days, to now, where TCP/
IP has displaced all others.
With the rise of the internet , the internet protocol has been accepted as a quasi universal stan-
dard. This protocol runs over other more basic protocols like the ethernet protocol, in the case
of coax, or token ring, or whataver is actually sending and receiving the data.
But let’s get practical. Let’s start a small program that we can do without a lot of effort.
We will start then, with a simple program to read files from the internet. We will use the
library of Microsoft provided with the operating system: Winhttp.lib. The header file is
winhttp.h, that you should include in all the programs that use this examples. You should
obviously include in the linker command line the library itself, winhttp.lib. This library is just
an import library for winhttp.dll that you should normally have in your windows direc-
tory. Other header files are necessary too, like stdlib.h for getting the prototype of the
“malloc” function, etc.
4.2 Protocols
Protocols are standardized exchanges between two automata: a “client”, that asks some infor-
mation or resource from the “server” automaton, that manages that information and sends it
through the network. A protocol has a standard interchange format: the precise sequence of
interactions between the two machines is fixed in all details to make automated communica-
tion between them possible.
The information exchanged can be anything: an image, sound, an e-mail, hyper-text you name
it. It can be even a pdf file where you read this lines.
2) Headers
3) Entity body
The request is sent as plain text over the network. It uses the TCP/IP port 80.
The HTTP Protocol 367
When a server receives a request, it responds by sending a message back to the client. The
message sent by the server is called an HTTP response. It consists of the following compo-
nents.
2) Headers
3) Entity body
The response either indicates that the request cannot be processed, or provides requested infor-
mation. Depending on the type of request, this can be information about a resource, such as its
size and type, or can be some or all of the resource itself. The part of a response that includes
some or all of the requested resource is called the "response data" or the "entity body,' and the
response is not complete until all the response data is received.
4.3.1 GetHttpUrl
The interface for our function should be as simple as possible. It will receive two character
strings, representing the name of the URL to retrieve, and the name of the output file where
the file should be stored. If you are interested in using the resulting code you can skip this sec-
tion.
We start by opening an http session. The general schema for an http session is like this:
Clasic. We open a session, connect, and send requests. In this case we are concerned only with
retrieving the data, i.e. a “GET” request.
We do then an open, we connect, and start a request for a resource. We read from the headers
some info (before we read the data, it would be nice to check that the resource actually exists)
and accordingly we read the content of the file or not.
368 C Tutorial
4.3.2 Implementation
We open a session using the WinHttpOpen API.
We got the headers. Now we parse them to find the return code in them. The standard HTTP
specifies a response like:
HTTP/1.1 200 OK
Date: Fri, 07 May 2004 09:08:14 GMT
Server: Microsoft-IIS/6.0
P3P: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo
OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"
X-Powered-By: ASP.NET
Content-Length: 39601
Content-Type: text/html
Expires: Fri, 07 May 2004 09:08:14 GMT
Cache-control: private
We are interested in the first line, that contains the return code for the operation. We just skip
the HTTP/1.1 and get it:
memset(szHeaders,0,dwSize);
wcstombs(szHeaders,swzHeaders,dwSize);
char *p = szHeaders;
while (*p != ' ')
p++;
while (*p == ' ')
p++;
sscanf(p,"%d",&rc);
if (rc == 404) {
rcContext.Status = 404;
goto cleanup;
}
The next thing to do is to open the output file. If we can’t open it, there is no point in going
further. We close the session and return an error code.
rcContext.OutputFile = fopen(outfile,"wb");
if (rcContext.OutputFile == NULL) {
WinHttpCloseHandle(hSession);
return -2;
}
Now we have everything. We have an open file ready to receive the data, a correct answer
from the server that tells that the file exists, so we have just to loop getting the data chunks
until there is none left.
do {
// Check for available data.
dwSize = 0;
if(!WinHttpQueryDataAvailable( rcContext.hRequest, &dwSize ))
goto cleanup;
// Allocate space for the buffer.
char *pszOutBuffer = malloc(dwSize+1);
if( !pszOutBuffer ) {
goto cleanup;
}
// Read the data.
ZeroMemory( pszOutBuffer, dwSize+1 );
if( !WinHttpReadData( rcContext.hRequest,
(LPVOID)pszOutBuffer,
dwSize, &dwDownloaded ) ) {
free(pszOutBuffer);
goto cleanup;
}
// OK Write the data
The FTP protocol 371
fwrite(pszOutBuffer,1,dwDownloaded,
rcContext.OutputFile);
// Free the memory allocated to the buffer.
free(pszOutBuffer);
} while( dwSize > 0 );
We are finished. We fall through to the cleanup section.
3) The name of the local file where the remote file will be written
int main(void)
{
return GetFtpUrl("ftp.cs.virginia.edu",
"/pub/lcc-win32/README",
"README");
}
To give you an idea, when the first FTP protocol implementations under windows 3.0 were
monster programs of several thousand lines. Of course those thousands of lines are still there,
in the code of those FtpGetFile primitives. The advantage is that you do not have to worry
about it.
To link this program you should use the wininet.lib import library.
137.The FTP protocol has two modes of transmission: binary, where no modifications are done to the
transmitted data, and text, where the sequence \r\n will be translated as a single \n. The text mode will
destroy an executable file or zip file that can contain embedded \r\n sequences.
Querying the network parameters 373
int main(void)
{
FIXED_INFO*pFixedInfo;
IP_ADDR_STRING*pIPAddr;
ULONG ulOutBufLen;
DWORD dwRetVal;
int rc;
/* We use the GetNetworkParams API to query the host name, the domain name, and other
related information. We make two calls: in the first one we give an insufficient buffer,. This call
fails obviously, returning in the ulOutBufLen the necessary buffer length. Using this information
we allocate the necessary memory, and then call GetNetworkParams again. This is a common
interface with many network APIs that need buffers of varying length.
*/
pFixedInfo = malloc( sizeof( FIXED_INFO ) );
ulOutBufLen = sizeof( FIXED_INFO );
IP_ADAPTER_INFO*pAdapterInfo,*pAdapter;
/* The same technique as above. We pass to the API a buffer of a given length, and if it doesn’t
suffice we allocate the buffer in the returned length
*/
pAdapterInfo = malloc(sizeof(IP_ADAPTER_INFO));
ulOutBufLen = sizeof(IP_ADAPTER_INFO);
rc = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen);
if (rc != ERROR_SUCCESS) {
free(pAdapterInfo);
374 C Tutorial
if (FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwRetVal,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL ))
{
printf("\tError: %s", lpMsgBuf);
}
LocalFree( lpMsgBuf );
}
/* Show the information for the ip address table */
MIB_IPADDRTABLE*pIPAddrTable;
DWORD dwSize;
struct in_addr IPAddr;
char *strIPAddr;
pIPAddrTable = malloc(sizeof(MIB_IPADDRTABLE));
dwSize = 0;
IPAddr.S_un.S_addr = ntohl(pIPAddrTable->table[1].dwAddr);
strIPAddr = inet_ntoa(IPAddr);
rc = GetIpAddrTable(pIPAddrTable, &dwSize, 0);
if (rc == ERROR_INSUFFICIENT_BUFFER) {
free( pIPAddrTable );
pIPAddrTable = malloc ( dwSize );
}
dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 );
if (dwRetVal != NO_ERROR ) {
printf("Call to GetIpAddrTable failed.\nError %d\n",
GetLastError());
return 1;
}
/* Now show the information */
printf("Address: %#x\n", pIPAddrTable->table[0].dwAddr);
unsigned int mask = pIPAddrTable->table[0].dwMask;
printf("Mask: %d.%d.%d.%d\n",0xff&mask,0xff&(mask >> 8),
0xff&(mask >> 16),0xff&(mask >> 24));
printf("Index: %ld\n", pIPAddrTable->table[0].dwIndex);
printf("BCast: %ld\n", pIPAddrTable->table[0].dwBCastAddr);
printf("Reasm: %ld\n", pIPAddrTable->table[0].dwReasmSize);
/* Get the statistics about IP usage */
MIB_IPSTATS *pStats;
It sounds simple but it isn’t. It took me quite a while to make an implementation that is at the
same time easy to use and robust. For instance, what happens if the other side never answers?
It would be really bad if ping would freeze waiting for an answer that will never come.
Many other problems can occur, and within the “PingInterface” structure there are a lot of
fields that inform you what is going on.
The basic design principle is that you establish a callback function that is called by the ping
function each time a packet arrives or a timeout is detected.
A more sophisticated usage of ping() is the “remake” of the program of the same name. Here
is a possible implementation of that.
First we write a callback function to show progress in the screen as we receive a packet.
#include <ping.h>
int PrintReport(PingInterface *Data)
{
if (Data->Errorcode == WSAETIMEDOUT) {
printf("Timed out\n");
return 1;
}
printf("%d bytes from %s:",Data->Bytes, Data->ip);
printf(" icmp_seq = %d. ",Data->Seq);
printf(" time: %d ms ",Data->Time);
printf("\n");
return 1;
}
This function is called by ping() and just prints out the fields. Note that we test the error code
flag to see if we are being called because of a timeout or because of a packet arrived.
In this function we could send a message to a GUI window, or we would update some pregress
control, etc.
The main function is now trivial. We zero the interface structure, we set some fields and there
we go.
int main(int argc, char **argv)
{
PingInterface p;
int r;
memset(&p,0,sizeof(p));
// We set the host name field
p.HostName = argv[1];
// We modify the default time between packets
p.SleepTime = 500;
// If we time out more than 5 times we exit
p.MaxTimeouts = 5;
// We set our callback
p.Callback = PrintReport;
r = ping(&p);
if (r == 0) {
printf(“%s is down\n”,p.HostName);
return 1;
}
printf("\n");
printf("\n%d packets from %s (%s). ",
p.TotalPackets, p.HostName,p.ip);
printf("Received: %d, Sent: %d\n",p.TotalReceived,p.TotalSent);
if (p.TotalPackets == 0)
p.TotalPackets = 1;
378 C Tutorial
Client Server
4.7.1.1 Initializing
All Winsock applications must be initialized to ensure that Windows sockets are supported on
the system. To initialize Winsock you should call the WSAStartup function, giving it the ver-
sion number you want to use, and the address of a WSADATA structure, where windows
writes information about the type of network software that is running.
A typicall call would be:
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2,1),&wsaData) != 0){
return GetLastError();
}
The cryptic MAKEWORD(2,1) means we want at least version 2.1 of the network software.
Note that the library included with lcc-win32 doesn’t need this step, since it will perform it
automatically if it detects that the network wasn’t initialized.
while (1) {
AcceptSocket = SOCKET_ERROR;
while ( AcceptSocket == SOCKET_ERROR ) {
AcceptSocket = accept( Socket, NULL, NULL );
}
When the client connection has been accepted, assign the temporary socket to the original
socket and stop checking for new connections.
printf( "Client Connected.\n");
Socket = AcceptSocket;
break;
}
There we go, we have a socket ready to send and receive data.
memset(&session,0,sizeof(session));
session.port = 25876;
session.Host = "127.0.0.1";
if (ClientConnect(&session)) {
printf("Unable to connect\n");
goto end;
}
if (Send(&session,5,"data")) {
printf("Unable to send\n");
goto end;
}
if (Receive(&session,5,buf)) {
printf("Unable to receive\n");
goto end;
}
buf[session.BytesReceived] = 0;
printf("Received %d bytes: %.5s\n",
session.BytesReceived, buf);
end:
CloseSession(&session);
}
Simple isn’t it?
A server looks like this:
#include <stdio.h>
#include "netutils.h"
int main(int argc,char *argv[])
{
Session session;
char buf[8192];
memset(&session,0,sizeof(session));
session.port = 25876;
session.Host = "127.0.0.1";
if (ServerConnect(&session))
return 0;
printf("Connected...\n");
memset(buf,0,sizeof(buf));
if (Receive(&session,sizeof(buf)-1,buf))
return 0;
printf("received request\n");
printf("data is: %s\n",buf);
if (Send(&session,5,"data"))
return 0;
CloseSession(&session);
}
Basically those procedures implement all the steps described above for client and server sides.
They are the building blocks for implementing the FTP or HTTP protocols we saw above. To
use this functions include “netutils.lib” in the linker command line and include “netutils.h” to
get the prototypes and structure declarations.
382 C Tutorial