关于.Net中Process的使用方法和各种用途汇总(二):用Process启动cmd.exe完成将cs编译成dll
上一章博客我为大家介绍了Process类的所有基本使用方法,这一章博客我来为大家做一个小扩展,来熟悉一下Process类的实际使用,废话不多说我们开始演示。
先看看我们的软件要设计成的布局吧。
首先我们需要给定会使用到的dll,记得vs中的引用那一项吗?我们虽然不需要将这里面的引用全部导入进来,但是我们需要将我们使用过的dll全部导入进来,不然编译时会提示找不到类方法之类的。
可能有些同学不知道怎么查看一个类或者方法所在的dll,其实只用对着那个方法或者类按下f12就能在打开的文档的正上方看到所在的dll了,包括dll所在的路径都写的非常清楚。
我后续更新是想完成导入工程,读取出来工程内的cs文件和资源,选择需要编译的cs文件和资源,然后点击编译,完成编译,不过功能比较多,我会慢慢的更新,没更新一个版本会在本博客的下方附上地址。
介绍就先到此为止,我们开始讲解代码的实现过程,由于整个工程的代码大部分和我们需要讲解的内容无关,所以我只挑出核心功能在这里介绍一下,其他的内容请自行下载工程查看。先贴上代码,我感觉代码中的注释已经写的非常明白的,不过我还是找些注释解释的不是很明白的地方详细讲解下。
/// <summary> /// 异步执行编译 /// </summary> private async void Compiled() { //注意:我们下面这几个变量提前拿出来是因为ui都式不能跨线程访问,异步操作也会被认为是多线程。 //dll的输出路径 string working = path_textBox.Text; //dll的名字 string name = name_textBox.Text; //vs的路径,这里我们使用的是vs的一个批处理文件进行dll的编译,它会自动帮我们配置好环境,虽然我们直接使用.net的csc.exe也能编译,但是你看我的代码里用的c#6.0的各种新语法都是需要编译器支持的,仅仅使用.net的csc.exe是无法完成编译的 string vs_Path = vs_Path_textBox.Text + "\\Common7\\Tools\\VsMSBuildCmd.bat"; //这个也不算新语法了,这个和上面的async是一对,你给一个方法标记上async表示这个方法是异步的,但是注意await等待的代码以外的代码是同步执行的,也就是说是主线程执行的,必须将需要执行的代码放在Task.Run()中,才会异步执行,我一开始就是有这个误区一直疑惑异步为啥还会卡。 await Task.Run(() => { //创建一个ProcessStartInfo,设置初始信息 System.Diagnostics.ProcessStartInfo start = new System.Diagnostics.ProcessStartInfo("cmd.exe"); //让应用可以接受输入,这里我们用于向控制台输入命令 start.RedirectStandardInput = true; //让应用可以输出数据,这里我们是打算读取在编译完毕后读取控制台的输出数据判断是否编译成功的,但是暂时无法完成此功能 start.RedirectStandardOutput = true; //这个属性设置成true可以让打开的控制台无窗口,以达到我们想要的效果 start.CreateNoWindow = true; //这个属性也不知道干什么用的,但是你想对控制台进行控制,这个属性必须设置为false,不然会抛出异常告诉你,必须要将此属性设置为false start.UseShellExecute = false; //终于开始正题了,就像上一章博客介绍的那样,我们通过ProcessStartInfo初始化一个Process对象 System.Diagnostics.Process process = System.Diagnostics.Process.Start(start); //以下就是像控制台输入命令的过程了,想从控制台读取数据,就直接调用StandardOutput.ReadLine();就能读取一行了,不过要注意了,你调用了此方法后,控制台会一直等待一个输入,所以很容易就一直卡在那里不执行下面的代码 //GetLetter方法是我自己定义个获取路径中的盘符加上:用的,用此方法获取到vs所在的盘,之后直接将命令输给cmd,就能进入vs所在的磁盘分区了,不明白的自己去补控制台命令 process.StandardInput.WriteLine(GetLetter(vs_Path)); //用cd命令进入VsMSBuildCmd.bat批处理文件所在的路径 process.StandardInput.WriteLine("cd " + System.IO.Path.GetDirectoryName(vs_Path)); //获取VsMSBuildCmd.bat文件的名字,并输入给cmd,执行这个批处理文件的命令 process.StandardInput.WriteLine(System.IO.Path.GetFileName(vs_Path)); //批处理文件执行完毕后,我们的控制台环境就被搭建好了,我们将路径再转向dll的输出目录 process.StandardInput.WriteLine(GetLetter(working)); process.StandardInput.WriteLine("cd " + working); //将会使用到的dll以","为分割符拼接起来。这里我说明一下会使用到的dll什么意思,假设我们的类调用第三方dll之类的时候,当然你调用了如System.Windows.MessageBox();之类的方法也需要引用对应的dll。
//不知道用的方法或者类是在哪个dll?对着你的方法或者类按下f12在代开的文档的最上方写着呢。 //StringBuilder是个高效的字符串拼接类,但是相应的功能没有string多,在拼接完所有字符串后,直接ToString就能得到字符串 StringBuilder dll = new StringBuilder(); foreach (string item in DllPath) { dll.Append(item); dll.Append(","); } //将需要编译的cs文件以空格为分隔符拼接起来,这里用空格,dll那里用,这是语法,别问我为什么。 StringBuilder cs = new StringBuilder(); foreach (string item in CsPath) { cs.Append(item); cs.Append(" "); } //如果没有使用其他dll的情况下 if (dll.ToString() == "") { //由于没有使用dll的情况。 //我一一解释这些命令都是干嘛用的,csc是用来编译我们cs文件的应用程序,/t:library代表我们要将cs文件编译成dll,当然也能编译成exe之类的,之后跟上空格再加上所有需要编译的cs文件。再之后用/out:指定输出的名字 process.StandardInput.WriteLine($"csc /t:library {cs.ToString()} /out:{name}"); } else { //使用了其他dll的情况下,其他的都跟上面相同。主要式添加了/r:,/r:后面跟上所有的dll,用","分割 process.StandardInput.WriteLine($"csc /r:{dll.ToString()} /t:library {cs.ToString()} /out:{name}"); } //所有命令都执行完毕了,接下来久给一个exit的命令退出控制台吧 process.StandardInput.WriteLine("exit"); //等待控制台关闭 process.WaitForExit(); //释放Process对象的所有资源 process.Close(); //执行事件,这里有个新语法,?.表示CompiledEndEvent不为null的情况下就出发CompiledEndEvent事件 CompiledEndEvent?.Invoke(); }); }
string vs_Path = vs_Path_textBox.Text + "\\Common7\\Tools\\VsMSBuildCmd.bat";
我们先说明一下VsMSBuildCmd.bat这个批处理文件吧,vs2013和vs2015我均已证实VsMSBuildCmd.bat所在路径是vs安装路径下的一指定路径下的,因此我这里久干脆拼接了一下字符串。如果低版本vs路径有所不同各位可以看着修改。VsMSBuildCmd.bat文件帮我们做了一些编译操作,不执行这个文件,而是直接执行.net目录下的csc.exe的话,我们是无法编译c#的一些新语法的。有一点各位要明白,c#版本和.net版本几乎没有太大关系,也不能说完全没关系,c#新版本的语法其实是编译器维护的,所以我们仅仅用csc.exe是无法识别那些新语法的。
再简单介绍一些async和await,这个是c#5.0增加的两个关键字,他让我们编写异步方法变得异常方法,通过上面的代码你也可以看到这个方法是可以混合异步和同步操作的,我们可以在方法中使用await等待一个费时的操作,在这个操作执行完毕后才会继续执行下面的方法,且不堵塞线程。仅仅只有Task.Run();中的方法是异步执行的,这样极大的方便了我们很多的操作。用法我已经告诉大家了,怎么去利用各自看着用吧。对了,别在Unity中使用,Unity中用的是c#4.0的语法和.Net2.0的版本,无法支持这两个关键字。
其他的没啥好讲的了,注释写的已经非常清楚了。我就给各位讲讲本代码中使用到的c#6.0的新语法吧。
首先是"$"运算符,这个运算符是为了简化string.Format();方法而被设计出来的,我给个$运算符和string.Format的例子一对比,你就会简单明了的明吧$干什么用的,怎么用了。
string a = "我", b = "是", c = "谁", d = "?"; string s = $"{a}到底{b}{c}呀{d}"; string s2 = string.Format("{0]到底{1}{2}呀{3}", a, b, c, d);
看着上面的例子,两种拼接字符串的方式,用$运算符可以降低我们不少工作量,而且阅读性也增加了。他们的输出结果自然也是相同的。
然后式?.运算符,中文名叫什么来着忘记了,不过也不用在意这种事。这个运算符依然是简化我们的工作用的,看下面的代码,这样很容易就能明白。
LL kk = null; //我们以前的写法 if (kk != null) kk.S(); //而用?.运算符,这个运算符意思就是如果kk不为null的话就执行kk.S();跟上面那两行代码一个功能 kk?.S();
说到?.运算符了,就不得不提提??运算符,?.运算符表示不为null时执行,??运算符则表示为null时执行。看段代码:
LL kk = null; //我们以前的写法 if (kk == null) kk = new LL(); //而用?.运算符 kk = kk ?? new LL();
好了,感谢阅读本篇博客,希望各位也能有所收获。Process类和ProcessStartInfo类还有很多功能,大家可以自己去多多研究研究,有机会的话我会在后续章节中为各位继续分享。
附上整个工程源码,工程是vs2015写成的WPF程序,使用了大量c#6.0的新语法,低版本的打开会提示大量的错误,vs2013貌似能正常编译,没试过,再低版本的都无法使用。不过只用将工程中的几个不兼容的小错误修改一下就能正常编译了。
如果你只是想要运行程序的话,直接在工程目录中的bin\Release目录下找到DLL编译器.exe即可拷贝走使用。
工程还有很多问题,比如无法获取编译结果,不会储存最近一次的操作信息登,这些问题我都会后续修复,并在此页面更新,每修改一版我都会写上版本。
1.0 beta版:https://files.cnblogs.com/files/menghuijinxi/DLL%E7%BC%96%E8%AF%91%E5%99%A8.zip
文章原创,欢迎转载,请标明出处。