乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 基于Mono在Ubuntu下开发.Net Framework桌面应用

关于Mono

Mono项目是提供一个符合微软公共语言规范(Common Language Infrastructure, CLI)的跨平台虚拟机实现,不同于.Net Framework只在Windows平台上实现。

image

历史

Mono的创始人是Miguel de Icaza,微软在2001年把CLI和C#提交给了ECMA(ECMA是一个致力于推动行业范围内采用信息和通信技术的非特定供应商的国际标准组织)标准化ECMA335和ECMA334,比Java还早的标准化了.NET平台。

image

Miguel de lcaza看到了C#语言的优雅和高效率,Ximian内部对如何创建能有效提升生产效率的工具进行了大量的讨论,他们的目标是通过这些创建出来的工具让用户可以在更短时间内创建出更多的应用程序从而缩短开发周期和降低开发成本。de Icaza所在的Ximian公司在2001年7月开始启动一个名叫Mono Project的开放源码版本".NET"的开发项目,旨在使开发者能够编写同时在Windows和Linux上运行的.NET程序。并在2004年发布了第一个版本,Mono目前的最新版本是3.2,同时Mono还在不断地持续更新。

Mono一直是由de Icaza直接领导,2012年Novell公司被收购,Mono项目的管理已经移交给de Icaza所创立的一家新公司Xamarin,由其指引Mono的发展方向。现在Xamarin的职责是发展Mono,同时负责开发Xamarin.IOS和Xamarin.Android以及让开发人员使用这些产品所需的软件。

2016年,微软突然宣布收购移动工具开发商Xamarin,后者是位于美国加利福尼亚,据称微软收购Xamarin交易价格在4亿到5亿美元之间。因此,微软获得了著名的开源倡导者和开发人员Miguel de Icaza,2022年3月05日,Miguel de Icaza发推显示自己已从微软离职。

image

里程碑

2001年12月-2002年2月。

一个新的平台诞生了。与惠普、英特尔和其他公司一起,创建了ECMA-335标准,该标准定义了支持多种编程语言的公共语言基础结构,C#和Visual Basic .Net。F#于2007年晚些时候发布,但今天还有20多种.net语言。Visual Studio .net已发布,并将c#、VB、C++开发都包含在一个框中。这是第一个真正跨多种语言集成的IDE

Mono项目开始。CLI规范使其他人能够创建自己的.Net实现。尽管Microsoft仅为适用于Windows构建了第一个.Net框架,但该规范有意地可跨操作系统和芯片组移植。Mono项目开始由Miguel de Icaza牵头,目标是在Linux和类似Unix的平台上实现Microsoft新的.Net开发平台。后来,由Miguel de Icaza创办了Xamarin,专注于跨平台、本地、移动开发,并在Mono的基础上构建。这允许开发人员使用c#和.Net为iOS和Android构建应用程序。Unity游戏开发也从Mono中出现

2008年

Asp.Net MVC Web开发堆栈作为开源发布到CodePlex。这是微软第一个作为开源发布的应用程序开发框架。但是,基础运行时和编译器仍处于封闭状态。

2014年

天方夜谭的事情真的发生了,太阳从西边出来了。2014年初在微软的BUILD会议上,C#之父Anders Heillsberg在舞台上宣布了.Net编译器平台“Roslyn”的开源。11月下旬,.Net Core项目开始启动,对外公开。技术世界感到震惊,.Net社区感到兴奋。.Net Core是一个新的云原生实现.Net,适用于跨平台、超大规模服务以及小型物联网设备。它的目的是将.Net引入未来15年的计算。而社区也一直给予极大的支持.....。

2016年

Mono回家了。2016年初,微软终于收购了Xamarin,并将Miguel de Icaza引入开发者部门。Mono加入.Net基金会,并得到Microsoft的正式支持和贡献。微软社区正式与Mono社区汇合。

2017年

.Net Core 2.0发布。我们的跨平台和开源实现.Net终于通过跨多个操作系统和编辑器的统一工具支持向世界发布

2018年

WinForm和WPF宣布开源。在Microsoft Connect 2018中,微软宣布了Windows forms和WPF桌面框架的开源。此后,我们看到了不可思议的贡献和活动。社区现在有能力指导这些框架的方向。

2019年

.Net Core 3.0发布。.Net Core 3.0将Windows桌面工作负载带到.Net Core运行时,这将允许自包含exe、并行安装和更快的性能。Build 2019宣布.NET Core 3.0之后的下一个版本将是.NET 5。这将是.NET系列的下一个重要版本。将来只会有一个.NET,您将能够使用它来开发Windows,Linux,macOS,iOS,Android,tvOS,watchOS和WebAssembly等等。我们将在.NET 5中引入新的.NET API、运行时功能和语言功能。从.NET Core项目开始,我们已经向平台添加了大约五万个.NET Framework API。.NET Core 3.0弥补了.NET Framework 4.8的大部分剩余功能差距,支持Windows Forms,WPF和Entity Framework 6。.NET 5构建于此工作之上,利用.NET Core和Mono的最佳功能创建一个平台,您可以用于所有现代.NET代码

2020年

将在2020年11月发布.NET5,并在2020年上半年推出第一个预览版。将在Visual Studio 2019、Visual Studio for Mac和Visual Studio Code的未来更新中支持它。

界面移植

移植用户界面可能很简单,也可能很复杂。基于Windows Forms和GTK#的用户界面比较容易移植,基于WPF技术的用户界面就难以移植了,Mono不支持WPF。

image

XBuild

XBuild是开源的MSBuild实现,MSBuild全称(Microsoft Build Engine),是用来生成.NET程序的平台。MSBuild不仅仅是一个构造工具,应该称之为拥有相当强大扩展能力的自动化平台。MSBuild平台的主要涉及到三部分:执行引擎、构造工程、任务。其中最核心的就是执行引擎,它包括定义构造工程的规范,解释构造工程,执行“构造动作”;构造工程是用来描述构造任务的,大多数情况下我们使用MSBuild就是遵循规范,编写一个构造工程;MSBuild引擎执行的每一个“构造动作”就是通过任务实现的,任务就是MSBuild的扩展机制,通过编写新的任务就能够不断扩充MSBuild的执行能力。

MSBuild有四个基本块(属性、项、任务、目标):

  • MSBuild属性:属性是一些键/值对,主要用来存储一些配置信息。
  • MSBuild项:主要是存储一些项目文件信息,以及文件的元数据信息(属性、项、任务、目标)。
  • MSBuild任务:Build过程中的一些原子操作(如CSC、MakeDir)。
  • MSBuild目标:按特定的顺序将任务组织在一起,并允许在命令行单独指定各个部分。

安装MonoDevelop

关于MonoDevelop

https://www.monodevelop.com

image

MonoDevelop使开发人员能够在Linux、Windows和macOS上快速编写桌面和Web应用程序。它还使开发人员能够轻松地将用Visual Studio创建的.NET应用程序移植到Linux和macOS上,为所有平台保持一个单一的代码库。

在Ubuntu里面我们只需要安装MonoDevelop即可,它顺带就安装了Mono运行时环境。

添加MonoDevelop的包源

适用于 >= Ubuntu 18.04

sudo apt install apt-transport-https dirmngr
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/ubuntu vs-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-vs.list
sudo apt update

image

image

通过APT方式安装MonoDevelop

sudo apt-get install monodevelop

image

安装成功后,可在Show Applications里面找到它。

image

查看安装

安装完之后,我们前往/bin/目录看一下,发现,还是装了不少东西。

image

还有一个更加彻底的方式,就是根据关键词来全局搜索,这样可以找到所有包含它的位置,哪天你要彻底卸载,就可能需要它。

sudo find / -name 'mono'

image

备选删除命令

sudo rm xxxxxxxxxx
sudo rm -rf xxxxxxxxxx

实战演练

书写HelloWorld程序

打开Text Editor,往你们贴一段最简单的代码。

image

将其保存为hello.cs文件。

接下来,我们通过终端找到它的位置

image

我们先编译它

csc hello.cs

image

编译成功后,它会生成一个同名的hello.exe

image

然后基于Mono运行它

mono hello.exe

如果直接运行它,会发现没权限

image

我们可以给它加上权限

chmod +x hello.exe

接下来,我们就可以直接运行它了。

./hello.exe

image

创建HelloWorld项目

通过MonoDevelop我们也可以创建项目,打开之后,在Solution版块,点击New按钮开始创建。

image

在左侧Other分类里面找到.Net分组,然后这里先选中一个Console Project类型的项目模板,点击Next按钮开始创建。

image

输入一个自定义的项目名称,比如HelloWorld,点击Create按钮即可。

image

image

点击Build,可以Build成功,但是运行好像有点问题,无法启动调试。

image

image

不过不要紧,根据经验,这大概是因为执行权限不够,我们可以走命令行来执行下。

在项目上右键,有一个Open Containing Folder选项,可以打开项目所在的文件夹。

image

image

前往Bin目录下的Debug目录,右键再以Open in terminal打开。

image

这里我们可以先以如下命令来执行它。

mono HelloWorld.exe

image

给它添加权限。

chmod +x HelloWorld.exe

然后再直接运行。

./HelloWorld.exe

image

创建HelloWindow项目

在Windows中我们都知道通过创建Winform程序来做窗体程序,但是在Linux中,替换成GTK来实现它。

打开MonoDevelop之后,我们点击Solution功能块中的New按钮来创建新项目。

image

在左侧.NET分组中,找到并选择Gtk#2.0 Project项目模板,然后点击Next按钮。

image

取名为HelloGTK项目名,创建它

image

创建成功并打开解决方案。

image

运行成功。

image

跨平台运行试验

通过Linux产生的exe能否在Windows上直接运行呢?按原理来说,因为Mono和.Net Framework都是对CLI的实现,应该是可以的,我们来验证下。

由于Ubuntu是在Vmware里面跑的,所以我们先来给它加一个共享文件夹,以便我们可以把Linux上的文件拷贝到Windows上来。

image

设置之后,我们会看到在Ubuntu的mnt目录下,有个hgfs,里面就能看到我们共享的那个文件夹了,这里我的文件夹名为VisualizeSpace

image

接着我们可以手动复制粘贴,也可以通过命令指令cp来执行。

cp ./HelloWorld.exe /mnt/hgfs//VisualizeSpace/

代表把这个exe拷贝到后面这个文件夹路径中。

image

马上,它就进去了,接下来我们在Windows这边也看到它了。

image

右键,以Windows Terminal打开

image

以Windows的方式执行它,完美执行,这也就验证了前面的猜想。

.\HelloWorld.exe

image

创建HelloMono解决方案

https://github.com/TaylorShi/HelloMono

这时候我们在Windows中先创建一个名为HelloMono的解决方案。

dotnet new sln -o HelloMono

image

切换到HelloMono目录

cd .\HelloMono\
.\HelloMono.sln

image

添加名为demoForWinForm20的WinFroms项目,指定其框架为.Net Framework 3.5

image

给它做一些简单的处理,添加图标、设置默认窗体名称等等。

image

添加名为demoForFormClass20的Windows窗体控件库,指定其框架为.Net Framework 3.5

image

image

image

添加名为demoForShareClass20的共享项目

image

image

image

Git拉取项目

接下来,我们通过Git从Github上拉取Git代码。

由于Linux版本的Git一般不自带git-lfs模块,我们先安装下它。

sudo apt install git-lfs

image

配置好Git用户信息

git config --global user.name "your_name"
git config --global user.email <your_email>

生成一对新的Git密钥对

ssh-keygen -t rsa -C "your_email"

image

切换到生成后的目录并且打印出来

cd /home/taylorshi/.ssh/
cat id_rsa.pub

image

添加到Github中

image

验证下密钥

ssh -T git@github.com

image

拉取代码

git clone git@github.com:TaylorShi/HelloMono.git

很遗憾,我这里因为网络限制,22端口不通,那么我们只能改用HTTPS模式了。

git clone https://github.com/TaylorShi/HelloMono.git

image

这里如果要指定分支拉取,就用-b

git clone -b main git@github.com:TaylorShi/HelloMono.git

这里-b等同于--branch,用来指定分支。

这里如果要指定下载后的文件夹名称,还可以在后面跟一个自定义的目录名称。

git clone -b main git@github.com:TaylorShi/HelloMono.git MonoMain

打开和运行WinForm项目

打开MonoDevelop,在Solution版块,点击Open按钮,选中刚才下载的HelloMono中的sln文件,就可以打开之前创建的那个WinForm项目了。

image

image

还挺顺利,没有报错。

image

Debug下,直接跑起来了,看来对WinForm兼容性还不错。

image

打包

有些情况下,可能需要一个包管理器来统一管理Linux平台上应用程序的发布和安装。在Linux系统下,对于软件包的管理有多种机制,有源代码方式、RPM软件包管理方式以及YUM软件管理方式。

RedHat包管理器(RedHat Package Manager,简写RPM)是Linux上最常用的包管理器。RPM是一个基于命令行的管理工具,可以用来安装、卸载、验证、查询和更新软件包。在RPM开发出来之前,Linux上没有很方便的方法来管理软件程序的安装。那时软件程序是通过压缩的tar文件来安装的,而且唯一能够查到一个应用程序是否依赖于其他包的方法是阅读随程序一起发布的文档或者通过运行程序来得到错误提示信息。RPM的出现改变了这一切

RPM格式的应用程序使得软件程序的维护和管理变得容易了很多。RedHat也因此变成一个最流行的Linux发行版。今天,绝大多数的Linux发行版都把RPM当作默认的包管理器。也正因为此,我们建议在把应用程序移植到Linux上时,打包成RPM格式。

用RPM打包应用程序

RPM的核心是RPM引擎。该引擎需要一些输入文件,并输出打包后的应用程序。其中一个输入文件是spec文件,该文件包含一些提示性和指令性的信息,在打包过程中或用户安装后的应用程序时,分别会用到这些信息

该spec文件包括以下各节(section):

  • Preamble:包含了一些可供显示的关于该包的信息。
  • Prep:包含RPM开始实际打包前用到的脚本。
  • Build:包含打包过程用到的脚本。
  • Install:包含安装打包后的应用程序时用到的命令
  • Install和Uninstall:包含安装和卸载脚本,用户用RPM工具安装或卸载软件程序时,这些脚本会被分别执行。
  • Verify:包含一个在用户系统上验证软件包安装情况的脚本。
  • Clean:包含一个在软件打包后负责清理工作的脚本。
  • FileList:包含要打包的文件列表、安装到用户系统后的文件权限、文档和配置信息。

打包过程中用到的spec文件的节是prep、build、install、clean和filelist;向用户系统上安装打包后的软件包时,用到的节是install和uninstall、verify和filelist。

RPM打包的过程如下:

  1. 创建一个包含源文件的编译环境。在该环境中给源文件打补丁(如果需要的话),并把源文件编译成二进制程序。
  2. 运行install节的安装脚本,把编译好的二进制程序安装到正确的目录位置。
  3. 二进制程序安装完成后,用filelist节中的内容创建RPM包。需要注意的是RPM不会自己创建文件列表,需要手工创建文件列表。
  4. 打包完成后的输出就是一个RPM格式的软件包。

LSB推荐的基准打包格式

为了使RPM包能在所有的Linux发行版上安装和运行,LSB(Linux Standard Base)1.2规范对Linux上RPM包格式提供一个基准建议。该规范建议Linux上的RPM包遵循1997版本的Maximum RPM附录部分的规格说明以及一些额外的限制。这些现在可以在LSB网站上找到。

在Linux上,还有一个可用的包管理器是DEB。除了包格式不同外,Deb和RPM在很多方面都很相似。虽然几乎所有的Linux平台上最常见的是用RPM来打包应用程序,但在Linux Debian发行版上,是用DEB来打包应用程序的,RPM包在转换成DEB格式后,也可以安装在Debian Linux上。一个叫做alien的工具可以把RPM包转换成DEB包,也可以把DEB包转换成RPM包。

APT工具

Advanced Package Tool,又名apt-get,是一款适用于Unix和Linux系统的应用程序管理器。最初于1998年发布,用于检索应用程序并将其加载到Debian Linux系统。Apt-get成名的原因之一在于其出色的解决软件依赖关系的能力。其通常使用.deb-formatted文件,但经过修改后可以使用apt-rpm处理红帽的Package Manager(RPM)文件。

软件包管理是区分不同发行版的一大特征,如RedHat使用RPM软件包来管理软件,Debian使用Deb软件包来管理软件apt-getDebian的Deb软件包管理工具,它的最低底层还是调用dpkg包管理程序,通过apt-get工具可使我们很好地解决软件包的依赖关系,方便软件的安装和升级。

image

Most used commands:
  update - Retrieve new lists of packages
  upgrade - Perform an upgrade
  install - Install new packages (pkg is libc6 not libc6.deb)
  reinstall - Reinstall packages (pkg is libc6 not libc6.deb)
  remove - Remove packages
  purge - Remove packages and config files
  autoremove - Remove automatically all unused packages
  dist-upgrade - Distribution upgrade, see apt-get(8)
  dselect-upgrade - Follow dselect selections
  build-dep - Configure build-dependencies for source packages
  satisfy - Satisfy dependency strings
  clean - Erase downloaded archive files
  autoclean - Erase old downloaded archive files
  check - Verify that there are no broken dependencies
  source - Download source archives
  download - Download the binary package into the current directory
  changelog - Download and display the changelog for the given package
sources.list

要使用好apt-get就要配置好一个名为sources.list的资源列表,资源列表指向Debian系统的软件库,apt-get会从该软件库安装各种软件包。sources.list文件位于/etc/apt目录下,下面是Sarge、Etch和Sid三个版本的写法,你可任选一种,最好不要多版本混用:

#sources.list for Sarge(stable):
deb http://http.us.debian.org/debian stable main contrib non-free
deb http://non-us.debian.org/debian-non-US stable/non-US main contrib non-free
deb http://security.debian.org stable/updates main contrib non-free
#Uncomment if you want the apt-get source function to work
#deb-src http://http.us.debian.org/debian stable main contrib non-free
#deb-src http://non-us.debian.org/debian-non-US stable/non-US main contrib non-free

#sources.list for Etch(testing):
deb http://http.us.debian.org/debian testing main contrib non-free
deb http://non-us.debian.org/debian-non-US testing/non-US main contrib non-free
deb http://security.debian.org testing/updates main contrib non-free
#Uncomment if you want the apt-get source function to work
#deb-src http://http.us.debian.org/debian testing main contrib non-free
#deb-src http://non-us.debian.org/debian-non-US testing/non-US main contrib non-free

#sources.list for Sid(unstable):
deb ftp://ftp.us.debian.org/debian unstable main contrib non-free
deb ftp://non-us.debian.org/debian-non-US unstable/non-US main contrib non-free
#Uncomment if you want the apt-get source function to work
#deb-src http://http.us.debian.org/debian unstable main contrib non-free
#deb-src http://non-us.debian.org/debian-non-US unstable/non-US main contrib non-free

sources.list文件的内容决定了Debian的版本。安全更新只存在于stable和testing版中,unstable没有安全更新。进入stable的软件都经过严格的依赖测试和安全测试,所以如果你想系统稳定,用于工作,最好使用stable,如果你想使用最新版的软件,就使用testing或unstable。Woody、Sarge和Sid是Debian3.x三个版本中的代号,我们一般都是以代号来称
呼debian不同版本。所有Debian发行版的代号全都取自电影Toy Story,Woody是那个牛仔,Sarge是绿色塑胶军队的领导,Sid是破坏玩具的小孩。

apt-get update

image

更新软件包信息库。在Debian中,软件包是通过一个数据库来管理的,通过这个数据库中可跟踪你系统中已安装、没有安装和现在可安装的软件包信息。apt-get安装软件包时就是依靠这个数据库来解决软件包间的依赖关系,从而可自动安装相关软件。我们需定期运行该命令,从而保持数据库的信息为最新。

apt-get install package_name1 package_name2 package_name3...

安装软件包。如果软件包需其它软件包支持,apt-get会通过搜索软件包数据库找到这种依赖关系,一起下载相关软件。在一个命令行中可同时安装多个软件包,中间用空格隔开即可。安装的软件包默认会存放在/var/cache/apt/archives目录下,以便以后重新安装。

如果已安装的软件包损坏了,你可通过--reinstall选项来重新安装。如:#apt-get --reinstall install package_name

在需安装的软件包名后加一个减号会删除软件包,如:apt-get install package_name-。

只是下载软件,不解包和安装使用-d选项,如:#apt-get -d install package_name

使用--dry-run选项可使apt-get在安装软件包前进行测试,如:#apt-get install package_name --dry-run

Debian软件包的名字和软件名不同,所以在安装前如不知道软件包的名字,可到Debian的官方软件库查询,网址是:http://www.debian.org/distrib/packages/ 。或者用下面介绍的apt-cache search package_name命令来查询。

apt-get remove package_name1 package_name2 package_name3...

删除软件包。如果你想删除没用的软件包,只要使用该命令即可。如果你想把该软件的配置文件也删除,可以用--purge选项,如:#apt-get --purge remove package_name

类似地,在删除软件包名后加一个加号会安装软件包,如:apt-get remove package_name+。

apt-get source package_name1 package_name2 package_name3

下载软件包的的源码版本。

apt-get upgrade package_name1 package_name2 package_name3...

软件包升级功能是APT系统这么成功的主要原因。通过该命令,我们就可把软件升级到最版本。在使用该命令前,最好先运行apt-get update命令,以更新软件包数据库。但该方案不是更新系统最好的方法,一些包会因为包依赖问题而保留(kept back)一些旧的软件包。

Debian提供了一个更好的升级方案,就是用dis-upgrade。下面一节会详细介绍。

apt-get dist-upgrade

更新整个Debian系统。可从网络或本地更新整个系统。它会重新安排好包的依赖性。如果有些包由于一些原因实在不能更新,我们可通过以下命令查询原因:#apt-get -o Debug::pkgProblemResolver=yes dist-upgrade

apt-show-versions -u可获得可升级软件包的列表。该命令还有一些有用的选项,可用-h选项查看详细帮助,了解更多功能。

apt-get clean

删除下载了的软件包,当我们通过apt-get安装软件包时,APT会把软件包下载到本地/var/cache/apt/archives/目录。该命令会删除该文件夹内的除锁住外的所有软件包。

apt-get autoclean

删除已下载的旧版本的软件包。该命令类似于上面的命令,但它会有选择地删除旧版本的软件包。

apt-get dselect-upgrade

通过dselect的“建议”和“推荐”功能更新系统。dselect是Debian中一个功能强大的包管理工具。它可帮助用户选择软件包来安装,其中一个有用功能是它会建议和推荐安装其它相关软件包。我们可在APT中使用它这个功能。

apt-get check

检查系统中已安装软件包的依赖性。

常用的APT命令参数:

apt-cache search package 搜索包
apt-cache show package 获取包的相关信息,如说明、大小、版本等
sudo apt-get install package 安装包
sudo apt-get install package - - reinstall 重新安装包
sudo apt-get -f install 修复安装"-f = --fix-missing"
sudo apt-get remove package 删除包
sudo apt-get remove package --purge 删除包,包括删除配置文件等
sudo apt-get update 更新源
sudo apt-get upgrade 更新已安装的包
sudo apt-get dist-upgrade 升级系统
sudo apt-get dselect-upgrade 使用 dselect 升级
apt-cache depends package 了解使用依赖
apt-cache rdepends package 是查看该包被哪些包依赖
sudo apt-get build-dep package 安装相关的编译环境
apt-get source package 下载该包的源代码
sudo apt-get clean && sudo apt-get autoclean 清理无用的包
sudo apt-get check 检查是否有损坏的依赖

其中:

  1. 有SUDO的表示需要管理员特权!
  2. 在UBUNTU中命令后面参数为短参数是用“-”引出,长参数用“--”引出
  3. 命令帮助信息可用man 命令的方式查看或者命令-H(--help)方式查看
  4. 在MAN命令中需要退出命令帮助请按“q”键!!
选项 含义 作用
sudo -h Help 列出使用方法,退出。
sudo -V Version 显示版本信息,并退出。
sudo -l List 列出当前用户可以执行的命令。只有在sudoers里的用户才能使用该选项。
sudo -u username|#uid User 以指定用户的身份执行命令。后面的用户是除root以外的,可以是用户名,也可以是#uid。
sudo -k Kill 清除“入场卷”上的时间,下次再使用sudo时要再输入密码。
sudo -K Sure kill 与-k类似,但是它还要撕毁“入场卷”,也就是删除时间戳文件。
sudo -b command Background 在后台执行指定的命令。
sudo -p prompt command Prompt 可以更改询问密码的提示语,其中%u会代换为使用者帐号名称,%h会显示主机名称。非常人性化的设计。
sudo -e file Edit 不是执行命令,而是修改文件,相当于命令sudo edit。

通过MKBundle创建单体程序

Mono可以将.NET应用程序(可执行代码和它的依赖关系)转化为独立的可执行文件,不依赖于Mono在系统中的安装,从而简化了.NET应用程序的部署

这是通过mkbundle工具完成的,它是一个交叉编译工具,可以从一个初始的程序集入口点、它的.NET依赖项和任何你的应用程序需要的附加程序集中产生一个用于任何Mono支持的平台的本地可执行文件。

mkbundle是一种static linker方式,会将所用到的dll连同应用程序一并embed到一个exe文件中,实际上mkbundle后不再需要类库的dll。mkbundle使用Unix-like toolchain。

演练

先通过msbuild把项目重新编译一下:

msbuild -t:Rebuild -m:4 HelloMono.sln

image

想象一下,你有一个.NET应用程序,比如CacheServer.exe,你想把它发布到各个操作系统上,而不需要在那里安装Mono运行时。你的应用程序也有一些依赖性,如Ssh.dll

你可以这样捆绑这个应用程序:

$ mkbundle -o CacheServer --simple CacheServer.exe --machine-config /etc/mono/4.5/machine.config
Using runtime: /Library/Frameworks/Mono.framework/Versions/5.8.0/bin/mono
     Assembly: /private/tmp/CacheServer.exe
     Assembly: /private/tmp/Ssh.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/mscorlib.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/System.Core.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/System.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/Mono.Security.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/System.Configuration.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/System.Xml.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/System.Security.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/Mono.Posix.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/I18N.West.dll
     Assembly: /Library/Frameworks/Mono.framework/Versions/5.8.0/lib/mono/4.5/I18N.dll
machineconfig: /etc/mono/4.5/machine.config
Generated CacheServer

以上将生成一个本地可执行文件,它不依赖系统上安装的Mono,是一个本地可执行文件。

$ ls -l CacheServer
-rwxr-xr-x  1 miguel  wheel  16056828 Jan 26 10:23 CacheServer
$ file CacheServer
CacheServer: Mach-O 64-bit executable x86_64

接下里,我们来实践下。

mkbundle -o WinForm20.exe --simple demoForWinForm20.exe --machine-config /etc/mono/2.0/machine.config --no-config

这里根据我们示例程序依赖的版本来切换到对应的配置:/etc/mono/2.0/machine.config,同时这里必须要指示有还是没有config,暂时没有用到,所以先给一个--no-config,至于输出的文件,我们可以随意定义它的名称,比如这里,我就用WinForm20.exe这个名称,这个-o应该就是输出的意思,所以回车之后:

image

看到确实通过分析,找到了Mono下依赖文件,重新生成了一个我们要的WinForm20.exe文件。

ls -l
file WinForm20.exe

image

发现它的体积还是蛮大的,毕竟是把依赖都打进去了,在命令行中可以运行它,但是直接双击还运行不了,不知道咋回事。

image

./WinForm20.exe

image

这很好,我们现在有一个适用于MacOS的x86-64二进制文件。但是,如果你想在另一个平台上交叉编译,例如在AMD64或Raspberri Pi上运行的Ubuntu呢?

首先,让我们检查一下我们的机器上是否安装了必要的运行时。

$ mkbundle --local-targets
Available targets locally:
  default - Current System Mono
  4.8.0-linux-libc2.12-amd64.zip

image

看起来我们没有,所以我们需要得到它。

这个目标库的本地位置位于~/.mono/targets

image

我们维护了一个用于各种平台的Mono运行时的集合,你可以将其捆绑起来,为了得到这个列表,运行。

$ mkbundle --list-targets
mono-5.8.0-debian-7-arm
[ ... removed lines for brevity ... ]
mono-5.8.0-raspbian-9-arm
mono-5.8.0-ubuntu-16.04-arm64
mono-5.8.0-ubuntu-16.04-x64
mono-5.8.0-ubuntu-16.04-x86

这里执行会报错,暂时无解,有个Issue说这里:mkbundle --list-targets on windows fails with System.Net.WebException CERTIFICATE_VERIFY_FAILED,噢,误解了,看来还是能解决,接下来会讲。

话说原来,这个目录还是可以手动访问的:https://download.mono-project.com/runtimes/raw/

在3.12之前,Linux上的Mono默认不信任任何SSL证书,所以当你访问HTTPS资源时就会出现错误。现在不需要了,因为3.12及以后的版本包括一个新的工具,它在软件包安装时运行,并将Mono的证书存储与系统的证书存储同步(在旧版本中,你必须通过运行mozroots --import --sync导入Mozilla的可信证书列表)。有些系统的配置方式使其在安装Mono时不会拉入必要的软件包,在这种情况下,请确保ca-certificates-mono软件包已经安装。

发现想要解决它,就要完整的装一次Mono,这时候我们需要安装Mono-Complete才行。

sudo apt install mono-complete

顺利装完,结果就可以了。

image

在上面的列表中,你将挑选你想要的Mono运行时版本,在我们的例子中,我们想要使用mono-6.8.0-ubuntu-16.04-x64

所以用--fetch-target选项下载这些交叉编译器工具。

mkbundle --fetch-target mono-6.8.0-ubuntu-16.04-x64

安静的响应意味着这个工具成功了。Unix工具不喜欢大量的聊天,mkbundle也不打算打破这个传统。

image

让我们检查一下我们是否已经安装了它。

$ mkbundle --local-targets
Available targets locally:
  default - Current System Mono
  4.8.0-linux-libc2.12-amd64.zip
  mono-6.8.0-ubuntu-16.04-x64

image

现在,交叉编译的结果,改变mkbundle的调用,使用--cross命令行选项,它的参数是上面的目标名称,在我们的例子中是mono-6.8.0-ubuntu-16.04-x64

$ mkbundle -o CacheServer --cross mono-5.8.0-ubuntu-16.04-x64 CacheServer.exe --machine-config /etc/mono/4.5/machine.config
From: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64
Using runtime: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/bin/mono
     Assembly: /private/tmp/CacheServer.exe
     Assembly: /private/tmp/Ssh.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/mscorlib.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/System.Core.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/System.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/Mono.Security.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/System.Configuration.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/System.Xml.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/System.Security.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/Mono.Posix.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/I18N.West.dll
     Assembly: /Users/miguel/.mono/targets/mono-5.8.0-ubuntu-16.04-x64/lib/mono/4.5/I18N.dll
machineconfig: /etc/mono/4.5/machine.config
Generated CacheServer

这里换成我们项目的示例来执行,命令是:

mkbundle -o WinForm20.exe --cross mono-6.8.0-ubuntu-16.04-x64 demoForWinForm20.exe --machine-config /etc/mono/2.0/machine.config --no-config

image

这很容易,让我们检查一下结果。

$ ls -l CacheServer
-rwxr-xr-x  1 miguel  wheel  15790588 Jan 26 10:36 CacheServer
$ file CacheServer
CacheServer: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d72d30588d0ecef88a2214d6d4dc3c91c5b4db20, stripped

image

我们有了一个Ubuntu的可执行文件。现在,我们所需要做的就是把生成的CacheServer可执行文件复制到我们的Linux机器上,我们就可以开始工作了。不需要安装额外的依赖性。

配置你的可执行文件

一些用户配置了命令行选项,以及在执行时影响Mono运行的环境变量。你可以通过一些配置选项将这些选项纳入你的可执行文件。

为此,你可以使用--options标志。例如,下面是通过向嵌入式可执行文件传递-O=-inline命令行选项来禁用内联。

$ mkbundle -o CacheServer --options -O=-inline --simple CacheServer.exe --machine-config /etc/mono/4.5/machine.config
...

或者说,你想在可执行文件中加入一个环境变量,CACHE_SERVER_PORT设置为9000,你可以像这样使用--env选项。

$ mkbundle -o CacheServer --env CACHE_SERVERPORT=9000 --simple CacheServer.exe --machine-config /etc/mono/4.5/machine.config
...

你也可以通过向--machine-config文件传递不同的路径来定制使用的.NET运行时配置文件machine.config

Mono还支持一个全局映射工具来驱动动态库的解析方式(在mono-config(5) man page上有更详细的描述),你可以通过使用--config命令行选项来指定你的自定义映射文件。

分发本地类库

有时你的应用程序需要的不仅仅是纯粹的管理库,你会想要分发本地共享库--那些被P/Invoke消耗的库(DllImport库)。要把这些库捆绑到你的应用程序中,请使用--library选项,像这样:

$ mkbundle -o CacheServer --simple CacheServer.exe --library fastdecoder,/usr/lib/libfastdecoder.so --machine-config /etc/mono/4.5/machine.config
...

上述命令所做的是注册P/Invoke目标fastdecoder,在你的源代码中,它看起来像这样:

[DllImport ("fastdecoder")]
extern static void fastdecoder_init ();

引用指定的文件,在这里是/usr/lib/libfastdecoder.so。它通过包括libfastdecoder.so的重命名副本来实现,以匹配P/Invoke签名(避免了对DllMap的需求)。

这并没有考虑到包含多个相互依赖的链接器库 - 例如,如果你P/Invoke fastdecoderfastencoder,而libfastencoder.solibfastdecoder.so有链接器依赖,那么像上面的例子那样多次调用--library将不起作用。相反,你需要使用简单的--library调用,并通过--config包含一个DllMap。比如说:

<configuration>
  <dllmap dll="fastdecoder" target="libfastdecoder.so"/>
  <dllmap dll="fastencoder" target="libfastencoder.so"/>
</configuration>
$ mkbundle -o CacheServer --simple CacheServer.exe --library /usr/lib/libfastdecoder.so --library /usr/lib/libfastencoder.so --machine-config /etc/mono/4.5/machine.config --config mydllmap.config
...

在最极端的情况下,这将允许你捆绑整个GUI框架和它的依赖性,使一个捆绑的应用程序在预先假定为零的有用的依赖性的机器上工作(注意下面的例子对于当前的Mono版本可能不完整,检查/etc/mono/config):

<configuration>
  <dllmap dll="libglib-2.0-0.dll" target="libglib-2.0.so.0"/>
  <dllmap dll="libgobject-2.0-0.dll" target="libgobject-2.0.so.0"/>
  <dllmap dll="libatk-1.0-0.dll" target="libatk-1.0.so.0"/>
  <dllmap dll="libgtk-win32-2.0-0.dll" target="libgtk-x11-2.0.so.0"/>
  <dllmap dll="libgdk-win32-2.0-0.dll" target="libgdk-x11-2.0.so.0"/>
  <dllmap dll="gtksharpglue-2" target="libgtksharpglue-2.so"/>
  <dllmap dll="glibsharpglue-2" target="libglibsharpglue-2.so"/>
  <dllmap dll="libc" target="libc.so.6"/>
  <dllmap dll="libintl" target="libc.so.6"/>
  <dllmap dll="i:libxslt.dll" target="libxslt.so" os="!windows"/>
  <dllmap dll="i:odbc32.dll" target="libodbc.so" os="!windows"/>
  <dllmap dll="i:odbc32.dll" target="libiodbc.dylib" os="osx"/>
  <dllmap dll="oci" target="libclntsh.so" os="!windows"/>
  <dllmap dll="db2cli" target="libdb2_36.so" os="!windows"/>
  <dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so"/>
  <dllmap dll="libmono-btls-shared" target="libmono-btls-shared.so"/>
  <dllmap dll="i:msvcrt" target="libc.so.6" os="!windows"/>
  <dllmap dll="i:msvcrt.dll" target="libc.so.6" os="!windows"/>
  <dllmap dll="sqlite" target="libsqlite.so.0" os="!windows"/>
  <dllmap dll="sqlite3" target="libsqlite3.so.0" os="!windows"/>
  <dllmap dll="libX11" target="libX11.so.6" os="!windows" />
  <dllmap dll="libgdk-x11-2.0" target="libgdk-x11-2.0.so.0" os="!windows"/>
  <dllmap dll="libgdk_pixbuf-2.0" target="libgdk_pixbuf-2.0.so.0" os="!windows"/>
  <dllmap dll="libgtk-x11-2.0" target="libgtk-x11-2.0.so.0" os="!windows"/>
  <dllmap dll="libglib-2.0" target="libglib-2.0.so.0" os="!windows"/>
  <dllmap dll="libgobject-2.0" target="libgobject-2.0.so.0" os="!windows"/>
  <dllmap dll="libgnomeui-2" target="libgnomeui-2.so.0" os="!windows"/>
  <dllmap dll="librsvg-2" target="librsvg-2.so.2" os="!windows"/>
  <dllmap dll="libXinerama" target="libXinerama.so.1" os="!windows" />
  <dllmap dll="libasound" target="libasound.so.2" os="!windows" />
  <dllmap dll="libcairo-2.dll" target="libcairo.so.2" os="!windows"/>
  <dllmap dll="libcairo-2.dll" target="libcairo.2.dylib" os="osx"/>
  <dllmap dll="libcups" target="libcups.so.2" os="!windows"/>
  <dllmap dll="libcups" target="libcups.dylib" os="osx"/>
  <dllmap dll="i:kernel32.dll">
    <dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/>
    <dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/>
    <dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/>
    <dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/>
  </dllmap>
  <dllmap dll="gdiplus" target="libgdiplus.so.0" os="!windows"/>
  <dllmap dll="gdiplus.dll" target="libgdiplus.so.0"  os="!windows"/>
  <dllmap dll="gdi32" target="libgdiplus.so.0" os="!windows"/>
  <dllmap dll="gdi32.dll" target="libgdiplus.so.0" os="!windows"/>
</configuration>
mkbundle -o gtktest --simple gtktest.exe --library /lib/x86_64-linux-gnu/libglib-2.0.so.0 --library /usr/lib/x86_64-linux-gnu/libffi.so.6 --library /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 --library /usr/lib/x86_64-linux-gnu/libdatrie.so.1 --library /usr/lib/x86_64-linux-gnu/libthai.so.0 --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 --library /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0 --library /usr/lib/x86_64-linux-gnu/libpixman-1.so.0 --library /lib/x86_64-linux-gnu/libz.so.1 --library /usr/lib/x86_64-linux-gnu/libpng16.so.16 --library /usr/lib/x86_64-linux-gnu/libfreetype.so.6 --library /lib/x86_64-linux-gnu/libexpat.so.1 --library /usr/lib/x86_64-linux-gnu/libfontconfig.so.1 --library /usr/lib/x86_64-linux-gnu/libXau.so.6 --library /lib/x86_64-linux-gnu/libbsd.so.0 --library /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 --library /usr/lib/x86_64-linux-gnu/libxcb.so.1 --library /usr/lib/x86_64-linux-gnu/libxcb-shm.so.0 --library /usr/lib/x86_64-linux-gnu/libxcb-render.so.0 --library /usr/lib/x86_64-linux-gnu/libX11.so.6 --library /usr/lib/x86_64-linux-gnu/libXrender.so.1 --library /usr/lib/x86_64-linux-gnu/libXext.so.6 --library /usr/lib/x86_64-linux-gnu/libcairo.so.2 --library /usr/lib/x86_64-linux-gnu/libgraphite2.so.3 --library /usr/lib/x86_64-linux-gnu/libharfbuzz.so.0 --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 --library /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0 --library /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0 --library /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0 --library /usr/lib/x86_64-linux-gnu/libXinerama.so.1 --library /usr/lib/x86_64-linux-gnu/libXi.so.6 --library /usr/lib/x86_64-linux-gnu/libXrandr.so.2 --library /usr/lib/x86_64-linux-gnu/libXfixes.so.3 --library /usr/lib/x86_64-linux-gnu/libXcursor.so.1 --library /usr/lib/x86_64-linux-gnu/libXcomposite.so.1 --library /usr/lib/x86_64-linux-gnu/libXdamage.so.1 --library /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0 --library /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 --library /usr/lib/cli/gtk-sharp-2.0/libgtksharpglue-2.so  --library /usr/lib/cli/glib-sharp-2.0/libglibsharpglue-2.so --library /usr/lib/libMonoPosixHelper.so --machine-config /etc/mono/4.5/machine.config --config custom.config

你觉得有多大的必要去使用捆绑的库,主要取决于你的特定目标环境。大部分的自定义配置来自于/etc/mono/config,它包含了Gtk#需要的条目(你只能指定一个--config条目)。

查看自定义配置

cd /etc/mono/
cat config

image

<configuration>
	<dllmap dll="i:cygwin1.dll" target="libc.so.6" os="!windows" />
	<dllmap dll="libc" target="libc.so.6" os="!windows"/>
	<dllmap dll="intl" target="libc.so.6" os="!windows"/>
	<dllmap dll="intl" name="bind_textdomain_codeset" target="libc.so.6" os="solaris"/>
	<dllmap dll="libintl" name="bind_textdomain_codeset" target="libc.so.6" os="solaris"/>
	<dllmap dll="libintl" target="libc.so.6" os="!windows"/>
	<dllmap dll="i:libxslt.dll" target="libxslt.so" os="!windows"/>
	<dllmap dll="i:odbc32.dll" target="libodbc.so.2" os="!windows"/>
	<dllmap dll="i:odbc32.dll" target="libiodbc.dylib" os="osx"/>
	<dllmap dll="oci" target="libclntsh.so" os="!windows"/>
	<dllmap dll="db2cli" target="libdb2_36.so" os="!windows"/>
	<dllmap dll="MonoPosixHelper" target="$mono_libdir/libMonoPosixHelper.so" os="!windows" />
	<dllmap dll="System.Native" target="$mono_libdir/libmono-native.so" os="!windows" />
	<dllmap dll="System.Net.Security.Native" target="$mono_libdir/libmono-native.so" os="!windows" />
	<dllmap dll="System.Security.Cryptography.Native.Apple" target="$mono_libdir/libmono-native.so" os="osx" />
	<dllmap dll="libmono-btls-shared" target="$mono_libdir/libmono-btls-shared.so" os="!windows" />
	<dllmap dll="i:msvcrt" target="libc.so.6" os="!windows"/>
	<dllmap dll="i:msvcrt.dll" target="libc.so.6" os="!windows"/>
	<dllmap dll="sqlite" target="libsqlite.so.0" os="!windows"/>
	<dllmap dll="sqlite3" target="libsqlite3.so.0" os="!windows"/>
	<dllmap dll="libX11" target="libX11.so.6" os="!windows" />
	<dllmap dll="libgdk-x11-2.0" target="libgdk-x11-2.0.so.0" os="!windows"/>
	<dllmap dll="libgdk_pixbuf-2.0" target="libgdk_pixbuf-2.0.so.0" os="!windows"/>
	<dllmap dll="libgtk-x11-2.0" target="libgtk-x11-2.0.so.0" os="!windows"/>
	<dllmap dll="libglib-2.0" target="libglib-2.0.so.0" os="!windows"/>
	<dllmap dll="libgobject-2.0" target="libgobject-2.0.so.0" os="!windows"/>
	<dllmap dll="libgnomeui-2" target="libgnomeui-2.so.0" os="!windows"/>
	<dllmap dll="librsvg-2" target="librsvg-2.so.2" os="!windows"/>
	<dllmap dll="libXinerama" target="libXinerama.so.1" os="!windows" />
	<dllmap dll="libasound" target="libasound.so.2" os="!windows" />
	<dllmap dll="libcairo-2.dll" target="libcairo.so.2" os="!windows"/>
	<dllmap dll="libcairo-2.dll" target="$mono_libdir/libcairo.2.dylib" os="osx" />
	<dllmap dll="libcups" target="libcups.so.2" os="!windows"/>
	<dllmap dll="libcups" target="libcups.dylib" os="osx"/>
	<dllmap dll="i:kernel32.dll">
		<dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/>
		<dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/>
		<dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/>
		<dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/>
	</dllmap>
	<dllmap dll="gdiplus" target="libgdiplus.so.0" os="!windows"/>
	<dllmap dll="gdiplus.dll" target="libgdiplus.so.0"  os="!windows"/>
	<dllmap dll="gdi32" target="libgdiplus.so.0" os="!windows"/>
	<dllmap dll="gdi32.dll" target="libgdiplus.so.0" os="!windows"/>
</configuration>

高级方案

mkbundle工具还支持其他的选项,这些选项在本页面中没有涉及,比如使用自己的本地服务器运行,C编译器驱动的嵌入等等。这些在mkbundle(1)的手册中都有介绍,请参考它以获得更多细节。

指定程序集目录

-L $path可用于指定程序集目录。

将"Path"添加到程序集的搜索列表中。其规则与编译器的-lib:-L标志类似。

mkbundle -o WinForm20.exe --cross mono-6.8.0-ubuntu-16.04-x64 demoForWinForm20.exe --machine-config /etc/mono/2.0/machine.config --no-config -L .

image

将输出日志丢黑洞:

mkbundle -o WinForm20.exe --cross mono-6.8.0-ubuntu-16.04-x64 demoForWinForm20.exe --machine-config /etc/mono/2.0/machine.config --no-config -L . 1> /dev/null

image

源码解读

从Github上是可以找到它的源代码的,这对帮我们理解这个工具很有帮助。mono/mono/blob/main/mcs/tools/mkbundle/mkbundle.cs

image

MSBuild命令行打包

https://docs.microsoft.com/zh-cn/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2022

获取帮助(-help)

通过-help或者-h来显示用法信息。

msbuild -help

image

执行最大并发数(-m)

通过-maxCpuCount:number或者-m:number可指定生成时要使用的最大并发进程数。若没有指定,默认值是1;

例如:

msbuild HelloMono.sln -m:4

image

指定生成目标(-t)

通过-target:targets或者-t:targets可在项目中生成指定目标。可单独指定每个目标,或使用分号或逗号分隔多个目标,可定义的目标很多,包括:MSBuild目标

例如:

msbuild HelloMono.sln -t:Rebuild

image

msbuild HelloMono.sln -t:Build

image

msbuild HelloMono.sln -t:Run

image

msbuild HelloMono.sln -t:Compile

image

msbuild HelloMono.sln -t:CopyFilesToOutputDirectory

image

msbuild HelloMono.sln -t:Clean

image

msbuild HelloMono.sln -t:CleanPublishFolder

image

msbuild HelloMono.sln -t:Publish

image

指定项目级属性(-p)

通过-property:name=value或者-p:name=value可设置或重写指定项目级属性,其中name是属性名称,value是属性值。

例如:

msbuild HelloMono.sln -p:WarningLevel=2

image

msbuild HelloMono.sln -p:Platform="Any CPU"

image

msbuild HelloMono.sln -p:Configuration=Release

image

msbuild HelloMono.sln -p:DefineConstants="TRACE NET35 Linux FregStructReturn GCCVTABLE Microsoft"

image

通过-nologo可不显示启动版权标志或版权消息。

例如:

msbuild HelloMono.sln -nologo

image

设置日志量级(-v)

通过-verbosity:level或者-v:level可指定要在生成日志中显示的信息量。每个记录器基于为该记录器设置的详细级别显示事件。可以指定以下详细级别:q[uiet]m[inimal]n[ormal](默认)d[etailed]diag[nostic]

例如:

msbuild HelloMono.sln -v:m

image

msbuild HelloMono.sln -v:q

image

参数传递到控制台(-clp)

通过-consoleLoggerParameters:parameters或者-clp:parameters可将指定的参数传递到控制台记录器,后者会在控制台窗口中显示生成信息。

可以指定以下参数:

  • PerformanceSummary,显示在任务、目标和项目中所花费的时间。
  • Summary,在末尾显示错误和警告摘要。
  • NoSummary,不在末尾显示错误和警告摘要。
  • ErrorsOnly,仅显示错误。
  • WarningsOnly,仅显示警告。
  • NoItemAndPropertyList,如果详细级别设置为diagnostic,则不在每个项目生成开头显示项和属性的列表。
  • ShowCommandLine,显示TaskCommandLineEvent消息。
  • ShowProjectFile,在诊断消息中显示项目文件的路径。 此设置默认启用。
  • ShowTimestamp,将时间戳显示为任何消息的前缀。
  • ShowEventId,显示每个已启动事件、已完成事件和消息的事件ID。
  • ForceNoAlign,不将文本与控制台缓冲区大小对齐。
  • DisableConsoleColor,将默认控制台颜色用于所有日志记录消息。
  • DisableMPLogging,在非多处理器模式下运行时,禁用输出的多处理器日志记录样式。
  • EnableMPLogging,启用多处理器日志记录样式(即使在非多处理器模式下运行)。默认情况下,此日志记录样式处于启用状态。
  • Verbosity,重写此记录器的-verbosity设置。

例如:

msbuild HelloMono.sln -clp:Summary

image

低级别警告(-noWarn)

通过-warnAsMessage:code1;code2或者-noWarn:code1;code2可设置视为低重要性消息的警告代码的列表,使用分号或逗号分隔多个警告代码。

例如:

msbuild HelloMono.sln -noWarn:MSB3277

image

综合测验

例如:

msbuild HelloMono.sln \
    -m:4 \
    -t:Rebuild \
    -p:Platform="Any CPU" \
    -p:Configuration=Release \
    -p:DefineConstants="TRACE NET35 Linux FregStructReturn GCCVTABLE Microsoft" \
    -nologo \
    -v:m \
    -clp:Summary \
    -noWarn:MSB3277

image

MonoTouch

什么是MonoTouch

直到最近,要为苹果的iPhone开发应用程序的唯一选择就是一头扎进苹果的开发系统中。这意味着,你必须“愿意”在XCode IDE中编写Objective-C代码。对于很多开发人员,学习Objective-C被看作是一个巨大的障碍。特别对于哪些从来不用担心内存管理、指针和C语言要负责处理的东西的开发人员来说,更是如此。

随着MonoTouch框架(Novell的MonoProject的一部分)的出现,这一切都将改变。Mono Project是微软.NET平台的开源实现。其允许你在几乎任何平台上运行.NET应用程序,包括Apple、FreeBSD、Linux、Unix等等。MonoTouch是Mono Project的新组成部分,让你能够用C#和.NET平台编写可以运行在iPhone上的应用程序

如何工作

在创建MonoTouch应用程序的时候,大部分非UI方面的.NET 3.5功能依旧可用,或者一些还处于计划之中(如.NET 4.0的功能)也囊括其中。这让你可以使用很多业已熟悉的.NET Framework技术来编写应用程序,包括Windows Communication Framework(WCF)、Workflow Foundation(WF)等等。也包括几乎所有的基类库(Base Class Library,BCL),涵盖诸如垃圾收集(Garbage Collection)、线程、数学函数、System.Net、加密等。对于可用的标准.NET程序集列表,见http://monotouch.net/Documentation/AssembliesMonoTouch是一种基础.NET函数库的特别定制版本,这也类似Silverlight和Moonlight的实现方式

这意味着,你能够使用MonoTouch的核心程序集来编译标准的.NET 3.5代码成相应的函数库,并在你的应用程序中使用它们。因此,如果你有一个用于其它应用程序的特别函数库,其包含着一些用于工程问题的高级数学函数,那么只需简单地把这些代码库加入到你的MonoTouch解决方案中,并引用它。在你构建解决方案的时候,编译器就利用MonoTouch核心函数库对其进行编译,接着就能在iPhone应用程序中使用它了

MonoTouch也包括一些原生iPhone API的包装函数库,如访问位置(Location,GPS)、加速计、地址簿等的函数。MonoTouch也提供相应的功能,让你能够调用那些尚未进行包装的原生Objective-C函数库,所以你可以直接和现存的Objective-C代码进行互操作

用户界面

MonoTouch应用程序的UI需要使用苹果的Interface Builder(界面创建器,IB)应用程序来创建,IB连同iPhone SDK一起提供。Interface Builder使用CocoaTouch(苹果用于iPhone的UI框架)控件对象,这些控件对象在iPhone上原生提供的。这意味着,你能在应用程序中使用所有的标准iPhone控件,如选择器(Pickers)、滑动条(Sliders)、按钮等等。

你也能通过代码来创建界面,即实例化CocoaTouch对象后,把它们添加到应用程序的视图(Views)中。

然而,你不能利用传统的.NET技术,如Silverlight、WPF、WinForms等来创建MonoTouch界面

CocoaTouch使用一种融合了MVC(Model View Controller)模式思想的结构。

分发应用

MonoTouch应用程序的分发完全和传统iPhone应用程序的分发一样,既可以通过苹果AppStore,也可以通过企业部署。

AppStore是一个在线资源库,让用户可以付费购买(如果不是免费的话)和下载应用程序。可以从iTunes中访问,或直接通过iPhone本身来访问。为了得到通过AppStore分发应用的许可,你必须向苹果注册,并支付每年99美元的年费。

企业部署方式就是为公司开发内部应用程序,并分发给员工等人员使用,无需把应用在AppStore中列出

许可模型

不像Mono那样,MonoTouch不是开源的,且是一个收费产品。这意味着,如果你打算开发一些实际的应用,就必须购买软件许可。

  • 专业版($399)——单个的个人开发人员许可,让你可以开发应用程序,并通过苹果AppStore来分发它们。
  • 企业版($999)——单个的企业开发人员许可,让你可以开发应用程序,并通过苹果AppStore来分发它们,或者进行企业部署。
  • 企业版,5人($3999)——和企业版一样,只是提供了5个坐席的授权。

以上所有选项都包括了一年的免费升级权益。

还有一个评估版本,你只能把应用部署到模拟器中。出于介绍的目的,我们只需要评估版本就行。

使用限制

没有即时(JIT)编译

根据苹果的iPhone政策,任何应用程序都不能包含需要JIT编译的代码。但是稍等,.NET确实能正确工作,是不?对,不过MonoTouch是通过把应用程序编译为原生的iPhone程序集来跳过这个限制的。但是,这也带来了几个限制。

  • 泛型——泛型是由JIT编译器在运行时进行实例化的,然而,Mono具备一种提前(Ahead of Time,AOT)编译的模式,可以为类似List这样的泛型集合生成方法和属性。而泛型的其他用法,例如泛型虚方法、泛型类型上的P/InvokesDictionary<TKey,TValue>上的值类型,就不被支持(虽然存在Dictionary<TKey,TValue>的代替方法)。
  • 动态代码生成——因为动态代码生成依赖于JIT编译器,所以对任何动态语言编译的过程也不能支持。包括System.Reflection.Emit、Remoting和动态语言运行时(DLR)。

C#是唯一的语言

另外,目前用于编写MonoTouch应用程序的唯一可用语言是C#。VisualBasic.NET有望在MonoTouch未来的发布中支持,不过此时此刻我们别无选择。

Mono运行日志追踪

运行时查看日志

可以通过MONO_LOG_LEVELMONO_LOG_MASK环境变量来控制日志输出。

MONO_LOG_LEVEL
The logging level, possible values are `error', `critical', `warning', `message', `info' and `debug'. See the DEBUGGING section for more details.

MONO_LOG_MASK
Controls the domain of the Mono runtime that logging will apply to. If set, the log mask is changed to the set value. Possible values are "asm" (assembly loader), "type", "dll" (native library loader), "gc" (garbage collector), "cfg" (config file loader), "aot" (precompiler), "security" (e.g. Moonlight CoreCLR support) and "all". The default value is "all". Changing the mask value allows you to display only messages for a certain component. You can use multiple masks by comma separating them. For example to see config file messages and assembly loader messages set you mask to "asm,cfg".

先切换到入口文件所在位置,然后运行如下命令:

MONO_LOG_LEVEL="debug" ./WinForm20.exe

这样可以打开debug等级的日志。

image

如果要查看引用的dll的日志,还可以使用

MONO_LOG_MASK="dll" ./WinForm20.exe

image

参考

posted @ 2022-07-18 20:34  TaylorShi  阅读(2080)  评论(0编辑  收藏  举报