|
Under the Hood : Running Ahead By Bruce Johnson
|
|
|
|
|
|
This article was first published in
Clarion Online
Volume 1, issue 1 and is reproduced here with permission
In this column Ill be discussing the Windows 32 bit Application Programming
Interface (Win32 API). Generally Clarion does a good job of hiding most
of the API complexity from you but there are still functions in the API
that are available, and useful, to the programmer who is prepared to dig
a little deeper.
As we progress with this journey though the API well build up a function
library that you can use in your everyday applications. Well make
full use of templates to make using these functions easier and more reusable
from one application to another. Well also see some of the benefits
of 32-bit programming.
In this first column well explore the 32-bit function called CreateProcess,
along with some related functions. Apart from creating a process well
also be able to stall the current process until the child shuts down. In
addition to this our function will return a handle which we can use to terminate
the process as well. Well do this using the TerminateProcess function.
The Clarion RUN function is a simpler version of CreateProcess. CreateProcess
however has some additional functionality that can come in handy when used
at the right time. Essentially CreateProcess, and Run, allow you to start
a program from within another program. In CW2 this was all that RUN allowed
you to do. In the new Clarion 4 (C4) one parameter has been added which
allows you to wait until the program you call has finished running before
continuing with the caller program. CreateProcess, can however do even more.
Now the idea is to create a wrapper function, called ARUN, in our library
to remove some of the complexity. What were aiming for is a function
that allows us more power than the Run function, but still isnt as
complex as the full CreateProcess function. For example in calling our wrapper
well use strings instead of cstrings, well allow omittable parameters
(with suitable defaults) and well remove parameters that dont
interest us for now.
Although CreateProcess can do a lot, most of it is beyond what we need.
For example if youre writing a debugger then CreateProcess has flags
that you would need. Its unlikely though that you would write a debugger
in Clarion so we dont need to worry about things like that. Im
not going to go into all the flags that can be set, but Im going to
focus on the ones youd most likely use. Specifically ARUN will offer
the following features:
- It can control the priority of the new process ( how much time it gets
to run). This is useful for launching things like background processes.
Remember Windows is a multitasking OS and part of that means moving background
processing to where it belongs, into the background.
- It can specify the initial state of the window of the new process (maximized,
minimized etc.) Sometimes you want a program to start, but not to come
immediately to the front. For example you may want to incorporate the
Windows calculator in your app. You can run the calculator, but run it
in a minimized state. Then its available immediately to the user
whenever they need it.
- It can give the process its own environment space (its own path for
example). This is very useful for running legacy DOS applications. For
example Clarion Professional Developer (an early version of Clarion for
DOS) created programs that made use of Environment variables like CLAVM0.
By launching programs with their own environment space you save having
to do complicated install functions like changing the AutoExec.Bat.
- It can start the process in a different directory. This is useful if
the program and data reside in different directories, as is often the
case on a network.
- It can set the position and size of the first window of the new process.
This means you have more control over how the program is going to look
when it opens.
In addition to this, inside ARUN I will use the GetLastError and WaitForSingleObject
functions (both Win32 API functions) to provide the following additional functionality;
- GetLastError returns the most recent windows error. Thus if the ARUN
fails this function will return the error value so that ARUN can return
it to you. It is essential to get the maximum error information back from
a function so that when it fails you can quickly identify the cause of
the problem.
- WaitForSingleObject. This function allows the calling program to efficiently
wait for the called program to terminate before execution resumes. This
is particularly useful where you are running another program to complete
a task, and when that task completes you want to resume processing the
current application.
To round it off ARUN will return a process handle (if it is successful) that
will contain a handle to the new process. We can use this handle, and the
TerminateProcess function, to terminate the child process if we want to. TerminateProcess
is a bit of a mouthful to remember so well create a wrapper function,
called ENDRUN to end a program started with the RUN statement.
An obvious sidebar here: a logical step from here is to be able to terminate
the current program from within itself. So you might be wondering how to get
hold of the handle of the current program. However the solution here is to
remember the HALT command provided by the Clarion language, which performs
that functionality for us already.
Here is the prototype of CreateProcess as it appears in the Win32 SDK. (This
is using a C compatible prototype statement).
BOOL CreateProcess(
LPCTSTR lpApplicationName,
// pointer to name of executable module
LPTSTR lpCommandLine,
// pointer to command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // pointer to process security
attributes
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread
security attributes
BOOL bInheritHandles,
// handle inheritance flag
DWORD dwCreationFlags,
// creation flags
LPVOID lpEnvironment,
// pointer to new environment block
LPCTSTR lpCurrentDirectory,
// pointer to current directory name
LPSTARTUPINFO lpStartupInfo,
// pointer to STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION
);
At first this looks a real mouthful !! However lets go through it piece by
piece and see that its actually not that bad.
Before looking at what everything does, lets translate this into a more familiar
Clarion prototype. Firstly in C the type the function returns is placed before
the function name, in this case BOOL. (Incidentally if theres nothing
before the name then its assumed the function returns a Long in 32 bit
and a Short in 16 bit. If the function returns nothing, i.e. its a procedure,
then the return type is VOID).
Another point to bear in mind is that the Win API uses Pascal parameter passing
techniques and you also need to use the RAW attribute since this is a non-Clarion
DLL. Actually the Raw attribute is only necessary in certain cases, but it
does no harm on the others, so I put it in on all API calls to make thinking
easier. The Name attribute isnt strictly necessary yet, but youll
see in a minute that it comes in handy. So far our prototype looks like this..
CreateProcess ( ??
), Byte, Pascal, Raw, Name(CreateProcess)
Now let's look at the parameters that it takes. Actually although it looks
complicated the types tell you what they are. For example LP stands for long
pointer, things ending in STR are a string and so on. Actually when dealing
with the API strings are always Cstrings, and are usually passed using a pointer
in this way - so get used to the LPSTR construction ! As you can see in the
above they come in a variety of flavors, but in Clarion all of these flavors
become a *CSTRING.
The two Security Attribute parameters are only used in Windows NT. As we want
our function to work for both Windows 95 and Windows NT well set these
to Null, i.e. 0. This means that in Windows NT the process we create will
get default security settings.
Bool as we mentioned earlier is a BYTE, and Dword stands for Double Word.
A Word in C is the same as a USHORT in Clarion, and a Dword is the same as
a ULONG in Clarion. The LPVoid type is also a pointer, but in this case its
a pointer to a group of strings. Well examine this one more closely
in a moment. The last two parameters are pointers to groups that contain a
number of options and flags and things like that. Its in these groups
(which Windows is very keen on) that most of the complexity, and indeed most
of the power, of API functions is revealed. The groups are documented inside
the library source code for those that are interested. Actually the first
group, SRARTUPINFO, sets a lot of the options for CreateProcess, and PROCESS_INFORMATION
contains a number of handles that are returned by the function.
So our eventual prototype looks like this:
CreateProcess ( *CString, *CString,
Long, Long, Byte, Ulong, *CString, *CString, |
*StartupInfo, *ProcessInfo ), Byte, Pascal, Raw, Name(CreateProcess)
The name of the wrapper function is called ARUN. It is prototyped as...
ARUN (String, <String>, <String>,
<Long>, <Long>, <Long>, <Long>, <Long>, <Long>,<Long>),
Long, Proc
... and is documented as...
ARUN (Program,
StartInDirectory, EnvironmentBlock,
Priority, InitialState,
Xpos, Ypos, Width,
Height, Wait)
Program
This is the name of the program, including its path (if necessary) and any
command line parameters it may take.
Start In Directory
This is the directory in which the program should initially start.
Environment Block
Some programs, especially legacy DOS and Windows 3.1 programs use the Environment
space to store startup settings. This can be a problem when running from Windows
as these settings usually require changes to the AutoExec.Bat, which in Windows
95 is something to avoid.
CreateProcess lets you specify an environment space specifically for a program.
This is passed to the function as a list of null-terminated strings, terminated
itself by a null. For example:
EnvStr String(512) ! note we use
a clarion string here
code
EnvStr = (path=c:\dos;c:\windows<0>clavm0=e:\<0><0>)
Note that each setting is separated by a <0>
and the whole string is terminated by a
<0> as well.
Priority
You set the priority of the new process to one of 0,1,2,3 or 4.
- Idle priority. This is the lowest priority. The new process will only
run if the system is idle. This is ideal for housekeeping type programs,
and background process type programs.
- Normal priority. This is the default if this parameter is omitted. If
the parameter is 0 then this is also the priority used. This is the same
priority as other Windows programs.
- High priority. This preempts other windows programs and gets most of
the available CPU time. This should be used with care as it stops other
programs from getting time.
- RealTime priority. This is the highest priority possible. It preempts
all other windows processes including the mouse and hard disk cache. This
should only be used with extreme care.
Initial State
You can set the new program to open in one of the following modes (valid values
0,1,2,3).
- Normal ( new program gets the focus). This is the default if the parameter
is omitted or set to 0.
- Maximized (new program gets the focus )
- Minimized ( calling program keeps the focus )
Note that some programs, especially Clarion programs, may not respond to this
requested behavior.
Xpos, Ypos, Width, Height
These parameters determine the starting position, and size of the program.
Note that although these apply they may not be obvious immediately. For example
if the program is open maximized, then these positions will only apply when
the window is "Restored".
Wait
This determines if the program must wait or not for the new program to finish
before carrying on. If this is set to 0 (or omitted) then the program doing
the calling (the Parent) carries on immediately after running the child. If
this is set to 1 then the Parent waits until the Child has terminated before
it continues.
ARUN Returns
The function returns a long containing one of the following:
- 0 - The program called ran correctly, and the function waited for the
called program to terminate before returning.
- >0 - The program called ran correctly and the function returned immediately
with the ProcessIdentifier of the new program that is running. This identifier
( often referred to in Windows as a Handle ) is needed if we want to make
use of the ENDRUN function.
- <0 - The program called could not run for some reason. The returned
value is the error code multiplied by -1. In other words if the error
code was 2, then the function would return -2. Note that this error code
is a Windows error, as returned by GetLastError, not a Clarion error.
The specific error message can be checked by looking up the error code
in a file called WinError.Clw. This file is included with the library
files.
Example
result long
code
result = aRun ('c:\windows\notepad.exe','d:\secwin') ! run the notepad
starting in the d:\Secwin dir.
result = aRun (c:\windows\notepad.exe,,,,,,,,,1) ! start the notepad,
and wait for it to finish.
result = aRun (c:\windows\calc.exe,,,,3) ! run the calculator, but
start it minimized.
EndRun (Result) ! Close the calculator opened in the above line
Next lets look at the TerminateProcess Function, and the wrapper well
make called ENDRUN.
Not surprisingly TerminateProcess is a lot simpler than CreateProcess, and
requires far fewer parameters. Nevertheless we can still simplify it a little.
BOOL TerminateProcess(
HANDLE hProcess, // handle to the process
UINT uExitCode // exit code for the process
);
By now you should be used to the layout as well as the BOOL
data type. Both HANDLEs
and UINTs are
ULONGS
in the Win32 API. So a Clarion prototype of the function is;
TerminateProcess(ULong, ULong), Byte, Pascal, Name(TerminateProcess)
The first parameter is a process identifier, as returned by the CreateProcess
function. The second is the exit code that the program should return when
it closes. Return codes are almost never used in Windows programs, so our
wrapper function, ENDRUN,
will simplify the call by setting this to 0. ENDRUN
is prototyped as follows:
ENDRUN ( Ulong )
and documented as follows
ENDRUN ( ProcessID )
ProcessID
A Process Identifier as returned by the ARUN
function.
Example
result long
code
result = aRun ('c:\windows\notepad.exe','d:\secwin') ! run the notepad
starting in the d:\Secwin dir.
! do some code here
ENDRUN(result)
The included files include a project library with source code for the
ARUN
and ENDRUN
functions, a simple template for using in your apps, and the WinError.Clw
errors file. The template defines a single global extension which adds the
prototypes to your app for you, and includes the library in your project.
You will need to register the template in your template registry, as well
as compile the project and copy the API.LIB file from your \cw20\obj directory
to your \cw20\lib directory.
As a last note, remember that ARUN
and ENDRUN
are 32 bit functions. Thus they cannot be used in 16 bit programs.
© 1997, Online Publications, Inc. Reproduced
with permission.
© 2012 CapeSoft Software CC
|