はじめに
とあるプログラムがC言語で書かれていて、それをwebシステムとしてnodeで実行したいと思ったのでやり方を調べました。整理すると、大したことはないです。
概要
準備
- c言語のソースをコンパイルして、モジュールを作成
- Webサーバーとして
express
を起動する
アクセス時
-
express
のサーバー処理がモジュールをキックし、標準出力を得る - 標準出力を元にクライアントへ返す
作ったソース
結果を一応貼っておくと、↓こういう文字を返すだけのwebシステムですw
詳細
全体
フォルダ構成
役割は、scripts
がc言語関係で、ソースとビルド用のスクリプト。server
がexpress
のソースです。
ちなみに最終的に、c言語のモジュールをserver
の下へ置きます。
c言語のソース
#include <stdio.h>
int main() {
printf("Hello, World!(c言語より!)\n");
return 0;
}
なんでもいいんです。標準出力すれば。
日本語のやり取りができることの確認もしてます。
c言語ソースのコンパイル
Render上でビルドしてほしいので、package.json
で次のように設定しておきます。
"scripts": {
"start": "node ./server/app.js",
"build": "npm install && node ./scripts/build-with-c.js"
},
Renderの設定で、ビルドはnpm run build
、起動はnpm run start
とすることを想定しています。
const { exec } = require('child_process');
const { copyFileSync } = require('fs');
const { platform } = require('os');
// コンパイルするCプログラムのパスと出力ファイル名
const scriptsDirName = "scripts";
const inputFilePath = `./${scriptsDirName}/hello.c`;
const outputFileName = 'hello' + (platform() === 'win32' ? '.exe' : '');
const outputFilePath = `./${scriptsDirName}/${outputFileName}`;
const buildOutputFilePath = `./server/${outputFileName}`;
// Cプログラムをコンパイル
exec(`gcc ${inputFilePath} -o ${outputFilePath}`, (compileError) => {
if (compileError) {
console.error(`コンパイルエラー: ${compileError}`);
process.exit(1);
}
console.log('Cプログラムのコンパイル成功');
// コンパイルしたプログラムをビルドディレクトリにコピー
copyFileSync(outputFilePath, buildOutputFilePath);
console.log('ビルドディレクトリにhelloをコピー');
});
ファイル名をごちゃごちゃやっているのは、開発環境ではexeにしたかったというだけです。
本筋は、exec
でgcc
を使ってコンパイルし、できたモジュールをcopyFileSync
で./server/
へコピーしてます。
gcc
は、Renderが持ってます。
expressサーバー
const express = require("express")
const path = require("path");
const { exec } = require("child_process");
const app = express()
const port = 3000
app.get('/', (req, res) => {
const outputFileName = 'hello' + (process.platform === 'win32' ? '.exe' : '');
const buildOutputPath = path.join(__dirname, outputFileName);
exec(buildOutputPath, (runError, stdout, stderr) => {
if (runError) {
console.error(`実行エラー: ${runError}`);
return res.status(500).send(`実行エラー: ${runError}`);
}
if (stderr) {
console.error(`標準エラー出力: ${stderr}`);
return res.status(500).send(`標準エラー出力: ${stderr}`);
}
res.send(stdout);
});
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
exec
で、出力したファイルを非同期で実行し、エラーなく進んだら、その標準出力stdout
を/
の戻り値にしてます。
あ、以上でした。この結果が、最初にも貼った下記のもの。日本語も渡ってます。
おわりに
JavaScriptでひとまずできました。Renderはほんと便利で感謝です。
つぎはTypeScriptでやってみます。
実は最初TypeScriptでやったらいろいろな問題が出たのですが、問題の切り分けが大変でした。(そのときのアプリ名がapp-c-lang。)そのため先にJavaScriptでやって足掛かりを作ってみた次第です。JavaScriptでできた上でのTypeScriptなら、TypeScript特有の手続きは何なのかっていうことが分かりやすいかなと思って。