Executar arquivo externo de dentro do C++

Aug 21 2020

Estou tentando criar um aplicativo que no front-end apresente ao usuário um editor de texto para inserir o código e, em seguida, executarei esse código e retornarei o resultado. Achei que seria um projeto divertido tentar construir minha própria versão do leetcode como um projeto de aprendizado.

No momento, é isso que estou fazendo para executar o código fornecido. Digamos que estamos executando o código python, porque isso é tudo que implementei agora.

Primeiro, pego o código que o usuário envia e crio um arquivo que contém o código fornecido:

std::string python(std::string code){
    std::string langCommand = "python3 ";
    std::string outFile;

    //I am hoping to parallelize this operation so I add threadID to output
    outFile = createOutFileName("PythonRunner.py");

    std::ofstream output;
    output.open(outFile);
    output << code;
    output.close();
    return langCommand + outFile;
}

A próxima coisa que faço é criar um arquivo de saída e executar o arquivo criado anteriormente, mas envio meu stdout/stderr para outro arquivo de saída:

std::string Program::run(){
    std::string command = createFile(this->lang, this->code);

    this->outputFile = createOutFileName("output.txt");

    std::stringstream newCommand;
    newCommand << command;
    newCommand << ">> ";
    newCommand << outputFile;
    newCommand << " 2>&1";

    system(newCommand.str().c_str());
    std::string output = getOutputFileData(this->outputFile);
    cleanupFiles(command);
    return output;
}

Por fim, devolvo o que obtive do meu arquivo de saída e é assim que estou executando meu código.

Eu tenho que pensar que há uma maneira mais fácil de fazer isso. Especialmente porque estou escrevendo tanto em um arquivo e depois lendo dele, existe alguma maneira de me livrar disso?

Eu também quero incluir mais de um idioma no futuro, então não quero usar nenhuma biblioteca específica para um determinado idioma.

Por fim, este é meu primeiro projeto C++, então adoraria receber dicas de C++!

Edit: Eu quero eventualmente paralelizar este código e encontrar alguma maneira de encapsular o programa para que ele não danifique o sistema em que está sendo executado. Se houver algum programa externo que seja bom para isso, me avise e também me dê seu stderr/stdout me avise.

Edit: Como alguém perguntou, aqui está o repo inteirohttps://github.com/lkelly93/coderunner

Respostas

2 MartinYork Aug 23 2020 at 00:13

Ao invés de system()você deveria popen().

A diferença é que o sistema executa o comando em um subprocesso sem acesso a esses processos, enquanto o popen executa o comando em um subprocesso, mas fornece acesso aos fluxos de entrada e saída dos subprocessos.

Isso permitirá que você execute os subprocessos e transmita a entrada diretamente para os processos (a partir do campo de entrada fornecido para entrada padrão) e, em seguida, leia a saída dos processos e grave-a no campo de saída em sua interface de usuário.

FILE*  proc = popen(command);
std::string inputFromUser = getUserInputFromUI();
// Using fwrite() correctly left to user.
// You need to check for errors and continue etc.
fwrite(inputFromUser.c_str(), 1, inputFromUser.size(), proc);

char  buffer[100];
std::size_t size;
while((size = fread(buffer, 1, 100, proc)) != 0) {
    // Check for read errors here.
    sendToUserInterface(std::string(bufffer, buffer + size));
}

pclose(proc);
 

Classificado como relacionado, você não precisa salvar seu script pythong como um arquivo. O comando python aceita -como um nome, o que significa ler o script da entrada padrão em vez do arquivo nomeado.

Assim, você pode executar o comando python (com popen ()) e escrever o script que deseja executar no fluxo de entrada do arquivo produzido.

Isso removerá a necessidade de quaisquer arquivos intermediários.

1 aki Aug 21 2020 at 19:56

coisas apreciaveis:

  • Documentação para implementações e seções feitas usando comentários.
  • Classes, funções e arquivos seguindo o Princípio da Responsabilidade Única .
  • Nomes descritivos de funções. (não variáveis ​​como observo abaixo)
  • Estrutura de teste! Mas os testes devem verificar as funções internas também, não apenas o programa inteiro.

A terminologia de fluxos de arquivos e nomes de arquivos é muito confusa e me faz procurar tipos de retorno de função ou declaração de variável com muita frequência.

Program::outputFileé o nome do arquivo que não está claro aqui. Eu confundi com FILE*.

Em outro lugar, std::ofstream output;a saída soa como o conteúdo de saída do programa, mas é um fluxo!

std::string output = getOutputFileData(this->outputFile);E aqui está uma corda de novo!


O código não cuida de caminhos absolutos e relativos.

O teste falha com isto:

runnerFiles/0x1005c05c0output.txt does not exist.

Com esse código, eu ficaria muito relutante em usar arquivos rm. No máximo, mantenha todos os arquivos descartáveis ​​em uma pasta e peça ao usuário para excluí-lo.


std::stringstream newCommand;
newCommand << command;
newCommand << ">> ";
newCommand << outputFile;
newCommand << " 2>&1";

system(newCommand.str().c_str());

std::stringstreampode ser evitado e você pode usar concatenate std::strings diretamente, desde que o primeiro item seja um std::string.

std::string newCommand = command + ">> " + outputFile + "2>&1";

O código getOutputFileDataque usa FILE*e char buffer (que você nem alocou!) pode ser substituído pelo seguinte (adicione tratamento de erros)

  std::ifstream run_output{outFileLocation};
  std::stringstream buffer;
  buffer << run_output.rdbuf();
  return buffer.str();
  • https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring?noredirect=1&lq=1

Como você não precisa de um controle fino sobre as linhas, não se preocupe com getline.

Prefira iostreams para E/S. iostreams são seguros, flexíveis e extensíveis.

  • https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rio-streams

std::ofstream output;
output.open(outFile);
output << code;
output.close();

Pode ser feito mais curto como

std::ofstream output(outFile);
output << code;

Não se preocupe em fechar se não for necessário. Quando outputsai do escopo, o arquivo será fechado por si só. É a mesma razão pela qual você não sai por aí excluindo todos os std::vectorarrays ou arrays triviais destrutíveis que serão limpos automaticamente.


Use const &ou std::string_viewonde as strings são apenas lidas. Eles são baratos para distribuir e indicam a intenção de que o conteúdo não será modificado.

std::string createFile(std::string lang, std::string code)
std::string getOutputFileData(std::string outFileLocation)
bool isSupportedLanguage(std::string lang)
void Program::cleanupFiles(std::string oldCommand)

auto iter = supportedLanguages.find(lang);

O C++20 terá containsisso para economizar algumas linhas.

  • https://en.cppreference.com/w/cpp/container/unordered_map

this->code

Em vez de this->, considere anexar ou preceder _as variáveis ​​para indicar que são membros privados.


É IMO mais legível se a ordem de implementação seguir a ordem de declaração das funções.

Em Program, o construtor pode ir para a parte superior do arquivo, em vez da parte inferior.

  • https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#nl16-use-a-conventional-class-member-declaration-order