Ejecutar archivo externo desde dentro de C++

Aug 21 2020

Estoy tratando de crear una aplicación que en la parte frontal presente al usuario un editor de texto para ingresar el código y luego ejecutaré ese código y devolveré el resultado. Pensé que sería un proyecto divertido intentar construir mi propia versión de leetcode como un proyecto de aprendizaje.

En este momento, esto es lo que estoy haciendo para ejecutar el código provisto. Digamos que estamos ejecutando código python, porque eso es todo lo que he implementado en este momento.

Primero tomo el código que envía el usuario y creo un archivo que contiene el código dado:

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;
}

Lo siguiente que hago es crear un archivo de salida y ejecutar el archivo creado anteriormente, pero envío mi stdout/stderr a otro archivo de salida:

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;
}

Finalmente, devuelvo lo que obtuve de mi archivo de salida y así es como estoy ejecutando mi código.

Tengo que pensar que hay una manera más fácil de hacer esto. Especialmente porque estoy escribiendo tanto en un archivo y luego leyendo de él, ¿hay alguna manera de deshacerme de eso?

También quiero incluir más de un idioma en el futuro, así que no quiero usar ninguna biblioteca que sea específica de un determinado idioma.

Por último, este es mi primer proyecto de C++, ¡así que me encantaría cualquier consejo de C++!

Editar: quiero eventualmente paralelizar este código y encontrar alguna forma de encapsular el programa para que no pueda dañar el sistema en el que se está ejecutando. Si tal vez hay algún programa externo que sería bueno para eso, házmelo saber y también me da su stderr/stdout, házmelo saber.

Editar: como alguien preguntó, aquí está el repositorio completohttps://github.com/lkelly93/coderunner

Respuestas

2 MartinYork Aug 23 2020 at 00:13

En lugar de system()lo que deberías popen().

La diferencia es que el sistema ejecuta el comando en un subproceso sin acceso a este proceso, mientras que popen ejecuta el comando en un subproceso pero proporciona acceso a los flujos de entrada y salida de los subprocesos.

Esto le permitirá ejecutar los subprocesos y transmitir la entrada a los procesos directamente (desde el campo de entrada que proporcionó para la entrada estándar) y luego leer la salida de los procesos y escribirla en el campo de salida en su interfaz de usuario.

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);
 

Ordenado por lo relacionado, no necesita guardar su secuencia de comandos de pythong como un archivo. El comando python acepta -como nombre, lo que significa leer el script desde la entrada estándar en lugar del archivo con nombre.

Entonces puede ejecutar el comando python (con popen()) y luego escribir el script que desea ejecutar en el flujo de entrada del archivo producido.

Esto eliminará la necesidad de archivos intermedios.

1 aki Aug 21 2020 at 19:56

Cosas apreciables:

  • Documentación de implementaciones y secciones realizadas mediante comentarios.
  • Clases, funciones y ficheros siguiendo el Principio de Responsabilidad Única .
  • Nombres descriptivos de funciones. (no variables como señalo a continuación)
  • Marco de prueba! Pero las pruebas también deben verificar las funciones internas, no solo todo el programa.

La terminología de flujos de archivos y nombres de archivos es muy confusa y me hace buscar tipos de devolución de funciones o declaraciones de variables con demasiada frecuencia.

Program::outputFilees el nombre del archivo que no está claro aquí. Lo confundí con FILE*.

En otro lugar, std::ofstream output;la salida suena como el contenido de salida del programa, ¡pero es una transmisión!

std::string output = getOutputFileData(this->outputFile);¡Y aquí está otra vez una cuerda!


El código no se ocupa de las rutas absolutas y relativas.

La prueba falla con esto:

runnerFiles/0x1005c05c0output.txt does not exist.

Con tal código, sería muy reacio a usar rm. Como máximo, guarde todos los archivos desechables en una carpeta y pídale al usuario que los elimine.


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

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

std::stringstreamse puede evitar y puede usar concatenar std::strings directamente siempre que el primer elemento sea un std::string.

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

El código getOutputFileDataque usa FILE*y el búfer de caracteres (¡que ni siquiera asignó!) se puede reemplazar con lo siguiente (agregar manejo de errores)

  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

Dado que no necesita un control fino sobre las líneas, no se moleste con getline.

Preferir iostreams para E/S. Los iostreams son seguros, flexibles y extensibles.

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

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

Se puede hacer más corto como

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

No se moleste en cerrar si no es necesario. Cuando outputsale del alcance, el archivo se cerrará solo. Es la misma razón por la que no elimina todos los destructibles triviales std::vectoro matrices que se limpiarán automáticamente.


Use const &o std::string_viewdonde las cadenas solo se leen. Son baratos para pasar e indican la intención de que el contenido no se modifique.

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);

C ++ 20 lo tendrá, containspor lo que le ahorrará unas pocas líneas.

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

this->code

En lugar de this->, considere agregar o anteponer _las variables para indicar que son miembros privados.


En mi opinión, es más legible si el orden de implementación sigue el orden de declaración de las funciones.

En Program, el constructor puede ir a la parte superior del archivo, en lugar de a la parte inferior.

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