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

运行环境: vs2013

框架: .net4.5

c编译器:mingw 32位

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

http://tdm-gcc.tdragon.net/download

选择tmd gcc 32位编译器下载

配置好后我们就可以使用该编译器对c程序进行编程

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

C#-JudgeSystem判题系统-C#编译C程序_第1张图片

 

保存为test.c

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

C#-JudgeSystem判题系统-C#编译C程序_第2张图片

通过类似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调用的程序,不然会无法控制一直运行在后台导致内存溢出

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

C#-JudgeSystem判题系统-C#编译C程序_第3张图片

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

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

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

接下来做测试,先测试正确编译的情况

Unnamed QQ Screenshot20150801175318

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

修改源代码制造语法错误

C#-JudgeSystem判题系统-C#编译C程序_第4张图片

C#-JudgeSystem判题系统-C#编译C程序_第5张图片

编译会提示出错信息

C#-JudgeSystem判题系统-C#编译C程序_第6张图片

再修改源代码

C#-JudgeSystem判题系统-C#编译C程序_第7张图片

这次是可以编译成功,过程中的警告会有所显示

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

C#-JudgeSystem判题系统-C#编译C程序_第8张图片

先做出可以提供输入输出的程序进行编译

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

C#-JudgeSystem判题系统-C#编译C程序_第9张图片

 

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

p.StartInfo.RedirectStandardInput = true;

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

这个程序并没有做泛化出来来适配各种各样的输入输出情况

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

C#-JudgeSystem判题系统-C#编译C程序_第10张图片

建立编译测试类库

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

然后我们定义一些类库的接口

构造函数有两个

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

Unnamed QQ Screenshot20150803174433

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

Unnamed QQ Screenshot20150803174503

下一步是编译方法

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

public bool Compile(string csrccode);

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

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

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

所以文件名依赖于线程

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

通过获取线程id作为文件名,可以保证不会影响其他进程的运行,如果通过加锁的方式来并发反而会影响效率

 

C#-JudgeSystem判题系统-C#编译C程序_第11张图片

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

C#-JudgeSystem判题系统-C#编译C程序_第12张图片

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

先运行程序

C#-JudgeSystem判题系统-C#编译C程序_第13张图片

运行等待输入输出操作

C#-JudgeSystem判题系统-C#编译C程序_第14张图片

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

C#-JudgeSystem判题系统-C#编译C程序_第15张图片

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

C#-JudgeSystem判题系统-C#编译C程序_第16张图片

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

C#-JudgeSystem判题系统-C#编译C程序_第17张图片

结束判断,如果程序还在运行中证明缺少输入或者输出,不能完全匹配测试,返回错误,关闭程序

C#-JudgeSystem判题系统-C#编译C程序_第18张图片

虽然尝试使用c++类似的输入输出流的重载,但是返回对象时候不能返回引用对象,要是new对象,可能我写法上有问题导致无法很好地重载,不然可以连续做输入输出判断,错误通过异常抛出的操作,现在暂时不成功

编写完成就开始测试

Unnamed QQ Screenshot20150803191620

引用类库

Unnamed QQ Screenshot20150803191650

对象new

Unnamed QQ Screenshot20150803191719

测试一下简单的hello world

C#-JudgeSystem判题系统-C#编译C程序_第19张图片

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

C#-JudgeSystem判题系统-C#编译C程序_第20张图片

结果是错误的因为我多输入了一次无效的操作

注释掉无效的操作后

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

C#-JudgeSystem判题系统-C#编译C程序_第21张图片

运行

C#-JudgeSystem判题系统-C#编译C程序_第22张图片

卡在第一次读取输出流中

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

Unnamed QQ Screenshot20150803200138

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

C#-JudgeSystem判题系统-C#编译C程序_第23张图片

此时可以看到正确的结果

由于原先的猜想无法成立,现在简化代码单纯完成输入输出检测不进行顺序检测

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

C#-JudgeSystem判题系统-C#编译C程序_第24张图片

同样是刚才那份代码

C#-JudgeSystem判题系统-C#编译C程序_第25张图片

C#-JudgeSystem判题系统-C#编译C程序_第26张图片

运行一下可以通过

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

exeRun.StandardOutput.ReadToEnd();

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

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

先做一个时间处理

ManualResetEvent timeEvent = new ManualResetEvent(false);

C#-JudgeSystem判题系统-C#编译C程序_第27张图片

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

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

proc.BeginInvoke(null, null, null);

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

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

改变了的话里面返回true

否则会超时后为false

如果超时常规退出不是行的了

C#-JudgeSystem判题系统-C#编译C程序_第28张图片

只能kill掉死掉的进程

Unnamed QQ Screenshot20150803215124

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

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

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

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

你可能感兴趣的:(多线程,server,C#,编译,判题系统)