Home |
4. PChar, Pointers in Using API functions |
Home |
You have seen the API functions called to interact with the OS. Functions like CreateWindow( ) have parameters that are pointers (like PChar type), and the API function definitions in the Windows.pas unit set the variable types used in these functions when you write code. You may have noticed that the parameter types given in the Win32 API Help are in C language and can seem different than the variable types set by the windows.pas unit. In Win32 API Help, the second parameter of the GetClientRect( ) function is listed as a LPRECT, a Long Pointer to a Rect structure (record). But when we used this function - GetClientRect(hMainForm, FormRect); - the second parameter was a TRect. Keep in mind that the windows.pas unit defined the function from the Win system User.dll as -
function GetClientRect(hWnd: HWND; var lpRect: TRect): BOOL;And that the lpRect (FormRect in this case) is a pointer to the record's memory location. So the GetClientRect( ) function works with a TRect variable type without having to change it to a Pointer type, because Delphi Pascal has the var parameter designation and C-Code does not. The strict type checking done by the Delphi compiler and the windows.pas API function definitions can be confusing when using some of the generalized functions like SendMessage( ) where the WParam and LParam parameters are an Integer type and you have to Typecast any non numeric variables to an Integer Type.
On this page you will get some help in using the "Pointers" in the API functions. We'll start with the PChar since it is used in so many of them. You may not need the information on this page to use API functions, but you should at least read through this and get an overview.
When coding in the delphi-pascal Form Unit, it is commom practice to place everything (variables and procedures) in an Object Oriented TObject Type Declaration. You will rarely need to use Pointers or Memory address references, since all of the Type Object's variable names will be used as pointers (converted to assembly laguage as a memory location) by the compiler. However, in the C code of Windows API, some of the parameters are a "reference" to a position in the memory where the information is located. Many of these API parameters will be used with the var parameter designation (Integer, Cardinal, TRect, TPoint), or a Typed Pointer like PChar. (These parameter variable types are based on the Windows System API, but are Type-Checked against the Pascal function "conversion" type definitions in the windows.pas unit) But many API function parameters will be a Cardinal or Integer Type that will be used to get or send a Pointer, like the WParam and LParam of the Window's Proc for messages. And in other API functions some of the parameters are a Pointer (Typed and UnTyped), so it will help you to have a basic understanding of pointers and using them.
Since Delphi Pascal uses a very strict Type Checking, you will need to do TypeCasting. You will see many examples in the programs here of TypeCasting the Variable's address (@) as Pointers (or integer reference), and Pointers as another Variable Type, so the Delphi Compiler will accept that variable in an API function. Strict type checking is in delphi to help prevent you from making coding errors, like placing the wrong variable name or wrong variable type as parameters. It may be helpful to keep in mind that the Code you are writting is set up to be "Human" readable, and help us Humans use References that are not binary numbers, but the code you write will NOT look like the assembly code accually used to compile your program. Pointers Window's API memory is used and referenced as an array of bytes. A Pointer is a variable that stores a memory-address (a numeric value, like an integer, memory array byte number). In the case of a Record pointer (PRect for example) or a Static Array pointer (Dynamic Arrays are different), the pointer holds the address of the first element in that variable. Pointers can be Typed to indicate the kind of data stored at the memory address of the pointer (like PChar and PInteger). The general-purpose "Pointer" type can represent a pointer to any data, while more specialized pointer types reference only specific types of data. Pointers occupy four bytes of memory, the same as the Integer or Cardinal type. First, understanding pointers will help you to understand Object Pascal, since pointers often operate behind the scenes in code where they don’t appear explicitly. Any data type that requires large, dynamically allocated blocks of memory uses pointers. Long-string variables, for instance, are implicitly pointers, as are class variables. The reserved word nil is a special constant that can be assigned to any pointer (called NULL in C language). When nil is assigned to a pointer, the pointer doesn’t reference anything (the numeric value of nil is Zero). You can declare a pointer to any type, using the syntax type pointerTypeName = ^typeWhen you define a record or other data type, it’s a common practice also to define a pointer to that type. This makes it easy to manipulate instances of the type without copying large blocks of memory. When a pointer holds the address of "another variable", we say that it points to the location of that variable in memory, but NOT to the data of that variable. Many Standard pointer types exist (like PInterger and PRect) to allow the pointer data size and use to be defined. The most versatile is Pointer, which can point to data of any kind, and has NO memory assigned to it, so you must always assign a memory block for it to use (usually with GetMem or AllocMem). There are some pascal Pointer operators that you will need to use with API function parameters, the @ and ^ operators. The @ operator, is used to get the memory address of a variable (this address is a Pointer Type). The @ operator returns the address of a variable, or of a function, procedure, or method, which means, @ returns a pointer to its operand variable. var X: Integer; P: Pointer; begin X := 22; P := @X; // Now P has the memory address for X end;The operator ^ has two purposes. When it appears before a type identifier — ^typeName It denotes a Type that represents pointers to variables of Type typeName. When it appears after a pointer variable (used in some API Parameters) - Pointer^ It dereferences the pointer, which means, it returns the value (data) stored at the memory address held by the pointer, this is nessary because the Delphi compiler will not allow you to assign ( := ) a Pointer to a non-Pointer variable. Derefencing tells the compiler that you want the data stored in the Pointer's memory location, instead of the Pointers memory location address. . var X, Y: Integer; Pnt1: Pointer; {Pnt1 is a NonTyped pointer} P: ^Integer; {P is now a Pointer Type to 4 bytes of Memory, which will treated as an Integer variable in assignments, := } {or you could use} {P: PInteger;} begin X := 22; P := @X; // assign the address of X to P Pnt1 := @X; // assign the address of X to Pnt1 Y := P^ // dereference P, assign the result to Y {this is NOT the same as Y := Integer(P);} Y := PInteger(Pnt1)^; // a non-typed pointer will have to be Type Cast end; Since a pointer is the numeric value of a memory location, you can TypeCast a numeric type variable like Integer or Cardinal to a Pointer Type, and TypeCast a Pointer Type to an Integer or Cardinal type. But remember, this TypeCast will get the numeric memory address, NOT the data in that memory, , Like This - var PStr1: PChar; Integer1, Integer2: Integer; Rect1: TRect; begin PStr1 := 'A Pchar string'; Integer1 := Integer(PStr1); {now Integer1 has the memory address of the PStr1 pointer. unlike some TypeCastes, this Integer Typecaste has Nothing to do with the Characters in the PStr1, only it's memory address} PStr1 := PChar(Integer1); {this is the same as PStr1 := PStr1 again, this typeCaste has Nothing to do with the Data (Characters) in the PStr1 character string} Rect1.Top := 44; Integer2 := Integer(@Rect1); // since Rect1 is NOT a Pointer type, you need the @ Integer1 := PRect(Integer2)^.Top; {to get the data values from a memory location number like Integer2, you will need to typecast the integer to the "Pointer Type" for the data type that the memory location is formated for, in this case a 16 byte TRect} end; Int1 := Integer(Pstr1); Pt1 := Pointer(Int1); //Char(Pointer(Cardinal(Pt1)+4)^) := 'B';//Byte(Ord('B')); //PChar(Pt1)[4] := 'B'; PChar(Int1)[4] := 'B'; {if you TypeCast the Integer to a Pchar, then you can use the [4] like a PChar} if Pstr1[4] = Char(Pointer(Cardinal(Pt1)+4)^){PChar(Int1+4)^} then MessageBox(0,'They Match', 'Is True', MB_OK or MB_ICONQUESTION or MB_DEFBUTTON2 or MB_SETFOREGROUND); The WParam and LParam in the Window Proc In the More Messages Program you saw how to TypeCast the Windows Proc LParam to a PWindowPos pointer type for the WM_WINDOWPOSCHANGING message. And the LParam was TypeCast as a PMinMaxInfo for the WM_GETMINMAXINFO message. Since the LParam was TypeCast to a Typed Pointer, we can use this Typed Pointer as the Variable type of this Pointer. When the LParam is TypeCast as a PWindowPos, if PWINDOWPOS(lParam).cx > 450 then we can use it as a TWindowPos variable and access the .cx and .cy elements of the TWindowPos Record. In recent versions of Delphi, if you add the period for Object access to a Typed Pointer, the compiler will automaticlly dereference the Pointer. The code above would be more correctly written as if PWINDOWPOS(lParam)^.cx > 450 then You also could declare a Typed Pointer variable and then assign the memory address of the lParam to that Typed Pointer. function MessageProc(hWnd, Msg, WParam, LParam: Integer): Integer; stdcall; var PWinPos1: PWindowPos; begin // code WM_WINDOWPOSCHANGING: begin PWinPos1 := Pointer(LParam); if PWinPos1.cx > 450 then PWinPos1.cx := 450; if PWinPos1^.cy > 450 then PWinPos1^.cy := 400; end; // code end;This second method may save some typing if you reference the PWinPos1 several times. NOTE: You can Typecast any 4-byte numeric variable (Integer, Cardinal) to a Pointer type, but that does NOT make it a Pointer (memory location). the PChar Variable type, it's a Pointer String Type Pointer, PChar PChar is the Type declaration for "Null Terminated" charater strings and is different than the Pascal "String" type. A PChar is a Pointer to a null-terminated string (array) of characters. PChars are, with short strings, one of the original Object Pascal string types. They were used primarily as a C language and Windows API compatibility type. The Widows System, using the C language, does not have a dedicated string data type. The Windows system using the C language, rely on null-terminated strings. A Null-Terminated string is a zero-based array of characters that ends with a NULL (#0), since the array has no length indicator (like Pascal Strings), the first NULL character marks the end of the string. You can use special functions to handle null-terminated strings when you need to share data with the Windows system. A Pascal Long-String is also null-terminated (has a #0 at the end of the string), but the term "null-terminated" is refering to PChar types. The pascal Short-String does NOT always have a NULL terminator, and uses the first byte as the valid text length. Most that use Delphi know the Pascal long "String" type used for charater string variables. This "String" type is easy to use, you can just assign the charaters to the variable without haveing to allocate memory for the variable or add a #0 to the end, and join two strings together with a + operator like this
You can TypeCaste a Pointer to an Integer or Cardinal type and you can TypeCast an Integer to a Pointer type.
You can Typecaste a long Pascal String to a Pchar, and a PChar to a Pascal String, which can make using PChar strings easier to code with the more familiar "+" string joining method. But you may want to use the PChar without typecasting to a String, so I want to mention some of the API functions for PChar strings (null-terminated). With PChar-Strings the + operator does not work, to manipulate null-terminated strings, it is often necessary to use fuctions and pointers. Here are 6 API PChar string functions which can be used to get the length, copy, join, or compare null-terminated strings. But remember that for the lstrcpy, lstrcpyn, and lstrcat functions, you have to do the memory allocation for the lpString1 variables, it is not "Automatic" like using pascal Strings.
Some examples for changing PChar strings
A powerful and useful, feature is accessing variables by the use of indirect addressing through pointers and pointer arithmatic, but it can cause problems, if there is not enough memory allocated to the variable. (A common misteak is to forget to add 1 to the memory allocation for the Null #0 char in PChar) Bugs introduced by misusing pointers can be difficult to detect and isolate because the error often corrupts memory unpredictably. Most of the time, without any thing seeming to be wrong, at least with no error or warning messages. Remember, the OS does not know and can not check the memory allocation for a variable passed to it as a function parameter. Be sure you do not make specific assumptions when Typecasting variables to different types especially to pointers that may not work if used outside of that specific way. The SizeOf( ) function is very useful to get the memory size of a "fixed-size" variable. If you use "Pointer Arithmatic" be very carefull when adding or subtrating amounts that you do not go past the begining or end of the memory allocation of that variable. Dereferencing a PChar var PStr1: Pchar Got1: Boolean; begin PStr1 := 'Some Text'; if PStr1^ = 'S' then Got1 := True else Got1 := False; end;Got1 will be True, because PStr1^ will be used as a Char variable, which will be the first charater of the PStr1 array, an 'S'. Be aware, that in some API functions you will need to Dereference a PChar variable, and ALL of the charaters in that memory array will be used by that function, not just the first character. All Pointers are 4 bytes of memory (32 bits) and so are the following variable types in Delphi 4 and above - Pointer, Cardinal, Integer, LongWord, DWORD, THandle. All of these can be Typecast as a Pointer. ?? warning ?? You may be more confused by this Page than helped, if you have not used pointers before and are not familar with the "Pointer" programming methods, which Delphi hides so well. Don't worry, it's not an easy step from RAD delphi programming to pointers, dereferencing, and memory allocation. You do not need a total understanding of pointers to use API functions. Bypassing Delphi Type-Checking of Function Parameters API Function Parameters So if you know enough to correctly use a variable of a different type, what can you do to make it accept your code? You can use the memory-address of your variable (place a @ in frount of it), and Typecast that as a typed-Pointer for the required variable (example - PRect for a TRect), and you must Dereference that pointer typecast with a ^. Below is an example, I will use the API function SetRect( ), with a parameter variable that is not a TRect. I define my own variable type TRectPlus with more than 4 integers. Code - type // the first 4 members of TRectPlus record are the same as a TRect TRectPlus = record Side,Above,Extent,Below: Integer; // like a TRect hWnd, Color: Cardinal; end; procedure aTest; var RectPlus1: TRectPlus; begin // SetRect(RectPlus1, 100, 20, 160, 58); // will NOT compile // SetRect(TRect(RectPlus1), 100, 20, 160, 58); // will NOT compile SetRect(PRect(@RectPlus1)^, 100, 20, 160, 58); // typecast to typed-pointer and dereference, this will work RectPlus1.hWnd := 0; // only changes first 4 members RectPlus1.Color := $FFFFFF; // you need to set the other two end;Please notice that I have the PRect(@RectPlus1)^ in place for the var TRect function parameter. This SetRect( ) will change the first 4 integer members of the RectPlus1 variable, but nothing is done to the hWnd , Color members. Delphi-pascal will not allow typecasting two "record types" if the types do not have the same members, so you could write - TRect(RectPlus1) as the parameter, but it will not compile with a fatal-error of "Invalid Typcast". However, you can typecast almost anything to a typed-pointer by using a memory-address, as I did here. The example above was used with the default compilier directive of {$TYPEDADDRESS OFF} ($T-}, , which controls the types of pointer values generated by the @ operator. In the (T-) , the result of the @ operator is always an untyped pointer. If you get a fatal error when trying to compile, you may can use the {$T-} to turn it off , but I would recomend trying this - procedure aTest; var RectPlus1: TRectPlus; memAddy: Cardinal; begin memAddy := Cardinal(@RectPlus1); // get a numeric Cardinal or Integer value SetRect(PRect(memAddy)^, 100, 20, 160, 58); // typecast number to typed-pointer and dereference end;Numeric values (Integer and Cardinal) can not be type checked, and the code will be compiled. Below is some more example code, this time I use the API function CreatePalette( ) and have a variable with the type of TMaxLogPalette. function BWPalette: Cardinal; var maxPal: TMaxLogPalette; begin maxPal.palVersion := $300; maxPal.palNumEntries := 2; maxPal.palPalEntry[0] := 0; maxPal.palPalEntry[1] := $FFFFFF; Result := CreatePalette(PLogPalette(@maxPal)^); // this works // typecast @maxPal to a PLogPalette and dereference end;You may find there are a few other API functions that you will have trouble using the parameter types as the windows.pas unit defines them, and will have to find some other way to code. Or if you do much API programming you will need to use your own type definitions in some function parameter, you can use this method to get the code to compile. But be careful and check that the memory block (variable data) that you typecast is large enough for the amount of memory used in reading or writing. |
Next We have made a program that creates windows, and has a Message Loop to interact with Windows OS. There are many more things about the display of a windows GUI that you need to know. Now we are using a Graphical Interface and have to be able to paint and draw on it, and use colors, fonts, and Device Contexts. So the next program will show some basic graphical device interface functions. Lesson 5, Fonts and using Device Contexts |