C#-JudgeSystem判题系统-C#编译C程序

运行环境: vs2013c++

框架: .net4.5算法

c编译器:mingw 32位shell

首先咱们下载一个c编译工具链编程

http://tdm-gcc.tdragon.net/downloadc#

选择tmd gcc 32位编译器下载windows

配置好后咱们就可使用该编译器对c程序进行编程并发

尝试写个简单的c代码测试一下编译框架

Unnamed QQ Screenshot20150801133956

 异步

保存为test.c函数

经过工具链的gcc程序进行编译

Unnamed QQ Screenshot20150801134328

经过相似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调用的程序,否则会没法控制一直运行在后台致使内存溢出

根据上面的步骤来综合编写程序

Unnamed QQ Screenshot20150801174919

很不幸运行程序过程当中系统提示部分dll找不到,而后我尝试经过控制台来测试,甚至直接修改环境变量导入dll到系统中仍没法解决该问题

目前有两个解决方案,as.exe路径或者在程序运行目录下放置必要的dll文件

为了避免破坏编译程序的结构,现采用复制到test目录下编译,虽然能够经过程序来创建临时路径来访问dll但会影响必定的速度,复制到目录中共享虽然影响必定的可移动性,但仍是能更高效的使用,为后面并发编译作准备

接下来作测试,先测试正确编译的状况

Unnamed QQ Screenshot20150801175318

程序不提醒任何错误,显示编译成功

修改源代码制造语法错误

Unnamed QQ Screenshot20150801175535

Unnamed QQ Screenshot20150801175614

编译会提示出错信息

Unnamed QQ Screenshot20150801175740

再修改源代码

Unnamed QQ Screenshot20150801175747

此次是能够编译成功,过程当中的警告会有所显示

能成功编译程序后咱们须要运行程序进行输入输出测试,这时候修改一下原程序

Unnamed QQ Screenshot20150801180049

先作出能够提供输入输出的程序进行编译

根据ExitCode判断编译成功后能够运行程序来进行测试

Unnamed QQ Screenshot20150801180543

 

接下来跟上面的外部调用同样,此次主要多开启了输入流重定向

p.StartInfo.RedirectStandardInput = true;

经过输入流以及读取输出流判断结果是否正确

这个程序并无作泛化出来来适配各类各样的输入输出状况

接下来泛化一下,作出类库封装编译与测试功能

Unnamed QQ Screenshot20150803164406

创建编译测试类库

该类库主要包含编译与测试两部分

而后咱们定义一些类库的接口

构造函数有两个

默认不带参数的构造方法,提供默认的gcc可执行路径,以及c文件编译测试的工具路径

Unnamed QQ Screenshot20150803174433

另一个是指定参数构建,能够方便非相对路径下的使用

Unnamed QQ Screenshot20150803174503

下一步是编译方法

编译须要c源文件代码,返回是否成功编译

public bool Compile(string csrccode);

方法中须要先保存为*.c来编译

由于考虑到后面的并发编译,这个文件名不得与其余的线程重复,避免出现资源占用的错误以及对结果的影响

而文件是跟线程共存亡的,线程结束该文件就无用了

因此文件名依赖于线程

string testID = Thread.CurrentThread.ManagedThreadId.ToString();

经过获取线程id做为文件名,能够保证不会影响其余进程的运行,若是经过加锁的方式来并发反而会影响效率

 

Unnamed QQ Screenshot20150803184622

写入文件,结合上面掌握的一些代码,来编写,这时候一些调试性代码能够关闭,不用在控制台输出影响效率

Unnamed QQ Screenshot20150803184645

接下来就要运行程序来判断输入输出流

先运行程序

Unnamed QQ Screenshot20150803184720

运行等待输入输出操做

Unnamed QQ Screenshot20150803184834

析构时候须要作关闭操做避免后台运行没有正常关闭致使内存异常

Unnamed QQ Screenshot20150803184935

输出,结果配对,不匹配返回false,匹配返回true

Unnamed QQ Screenshot20150803185117

输入部分,判断程序是否已经退出了(退出不关闭依旧能够正常输入流)

Unnamed QQ Screenshot20150803185222

结束判断,若是程序还在运行中证实缺乏输入或者输出,不能彻底匹配测试,返回错误,关闭程序

Unnamed QQ Screenshot20150803191338

虽然尝试使用c++相似的输入输出流的重载,可是返回对象时候不能返回引用对象,要是new对象,可能我写法上有问题致使没法很好地重载,否则能够连续作输入输出判断,错误经过异常抛出的操做,如今暂时不成功

编写完成就开始测试

Unnamed QQ Screenshot20150803191620

引用类库

Unnamed QQ Screenshot20150803191650

对象new

Unnamed QQ Screenshot20150803191719

测试一下简单的hello world

Unnamed QQ Screenshot20150803191747

判断代码,判断输出一次hello world!再输入一次hello world!

Unnamed QQ Screenshot20150803191808

结果是错误的由于我多输入了一次无效的操做

注释掉无效的操做后

Unnamed QQ Screenshot20150803191911

运行成功

#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也好,都是不能读取数据,只会阻塞等待,因此程序只能不断输入,再一次性输出,这会致使系统只能有一次流完整读取操做。

Unnamed QQ Screenshot20150803195952

运行

Unnamed QQ Screenshot20150803200026

卡在第一次读取输出流中

这意味着程序没法输入输出按次判断,只能完整输入判断输出

Unnamed QQ Screenshot20150803200138

修改只有一次写入一次读取

Unnamed QQ Screenshot20150803200204

此时能够看到正确的结果

因为原先的猜测没法成立,如今简化代码单纯完成输入输出检测不进行顺序检测

最后经过public bool RunTest(string exein,string exeout)进行判断

Unnamed QQ Screenshot20150803211510

一样是刚才那份代码

Unnamed QQ Screenshot20150803211545

Unnamed QQ Screenshot20150803211612

运行一下能够经过

接下来有一句容易致使超时的语句要进行处理

exeRun.StandardOutput.ReadToEnd();

由于readtoend可能因为死循环,等待输出等缘由阻塞或者卡死,不做超时处理会有大量的死掉的进程,并且还没法给客户端及时的信息反馈

这时候咱们就要单独对这个语句作一个超时处理

先作一个时间处理

ManualResetEvent timeEvent = new ManualResetEvent(false);

Unnamed QQ Screenshot20150803214531

创建委托,完成任务着set一下事件

而后主进程会异步调用改委托

proc.BeginInvoke(null, null, null);

bool flag = timeEvent.WaitOne(time, false);

而后线程在等待时间内不断检测是否改变timeEvent标志

改变了的话里面返回true

不然会超时后为false

若是超时常规退出不是行的了

Unnamed QQ Screenshot20150803215033

只能kill掉死掉的进程

Unnamed QQ Screenshot20150803215124

为了实现超时时间的可控性,为runtest函数重载

增长一个timeout参数能够应对算法较复杂的程序,类内默认初始值为5000ms

而后进行测试肯定可用,准备用于下一个综合实验

经过该实验我掌握了exe的外部调用,以及对输入输出流的读写控制,以及gcc编译器工具在windows下的编译使用的方法,以及不断修改到最后作出可超时检测的c编译器调用与程序测试的程序,因为技术知识缺乏,暂时未能解决对输入输出的步骤控制

相关文章
相关标签/搜索