воскресенье, 9 октября 2011 г.

Несложная обертка для Graphviz. Строим граф из файла из-под Windows.

Не так давно потребовалось визуализировать AST-дерево. В принципе, для визуализации у Graphviz есть библиотеки для языка C++, однако не хотелось их тащить ради такой мелкой цели. В результате родился следующий несложный код:

/*! Функция для создания графа из файла. Решение WIN32-only
    \param[in] firstname строка, использующаяся для формирования 
                         имени конечного файла
    \param[in] filename имя файла с графом
*/
void spawn_dot(const char * firstname,const std::string & filename)
{
 //Создадим структуру для выходных параметров создания процесса
 PROCESS_INFORMATION * pi=new PROCESS_INFORMATION();
 STARTUPINFOA * inf=new STARTUPINFOA();
 memset(pi,0,sizeof(PROCESS_INFORMATION));
 memset(inf,0,sizeof(STARTUPINFOA));
 //Заполним параметры запуска. Указываем то, что выходной поток дочернего
 //процесса будет перенаправлен
 inf->cb=sizeof(STARTUPINFOA);
 inf->dwFlags=STARTF_USESTDHANDLES;
 //Сформируем имя конечного файла
 std::string output_filename=firstname;
 output_filename+=".png";
 //Здесь происходит очень интересная вещь:
 //Если просто перенаправить поток выходного процесса в файл
 //то есть шанс, что он туда ничего не запишет, так как не будет очищен буффер
 //дескриптора выходного потока. Поэтому мы, сохраняем контроль над ним,
 //перенаправляя наш выходной поток в файл и передавая его дескриптор
 //в дочерний процесс.
 freopen(output_filename.c_str(),"wb",stdout);
 inf->hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
 //Формируем строку вызова. Нам нужно, чтобы dot вызвался
 //По информации из переменной среды PATH, поэтому мы должны сформировать её полностью
 //Кроме того из документации следует, что указатель должен быть изменяемым.
 char * tmp=new char[271];
 strcpy(tmp,"dot.exe ");
 strcat(tmp,filename.c_str());
 strcat(tmp," -Tpng");
 //Создаем процесс
 BOOL result = CreateProcessA(NULL,tmp, NULL, NULL, TRUE,
                              0, NULL,NULL, inf, pi);
 //Если создать процесс не удалось
 if (result==FALSE)
 {
  //Восстанавливаем изначальный выходной поток и выводим ошибку
  freopen("CONOUT$","wb",stdout);
  printf("Failed to spawn dot %d\n",GetLastError());
 }
 else
 {
  //Дожидаемся завершения процесса
  WaitForSingleObject(pi->hProcess,INFINITE);
  //Закрываем дескрипторы и восстанавливаем выходной поток.
  CloseHandle(pi->hProcess);
  CloseHandle(pi->hThread);
  freopen("CONOUT$","wb",stdout);
 }
 //Очищаем память
 delete pi;
 delete inf;
 delete tmp;
}