Home |
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 liketype 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 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 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 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')) |
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
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 |