使用Visual Studio制作安装包

1 合并模块    3

1.1 SystemDll    3

1.1.1 收集文件    3

1.1.2 新建项目    4

1.1.3 增加自定义文件夹    4

1.1.4 设置部署位置    6

1.1.5 设置部署条件    6

1.1.6 卸载时不删除    8

1.1.7 编译    9

1.2 Post    9

1.2.1 新建项目    9

1.2.2 增加安装位置固定的文件    9

1.2.3 增加可重定位文件    11

1.2.4 编译    12

1.3 MapX    12

1.3.1 注册COM组件    12

1.3.2 注册表    13

1.3.3 编译    15

2 主安装项目    16

2.1 新建项目    16

2.2 增加合并模块    16

2.2.3 传递变量    18

2.3 增加应用程序文件    19

2.4 创建快捷方式    20

2.4.1 主程序快捷方式    20

2.4.2 "卸载程序"快捷方式    21

2.4.3 快捷方式的问题    22

2.5 详述项目属性    23

2.5.1 AddRemoveProgramsIcon属性    25

2.5.2 InstallAllUsers属性    26

2.5.3 Localization属性    26

3 其它    28

3.1 MSI API    28

3.1.1 MD5    28

3.1.2 共享文件    28

3.1.3 查看文件ComponentId    29

3.1.4 获取文件安装目录    30

3.2 exemsi    31

3.3 vdproj文件格式    31

3.4 脚本    32

3.5 点评    32

3.5.1 优点    32

3.5.2 缺点    33

 

1 合并模块

合并模块用来简化重复的打包行为。举例说明:假如软件AB均需要使用MapX控件,那么制作AB的安装包时就需要把MapX控件所需的文件也打包至安装包内。这个过程虽然简单,但是因为相关文件比较多且需要改写注册表,因此这是一项耗时、繁琐的重复性工作。把MapX制作成合并模块,制作AB安装包时直接使用这个合并模块,是一个高效、省心的方法。

本章将制作三个合并模块,它们的名称及特点如下:

合并模块

SystemDll 

文件安装位置固定;有部署条件;卸载时不删除

Post 

文件安装位置一部分固定一部分可重定位

MapX 

文件安装位置可重定位,根据重定位目录改写注册表

注意:经笔者测试发现,使用Visual Studio 200220032008制作的合并模块是有BUG的:由这些模块制作而成的安装包,在Windows XP下安装时不能修改安装目录。Visual Studio 20052010制作的合并模块,不存在这样的BUG

1.1 SystemDll

SystemDll里包含了MFC程序运行时需要的文件,如:mfc42.dllmsvcrt.dll……这些文件将被安装到电脑的System目录下,供所有MFC程序共用。

1.1.1 收集文件

同样的mfc42.dll文件,在Windows 98Windows 2000Windows XP……是不同的。如果从Windows XPSystem目录下(一般位于C:WindowsSystem32)获得mfc42.dll,然后直接安装到Windows 98System目录下(一般位于C:WindowsSystem),这个文件很可能是不能用的。因此,需要针对各种操作系统,分别搜集mfc42.dll

Windows 2000 及其以上版本的 Windows,系统自带了诸如mfc42.dllmsvcrt.dll……这些常见的文件,搜集起来很容易,直接到C:WindowsC:WinNT目录下查找,然后复制出来即可。

Windows 98 就比较麻烦了,系统没有自带这些常用的文件。只能安装VC++6.0或其它一些软件之后,到C:Windows目录下查找,然后复制出来。

建立三个文件夹:win98win2000winXP,将ATL.dllmfc42.dllmsvcrt.dlloleaut32.dll……从Windows 98Windows 2000Windows XP里复制出来,粘贴到各个文件夹内。

1.1.2 新建项目

使用VS2008新建一个名为SystemDll的"安装和部署"项目,如下图所示:

1.1 新建合并模块

1.1.3 增加自定义文件夹

鼠标右键单击"目标计算机上的文件系统",在弹出菜单中,单击【添加特殊文件夹】下的【Custom 文件夹】

1.2 增加自定义文件夹

VS2008显示如下。请输入文件夹名称,如:win98,然后按下Enter键。win98文件夹就被创建出来。

1.3 创建自定义文件夹

使用相同的方法再创建win2000winXP文件夹,最后显示如下:

1.4 添加文件夹后的显示

再把 Windows 98Windows 2000Windows XP 需要的文件分别增加到上图的 win98win2000winXP 文件夹下。具体操作:使用鼠标把文件或文件夹从资源管理器里拖动至上图右边的窗格内。

1.1.4 设置部署位置

我们希望win98win2000winXP这三个文件夹里的文件将被安装到目标电脑的System目录下。以win98文件夹的设置为例,其步骤如下:

鼠标左键单击win98文件夹,然后设置DefaultLocation属性为[SystemFolder]

1.5 设置部署位置

使用相同的方法,设置win2000winXP文件夹的部署位置。

1.1.5 设置部署条件

win98下的文件只能安装到Windows 98 Windows Mewin2000 下的文件只能安装到 Windows 2000winXP 下的文件只能安装到 Windows XP 及其以上版本的 Windows。这些需要设置部署条件。

鼠标左键单击win98文件夹,设置Condition属性为 Version9X >= 410。这样win98下的文件只能被部署到Windows 98Windows Me下。

1.6 设置win98部署条件

请设置win2000Condition属性为 VersionNT = 500。请设置winXPCondition属性为 VersionNT >= 501

需要说明的是:Version9XWindows 9X的版本号,VersionNTWinNT的版本号,它们都等于主版本号乘以100再加上次版本号。具体见下表:

操作系统

主版本

次版本

Version9X

VersionNT

Windows 95 

4 

0 

400 

 

Windows 98 

4 

1998

2398

 

Windows 982

4 

10

410 

 

Windows Me 

4 

90 

490 

 

Windows NT3/NT4 

3/4 

未知

 

未知

Windows 2000 

5 

0

 

500 

Windows XP 

5 

1 

 

501 

Windows 2003 

5 

2 

 

502 

Windows Vista2008

6 

0 

 

600 

Windows 72008 R2

6 

1 

 

601 

需要说明的是,如果部署到Windows 7下,不要担心winXP文件夹下的文件会破坏Windows 7已有的文件。因为安装的时候,安装程序会比较文件的版本号,Windows XP文件的版本号比Windows 7文件的版本号低,因此不会覆盖。

1.1.6 卸载时不删除

System目录下的文件是公用的。卸载程序的时候,如果把mfc42.dll这些文件也删除掉,会导致其它程序不能运行。所以在卸载程序的时候是不能删除这类文件的,具体的设置如下:

鼠标左键单击win98文件夹,然后全选该文件夹下所有文件,最后设置这些文件的PermanentTrue。这样在卸载程序的时候,这些文件就不会被删除了。使用相同方法设置win2000winXP下的所有文件的Permanent属性。

1.7

注意:在Windows 2000及其以上版本的WindowsSystem目录下的文件是有保护机制的。如:在Windows XPC:WindowsSystem32目录下搜索mfc42.dll,你会发现有两个这样的文件,如下图所示。如果有安装程序破坏、删除了mfc42.dllWindows系统会根据C:WindowsSystem32dllcache下的备份恢复。

1.8 文件保护机制

1.1.7 编译

选择解决方案配置为Release,然后单击【生成】菜单下的【生成解决方案】或【重新生成解决方案】菜单项。在Release目录下将生成SystemDll.msm

1.2 Post

Post模块的文件分为两大类:一类文件要安装到Common Files目录下,即安装位置固定;另一类文件要和主程序(exe文件)在同一文件夹下,这类文件被称之为可重定位文件。

1.2.1 新建项目

使用VS2008新建一个名为Post的"安装和部署"项目,如下图所示:

1.9 新建合并模块

1.2.2 增加安装位置固定的文件

新建项目后,可以看到下图所示的"文件系统"。如果看不到,请使用鼠标左键单击VS2008的【视图】【编辑器】【文件系统】菜单项。

Post模块的文件中,有一部分需要安装到Common Files目录下。一般情况下,这个目录位于C:Program FilesCommon Files。但也有特殊情况,如:系统盘为DWindows XP,这个目录就是D:Program FilesCommon Files。再比如:64Windows 7下,32位程序的Common Files目录就是C:Program Files (x86)Common Files

首先,需要添加Common Files目录,其操作如下图所示:鼠标右键单击"目标计算机上的文件系统",在弹出菜单中单击【添加特殊文件夹】下的【Common Files 文件夹】菜单项

1.10 添加Common Files 文件夹

下图中,可以看到Common Files 文件夹已经被添加。请在这个文件夹里添加所需的文件和文件夹。具体操作:使用鼠标把文件或文件夹从资源管理器里拖动到下图右边的窗格内。

1.11 添加Common Files文件夹之后的显示

1.2.3 增加可重定位文件

下图的"Module Retargetable文件夹"表示该文件夹下的文件安装到电脑上的路径不固定,是可以重新定位的。

1.12 文件系统

Post模块所需的文件中,需要和exe文件在一起的文件可以放在这个"Module Retargetable文件夹"下。具体操作为:鼠标右键单击左窗格的"Module Retargetable文件夹"。在弹出菜单中,单击【添加】【文件】(或【文件夹】)将需要的文件添加进来即可。

1.13 增加文件或文件夹

也可以通过鼠标将文件或文件夹从资源管理器里拖放到上图的右窗格内,这样最简单。增加文件后,显示如下:

1.14 增加文件后的显示

1.2.4 编译

选择解决方案配置为Release,然后单击【生成】菜单下的【生成解决方案】或【重新生成解决方案】菜单项。在Release目录下将生成Post.msm

1.3 MapX

使用VS2008新建一个"合并模块项目",项目名称为MapX。把需要部署的文件增加进来。

1.3.1 注册COM组件

MapX 5.00.30的安装文件中,有两个COM组件需要注册:MapX50.dll需要用 regsvr32.exe 注册;mdatasetint.tlb是类型库,需要用 regtyplib.exe 注册。

对于MapX50.dll请选择注册方式为 vsdrfCOMSelfReg

对于mdatasetint.tlb请选择注册方式为 vsdrfCOM

1.15 注册属性

事实上,把文件增加进来的时候,VS2008一般情况下都能很好的判断出使用何种注册方式。不过一旦安装时发生组件注册错误的时候,请认真考虑该如何设置这一项。

1.3.2 注册表

安装MapX控件需要修改注册表。在本机上安装MapX之后,运行regedit.exe,进入注册表编辑器。导出HKEY_LOCAL_MACHINESOFTWAREMapInfoMapX5.0至一个reg文件。

1.16 注册表

VS2008里,单击【视图】【编辑器】【注册表】

1.17 打开安装项目的注册表

可以看到将要部署到目标计算机上的注册表项目:

1.18 安装项目的注册表

鼠标右键单击"目标计算机上的注册表",在弹出菜单里单击【导入】菜单项,将本机导出的reg文件导入。这样就省去了一项一项增加注册表项目的麻烦。

往注册表里写入的内容并不是固定不变的。下面标有下划线、粗体部分是MapX50.dll的安装目录。

[HKEY_LOCAL_MACHINESOFTWAREMapInfoMapX5.0]

"CommonDLLDir"="C:\Program Files\Dacel\GISMapper Office\MapX 5.0"

"GeoDictionary"="C:\Program Files\Dacel\GISMapper Office\MapX 5.0\GeoDict.DCT"

"ProgramDir"="C:\Program Files\Dacel\GISMapper Office\MapX 5.0"

"SearchPaths"=""

"VersionCode"="5.01"

问题是:现在无法知道MapX50.dll的安装目录,怎么办?请使用一个变量来代替吧,如:MAPXPATH,然后将上面下划线部分替换为[MAPXPATH]。具体的就是修改下图中CommonDLLDirGeoDictionaryProgramDirValue属性,修改结果如下图所示。

1.19 注册表里使用变量

注意:变量名MAPXPATH里不能有小写字母。

至于安装程序如何将MapX50.dll的安装路径传递给MAPXPATH,后面的章节将做说明。

1.3.3 编译

选择解决方案配置为Release,然后单击【生成】菜单下的【生成解决方案】或【重新生成解决方案】菜单项。在Release目录下将生成MapX.msm

 

2 主安装项目

这一章将搭建主安装项目,假定其名称为GMO

2.1 新建项目

主安装项目的项目类型为"安装项目",如下图所示:

2.1 新建项目类型

编译这种类型的项目,生成的将是最终的安装程序(*.msiSetup.exe)。

2.2 增加合并模块

2.2.1 增加

鼠标右键单击GMO项目,弹出菜单里单击【添加】【合并模块】,然后将SystemDll.msmPost.msmMapX.msm分别添加到GMO里。

2.2 添加合并模块

2.2.2 重定位

Post.msm里有一部分文件需要重定位,其操作步骤如下:

新建自定义文件夹Post,设置其DefaultLocation属性。如下图所示。

2.3

设置合并模块Post.msm的属性,指定可重定位文件夹的具体位置,如下图所示。如果Post.msm有多个可重定位文件夹,则MergeModuleProperties里也会有多个对应的文件夹。

2.4 设置Post.msm属性

现在,Post.msm里的可重定位文件通过自定义文件夹Post,将被安装到[TARGETDIR]Bin

同样的方法也可以重定位MapX.msm里的文件至自定义文件夹MapX

2.2.3 传递变量

MapX.msm在修改注册表时需要变量MAPXPATH,此变量是MapX的重定位目录,其设置界面如下:(文件夹MapXMapX.msm的重定位文件夹)

2.5 MapX文件夹属性

DefaultLocation属性就不用解释了。Property属性是关键:安装程序会将DefaultLocation的实际路径传递给Property属性指定的变量里。这里就是将[TARGETDIR]MapX5.00.30这个目录传给变量MAPXPATHMapX.msm这个模块里根据MAPXPATH修改了注册表。也就是说:主安装程序是通过Property属性给合并模块传递变量的。

2.3 增加应用程序文件

将应用程序所需文件加入到 GMO 安装和部署项目中来,如下图所示。

2.6 增加应用程序文件

BCGCBPro 界面库所需文件可单独放到一个Custom文件夹内(上图中的BCG文件夹),然后指定该文件夹的DefaultLocation属性为[TARGETDIR]Bin,这样这些文件就会和主程序文件一起被安装到目录[TARGETDIR]Bin

主程序文件及相关的一些文件,可以增加到上图中的"应用程序文件夹",它的DefaultLocation属性就是默认的安装目录,即TARGETDIR。应该对其进行设置,如下图所示:

2.7 应用程序文件夹的属性

上图中的ProgramFilesFolder表示程序目录,一般就是C:Program FilesProductName表示软件产品名称,这个是主安装项目的一个属性,是可以设置的。

2.4 创建快捷方式

2.4.1 主程序快捷方式

现在要在开始菜单中,增加主程序的快捷方式。其操作步骤如下图所示:鼠标左键单击"用户的"程序"菜单"。然后鼠标右键单击右窗格,在弹出菜单中单击【创建新的快捷方式】菜单项。

2.8 创建快捷方式

VS2008显示如下界面,请在这个界面里选择"应用程序文件夹"下的Bin文件夹里的主程序文件GMOffice.exe,然后单击"确定"按钮。

2.9 选择快捷方式的目标文件

请修改这个快捷方式的NameIcon属性,如下图所示:

2.10 快捷方式属性

2.4.2 "卸载程序"快捷方式

现在要增加一个卸载程序的快捷方式,用户单击它之后就可以卸载GMO了。它的原理就是调用程序 msiexec.exe,并给这个程序传入参数:/x[ProductCode]MSI安装程序为每个软件都起了一个独一无二的产品代码(ProductCode),如:msiexec.exe /x{34DB9720-A27B-4F57-8057-13438DB86BD5}将卸载产品代码为{34DB9720-A27B-4F57-8057-13438DB86BD5}的软件。

主安装程序里,有这个ProductCode。鼠标左键单击解决方案资源管理器中的项目名称,在属性窗口将能看到ProductCode属性:

2.11 ProductCode属性

既然要用到程序msiexec.exe,就要将其加入安装项目。需要注意的是:不能增加 Windows XP msiexec.exe,而应该增加 Windows 98 系统目录下的msiexec.exeWindows的向下兼容使得这个程序能在多数Windows下运行。

创建一个快捷方式,其操作步骤请参考上一节内容。配置这个快捷方式的属性如下:

2.12 "卸载程序"快捷方式的属性

2.4.3 快捷方式的问题

一个不幸的消息是:VS2008制作的安装包在安装程序后,快捷方式是有问题的:一旦被安装的文件发生了改变,它就会再运行一次安装包进行修复。如果安装包被删除了,这个快捷方式根本无法运行!

解决方法就是使用OrcaMis程序,修改msi文件的快捷方式(Shortcut)的目标(Target)属性。如:卸载程序的Target被更改为[TARGETDIR]msiexec.exe。如下图所示:

2.13 修改msi文件的Shortcut

2.5 详述项目属性

鼠标左键单击解决方案资源管理器中的项目名称,在属性窗口将能看到该项目的属性,如下图所示:

2.14 安装项目属性

在资源管理器里,可以查看msi文件的属性。上图的TitleAuthorKeywordsDescription分别对应下图的标题、作者、关键字、备注。

2.15 msi文件属性

2.5.1 AddRemoveProgramsIcon属性

通过"控制面板"下的"添加或删除程序"也可以删除安装好的 GISMapper Office。这个属性是用来设置下图中的图标的:

2.16 删除程序里的图标

2.5.2 InstallAllUsers属性

Windows NT 下安装程序,快捷方式的位置有两种选择:

2.17 InstallAllUsers属性

如果选中了"只有我",则快捷方式将被创建在如下目录。???表示安装程序时登录Windows的用户名,这样这个快捷方式只有这个用户能看见。

C:Documents and Settings???「开始」菜单程序

如果选中了"任何人",则快捷方式将被创建在如下目录。这样这个快捷方式可以被所有用户看见。

C:Documents and SettingsAll Users「开始」菜单程序

InstallAllUsers属性为TRUE的时候,默认选择"任何人",否则选择"只有我"。

2.5.3 Localization属性

用来指定安装程序的语言属性。如果选为English,则安装程序显示的将是英文。这个属性为安装程序的国际化带来了便利。

3 其它

3.1 MSI API

3.1.1 MD5

函数MsiGetFileHash用来获得某个文件内容的MD5校验码。下面的代码将获得文件version.txtMD5校验码:

MSIFILEHASHINFO mfi;

memset(&mfi,0,sizeof(mfi));

mfi.dwFileHashInfoSize = sizeof(mfi);

if(ERROR_SUCCESS == MsiGetFileHash(_T("version.txt"),0,&mfi))

{

BYTE* pMD5 = (BYTE*)mfi.dwData;

//pMD5[0] 至 pMD5[15] 就是 16 个字节的 MD5

}

3.1.2 共享文件

假定两个安装包都将文件version.txt安装到C:Program FilesDacelMapTrack。卸载其中一个安装包时,version.txt不会被删除;只有两个安装包均被卸载时,version.txt才会被删除。那么,这个是如何实现的呢?答案在注册表里:

[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionInstallerUserDataS-1-5-18Components44F7C3AB826B666D93660073D058ACC1]

"01CFD0D00BD3BA946B3D2DA73C557E70"="C:\Program Files\Dacel\MapTrack\Version.txt"

"34DB9720A27B4F57805713438DB86BD5"="C:\Program Files\Dacel\MapTrack\Version.txt"

44F7C3AB826B666D93660073D058ACC1是根据Version.txt文件内容生成的ComponentId(应该与MD5有关)。01CFD0D00BD3BA946B3D2DA73C557E7034DB9720A27B4F57805713438DB86BD5是两个安装包的ProductCode。注意字节顺序:01CFD0D0 0BD3 BA94 6B3D2DA73C557E70表示的ProductCode{0D0DFC10-3DB0-49AB-B6D3-D27AC355E707}

卸载安装包{0D0DFC10-3DB0-49AB-B6D3-D27AC355E707}时,"C:\Program Files\Dacel\MapTrack\Version.txt"的个数不为零,所以Version.txt不会被删除。当两个安装包均卸载后,"C:\Program Files\Dacel\MapTrack\Version.txt"的个数为零,Version.txt就会被删除。

注意:Windows98下没有UserDataS-1-5-18,即注册表项的位置为:[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionInstallerComponents44F7C3AB826B666D93660073D058ACC1]

3.1.3 查看文件ComponentId

具体如何根据文件内容(Version.txt)生成ComponentId44F7C3AB826B666D93660073D058ACC1),笔者目前并不清楚。不过可以使用Orca软件来查看。其步骤为:

首先用Orca打开安装包(*.msi)。在Property里可以查看到ProductCode

3.1

File里可以查看到Version.txtComponent

3.2

Component里,可以通过Component9ECA8F92E8E84175A7AF8D6C9FE87C3D)查到ComponentId,即{BA3C7F44-B628-D666-3966-00370D85CA1C},它对应的就是注册表里的44F7C3AB826B666D93660073D058ACC1(同样要注意字节顺序)。

3.3

3.1.4 获取文件安装目录

已知ProductCodeComponentId,可以通过查询注册表获得安装文件(Version.txt)的安装目录。不用自己编码去实现此功能,直接调用MsiGetComponentPath即可,其代码如下:

LPCTSTR szProduct = _T("{0D0DFC10-3DB0-49AB-B6D3-D27AC355E707}");

LPCTSTR szComponentId = _T("{BA3C7F44-B628-D666-3966-00370D85CA1C}");

DWORD dwSize = MAX_PATH;

TCHAR* szPath = new TCHAR[dwSize];

INSTALLSTATE nState = MsiGetComponentPath(szProduct,szComponentId

,szPath,&dwSize);

if(nState == INSTALLSTATE_LOCAL || nState == INSTALLSTATE_SOURCE)

{

AfxMessageBox(szPath);

}

else

{

AfxMessageBox(_T("未安装"));

}

delete[] szPath;

3.2 exemsi

使用VS2008编译生成的安装程序主要有两个,一个是msi文件,一个是exe文件。msi文件是Windows Installer的数据包,运行msi文件的实质是运行Windows Installer程序,然后打开msi文件进行安装。exe文件用于检查安装环境,如是否需要.NET framework 3.5?如果需要就调用.NET framework 3.5的安装程序进行安装。如果msi不需要其它的安装项,则exe文件是可有可无的。

3.3 vdproj文件格式

vdprojUTF-8编码的文本文件,可以使用记事本打开。

"VSVersion" = "3:800" 表示 Visual Studio 的版本。

每个变量都是 VARIANT 类型的,3 表示数据类型,具体含义见如下定义(定义节选自文件C:Program FilesMicrosoft Visual StudioVC98IncludeWTYPES.H):

enum VARENUM

{

    VT_I2    = 2,

    VT_I4    = 3,

    VT_R4    = 4,

    VT_R8    = 5,

    VT_CY    = 6,

    VT_DATE    = 7,

    VT_BSTR    = 8,

    VT_BOOL    = 11,

    VT_I1    = 16,

    VT_UI1    = 17,

    VT_UI2    = 18,

    VT_UI4    = 19,

    VT_I8    = 20,

    VT_UI8    = 21,

    VT_INT    = 22,

    VT_UINT    = 23,

};

"VSVersion" = "3:800" 中的 3 表示它是一个4字节的整数(VT_I4),其值为 800

Visual Studio 2002 的版本为 7.0,这个数值为 700

Visual Studio 2003 的版本为 7.1,这个数值为 710

Visual Studio 2005 的版本为 8.0,这个数值为 800

Visual Studio 2008 的版本为 9.0,这个数值应为 900,但它却是 800,也就是说vdproj对于VS2005VS2008是通用的。

3.4 脚本

有些打包软件如Install Shield提供了脚本语言,可以对安装过程进行控制。VS2008提供了自定义操作(Custom Actions)的功能。可以编写dllexeVB脚本、JAVA脚本来实现自定义操作。如果以后打包需要,可以进行深入的研究。具体请参考MSDN2008里的Custom Actions

3.5 点评

3.5.1 优点

1、操作比较简单;

2、通用性强,制作的安装包可以在各种Windows上运行;

3、切换安装语言比较方便;

4、对新的Windows支持比较及时,如:"Program Files (64 )文件夹";

5、通过合并模块实现了打包结果的复用。

3.5.2 缺点

1、快捷方式的问题。打包之后使用OrcaMis修改msi文件非常的不方便;

2、经笔者测试发现,使用Visual Studio 200220032008制作的合并模块是有BUG的:由这些模块制作而成的安装包,在Windows XP下安装时不能修改安装目录。Visual Studio 20052010制作的合并模块,不存在这样的BUG

3、将过多的信息写入注册表。msi安装程序会将每个安装文件都编一个GUID,安装时会将相关信息写入注册表;

4、编译速度有点慢。

原文地址:https://www.cnblogs.com/hanford/p/6164375.html