前言:
90年代初,微软出了Quick系统对抗Borland Turbo系列,其中包括 QuickBasic, QuickPascal和Quick C。1991年,Quick C for Windows 1.0发布,后来它被Visual C++取代。我自己觉得微软成就在那个winstub.exe桩上,后来从xp开始挖掉那个桩。为了突破DOS内存限制,DOS4GW, pharlap都有办法,但只有winstub.exe架起了微软的WINDOWS平台,其它的都被淘汰了。现在CPU有内存管理单元,这些东西都不需要了。
步骤一:QCWIN (Quick C 1.0 for Windows) 安装
在WIN10上安装VMWARE虚拟机,在虚拟机中安装XP。虚拟机挂载共享文件夹,在WIN10上将5张盘的内容放在文件夹中,XP系统中打开共享文件夹,双击运行DISK1中的SETUP.EXE
安装程序会自动顺畅地安装好5张盘的内容。下图是安装后的文件分布情况。
步骤二:运行QCWIN (Quick C 1.0 for Windows)
界面看上去很简洁,在XP theme夹持下感观上很舒服。下面分步建立一个简单程序。
步骤三:写一个简单程序
先打开QUICK CASE,用它是做一个SDI单文档界面,也是程序的主界面。
3.1 输入标题
3.2 输入菜单项
建完 File - Exit 后,在它旁边用同样的方法建 Help - About。 在Quick Case 的Design菜单项下可以设置图标及样式。
3.3 回到QCWIN程序(最小化但不要关闭QuickCase),在TOOLS下选用Dialog Editor
3.4 从任务栏上恢复QuickCase窗口(最小化但不要关闭Dialog Editor),先保存文件,然后点击Build下的生成。如果保存文件名是Hello.WIN的话,则会生成Hello.C文件和Hello.H文件。
把它最小化到任务栏,同时恢复任务栏上的Dialog Editor,保存Dialog文件并设置Include头文件。
3.5 Open装入QuickCase生成的MAK文件,然后Build或Rebuild All生成EXE文件。
步骤四:回观并感受上面的代码
生成的文件
生成的代码是WIN16 API代码,与现在的WIN32 API代码基本上相同,但个别API函数没有,比如LoadImage,需要用其它函数组合实现。生成的代码基本没改,只是读取了桌面尺寸和默认生成的主窗体尺寸,然后根据尺寸参数将主窗体放在屏幕的正中间位置。代码从注册应用程序类、创建主窗口、显示主窗口、进入主消息队列,与现在的API编程是完全一样的,这套思路从1991年到现在30年间似乎没有什么改变。
/* QuickCase:W KNB Version 1.00 */
#include "HELLO.h"/************************************************************************/
/* */
/* Windows 3.0 Main Program Body */
/* */
/* The following routine is the Windows Main Program. The Main Program */
/* is executed when a program is selected from the Windows Control */
/* Panel or File Manager. The WinMain routine registers and creates */
/* the program's main window and initializes global objects. The */
/* WinMain routine also includes the applications message dispatch */
/* loop. Every window message destined for the main window or any */
/* subordinate windows is obtained, possibly translated, and */
/* dispatched to a window or dialog processing function. The dispatch */
/* loop is exited when a WM_QUIT message is obtained. Before exiting */
/* the WinMain routine should destroy any objects created and free */
/* memory and other resources. */
/* */
/************************************************************************/int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{/***********************************************************************//* HANDLE hInstance; handle for this instance *//* HANDLE hPrevInstance; handle for possible previous instances *//* LPSTR lpszCmdLine; long pointer to exec command line *//* int nCmdShow; Show code for main window display *//***********************************************************************/MSG msg; /* MSG structure to store your messages */int nRc; /* return value from Register Classes */HWND hDesk; //handle of DESKTOP as a root windowint WndMainX, WndMainY; //new centered main windowint nWndMainWidth, nWndMainHeight; //width and height of DESKTOPRECT rectWndDesk; //rectanglur structure of DESKTOPRECT rectWndMain; //rectanglur structure of main windowstrcpy(szAppName, "HELLO");hInst = hInstance;if(!hPrevInstance){/* register window classes if first instance of application */if ((nRc = nCwRegisterClasses()) == -1){/* registering one of the windows failed */LoadString(hInst, IDS_ERR_REGISTER_CLASS, szString, sizeof(szString));MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);return nRc;}}hDesk = GetDesktopWindow();
GetWindowRect(hDesk, &rectWndDesk);/* create application's Main window */hWndMain = CreateWindow(szAppName, /* Window class name */NULL, /* no title */WS_CAPTION | /* Title and Min/Max */WS_SYSMENU | /* Add system menu box */WS_MINIMIZEBOX | /* Add minimize box */WS_MAXIMIZEBOX | /* Add maximize box */WS_THICKFRAME | /* thick sizeable frame */WS_CLIPCHILDREN | /* don't draw in child windows areas */WS_OVERLAPPED,CW_USEDEFAULT, 0, /* Use default X, Y */CW_USEDEFAULT, 0, /* Use default X, Y */NULL, /* Parent window's handle */NULL, /* Default to Class Menu */hInst, /* Instance of window */NULL); /* Create struct for WM_CREATE */if(hWndMain == NULL){LoadString(hInst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);return IDS_ERR_CREATE_WINDOW;}GetWindowRect(hWndMain, &rectWndMain);WndMainX = (rectWndDesk.right - rectWndMain.right + rectWndMain.left)/2;
WndMainY = (rectWndDesk.bottom - rectWndMain.bottom + rectWndMain.top)/2;
nWndMainWidth = rectWndMain.right - rectWndMain.left;
nWndMainHeight = rectWndMain.bottom - rectWndMain.top;MoveWindow(hWndMain, WndMainX, WndMainY, nWndMainWidth, nWndMainHeight, FALSE);ShowWindow(hWndMain, SW_SHOWNORMAL); /* display main window */UpdateWindow(hWndMain);while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */{TranslateMessage(&msg);DispatchMessage(&msg);}/* Do clean up before exiting from the application */CwUnRegisterClasses();return msg.wParam;
} /* End of WinMain */
/************************************************************************/
/* */
/* Main Window Procedure */
/* */
/* This procedure provides service routines for the Windows events */
/* (messages) that Windows sends to the window, as well as the user */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding */
/* keyboard accelerators. */
/* */
/* The SWITCH statement shown below distributes the window messages to */
/* the respective message service routines, which are set apart by the */
/* CASE statements. The window procedures must provide an appropriate */
/* service routine for its end user initiated messages, as well as the */
/* general Windows messages (ie. WM_CLOSE message). If a message is */
/* sent to this procedure for which there is no programmed CASE clause */
/* (i.e., no service routine), the message is defaulted to the */
/* DefWindowProc function, where it is handled by Windows */
/* */
/************************************************************************/LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{HMENU hMenu=0; /* handle for the menu */HBITMAP hBitmap=0; /* handle for bitmaps */HDC hDC; /* handle for the display device */PAINTSTRUCT ps; /* holds PAINT information */int nRc=0; /* return code */switch (Message){case WM_COMMAND:/* The Windows messages for action bar and pulldown menu items *//* are processed here. */switch (wParam){case IDM_F_EXIT:/* Place User Code to respond to the *//* Menu Item Named "Exit" here. */break;case IDM_H_ABOUT:/* Place User Code to respond to the *//* Menu Item Named "About" here. */{FARPROC lpfnDIALOGSMsgProc;lpfnDIALOGSMsgProc = MakeProcInstance((FARPROC)DIALOGSMsgProc, hInst);nRc = DialogBox(hInst, (LPSTR)"AboutBox", hWnd, lpfnDIALOGSMsgProc);FreeProcInstance(lpfnDIALOGSMsgProc);}break;default:return DefWindowProc(hWnd, Message, wParam, lParam);}break; /* End of WM_COMMAND */case WM_CREATE:/* The WM_CREATE message is sent once to a window when the *//* window is created. The window procedure for the new window *//* receives this message after the window is created, but *//* before the window becomes visible. */break; /* End of WM_CREATE */case WM_MOVE: /* code for moving the window */break;case WM_SIZE: /* code for sizing client area */break; /* End of WM_SIZE */case WM_PAINT: /* code for the window's client area *//* Obtain a handle to the device context *//* BeginPaint will sends WM_ERASEBKGND if appropriate */memset(&ps, 0x00, sizeof(PAINTSTRUCT));hDC = BeginPaint(hWnd, &ps);/* Included in case the background is not a pure color */SetBkMode(hDC, TRANSPARENT);/* Inform Windows painting is complete */EndPaint(hWnd, &ps);break; /* End of WM_PAINT */case WM_CLOSE: /* close the window *//* Destroy child windows, modeless dialogs, then, this window */DestroyWindow(hWnd);if (hWnd == hWndMain)PostQuitMessage(0); /* Quit the application */break;default:/* For any message for which you don't specifically provide a *//* service routine, you should return the message to Windows *//* for default message processing. */return DefWindowProc(hWnd, Message, wParam, lParam);}return 0L;
} /* End of WndProc */
/************************************************************************/
/* */
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/* The SWITCH statement in the function distributes the dialog box */
/* messages to the respective service routines, which are set apart by */
/* the CASE clauses. Like any other Windows window, the Dialog Window */
/* procedures must provide an appropriate service routine for their end */
/* user initiated messages as well as for general messages (like the */
/* WM_CLOSE message). */
/* Dialog messages are processed internally by windows and passed to the*/
/* Dialog Message Procedure. IF processing is done for a Message the */
/* Message procedure returns a TRUE, else , for messages not explicitly */
/* processed, it returns a FALSE */
/* */
/************************************************************************/BOOL FAR PASCAL DIALOGSMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG lParam)
{switch(Message){case WM_INITDIALOG:cwCenter(hWndDlg, 0);/* initialize working variables */break; /* End of WM_INITDIALOG */case WM_CLOSE:/* Closing the Dialog behaves the same as Cancel */PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);break; /* End of WM_CLOSE */case WM_COMMAND:switch(wParam){case Edit1: /* Edit Control */break;case IDS_ERR_REGISTER_CLASS: /* Button text: "Push" */break;case IDCANCEL:/* Ignore data values entered into the controls *//* and dismiss the dialog window returning FALSE */EndDialog(hWndDlg, FALSE);break;}break; /* End of WM_COMMAND */default:return FALSE;}return TRUE;
} /* End of DIALOGSMsgProc *//************************************************************************/
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************/int nCwRegisterClasses(void)
{WNDCLASS wndclass; /* struct to define a window class */memset(&wndclass, 0x00, sizeof(WNDCLASS));/* load WNDCLASS with window's characteristics */wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW;wndclass.lpfnWndProc = WndProc;/* Extra storage for Class and Window objects */wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInst;wndclass.hIcon = LoadIcon(hInst, "HELLO");wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);/* Create brush for erasing background */wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wndclass.lpszMenuName = szAppName; /* Menu Name is App Name */wndclass.lpszClassName = szAppName; /* Class Name is App Name */if(!RegisterClass(&wndclass))return -1;return(0);
} /* End of nCwRegisterClasses *//************************************************************************/
/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/* */
/************************************************************************/void cwCenter(hWnd, top)
HWND hWnd;
int top;
{POINT pt;RECT swp;RECT rParent;int iwidth;int iheight;/* get the rectangles for the parent and the child */GetWindowRect(hWnd, &swp);GetClientRect(hWndMain, &rParent);/* calculate the height and width for MoveWindow */iwidth = swp.right - swp.left;iheight = swp.bottom - swp.top;/* find the center point and convert to screen coordinates */pt.x = (rParent.right - rParent.left) / 2;pt.y = (rParent.bottom - rParent.top) / 2;ClientToScreen(hWndMain, &pt);/* calculate the new x, y starting point */pt.x = pt.x - (iwidth / 2);pt.y = pt.y - (iheight / 2);/* top will adjust the window position, up or down */if(top)pt.y = pt.y + top;/* move the window */MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any refrences to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/void CwUnRegisterClasses(void)
{WNDCLASS wndclass; /* struct to define a window class */memset(&wndclass, 0x00, sizeof(WNDCLASS));UnregisterClass(szAppName, hInst);
} /* End of CwUnRegisterClasses */
编译生成的EXE尺寸14KB,在WINXP平台上运行顺畅,再做点儿工作就可以放在WIN10或WIN11平台上独立运行了。
在WIN10平台上建立16位程序运行环境
到Github上下载otvdm并解压到C盘,安装即是简单地运行一个inf文件,原理是把16位Windows的一些东西改动到Win10上,当16位运行在Win10上运行出错时截取下来,再用16位这些东西运行试一下,再出错再提示给用户错误信息,类似16位程序的虚拟机。
在WIN10平台上运行的效果如下图
关于优化xp平台的几点说明
XP平台比较老了,在虚拟中运行的话可以优化。
1. 现在机器的分辨率都比较高,要在设置中将分辨率设在120DPI到150DPI,同时选大字体、大图标,达到比较好的外观效果。
2. 现在的3键鼠标必须在XP上安装微软4,12版的鼠标驱动程序,其它版本的不管用。用意主要是激活滚轮。
3. 在控制面板中去掉Internet explorer的对勾,安装MyPal浏览器,它能正常访问现在的互联网。
4. 可以安装外挂万能无笔输入法和其它自己喜欢的输入法。与母机共享文件可以在虚拟机上外挂母机的共享文件夹,也可以通过网络邻居方式使用母机上的WebDAV空间。如何在IIS上配置WebDAV服务,在我的笔记《IIS WebDAV配置,https绑定及asp设置》中有详细记录。IIS WebDAV配置,https绑定及asp设置_Mongnewer的博客-CSDN博客
感觉IT的黄金时期是DOS到Windows的变革阶段,似乎那时全世界都在拥抱IT,甚至写个DBASE程序都能让人羡慕,那时有知识的人很受社会尊重,这与现在普遍叫喊的“内卷”似乎是完全不一样的状况。时代久远,翻遍互联网可能也找不到一篇如何用Quick C for windows的文章可参考了,我体验了就放记到CSDN笔记上吧。