Site hosted by Angelfire.com: Build your free website today!

Home
DelphiZeus
1. Creating a .dpr Program

Home



Most Delphi Coders are used to working in the "Unit" code page for a TApplication Form, and are familar with the methods and code arangement used in a Delphi Unit for a TForm. In this lesson I will be working in the .DPR "Program" code page, which has a different code arangement and a few different coding methods. I will give some information in this lesson about using the Program code page for your application development. There is also some Basic information about using API functions. Even if you are already familar with coding in the Program file, you should read through this lesson.

Because I have included many references the Windows Win32 API Help, I will have some web page Links to the Microsoft Developers Network's web pages for their information about the API functions. These links will have the MSDN- as a prefix, like this link to the MSDN Library -
MSDN-Microsoft Developers Network Library
These MSDN Links shoud open in a separate browser window.


When you create a New Application in Delphi you will see a Form (Form1 automatically created by Delphi) and the code for this form is in a unit called Unit1. If you open the "Project Code Page" called Project1, to see the code in this  .dpr  file and it will look like this -


program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
The normal Delphi coding for this form is in a .pas file (Unit1.pas). If you compile and run this program it will be around 250 Kb (depending on your version of delphi), even though it does not do anything. To create this API " .dpr only program" you will need to take out the Form1, by clicking the "Remove file from project" button and highlight the Unit1 (Form1) and click OK. Now your app will not have any forms and Project1 will look like -

program Project1;

uses
  Forms;

{$R *.RES}

begin
  Application.Initialize;
  Application.Run;
end.
You should notice that the first word in this file is "Program" and Not "Unit", like the Form units that you are used to using.
Now Remove the uses clause compleately and take out the   Application.Initialize;   and the   Application.Run;   lines, Take out the Res file compiler directive
{$R *.RES}
so it looks like -

program Project1;

begin

end.
If you are not familar with the .dpr program file, it is unlike the Unit1.pas for forms, all funtions and procedures in the Form .pas file must be "Called" in order for it to execute. In a .dpr , the program execution starts at  begin  and goes down line by line (executes each line of code) until it gets to end. were code in the System unit will be executed to end the program and exit the process. (In a GUI program, a loop stops the progression preventing the end. from being executed).   Compile and Run this three line .DPR file above (you may get a warning message about application or forms not being in your code, continue anyway). It will compile and run (for a couple of milliseconds), you will not see anything and it does not do anything and the app file size will be 16 Kb (in delphi 5). You should wonder how it's possible to have a program be created run WITHOUT ANY CODE at all. Well, there is code being executed in the Sysinit and System units, which are always used. You needn’t explicitly use any units in a project, but all programs automatically use the System unit. Code is automaticaly being used to start your program (diferent code is used for a "Console" app), in the Sysinit there are calls to Intialize your program with a "Module", "hInstance", and others. In the System unit more startup things are done, it always creates a Memory Manager, and more things like these two functions in the System.pas file to set up the ParamStr( ) function. Just look at the folowing code from System.pas, -
function GetParamStr(P: PChar; var Param: string): PChar;
var
  Len: Integer;
  Buffer: array[0..4095] of Char;
begin
  while True do
  begin
  while (P[0] <> #0) and (P[0] <= ' ') do Inc(P);
  if (P[0] = '"') and (P[1] = '"') then Inc(P, 2) else Break;
  end;
  Len := 0;
  while (P[0] > ' ') and (Len < SizeOf(Buffer)) do
    if P[0] = '"' then
    begin
      Inc(P);
      while (P[0] <> #0) and (P[0] <> '"') do
      begin
        Buffer[Len] := P[0];
        Inc(Len);
        Inc(P);
      end;
      if P[0] <> #0 then Inc(P);
    end else
    begin
      Buffer[Len] := P[0];
      Inc(Len);
      Inc(P);
    end;
  SetString(Param, Buffer, Len);
  Result := P;
end;


function ParamCount: Integer;
var
  P: PChar;
  S: string;
begin
  P := GetParamStr(GetCommandLine, S);
  Result := 0;
  while True do
  begin
    P := GetParamStr(P, S);
    if S = '' then Break;
    Inc(Result);
  end;
end;
And lots more code to start a 32 bit windows program and have usable info like ParamStr(Index: Integer). You may want to look in your Delphi Help for the methods availible in the System unit. In begining windows programing (for C code) you would have WinMain( ) to initialize your program (get an hInstance and command line), Delphi initializes it for you in the Sysinit unit, so you do not have a WinMain( ) in Delphi. There is also some finialization code executed at the " end. ", which will do some clean up and exit the process.



the First dpr program

Ok, lets get this program to do something. Before you start doing GUI (graphical user interface) and creating windows, lets see if you can do things without a GUI. We will be using windows operating system API functions so add the "uses" clause with the basic "Windows". We will create a Folder and a text file then write the ParamStr(0) to the text. Look in your Windows Developer "Application Programming Interface" (API) Win32 Help (included with Delphi), under word index for "CreateDirectory". Or go to the MSDN web site page for MSDN-CreateDirectory You will see

BOOL CreateDirectory(
LPCTSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes );

If you are new to C code as used in the Windows API Win32 Help, the variable "Type" is put before the variable, not after it. Also the Function Result (Return in C) is put Before the function, not after it. So in delphi Pascal it would go

CreateDirectory(lpPathName: PChar; lpSecurityAttributes: PSecurityAttributes ): Boolean;

For some info about reading the C code used in Win32 API Help click this button -

In this First dpr program you will include the compiler directive {$R *.RES} in the code, so you can use the Delphi Menu for "Project" click the "Options" menuItem to set a program Icon and place Version Info in the exe file as you would with a VCL project. In this application, a txt file will be created and written to using the Delphi "System" text file procedures AssignFile( ), Rewrite( ) and CloseFile( ). The compiler directive {$I-} is used with the Rewrite( ) procedure to keep the program from throwing an Exception on failure of the procedure.

Copy and paste from, or use this code -
program Project1;

uses
  Windows;

{$R *.RES}

var
File1: TextFile;
{global variables are put here, above the begin}

begin  // main program begin
if  CreateDirectory('C:\Some Test Folder', nil) then
{securityAttributes are nil}
  begin
  AssignFile(File1, 'C:\Some Test Folder\test note.txt');
{use the delphi text file procedures defined in the System unit}
  {$I-}
  Rewrite(File1);
    {put the $I compiler option so exceptions will 
     not be generated and stop this program}
  {$I+}
{test the IOResult for success}
  if IOResult = 0 then
  Write(File1,'ParamStr 0 is '+ ParamStr(0));
  {$I-}
  CloseFile(File1);
  {$I+}
  end;

end.
If you compile and run this program, then look on your C drive for the folder "Some Test Folder" there should be a text file in it called "test note.txt". The Folder and File were created without any visual display (GUI) to the user. You can do many file functions without showing anything, However there is no oportunity to get user input.

Technical Note - You may not know it, but Delphi creates and starts it's own "Memory Manager" in the System.pas . The System.pas Unit will always start up a Memory Manager for your Delphi program (or library) when your program initilizes, (starts execution). This Memory Manager sets up it's own Heap memory block to assign and allocate, this Memory Manager handles the memory use for non-fixed size variables, which include variables like type "String", "PChar" and Dynamic Arrays. Also Calls to GetMem( ) and FreeMem( ) will use the memory Manager. This is one of the reasons that the Delphi program file size starts out at 16 Kbs. Without this Memory Manager, Delphi would not have the Long String methods that are so convient and easy

Program file .DPR coding is diferent than Unit coding

If you are used to coding in the Delphi Form Unit, you will notice that the Program .DPR file does NOT have a interface section or a implementation section like the Unit has. Also in the Unit .PAS file, there is NO   begin  and   end.  code block.

Let's look at the program code above, , you will not see the "interface" and "implementation" sections that you are used to in the .pas units. Also notice that there are no "type" declartions like
type
  TForm1 = class(TForm)
    Button1: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;
Using the Program file requires a different approach than the unit .pas files. You see the variable var declaration above the begin you may also declare a "type" above the begin. Below the var, const, and type declarations and above the begin you can place defintions for procedures and functions you will need to use. You can NOT place definitions for procedures and functions below the begin. You can call procedures you have defined Above the main begin line, from Below the begin line.

If you are used to working in the Delphi Form unit, you know you need to call "Close" or "Application.Terminate" to cause your program to end and exit. And I have seen code examples for a .dpr file that will call "Halt;" or "ExitProcess(hInstance);" just before the last line (end.) of code to terminate the program. You do NOT need to call anything to end your program. The program will stop and exit it's process when it gets to the "end." of the code without you doing anything. There is cleanup code executed at the end., that should be allowed to happen if possible. Like coding in a function between the begin and end , you can call Exit; in the programs main code block, which will cause the code execution to go to the end. line to end your program.

When you did coding in a Form Unit's Implementation section, you did Not need to be aware of the order of your procedure and function definitions. When working above the main begin line in a .dpr file, you will need to place any procedure you call from another procedure ABOVE that procedure that calls it. The compilier will read your code from Top to Bottom when it assembles your program. So it does Not know about any code below the line of code it is currently reading and any procedures that are called on that line, will have to be defined Above that line. If you find that you need to call a procedure and can Not move it above the line that calls it, you can use the   forward   compilier directive. The purpose of a forward declaration is to extend the reading of a procedure or function definition to an earlier point in your code. This allows other procedures and functions in the .dpr file to call the forward-declared routine before it is actually defined.

Message Boxes
In the next program I will use the standard Windows System "Message Box" to get some user input. If you look at the Win32 API Help in it's index for "Message Boxes", or go to the MSDN web site page for MSDN-MessageBox you will find some information about Message Boxes. Now Look in the API Help for "MessageBox" and there is the function definition for MessageBox( ) and the parameter options and use. The MessageBox function is defined in the API Help "C" as - -

int MessageBox(
    HWND hWnd,	// handle of owner window
    LPCTSTR lpText,	// address of text in message box
    LPCTSTR lpCaption,	// address of title of message box  
    UINT uType 	// style of message box
   );
And in Pascal - - -
function MessageBox(hWnd: Cardinal; lpText, lpCaption: PChar; 
                     uType: Cardinal): Integer;
The first Parameter is usually set to the "Winows Handle" of the Main "Form" window. When the message box closes the system will give the Keyboard focus to the window who's handle is here. Since this next program does not have any windows this parameter is set to zero (NULL in "C"). The next parameter is for the "Message" text displayed and the third parameter is for the "Title" bar text. Both of these are PChar type. The fourth parameter is for uType, a Cardinal type. You will set the options for the message box in this parameter. In many window API functions the options for functions are numeric values called a "Flag" (see "Using API Function Flags" below). There are 2 Flags you need to set for a MessageBox, the first is the "Button" type and the next is the "Icon" type. Look at the code for a Message Box - -
MessageBox(hMainWindow,'There was an Error somewhere',
    ' ERROR, you got an Error', MB_OK or MB_ICONERROR);
This will make a message box with One "OK" button and the windows system Icon for "Error". If you use the Flags "MB_OKCANCEL or MB_ICONQUESTION" you will get a message box with two buttons an "OK" button and a "Cancel" Button, and the Icon will be a "Question Mark". Since we need some user input in the "System Info Program" program below the MB_YESNO flag is used in the first message box and the MB_OKCANCEL is used in the next one. By testing the Result of the MessageBox function you can see if One of the buttons was clicked, it will have the "ID" value of the button like "ID_YES". There are other Flags for buttons, icons, display, and modal options, read the API help and try the options to see how they affect the message boxes.

Using API Function Flags
In most API functions there will be ways to change what the function does and other options for that function. A very common way is to use numereic Flags in a parameter like in the fourth parameter of the MessageBox( ) function above. Since this method is used so much, some explanation may help you understand it. You may be used to the Object Oriented Pascal methods of Option selection, API (other than COM) isn't Object Oriented, so API fuctions are used to create and change things. One method for setting options in a function is with Numeric Constants like MB_ICONERROR. In windows.pas some of the message box Flags are definded as - -

const
  MB_OK = $00000000;
  MB_OKCANCEL = $00000001;
  MB_ABORTRETRYIGNORE = $00000002;
  MB_YESNOCANCEL = $00000003;
  MB_YESNO = $00000004;
  MB_RETRYCANCEL = $00000005;

  MB_ICONERROR = $00000010;
  MB_ICONQUESTION = $00000020;
  MB_ICONWARNING = $00000030;
  MB_ICONASTERISK = $00000040;

  MB_DEFBUTTON1 = $00000000;
  MB_DEFBUTTON2 = $00000100;
  MB_DEFBUTTON3 = $00000200;

  MB_APPLMODAL = $00000000;
  MB_SYSTEMMODAL = $00001000;
  MB_TASKMODAL = $00002000;
Notice that the MB_OK, MB_DEFBUTTON1 and MB_APPLMODAL are all equal to Zero (hex $00000000). This is because all of these are the "Default" function options, so placing the MB_OK in the parameter or leaving it out, doesn't really change the value but it does give you an indicator in your code of what the function will do. Function Flags are given Constant Names that try to give a description for what option that flag will produce. It is easier to read the code - -
MessageBox(hWindow,'Some Error', ' ERROR', MB_OK or MB_ICONERROR);

than --
MessageBox(hWindow,'Some Error', ' ERROR', 16);

To Combine these Flag constant numbers, you should use the "or" binary operator. This is like the "+" operator for Numeric Addition if the binary bits are not present in the value, but if the binary bits are already in the number, then it will Not "Add" the binary bits to the value. This is safer to use since some of the contant values may be the same. For Example --
MB_OKCANCEL or MB_ICONERROR or MB_OKCANCEL is equal to 17
1 or 16 or 1 = 17   , , , but
MB_OKCANCEL + MB_ERROR + MB_OKCANCEL is equal to 18
1 + 16 + 1 = 18
You can easily see here that the MB_OKCANCEL is the same value, but other flag values may be the same. Using the "or" operator is more important if you are using your own numeric variable for the parameter value.

In the Object Oriented Pascal you set many options by a Boolean value or a value in a Set of values, and if the value you assign is not in that Set then the program will not compile with a "value not in Set" error. With API functions using numeric flags you will get no compiler warnings or errors for values that are not in the function's numeric range of values. This parameter is Defined as a Cardinal value, so the compiler will accept any value between 0 and 4294967295. When you first start using API flags like "SPI_GETWORKAREA", "SM_MOUSEPRESENT" and "MB_ICONQUESTION", you may forget that these are defined as numeric values, and you may be used to a Delphi "Set" for options and think that "SPI_GETBORDER" is part of a delphi set, but it's just a number ( SPI_GETBORDER := 5 )

Binary Combine with the "OR" operator
Some Flags are Exclusive (like the button flags fo MessageBox) and Not meant to be combined. For example if you wanted a message box with 4 buttons on it, an "OK" button and a "Cancel" button, with a "Yes" button and a "No" button. So you used the -
      MB_OKCANCEL or MB_YESNO or MB_ICONERROR
as the Flags. This would not produce a message box with 4 buttons, what you would get is a message box the 2 buttons, a "Retry" button and a "Cancel" button. Why? because MB_OKCANCEL or MB_YESNO is equal to 5 (1 or 4 = 5) and 5 is the constant value for the Retry and Cancel buttons. What if you combined the MB_OKCANCEL or MB_YESNOCANCEL, what buttons would you get? If you said the "Yes" and "No" buttons (MB_YESNO = 4) you would be wrong, because "1 or 3 = 3", so you would get the "Yes", "No" and "Cancel" buttons. The "or" operator works on the binary (bit) positons of the numbers, the decimal "1 or 3" is binary "0001 or 0011", since the 0001 bit is already in 0011 it can not put it there again. So you should only use a single button flag. This is why in the API help it says -
"Specify one of the following flags, to indicate the buttons contained in the message box:"
Many of the Flag constant values are meant to be combined and will have the bitmask values of 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, ect. All of these values correspond to a single "Bit" position in the binary bits for a number's value. This is why these constant values are defined in Hex, which is easier to see binary relationships than in decimal.

If you have not used the boolean "OR" operator very much (or other Boolean Logic), you might want to experiment with it for the Flags in the MessageBox( ) functions below. You can get some information about binary operators in the Delphi Help under index "Boolean Operators" and index "Logical (bitwise) operators". Binary operations are fundamental to programming, so you should have some knowledge of it, for API programming. For instance, what does "17 or 9" equal? ?

Note - - PChar( ) typecast
I have seen an abundance of code presented for using API functions where a Charater Array (text with two ' ) in the parameters will be TypeCast as a PChar( ), , , , like this -

MessageBox(hMainWindow,PChar('There was an Error somewhere'),
    PChar(' ERROR, you got an Error'), MB_OK or MB_ICONERROR);
I will Not use this method here, because as far as I can determine the Delphi Compiler will automatically create whatever variable type is required for that function's parameter using the Character array in the parameter. The compiler reads the "charaters" (letters) between the two apostrophes  '  as an array of characters (not as a pascal String), and then automatically creates a variable type to match the function's required variable type. So if the function uses a String type then a String type is created, if it needs a PChar type, then a PChar type is created, a Wide String will get a wide String type, a PWChar will get a PWChar type, , ect. So this would work, and save you some typing -
MessageBox(hMainWindow,'There was an Error somewhere',
    ' ERROR, you got an Error', MB_OK or MB_ICONERROR);
There are times you will need to Type cast text to a PChar, if the type cast does not have a required type, for example Integer type cast
Integer('Some text')
will NOT work, it will have to be
Integer(PChar('Some text'))




System Info Program

Now let's get some user Input. An easy way to get a small amount user input is with Message Boxs. In this System Info program a Message Box will ask about creating a folder and create a folder on the C:\ drive if the user clicks the "YES" button. Another Message Box will ask about creating a "System Info.txt" file, and this "System Info.txt" file will have alot of system information written to it using the methods of the first program. This program shows how to get information about the computer like the windows version, screen resolution, and the work area rectangle, so later when you do window creation you can change them for system values like Screen resolution.

Getting info from the OS
This program will show several ways to get charater strings from Windows OS functions, see the GetWindowsDirectory( ), GetCurrentDirectory( ) and GetComputerName( ) functions in the code below. When geting a PChar from windows OS, you will have to deal with making sure your variables that recieve null-terminated strings have enough memory for this string. There is more infomation about the PChar Variable type and API pointers, in the Lesson 4 web page, Using PChar in API functions, and Pointers.

Comments have been added to the code to help you, but you must read the Win32 API Help for each of these functions to see the other options or info that is availible from them. The following functions should be helpful to every windows coder. There is a Link after each function to the Micrisoft Developers Network web page for that function.
  1. procedure GetLocalTime(var lpSystemTime: TSystemTime);
        MSDN-GetLocalTime
  2. function GetWindowsDirectory(lpBuffer: PAChar; uSize: Cardinal): Cardinal;
        MSDN-GetWindowsDirectory
  3. function GetSystemDirectory(lpBuffer: PChar; uSize: Cadinal): Cardinal;
        MSDN-GetSystemDirectory
  4. function GetComputerName(lpBuffer: PChar; var nSize: Cardinal): Boolean;
        MSDN-GetComputerName
  5. function GetUserName(lpBuffer: PChar; var nSize: Cardinal): Boolean;
        MSDN-GetUserName
  6. function GetSystemMetrics(nIndex: Integer): Integer;
        MSDN-GetSystemMetrics
  7. function SystemParametersInfo(uiAction, uiParam: Cardinal; pvParam: Pointer; fWinIni: Cardinal): Boolean;
        MSDN-SystemParametersInfo
  8. function GetVersionEx(var lpVersionInformation: TOSVersionInfo): Boolean;
        MSDN-GetVersionEx
  9. function ExpandEnvironmentStrings(lpSrc: PChar; lpDst: PChar; nSize: Cardinal): Cardinal;
       MSDN-ExpandEnvironmentStrings
  10. procedure GlobalMemoryStatus(var lpBuffer: TMemoryStatus);
        MSDN-GlobalMemoryStatus
  11. function ShellExecute(hWnd: HWND; Operation, FileName, Parameters, Directory: PChar; ShowCmd: Integer): HINST;
        MSDN-ShellExecute


Compile and run at the following code -


 
program Project1;
{This Program gets System Info to a text file and opens that
file in the default Text edit program. There is no GUI created
and No message loop is used. User interaction is done with
2 Message Boxes}

uses
  Windows, ShellApi;
  {ShellApi is added to use ShellExecute( )}

{$R *.RES}

var
WinDir: Array[0..4095] of Char;
{WinDir: Array[0..65535] of Char;}
{Getting PChar strings from the Windows OS requires
the recieving var to have memory for that string.
An array of Char does not change it's memory use like
a PChar or String var. Using a Larger array of Char
will make sure you have enough space for the string.
I use Array[0..4095] alot, 4 Kb will be more than
is needed most of the time, If you need it for a
Multiline Edit then Array[0..65535] will cover the
max size of it's text buffer}

WindowsDir, SystemDir, CurrentDir, ComName, UserName: String;
Transfer: PChar;
SystemTime1: TSystemTime;
ScreenWidth, ScreenHeight, WinBorder: Integer;
CharSize: Cardinal;
GotMouse, ShowSound: Boolean;
GotVersion: Boolean = False;
WorkRect: TRect;
OSVersionInfo1: TOSVersionInfo;

const
TxtFile = 'C:\Info Folder\System Info.txt';

function Int2Str(Number : Int64) : String;
var Minus : Boolean;
begin
{SysUtils is not in the Uses Clause so I can not use IntToStr( )
and have to define an Int2Str( ) function here}
   Result := '';
   if Number = 0 then
      Result := '0';
   Minus := Number < 0;
   if Minus then
      Number := -Number;
   while Number > 0 do
   begin
      Result := Char((Number mod 10) + Integer('0')) + Result;
      Number := Number div 10;
   end;
   if Minus then
      Result := '-' + Result;
end;


procedure MakeTextFile;
  var
  File1: TextFile;
  MemStatus: TMemoryStatus;
begin
AssignFile(File1, TxtFile);
{$I-}
Rewrite(File1);
{$I+}
if IOResult = 0 then
  begin
  WriteLn(File1,'           the Time is '+Int2Str(SystemTime1.wHour)+':'
    +Int2Str(SystemTime1.wMinute));
  WriteLn(File1,'           This File is '+TxtFile);
  WriteLn(File1,'This Program is '+ParamStr(0));
  WriteLn(File1,'Windows Folder is '+WindowsDir);
  WriteLn(File1,'Windows System Folder is '+WinDir {SystemDir});
  {WinDir, Array of Char can be used as a String}
  WriteLn(File1,'Current Folder is '+CurrentDir);
  WriteLn(File1,'Computer Name is '+ComName);
  WriteLn(File1,'User Name is '+UserName);

{ExpandEnvironmentStrings is used to get DOS environment info}
  if ExpandEnvironmentStrings('%PATH%',WinDir,512) <> 0 then
    WriteLn(File1,'Path is - '+WinDir);
  if ExpandEnvironmentStrings('%TMP%',WinDir,MAX_PATH) <> 0 then
    WriteLn(File1,'TMP is - '+WinDir);

  if GotMouse then
    WriteLn(File1,'Mouse is Present')
    else WriteLn(File1,'No Mouse is Present');
  if ShowSound then
    WriteLn(File1,'Sounds are made Visible')
    else WriteLn(File1,'Sounds are Not made visible');
  WriteLn(File1,'Screen Work Area Rectangle Top is '+Int2Str(WorkRect.Top)+
    ' Left is '+Int2Str(WorkRect.Left)+' Bottom is '+Int2Str(WorkRect.Bottom)
    +' Right is '+Int2Str(WorkRect.Right));
  WriteLn(File1,'Windows Sizing Border multiplier is  '+Int2Str(WinBorder));
  WriteLn(File1,'Screen Width is '+Int2Str(ScreenWidth)+' Screen Height is '
    +Int2Str(ScreenHeight));

  MemStatus.dwLength := SizeOf(MemStatus);
  {TMemoryStatus has a Size variable so you have to intialize it}
  GlobalMemoryStatus(MemStatus);
  {GlobalMemoryStatus gets memory and page file info}
  WriteLn(File1,'Tolal System Memory is '+Int2Str(MemStatus.dwTotalPhys)
    +' bytes -  Total Page File is '+Int2Str(MemStatus.dwTotalPageFile)
    +' Memory Load is '+Int2Str(MemStatus.dwMemoryLoad)+'%');

  if GotVersion then
    begin
    WriteLn(File1,'Windows Major Version is '
    +Int2Str(OSVersionInfo1.dwMajorVersion)
    +' Minor Version is '+Int2Str(OSVersionInfo1.dwMinorVersion)
    +' CSDversion is'+OSVersionInfo1.szCSDVersion);
    if OSVersionInfo1.dwPlatformId = VER_PLATFORM_WIN32_NT then
    WriteLn(File1,'This is a windows NT system');
    end;
  end;
{$I-}
CloseFile(File1);
{$I+}
if IOResult = 0 then
ShellExecute(0, 'open', TxtFile, nil, nil, SW_SHOWNORMAL);
{ShellExecute( ) is included here because it is a very useful function.
Here it displays the "System Info.txt" so I do not have to make a GUI
with a Multiline Edit control to show it. It can also be set to do the same
with NotePad.exe instead of the default, like this
ShellExecute(0, 'open', 'Notepad.exe', TxtFile, nil, SW_SHOWNORMAL);}
end;


begin  // / / / / MAIN PROGRAM BEGIN / / / /
GetLocalTime(SystemTime1);
{GetLocalTime will find out the Date and Time}

{when geting a PChar from windows OS, you will have to deal
with getting and setting the amount of memory needed
for the PChar, Array of Char, or String. The following
examples will show different methods of getting strings}

GetWindowsDirectory(WinDir, MAX_PATH);
{WinDir is a Fixed length array of Char, the array can be
larger than the amount of charaters needed, but not smaller.
See the various array lengths in the var clause above.
MAX_PATH is a const used for the Maximum number of charaters
allowed in a path string}

WindowsDir := String(WinDir);

GetSystemDirectory(WinDir,GetSystemDirectory(WinDir,1)+1);
{the result of GetSystemDirectory(WinDir,1) is the length of
the PChar string for the System Directory, add 1 for the
null charater at the end of a PChar string}
SystemDir := String(WinDir);

SetLength(CurrentDir,GetCurrentDirectory(1,nil));
{You can use a pascal String to get the PChar string if you
SeLength( ) to the amount of characters needed and then
typecast the String to PChar}
GetCurrentDirectory(Length(CurrentDir)+1,PChar(CurrentDir));

CharSize := MAX_COMPUTERNAME_LENGTH + 1;
{the computerName has a max length, given in
MAX_COMPUTERNAME_LENGTH, so you can use this to set the memory
needed for the PChar string, it's OK if it's more than is needed}
GetMem(Transfer, CharSize);
{allocate memory to the Transfer variable
Always use FreeMem( ) after a GetMem}
GetComputerName(Transfer, CharSize);
ComName := String(Transfer);
FreeMem(Transfer);

GetUserName(nil,CharSize);
{GetUserName(nil,CharSize) gets the length of the
UserName string into the CharSize var}
SetLength(UserName,CharSize);
GetUserName(@UserName[1],CharSize);
{the address of the first Charater of the String
@UserName[1] is used instead of a PChar typecast}


ScreenWidth := GetSystemMetrics(SM_CXSCREEN);
{GetSystemMetrics( ) can get you alot of useful info
see API help for the List of parameters}
ScreenHeight := GetSystemMetrics(SM_CYSCREEN);
GotMouse := Boolean(GetSystemMetrics(SM_MOUSEPRESENT));

SystemParametersInfo(SPI_GETSHOWSOUNDS,0,@ShowSound,0);
{SystemParametersInfo( ) can get and set some System settings}
SystemParametersInfo(SPI_GETWORKAREA,0,@WorkRect,0);
SystemParametersInfo(SPI_GETBORDER,0,@WinBorder,0);

OSVersionInfo1.dwOSVersionInfoSize := SizeOf(OSVersionInfo1);
{many Windows Record structures have a Size variable which
must be filled BEFORE the address of the Record is passed
to Windows}
if GetVersionEx(OSVersionInfo1) then
GotVersion := True;
{GetVersionEx will provide you with Windows version info
for the computer that is running this program
Notice that the variable OSVersionInfo1 does not need a @, like
the @WorkRect and @WinBorder used in the SystemParametersInfo,
there is only one Type of variable allowed in GetVersionEx,
but in SystemParametersInfo the variable can be a TRect, a Boolean
an Integer or other Type so a Pointer Type is used}


{All user input is from Message Boxs, and the Result (ID_YES) will
determine what is done
look in the API help for more MessageBox button and icon options}
if MessageBox(0,PChar('Today is '+Int2Str(SystemTime1.wMonth)+'/'
     +Int2Str(SystemTime1.wDay)+'/'
     +Int2Str(SystemTime1.wYear)+#10
     +'Do you want to create the Folder "Info Folder" ? ?'
     +#10+'To place a System Info.txt file with System Information'),
     'Make Folder ?',  MB_YESNO or MB_ICONQUESTION or MB_DEFBUTTON2 
     or MB_SETFOREGROUND) = ID_YES then
  begin
  if  CreateDirectory('C:\Info Folder',nil) then
    begin
    {in the MessageBox function there is the hWnd, handle of owner window,
     which is the window handle to return focus to, after the message box.
     You would set your Main Window handle here, but there is no Main
     Window in this App so I set it to 0 instead.}

  if MessageBox(0,'Do you want to create the File "System Info.txt" ? ?',
    'Make File ?', MB_OKCANCEL or MB_ICONQUESTION) = ID_OK then
    MakeTextFile;
    end else
    MessageBox(0,'Could not create a Folder',
    ' ERROR, folder was not created', MB_OK or MB_ICONERROR)
  end;

end.



The message boxes show up and get some user input, but there are no calls for "CreateWindow" and there is no Message loop in this app. Windows system creates the message boxes and handles the messages, also they are Modal to your app, so execution stops while the messsage boxes are shown. Notice that the text file creation has been moved to a procedure above the "begin" for the main program. SysUtils is not in the uses clause so the function Int2Str( ) has been added. The SysUtils unit will add about 22 Kb to your App's file size because it addes many Exception message strings and other initialization data and functions.
There were several API functions which returned a pointer to a null-Terminated string, like GetUserName(@UserName[1],CharSize);, and you were shown several ways to get a variable to have the memory needed to receive the string. The windows API functions use pointers to send and receive information. When working in the normal Delphi VCL Forms Units you may not be aware of the Type checking and pointers used by the compiler. Since API functions use pointers like the PChar so much, you might look at the Delphi Help for "Overview of pointers", "Using pointers, arrays, and string constants" and "Pointer types". Other Delphi Help topics you might look at are "Working with null-terminated strings" and "Mixing Pascal strings and null-terminated strings". There will also be some information aboult PChar and Pointers in lesson Four.

I will code in the .DPR program file in these first lessons, but using Units with your API programming is a good idea to help you with code organization. I will show some code in units in Lesson eleven.



                           
Next
We have made a Formless program that gets user input and creates a file, with a file size of the First DPR program less than 20 Kb. It's time to create some windows and get this program communicating with windows OS with messages.
  2. Making a API window and message loop program


       

Lesson -     One  Two  Three  Four  Five  Six  Seven  Eight  Nine  Ten  Eleven  Twelve  Thirteen  Fourteen




H O M E