C++制作安装包【1】—— 控制台实现

在这里插入图片描述

之前用NSIS的时候发现不方便自制UI,找了其他的安装包开发框架,开发手感还是不好,功能残缺不一。我就想 如何使用纯代码的方式制作安装包 呢?经历了众多艰难才终于摸出方法。网上现有的文章都是用NSIS,Windows Installer等等现成框架制作安装包的。所以我另开一个专栏,直接讲如何手撸安装包!

1.新建项目,包含所需的头文件

推荐开发环境:VS2022

#include<Windows.h>								//大Win头
#include<ShlObj.h>								//COM接口头文件
#include<wrl.h>									//用于COM接口,方便我们调用
using namespace Microsoft::WRL;

#include<iostream>								//C++标准输入输出
#include<fstream>								//C++标准文件
#include<string>								//C++标准字符串,另外也用于宽窄字符转换
using namespace std;

#include "zlib/zlib.h"							//源于Zlib,是Zlib的源头
#include "zlib/unzip.h"							//Zlib第三方库minizip的一个头文件,用于解压zip文件

#pragma comment(lib,"zlib/zlibstat")			//Zlib链接库

#include "resource.h"							//向项目添加资源时自动生成的资源头文件

Zlib地址:https://wwek.lanzoub.com/irjyk0i1gyhc

这里我们引用了一个第三方库 Zlib,上边已经有我编译好的Zlib库。各位读者直接去链接下载解包,将文件夹直接放进项目文件夹就行了。(这里的Installer是我的项目文件夹)

在这里插入图片描述

为什么我们要引用 Zlib 库?一般而言,安装包其实是一个本身包含压缩文件的可执行程序 ,运行时就会将文件解压到安装目录,并通过修改注册表的方式将软件录入到系统里,从而实现程序的安装与卸载。所以我们使用 Zlib 库,实现程序自解压文件的功能 (当然你可以自己造轮子实现)

在这里插入图片描述

2.将压缩文件添加到项目

那么我们如何将压缩文件“嵌入”到程序中呢?
我们可以利用 Windows资源文件(.rc文件) 载入

1.将压缩文件放入项目中

在这里插入图片描述

2.右键项目->添加->新建项,资源->选择 资源文件(.rc)->添加

在这里插入图片描述
在这里插入图片描述

3.在资源视图里右键点击RC文件->添加资源->自定义->类型名称填入ZIP (可以自定义,只是个字符串资源),确定后就会发现我们已经将"ZIP"这个字符串加入资源文件里了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.按第3步的方法再次添加资源,选择导入->右下角选择 所有文件 ->点击压缩文件->打开,选择你刚刚创建的资源类型 (弹出的列表会有),确定后回到“解决方案资源管理器”即可。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,新生成了四个文件项:

resource.h:这个头文件是VS自动生成的,定义资源ID这些琐事由VS帮我们做
Resource.rc:定义资源的引用列表
test.zip:使用的压缩文件,本质上还是对该文件的引用。所以在编译前我们是可以随时修改这个压缩文件的
zip1.bin:将链接资源的二进制文件,编译器通过这个实现文件的“嵌入”

当然,你嫌VS帮你做,结果导致报错改来改去很麻烦,可以手动重写 Resource.rc 和 resource.h 文件,不要忘记将Zip导入项目中就行了,这里不在一一赘述。

3.将资源从内存写入硬盘中

我们光有zip资源是不行的,我们要想一个方法将资源从程序中“取出来”,过程如下:

  1. FindResource() 通过资源描述符寻找zip资源
  2. LoadResource() 加载资源到内存
  3. LockResource() 锁定资源并得到资源在内存的指针

有了这三步,就能取zip文件到硬盘了。


	HINSTANCE hins = GetModuleHandle(0);    // 获取当前程序实例
	HRSRC my_zip = nullptr;					// 资源标识句柄
	DWORD zip_size = 0;						// 资源大小
	HGLOBAL zip_data = nullptr;				// 资源数据
		
	my_zip = ::FindResource(hins, MAKEINTRESOURCE(IDR_ZIP2), L"ZIP");  //通过资源标识符寻找资源
	zip_size = SizeofResource(nullptr, my_zip);						   //获取资源大小
	zip_data = LoadResource(nullptr, my_zip);						   //加载资源

	WCHAR SetupPath[MAX_PATH] = L"D:\\desktop\\myInstaller";		   //安装目录
	CreateDirectory(SetupPath, nullptr);							   //创建目录

	wstring AddStrEngine(SetupPath);
	AddStrEngine = AddStrEngine + wstring(L"\\m_zip.zip");

	// 先创建文件
	HANDLE zipfile = CreateFile2(AddStrEngine.c_str(), GENERIC_WRITE, 0, CREATE_ALWAYS, nullptr);
	// 将数据写入文件
	WriteFile(zipfile, (LPCVOID)LockResource(zip_data), zip_size, nullptr, nullptr);
	// 关闭文件
	CloseHandle(zipfile);

4.解压文件

我们将zip文件取出来,接下来就可以解压了。
解压要连续经历7步:

  1. unzOpen64()————————————————打开zip
  2. unzGetGlobalInfo64()———————————— 读取zip全局数据
  3. unzGetCurrentFileInfo64()—————————— 获取当前解压文件信息
  4. unzOpenCurrentFile()———————————— 打开文件
  5. unzReadCurrentFile()———————————— 解压当前文件到内存
  6. unzCloseCurrentFile()————————————关闭当前文件
  7. unzClose()—————————————————关闭zip文件

在这里插入图片描述

本质上还是依靠循环来读取并解压的。


	char zipLocation[MAX_PATH];				//zip位置
	unzFile compressZip = nullptr;			//zip句柄
	unz_global_info64 globalinfo = {};		//zip全局信息
	size_t zip_info_filename_length = 0;	//当前解压的文件名长度
	size_t zip_info_file_size = 0;			//当前解压的文件大小
	char* zip_info_FileName = nullptr;		//当前解压的文件名
	char* zip_info_FileData = nullptr;		//当前解压的文件数据
	unz_file_info64 fileinfo = {};			//解压文件的信息
	ofstream unzip_fstream;					//解压文件流
	string AStrEngine;						//用于拼接字符串

	sprintf_s(zipLocation, MAX_PATH, "%ws", AddStrEngine.c_str());
	compressZip = unzOpen64(zipLocation);		//打开zip

	unzGetGlobalInfo64(compressZip, &globalinfo);	//读取zip全局数据


	for (size_t i = 0; i < globalinfo.number_entry; ++i)	//逐个解压
	{
		AStrEngine.clear();
		AStrEngine = "D:\\desktop\\myInstaller\\";
		fileinfo = {};
		zip_info_FileName = new char[MAX_PATH];
		unzGetCurrentFileInfo64(compressZip, &fileinfo, zip_info_FileName, MAX_PATH,
			nullptr, 0, nullptr, 0);
		//获取当前解压文件信息

		zip_info_file_size = fileinfo.uncompressed_size;
		zip_info_FileData = new char[zip_info_file_size];
		zip_info_filename_length = strlen(zip_info_FileName);


		//如果是文件夹
		if (zip_info_FileName[zip_info_filename_length - 1] == '/')
		{
			AStrEngine += zip_info_FileName;
			CreateDirectoryA(AStrEngine.c_str(), nullptr);	
			//创建文件夹,解压文件时会解压到相应目录
		}
		else    //如果是文件就解压
		{
			unzOpenCurrentFile(compressZip);	//打开文件

			AStrEngine += zip_info_FileName;
			unzip_fstream.open(AStrEngine.c_str(), ios::out | ios::binary);
			unzReadCurrentFile(compressZip, reinterpret_cast<void*>(zip_info_FileData), zip_info_file_size);
			//读取当前文件到内存

			unzip_fstream.write(zip_info_FileData, zip_info_file_size);		//将文件从内存写入到硬盘里
			unzip_fstream.close();											//关闭文件
		}
		unzCloseCurrentFile(compressZip);	//关闭当前文件

		delete[] zip_info_FileName;
		delete[] zip_info_FileData;

		if (i + 1 < globalinfo.number_entry)
		{
			unzGoToNextFile(compressZip);	//解压下一个文件,指针偏移
		}
	}

	unzClose(compressZip);	//关闭zip文件

看到代码这么多,估计你此时和我都是一个表情 (😅),其实这部分代码很适合封装成函数,因为其功能对于做一个普通的安装包来说已经够完备了!你只要理解上述7步就可以立刻上手Zlib解压,只是麻烦在内存读取罢了。

5.修改注册表,将程序录入到系统中

在这里插入图片描述
读者是不是经常看到这个“烦人”的UAC (用户账户控制) 窗口?然而,这个窗口可以提供基本的保护,起码不受简单恶意程序对系统的篡改 (绕过UAC的除外) 。安装或卸载程序时如果有设置UAC,这个窗口就会弹出,如果安装程序没有获得管理员的许可,获得相应的权限,是不能读取与修改注册表的,即安装失败。 这个时候,我们就需要申请权限。
在这里插入图片描述
如何像一般安装包一样,先申请管理员权限再安装?

右键项目->属性->链接器->清单文件->UAC执行级别->将级别设为 RequireAdministrator (管理员)

在这里插入图片描述
在这里插入图片描述
有了管理员权限,仅差一步就大功告成了:


	HKEY hkey = nullptr;	//根键句柄

	//创建一个叫 MyInstaller 的子键,这个是安装与卸载程序的地方
	RegCreateKeyEx(HKEY_LOCAL_MACHINE,
		L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MyInstaller",
		0, (WCHAR*)L"", 0, KEY_READ | KEY_WRITE,
		nullptr, &hkey, nullptr);

	//打开子键
	RegOpenKey(HKEY_LOCAL_MACHINE,
		L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MyInstaller", &hkey);

	//设置在"程序与功能"中显示的应用图标 (启动程序地址)
	RegSetValueEx(hkey, L"DisplayIcon", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"D:\\desktop\\myInstaller\\d3dx12.exe"),
		wcslen(L"D:\\desktop\\myInstaller\\d3dx12.exe") * 2);

	//设置在"程序与功能"中显示的应用名称
	RegSetValueEx(hkey, L"DisplayName", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"我的程序"),
		wcslen(L"我的程序") * 2);

	//设置版本号
	RegSetValueEx(hkey, L"DisplayVersion", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"1.0.0.0"),
		wcslen(L"1.0.0.0") * 2);

	//设置发布者 (所属公司)
	RegSetValueEx(hkey, L"Publisher", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"DGAF"),
		wcslen(L"DGAF") * 2);

	//设置安装路径
	RegSetValueEx(hkey, L"InstallLocation", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"D:\\desktop\\myInstaller\\"),
		wcslen(L"D:\\desktop\\myInstaller\\") * 2);

	//设置卸载地址
	RegSetValueEx(hkey, L"UninstallString", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"D:\\desktop\\myInstaller\\Uninstall.exe"),
		wcslen(L"D:\\desktop\\myInstaller\\Uninstall.exe") * 2);

	//关闭子键
	RegCloseKey(hkey);
	

用到了以下四个函数,读者可以自行网上搜:

  1. RegCreateKeyEx
  2. RegOpenKey
  3. RegSetValueEx
  4. RegCloseKey

在这里插入图片描述

我这边没有用管理员开VS,就会弹出这个窗口,可以直接到编译路径找到.exe,点击运行就行了

在这里插入图片描述

效果就是这样,大家可以注意到,我现在还没有给出 Uninstall.exe 的源码。下一篇教程,我会讲如何做安装界面,顺带连卸载程序一并讲。可能有聪明的读者已经想到,原理都是一个样,还是用 修改注册表,删程序 的方式卸载,只不过是卸完擦屁股罢了。

6.添加程序快捷方式到桌面和开始菜单中

不多说,上代码:


	WCHAR DesktopPath[MAX_PATH];				//桌面路径
	WCHAR Programs_path[MAX_PATH];				//开始菜单路径
	::SHGetSpecialFolderPath(nullptr, DesktopPath, CSIDL_DESKTOPDIRECTORY, false);		//获取桌面路径
	::SHGetSpecialFolderPath(nullptr, Programs_path, CSIDL_PROGRAMS, false);			//获取菜单路径

	ComPtr<IShellLink>		pShortcutLink;		//lnk链接接口
	ComPtr<IPersistFile>	pPersistFile;		//lnk文件接口

	//创建pShortcutLink
	CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShortcutLink)); 

	pShortcutLink->SetPath(L"D:\\desktop\\MyInstaller\\Media Play.exe");			//设置启动程序
	pShortcutLink->SetIconLocation(L"D:\\desktop\\MyInstaller\\softmgr.ico", 0);	//设置快捷方式图标位置
	pShortcutLink->SetWorkingDirectory(L"D:\\desktop\\MyInstaller\\");				//设置快捷方式工作目录
	pShortcutLink->SetDescription(L"一个快捷方式");									//设置说明

	pShortcutLink.As(&pPersistFile);	//将数据传递给pPersistFile接口

	//最终创建快捷方式

	pPersistFile->Save((wstring(DesktopPath) + wstring(L"\\myInstaller.lnk")).c_str(), false);	//添加到桌面
	pPersistFile->Save((wstring(Programs_path) + wstring(L"\\myInstaller.lnk")).c_str(), false);	//添加到开始菜单

到这里,你的安装包就大功告成了。

在这里插入图片描述

完整代码示例


// Media Player Setup.cpp

#include<Windows.h>								//大Win头
#include<ShlObj.h>								//COM接口头文件
#include<wrl.h>									//用于COM接口,方便我们调用
using namespace Microsoft::WRL;

#include<iostream>								//C++标准输入输出
#include<fstream>								//C++标准文件
#include<string>								//C++标准字符串,另外也用于宽窄字符转换
#include<conio.h>								//用于接收键盘消息
using namespace std;

#include "zlib/zlib.h"							//源于Zlib,是Zlib的源头
#include "zlib/unzip.h"							//Zlib第三方库minizip的一个头文件,用于解压zip文件

#pragma comment(lib,"zlib/zlibstat")			//Zlib链接库

#include "resource.h"							//向项目添加资源时自动生成的资源头文件



int main()
{
	::CoInitialize(nullptr);	//初始化COM接口

	HINSTANCE hins = GetModuleHandle(0);				//获取当前程序实例
	HRSRC my_zip = nullptr;								//资源标识句柄
	DWORD zip_size = 0;									//资源大小
	HGLOBAL zip_data = nullptr;							//资源数据
	wstring StrPrintfEngineW;							//用于拼接字符串的Unicode版本
	WCHAR* SetupPathW = new WCHAR[MAX_PATH];			//安装目录的Unicode版本
	BROWSEINFO file_open_dialog = {};					//文件夹选择框
	LPITEMIDLIST idl = nullptr;
	

	cout << endl;
	cout << "您正在安装零秋Player!\n"
		 << "按Y确认安装,并选择安装目录\n"
		 << "按N取消安装并退出\n";

	bool isExit = false;	//是否退出循环

	while (!isExit)
	{
		if (_kbhit())			//如果有键盘消息
		{
			switch (_getch())
			{
			case 'N':
			case 'n':
				exit(0);
				break;

			case 'Y':
			case 'y':
				file_open_dialog.hwndOwner = nullptr;
				file_open_dialog.pszDisplayName = SetupPathW;
				file_open_dialog.lpszTitle = L"请选择你的安装目录:";
				file_open_dialog.ulFlags = BIF_RETURNFSANCESTORS;

				idl = SHBrowseForFolder(&file_open_dialog);		//如果没有选择就继续
				if (nullptr == idl)
				{
					continue;
				}
				SHGetPathFromIDList(idl, SetupPathW);
				isExit = true;
				break;

			}
		}
	}


	//————————————————————————————————————————————————————————————————————————————————————————————————————————————
	


	my_zip = ::FindResource(hins, MAKEINTRESOURCE(IDR_ZIP2), L"ZIP");  //通过资源标识符寻找资源
	zip_size = SizeofResource(nullptr, my_zip);						   //获取资源大小
	zip_data = LoadResource(nullptr, my_zip);						   //加载资源


	StrPrintfEngineW = SetupPathW;
	StrPrintfEngineW += wstring(L"\\ZAMediaPlayerSetup.zip");

	// 先创建文件
	HANDLE zipfile = CreateFile2(StrPrintfEngineW.c_str(), GENERIC_WRITE, 0, CREATE_ALWAYS, nullptr);
	// 将数据写入文件
	WriteFile(zipfile, (LPCVOID)LockResource(zip_data), zip_size, nullptr, nullptr);
	// 关闭文件
	CloseHandle(zipfile);



	//————————————————————————————————————————————————————————————————————————————————————————————————————————————


	char SetupPathA[MAX_PATH];				//安装路径的ASCII版本
	char zipLocation[MAX_PATH];				//zip位置
	unzFile compressZip = nullptr;			//zip句柄
	unz_global_info64 globalinfo = {};		//zip全局信息
	size_t zip_info_filename_length = 0;	//当前解压的文件名长度
	size_t zip_info_file_size = 0;			//当前解压的文件大小
	char* zip_info_FileName = nullptr;		//当前解压的文件名
	char* zip_info_FileData = nullptr;		//当前解压的文件数据
	unz_file_info64 fileinfo = {};			//解压文件的信息
	ofstream unzip_fstream;					//解压文件流
	string StrPrintfEngineA;				//用于拼接字符串的ASCII版本

	sprintf_s(SetupPathA, MAX_PATH, "%ws", SetupPathW);
	sprintf_s(zipLocation, MAX_PATH, "%ws", StrPrintfEngineW.c_str());
	compressZip = unzOpen64(zipLocation);		//打开zip

	unzGetGlobalInfo64(compressZip, &globalinfo);	//读取zip全局数据


	for (size_t i = 0; i < globalinfo.number_entry; ++i)	//逐个解压
	{
		StrPrintfEngineA = SetupPathA;
		StrPrintfEngineA += "\\";
		fileinfo = {};
		zip_info_FileName = new char[MAX_PATH];
		unzGetCurrentFileInfo64(compressZip, &fileinfo, zip_info_FileName, MAX_PATH,
			nullptr, 0, nullptr, 0);
		//获取当前解压文件信息

		zip_info_file_size = fileinfo.uncompressed_size;
		zip_info_FileData = new char[zip_info_file_size];
		zip_info_filename_length = strlen(zip_info_FileName);


		//如果是文件夹
		if (zip_info_FileName[zip_info_filename_length - 1] == '/')
		{
			StrPrintfEngineA += zip_info_FileName;
			CreateDirectoryA(StrPrintfEngineA.c_str(), nullptr);
			//创建文件夹,解压文件时会解压到相应目录
		}
		else    //如果是文件就解压
		{
			unzOpenCurrentFile(compressZip);	//打开文件

			StrPrintfEngineA += zip_info_FileName;
			unzip_fstream.open(StrPrintfEngineA.c_str(), ios::out | ios::binary);
			unzReadCurrentFile(compressZip, reinterpret_cast<void*>(zip_info_FileData), zip_info_file_size);
			//读取当前文件到内存

			unzip_fstream.write(zip_info_FileData, zip_info_file_size);		//将文件从内存写入到硬盘里
			unzip_fstream.close();											//关闭文件
		}
		unzCloseCurrentFile(compressZip);	//关闭当前文件

		delete[] zip_info_FileName;
		delete[] zip_info_FileData;

		if (i + 1 < globalinfo.number_entry)
		{
			unzGoToNextFile(compressZip);	//解压下一个文件,指针偏移
		}
	}

	unzClose(compressZip);	//关闭zip文件


	//————————————————————————————————————————————————————————————————————————————————————————————————————————————


	HKEY hkey = nullptr;	//根键句柄

	//创建一个叫 MyInstaller 的子键,这个是安装与卸载程序的地方
	RegCreateKeyEx(HKEY_LOCAL_MACHINE,
		L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ZA_MediaPlayer",
		0, (WCHAR*)L"", 0, KEY_READ | KEY_WRITE,
		nullptr, &hkey, nullptr);

	//打开子键
	RegOpenKey(HKEY_LOCAL_MACHINE,
		L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ZA_MediaPlayer", &hkey);

	StrPrintfEngineW = SetupPathW;
	StrPrintfEngineW += L"\\";
	StrPrintfEngineW += L"Media Player.exe";

	//设置在"程序与功能"中显示的应用图标 (启动程序地址)
	RegSetValueEx(hkey, L"DisplayIcon", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(StrPrintfEngineW.c_str()),
		StrPrintfEngineW.length() * 2);


	//设置在"程序与功能"中显示的应用名称
	RegSetValueEx(hkey, L"DisplayName", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"零秋Player"),
		wcslen(L"零秋Player") * 2);

	//设置版本号
	RegSetValueEx(hkey, L"DisplayVersion", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"1.0.0.0"),
		wcslen(L"1.0.0.0") * 2);

	//设置发布者 (所属公司)
	RegSetValueEx(hkey, L"Publisher", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(L"DGAF"),
		wcslen(L"DGAF") * 2);


	//设置安装路径
	RegSetValueEx(hkey, L"InstallLocation", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(SetupPathW),
		wcslen(SetupPathW) * 2);


	StrPrintfEngineW = SetupPathW;
	StrPrintfEngineW += L"\\";
	StrPrintfEngineW += L"Uninstall.exe";


	//设置卸载地址
	RegSetValueEx(hkey, L"UninstallString", 0, REG_SZ,
		reinterpret_cast<const BYTE*>(StrPrintfEngineW.c_str()),
		StrPrintfEngineW.length() * 2);

	//关闭子键
	RegCloseKey(hkey);



	//————————————————————————————————————————————————————————————————————————————————————————————————————————————



	WCHAR DesktopPath[MAX_PATH];				//桌面路径
	WCHAR Programs_path[MAX_PATH];				//开始菜单路径

	::SHGetSpecialFolderPath(nullptr, DesktopPath, CSIDL_DESKTOPDIRECTORY, false);		//获取桌面路径
	::SHGetSpecialFolderPath(nullptr, Programs_path, CSIDL_PROGRAMS, false);			//获取菜单路径

	ComPtr<IShellLink>		pShortcutLink;		//lnk链接接口
	ComPtr<IPersistFile>	pPersistFile;		//lnk文件接口

	CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShortcutLink));

	StrPrintfEngineW = SetupPathW;
	StrPrintfEngineW += L"\\";

	pShortcutLink->SetPath((StrPrintfEngineW + wstring(L"Media Player.exe")).c_str());			//设置启动程序
	pShortcutLink->SetIconLocation((StrPrintfEngineW + wstring(L"appicon.ico")).c_str(), 0);	//设置快捷方式图标位置
	pShortcutLink->SetWorkingDirectory(SetupPathW);												//设置快捷方式工作目录
	pShortcutLink->SetDescription(L"零秋播放器——快捷方式");										//设置说明

	pShortcutLink.As(&pPersistFile);	//将数据传递给pPersistFile接口

	//最终创建快捷方式

	pPersistFile->Save((wstring(DesktopPath) + wstring(L"\\零秋播放器.lnk")).c_str(), false);		//添加到桌面
	pPersistFile->Save((wstring(Programs_path) + wstring(L"\\零秋播放器.lnk")).c_str(), false);		//添加到开始菜单


	StrPrintfEngineW = SetupPathW;
	StrPrintfEngineW += wstring(L"\\ZAMediaPlayerSetup.zip");

	DeleteFile(StrPrintfEngineW.c_str());		//删除多余的安装zip

	cout << endl;
	cout << "安装已完成!" << endl;
	system("pause");

}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

这里有示例😎项目下载:https://wwek.lanzoub.com/iA0RE0ia3yri
密码:dgaf
下一章,我们将会用 Windows窗口 完善这个安装包。

  • 10
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
下面是一个简单的 C++ 控制台应用程序,用于绘制三角形、圆、直线和矩形: ```c++ #include <iostream> #include <cmath> using namespace std; void drawLine(int x1, int y1, int x2, int y2) { int dx = abs(x2 - x1), dy = abs(y2 - y1); int sx = x1 < x2 ? 1 : -1, sy = y1 < y2 ? 1 : -1; int err = dx - dy; while (x1 != x2 || y1 != y2) { cout << "."; int e2 = 2 * err; if (e2 > -dy) { err -= dy; x1 += sx; } if (e2 < dx) { err += dx; y1 += sy; } } cout << endl; } void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { drawLine(x1, y1, x2, y2); drawLine(x2, y2, x3, y3); drawLine(x3, y3, x1, y1); } void drawCircle(int xc, int yc, int r) { int x = 0, y = r; int d = 3 - 2 * r; while (x <= y) { cout << "."; if (d < 0) { d += 4 * x + 6; } else { d += 4 * (x - y) + 10; y--; } x++; } cout << endl; } void drawRectangle(int x, int y, int w, int h) { drawLine(x, y, x + w, y); drawLine(x + w, y, x + w, y + h); drawLine(x + w, y + h, x, y + h); drawLine(x, y + h, x, y); } int main() { // 绘制三角形 cout << "Triangle:" << endl; drawTriangle(10, 10, 20, 30, 30, 10); // 绘制圆形 cout << "Circle:" << endl; drawCircle(20, 20, 10); // 绘制直线 cout << "Line:" << endl; drawLine(10, 30, 30, 10); // 绘制矩形 cout << "Rectangle:" << endl; drawRectangle(40, 10, 20, 30); return 0; } ``` 这个程序使用了一些基本的绘图算法,包括绘制直线的 Bresenham 算法、绘制圆形的中点画圆算法、以及绘制矩形的基于直线的算法。你可以根据需要进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dgaf

谢谢大佬打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值