我住包子山

this->blog.MoveTo("blog.baozishan.in")

[基础]Win32 Console Applications - Part 2 of 6 from adrianxw.dk

Win32 Console Applications - Part 2.

I started part 1 of this tutorial by showing the classic "Hello Worlrd!" program. The output looked like this.

Hello World

By default, Windows places the path of the executable in the window title. You can change this to a word or phrase of your choice. Here is the Hello World! program modified to do this.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    SetConsoleTitle("Hello!");
    cout << "Hello World!" << endl;
    return 0;
}

Notice I've now added the windows.h header to the program, and a call to SetConsoleTitle(). The output now looks like this.

Hello World

Later on, we'll see that knowing the name of your window can be useful, and setting it yourself is, of course, an easy way of knowing it!

As an aside, almost all of the API routines return a status indicating success or failure. For clarity of the example code in these tutorials, I am not checking this value. To be more professional, you should check the return values of API routines you call. Even if you cannot recover from the error, it is often useful in debugging to know what routine failed, and if possible, why. This program shows the checking of the return status and aborts the program if the routine fails, (note the inclusion of process.h for the exit() API routine). You should refer to your compiler's help or MSDN to see what the return values of a routine are.

#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

int main()
{
    int Status;

    Status = SetConsoleTitle("Hello!");
    if (Status == 0)
    {
        Status = GetLastError();
        cout << "SetConsoleTitle() failed! Reason : " << Status << endl;
        exit(Status);
    }

    cout << "Hello World!" << endl;
    return 0;
}
----------

All consoles have 3 "standard handles" and many of the console handling functions require a "handle" to perform I/O.

A "handle" is simply a 32 bit integer. It is the way Windows differentiates between objects of the same type. Consider the console, it has a title bar, minimize, maximize and close buttons, one or more scroll bars, etc. It is, when you think about it, quite a complicated thing, and somewhere there must be a lot of data that the system is using to make it all work. Windows hides all of that complexity from you, you can play with all this data if you want to of course, but the point here is, you don't have to if you don't want to. Windows looks after it all for you, and all you have to do is tell it the handle of whatever it is you want to use. Once you get into full Windows programming, you'll find a lot of things are used by supplying a "handle". If you don't understand right now, don't worry about it, a handle is easy to get, and easy to use.

To get the standard handles, declare variable of type HANDLE and initialise them with a call to GetStdHandle(). This program, (which doesn't actually do anything!), illustrates the process. We'll use the standard handles later.

#include <windows.h>

int main()
{
    HANDLE hIn;
    HANDLE hOut;
    HANDLE hError;

    hIn = GetStdHandle(STD_INPUT_HANDLE);
    hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    hError = GetStdHandle(STD_ERROR_HANDLE);

    return 0;
}

The standard input handle is used with routines that read data from a console, the standard output handle with routines that send data to a console. The standard error handle also outputs to the console by default, and frankly is not used very much, just be aware of it.

There are a couple of routines ReadConsole() and WriteConsole() that perform I/O using these handles, but for now at least, we'll continue with the standard C++ I/O functions.

----------

Now we've got the handles, lets do something with them, let's move the cursor around. To do this we'll need to use a COORD structure. This is a very simple structure containing an x and y coordinate. It is declared like this.

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD; 

To move the cursor, you simply set up the COORD to the coordinates you want the cursor moved to and call the SetConsoleCursorPosition() API function. Here is a simple program which does just that.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    HANDLE hOut;
    int i;
    COORD Position;

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    for(i=0; i<10; i+=2)
    {
        Position.X = i;
        Position.Y = i;
        SetConsoleCursorPosition(hOut,
                                 Position);
        cout << "*" << flush;
    }

    return 0;
}

The output looks like this.

Move Cursor

As you see, each iteration of the loop moves the cursor two rows down and two columns right. To return the cursor to the "home" position, (top left corner), send it to (0,0). Creative use of this function can give pleasing effects.

----------

The next routine I want to show you is FillConsoleOutputCharacter(). This routine allows you to write a whole load of characters to the console in one go. The function prototype is shown here.

BOOL FillConsoleOutputCharacter(
  HANDLE hConsoleOutput,
  TCHAR cCharacter,
  DWORD nLength,
  COORD dwWriteCoord,
  LPDWORD lpNumberOfCharsWritten
);

A bit more complicated then any of the routines we've used so far, but really very easy to use. The first parameter is the handle to the output buffer, you know how to get that. The next is the character you want to write, easy enough. The next, is how many times you want it to write the character out, still easy. Next is a COORD telling it where to start, we've already covered that. Finally, the routine wants a place where it can tell you how many characters it actually wrote, this has to be therefore, the address of a variable in your program. You may be wondering why this argument is there at all, but consider the situation where your screen buffer is, for example, 5000 characters long. If you tell the routine to output 5100, it will not error, it will fill the 5000 characters, and then tell you that it did that by setting the value accordingly. If you're not interested, ignore it, but you must supply a place for the routine to use, in this case, I've passed the address of the "Written" variable like this &Written.

This program uses FillConsoleOutputCharacter() to draw a line of 15 "X" characters starting at (4,4), the unsuprising output of the program is also shown below.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    HANDLE hOut;
    COORD Position;
    DWORD Written;

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    Position.X = 4;
    Position.Y = 4;

    FillConsoleOutputCharacter(hOut,
                               'X',
                               15,
                               Position,
                               &Written);

    return 0;
}
Fill Console

See what happens if I change the call to output 150 instead of 15 characters, (I've shrunk the picture to make it load faster - changing the call did not change the screen resolution!!!).

Fill Console

The point to note is that when the routine reached the end of the line, it continued at the beginning of the line below. Suppose, instead of line 5 column 5 I'd said (0,0), and suppose I'd used a space character instead of an 'X', and suppose I'd told it to draw enough characters to completely fill the screen buffer - hey - I've cleared the screen! Clearing the screen is THE most common console related question that comes up in the forums, there's the answer.

----------

It is also possible to read one or more characters from a specific position in the screen buffer. To do that, we use the ReadConsoleOutputCharacter() API routine. This routine has very similar parameters to the last, it's prototype is shown here.

BOOL ReadConsoleOutputCharacter(
  HANDLE hConsoleOutput,
  LPTSTR lpCharacter,
  DWORD nLength,
  COORD dwReadCoord,
  LPDWORD lpNumberOfCharsRead 
);

As before, the first is the standard handle. Next, remember, we are reading now, so the routine wants somewhere to write the information to, so it needs a pointer to a place where it can store characters. The rest of the parameters are as before, the number of characters to process, the start point, and a pointer to a place it can write the actual number read.

This program uses the routine twice, once to retrieve the single character at the (0,0) position, and a second time to retrieve 5 characters starting from position (4,0). If the number of characters requested is longer than the current line, reading continues from the first character of the line below as before. Again, as before, if the number requested is more than there are in the buffer, the characters up to the end of the buffer are returned, the actual number appearing in the final parameter.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    HANDLE hOut;
    char Letter;
    char Letters[5];
    COORD Where;
    DWORD NumRead;
    int i;

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    cout << "A line of little consequence." << endl;

    Where.X = 0;
    Where.Y = 0;
    ReadConsoleOutputCharacter(hOut,
                               &Letter,
                               1,
                               Where,
                               &NumRead);
    cout << "Letter at (0,0) is " << Letter << endl;

    Where.X = 4;
    ReadConsoleOutputCharacter(hOut,
                               Letters,
                               5,
                               Where,
                               &NumRead);
    cout << "5 letters starting from (4,0) ";
    for (i=0; i<5; i++)
    {
        cout << Letters[i];
    }
    cout << endl;

    return 0;
}

The output from the program looks like this.

ReadConsoleOutputCharacter

It would have been quite permissable to dimension the Letters array to 6, read the 5 characters from the console then manually set the final character to NULL. In that way, the cout could have taken the name of the string, (as it is now NULL terminated), instead of writing the characters out individually in a loop.

In the next part of the tutorial, we'll look at the limited graphical capabilities of consoles, drawing lines, boxes and grids.

Win32 Console Applications - Part 2.

I started part 1 of this tutorial by showing the classic "Hello Worlrd!" program. The output looked like this.

Hello World

By default, Windows places the path of the executable in the window title. You can change this to a word or phrase of your choice. Here is the Hello World! program modified to do this.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    SetConsoleTitle("Hello!");
    cout << "Hello World!" << endl;
    return 0;
}

Notice I've now added the windows.h header to the program, and a call to SetConsoleTitle(). The output now looks like this.

Hello World

Later on, we'll see that knowing the name of your window can be useful, and setting it yourself is, of course, an easy way of knowing it!

As an aside, almost all of the API routines return a status indicating success or failure. For clarity of the example code in these tutorials, I am not checking this value. To be more professional, you should check the return values of API routines you call. Even if you cannot recover from the error, it is often useful in debugging to know what routine failed, and if possible, why. This program shows the checking of the return status and aborts the program if the routine fails, (note the inclusion of process.h for the exit() API routine). You should refer to your compiler's help or MSDN to see what the return values of a routine are.

#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

int main()
{
    int Status;

    Status = SetConsoleTitle("Hello!");
    if (Status == 0)
    {
        Status = GetLastError();
        cout << "SetConsoleTitle() failed! Reason : " << Status << endl;
        exit(Status);
    }

    cout << "Hello World!" << endl;
    return 0;
}
----------

All consoles have 3 "standard handles" and many of the console handling functions require a "handle" to perform I/O.

A "handle" is simply a 32 bit integer. It is the way Windows differentiates between objects of the same type. Consider the console, it has a title bar, minimize, maximize and close buttons, one or more scroll bars, etc. It is, when you think about it, quite a complicated thing, and somewhere there must be a lot of data that the system is using to make it all work. Windows hides all of that complexity from you, you can play with all this data if you want to of course, but the point here is, you don't have to if you don't want to. Windows looks after it all for you, and all you have to do is tell it the handle of whatever it is you want to use. Once you get into full Windows programming, you'll find a lot of things are used by supplying a "handle". If you don't understand right now, don't worry about it, a handle is easy to get, and easy to use.

To get the standard handles, declare variable of type HANDLE and initialise them with a call to GetStdHandle(). This program, (which doesn't actually do anything!), illustrates the process. We'll use the standard handles later.

#include <windows.h>

int main()
{
    HANDLE hIn;
    HANDLE hOut;
    HANDLE hError;

    hIn = GetStdHandle(STD_INPUT_HANDLE);
    hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    hError = GetStdHandle(STD_ERROR_HANDLE);

    return 0;
}

The standard input handle is used with routines that read data from a console, the standard output handle with routines that send data to a console. The standard error handle also outputs to the console by default, and frankly is not used very much, just be aware of it.

There are a couple of routines ReadConsole() and WriteConsole() that perform I/O using these handles, but for now at least, we'll continue with the standard C++ I/O functions.

----------

Now we've got the handles, lets do something with them, let's move the cursor around. To do this we'll need to use a COORD structure. This is a very simple structure containing an x and y coordinate. It is declared like this.

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD; 

To move the cursor, you simply set up the COORD to the coordinates you want the cursor moved to and call the SetConsoleCursorPosition() API function. Here is a simple program which does just that.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    HANDLE hOut;
    int i;
    COORD Position;

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    for(i=0; i<10; i+=2)
    {
        Position.X = i;
        Position.Y = i;
        SetConsoleCursorPosition(hOut,
                                 Position);
        cout << "*" << flush;
    }

    return 0;
}

The output looks like this.

Move Cursor

As you see, each iteration of the loop moves the cursor two rows down and two columns right. To return the cursor to the "home" position, (top left corner), send it to (0,0). Creative use of this function can give pleasing effects.

----------

The next routine I want to show you is FillConsoleOutputCharacter(). This routine allows you to write a whole load of characters to the console in one go. The function prototype is shown here.

BOOL FillConsoleOutputCharacter(
  HANDLE hConsoleOutput,
  TCHAR cCharacter,
  DWORD nLength,
  COORD dwWriteCoord,
  LPDWORD lpNumberOfCharsWritten
);

A bit more complicated then any of the routines we've used so far, but really very easy to use. The first parameter is the handle to the output buffer, you know how to get that. The next is the character you want to write, easy enough. The next, is how many times you want it to write the character out, still easy. Next is a COORD telling it where to start, we've already covered that. Finally, the routine wants a place where it can tell you how many characters it actually wrote, this has to be therefore, the address of a variable in your program. You may be wondering why this argument is there at all, but consider the situation where your screen buffer is, for example, 5000 characters long. If you tell the routine to output 5100, it will not error, it will fill the 5000 characters, and then tell you that it did that by setting the value accordingly. If you're not interested, ignore it, but you must supply a place for the routine to use, in this case, I've passed the address of the "Written" variable like this &Written.

This program uses FillConsoleOutputCharacter() to draw a line of 15 "X" characters starting at (4,4), the unsuprising output of the program is also shown below.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    HANDLE hOut;
    COORD Position;
    DWORD Written;

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    Position.X = 4;
    Position.Y = 4;

    FillConsoleOutputCharacter(hOut,
                               'X',
                               15,
                               Position,
                               &Written);

    return 0;
}
Fill Console

See what happens if I change the call to output 150 instead of 15 characters, (I've shrunk the picture to make it load faster - changing the call did not change the screen resolution!!!).

Fill Console

The point to note is that when the routine reached the end of the line, it continued at the beginning of the line below. Suppose, instead of line 5 column 5 I'd said (0,0), and suppose I'd used a space character instead of an 'X', and suppose I'd told it to draw enough characters to completely fill the screen buffer - hey - I've cleared the screen! Clearing the screen is THE most common console related question that comes up in the forums, there's the answer.

----------

It is also possible to read one or more characters from a specific position in the screen buffer. To do that, we use the ReadConsoleOutputCharacter() API routine. This routine has very similar parameters to the last, it's prototype is shown here.

BOOL ReadConsoleOutputCharacter(
  HANDLE hConsoleOutput,
  LPTSTR lpCharacter,
  DWORD nLength,
  COORD dwReadCoord,
  LPDWORD lpNumberOfCharsRead 
);

As before, the first is the standard handle. Next, remember, we are reading now, so the routine wants somewhere to write the information to, so it needs a pointer to a place where it can store characters. The rest of the parameters are as before, the number of characters to process, the start point, and a pointer to a place it can write the actual number read.

This program uses the routine twice, once to retrieve the single character at the (0,0) position, and a second time to retrieve 5 characters starting from position (4,0). If the number of characters requested is longer than the current line, reading continues from the first character of the line below as before. Again, as before, if the number requested is more than there are in the buffer, the characters up to the end of the buffer are returned, the actual number appearing in the final parameter.

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    HANDLE hOut;
    char Letter;
    char Letters[5];
    COORD Where;
    DWORD NumRead;
    int i;

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    cout << "A line of little consequence." << endl;

    Where.X = 0;
    Where.Y = 0;
    ReadConsoleOutputCharacter(hOut,
                               &Letter,
                               1,
                               Where,
                               &NumRead);
    cout << "Letter at (0,0) is " << Letter << endl;

    Where.X = 4;
    ReadConsoleOutputCharacter(hOut,
                               Letters,
                               5,
                               Where,
                               &NumRead);
    cout << "5 letters starting from (4,0) ";
    for (i=0; i<5; i++)
    {
        cout << Letters[i];
    }
    cout << endl;

    return 0;
}

The output from the program looks like this.

ReadConsoleOutputCharacter

It would have been quite permissable to dimension the Letters array to 6, read the 5 characters from the console then manually set the final character to NULL. In that way, the cout could have taken the name of the string, (as it is now NULL terminated), instead of writing the characters out individually in a loop.

In the next part of the tutorial, we'll look at the limited graphical capabilities of consoles, drawing lines, boxes and grids.

posted on 2006-07-20 22:46 Gohan 阅读(669) 评论(0)  编辑 收藏 引用 所属分类: C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理