文章题目看起来有点绕,解释一下,假如你基于框架写了一个程序,想装到客户机上,但是客户机上可能并没有安装框架,因此你的程序需要预先将框架安装在目标机上,然后再执行一些安装程序的标准功能,如创建快捷方式、创建程序组、写入卸载信息以便让Windows能够对程序进行卸载管理等,实现这个功能的方法有很多,例如使用InstallShield、Wix Toolset等均可实现此功能。


不过本文并不是介绍使用这些工具的方法,而是要使用框架来编写一个安装程序,实现一般安装程序的复制文件、创建快捷方式、创建程序组、安装字体、安装服务、写入反安装信息等一些常见的功能,有重复发明轮子之嫌,主要目的是个人兴趣,想探究一下安装程序是怎么实现这些有趣的功能的,觉得无聊的朋友可飘过,勿喷。


说明:

框架安装程序:指安装.Net Framework 4.0到客户机上的安装程序,框架安装程序使用微软提供的dotNetFx40_Full_x86_x64.exe,为32位平台和64位平台通用的安装包。

应用安装程序:指基于.Net Framework 4.0编写的安装程序,将应用程序安装到客户机上。

编程环境: Visual Studio 2010 + .Net Framework 4.0。


要想用安装程序将编写好的程序安装到客户机上,首先得解决安装程序运行的问题,安装程序是基于框架编写的,得装上框架才行,恩,要有鸡先得有蛋。好,来理一理思路:

(1)使用一个引导程序安装.Net Framework 4.0,这个引导程序应该是已经编译为二进制代码的可执行文件,要求在 Windows XP 以上的操作系统中直接运行,不需依赖第三方DLL。这个引导程序需要检查目标机上是否安装了框架,如果已经安装了框架,则直接启动应用安装程序进行安装,如果检测到没有安装,则启动微软提供的.Net Framework 4.0框架安装程序进行框架的安装。

(2)框架安装成功后,启动应用安装程序,显示安装协议、提供程序功能选择、选择安装路径、执行安装(创建桌面快捷方式、创建程序组、复制文件到指定目录、安装字体、安装服务、写入反安装信息以便在控制面板中的“添加/删除程序”中能看到安装的程序、生成本地可执行映像以加快程序启动速度等等功能),安装完成后,提供“启动程序”的选项,以便用户能够在安装完成后立即启动程序。

(3)需要编写一个反安装程序,该程序根据已经安装的程序功能和配置文件删除已经安装的程序功能。

好了,应该要写三个程序,一步一步来。


1 引导程序的制作

引导程序的作用是检测客户机上是否已经安装了.Net Framework 4.0 框架,可以通过检查注册表相关键值的方式来实现,微软知识库提到可以检测如下的注册表项来查看是否安装了.Net Framework 4.0:


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full

该键下有名称为“Install”的项目,如果该项目值为1(0x1)则表示该计算机已经安装了框架,若不存在该注册表项目或该键值不为1,则可认为当前计算机尚未安装框架。


为了能够让引导程序能够独立运行,又能比较容易的编写,且可执行读写注册表等操作,我使用MFC框架来编写这个引导程序,并选择“在静态库中使用 MFC”这个选项,这样就能够将引导程序所使用的相关库文件打包到最终的EXE中,虽然看起来有点大(编译后1.78MB),但是放到客户机上可直接运行,并不需要客户机再安装MFC框架来运行这个引导程序。

(1)新建一个MFC应用程序,命名为Setup。


新建一个MFC应用程序


(2)在应用程序向导的应用程序类型设置中,选择“基于对话框”和“在静态库中使用 MFC”,其他选择可以使用默认设置。


在静态库中使用 MFC


创建完毕后,编写一个函数来检测是否安装了框架,这里借用了微软知识库的方法:


// 检测是否已经安装 .Net Framework 4.0。
#define NETFX40_FULL_REVISION 0
#define NETFX45_RC_REVISON MAKELONG(50309, 5)
bool IsNetFx4Present(DWORD dwMinimumRelease)
{DWORD dwError = ERROR_SUCCESS;HKEY hKey = NULL;DWORD dwData = 0;DWORD dwType = 0;DWORD dwSize = sizeof(dwData);dwError = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full", 0, KEY_READ, &hKey);if (ERROR_SUCCESS == dwError){dwError = ::RegQueryValueExW(hKey, L"Release", 0, &dwType, (LPBYTE)&dwData, &dwSize);if ((ERROR_SUCCESS == dwError) && (REG_DWORD != dwType)){dwError = ERROR_INVALID_DATA;}else if (ERROR_FILE_NOT_FOUND == dwError){dwError = ::RegQueryValueExW(hKey, L"Install", 0, &dwType, (LPBYTE)&dwData, &dwSize);if ((ERROR_SUCCESS == dwError) && (REG_DWORD == dwType) && (dwData == 1)){// 如果安装的是 .Net 4.0,则认为其版本号为 0。dwData = 0;}else{dwError = ERROR_INVALID_DATA;}}}if (hKey != NULL){::RegCloseKey(hKey);}return ((ERROR_SUCCESS == dwError) && (dwData >= dwMinimumRelease));
}

如果检测到没有安装框架,则直接启动 .Net Framework 4.0 安装程序。这里还有一个问题需要处理,.Net Framework 4.0 安装程序在执行安装时需要 Windows Imaging Component 的支持,如果客户机上未安装此组件,则框架安装程序将无法继续,经过试验,在新装的 MSDN Windows Server 2003 SP1 上该组件是未安装的,而对于 Windows XP SP3,Windows Vista,Windows7 等以上的操作系统,都是已经包含了该组件的,所以为了保证框架安装程序能够正常安装,必需保证操作系统已经包含了该组件,因此需要检测操作系统上是否已经安装了WIC(Windows Imaging Component)。经过一番网上查阅,发现有两种方法来间接判断是否已经安装了WIC,一种是通过检测注册表相关键值的方式,即检测下列注册表键值是否存在:


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WIC

另外一种方法是可以通过检测系统目录是否包含了WindowsCodecs.dll 这个文件来进行判定,只要系统安装了WIC,系统目录下就会包含这个文件,我使用检测文件的这种方法。


// 检查计算机是否安装了 Windows Imaging Component,通过检测系统目录是否存在对应的文件:WindowsCodecs.dll 来进行判定。
bool IsWicInstalled()
{// 获取系统目录。int MAX_PATH_LENGTH = 64;wchar_t systemDirectory[MAX_PATH_LENGTH];int nLength = ::GetSystemDirectoryW(systemDirectory, MAX_PATH_LENGTH);// 函数调用失败或者返回的系统路径超过缓冲区长度。if ((nLength == 0) || (nLength > (MAX_PATH_LENGTH + 1))){return false;}// 生成文件名。CString wicFileName = CString(systemDirectory);if (wicFileName.GetAt(wicFileName.GetLength() - 1) != '\\'){wicFileName += L"\\"; }wicFileName += L"WindowsCodecs.dll";// 查找文件是否存在。WIN32_FIND_DATA fData;HANDLE hFile;hFile = FindFirstFile(wicFileName, &fData);bool isFileExist = (hFile != INVALID_HANDLE_VALUE);FindClose(hFile);return isFileExist;
}

如果检测到WIC尚未安装,则首先运行WIC的补丁安装程序,这个补丁安装程序可以使用命令行参数来定义安装行为,安装程序的可用选项如下图所示:



我使用的是 /passive /norestart 这两个参数,通过如下方式来启动安装程序并获取其退出码,当退出码为0时,表示安装程序正常退出,可用继续框架安装程序的安装了。


// 从命令行启动安装并返回退出码。
DWORD CSetupApp::InstallPrerequisiteAndReturnExitCode(CString cmdline)
{STARTUPINFO si = {0};si.cb = sizeof(si);PROCESS_INFORMATION pi = {0};BOOL bLaunchedSetup = ::CreateProcess(NULL, cmdline.GetBuffer(),NULL, NULL, FALSE, 0, NULL, NULL, &si,&pi);DWORD dwExitCode;if (bLaunchedSetup){// 持续等待相关进程退出。::WaitForSingleObject(pi.hProcess, INFINITE);::GetExitCodeProcess(pi.hProcess, &dwExitCode);::CloseHandle(pi.hProcess);}return dwExitCode;
}

2 .Net Framework 4.0 安装程序安装进度的获取

在进行框架安装程序的安装时,有两种方式可以选择,一种是让框架安装程序以主动模式安装,这样可以看到进度显示,且不需要用户进行交互(如单击接受协议、下一步等),可以通过在调用框架安装程序时使用参数 /passive 来解决(下图列出了框架安装程序能够使用的命令行开关),这种方式比较简单,处理过程就和上述的安装WIC组件无太大差别,都是以特定参数启动安装程序,然后获取退出码来判断安装是否正常完成。这里有点特殊的是,如果框架安装程序正常退出,则退出码为0,如果正常退出但需要重启,则退出码为3010。



另外一种方法是让框架安装程序在后台运行,自己编写代码获取进度并予以显示。我感兴趣的当然是后面一种方法。经过查阅微软的知识库,发现微软已经为这个问题编写一个链接器示例程序。该链接器使用内存共享的方法将安装进程相关的信息通知调用方。调用方通过访问指定名称的内存数据结构来获取安装进度。具体方法是在调用框架安装程序时就为其指定一个名称唯一的信道,这可以通过开关 /pipe 来进行指定。对应的,微软在链接器中定义了一个数据结构来表示当前的安装进度,如下所示:


// MMIO data structure for inter-process communication
struct MmioDataStructure
{// Is download done yet?bool m_downloadFinished;// Is installer operation done yet?bool m_installFinished;// Set to cause downloader to abortbool m_downloadAbort;// Set to cause installer operation to abortbool m_installAbort;// HRESULT for downloadHRESULT m_hrDownloadFinished;// HRESULT for installer operationHRESULT m_hrInstallFinished;// Internal error from MSI if applicableHRESULT m_hrInternalError;// This gives the windows installer step being executed if an error occurs while processing an MSI, for example, "Rollback"WCHAR m_szCurrentItemStep[MAX_PATH];// Download progress 0 - 255 (0 to 100% done)                                       unsigned char m_downloadProgressSoFar;// Install progress 0 - 255 (0 to 100% done)unsigned char m_installProgressSoFar;// Event that chainer creates and chainee opens to sync communications.WCHAR m_szEventName[MAX_PATH]; 
};

调用方通过不断读取该数据结构的数据成员来获取安装进度,此时可以在安装界面上放置一个进度条控件,然后定义一个计时器,以指定的间隔不断访问安装进度成员变量,更新显示即可。当安装进度达到最大值,且检测成员变量 m_installFinished 为 true 时,表示安装已结束,检测退出码是否为指示成功的相应值就可以判断框架是否安装成功了。以下我对微软的示例做了一点简单的修改以适应我的安装程序。


class MmioChainer : protected MmioChainerBase
{
public:MmioChainer (LPCWSTR sectionName, LPCWSTR eventName): MmioChainerBase(CreateSection(sectionName), CreateEvent(eventName)){Init(eventName);}virtual ~MmioChainer (){::CloseHandle(GetEventHandle());::CloseHandle(GetMmioHandle());}public:using MmioChainerBase::IsDone;using MmioChainerBase::Abort;using MmioChainerBase::IsAborted;using MmioChainerBase::GetInstallResult;using MmioChainerBase::GetInstallProgress;using MmioChainerBase::GetDownloadResult;using MmioChainerBase::GetDownloadProgress;using MmioChainerBase::GetCurrentItemStep;HRESULT GetInternalErrorCode(){return GetInternalResult();}bool Launch(const CString& args){CString cmdline = L"Prerequisite\\dotNetFramework4\\dotNetFx40_Full_x86_x64.exe /pipe installing " + args;STARTUPINFO si = {0};si.cb = sizeof(si);PROCESS_INFORMATION pi = {0};BOOL bLaunchedSetup = ::CreateProcess(NULL, cmdline.GetBuffer(),NULL, NULL, FALSE, 0, NULL, NULL, &si,&pi);if (bLaunchedSetup != 0){handleThread = pi.hThread;handleProcess = pi.hProcess;}else{handleThread = NULL;handleProcess = NULL;}return (bLaunchedSetup != 0);
}void CloseThreadAndProcess(){::CloseHandle(handleThread);::CloseHandle(handleProcess);}private:HANDLE handleThread, handleProcess;private:static HANDLE CreateSection(LPCWSTR sectionName){return ::CreateFileMapping (INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,sizeof(MmioDataStructure),sectionName);}static HANDLE CreateEvent(LPCWSTR eventName){return ::CreateEvent(NULL, FALSE, FALSE, eventName);}
};

其中内存共享的关键是使用函数MapViewOfFile建立内存映射,具体可以参考微软的MSDN。


3 安装流程

参考InstallShield的安装程序,我设计了以下的安装程序界面:



(1)安装协议接受界面。显示安装使用协议,安装使用协议存放在一个RTF格式的文档中,当安装程序运行时使用富文本框控件自动加载,这样也可以很方便的修改安装使用协议。只有当用户勾选了同意安装协议才能单击下一步按钮。



(2)程序功能选择界面。用户在此界面选择不同的程序功能,现在只支持一次安装单个的程序功能,可以扩展为一次安装多个程序功能,这样安装程序的适用性更好一些。有兴趣的朋友可以在此基础上进一步修改。



(3)安装路径选择界面。可以让用户选择不同的安装路径。



(4)安装摘要界面。显示简单的摘要信息。



(5)执行安装界面。将用户选择的功能安装到客户机上。



(6)完成安装界面。可以选择是否立即启动程序。


4 安装程序结构及安装配置文件

整个安装程序结构如下:


Setup
|--Setup.exe
|--Installer
|--|--Data.zip
|--|--Installer.exe
|--|--Installer.xml
|--|--Ionic.Zip.dll
|--|--License.rtf
|--Prerequisite
|--|--dotNetFramework4
|--|--|--dotNetFx40_Full_x86_x64.exe
|--|--wic
|--|--|--wic_x86_chs.exe

其中Setup.exe为引导程序,负责检测和安装WIC和框架,Installer文件夹内为应用安装程序,负责将应用安装到客户机上。Prerequisite文件夹内包含了WIC和框架的安装程序。卸载程序已经打包到了Data.zip中,当安装应用程序时直接复制到安装目录下以便执行卸载。Installer.exe为应用安装主程序。Installer.xml为安装配置文件。Ionic.Zip.dll为解压缩组件。License.rtf为安装协议。

为了和安装流程相适应,同时为了控制安装程序的行为,我采用XML文件的方式来定义应用安装程序的一些特性。


<?xml version="1.0" encoding="utf-8"?>
<installer title="测试安装程序" code="Test" version="1.0.0.0" publisher="我"><features><feature name="服务端" code="Server" data="Server" ngen="true" launchapplication="Server.exe" guid="11d3ff3c-c890-4f62-9e44-a88457fd9c18"><shortcut type="program" name="服务端" target="Server.exe" icon="Server.ico"><shortcut type="program" name="用户手册" target="Help.chm" icon="Help.ico"><shortcut type="program" name="卸载" target="Uninstaller.exe" icon="Uninstaller.ico"><shortcut type="desktop" name="测试程序(服务端)" target="Server.exe" icon="Server.ico"><service name="UdpServer" target="UdpServer.exe"></service></shortcut></shortcut></shortcut></shortcut></feature><feature name="客户端" code="Client" data="Client" ngen="true" launchapplication="Client.exe" guid="954f3e22-a1b1-4fb3-8adc-1ef61d979d19"><shortcut type="program" name="客户端" target="Client.exe" icon="Client.ico"><shortcut type="program" name="用户手册" target="Help.chm" icon="Help.ico"><shortcut type="program" name="卸载" target="Uninstaller.exe" icon="Uninstaller.ico"><shortcut type="desktop" name="测试程序(客户端)" target="Client.exe" icon="Client.ico"><span name="微软雅黑" file="msyh.ttf" type="TrueType" style=""></span></shortcut></shortcut></shortcut></shortcut></feature></features>
</installer>

各个元素的意义:


title:安装程序的标题,会显示在安装界面上。
code:安装程序的代号,用于在用户选择安装路径后附加在安装路径上,例如用户选择了安装路径为C:\Program Files,则最终应用程序安装在C:\Program Files\$code下。
version:应用程序的版本。用于显示在“Windows安装/卸载程序”界面。
publisher:应用程序的发布者,用于显示在“Windows安装/卸载程序”界面。
feature:安装程序的功能。如果应用程序有多个功能,可以分别列出供用户进行选择安装。
feature\name:应用程序的功能名称。
feature\code:功能代码。用于生成安装路径。
feature\data:安装文件在ZIP压缩包中的文件夹名称。不同的安装功能对应了不同的安装文件,将这些安装文件集中在一起放置在一个压缩包中。不同的功能在压缩包中以不同的文件夹名称予以命名,当用户选择了某个功能时,将对应的文件夹内容解压缩到目标目录。
feature\ngen:安装完成后,是否对主程序执行本机映像生成以提高启动速度。
feature\launchApplication:安装完毕后需要启动运行的应用程序。
feature\guid:程序功能的唯一ID,用于在生成反安装信息时作为注册表键值的名称。
shortcut:表示一个快捷方式。
shortcut\type:快捷方式的类型,program表示为程序组的快捷方式,desktop表示为桌面的快捷方式。
shortcut\name:快捷方式的名称。
shortcut\target:快捷方式所关联的应用程序或文件。
shortcut\icon:快捷方式所使用的图标文件。
service:表示一个服务。
service\name:服务的名称。用于在卸载时识别服务使用。
service\target:服务对应的服务文件。
font:表示要安装的字体。
font\name:字体的名称。
font\file:字体文件名。
font\type:字体的类型。

所有文件均以相对路径的方式进行表示,例如图标文件,如果快捷方式使用了一个图标文件,在安装程序的对应路径下的应该放置一个名称为“icon”的文件夹,把使用的图标文件放在其中,字体放置在名称为“font”的文件夹中,以便安装程序复制和引用。


5 复制文件

应用安装程序的一个重要任务是复制安装文件到指定的目录,一般在其他安装软件中,都是要用户逐个指定要创建的文件夹和文件。要是也这样来准备安装文件,不免有些繁琐,我采用的是将所有需要安装到客户机上的文件打包为一个ZIP格式的压缩包,然后再释放到用户选择的安装路径下。如果一个安装程序有多个程序功能(如服务端、客户端有不同的安装文件),则将其分别放置在压缩包内的不同文件夹下,在用户选择了要安装的程序功能后,根据功能解压缩指定文件夹的文件。在这里我使用的是DotNetZipLib-1.9的压缩和解压缩组件。可以先使用其他压缩工具将需要安装到客户机上的文件打包为一个ZIP包,如WinRAR,WinZip等工具将各个功能以文件夹的形式分别存放。



在安装时使用如下的代码直接将文件释放到用户选择的安装路径上:


/// <summary>
/// 解压缩安装文件到指定的目录。
/// </summary>
/// <returns></returns>
private bool UnzipFile()
{// 解压缩安装文件到指定目录。try{string dataFileName = Path.Combine(Application.StartupPath, "data.zip");string targetPath = aInstallerConfiguration.Destionation;if (Directory.Exists(targetPath) == false){Directory.CreateDirectory(targetPath);}using (ZipFile aZipFile = new ZipFile(dataFileName, Encoding.GetEncoding("gb2312"))){aProgressBar.Maximum = aZipFile.Entries.Count;foreach (ZipEntry aEntry in aZipFile.Entries){if (aEntry.FileName.StartsWith(aInstallerConfiguration.Feature.Data, StringComparison.OrdinalIgnoreCase)){aEntry.Extract(targetPath, ExtractExistingFileAction.OverwriteSilently);aProgressTip.Text = string.Format("复制文件 {0}", aEntry.FileName);Application.DoEvents();if (this.aProgressBar.Value < this.aProgressBar.Maximum){this.aProgressBar.Value++;}}}}this.aProgressBar.Value = this.aProgressBar.Maximum;}catch (Exception ex){MessageDisplayer.DisplayError("复制安装文件发生错误。", ex.Message);return false;}return true;
}

需要注意的是,DotNetZipLib对于中文支持似乎有问题,使用WinRAR压缩的ZIP包,如果其中有文件或文件夹的名称包含中文字符,即使使用GB2312编码进行解压缩,解压缩后的文件名仍旧为乱码,但是使用DotNetZipLib本身以GB2312编码对包含中文名称的文件或文件夹进行压缩,然后再解压缩,文件夹或文件名是正常的,不会产生乱码。


6 创建桌面快捷方式

在 .Net Framework 4.0 的类中,并没有相应的类来完成这个功能,因此需要借助其他组件的功能来实现。最简便的方法是使用Windows Script Host Object Model 库(其为一ActiveX组件,一般位于system32下,名称为wshom.ocx),可以使用添加COM引用的方式来建立对该组件的引用。


IWshRuntimeLibrary.IWshShell_Class shell = new IWshShell_Class();
foreach (Shortcut aShortcut in aInstallerConfiguration.Feature.Shortcuts)
{string destDirectory = aShortcut.Destination == ShortcutDestination.Program ? featureDirectory : desktopDirectory;string pathLink = Path.Combine(destDirectory, aShortcut.Name + ".lnk");IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(pathLink);shortcut.TargetPath = Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code + "\\" + aShortcut.Target);shortcut.Arguments = string.Empty;shortcut.Description = aShortcut.Name;shortcut.WorkingDirectory = Path.GetDirectoryName(shortcut.TargetPath);shortcut.IconLocation = Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code + "\\Icon\\" + aShortcut.Icon);shortcut.WindowStyle = 1;shortcut.Save();
}
shell = null;

使用其中的 IWshShortcut 接口,可以进行快捷方式的创建,其中的几个参数意义解释如下:


TargetPath:链接目标的路径,即该快捷方式所关联的程序的路径。
Arguments:参数,运行程序时附加的参数。
Description:快捷方式的描述,将作为快捷方式的名称进行显示。
WorkingDirectory:工作目录,关联的程序所在目录名称。
IconLocation:快捷方式使用的图标文件路径,可以使用EXE中的图标资源。
WindowStyle:启动程序时窗口的风格。

定义完毕后保存就创建了一个快捷方式。需要注意的是,快捷方式的保存位置是在CreateShortcut 方法中指定的,因为创建的是桌面快捷方式,所以首先需要获取“桌面”这个特殊文件夹的路径,这个可以通过 C# 中的 Environment 类实现,如下所示:


string desktopDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

该类有一个 GetFolderPath 方法,可以为其指定参数获取不同的系统目录,很是方便。


7 创建程序组

创建程序组和创建快捷方式实质上没有太大的差别,只不过程序组是把一组快捷方式集中放置在一个文件夹中,而这个文件夹有点特殊,叫程序文件夹,一样的可以通过调用 Environment 类的 GetFolderPath 方法为其指定参数来获取这个特殊文件夹:


string startMenuDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Programs, Environment.SpecialFolderOption.DoNotVerify);

获取到之后,可以在其下创建文件夹,创建快捷方式等。


8 安装字体

如果程序需要使用一些特殊字体,如微软的雅黑字体,客户机上不一定会安装,为了保证程序的显示效果,需要将这些字体安装到客户机上。一般程序员都知道,系统文件夹中有一个 Fonts 文件夹,其中存储了系统已经安装的字体。有时为了省事,直接将字体文件复制到这个文件夹,系统会自动为你注册字体,但是在安装程序中,光是将文件复制到这个文件夹下是不起作用的,少了后面注册的那一步,在系统环境下复制是因为有系统的Applet 来帮助进行注册,在安装程序这个环境中只能自己注册。.NetFramework 4.0中只有枚举系统已安装字体的类,并没有提到可以帮助注册字体的类,怎么办?还是请 Windows 32 DLL 函数出场吧!


[DllImport("Gdi32.dll", SetLastError = true)]
private static extern int AddFontResource(string lpName);

可以使用 AddFontResource 函数对字体进行注册,需要注意的是该函数注册字体只对当前的 Windows 会话有效,一旦重新启动后,该字体在系统字体列表中将会消失,为了永久注册该字体,需要将字体信息写入注册表,注册表键值为:


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts

将字体名称和字体文件名写入注册表,再将字体文件复制到系统字体文件夹即可完成字体的永久注册,这样在系统重启后,仍然有该字体的相关信息。在注册字体后,一般需要通过发送一条字体改变的消息来通知其他程序,以便其他程序根据需要调整显示。代码如下:


public class FontInstaller
{[DllImport("Gdi32.dll", SetLastError = true)]private static extern int AddFontResource(string lpName);[DllImport("User32.dll", SetLastError = true)]private static extern int SendMessage(IntPtr hWnd, int nMsg, IntPtr wParam, IntPtr lParam);private IntPtr HWND_BROADCAST = (IntPtr)0xFFFF;private int WM_FONTCHANGE = 0x001D;public void InstallFont(Font theFont){try{// 检查字体是否存在。string systemFontFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), Path.GetFileName(theFont.File));if (File.Exists(systemFontFileName)){return;}// 复制字体到系统字体文件夹。File.Copy(theFont.File, systemFontFileName, true);// 添加字体到系统字体列表。注意使用此函数添加只对当前 Windows 会话有效,重启后丢失。int fontAdded = AddFontResource(systemFontFileName);// 广播字体改变消息以便其他程序适时更改显示。fontAdded = SendMessage(HWND_BROADCAST, WM_FONTCHANGE, (IntPtr)0, (IntPtr)0);// 将字体信息写入注册表以便重启后仍可使用。RegistryKey keyFonts = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", true);if (keyFonts != null){// 检查键值是否已经存在。string valueName = string.Format("{0} ({1})", theFont.Name, theFont.Type);if (keyFonts.GetValue(valueName) == null){keyFonts.SetValue(valueName, Path.GetFileName(theFont.File), RegistryValueKind.String);}}}catch (Exception){throw;}}
}

9 安装服务

有的时候,应用程序是一个服务端,需要在服务器上以系统服务的方式运行,要求在安装程序的时候将服务注册到系统中。这可以通过 TransactedInstaller 来实现。


/// <summary>
/// 安装服务。
/// </summary>
/// <returns></returns>
private bool InstallServices()
{// 获取服务的文件名称。foreach (KeyValuePair<string string=""> aService in aInstallerConfiguration.Feature.Services){string servicesFileName = Path.Combine(aInstallerConfiguration.Destionation, aInstallerConfiguration.Feature.Code, aService.Value);if (File.Exists(servicesFileName) == false){MessageDisplayer.DisplayError("服务文件不存在。");return false;}try{string[] cmdline = { };TransactedInstaller transactedInstaller = new TransactedInstaller();AssemblyInstaller assemblyInstaller = new AssemblyInstaller(servicesFileName, cmdline);transactedInstaller.Installers.Add(assemblyInstaller);transactedInstaller.Install(new System.Collections.Hashtable());}catch (Exception ex){MessageDisplayer.DisplayError("安装服务发生错误。", ex.Message);return false;}}return true;
}
</string>

卸载服务则相反,只不过需要使用 TransactedInstaller的Uninstall方法。


10 执行本机映像生成

使用框架编写的程序生成的是中间代码,在客户机上启动运行时需要经过编译,为了提高启动速度,提高内存使用效率,可以在安装阶段使用框架提供的NGEN工具对程序执行本机映像生成。那么如何获取NGEN的安装路径以便调用呢?可以通过访问注册表的方式来获取框架的安装路径。


private bool Ngen()
{// 检查是否需要执行本机映像生成。if (aInstallerConfiguration.Feature.Ngen == false){return true;}// 对安装的程序集执行本机映像生成以提高启动速度。try{aProgressTip.Text = string.Format("优化程序性能...");Application.DoEvents();// 调用 NGen 执行本地映像生成。RegistryKey keyLocalMachine = Registry.LocalMachine;RegistryKey keyNetFramework4 = keyLocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full");if (keyNetFramework4 != null){object netFramework4InstallPath = keyNetFramework4.GetValue("InstallPath");if (netFramework4InstallPath != null){string ngenFileName = Path.Combine(netFramework4InstallPath.ToString(), "ngen.exe");if (File.Exists(ngenFileName)){// 生成执行本机映像生成的命令行并启动之。ProcessStartInfo aStartInfo = new ProcessStartInfo();aStartInfo.CreateNoWindow = true;aStartInfo.WindowStyle = ProcessWindowStyle.Hidden;aStartInfo.FileName = ngenFileName;aStartInfo.Arguments = string.Format("install \"{0}\"",Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code, this.aInstallerConfiguration.Feature.LaunchApplication));Application.DoEvents();// 无限期等待?Process.Start(aStartInfo).WaitForExit();}}keyNetFramework4.Close();}keyLocalMachine.Close();}catch (Exception){return false;}return true;
}

11 生成反安装配置信息并写入注册表

为了便于反安装程序的工作,需要在安装时将用户选择的一些安装设置信息保存起来,当用户执行卸载程序时,解析这些配置文件,根据配置进行卸载。反安装程序是和安装文件放置在一起的,在安装时一起释放到用户选择的安装路径下,这和其他的安装程序类似。通过在程序组中创建快捷方式来指向反安装程序,这样用户就可以在开始菜单的程序组中对程序进行卸载操作。


/// <summary>
/// 生成反安装配置文件。
/// </summary>
/// <returns></returns>
private bool BuildUnintallXml()
{// 生成反安装配置文件。try{aProgressTip.Text = string.Format("生成反安装配置文件...");Application.DoEvents();StringBuilder aUninstallXml = new StringBuilder();aUninstallXml.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");aUninstallXml.AppendLine("<uninstaller>");// 记录程序名称。aUninstallXml.AppendLine(string.Format("  <title>{0}</title>", this.aInstallerConfiguration.ApplicationName));// 记录安装的程序功能。aUninstallXml.AppendLine(string.Format("  <feature name="\'{0}\'" code="\'{1}\'" data="\'{2}\'" launchapplication="\'{3}\'" guid="\'{4}\'">",this.aInstallerConfiguration.Feature.Name,this.aInstallerConfiguration.Feature.Code,this.aInstallerConfiguration.Feature.Data,this.aInstallerConfiguration.Feature.LaunchApplication,this.aInstallerConfiguration.Feature.Guid));// 记录生成的桌面快捷方式。foreach (Shortcut aShortcut in this.aInstallerConfiguration.Feature.Shortcuts){if (aShortcut.Destination == ShortcutDestination.Desktop){aUninstallXml.AppendLine(string.Format("  <desktop name="\'{0}\'">", aShortcut.Name));}}// 记录安装的服务。foreach (var aKeyValue in this.aInstallerConfiguration.Feature.Services){aUninstallXml.AppendLine(string.Format("  <service name="\'{0}\'" target="\'{1}\'">", aKeyValue.Key, aKeyValue.Value));}aUninstallXml.AppendLine("</service></desktop></feature></uninstaller>");File.WriteAllText(Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code + "\\Uninstaller.xml"), aUninstallXml.ToString());}catch (Exception ex){MessageDisplayer.DisplayError("生成反安装配置文件发生错误。", ex.Message);return false;}return true;
}

完成了安装信息的记录,如何将安装的程序让系统知道,并能够在系统的“添加/删除程序”界面进行卸载操作呢?实际上,一般安装的程序都会在注册表的下列键值注册一些信息以便让系统知道如何调用卸载程序:


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

该键值包含以下关键项目:


DisplayName:在程序列表中显示的程序名称。
DisplayVersion:显示的程序版本。
InstallLocation:安装路径。
Publisher:程序发布者。
UninstallString:表示反安装装程序的路径和调用参数(原来是这样来实现卸载的啊!)。
VersionMajor:主版本号。
VersionMinor:次版本号。

/// <summary>
/// 将卸载信息写入注册表。
/// </summary>
/// <returns></returns>
private bool WriteUninstallRegistry()
{bool isSuccessed = true;try{aProgressTip.Text = "将反安装信息写入注册表...";Application.DoEvents();RegistryKey keyLocalMachine = Registry.LocalMachine;RegistryKey keyUninstall = keyLocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", true);if (keyUninstall != null){string FeatureKeyName = string.Format("{{{0}}}", this.aInstallerConfiguration.Feature.Guid);RegistryKey keyFeature = keyUninstall.CreateSubKey(FeatureKeyName);if (keyFeature != null){keyFeature.SetValue("DisplayIcon", Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code, this.aInstallerConfiguration.Feature.LaunchApplication), RegistryValueKind.String);string displayName = this.aInstallerConfiguration.ApplicationName;if (this.aInstallerConfiguration.Features.Count > 1){displayName = string.Format("{0}({1})", this.aInstallerConfiguration.ApplicationName, this.aInstallerConfiguration.Feature.Name);}keyFeature.SetValue("DisplayName", displayName, RegistryValueKind.String);keyFeature.SetValue("DisplayVersion", this.aInstallerConfiguration.Version.ToString(), RegistryValueKind.String);keyFeature.SetValue("InstallLocation", Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code), RegistryValueKind.String);keyFeature.SetValue("Publisher", this.aInstallerConfiguration.Publisher, RegistryValueKind.String);keyFeature.SetValue("UninstallString", Path.Combine(this.aInstallerConfiguration.Destionation, this.aInstallerConfiguration.Feature.Code, "Uninstaller.exe"), RegistryValueKind.String);keyFeature.SetValue("VersionMajor", this.aInstallerConfiguration.Version.Major, RegistryValueKind.DWord);keyFeature.SetValue("VersionMinor", this.aInstallerConfiguration.Version.Minor, RegistryValueKind.DWord);keyFeature.Close();}else{isSuccessed = false;MessageDisplayer.DisplayError("无法创建注册表项目。");}keyUninstall.Close();}keyLocalMachine.Close();}catch (Exception ex){isSuccessed = false;MessageDisplayer.DisplayError("写入注册表信息发送错误。", ex.Message);}return isSuccessed;
}

只要写入了这些信息,就可以在系统的“添加/删除程序”列表中看到你的程序了,恩,不错!


12 应用程序的卸载

应用程序的卸载相对来说就简单一些了。主要是读取安装时生成的反安装配置文件,删除复制的文件、创建的快捷方式、卸载服务,字体以及框架可根据需要决定是否卸载。需要注意的是在删除快捷方式时路径的处理,不要把整个桌面文件夹或者程序组文件夹都给删掉了,这样就麻烦了,刚开始测试的时候,没有考虑周全,结果把整个程序组都给删掉了,点击“开始”->“所有程序”,所有程序组都没了,还好是在虚拟机中进行的测试。


13 总结

本文初步实现了一个可用的安装程序,探究了安装程序的一些行为。可供感兴趣的朋友继续在此基础上深化提高。例如实现多个程序功能同时安装,支持在安装前检查程序是否安装,如果已安装则提供修复和重安装选项等。


14 代码下载及版权说明

(1)完整代码我已经放置在CSDN的下载网站上(下载链接),有兴趣的朋友可以下载自己修改。其中的Data.zip文件我只放了图标文件,其他的文件均为0字节的文件,如果你需要测试安装程序,可将文件自行替换。

(2)对于修改和使用下载代码对系统造成异常或任何其他损失,本文作者不负连带责任。

(3)转载本文和使用下载的代码请注明出处以尊重作者的劳动。本文部分代码为微软MSDN的示例代码,在代码中使用了DotNetZipLib组件,请遵守各自的License。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Java解析AndroidManifest文件获取包名入口点权限等信息

    Java解析AndroidManifest文件工具类,输入XML文件路径得到该APK的包名、入口Activity、所有权限列表、活动列表。函数说明:xmlHandle()XML解析入口方法,解析权限、活动列表findPackage()解析包名信息findLaucherActivity()解析入口Activity通过调用xmlHandle()方法可以直接完…...

    2024/4/17 12:36:25
  2. C# 搭建一个简单的WebApi项目

    WebApi相关文章:C# 搭建一个简单的Web API项目 C# WebApi 路由配置 c# WebApi之解决跨域问题:Cors c# WebApi之身份验证:Basic基础认证 c# WebApi之接口返回类型详解一、创建Web API 1、创建一个新的web API项目 启动VS 2013,并在“开始页”选择“新项目”。或从“文件”菜…...

    2024/4/17 12:36:00
  3. 【前端教程】给网站添加暗黑模式指南

    导读:给网站添加暗黑模式是随着macOS中的暗黑模式(Dark Mode)出现之后的一个热门话题。社区中有关于这方面的讨论也很多,都在围绕着怎么给网站添加暗黑模式。今天在这篇文章再次和大家一起聊聊这个已久的话题,不同的是,这篇文章将和大家从不同的角度来聊怎么给网站添加暗…...

    2024/4/23 21:06:27
  4. Eclipse 打jar包,并且在win7 下执行jar文件,MANIFEST.MF文件指定执行入口

    1:为即将导出的java项目新增一个文件,文件名为:MANIFEST.MF这个文件可以定义jar包的一些参数,主要是jar文件执行入口; 文件内容为: Manifest-Version: 1.0 Class-Path: . Main-Class: test 将test修改为你的执行入口,2:右键项目 -- > export -- > jar file -- &g…...

    2024/4/18 7:29:03
  5. 个人整理:关于汉字拼音首字母方面的查询

    一,利用SQL的自定义函数来实现查询 1,在SQL中建立自定义函数 create function MyTest(@str nvarchar(4000)) returns nvarchar(4000) as begin declare @strlen int,@re nvarchar(4000) declare @t table(chr nchar(1) collate Chinese_PRC_CI_AS,lette…...

    2024/4/17 12:35:30
  6. 第一个微信小程序

    申请帐号进入小程序注册页 根据指引填写信息和提交相应的资料,就可以拥有自己的小程序帐号。在这个小程序管理平台,你可以管理你的小程序的权限,查看数据报表,发布小程序等操作。登录 小程序后台 ,我们可以在菜单 “设置”-“开发设置” 看到小程序的 AppID 了 。小程序的…...

    2024/4/20 7:02:01
  7. 【laralve项目】@17 laravel-admin实现三级联动

    laravel-admin实现三级联动实现效果素材实现步骤访问 实现效果素材 git地址:https://github.com/fangkang7/laravel.git 实现步骤 1.创建goods控制器2.主要针对修改的方法 对下面方法解释 options是用来获取所有的分类数据 其中load(‘category_2’, url(‘admin/api/category…...

    2024/5/4 8:28:53
  8. 通过【Windows10安装程序---MediaCreationTool】来制作并安装Win10正式版

    由于“Windows10安装程序”由微软官方推出,因此拥有可靠性和广泛使用性。对此我们需要从微软官方网站下载名为“MediaCreationTool”的“Windows 10安装程序”来升级Win10正式版系统。 一、通过MediaCreationTool.exe制作优盘win10系统盘 1、打开微软win10官方网站 win10下载官…...

    2024/4/23 10:53:24
  9. 当添加对MEF插件项目的引用时,为什么会出现警告图标?

    本文翻译自:Why do I get a warning icon when I add a reference to an MEF plugin project? I wish to test the core class of a plugin by directly referencing the plugin project and instantiating the plugin class. 我希望通过直接引用插件项目并实例化插件类来测试…...

    2024/4/20 4:36:18
  10. URL 编码 解码 空格变加号 加号变空格 解决

    http://reason2003.iteye.com/blog/719682http://zoumeili.blog.163.com/blog/static/207322112201242423620758/URL传参中加号变空格的解决方案URL传参加号变空格的问题,在页面中把+号替换为% 2B可以得到解决 str = str.replace(“+”,“% 2B”); 说明:%和2之间是没有空格的,…...

    2024/5/4 8:23:38
  11. Elasticsearch Java API(十三)--Java API获取分词结果

    需求Java API获取Elasticsearch的分词结果.版本Elasticsearch 5.4 已安装ik分词器测试先创建一个索引:curl -XPUT localhost:9200/bbb返回结果:{"acknowledged":true,"shards_acknowledged":true }好了,现在es里面有一个bbb的索引了。Java代码:标准分…...

    2024/4/18 7:46:24
  12. php url编码与解码(加/解密)

    base64_encode将字符串以 BASE64 编码。语法:string base64_encode(string data);返回值:字符串函数种类:编码处理内容说明本 函数将字符串以 MIME BASE64 编码。此编码方式可以让中文字或者图片也能在网络上顺利传输。在 BASE64 编码后的字符串只包含英文字母大小写、阿拉伯数…...

    2024/4/11 14:53:24
  13. mysql连接数据库url

    mysql8.0以上的就用下面的链接和url吧 url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true driver: com.mysql.cj.jdbc.Driver...

    2024/4/17 12:36:19
  14. android编译library成arr文件时,AndroidManifest.xml文件报错

    本打算修改library工程下的AndroidManifest.xml文件中的东西,但是在修改完成以后再次打包发现会出现如下错误::utilslibrary:verifyReleaseResources error: invalid file path D:\test\eseplugin\utilslibrary\build\intermediates\manifests\aapt\release\AndroidManifest.…...

    2024/4/17 12:35:15
  15. 好用的编写Lua的Ide—LuaPerfect

    前言 在很久以前,写lua实际是一个很烦的事情 (虽然现在也是) ,那个时候只能用笔记本写,没有代码提示都不是什么问题,问题是,没有语法检查,没法断点调试,这对程序员来说简直就是噩梦… 这个帖子呢,是想给介绍一个还不错的Ide,叫LuaPerfect,好像是17年出的吧,我记得…...

    2024/5/4 8:36:44
  16. 利用Inno Setup制作补丁安装程序

    今天需要给之前打包的一个软件制作补丁的安装程序,因为当时并没有考虑到这个小软件会做补丁安装程序,所以当时并没有在注册表中记录这个软件的安装位置,不过幸好在环境变量中记录了与这个软件一起使用的数据库的位置(数据库与该软件在同一个目录下),所以将这个环境变量处…...

    2024/5/4 5:07:39
  17. C#学习(十三)——C# 使用 GDI+ 画图

    GDI - 图形设备接口(Graphics Device Interface) GDI是图形设备接口的英文缩写,主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形和图像输出。GDI的出现使程序员无需要关心硬件设备及设备正常驱动,就可以将应用程序的输出转化为硬件设备上的输出和构成…...

    2024/5/4 1:12:46
  18. k8s restful api 访问

    restful api访问k8s集群,增删改 查信息,做界面二次开发。 需要预先创建访问权限的配置。官网api文档https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/下面罗列部分apicurl -u admin:admin "https://localhost:6443/api/v1" -k curl -u admin:…...

    2024/4/18 17:53:44
  19. 人人商城二次开发 各个装修模块diypage的自定义样式

    addons/ewei_shopv2/plugin/diypage/template/mobile/default/index.html这是手机端入口的主页 在第45行加入{$diyitem[id]} 查看每个自定义模块对应的页面然后在这个模板目录下寻找到你需要修改的模板文件 addons/ewei_shopv2/plugin/diypage/template/mobile/default/templat…...

    2024/4/17 12:35:18
  20. vue 给url 中文参数 添加编码解码

    // 解码用 decodeURIComponent(str) // 编码用 encodeURIComponent(str) 转载于:https://www.cnblogs.com/dudu123/p/10278145.html...

    2024/4/19 13:27:25

最新文章

  1. SVM单类异常值检测

    SVM是一种广泛使用的分类器&#xff0c;通常用于二分类或多分类问题。然而&#xff0c;在异常点检测的场景中&#xff0c;我们通常会将数据视为一个类别&#xff08;即正常数据点&#xff09;&#xff0c;并尝试找到那些与正常数据点显著不同的点&#xff08;即异常点&#xff…...

    2024/5/4 8:50:01
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. C++ //练习 11.14 扩展你在11.2.1节练习(第378页)中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名和生日。

    C Primer&#xff08;第5版&#xff09; 练习 11.14 练习 11.14 扩展你在11.2.1节练习&#xff08;第378页&#xff09;中编写的孩子姓到名的map&#xff0c;添加一个pair的vector&#xff0c;保存孩子的名和生日。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#x…...

    2024/5/1 13:00:20
  4. promise.all方式使用

    romise.all( ).then( ) 处理多个异步任务&#xff0c;且所有的异步任务都得到结果时的情况。 比如&#xff1a;用户点击按钮&#xff0c;会弹出一个弹出对话框&#xff0c;对话框中有两部分数据呈现&#xff0c;这两部分数据分别是不同的后端接口获取的数据。 弹框弹出后的初…...

    2024/5/2 21:09:45
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/3 23:10:03
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/4 2:59:34
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57