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有内存管理单元,这些东西都不需要了。
在WIN10上安装VMWARE虚拟机,在虚拟机中安装XP。虚拟机挂载共享文件夹,在WIN10上将5张盘的内容放在文件夹中,XP系统中打开共享文件夹,双击运行DISK1中的SETUP.EXE
安装程序会自动顺畅地安装好5张盘的内容。下图是安装后的文件分布情况。
界面看上去很简洁,在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 window
int WndMainX, WndMainY; //new centered main window
int nWndMainWidth, nWndMainHeight; //width and height of DESKTOP
RECT rectWndDesk; //rectanglur structure of DESKTOP
RECT rectWndMain; //rectanglur structure of main window
strcpy(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平台上独立运行了。
到Github上下载otvdm并解压到C盘,安装即是简单地运行一个inf文件,原理是把16位Windows的一些东西改动到Win10上,当16位运行在Win10上运行出错时截取下来,再用16位这些东西运行试一下,再出错再提示给用户错误信息,类似16位程序的虚拟机。
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笔记上吧。