[学习记录用][Win32 API]AutoRun免疫工具编写

       经历高考,终于是迈入大学校门,虽然不是一流大学,但至少获得了一个宽松的学习环境,不会再像之前一样处处时时被人紧逼。9月开始,随着大学生活开始,我也正式开始编程方面的学习。这其中,基于种种原因,我首先计划掌握的是关于Windows开发的方方面面。所以,一方面为了记录学习进度和相关资料,以免重复学习和减少时间成本,另一方面,也为了监督自己推进学习进度;我决定开始在此撰写相关的学习博文。


       2014年,我想使用在众多AV(AntiVirus)都支持禁止autorun.inf运行和各种U盘病毒专杀的年代,使用autorun.inf让自身运行的autorun病毒大概已经成为一种可贵的历史了。

       所以这篇博文并非开发记录。因为做出来的东西并没有多大的实际价值,仅在于对于win32API开发的一种练习。程序包含窗体的所有功能和组件皆由Windows API直接实现(注1)。

       免疫工具的实现原理很简单,无非就是在将免疫的盘下直接创建一个无法删除的名为autorun.inf的文件夹,使得同名的autorun.inf文件无法建立。建立同名文件很简单,关键是“无法删除”这一特性;其实实现起来也很简单,要利用windows文件系统中给文件夹命名的一个漏洞(注2),就是创建一个以“.”结尾的文件夹,这在资源管理器是没法办到的(注3)。但在命令提示符下用md命令创建诸如"Directory..\"以"."结尾的文件夹就不能在资源管理器下正常的删除,也不能用API正常的获取其名称(也就是路径)。当然结尾需要两个以上的点,一个点会被忽略,而且结尾的"\"是不能省略的,不然多少个"."都会像在资源管理器下一样被忽视。这个漏洞的原理我至今不甚明了,我知道我可以使用它实现我程序的功能(注4)


       那么废话讲完,开始我的练习过程:

       OS:Microsoft Windows 6.1.7601

       IDE:Dev-C++ 5.7.1 with TDM-GCC 4.8.1(注5)


       首先是最原始的程序代码,实现了一个窗口,控件为一个组合框(显示可选盘符)和两个按钮(分别为设置免疫和取消免疫):

头文件及主文件:

AutoRunImmunity.h

#include <windows.h>
#include <tchar.h>

/*全局变量声明*/
HINSTANCE g_hInstance;
BOOL g_bIfStart;

/*函数声明*/
VOID CreateAllControls(HWND);
VOID InitComboBox(HWND);
BOOL SetImmunity(HWND);
BOOL CnclImmunity(HWND); 

/*各式控件ID*/
#define IDC_COMBOBOX_MAIN 01000
#define IDC_BUTTON_IM 01001
#define IDC_BUTTON_CA 01002

AutoRunImmunity.c

#include "main.h"

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
	switch (Message){
		case WM_CREATE :
			CreateAllControls(hwnd);
		break;
		
		case WM_DESTROY :
			PostQuitMessage(0);
		break;	
		
		case WM_COMMAND :
			switch (LOWORD(wParam)){
				case IDC_BUTTON_IM :
					SetImmunity(hwnd);
				break;
				
				case IDC_BUTTON_CA:
					CnclImmunity(hwnd);
				break;
//				
//				case IDC_COMBOBOX_MAIN:
//					
//				break;
			}
		break;
		
		default:
			return DefWindowProc(hwnd, Message, wParam, lParam);
	}
	return 0;
}

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	HWND hwnd;
	MSG Msg;
	WNDCLASS wc;
	
	wc.style		 = 0;
	wc.cbWndExtra	 = 0;
	wc.cbClsExtra	 = 0; 
	wc.lpfnWndProc	 = WndProc;
	wc.hIcon		 = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor		 = LoadCursor(NULL, IDC_ARROW);
	wc.hInstance	 = hInstance;
	wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = TEXT("AutoRunImApp");
	g_hInstance		 = hInstance;
	
	if (!RegisterClass(&wc)){
		MessageBox(NULL, TEXT("注册类失败!"), TEXT("错误"), MB_OK | MB_APPLMODAL | MB_ICONERROR);
		return 1; 
	}
	
	hwnd = CreateWindow(TEXT("AutoRunImApp"),
						TEXT("AutoRun免疫"),
						WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						225,
						90,
						NULL,
						NULL,
						hInstance,
						NULL);
	if(!hwnd){
		MessageBox(NULL, TEXT("窗口创建失败!"), TEXT("错误"), MB_OK | MB_APPLMODAL | MB_ICONERROR);
		return 1; 
	}
	
	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	/*消息获取和分发*/
	while (GetMessage(&Msg, NULL, 0, 0) > 0){
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}
	
	return Msg.wParam;
}

/*建立所有控件*/
VOID CreateAllControls(HWND hwnd)
{
	HWND hwndCmbBx;
	HWND hwndBtnIm;
	HWND hwndBtnCa;
	
	/*建立组合框*/
	hwndCmbBx = CreateWindow(TEXT("ComboBox"), NULL,
							 WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWN | CBS_NOINTEGRALHEIGHT,		/*使用CBS_NOINTEGRALHEIGHT保证项目很多时仍是可视的*/
							 0, 0, 220, 100,
							 hwnd, (HMENU)IDC_COMBOBOX_MAIN, g_hInstance, NULL);
	InitComboBox(hwnd);
	
	/*建立设置免疫按钮*/						 
	hwndBtnIm = CreateWindow(TEXT("Button"), TEXT("设置免疫"),
							 WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
							 0, 27, 110, 30,
							 hwnd, (HMENU)IDC_BUTTON_IM, g_hInstance, NULL);
	
	/*建立取消免疫按钮*/						 
	hwndBtnIm = CreateWindow(TEXT("Button"), TEXT("取消免疫"),
							 WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
							 110, 27, 110, 30,
							 hwnd, (HMENU)IDC_BUTTON_CA, g_hInstance, NULL);
	
	
	
	return;	
}

/*初始化组合框,添加选择项*/
VOID InitComboBox(HWND hwnd)
{
	TCHAR szDrive[ MAXBYTE ] = { 0 };
	DWORD dwDrive = 0L;
	HWND hwndCB = GetDlgItem(hwnd, IDC_COMBOBOX_MAIN); 
	
	/*获取盘符并保存*/
	dwDrive = GetLogicalDriveStrings(MAXBYTE*sizeof(TCHAR), szDrive);
	
	/*设置初始项*/
	SetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, TEXT("请选择欲免疫的盘符..."));
	/*循环向组合框内添加盘符选项*/
	for (DWORD i = 0L; i < dwDrive/sizeof(TCHAR); i+=(lstrlen(szDrive+i)+1)){
		SendMessage(GetDlgItem(hwnd, IDC_COMBOBOX_MAIN), CB_ADDSTRING, (WPARAM)0, (LPARAM)(szDrive+i));
	}
	
	return ; 
}

/*处理设置免疫事件*/
BOOL SetImmunity(HWND hwnd)
{
	LPCTSTR szAutoRun = TEXT("autorun.inf");
	LPCTSTR szImmunity = TEXT("\\Immunity...\\");
	TCHAR szImPath[ MAX_PATH ] = { 0 };
	
	/*获取选择的盘符*/
	GetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, szImPath, MAX_PATH);
	
	/*创建免疫目录*/
	lstrcat(szImPath, szAutoRun);
	if (!CreateDirectory(szImPath, NULL)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者已经免疫!"), TEXT("设置免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;	
	}
	
	lstrcat(szImPath, szImmunity);
	if (!CreateDirectory(szImPath, NULL)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者已经免疫!"), TEXT("设置免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;	
	}
	
	MessageBox(hwnd, TEXT("成功设置免疫!"), TEXT("信息"), MB_OK | MB_ICONINFORMATION);
	return TRUE;
}

/*处理取消免疫事件*/
BOOL CnclImmunity(HWND hwnd)
{
	LPCTSTR szAutoRun = TEXT("autorun.inf");
	LPCTSTR szImmunity = TEXT("\\Immunity...\\");
	TCHAR szDrive[ MAX_PATH ] = { 0 };
	TCHAR szImPath[ MAX_PATH ] = { 0 };
	
	/*获取选择的盘符*/
	GetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, szDrive, MAX_PATH);
	
	/*移除免疫目录*/
	lstrcpy(szImPath, szDrive);
	lstrcat(szImPath, szAutoRun);
	lstrcat(szImPath, szImmunity);
	if(!RemoveDirectory(szImPath)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者尚未免疫!"), TEXT("取消免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;	
	}
	
	lstrcpy(szImPath, szDrive);
	lstrcat(szImPath, szAutoRun);
	if(!RemoveDirectory(szImPath)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者尚未免疫!"), TEXT("取消免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;
	}
	
	MessageBox(hwnd, TEXT("成功取消免疫!"), TEXT("信息"), MB_OK | MB_ICONINFORMATION);
	
	return TRUE;
}

       这样这个程序已经基本实现了我们所要求的免疫功能,但是实际操作发现还是有着不小的问题。那就是由于组合框是含有一个可编辑内容的编辑框的,而用于获取所选择的盘符的GetDlgItemText函数实质就是获取的编辑框内的字符串,但是由于其内容是可以编辑的,就导致了实际使用的时候由于用户的编辑,程序不一定能获取正确的盘符。于是程序就很可能在所在目录下创立一个无法删除的文件夹而且不能正常免疫。还有就是在未选择盘符时,用户就直接点击了 设置免疫 按钮也是个灾难,程序直接就建立了一个无法删除的"请选择欲免疫的盘符..."文件夹,也是无奈。

      于是为了解决上述问题,我们首先应该将组合框的风格设置为CBS_DROPDOWNLIST,然后将InitComboBox改为InitAllControls,在其中同时完成对按钮的初始化,也就是在程序开始时禁止两个按钮,在窗口收到来自组合框的CBN_SELENDOK消息时再将按钮激活。

修改的InitAllControls函数内容如下,就是添加了两个EnableWindow函数:

/*初始化所有控件*/ 
VOID InitAllControls(HWND hwnd)
{
	TCHAR szDrive[ MAXBYTE ] = { 0 };
	DWORD dwDrive = 0L;
	HWND hwndCB = GetDlgItem(hwnd, IDC_COMBOBOX_MAIN); 
	
	/*程序开始时禁止按钮*/	
	EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_IM), FALSE);
	EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_CA), FALSE);
	
	/*获取盘符并保存*/
	dwDrive = GetLogicalDriveStrings(MAXBYTE*sizeof(TCHAR), szDrive);
	
	/*设置初始项*/
	SetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, TEXT("请选择欲免疫的盘符..."));
	/*循环向组合框内添加盘符选项*/
	for (DWORD i = 0L; i < dwDrive/sizeof(TCHAR); i+=(lstrlen(szDrive+i)+1)){
		SendMessage(GetDlgItem(hwnd, IDC_COMBOBOX_MAIN), CB_ADDSTRING, (WPARAM)0, (LPARAM)(szDrive+i));
	}
	
	return ; 
}

       当然这样看起来程序已经没什么问题了,但是我们又迎来一个新问题,那就是不能在组合框内设置初始提示用的内容了!也就是说这条初始化语句失去了它本身的作用:

	/*设置初始项*/
	SetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, TEXT("请选择欲免疫的盘符..."));

       或许这本身不影响程序本身的功能,但是对于我这种强迫症是没法忍的。于是我将CBS_DROPDOWNLIST改回了之前的CBS_DROPDOWN,然后在消息处理函数里拦截了CBN_EDITCHANGE消息和CBN_SELENDOK消息然后进行相应处理。定义一个全局的字符串用于储存修改前组合框内的内容,一旦有SELENDOK消息就储存组合框内容,只要用户一修改就立即修改回之前的字符串,由于控件刷新速度极快(在当前大部分电脑上,什么内存250MB,CPU 0.8GHz就不要来扯了),于是便造成一种不可修改的错觉。

       于是这便是这便是最后成型的代码:

#include 
    
    
     
     
#include 
     
     
      
      

/*全局变量声明*/
HINSTANCE g_hInstance;
TCHAR g_szCBContent[ MAX_PATH ];

/*函数声明*/
BOOL CreateAllControls(HWND);
VOID InitAllControls(HWND);
BOOL SetImmunity(HWND);
BOOL CnclImmunity(HWND); 
BOOL SetCBRestoreText(HWND);

/*各式控件ID*/
#define IDC_COMBOBOX_MAIN 01000
#define IDC_BUTTON_IM 01001
#define IDC_BUTTON_CA 01002
#include "AutoRunImmunity.h"

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
	switch (Message){
		case WM_CREATE :
			CreateAllControls(hwnd);
		break;
		
		case WM_DESTROY :
			PostQuitMessage(0);
		break;	
		
		case WM_COMMAND :
			switch (LOWORD(wParam)){	
				case IDC_BUTTON_IM :
					SetImmunity(hwnd);
				break;
				
				case IDC_BUTTON_CA:
					CnclImmunity(hwnd);
				break;
				
				case IDC_COMBOBOX_MAIN:			
					switch (HIWORD(wParam)){
						case CBN_SELENDOK :
							SetCBRestoreText(hwnd);
						break;	
						
						case CBN_EDITCHANGE :
							SetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, g_szCBContent);
						break;
					}	
				break;
			}
		break;
		
		default:
			return DefWindowProc(hwnd, Message, wParam, lParam);
	}
	return 0;
}

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	HWND hwnd;
	MSG Msg;
	WNDCLASS wc;
	
	wc.style		 = 0;
	wc.cbWndExtra	 = 0;
	wc.cbClsExtra	 = 0; 
	wc.lpfnWndProc	 = WndProc;
	wc.hIcon		 = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor		 = LoadCursor(NULL, IDC_ARROW);
	wc.hInstance	 = hInstance;
	wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = TEXT("AutoRunImApp");
	g_hInstance		 = hInstance;
	
	if (!RegisterClass(&wc)){
		MessageBox(NULL, TEXT("注册类失败!"), TEXT("错误"), MB_OK | MB_APPLMODAL | MB_ICONERROR);
		return 1; 
	}
	
	hwnd = CreateWindow(TEXT("AutoRunImApp"),
						TEXT("AutoRun免疫"),
						WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						225,
						90,
						NULL,
						NULL,
						hInstance,
						NULL);
	if (!hwnd){
		MessageBox(NULL, TEXT("窗口创建失败!"), TEXT("错误"), MB_OK | MB_APPLMODAL | MB_ICONERROR);
		return 1; 
	}
	
	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	/*消息获取和分发*/
	while (GetMessage(&Msg, NULL, 0, 0) > 0){
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}
	
	return Msg.wParam;
}

/*建立所有控件*/
BOOL CreateAllControls(HWND hwnd)
{
	HWND hwndCmbBx;
	HWND hwndBtnIm;
	HWND hwndBtnCa;
	
	/*建立组合框*/
	hwndCmbBx = CreateWindow(TEXT("ComboBox"), NULL,
							 WS_CHILD | WS_VISIBLE | WS_TABSTOP | \
							 CBS_DROPDOWN | CBS_NOINTEGRALHEIGHT | CBS_HASSTRINGS,		
							 /*使用CBS_NOINTEGRALHEIGHT保证项目很多时仍是可视的*/
							 0, 0, 220, 100,
							 hwnd, (HMENU)IDC_COMBOBOX_MAIN, g_hInstance, NULL);
	
	/*建立设置免疫按钮*/						 
	hwndBtnIm = CreateWindow(TEXT("Button"), TEXT("设置免疫"),
							 WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
							 0, 27, 110, 30,
							 hwnd, (HMENU)IDC_BUTTON_IM, g_hInstance, NULL);
	
	/*建立取消免疫按钮*/						 
	hwndBtnCa = CreateWindow(TEXT("Button"), TEXT("取消免疫"),
							 WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
					    	 110, 27, 110, 30,
							 hwnd, (HMENU)IDC_BUTTON_CA, g_hInstance, NULL);
							 
	/*检查空句柄问题*/						 
	if (!(hwndCmbBx && hwndBtnIm && hwndBtnCa)){
		MessageBox(hwnd, TEXT("控件创建失败"), TEXT("错误"), MB_OK | MB_ICONERROR);
		return FALSE;
	}
	
	/*初始化所有控件*/ 
	InitAllControls(hwnd);
	
	return TRUE;	
}

/*初始化所有控件*/ 
VOID InitAllControls(HWND hwnd)
{
	TCHAR szDrive[ MAXBYTE ] = { 0 };
	DWORD dwDrive = 0L;
	HWND hwndCB = GetDlgItem(hwnd, IDC_COMBOBOX_MAIN);
	LPCTSTR lpszCBInit = TEXT("选择欲免疫的盘符...");
	
	/*程序开始时禁止按钮*/	
	EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_IM), FALSE);
	EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_CA), FALSE);
	
	/*获取盘符并保存*/
	dwDrive = GetLogicalDriveStrings(MAXBYTE*sizeof(TCHAR), szDrive);
	
	/*设置初始项*/
	SetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, lpszCBInit);
	lstrcpy(g_szCBContent, lpszCBInit);
	
	/*循环向组合框内添加盘符选项*/
	for (DWORD i = 0L; i < dwDrive/sizeof(TCHAR); i+=(lstrlen(szDrive+i)+1)){
		SendMessage(GetDlgItem(hwnd, IDC_COMBOBOX_MAIN), CB_ADDSTRING, (WPARAM)0, (LPARAM)(szDrive+i));
	}
	
	return ; 
}

/*处理设置免疫事件*/
BOOL SetImmunity(HWND hwnd)
{
	LPCTSTR szAutoRun = TEXT("autorun.inf");
	LPCTSTR szImmunity = TEXT("\\Immunity...\\");
	TCHAR szImPath[ MAX_PATH ] = { 0 };
	
	/*获取选择的盘符*/
	GetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, szImPath, MAX_PATH);
	
	/*创建免疫目录*/
	lstrcat(szImPath, szAutoRun);
	if (!CreateDirectory(szImPath, NULL)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者已经免疫!"), TEXT("设置免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;	
	}
	
	lstrcat(szImPath, szImmunity);
	if (!CreateDirectory(szImPath, NULL)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者已经免疫!"), TEXT("设置免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;	
	}
	
	MessageBox(hwnd, TEXT("成功设置免疫!"), TEXT("信息"), MB_OK | MB_ICONINFORMATION);
	return TRUE;
}

/*处理取消免疫事件*/
BOOL CnclImmunity(HWND hwnd)
{
	LPCTSTR szAutoRun = TEXT("autorun.inf");
	LPCTSTR szImmunity = TEXT("\\Immunity...\\");
	TCHAR szDrive[ MAX_PATH ] = { 0 };
	TCHAR szImPath[ MAX_PATH ] = { 0 };
	
	/*获取选择的盘符*/
	GetDlgItemText(hwnd, IDC_COMBOBOX_MAIN, szDrive, MAX_PATH);
	
	/*移除免疫目录*/
	lstrcpy(szImPath, szDrive);
	lstrcat(szImPath, szAutoRun);
	lstrcat(szImPath, szImmunity);
	if (!RemoveDirectory(szImPath)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者尚未免疫!"), TEXT("取消免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;	
	}
	
	lstrcpy(szImPath, szDrive);
	lstrcat(szImPath, szAutoRun);
	if (!RemoveDirectory(szImPath)){
		MessageBox(hwnd, TEXT("磁盘处于不可写状态或者尚未免疫!"), TEXT("取消免疫失败"), MB_OK | MB_ICONERROR);
		return FALSE;
	}
	
	MessageBox(hwnd, TEXT("成功取消免疫!"), TEXT("信息"), MB_OK | MB_ICONINFORMATION);
	
	return TRUE;
}

/*设置组合框恢复字符串*/ 
BOOL SetCBRestoreText(HWND hwnd)
{
	static BOOL bIfStart = FALSE;
	UINT nIndex = 0; 
	HWND hwndCB = GetDlgItem(hwnd, IDC_COMBOBOX_MAIN);
	
	/*第一次运行则激活按钮*/
	if (!bIfStart){
		EnableWindow(GetDlgItem(hwnd ,IDC_BUTTON_IM), TRUE);
		EnableWindow(GetDlgItem(hwnd ,IDC_BUTTON_CA), TRUE);
		bIfStart = TRUE;
	}
	
	/*获取被选项字符串并设置恢复字符串*/
	nIndex = (UINT)SendMessage(hwndCB, CB_GETCURSEL, (WPARAM)0, (LPARAM)0);
	SendMessage(hwndCB, CB_GETLBTEXT, (WPARAM)nIndex, (LPARAM)g_szCBContent);
	
	return TRUE;
}

     
     
    
    





注1:为什么不用MFC?1、我对C比较熟,C++尚在学习中;2、都说了是Win32 API练习了,为什么要借用封装了API的MFC?
注2:当然我也不知道算不算的上漏洞,因为这完全是因为用户,也就是我们创建了非法路径。
3:创建新文件夹结尾的"."会被自动清理,重命名文件夹加上"."会使文件夹暂时消失,刷新后出现。
注4:当然我猜测这跟DOS下".."是代表上级路径,"."代表当前路径有关,因为Windows就是基于DOS开发的,要知道Win95也就是个有GUI的DOS程序。
注5:刻意使用非VS环境,不使用程序生成的框架和自动补全功能,因为我是在学习,不是开发,多敲敲代码没坏处,只要时间成本不太高。








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值