运行环境: vs2013
框架: .net4.5
c编译器:mingw 32位
首先我们下载一个c编译工具链
http://tdm-gcc.tdragon.net/download
选择tmd gcc 32位编译器下载
配置好后我们就可以使用该编译器对c程序进行编程
尝试写个简单的c代码测试一下编译
 
保存为test.c
通过工具链的gcc程序进行编译
通过类似gnu gcc的方式进行编译
可以正确运行出结果
测试c编译器可用的情况下我们尝试使用c#进行外部调用
在原先的项目中添加ExeExecute项目
要调用外部的exe程序我们需要引入
using System.Diagnostics;
而要使用外部exe主要是掌握Process对象的使用
Process p = new Process();
而使用Process主要分为三个步骤,第一步是设定启动参数,第二步是启动exe程序,第三步是捕抓程序的输入输出流进行控制
然后第一步的参数设置:
确定编译器对象为gcc.exe
p.StartInfo.FileName = @"C:\Users\Administrator\Desktop\gcc-5.1.0\bin\gcc.exe";
gcc程序不在相同路径下需要使用完整路径
设定好程序路径我们还需要设定工作路径,也就是源代码以及生成程序代码的路径
p.StartInfo.WorkingDirectory = @"C:\Users\Administrator\Desktop\test\";
最后要设定编译参数
p.StartInfo.Arguments = "test.c -o test.exe -m32 -g3 -static-libgcc";
采用静态编译,因为部分dll并没有添加到系统环境变量中
最后为了能捕抓程序的输入输出流,我们不采用外部调用系统的shell,输入输出流重定向到程序中
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
第二步程序运行
p.Start();
第三部捕抓输入输出
编译结果输出信息重定向到c#程序的控制台上
Console.Write(p.StandardOutput.ReadToEnd());
编译正常除了警告外是不会有其他输出信息的
Console.WriteLine(p.ExitCode);
而程序退出码则是判断是否成功编译的关键
p.WaitForExit();
p.Close();
最后必须退出外部exe调用的程序,不然会无法控制一直运行在后台导致内存溢出
根据上面的步骤来综合编写程序
很不幸运行程序过程中系统提示部分dll找不到,然后我尝试通过控制台来测试,甚至直接修改环境变量导入dll到系统中仍无法解决该问题
目前有两个解决方案,as.exe路径或者在程序运行目录下放置必要的dll文件
为了不破坏编译程序的结构,现采用复制到test目录下编译,虽然可以通过程序来建立临时路径来访问dll但会影响一定的速度,复制到目录中共享虽然影响一定的可移动性,但还是能更高效的使用,为后面并发编译做准备
接下来做测试,先测试正确编译的情况
程序不提醒任何错误,显示编译成功
修改源代码制造语法错误
编译会提示出错信息
再修改源代码
这次是可以编译成功,过程中的警告会有所显示
能成功编译程序后我们需要运行程序进行输入输出测试,这时候修改一下原程序
先做出可以提供输入输出的程序进行编译
根据ExitCode判断编译成功后可以运行程序来进行测试
 
接下来跟上面的外部调用一样,这次主要多开启了输入流重定向
p.StartInfo.RedirectStandardInput = true;
通过输入流以及读取输出流判断结果是否正确
这个程序并没有做泛化出来来适配各种各样的输入输出情况
接下来泛化一下,做出类库封装编译与测试功能
建立编译测试类库
该类库主要包含编译与测试两部分
然后我们定义一些类库的接口
构造函数有两个
默认不带参数的构造方法,提供默认的gcc可执行路径,以及c文件编译测试的工具路径
另外一个是指定参数构建,可以方便非相对路径下的使用
下一步是编译方法
编译需要c源文件代码,返回是否成功编译
public bool Compile(string csrccode);
方法中需要先保存为*.c来编译
因为考虑到后面的并发编译,这个文件名不得与其他的线程重复,避免出现资源占用的错误以及对结果的影响
而文件是跟线程共存亡的,线程结束该文件就无用了
所以文件名依赖于线程
string testID = Thread.CurrentThread.ManagedThreadId.ToString();
通过获取线程id作为文件名,可以保证不会影响其他进程的运行,如果通过加锁的方式来并发反而会影响效率
 
写入文件,结合上面掌握的一些代码,来编写,这时候一些调试性代码可以关闭,不用在控制台输出影响效率
接下来就要运行程序来判断输入输出流
先运行程序
运行等待输入输出操作
析构时候需要做关闭操作避免后台运行没有正常关闭导致内存异常
输出,结果配对,不匹配返回false,匹配返回true
输入部分,判断程序是否已经退出了(退出不关闭依旧可以正常输入流)
结束判断,如果程序还在运行中证明缺少输入或者输出,不能完全匹配测试,返回错误,关闭程序
虽然尝试使用c++类似的输入输出流的重载,但是返回对象时候不能返回引用对象,要是new对象,可能我写法上有问题导致无法很好地重载,不然可以连续做输入输出判断,错误通过异常抛出的操作,现在暂时不成功
编写完成就开始测试
引用类库
对象new
测试一下简单的hello world
判断代码,判断输出一次hello world!再输入一次hello world!
结果是错误的因为我多输入了一次无效的操作
注释掉无效的操作后
运行成功
#include<stdio.h>
int main(){
int a,b;
printf("input two number:\n");
scanf("%d %d",&a,&b);
printf("res:%d",a + b);
return 0;
}
第二次我用输出输入输出的方式,然后验证的时候在读取第一个输入的时候无法读取,用read方法也好,readline也好,readtoend也好,都是不能读取数据,只会阻塞等待,所以程序只能不断输入,再一次性输出,这会导致系统只能有一次流完整读取操作。
运行
卡在第一次读取输出流中
这意味着程序无法输入输出按次判断,只能完整输入判断输出
修改只有一次写入一次读取
此时可以看到正确的结果
由于原先的猜想无法成立,现在简化代码单纯完成输入输出检测不进行顺序检测
最后通过public bool RunTest(string exein,string exeout)进行判断
同样是刚才那份代码
运行一下可以通过
接下来有一句容易导致超时的语句要进行处理
exeRun.StandardOutput.ReadToEnd();
因为readtoend可能由于死循环,等待输出等原因阻塞或者卡死,不作超时处理会有大量的死掉的进程,而且还无法给客户端及时的信息反馈
这时候我们就要单独对这个语句做一个超时处理
先做一个时间处理
ManualResetEvent timeEvent = new ManualResetEvent(false);
建立委托,完成任务着set一下事件
然后主进程会异步调用改委托
proc.BeginInvoke(null, null, null);
bool flag = timeEvent.WaitOne(time, false);
然后线程在等待时间内不断检测是否改变timeEvent标志
改变了的话里面返回true
否则会超时后为false
如果超时常规退出不是行的了
只能kill掉死掉的进程
为了实现超时时间的可控性,为runtest函数重载
增加一个timeout参数可以应对算法较复杂的程序,类内默认初始值为5000ms
然后进行测试确定可用,准备用于下一个综合实验
通过该实验我掌握了exe的外部调用,以及对输入输出流的读写控制,以及gcc编译器工具在windows下的编译使用的方法,以及不断修改到最后做出可超时检测的c编译器调用与程序测试的程序,由于技术知识缺少,暂时未能解决对输入输出的步骤控制