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

99封情书 提交于 2019-12-10 17:48:35

运行环境: vs2013

框架: .net4.5

c编译器:mingw 32位

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

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

选择tmd gcc 32位编译器下载

配置好后我们就可以使用该编译器对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编译器调用与程序测试的程序,由于技术知识缺少,暂时未能解决对输入输出的步骤控制

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!