Inno Setup指南 —— 教程索引

InnoSetup指南 —— 教程索引

鉴于VSCode都用inno setup打包了,个人就学习一下用inno做安装包的简易操作

用Inno做一个最简单的安装包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

[Setup]
AppName=HelloWorld
AppVersion=1.0.0.0
WizardStyle=modern
DefaultDirName={autopf}\HelloWorld
DefaultGroupName=HelloWorld
UninstallDisplayIcon={app}\HelloWorld.exe
Compression=lzma2
SolidCompression=yes
OutputDir=userdocs:Inno Setup Examples Output
OutputBaseFilename=HelloWorldSetup

[Files]
Source: "HelloWorld.exe"; DestDir: "{app}"

  • Inno Setup Compiler打开.iss文件,若安装时,关联了.iss文件,可以直接双击打开
  • 点击编译 Compile 按钮,HelloWorldSetup.exe安装包将生成到用户文档目录下的Inno Setup Examples Output目录中

Read More

Wix Toolset 本地化MSI中文安装包制作

Wix Toolset 本地化MSI中文安装包制作

本文针对Wix制作**中文安装包(.msi)**中的一些内容进行整理 —— 其他语言类似
本文采用Visual Studio构建安装包 —— 推荐,因为足够方便。
示例源码

设置项目Cultures属性

  • zh-CN添加到项目的Cultures属性中【可以添加多种语言支持,以分号 ; 分隔】。
    • 方式一:右键项目属性修改:
    • 方式二:直接修改.wixproj项目文件内容,添加如下代码:
1
2
3
4
5

<PropertyGroup>
<Cultures>zh-CN;</Cultures>
</PropertyGroup>

添加WixUIExtension.dll引用

Wix安装包中基本流程的本地化资源已经打包至WixUIExtension.dll,引用此文件,即可在安装流程中显示中文流程。

见官方代码:https://github.com/wixtoolset/wix3/blob/master/src/ext/UIExtension/wixlib/WixUI_zh-CN.wxl

修改ProductLanguage属性

此值设置与否,并不影响中文安装包打包,不过既然做中文包,就做的彻底一些。

ProductLanguage的默认值为1033,其为en-usLCID,将其值改为2052,即zh-cn

1
2
3
4
5
6
7
8
9

<Product Id="*"
Name="Jess.Sample.Setup.ChineseLocalization"
Language="2052"
Version="1.0.0.0"
Manufacturer="Test中文"
UpgradeCode="0045cd81-6128-4e08-b8c2-357327c5c9ba">
</Product>

设置PackageSummaryCodepage属性

MSI属性 详细信息显示中文

PackageSummaryCodepage属性设置为936

1
2
3
4
5
6

<Package InstallerVersion="200"
Compressed="yes"
SummaryCodepage="936"
InstallScope="perMachine" />

设置此属性的作用,是让MSI安装包的属性-详细信息显示中文。

如下图,若不配置SummaryCodepage作者信息显示不了中文:

当未配置SummaryCodepage时,作者信息显示 Test,而非 Test中文


奇葩史

WPF中登录窗口的跳转处理

WPF中登录窗口的跳转处理

在WPF应用设计中,常常需要在主窗口之前设置一个前置登录窗口,为此整理了一下可行的方案。
示例源码:Jess.Sample.LoginWindow

方案一:添加Program.cs,模仿Winform初始代码

不做整理,个人不推荐,既然用了WPF,就尽量不把Winform的东西引入进来。

方案二:在主窗体MainWindow的构造函数中处理登录操作

此方案相对局限,若单纯只有个登录操作,可以使用此法 —— 可依据需要调整InitializeComponent();的先后顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public partial class MainWindow : Window
{
public MainWindow()
{
WindowLogin win = new WindowLogin();
if (!(win.ShowDialog() ?? false))
{
App.Current.Shutdown();
return;
}

InitializeComponent();
}
}

方案三:重写ApplicationOnStartup方法

注意事项:

  • OnStartup中关闭窗体,会导致进入不了主窗体,因此需要手动控制ShutdownMode【默认值:ShutdownMode.OnLastWindowClose
  • 不得在App构造函数中,进行UI显示操作,因为这会导致一些锁的释放,引发OnStartup提前触发【OnStartup是在Application的构造函数中异步调用的】

官方Window.cs源码的窗体关闭流程中有一段以下代码:

1
2
3
4
5
6
7

if (((App.Windows.Count == 0) && (App.ShutdownMode == ShutdownMode.OnLastWindowClose))
|| ((App.MainWindow == this) && (App.ShutdownMode == ShutdownMode.OnMainWindowClose)))
{
App.CriticalShutdown(0);
}

由此可见,默认情况下,在OnStartup中调用关闭窗体,会直接导致Shutdown的调用。


鉴于此,主要有以下修改方式【只要避免内部调用Shutdown即可】:

保证登录窗体关闭时,不会满足Shutdown调用条件

  • 全局自己手动控制Shutdown:使用ShutdownMode.OnExplicitShutdown,在需要退出应用时,调用App.Current.Shutdown();
  • 在登录窗体前后,切换ShutdownMode:登录前改为ShutdownMode.OnExplicitShutdown,窗体关闭后,恢复为ShutdownMode.OnLastWindowClose
  • 控制只有主窗体关闭时,才退出程序:指定MainWindow,手动启动MainWindow,不在依赖StartupUri

全局手动控制Shutdown

  • 设置ShutdownMode为OnExplicitShutdown;
  • 在需要退出应用的窗体中,重写OnClosed方法,手动调用App.Current.Shutdown()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public partial class App : Application
{
public App()
{
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
}

protected override void OnStartup(StartupEventArgs e)
{
WindowLogin win = new WindowLogin();
if (!(win.ShowDialog() ?? false))
{
App.Current.Shutdown();
return;
}

base.OnStartup(e);
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
App.Current.Shutdown();
}
}

窗体显示前后切换ShutdownMode

  • 直接在登录窗体前后设置ShutdownMode即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
WindowLogin win = new WindowLogin();
if (!(win.ShowDialog() ?? false))
{
this.Shutdown();
return;
}
this.ShutdownMode = ShutdownMode.OnLastWindowClose;

base.OnStartup(e);
}
}

指定App主窗体MainWindow

  • 删除App.xaml文件中是StartupUri属性设置。
  • 设置主窗体与程序关闭模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public partial class App : Application
{
public App()
{
this.MainWindow = new MainWindow();
this.ShutdownMode = ShutdownMode.OnMainWindowClose;
}

protected override void OnStartup(StartupEventArgs e)
{
WindowLogin win = new WindowLogin();
if (!(win.ShowDialog() ?? false))
{
this.Shutdown();
return;
}

base.OnStartup(e);

this.MainWindow.Show();
}
}

补充说明

在上面前两种的OnStartup的处理中,有个隐藏的问题,他们仍然会去创建MainWindow。如果想避免初始化MainWindow,有以下两种方式:

  • 在调用Shutdown之后调用Environment.Exit(0);,强制退出。
  • 重写ApplicationOnNavigating方法,设置事件参数的Cancel属性。
1
2
3
4
5
6
7

protected override void OnNavigating(NavigatingCancelEventArgs e)
{
e.Cancel = true;
base.OnNavigating(e);
}


掘:奇葩史

.Net中使用protobuf序列化:Google.Protobuf vs protobuf-net

旧地址:https://www.shisujie.com/blog/Google-ProtoBuf-vs-ProtoBuf-Net

Protocol Buffers - Google官方教程文档
示例源代码

什么是protobuf?

protobuf全称Protocol Buffers,由Google推出的一种平台、语言无关的数据交互格式。

为什么用protobuf

由于公司项目开发中,使用了WCF,其默认数据序列化是DataContractSerializer,采用xml格式,考虑到性能提升,于是采用了protobuf。

比较选择

protobuf的.net实现主要有两个版本:

  • Google.Protobuf Google.Protobuf
    • Google官方维护,项目前身是protobuf-csharp-port
    • 优势:在多平台协作开发时,定义一份协议,可以在各个语言中共用。
  • protobuf-net protobuf-net
    • 社区维护,主要由Marc Gravell管理维护
    • 优势:相对来说,更符合.net开发习惯;对于纯粹的.NET程序,使用起来更加方便

Google.ProtoBuf 如何使用

  • 安装Nuget包:Google.Protobuf和Google.Protobuf.Tools
1
2
3
4
5
6
7
8
9
10
11

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.12.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.12.3" />
</ItemGroup>
</Project>

  • 定义proto协议描述文件(.proto)

类似于.net中的类结构描述

1
2
3
4
5
6
7
8
9
10

syntax = "proto3";

package Jess.Sample.GoogleProtoBuf;

message Test {
int32 ID = 1;
string Name = 2;
}

  • 基于上面定义的proto文件,生成相应的C#类文件

命令行中执行如下代码:

1
2
3

<自己的protoc.exe文件目录>protoc.exe --proto_path=<proto文件所在目录> --csharp_out=<proto生成的cs文件目录> <定义的proto文件名>

或者参考如下powershell脚本内容

将脚本内容保存到项目目录下,直接执行ps1文件。

1
2
3
4
5
6
7
8
9
10

# 跳转到当前powershell脚本文件目录
$scriptpath = $PSScriptRoot
cd $scriptpath

# 获取用户目录
$userpath=$env:USERPROFILE
# 执行protoc生成类
&$userpath'\.nuget\packages\google.protobuf.tools\3.12.3\tools\windows_x64\protoc.exe' --proto_path=./protofile --csharp_out=./protoclass test.proto

  • 编写序列化相关代码
1
2
3
4
5
6
7
8
9
10
11
12
13

public void SimplestUse()
{
Test test_forProtobuf = new Test();
test_forProtobuf.ID = 2;
test_forProtobuf.Name = "测试 Google.ProtoBuf";

using (MemoryStream memoryStream = new MemoryStream())
{
test_forProtobuf.WriteTo(memoryStream);
}
}

更多使用示例,见本文开头源码链接。


protobuf-net 如何使用

  • 安装Nuget包:protobuf-net
1
2
3
4
5
6
7
8
9
10

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.0.29" />
</ItemGroup>
</Project>

  • 编写待序列化的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

using ProtoBuf;

namespace Jess.Sample.ProtoBufNet
{
[ProtoContract]
public class Test
{
[ProtoMember(1)]
public int ID { get; set; }

[ProtoMember(2)]
public string Name { get; set; }
}
}

  • 实现序列化方法
1
2
3
4
5
6
7
8
9
10
11
12
13

public void SimplestUse()
{
Test test_forProtobuf = new Test();
test_forProtobuf.ID = 1;
test_forProtobuf.Name = "测试 protobuf-net";

using (MemoryStream memoryStream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(memoryStream, test_forProtobuf);
}
}

更多使用示例,见本文开头源码链接。

Google.ProtoBuf 和 protobuf-net 性能比较

性能测试工具使用:BenchmarkDotNet

  • MemoryStream序列化

直接传入MemoryStream的WriteTo扩展方法,其实是官方内部封装了CodedOutputStream —— 从性能数据上看,也是无差别的。

1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Median Rank
Google.ProtoBuf使用MemoryStream 789.7 ns 15.72 ns 46.36 ns 775.3 ns 2
Google.ProtoBuf使用MemoryStream嵌套CodedOutputStream 799.6 ns 16.14 ns 46.81 ns 791.6 ns 2
protobuf-net使用MemoryStream 491.7 ns 7.13 ns 6.32 ns 488.8 ns 1
  • FileStream序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 460.3 μs 14.65 μs 43.18 μs 1
protobuf-net使用FileStream 457.9 μs 15.37 μs 45.31 μs 1
  • FileStream反序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 249.8 μs 4.97 μs 10.15 μs 1
protobuf-net使用FileStream 260.5 μs 4.84 μs 9.88 μs 2

上方是简易数据结构性能,下方是相对复杂一些的数据结构性能


注意事项【一些坑】:

  • proto3移除了required,对于required的作用,其实争议很大。proto3更加追求极简。
  • 但在我的之前遇到的情况,bool和enum未赋值的情况下,序列化时,默认值是有问题的。所以,单纯.net环境下,目前还是推荐proto2吧。
  • _:proto文件中下划线表示下划线后一个字母为大写。生成的类中,属性是无法带有下划线的。
  • 官方建议属性定义均用小写
  • FileStream序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 470.3 μs 17.45 μs 51.47 μs 1
protobuf-net使用FileStream 466.9 μs 14.68 μs 43.28 μs 1
  • FileStream反序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 281.3 μs 7.42 μs 21.88 μs 1
protobuf-net使用FileStream 283.1 μs 5.64 μs 13.29 μs 1

由此可见,两个库直接性能差距并不是很大,鉴于个人的使用场景,更加推荐protobuf-net。


掘:奇葩史