|
|
 |
 |
|
|
 |
 |
|
Join the Discussion
|
Post your views and comments to this chapter of the free "raw API programming" Delphi Course
Discuss!
|
|
 |
 |
|
|
 |
 |
 |
Once, that you know how to code a simple windowless Delphi application with no user interaction, and how to call an API message box, you are ready for your second application...
System Info Program
Now, let's get some user input. An easy way to get a small amount user input is with Message Boxes. 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 picks the "YES" button. Another message box will ask about creating a "System Info.txt" file, and this "System Info.txt" file will have a lot of system information written to it using the methods of the first program. This application 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
The following program will show several ways to get character strings from Windows OS functions, see the GetWindowsDirectory(), GetCurrentDirectory() and GetComputerName() functions in the code below. When getting a PChar from windows, you will have to deal with making sure your variables that receive null-terminated strings have enough memory for this string. There will be more information about the PChar Variable type and API pointers, in the future chapters, 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 available from them. The following functions should be helpful to every Windows coder:
procedure GetLocalTime(var lpSystemTime: TSystemTime);
function GetWindowsDirectory(lpBuffer: PAnsiChar; uSize: Cardinal): Cardinal;
function GetSystemDirectory(lpBuffer: PChar; uSize: Cadinal): Cardinal;
function GetComputerName(lpBuffer: PChar; var nSize: Cardinal): Boolean;
function GetUserName(lpBuffer: PChar; var nSize: Cardinal): Boolean;
function GetSystemMetrics(nIndex: Integer): Integer;
function SystemParametersInfo(uiAction, uiParam: Cardinal; pvParam: Pointer; fWinIni: Cardinal): Boolean;
function GetVersionEx(var lpVersionInformation: TOSVersionInfo): Boolean;
function ExpandEnvironmentStrings(lpSrc: PChar; lpDst: PChar; nSize: Cardinal): Cardinal;
procedure GlobalMemoryStatus(var lpBuffer: TMemoryStatus);
function ShellExecute(hWnd: HWND; Operation, FileName, Parameters, Directory: PChar; ShowCmd: Integer): HINST;
Compile and run the code below. The pictures show the program "results":

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 :String;
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() so I 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 message 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 adds 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 with 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 about PChar and Pointers the following chapters.
To the next chapter: A Guide to raw API programming
If you need any kind of help at this point, please post to the Delphi Programming Forum where all the questions are answered and beginners are treated as experts.
First page > Your first Delphi application without the Forms unit > Page 1, 2, 3
A guide to developing Delphi programs in raw Windows API: Next Chapter >> >>
Creating an API GUI Windows program with message loop
|