WinDosIO is a powerful dynamic link library which allows structured programming, terminal IO and established graphics routines to work under Microsoft Windows. Yet the full Windows API is always available. Your programs can constitute any combination of the structured and event driven paradigms. Many DOS programs can be ported to Windows without changing a single line of code!
Getting Started
The easiest way to use WinDosIO is to let WinDosIO take care of all the Windows functions for you. Take a look at EXAMPL2, included on the distribution disk and printed below.
Look familiar? Its the classic "Hello, World" program for beginning C programmers. Have you ever seen the Window's version of this simple program? It goes on for several pages. Yet the WinDosIO version of this program looks exactly like the DOS or UNIX version with one exception. Instead of including <stdio.h>, <WinDosIO.h> was included. That is only because this program can be compiled under any memory model. However, since printf is located in the WinDosIO DLL, it is always far(a 4 byte address). If you compile with the large memory model then <stdio.h> can be included instead of <WinDosIO.h>. In that case, "Hello, World" converts from DOS to Windows with no changes at all to the code! It can be resized, moved, and up to 32 instances can be run at once.
It Isn't Magic
Part of the secret behind the seamless conversion is a source module WinDosIO.C, included on the distribution disk. You compile and link this module with your DOS style program and the WinDosIO import library. A compiler which includes the Window's Software Development Kit is also required. By following the sample compile and link instructions below, you now know enough to begin converting DOS programs to Microsoft Windows.
WinDosIO.C, The Secret Unfolds
Experienced users of WinDosIO will want to take advantage of Microsoft Windows capabilities to enhance their programs ported over from DOS. Understanding how to integrate the environments, requires a basic understanding of WinDosIO.C
/* Use WinDosIO(WD_DESTROY,hwnd, 0); to terminate application
without having to click on system close icon.
*/
while( GetMessage( &msg, 0, 0, 0 ) != 0 )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return 0;
}
Notice the #include files at the top include both <windows.h> and <WinDosIO.h>. Windows.h is included with the Window's Software Development Kit. The #define's indicate that up to 16 command line arguments can be given(15 plus the program name) in a command line not to exceed 128 characters.
The WinMain routine is the high level routine of all Windows programs in C and C++. It is declared as returning an integer and following the Pascal calling convention for its arguments. The function is called with four arguments. The first, hInstance is a unique instance handle for this particular instance of the program. The second argument hPrevInstance is non-zero if at least one previous instance of the program is currently executing. The third argument is the command line used to execute the program. The fourth indicates how to initially display the main window.
WinDosIO.C must convert the command line into standard argc, argv format. To parse in a memory model consistent manner, the command line is first moved onto the stack.
Next the WinDosIO DLL is initialized with the instance handle of the program. Without this initialization call , WinDosIO(WD_INIT, hInstance, 0) , no other WinDosIO functions will operate properly. If you write your own WinMain function, you must initialize WinDosIO before using any of the functions.
Every Windows program has at least one window, often called the main or high level window. Associated with this window is a window procedure which handles messages sent by Windows itself. To create this main window two WinDosIO function calls are required. The first WinDosIO(WD_REGISTER_WINDOW, hPrevInstance, 0) associates certain properties, including the window procedure with the WinDosIO window class. hPrevInstance is an argument, because this is only done if not previously done by another instance of the program currently running. Having registered the WinDosIO window class, the main window itself is created. This is done with a call to WinDosIO(WD_CREATE_MAIN_WINDOW, cmdshow, windowTitle). The windowTitle must be a far pointer to a character string cast as a long. The example shows how to do this with a simple string.
This is all well and good for many programs, however, as you gain proficiency in Window's programming, you will want to create your own main window and enhance the window procedure for additional functionality. Here is the code which is executed in the DLL when these two WinDosIO functions are executed.
// Some of the constructs, such as this comment are in C++
long far pascal WndProc(HWND, WORD, WORD, LONG);
// Called when WinDosIO(WD_REGISTER_WINDOW,...) is called
static void RegisterWindow(HANDLE hPrevInstance)
{
// Check to insure application has been registered
// If user registers their own window, this is not required
AI = GetAppinfo();
if (!AI)
Exit(DOSCOM,1);
// If another instance of the program has not already registered
// the window class.
if (!hPrevInstance)
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WndProc; // Window procedure
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = AI->hInst;
wc.hIcon = LoadIcon( 0, IDI_APPLICATION );
wc.hCursor = LoadCursor( 0, IDC_ARROW );
wc.hbrBackground = COLOR_WINDOW + 1;
wc.lpszMenuName = "WinDosIO";
wc.lpszClassName = "WinDosIO"; // Class Name
RegisterClass( &wc );
}
}
// Called when WinDosIO(WD_CREATE_MAIN_WINDOW,...) is called
return hwnd; // Return the handle to the newly created window
}
// This is the minimum Main Window Procedure required for WinDosIO
long far pascal WndProc(HWND hwnd, WORD msg, WORD wParam, LONG lParam)
{
switch( msg )
{
case WM_SETFOCUS:
WinDosIO(WD_SETFOCUS,0,0);
break;
case WM_CLOSE:
WinDosIO(WD_DESTROY,hwnd,0);
return FALSE;
case WM_DESTROY:
PostQuitMessage(0);
return FALSE;
}
return DefWindowProc(hwnd,msg,wParam,lParam);
}
If you choose to create your own main window, there are three messages that the associated window procedure must respond to. These are WM_SETFOCUS, WM_CLOSE and WM_DESTROY. The window procedure must also be exported in the EXPORTS section of the .def file or with the _export keyword available on some compilers.
The WM_SETFOCUS uses WinDosIO(WD_SETFOCUS,0,0) to inform the DLL that the current termIO window(See below), is to get the input (and output) focus. Your program can take other actions here as well.
The WM_CLOSE message closes down WinDosIO for your application, by calling WinDosIO(WD_DESTROY,hwnd,0). hwnd is the handle of the main window returned by CreateMainWindow above. This WD_DESTROY function can also be called as the last line of your program, if you do not wish the window to persist beyond the programs termination. If you wish to terminate early, call WinDosIO(WD_DESTROY,...) before calling exit().
The WM_DESTROY message will be posted by the WD_DESTROY function called out of WM_CLOSE, once it has closed down WinDosIO. Like most standard Windows programs, it posts a WM_QUIT message.
If you register your own main window class, then you must also write the corresponding window procedure. You can create your own main window of class "WinDosIO" after calling WinDosIO(WD_REGISTERWINDOW,...) without writing a window procedure.
With the main window created, there is still another step required before you can use printf's, gets, gotoxy, arc, etc. You must create at least one termIO window. This is a window of a pre-registered class which serves as a glass TTY for your terminal input and output, as well as graphics. Any given application can create up to 128 of these windows, although usually one is sufficient. The generic WinDosIO.C creates one of these windows . It is created to be the same size as the main window, so the user only sees one window. This is done in WinDosIO.C by getting the size of the main window with GetClientRect, filling the CWS structure with this information and calling WinDosIO(WD_CREATE_CHILD_WINDOW,0,(unsigned long)cwsP) to actually create the window.
You can fill in cws with your own values to control the size, position and other properties of the termIO window, or you can create the termIO window yourself. Here is the code used inside of the DLL to create a termIO window.
The important thing to note is that the window is of type "termIO". AI->owner contains the handle of the parent or main window created above, while AI->hInst contains the instance handle of the application.
After creating the termIO window , WinDosIO.C parses the command line into argc, argv format. Once that is complete, it calls main, your program, with argc and argv. With all of the initialization complete, your program can call printf and the over 200 other functions provided in the WinDosIO DLL. When your program has completed and main returns, WinDosIO.C falls into a standard windows message loop, waiting to be terminated.
Proper Termination
With Windows, the user can terminate your program, by clicking on the system close icon long before you planned on having the program terminate. Unless you disallow the close in the WM_CLOSE message handling procedure, WinDosIO will be closed down and the WM_QUIT message will be posted. When this happens, WinDosIO cooperates by returning a negative return code, a NULL pointer or nothing depending on function type. It also sets its internal error to WDE_USERQUIT. This can be obtained with the WinDosIO(WD_ERRNO,0,0) function.
Most DOS programs do not bother checking the return codes from printf and other such functions, especially for a negative value. This is OK, as long as the program does not get into a loop waiting for IO and not checking for EOF or NULL pointers, depending on function. The most well behaved programs will sense an error, and return from main. Another option is to call exit() although this could result in a non-graceful termination with the infamous UAE. Another option is to do nothing, falling through to the end, assuming no damage will be done due to lack of IO, and assuming there are no infinite loops waiting on a specific input without checking for error returns. The latter case could hang up Windows itself! If there are such critical periods, the application developer should set a flag which disables the WM_CLOSE action. The flag must be cleared at some point, or the user will not be able to terminate the program.
case WM_CLOSE:
if (!closeDisabled)
WinDosIO(WD_DESTROY,hwnd,0);
return FALSE;
Memory Models and WinDosIO
Programs can be compiled in any of the memory models, however, all functions in the DLL are far and all pointers must also be far. If you compile with the large model, this is not a problem. However, if you compile with any other model, certain precautions should be taken.
Make sure that stdio.h, conio.h and graphics.h(graph.h) are not included. All correct function prototypes can be found in WinDosIO.h. This only leaves the problem of arguments that cannot be prototyped such as the 2nd and subsequent arguments in printf and scanf. MAKE SURE YOU EITHER DECLARE ANY POINTERS USED AS FAR OR CAST THEM AS FAR. There is no way the compiler can warn you about this problem.
One method of development is to use the large model until the program is working and then switch to a more efficient memory model. For existing programs, compile first with the large model, to eliminate this potential source of error.
Getting a Window Handle
Even if you did not create your own termIO window(s), you can get the handle of the current termIO window with a call to WinDosIO(WD_GET_HANDLE,0,0). Once you have the handle to the window, there are many things that can be done, such as moving, resizing, intercepting and sending messages.
Getting a Device Context Handle
The graphics routines in the WinDosIO library give you a great deal of power. However, it would be nice to mix the very powerful Windows GDI drawing functions on the same window termIO window used for the WinDosIO graphics functions. In order to do make these drawings, you must obtain a device context handle for the termIO window.
This is not the device context handle directly associated with the screen, but rather one associated with a bitmap which keeps the termIO window persistent. For that reason, the drawing is done prior to the actual painting of the screen. Here is an example drawing a rounded rectangle on the current termIO window.
This code can be freely mixed with other graphics calls, printf's, getch, etc.
Input and Output Focus
When a termIO window is created, it is automatically given the input focus and "output focus". Output focus is a WinDosIO term which refers to the current window where any terminal IO or graphics will be displayed.
Only after the first termIO window is created do supported functions become operational.
Selecting a Window
There can be up to 128 termIO windows per application. By default, the last window created has the input and output focus. If a window is created with the WDS_SELECT style, that window will gain the input and output focus when it is clicked on with the left button of the mouse. Any printing or prompting that is happening in one termIO window will switch over to the window that receives the input and output focus. A window can be given the input and output focus from software with WinDosIO(WD_SELECT,termIOHandle,0); where termIOHandle is the window handle returned from the CreateWindow or WinDosIO(WD_CREATE_CHILD_WINDOW,...) call.
Error Codes
All error codes are negative numbers. While the WinDosIO calls return their errors directly, the supported functions have well defined return codes. Many of them return EOF or NULL on error. To obtain the actual WDE error code, errorCode = WinDosIO(WD_GETERRNO,0,0); These error codes are described in the reference section of this manual.
Microsoft and Borland graphics routines have their own error conventions and codes. The error is set internally and is returned in the appropriate form by the appropriate function, grstatus() for the Microsoft graphics functions and graphresult() for the Borland Graphics functions. The reference section lists errors for functions in the expected form, although equivalent errors can often be obtained through one of the other two functions.
Notification Codes
The main window receives notification of four events through a WM_COMMAND message. If the termIO window is used in a dialog box, the dialog box procedure receives the notification. As in the notification messages from built in child controls, the ID of the window is found in the wParam. The handle of the child window is stored in the LOWORD of lParam and the notification code itself is stored in the HIWORD.
Notification codes are described in the reference section and are any of the following: WDN_SETFOCUS, WDN_KILLFOCUS, WDN_LBUTTON, WDN_RBUTTON.
EXAMPL1
Exampl1 on the distribution disk, "Counting to a Million" demonstrates a program which creates its own main window and window procedure, creates 11 termIO windows and uses the windows timer function to automatically switch the input and output focus. In addition to the timer, the user can click on one of the buttons and set the output focus to the associated window.
Contrast this program with exampl2, the simple "Hello, World" example. Exampl1 is a Windows program which makes use of WinDosIO while exampl2 is a DOS program, converted to windows with WinDosIO.
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdline, int cmdshow)
{
HWND hwnd;
WNDCLASS wc;
short i;
RECT r;
struct WINDOSIO w;
long l;
MSG msg;
char buffer[240];
char far *p;
/* Register Main Window Class */
hInst = hInstance;
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( 0, IDI_APPLICATION );
wc.hCursor = LoadCursor( 0, IDC_ARROW );
wc.hbrBackground = COLOR_WINDOW + 1;
wc.lpszMenuName = "Example1";
wc.lpszClassName = "Example1";
RegisterClass( &wc );
}
/* Initialize terminal IO */
WinDosIO(WD_INIT,hInstance,0);
/* Create Main Window */
hwnd = CreateWindow(
"Example1",
"Counting to a Million",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
0,
0,
hInstance,
NULL
);
ShowWindow(hwnd, cmdshow);
/* Wrap width for small windows */
w.wrapWidth = 12;
/* Create 5 buttons and 5 associated counting windows */
for (i = 0; i < 5; i++)
{
r.top = 130;
r.left = 10 + (i * 140);
handle[i] = CreateWindow("termIO",0,
WS_CHILD | WDS_NOPAGES | WDS_NOTEXTBUFFER,
r.left,r.top,70,60,
hwnd,i+50,hInstance,(LPSTR)&w);
textcolor(i + 10);
ShowWindow(handle[i],SW_SHOW);
UpdateWindow(handle[i]);
r.top = 18;
r.left = 25 + (i * 140);
button[i] = CreateWindow("termIO",0,
WS_CHILD | WDS_SELECT | WDS_NOPAGES,
r.left,r.top,40,38,
hwnd,i,hInstance,(LPSTR)&w);
textcolor(i + 10);
ShowWindow(button[i],SW_SHOW);
UpdateWindow(button[i]);
printf("╔═══╗\n");
printf("║ %d ║\n",i+1);
printf("╚═══╝");
}
/* Create ID and Password window */
r.top = 200;
r.left = 10;
idWindow = CreateWindow("termIO","ID and Password",
WS_CHILD | WS_THICKFRAME | WS_CAPTION |
WDS_SELECT | WDS_NOPAGES,
r.left,r.top,290,100,
hwnd,20,hInstance,0);
textcolor(WHITE);
ShowWindow(idWindow,SW_SHOW);
UpdateWindow(idWindow);
/* Get info from user */
printf("Enter ID ? ");
gets(buffer);
_setcursortype(_NOCURSOR);
p = getpass("Enter password:");
printf("The password is '%s'\n",p);
_setcursortype(_SOLIDCURSOR);
printf("Hit any key to continue\n");
getche();
ShowWindow(idWindow,SW_HIDE);
UpdateWindow(idWindow);
/* Loop through the windows while counting to a million,
using the timer to change windows.
*/
currentHandle = 4;
WinDosIO(WD_SELECT,handle[currentHandle],0);
SetTimer(hwnd,1,500,0);
for (l = 0; l < 1000000L; l++)
if (printf("%ld\n",l) < 0) break;
KillTimer(hwnd,1);
while( GetMessage( &msg, 0, 0, 0 ) != 0 )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return 0;
}
long far pascal WndProc(HWND hwnd, WORD msg, WORD wParam, LONG lParam)
{
short i;
short nCode;
switch( msg )
{
case WM_TIMER:
if (++ticks >= 36)
{
WinDosIO(WD_SELECT,handle[currentHandle],0);
currentHandle = ++currentHandle % 5;
ticks = 0;
}
break;
case WM_COMMAND:
nCode = HIWORD(lParam);
switch (nCode)
{
case WDN_LBUTTON:
for (i = 0; i < 5; i++)
{
if (wParam == i)
{
currentHandle = i;
WinDosIO(WD_SELECT,
handle[currentHandle],0);
ticks = 0;
break;
}
}
}
break;
case WM_SETFOCUS:
WinDosIO(WD_SETFOCUS,0,0);
break;
case WM_CLOSE:
WinDosIO(WD_DESTROY,hwnd,0);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,msg,wParam,lParam);
}
Coroutines
Event driven code is user driven, while structured code is driven by its own structure. A case can be made for both, in different circumstances. WinDosIO gives you the ability to program both ways, even in the same program. Picture a recursive routine that pops up a different child window at various levels of recursion. This is so different than the paradigm where a message is received, processed and control returned back to windows. It is also different than the structured paradigm, because how do we handle the window processing without reinventing the wheel, when Windows already does it so well.
The answer lies in coroutines. The recursive routine puts the window up and returns control to Windows with the ReturnToWindows function call. The child window is processed in its window procedure like normal. When the user clicks on the button which would cause the recursive routine to move up or down a level of recursion, code in the window procedure calls ReturnToApplication(short returnCode) and the ReturnToWindows call returns back to the recursive routine with the return code. Based on the return code, the recursive routine can decide what to do next.
Exampl3 demonstrates coroutines and recursion with a simple and useless application called "The Living Trie" . Click on a letter in the list box and add it to the word displayed. Click on up level and delete a letter. Notice that a dummy termIO window is created because ReturnToWindows and ReturnToApplication will not function unless there is at least one termIO window.
You can nest ReturnToWindows calls as deep as the stack allows. Each ReturnToApplication will pop back to the most recent ReturnToWindows. Calling ReturnToApplication, with no ReturnToWindows on the stack, will result in no error.
long far pascal WndProc(HWND hwnd, WORD msg, WORD wParam, LONG lParam)
{
WORD notify = HIWORD(lParam);
switch (msg) {
case WM_COMMAND:
switch (wParam)
{
case IDD_BUTTON:
ReturnToApplication(UP_LEVEL);
break;
case IDD_LISTBOX:
if (notify == LBN_DBLCLK)
{
word[level++] = 'A' +
SendMessage(hwndListBox,LB_GETCURSEL,0,0);
LivingTrie();
level--;
ReturnToApplication(SAME_LEVEL);
}
break;
default:
break;
} /* End switch wParam */
break;
case WM_SETFOCUS:
WinDosIO(WD_SETFOCUS,0,0);
break;
case WM_CLOSE:
WinDosIO(WD_DESTROY,hwnd,0);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
} /* End switch message */
return DefWindowProc(hwnd,msg,wParam,lParam);
}
Exampl4 and Exampl5
These programs demonstrate various text and cursor IO and positioning routines. Exampl4 uses WinDosIO.c, while exampl5 works from the WinMain level.
Exampl6
This example explores many of the functions which emulate the Borland Graphics Interface. It is compiled with WinDosIO.c. Example6 experiments with deferring painting while putting pixels. It also uses a GDI call after obtaining the termIO device context. Unfortunately, due to problems with different graphics cards, functions changing the palette had to be removed from this version.