環境
Ubuntu 18.04 (WSL2 / Windows 11)
.NET 5.0 / C# 9.0
gcc 7.5.0
やること
ワークスペース直下にCソースを生成し、gccでコンパイルして実行まで.NETアプリ(C#)で行います。gccが吐いたエラーはそのまま表示します。
ソース生成
// using System.Text;
// using System.IO;
var sb = new StringBuilder();
sb.AppendLine($@"#include <stdio.h>");
sb.AppendLine($@"");
sb.AppendLine($@"int main() {{");
sb.AppendLine($@" printf(""Hello C!\n"");");
sb.AppendLine($@" return 0;");
sb.AppendLine($@"}}");
var writer = new StreamWriter("hello.c");
writer.WriteLine(sb.ToString());
writer.Close();
これで、↓が生成されます。
#include <stdio.h>
int main() {
printf("Hello C!\n");
return 0;
}
ちょっと例が悪いですが、ソースには変数の埋め込みとかしたいはずなので $@""
形式の補完文字列を使っています。この文字列には、{a}
や{a * b}
のような形で(ほとんど)任意の変数や式を書くことができ、また改行文字などをエスケープできます(かわりに、文字として出力させたい場合は"
のほか{}
も2つ重ねて書く必要があります)。ちなみに$@
から始まる補完文字列は複数行をまとめて書けるので、たとえば
string source = $@"
#include <stdio.h>
int main() {{
printf(""Hello C!\n"");
return 0;
}}
";
みたいにすることもできます。ただ、ソース文字列の左にあるインデントが出力先にそのまま反映される(エディタ・フォーマッタの設定次第?)ので、たとえば同じ方法でCではなくPythonのソースファイルを生成すると怒られる可能性があります。ソース部分がインデントされていなければ問題ないはず。
コンパイル・実行
// using System.Diagnostics;
var pInfo = new ProcessStartInfo()
{
FileName = "gcc",
Arguments = "hello.c",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var process = Process.Start(pInfo);
process.WaitForExit();
var output = process.StandardOutput.ReadToEnd();
Console.WriteLine(output);
var error = process.StandardError.ReadToEnd();
Console.WriteLine(error);
UseShellExecute
を false
にしておかないと、C#側で出力を読めません。逆に言えば、C#側で読みだす必要がなければUseShellExecute
はtrue
にしておくことで標準出力をシェルにそのまま吐き出してくれます(その場合はRedirectStandardOutput
はfalse
にしておかないと例外)。ここではコンパイルの成否によってそのまま実行するかいったん止めるか分けたかったのでこうなっています。
2022/03/20追記
...
標準エラーもリダイレクトしないとコンパイルの成否判定はできません(ソース修正済み、RedirectStandardError = true
とすればOK)。
当たり前ですがすっかり失念していました...
...
Arguments
には空白区切りにして複数の引数を記述できます。たとえば、
Arguments = "hello.c -o hello"
などとすることもできます。gccなら-march
や-O
オプションなどもOK。
同じ要領で、FileName
をa.out
(または-o
オプションで指定した実行ファイル名)にしてやればコンパイルしたCアプリを実行できます。