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編譯器調用與程序測試的程序,因爲技術知識缺乏,暫時未能解決對輸入輸出的步驟控制

相關文章
相關標籤/搜索