Author Topic: Fred's Tutorial #1: Windows Application Programming Interface ( API ) Tutorial  (Read 9539 times)

0 Members and 1 Guest are viewing this topic.

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
 
     This is the first program in a series I have started to advance the use of the Windows Application Programming Interface ( Api ) for creating Windows programs with PowerBASIC.  A comment thread has been started in Articles Tutorials and HowTos at:

http://www.jose.it-berater.org/smfforum/index.php?topic=1255.0

     As of middle August 2007 there are 11 seperate posts, as described below...

     Post #1   (this post) Form1.bas.  The very basics of using the Windows Api to construct a window.  Kind of a 'Hello, World!' without the Hello.  Mostly covers some basics such as Win32Api.inc, parameter passing and function calls.  Also delves into WinMain(). It is located at

http://www.jose.it-berater.org/smfforum/index.php?topic=1243.0

     Post #2   Starts working on the concept of the Window Procedure, and finishes with a program that writes to a display window the current mouse position, window sizes, keypresses and mouse left button clicks.  It’s at

http://www.jose.it-berater.org/smfforum/index.php?topic=1254.0  

     The original Post #2 had a statically defined variable in it which drew some comment.  I replaced that variable in an alternate version which uses a pointer to dynamic memory.  It is for more advanced beginners and is at
 
http://www.jose.it-berater.org/smfforum/index.php?topic=1252.0

     Post #3   Continues discussions of parameter passing and interpretation of C Api documentation in the context of PowerBASIC.  Shows how to modularize code, create windows through button presses, draw text, etc.  It is located at

http://www.jose.it-berater.org/smfforum/index.php?topic=1253.0

     Post #4   Shows how to create a simple menu using a resource file.  It’s at

http://www.jose.it-berater.org/smfforum/index.php?topic=1251.0
              
     Post #5   Shows how to use Open File Common Dialog Box with resource script created menu, also includes an ordinary resource script created dialog box;
    
http://www.jose.it-berater.org/smfforum/index.php?topic=1267.0

     Post #6   Shows how to use pointers for dynamic memory allocations and instance data.  Also covers function pointers and modifying memory with pointers.  Link is...

http://www.jose.it-berater.org/smfforum/index.php?topic=1292.0

     Post #7   Develops a multifile app that demonstrates scrolling of data in text boxes and listboxes.  Link is...

http://www.jose.it-berater.org/smfforum/index.php?topic=1296.0
    
     Post #8  Simplest.bas  Introduces how to write scroll code.  First in series of three.  Link is...

http://www.jose.it-berater.org/smfforum/index.php?topic=1299.0

     Post #9   Further elaborates on Simplest.bas by showing how to use the more advanced ScrollWindow() to scroll text in a window.  Link is...

http://www.jose.it-berater.org/smfforum/index.php?topic=1301.0

     First, just a few housekeeping chores about using these example programs before we jump right into the tutorial.  You should be able to use the PowerBASIC Console Compiler to compile and run all these programs if you don't have the Windows Compiler.  The only thing different that will happen is that a console window will open in addition to whatever graphical output the program displays.  And even this can even be turned off through the use of the #Console Off directive right after #Compile Exe.  If you have any difficulties, send me an email.  If we can't resolve the issue I'll refund you your money!

     Also, if you are new to the PowerBASIC Forums (or even if you aren't) let me clue you into an excellent way of getting these posts into your programming editor.  The reason you need to know this is that if you copy code right out of the html/ubb display of your browser, all code formatting will be lost in the html shuffle, along with the carriage return, line feed sequences your code editor needs. You will then have a truly miserable job trying to correctly reformat the code.  

     Here is what you must do.  When in the Source Code Forum viewing a post you are interested in copying, click on the icon to edit the post - just as if it were your own post.  I don't believe the PowerBASIC Forum software will actually allow you to change and re-submit someone elses post, however, it will display it for you in the textbox/edit control in which it was originally entered.  At that point you can Select all the text through a 'Right Click', do a 'Copy', and 'Paste' it into your programming editor with all the original author's formatting preserved.  Naturally, you'll have to remove non-compilable text from the editor before it will compile, but you will have saved yourself a lot of hassle. Amazingly enough, even some very  experienced Forum members are unaware of this.  With that out of the way, lets get to work.

     You won't be an expert after working through these examples, but if you follow my explanations carefully, you'll have at least that first toehold you need to get started.  As a prerequisite to mastering this material you should understand functions, return values, parameter passing mechanisms, and TYPEs. It is a great deal of fun.  So lets begin.

      Here are the four steps for creating an SDK or Win32 style Windows Program with PowerBASIC or C:

      1)  Include Windows header file, i.e., Win32Api.inc for PowerBASIC or Windows.h for C;
      2)  Fill out a WndClassEx TYPE (PB) or a WNDCLASSEX structure (C) and Register a Window Class;
          The latter will require making a call to the Api RegisterClassEx() function;
      3)  Create a window of the registered class by making a call to CreateWindowEx();
      4)  Receive and process messages from Windows by creating a message loop and Window Procedure.

      We'll take each one, step by step.  Step #1 is pretty easy.  The following program, which looks pretty short and doesn't seem to do anything, completes that step.  Please compile and run it...

Code: [Select]
#Compile Exe
#Dim All
#Include "Win32Api.inc"

Function PBMain() As Long
End Function

      Please run that code in the PowerBASIC compiler.  If you've set up your IDE to generate and display compiler output you'll see the compiler report that the program contains 49,697 lines of code and it generates an executable containing 9216 bytes on disk!  How so, you might ask?  Well, the answer is that huge Win32Api.inc file.  It contains all the equates, type definitions, and function prototypes (declares) for the core Windows dlls that comprise the operating system.  If you are not sure what all those terms mean, you shortly will, as I'm going to have you digging around in that file pretty deep.

     If you ran the above program you saw that nothing seemed to happen, at least nothing apparent.

     Now lets make one Windows Api call to find the size of the Windows desktop.  Paste this program in your code editor, then compile and run it.  I'll dissect it to death shortly.

Code: [Select]
#Compile Exe
#Dim All
#Include "Win32Api.inc"

Function PBMain() As Long
  Local rc As RECT
  
  Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)
  MsgBox("rc.nRight=" & Trim$(Str$(rc.nRight)) & " And rc.nBottom=" & Trim$(Str$(rc.nBottom)))
  
  PBMain=0
End Function

     When you ran this program you should have seen a message box display the width and height of your computer screen in pixels.  Now I want to explain to you what happened.  First, open up the Win32Api.inc file in PBEdit or whatever editor you are using, or even Notepad would be good enough.

     The file should be included with other #Includes in the \Includes directory of your PB install.

     When you have it open go to the edit menu and do a 'Find' for 'SystemParametersInfo'.  Your editor will locate the DECLARE for that function almost at the bottom of the file.  It is very long and you'll probably need to scroll to see it all.  Copy that declare and paste it somewhere for temporary safe keeping.  Here is how I oftentimes reformat these things to make them easier to examine:

Code: [Select]
    Declare Function SystemParametersInfo Lib "USER32.DLL" Alias "SystemParametersInfoA" _
     ( _
       ByVal uAction As Dword, _
       ByVal uParam As Dword, _
       ByRef lpvParam As Any, _
       ByVal fuWinIni As Dword _
     ) As Long

     The next thing I want you to do is either go to MSDN and search for the SystemParametersInfo function, or better yet, open your copy of Api Help you should have gotten with your compiler.  Below is what you'll see at the top of the page:

Code: [Select]
    BOOL SystemParametersInfo(
       UINT uiAction, // system parameter to query or set
       UINT uiParam,  // depends on action to be taken
       PVOID pvParam, // depends on action to be taken
       UINT fWinIni   // user profile update flag
     );

     Now the SystemParametersInfo Api function is a fairly complex one that can return piles of disparate data, but all we want of it is to get the size of a computer's desktop screen.  You tell SystemParametersInfo what info you want through the first parameter to the function described in the declare as a 'ByVal uAction as Dword'.  If you peruse the possible values for this first parameter you'll find SPI_GETWORKAREA near the bottom.  This is an equate.  This particular function has quite a variety of them.  I want you to now search the Win32Api.inc file for this equate.  In the 'Find' dialog box type %SPI_GETWORKAREA.  Below is what you'll find:

     %SPI_GETWORKAREA           = 48

     Nothing too magical about that.  It's equal to 48.  I've more work for you to do.  Return to the Win32Api.inc file and now do a search for Type RECT.  This is what you'll find...

Code: [Select]
    Type RECT
       nLeft As Long
       nTop As Long
       nRight As Long
       nBottom As Long
     End Type

     Now we have all the pieces of the puzzle together in one place where we can see what is going on. In this next program we will eliminate the Win32Api.inc file altogether just to prove there is nothing magical about it, and so you can see better what is actually happening in an Api program.  Here is the next program...

Code: [Select]
#Compile Exe
#Dim All
%SPI_GETWORKAREA = 48

Declare Function SystemParametersInfo Lib "USER32.DLL" Alias "SystemParametersInfoA" _ 'We're embedding
( _                                                                                    'this declare
  ByVal uAction As Dword, _                                                            'from Win32Api
  ByVal uParam As Dword, _                                                             'right in our
  ByRef lpvParam As Any, _                                                             ' program.
  ByVal fuWinIni As Dword _
) As Long

Type RECT                       'This complex variable type made up of
  nLeft As Long                 'four simple 32 bit longs is used in
  nTop As Long                  'the function call
  nRight As Long
  nBottom As Long
End Type

Function PBMain() As Long
  Local rc As RECT              'To get the screen size the function needs this variable

  Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)
  MsgBox("rc.nRight=" & Trim$(Str$(rc.nRight)) & " And rc.nBottom=" & Trim$(Str$(rc.nBottom)))

  PBMain=0
End Function

     It should produce the exact same output as before, at least in terms of the running program.  The compiler output, however, will be considerably different.  Instead of the compiler reporting that the program contains almost 50,000 lines, it will report 28.  If equates scare you (they still scare me a little bit), you can delete the %SPI_GETWORKAREA in the program's third line and just stick '48' in its place in the function.  That makes the thing look even more tame.

     Note that we took everything we needed out of Win32Api.inc and included it right in our little program, just to show in an easy and non-mysterious manner the pieces of the puzzle and what they do.

     All of these pieces are things with which you should already be somewhat if not greatly familiar.  First off, we have an equate, namely %SPI_GETWORKAREA.  This is conceptually no different from %TRUE = 1 and %FALSE = 0.  No mystery there.  Then we have a rather nasty declare we obtained from the Win32Api.inc file for SystemParametersInfo.  But conceptually, this isn't a whole lot different from something as simple as a function to square a number with a declare like so...

Code: [Select]
    Declare Function MySquare(x As Double) As Double

     ...and a function definition like so...

     Function MySquare(x As Double) As Double
       MySquare=x*x
     End Function

     Its just a little bigger and uglier is all.  Finally, we have a particular TYPE required by one of the parameters of the function itself, that is, a RECT type.  These are simply a bunch of Longs for the coordinates of a rectangle.  Believe me, you'll run into that one a lot in Windows programming as most things ultimately resolve to a rectangle!

     In terms of the function call itself, there are several things worth noting.  First, since this function returns a wide variety of information, depending of course on the equate fed into it, you need to carefully pour through the documentation to know what values need to be filled out, and which don't. Many Api functions are like this.  In our case above we only needed the first and third parameters, and the second and fourth we left at zero.  Here is what the Api reference says about SPI_GETWORKAREA...

     "Retrieves the size of the work area on the primary display monitor. The work area is the portion of the screen not obscured by the system taskbar or by application desktop toolbars."

     "The pvParam parameter must point to a RECT structure that receives the coordinates of the work area, expressed in virtual screen coordinates."

     Now there is a lot for me to comment on there, and a very great deal with which you might be confused.  So if you don't fully understand what it is saying, don't be alarmed.  I'll take you through it all piece by piece.  The part that I'm thinking you might have trouble with is the "pvParam parameter must point to a RECT structure that receives the coordinates of the work area".

     Lets really back up and start at the beginning, perhaps at something you may not have even noticed about this program.  If you look at the way I invoked the SystemParametersInfo function above you'll note I used the Call reserved word of PowerBASIC.  How can this be?  After all, you may be thinking, doesn't a function return a value?  What I've done is thrown away the value returned by the function, which, by the way, wasn't the size of the screen that we were looking for anyway.  For you see, this Api function as well as almost all others return values through function parameters, not return values from functions.

     If you look carefully at the C description of the function in your Api reference or where I copied it into this document above, you'll see the function returns a BOOL, which, in the PowerBASIC declare was converted to a Long.  The fact is, Api functions return SUCCESS/FAILURE codes through return values - not data.  In using the Api you really have to disabuse yourself of the idea of returning data through function return values.

     What you really need to do is think of functions/procedures as two way data transfer mechanisms in which data is both transferred to and received from the function through its parameters rather than through a simple scalar return value.  Another important point is that due to the complexity of the operating system itself, there are oftentimes a great deal of data to transfer through parameters, and to avoid excessively long parameter lists, complex data types are used.  In PowerBASIC we term them TYPEs and in C structures.  In the above program we declared a variable of TYPE RECT, and we passed that object to the SystemParametersInfo function in the third parameter to the function.  Further note that we declared the rc variable of TYPE RECT in PBMain() but we never initialized it to anything before passing it to the function.  The reason we didn't initialize it to anything is because its purpose is to receive information from the function - not to provide the function with information.  It is an Output parameter.

     In terms of that sort of terminology, the first parameter to that function is an Input parameter (SPI_GETWORKAREA) and the third parameter is an Output parameter (rc).

     And now for the bad part.  This is the part where you get lost or the whole thing seems fuzzy or incomprehensible.  I mean the part about "The pvParam parameter must point to a RECT structure....".  Yes, that part.  I won't deny it, this is hard.  The reason its hard is because this is a completely C centric way of looking at the computer.  Had Windows been written in PowerBASIC, or even had the Windows Api documentation been written in PowerBASIC, this difficulty would not exist.  But the fact is that the documentation on the Windows Api is written in C, so if you want to use it with PowerBASIC, you have to learn how to translate the documentation to BASIC datatypes and function calls.  So here is what is happening here.  If the above program was written in C as a console program the PBMain() part would look like this...

Code: [Select]
    int main(void)
     {
      RECT rc;
      //
      SystemParametersInfo(SPI_GETWORKAREA,0,&rc,0)
      printf("rc.Right=%u\trc.Bottom=%u\n",rc.Right,rc.Bottom);
      system("PAUSE");
      //
      return 0;
     }

     Note the variable declaration of rc.  It is backwards from PowerBASIC but nonetheless easily discernable.  Now note the third parameter to the function.  It looks like this...

     &rc.

     Now what could that mean?  What's with the ampersand?  If you refer back to the docs - that '&' symbol is heavily implicated in the "...point to a RECT structure"  bit.  For you see, a pointer is a variable that holds a particular and very specific kind of value, and what that value must be is the address of another variable.  In C, pointers have to be declared as such, and in the above C example it could be done like so...

     RECT rc;
     RECT *ptrRectangle;

     The asterisk in front of ptrRectangle, i.e., *ptrRectangle, tells the C compiler that the variable is not a rectangle structure, but rather a pointer to a ectangle structure, i.e., it holds the address of a rectangle structure allocated in memory somewhere else.  Had I done that in the above program it would have looked like this...

     RECT rc;
     RECT *ptrRectangle;

     ptrRectangle = &rc;
     SystemParametersInfo(SPI_GETWORKAREA,0,ptrRectangle,0)

     What the ampersand symbol is is the 'address of' operator.  It is exactly equivalent to PowerBASIC's VarPtr() function.  It takes the address of anything it is pre-pended to.  Rather than encumbering a program with an unneeded variable, most C programmers would do as in the first case and just put an '&rc' n the function call rather than create a pointer variable and assign the address of the RECT to it.  And don't lose sight of the fact that what this function needs is the address of a RECT variable allocated someplace, anywhere, to put RECT data into (our screen size).  So, for example, if when the program is loaded into memory and storage provided for a RECT TYPE/structure, and the address of the variable happens to be say, 1048256, then that function wants to get 1048256 fed to it.  When it receives 1048256, it already knows what a RECT type is, because it found a type declaration for that type.  And whether that type declaration was read out of the Win32Api.inc, or read from the top of the main module's source it neither knows nor cares.  What is important to the function is that when it has the address of a RECT type, bytes 0 - 3 offset from the base address (1048256) get the upper left x coordinate of the RECT put
into it, bytes 4 - 7 the top, and so on.

     Now the situation with PowerBASIC is interesting.  Recall all we had for that problematic third parameter was a declaration of the parameter like so...

     Local rc As RECT

     ...and it was used in the function call similiarly...

     Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)

     So now you might legitimately ask me this. "OK smart guy, you say that the function needs the address of a RECT type variable passed to it, and PowerBASIC uses the VarPtr() function to do what the '&' operator does in C, so why doesn't the function have a VarPtr(rc) term in it???"  Well, smart guy retorts, "PowerBASIC passes parameters to procedures by reference unless told to do otherwise, which means that the compiler, in setting up the function call mechanism, automatically passes the address of any parameters of the function call!"  So there you have it.  Sixteen bytes of storage were allocated for a RECT type at the top of PBMain(), and in the function call itself PowerBASIC passed the address in memory of that variable to the SystemParametersInfo function because the PowerBASIC declare specified a By Reference parameter.  This is of such overwhelming importance I'll have much more to say about it.

     We will directly turn to creating our first window using the Api, but before we do that lets review what you should have learned in my discussion above.  First, the Win32Api.inc file contains equates, type declarations, and procedure declarations which are necessary for the PowerBASIC compiler, or for any other compiler for that matter, to connect to functions contained in the critical operating system dlls.

     In our simple program above we had an example of each of these items, and we in fact pulled them right out of the Win32Api.inc file and embedded them right in our program.  The next important issue involves passing function parameters to Win32 functions.  There are issues here that you might not have had to face before if all your previous programming experience was within the basic language calling only procedures contained within the run time engine of that language.  This topic we just briefly touched upon, and will have considerably more to say about it.

     Let us now turn to step #2 in my original 4 step process of creating a Windows program (all of the above was only step #1, unfortunately!).  As was the case with SystemParametersInfo() above where we found we needed a RECT type in the function call, creating a window will require a type known as WNDCLASSEX.  You should look for it in Win32Api.inc, but for discussion purposes I have copied it in only slightly altered form below...

Code: [Select]
Type WNDCLASSEX
  cbSize As Dword              'size of the type (just count up the size of its constituent members)
  style As Dword               'class styles are obtained from the docs on the type in Api reference
  lpfnWndProc As Long          'the address of the window procedure
  cbClsExtra As Long           'extra bytes for class specific data
  cbWndExtra As Long           'extra bytes for each window of the class that is instantiated
  hInstance As Dword           'handle to instance of program (actually an address)
  hIcon As Dword               'handle to an icon
  hCursor As Dword             'handle to a cursor
  hbrBackground As Dword       'handle to background brush for window's background
  lpszMenuName As Asciiz Ptr   'pointer to a string containing menu name
  lpszClassName As Asciiz Ptr  'pointer to class name string
  hIconSm As Dword             'yet another handle to an icon
End Type

     In order to create a main window for a program it is necessary to create a variable of this type, fill out the fields of the type variable, then call an Api function named RegisterClassEx() passing to it the address of the variable itself.  This has some similarities to what we did above with SystemParametersInfo().  In that case we created a RECT variable, passed the address of that variable to the function, and the function dutifully filled out the fields for us so we could extract the width and  height of our desktop.  In that sence the RECT type variable was an output parameter.  In this case, our WNDCLASSEX type variable will be an input parameter in that we'll fill out the fields for the function call and then pass the address of the variable to the function.  Don't be overwhelmed by the large number of fields or the seeming complexity of the variables as I'll lead you through the most difficult parts.

     And I'm pretty sure the most difficult parts for you will be the pointers.  The handles I consider to be pretty much in the nature of boilerplate.  In our SystemParametersInfo() program above we already had our first brush with pointers in that the third parameter to that function wanted a pointer to a RECT type.  We really got off easy in that case because in placing that rc variable of RECT type as an argument to that function, the PowerBASIC compiler passed its address automatically to the function, which, after all, is what any function wants when it has a pointer for a parameter.  This occurred, as we previously learned, because PowerBASIC's default parameter passing mechanism is to pass the address of the parameter to the function.  In this case, however, there is no function call involved in filling out the members of the type, and several member variables in the Type require an address, and we are going to have to provide it.  PowerBASIC has several internal functions we can easily use for this.  Take note of the next to last member - lpszClassName.  I consider this to be one of the two most important members of the WNDCLASSEX class, because it is the name Windows will know this class by when it comes time to request of Windows that it instantiate a window of this class for us.

     At this point I'm going to make a slight digression and explain to you something about the variable names in this class, particularly the lpsz stuff.  If you are going to learn the Sdk style of Windows programming you are going to have to learn just a little bit about pointers, addresses and asciiz strings so that you can translate C documentation.  This is actually very important or I wouldn't be doing it so please bear with me.

     Below is a complete C program which arranges a memory allocation for the literal string "Form1", then outputs to the screen all the characters and their addresses.  The output from the program is directly beneath it.

Code: [Select]
#include <stdio.h>

int main(void)
{
  char szName[]="Form1";
  unsigned int i;

  printf("i\tszName[i]\tAsci\tchar\n");
  printf("====================================\n");
  for(i=0;i<strlen(szName);i++)
      printf("%u\t%u\t\t%u\t%c\n",i, &szName[i], szName[i], szName[i]);
  printf("\n");
  printf("%u\t%s\n", szName, szName);
  getchar();

  return 0;
}

Output:

i       szName[i]       Asci    char
====================================
0       2293600         70      F
1       2293601         111     o
2       2293602         114     r
3       2293603         109     m
4       2293604         49      1

2293600 Form1

     The first line of the program beneath main() declares an array of characters and assigns the string literal to it.  The empty braces tell the C compiler to allocate enough memory in the program's data segment to hold the following string and to terminate the string with a null character.  Many Windows programmers prefix string variables with an 'sz' to denote 'string terminated with a zero byte'.  This is the low level representation of character strings used in C and assembly language.  All the C, assembler, and Windows Api Win32 functions expect strings to be represented this way.  In this sense, a string consists of a starting memory address followed by the characters in the string, and then ending with that null byte.

     Note in the printf() function there is a string containing what are termed format specifiers.  The first %u tells the function to interpret the first variable, 'i', after the string as an unsigned interger and to print it.  The %c tells the function to interpret the final szName as a character.  Take note that the first szName in the variable list to output has the ampersand symbol prepended to it, and that causes the address in memory of the character to be printed.  In this case the string started at 2,293,600.

     Note finally in the last printf function call that we've asked the function to output szName twice.

     However, in the output you'll see that what got printed was the following: '2293600 Form1'.  Can you imagine that?  What is happening here is that even though we've told the function to print the same variable twice, we've also told the function to interpret the first szName as an unsigned integer due to the %u format specifier, and to interpret the second szName as a character string with the %s format specifier.  Further, note that the address for szName, i.e., 2293600 is the same number printed out in the loop above for &szName[0].  This is important and is due to the fact that in the C programming language an array name without the brackets such as szName indicates the address of the first byte of the string.  In using the Windows Api you'll constantly have occasion to to fill out member variables of Api Types (structures in C), and you'll constantly see this exact terminology.  Strings are everywhere. They're in combo boxes, listboxes, text boxes, on and on.

     Now lets take a look at the exact same program in PowerBASIC using the Console Compiler....

Code: [Select]
#Compile Exe

Function PBMain() As Long
  Local szName As Asciiz*8
  Local pChar As Byte Ptr
  Local i As Dword

  szName="Form1"
  pChar=VarPtr(szName)
  Print " i             pChar         @pChar       Chr$(@pChar)"
  Print "======================================================"
  For i=0 To 4
    Print i,pChar,@pChar,Chr$(@pChar)
    Incr pChar
  Next i
  Print
  Print "VarPtr(szName)="VarPtr(szName),"szName="szName
  WaitKey$

  PBMain=0
End Function

Code: [Select]
Output:

 i             pChar         @pChar       Chr$(@pChar)
======================================================
 0             1244804       70           F
 1             1244805       111          o
 2             1244806       114          r
 3             1244807       109          m
 4             1244808       49           1

VarPtr(szName)= 1244804     szName=Form1

     You can see it took a few more lines to represent it than in the C program.  C is brutally terse.  We used an Asciiz string to store the term 'Form1', as we did in the C program, and we used the PowerBASIC VarPtr function to extract the address of the string itself.  A Byte Ptr was used to move along the string and print out characters and addresses.  The important point to take home though is that to obtain the address of an Asciiz string we are going to have to use the VarPtr function.  An important rule emerges from this that will be extremely helpful to you in your interpretation of Api documentation, and the rule is that when filling out types that contain a pointer term, i.e., they need the address of a asciiz string, or the address of a structure such as the RECT structure, you are going to have to use the VarPtr function to obtain the required address.  The second part of the rule is that if a pointer is required for a parameter in a function call (and this is a very different thing), if the parameter is being passed by reference, then you just substitute the variable in the function call without using the VarPtr function on it.  The reason you can do this should be clear to you by now.  PowerBASIC will pass the required address as part of the setup of the function call.

     Having laid this important groundwork, we are now in a position to fully understand how to fill out the WNDCLASSEX type so that we can create a window with PowerBASIC.  Below is finally Form1.bas.  I have made it about as short and simple as I could.  Please compile and run it.  Explanations will follow
afterwards.

'Program Name Form1.bas
Code: [Select]
#Compile Exe
#Include "Win32api.inc"        'Equates, declares, types translated from Windows.h

Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  If wMsg=%WM_DESTROY Then       'This is the all important window procedure.  It is not called anywhere
     Call PostQuitMessage(0)     'in this program.  Windows itself calls this function when any event
     WndProc=0                   'occurs that pertains to this window.  The runtime address of this
     Exit Function               'procedure was obtained for Windows by PowerBASIC's CodePtr function
  End If                         'back in WinMain() when the wc.lpfnWndProc member was set in the
                                 'WndClassEx type variable.  All this particular WndProc is interested in
  WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam) 'is being WM_DESTROY'ed when a %WM_DESTROY message comes
End Function                                     'through

Function WinMain(ByVal hIns As Long,ByVal hPrev As Long,ByVal lpCmdLn As Asciiz Ptr,ByVal iShow As Long) As Long
  Local szClassName As Asciiz*6 'The address of this string containing the Class Name will be fed to a WndClassEx type
  Local wc As WndClassEx        'A variable of this type needs to be filled out its address passed to RegisterClassEx()
  Local hMainWnd As Dword       'Handle of Main Window returned by CreateWindowEx() Call
  Local Msg As tagMsg           'Type defined in Win32Api.inc that Windows uses to pass message information

  szClassName             ="Form1"
  wc.cbSize               =SizeOf(wc)
  wc.style                =0
  wc.lpfnWndProc          =CodePtr(WndProc)    'Critically important!  Address of window procedure!
  wc.cbClsExtra           =0
  wc.cbWndExtra           =0
  wc.hInstance            =hIns  'this was obtained from the first parameter in WinMain()
  wc.hIcon                =LoadIcon(%NULL,ByVal %IDI_APPLICATION)  'Api call to LoadIcon()
  wc.hCursor              =LoadCursor(%NULL,ByVal %IDC_ARROW)      'Api call to LoadCursor()
  wc.hbrBackground        =%COLOR_BTNFACE+1                        'Api call to set window color       
  wc.lpszMenuName         =%NULL
  wc.lpszClassName        =VarPtr(szClassName) 'Simple, but very important!
  wc.hIconSm              =LoadIcon(%NULL,ByVal %IDI_APPLICATION)
  Call RegisterClassEx(wc)'This call registers the Form1 class with Windows
  hMainWnd=CreateWindowEx(0,szClassName,"Form1",%WS_OVERLAPPEDWINDOW,200,100,325,300,%HWND_DESKTOP,0,hIns,ByVal 0)
  Call ShowWindow(hMainWnd,iShow)
  While GetMessage(Msg,%NULL,0,0)    'infamous message loop.  WinMain() will be running this message processing
    Call TranslateMessage(Msg)       'loop the entire time your application is running.  When Windows detects that
    Call DispatchMessage(Msg)        'any event pertaining to this program occurs, it will GetMessage() here and
  Wend                               'route the message to the Window Procedure by calling it - here WndProc()

  WinMain=msg.wParam
End Function

     From our first simple explorations with SystemParametersInfo(), you should be able to identify all the elements of this program. Almost at the top, just inside the WndProc() function is an equate - %WM_DESTROY.  Api functions are all over the place.  The first one encountered is PostQuitMessage().  You should know where to find out about this function by now.  If you search your Win Api reference material for PostQuitMessage you'll find a description of it.  If you do a 'Find' in Win32Api.inc you'll come up with the DECLARE.  But my purpose here is not to get to involved with the Window Procedure.  I want you to understand WinMain().

     Back at the very beginning I listed the four steps for creating a window and you can see three of them in WinMain(). At the top a variable of WndClassEx type is dimensioned.  Most of the bulk of WinMain() is the filling out of the fields of this type.  You can think of the type as representing general charactistics of a Window you want to create.  In this case it is a pretty much 'plain jane' window.  I made it as simple as possible.  A lot of it is 'boilerplate'.  Once the fields are filled out the very important function RegisterClassEx() is called.  If you by chance have any C documentation on this function handy, perhaps the Api docs or even Charles Petzold's often recommended Windows books you'll see that a pointer to the WNDCLASSEX variable needs to be passed to this function.  And, being as you have studied very hard and understood everything I explained above about passing parameters in C and PowerBASIC, you clearly see that all you have to do in this PowerBASC program is stick that wc variable between the brackets in the function call because PowerBASIC will pass its address to the Api function.

     That finally brings us to the BIG ONE - the CreateWindowEx() call.  This is the call that will attempt to create a specific instance of the Window Class you just registered.  At this point we are no longer talking generalities.  We want to create a window of a specific class - specifically the szClassName class.  We want it to have a very specific caption, i.e., "Form1'.  The equate %WS_OVERLAPPEDWINDOW is a composite style we would like the window to have.  These can be found in your docs and in Win32Api.inc.

     We want the window to be 200 pixels from the left border of the screen, 100 from the top, 325 pixels wide and 300 pixels deep.  Finally, the parent of the window should be the desktop (another equate).

     The instant the CreateWindowEx() function executes, and before it returns to hit ShowWindow(), Windows will send a %WM_CREATE message to the window procedure listed in the lpfnWndProc (long pointer to function Window procedure) member of the WndClassEx type variable registered for the szClassName member of the type.

     That is the first instant the program will have access to a valid window handle for the program (the hWnd will come through in WndProc()).  After the function returns the ShowWindow() function will cause the window to become visible.  After that the program will drop into the message loop, and keep spinning there until a %FALSE or 0 value is received by the GetMessage() function.  In that case the program will end and WinMain() return.

     This program of course doesn't do much, although it took a fair amount to get us here, didn't it?

     Well, to get the program to do anything we pretty much have to leave WinMain() and move on to the Window Procedure.  I'd venture to say that Windows programmers don't spend much time fooling around in WinMain().

     That is pretty much a copy and paste deal and out! Next we'll move to WndProc()!
« Last Edit: April 07, 2011, 09:40:04 PM by Frederick J. Harris »