Hardcore Clarion - 32 Bit DOS? Not Quite, But it's close...

By Paul Attryde

Posted December 1 1998

Printer-friendly version

Download the code here

In the age of Windows development, one of the things most developers forget is that not all programs have to use the GUI. There are, in fact, two types of 32-bit applications that can run under Windows - the typical GUI applications that we all know and love, and console applications.

As their name suggests, console applications "provide consoles that manage input and output (I/O) for character-mode applications (applications that do not provide their own graphical user interface)". It's not often (read as "almost never") that we need to write console applications in Clarion, just because it's so easy to write GUI applications - how difficult can it be to press Ctrl-F, define a screen and type the word ACCEPT?

However, there is always the possibility that some day, for some reason, you may want to write a console application. If you think of them as 32-bit DOS applications, which they're really very similar to, then I'm sure you can think of some more uses. Admittedly the console screen handling isn't that easy, but once you write a couple of wrapper functions you can quickly write a character-based application that breaks the segment:offset memory handling of DOS and uses the entire linear memory of the machine.

So, this month I'm going to show you how to write a console application. It does nothing except prompt the user to press a key and, when they press X, it quits. You can certainly do a lot more, such as setting the cursor position, and foreground and background colour, but for now we'll keep it simple.

There are actually 42 API functions related to console applications, but we'll only use a few of those today:

GetStdHandle

SetStdHandle

AllocConsole

FreeConsole

CreateConsoleScreenBuffer

SetConsoleActiveScreenBuffer

WriteConsole

ReadConsole

SetConsoleTitle

SetConsoleMode

GetConsoleMode

Assuming we've prototyped the functions correctly (using the trusty MSDN), the first thing we need to do is actually create a console to use. GUI applications don't have a console associated with them - that's why they're GUI applications! A console can have one input stream but many output streams, of which one must be active. In Microsoft terms they're called the "console input buffer " and "console screen buffer"

To allocate our console, we call AllocConsole() (we can only call AllocConsole() from within a GUI application - for other environments that can create native console applications, calling AllocConsole() again produces an error). This creates a new console for use with our GUI application, and sets up the standard input, output and error handles. If we've already called AllocConsole() once already, this call will fail and we have to call FreeConsole() before we can allocate another.

If ~AllocConsole()
Halt(1,'Unable to allocate console (' & GetLastError() & ')')
End

The next thing we have to do is create the first (of possibly many) of our console screen buffers. We do this by calling CreateConsoleScreenBuffer(). If you intend to have multiple screen buffers then you will need to call it as many times as required (you may want to prepare screens in the background and only display them when required). As is the case with a lot of API calls, you have to pass the access rights you want the object to have. I'm not quite sure why you would want to create a console which doesn't have read or write access, but it's certainly possible.

The 0 in the following example means that the buffer can't be shared for read or write access, and the CONSOLE_TEXTMODE_BUFFER is the only type of console you can currently create.

Loc:OutputHandle = CreateConsoleScreenBuffer(GENERIC_READ+|
  GENERIC_WRITE,0,NULL,CONSOLE_TEXTMODE_BUFFER,NULL)
If Loc:OutputHandle = INVALID_HANDLE_VALUE
 Halt(2,'Unable to create console (' & GetLastError() & ')')
End

Once the screen buffer has been created, we need to make it active, and just because this is a demo program I also set the console title. You accomplish this by calling SetConsoleActiveScreenBuffer() and SetConsoleTitle(). Be aware that SetConsoleTitle() works on the current console window, so that's why we don't have to pass in the handle to current screen buffer - although we can have many screen buffers, they will all share the same physical window.

If SetConsoleActiveScreenBuffer(Loc:OutputHandle) = 0 Then
Halt(3,'Unable to set active console (' & GetLastError() & ')')
End
Loc:Windowtitle = 'Clarion 5 32-bit Console Application'
If SetConsoleTitle(Address(Loc:Windowtitle)) = 0 Then
Message('Unable to set console title (' & GetLastError() & ')')
End

If you intend to read from the console (i.e., you want the user to press a key in response to a prompt) you'll also need to get the input handle. I did find that Win2000 (NT 5 to those of you who don't keep up with the news) and Win95/98 are slightly different here. Win2000 lets you read directly from STD_INPUT_HANDLE but under Win95/98 you must get the standard handle first and use the returned copy.

Loc:InputHandle = GetStdHandle(STD_INPUT_HANDLE)
If Loc:InputHandle = INVALID_HANDLE_VALUE
Halt(4,'Unable to get console input handle (' & GetLastError() & ')')
End

There are a couple of different options that you can set regarding input from a console. They are:

Value

Meaning

ENABLE_LINE_INPUT Any data read from the console must be followed by a carriage return character.
ENABLE_ECHO_INPUT .Characters are written to the active screen buffer as they are read. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.
ENABLE_PROCESSED_INPUT Ctrl+C is processed by the system and is not placed in the input buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed characters are handled by the system.
ENABLE_WINDOW_INPUT User interactions that change the size of the console screen buffer are reported in the console's input buffer.
ENABLE_MOUSE_INPUT If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by mouse movement and button presses are placed in the input buffer.

When a console is created, all input modes except ENABLE_WINDOW_INPUT are enabled by default, but in my case, I don't want the user to have to press Enter to end the input, so I would want to disable ENABLE_LINE_INPUT. I would really like to leave ENABLE_ECHO_INPUT, but it can only be used if ENABLE_LINE_INPUT is enabled. The easy workaround for that is to immediately write any characters read back to the screen again. Again, because this is a simple application I don't really care about mouse or window resize events, so all I end up doing is enabling ENABLE_PROCESSED_INPUT like this:

If ~SetConsoleMode(Loc:InputHandle,ENABLE_PROCESSED_INPUT )
 Halt(5,'Unable to set console mode (' & GetLastError() & ')')
End

Once you're reached this point your console is set up so that you can read and write to the console using ReadConsole() and WriteConsole(). Any keys the user presses will not be echoed to the screen, but the console will automatically handle backspace & enter for you. In this sample application, I just prompt the user to press a key, and quit when they press 'X'

Loop
 Loc:OutBuffer = 'press any key to continue, X to quit<10><13>'
 x# = WriteConsole(Loc:OutputHandle,Address(Loc:OutBuffer),Len(Loc:OutBuffer),|
                                           Address(Loc:BytesWritten),NULL)
 Clear(Loc:InBuffer)
 Loop
  IF ReadConsole(Loc:InputHandle,Address(Loc:InBuffer),100,Address(Loc:BytesRead),NULL) =
0 THEN
   Halt(5,'Error on read console (' & GetLastError() & ')')
   Break
  End
 Until Loc:BytesRead > 0
 If Upper(Loc:Inbuffer[1]) = 'X' then
  Break
 End
End

All that's left now is remembering to free the console when your application has finished.

If ~FreeConsole()
 Halt(6,'Unable to free console (' & GetLastError() & ')' )
End

And voila! You now have a simple 32-bit character mode application. At first glance its uses are admittedly limited - even MSDN only uses the example of a GUI process creating a console when an error occurs that prevents it from using its normal graphical interface. With a lot of work, I see no reason why conventional CDD applications can't be converted, but that's almost certainly beyond the scope of all but the simplest DOS applications.

So, back to the original question - why? Well, there are a number of Windows applications that are easier to write if they're character based - anything to do with debugging is the first example that springs to mind. (For those of you who read Dr. Dobbs, my CW port of the character-based debugger in November's issue is progressing, although not as fast as I'd like.)

Summary

I'll be the first to admit that in the grand scheme of things it isn't much, but you never know when it may come in useful.

Next Time ...

Using the registry from within CW applications

And Remember ...

As always, the best place for information is MSDN. If you don't subscribe, you can still get to the information via the web at http://msdn.microsoft.com/

Article comments

Post a comment

You must be logged on to post comments.

Clarion Roadmap

Try the roadmap (beta)

Search ClarionMag

 

Advanced search

From the archives

Sending Clarion Reports as Email Attachments (Part 1)

1/9/2001 12:00:00 AM

The email capability in version 5.5 is a nice addition to the Clarion toolset. What is still missing however, is the ability to easily send a report as an email attachment. In this article David Potter demonstrates one possible solution to this problem. Part 1 of 2.