原标题:ConsolePauserPlusPlus 透彻指南
Q: 为什么叫这个名字?
A: ConsolePauserPlus & ConsolePauser
这篇文章可以看作是对 solstice23 Dalao 的补充,所以又加了一个 Plus,简称 CPPP。
在 CPPP 基础上再次增加新功能,命名为 ci,精简版为 ci_mini。
0. 开始之前
首先你需要一台电脑(雾,还需要安装好 VSCode,然后就可以继续了。
Command Runner 的使用:配置指南 by Ptilopsis_w Dalao。
1. 重定向文件输入输出
你不觉得复制粘贴样例很痛苦么?
emm,你用 freopen 啊!
啊? 我不想手打,,,
然后就有了这篇文章。
其实 cmd 里提供了重定向文件IO的功能,如:
P0000.exe < sample.in > sample.ans
套到上面的编译运行指令里就好,
如果我们需要将 DEBUG 信息输出到控制台而非输出文件,可以用 std::cerr
,用法与 std::cin
基本相同。
2. 计时误差
大多数情况下,计时误差主要来源于:
评测机状态波动,这点在个人电脑尤为明显;
调用计时函数,终端等误差。
虽然前者已经决定了 99.114514%,但为了严谨,还是引入了系统调用误差。
在原版 ConsolePauserPlus 和 ConsolePauser 中调用 GetClockTick()
和 GetClockFrequency()
实现计时,为减少函数调用时间损耗,将其替换成了精度更高更简单的 chrono
,但还是存在终端调用的时间损耗,因此,我们创建了一个空程序,模拟调用并计时,计入 deviation
,
这个空程序的源代码只有 12 bytes:
int mian(){}
有它我们可以得到当前状态下系统调用误差,经测试,误差正常范围在 $\text{0.005s-0.008s}$ 之间,虽然这个数据并没有什么用,但还是保留在了 DEBUG 中。
保留在 CPPP 的 DEBUG 中,在 ci_mini 中移除,ci 待确定。
3. 调试信息颜色
为了好 van,在输出中加入了字体颜色控制。
4. 加入编译运行功能
为了支持更多的功能,将编译运行纳入程序。
程序传参示例:
CPPP.exe 2 P0000
2
表示功能类型:
在 CPPP 中:0
为仅编译,1
为仅运行,2
为编译运行;
在 ci 和 ci_mini 中:1
为仅编译,2
为仅运行,3
为编译运行;
P0000
表示需要编译/运行的程序名,不含后缀名。
暂不支持自定义复杂性传参,倒是可以加入咕咕列表。
ci 中支持较复杂的传参。
此时你就可以将 setting.json 的内容改为:
"command-runner.terminal.autoFocus": true,
"command-runner.commands": {
"compile-cpp": "cls && cd ${fileDirname} && ci_mini 1 ${fileBasenameNoExtension}",
"run-cpp": "cls && cd ${fileDirname} && ci_mini 2 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
"compile&run-cpp": "cls && cd ${fileDirname} && ci_mini 3 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
// "compile-cpp": "cd ${fileDirname} && consolepauserplusplus 0 ${fileBasenameNoExtension}",
// "run-cpp": "cd ${fileDirname} && consolepauserplusplus 1 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
// "compile&run-cpp": "cd ${fileDirname} && consolepauserplusplus 2 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
// "compile-cpp": "cls && cd ${fileDirname} && echo Compiling... && g++ -g -std=c++14 -O2 -lpsapi ${fileBasenameNoExtension}.cpp -o ${fileBasenameNoExtension}.exe && cls && echo Compilation Completed!",
// "run-cpp": "cls && cd ${fileDirname} && echo The program is running! && consolepauserplus 1 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
// "compile&run-cpp": "cls && cd ${fileDirname} && echo Compiling... && g++ -g -std=c++14 -O2 ${fileBasenameNoExtension}.cpp -o ${fileBasenameNoExtension}.exe && cls && echo Compilation Completed! && cls && cd ${fileDirname} && echo The program is running! && consolepauserplus 2 ${fileBasenameNoExtension}.exe < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out"
},
5. fc 判断结果
调用 system("fc a.out a.ans >> a.diff")
可以实现文件比较,进而判断样例运行是否通过。
在 CPPP 中存在误判情况,ci 中运行正常。
6. 完整代码
ci_mini
#include <bits/stdc++.h>
#include <windows.h>
#include <psapi.h>
using namespace std;
typedef unsigned long uint;
const string compileOptions = " -g -std=c++14 -O2 -lpsapi ";
double usedMemory = 0;
int usedMemoryUnit = 0;
char usedMemoryUnitsName[5][5] = {"B", "KB", "MB", "GB", "TB"};
uint ExecuteCommand(int opt, string command) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof si);
si.cb = sizeof(si);
memset(&pi, 0, sizeof pi);
if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
fprintf(stderr, "\nFailed to execute \"%s\":",command.c_str());
}
WaitForSingleObject(pi.hProcess, INFINITE);
if (opt == 1) {
PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(pi.hProcess, &pmc, sizeof(pmc));
usedMemory = pmc.PeakPagefileUsage;
while (usedMemory >= 1024){
usedMemory /= 1024.0;
usedMemoryUnit++;
}
}
uint result = 0;
GetExitCodeProcess(pi.hProcess, &result);
return result;
}
void CompileFile(string filename) {
string compile = "g++ " + compileOptions + filename + ".cpp -o " + filename + ".exe";
uint returnVal = ExecuteCommand(0, compile);
if (returnVal) {
fprintf(stderr, "\033[1;33;40mci >> Compile Error\033[0m\n"), exit(0);
} else {
fprintf(stderr, "\033[0;32;40mci >> Compiled Successfully\033[0m\n");
}
}
void RunFile(string filename) {
string command = filename + ".exe";
chrono::time_point<chrono::system_clock> start = chrono::system_clock::now();
uint returnVal = ExecuteCommand(1, command);
chrono::time_point<chrono::system_clock> end = chrono::system_clock::now();
chrono::duration<double> elapsed = end - start;
if (returnVal) {
return fprintf(stderr, "\033[1;35;40mci >> Runtime Error\033[0m\n"), void();
} else {
fprintf(stderr, "\033[0;32;40mci >> Run Successfully\033[0m\n");
}
fprintf(stderr, "\033[1;34;40mTime \033[0m%0.3lf s \033[1;34;40mMemery \033[0m%0.2lf %s\n",
elapsed, usedMemory, usedMemoryUnitsName[usedMemoryUnit]);
}
int main(int argc, char* argv[]) {
if ((string)argv[1] == "1") {
CompileFile((string)argv[2]);
}
if ((string)argv[1] == "2") {
RunFile((string)argv[2]);
}
if ((string)argv[1] == "3") {
CompileFile((string)argv[2]);
RunFile((string)argv[2]);
}
return 0;
}
ci
#include <string>
#include <chrono>
#include <stdio.h>
#include <direct.h>
#include <iostream>
#include <windows.h>
#include <Psapi.h>
#include <SimpleIni.h>
using namespace std;
typedef unsigned long uint;
const char version[32] = "0.1.2 Dev";
const int MAX_COMMAND_LENGTH = 32768;
const int MAX_ERROR_LENGTH = 2048;
string CiPath = "";
string CiSettingPath = "";
// ci_setting.ini
bool showDebug = 1;
#define DEBUG if(showDebug)
string compileOptions = "-g -std=c++14 -O2 -lpsapi";
string samplePath = "";
double usedMemory = 0;
int usedMemoryUnit = 0;
char usedMemoryUnitsName[5][5] = {"B", "KB", "MB", "GB", "TB"};
void Init();
void FirstUse();
void CheckFile();
void MakeSampleExe();
void RunFile(string);
void CompileFile(string);
string GetCiPath();
string GetErrorMessage();
uint ExecuteCommand(int, string);
int main(int argc, char* argv[]) {
Init();
if (argv[1][0] == '-') {
// help
if (argv[1][1] == 'h') {
fprintf(stderr,"ci >> help\n");
fprintf(stderr, "-------------------------------------\n");
fprintf(stderr, "-h(elp) | ci help \n");
fprintf(stderr, "-a(bout) | ci about \n");
fprintf(stderr, "-d(ebug) | ci debug \n");
fprintf(stderr, "-v(ersion) | ci version \n");
fprintf(stderr, "-s(ettings) | ci settings \n");
fprintf(stderr, "1 <filename> | compile file \n");
fprintf(stderr, "2 <filename> | run file \n");
fprintf(stderr, "3 <filename> | cp & run file \n");
fprintf(stderr, "4 | check file(fc) \n");
fprintf(stderr, "compile <filename> | compile file \n");
fprintf(stderr, "run <filename> | run file \n");
fprintf(stderr, "cprun <filename> | cp & run file \n");
fprintf(stderr, "check | check file(fc) \n");
fprintf(stderr, "make sample.exe | DEBUG & TEST \n");
fprintf(stderr, "-------------------------------------\n");
}
// version
if (argv[1][1] == 'v') {
fprintf(stderr, "\nci version: %s\n", version);
}
// settings
if (argv[1][1] == 's') {
fprintf(stderr,"ci >> settings(current)\n");
fprintf(stderr, "-------------------------------------------------------------\n");
fprintf(stderr, "sample_path | %s\n", samplePath.c_str());
fprintf(stderr, "compile_options | %s\n", compileOptions.c_str());
fprintf(stderr, "-------------------------------------------------------------\n");
fprintf(stderr,"plause edit \'ci_settings.ini\' to change settings \n");
}
// about
if (argv[1][1] == 'a') {
//
}
// debug
if (argv[1][1] == 'd') {
fprintf(stderr,"ci >> debug(current status)\n");
fprintf(stderr, "-------------------------------------------------------------\n");
fprintf(stderr, "ci_path | %s\n", CiPath.c_str());
fprintf(stderr, "ci_setting_path | %s\n", CiSettingPath.c_str());
fprintf(stderr, "sample_path | %s\n", samplePath.c_str());
fprintf(stderr, "compile_options | %s\n", compileOptions.c_str());
fprintf(stderr, "-------------------------------------------------------------\n");
}
return 0;
}
if ((string)argv[1] == "make") {
if ((string)argv[2] == "sample.exe") {
MakeSampleExe();
}
}
if ((string)argv[1] == "1" || (string)argv[1] == "compile") {
CompileFile((string)argv[2]);
}
if ((string)argv[1] == "2" || (string)argv[1] == "run" ) {
RunFile((string)argv[2]);
}
if ((string)argv[1] == "3" || (string)argv[1] == "cprun" ) {
CompileFile((string)argv[2]);
RunFile((string)argv[2]);
}
if ((string)argv[1] == "4" || (string)argv[1] == "check" ) {
CheckFile();
}
}
string GetErrorMessage() {
string result(MAX_ERROR_LENGTH,0);
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),&result[0],result.size(),NULL);
for(int i = result.length()-1;i >= 0;i--) {
if(isspace(result[i])) {
result[i] = 0;
} else {
break;
}
}
return result;
}
uint ExecuteCommand(int opt, string command) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof si);
si.cb = sizeof(si);
memset(&pi, 0, sizeof pi);
if (samplePath != "" && opt == 1) {
string Input = samplePath + "sample.in";
string Output= samplePath + "sample.out";
si.hStdInput = freopen(Input.c_str(), "r", stdin);
si.hStdOutput=freopen(Output.c_str(), "w", stdout);
}
if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
fprintf(stderr, "\nFailed to execute \"%s\":",command.c_str());
fprintf(stderr, "\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str());
}
WaitForSingleObject(pi.hProcess, INFINITE);
if (opt == 1) {
PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(pi.hProcess, &pmc, sizeof(pmc));
usedMemory = pmc.PeakPagefileUsage;
while (usedMemory >= 1024){
usedMemory /= 1024.0;
usedMemoryUnit++;
}
}
uint result = 0;
GetExitCodeProcess(pi.hProcess, &result);
return result;
}
void MakeSampleExe() {
freopen("ci_sample.cpp", "w", stdout);
printf("int main(){}");
fclose(stdout);
DEBUG fprintf(stderr, "ci >> Write Successfully\n");
string compile = "g++ -g ci_sample.cpp -o ci_sample.exe";
uint returnVal = ExecuteCommand(0, compile);
if (returnVal) {
fprintf(stderr, "ci >> Compile Error\n");
} else {
fprintf(stderr, "ci >> Compiled Successfully\n");
}
}
void CompileFile(string filename) {
string compile = "g++ " + compileOptions + " " + filename + ".cpp -o " + filename + ".exe";
DEBUG fprintf(stderr, "ci >> Compile %s\n", compile.c_str());
uint returnVal = ExecuteCommand(0, compile);
if (returnVal) {
fprintf(stderr, "\033[1;33;40mci >> Compile Error\033[0m\n");
} else {
fprintf(stderr, "\033[0;32;40mci >> Compiled Successfully\033[0m\n");
}
}
void RunFile(string filename) {
string command = filename + ".exe";
if (samplePath != "") {
cerr << samplePath << endl;
}
DEBUG fprintf(stderr, "ci >> Command %s\n", command.c_str());
chrono::time_point<chrono::system_clock> start = chrono::system_clock::now();
uint returnVal = ExecuteCommand(1, command);
chrono::time_point<chrono::system_clock> end = chrono::system_clock::now();
chrono::duration<double> elapsed = end - start;
if (returnVal) {
return fprintf(stderr, "\033[1;35;40mci >> Runtime Error\033[0m\n"), void();
} else {
fprintf(stderr, "\033[0;32;40mci >> Run Successfully\033[0m\n");
}
fprintf(stderr, "\033[1;34;40mTime \033[0m%0.3lf s \033[1;34;40mMemery \033[0m%0.2lf %s\n",
elapsed, usedMemory, usedMemoryUnitsName[usedMemoryUnit]);
}
void CheckFile() {
if (samplePath == "") {
return fprintf(stderr, "\033[1;31;40mci >> Please set the sample_path first\033[0m\n"), void();
}
string command = "fc " + samplePath + "sample.out " + samplePath + "sample.ans > " + samplePath + "sample.diff";
cerr << command << endl;
if (system(command.c_str())) {
fprintf(stderr, "\033[1;31;40mci >> Difference are saved in \'sample.diff\'\033[0m\n");
} else {
fprintf(stderr, "\033[0;32;40mci >> No Difference\033[0m\n");
}
}
void Init() {
CiPath = GetCiPath();
CiSettingPath = CiPath + "ci_setting.ini";
CSimpleIniA ini;
ini.SetUnicode();
SI_Error rc = ini.LoadFile(CiSettingPath.c_str());
if (rc < 0) {
return FirstUse(), void();
}
showDebug = ini.GetBoolValue("ci_setting", "show_debug");
samplePath = (string)ini.GetValue("ci_setting", "sample_path");
compileOptions = (string)ini.GetValue("ci_setting", "compile_options");
}
void FirstUse() {
CSimpleIniA ini;
DEBUG fprintf(stderr, "ci >> init ci_setting.ini\n");
samplePath = CiPath;
freopen(CiSettingPath.c_str(), "w", stdout);
printf("; use \';\' as a note\n");
printf("; sample_path example: D:\\code\\.vscode\\\n");
fclose(stdout);
SI_Error rc = ini.LoadFile(CiSettingPath.c_str());
if (rc < 0) {
return fprintf(stderr, "ci >> Error !"), void();
}
ini.SetBoolValue("ci_setting", "show_debug", showDebug);
ini.SetValue("ci_setting", "sample_path", samplePath.c_str());
ini.SetValue("ci_setting", "compile_options", compileOptions.c_str());
ini.SaveFile(CiSettingPath.c_str());
}
string GetCiPath() {
char _szPath[255 + 1]={0};
GetModuleFileName(NULL, _szPath, 255);
(strrchr(_szPath, '\\'))[1] = 0;
return _szPath;
}
CPPP
已经咕了,不再维护。
#include <string>
#include <chrono>
#include <stdio.h>
#include <iomanip>
#include <iostream>
#include <windows.h>
#include <Psapi.h>
using std::string;
#define MAX_COMMAND_LENGTH 32768
#define MAX_ERROR_LENGTH 2048
// LONGLONG GetClockTick() {
// LARGE_INTEGER dummy;
// QueryPerformanceCounter(&dummy);
// return dummy.QuadPart;
// }
// LONGLONG GetClockFrequency() {
// LARGE_INTEGER dummy;
// QueryPerformanceFrequency(&dummy);
// return dummy.QuadPart;
// }
// void PauseExit(int exitcode) {
// system("pause");
// exit(exitcode);
// }
string GetErrorMessage() {
string result(MAX_ERROR_LENGTH,0);
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),&result[0],result.size(),NULL);
// Clear newlines at end of string
for(int i = result.length()-1;i >= 0;i--) {
if(isspace(result[i])) {
result[i] = 0;
} else {
break;
}
}
return result;
}
string GetCommand(int argc,char** argv) {
string result;
for(int i = 1;i < argc;i++) {
// Quote the arguments in case they contain spaces
// Could use additional quoting code around the argument
if(string(argv[i]).find(" ")!=string::npos) {
result += string("\"") + string(argv[i]) + string("\"");
} else {
result += string(argv[i]);
}
// Add a space except for the last argument
if(i != (argc-1)) {
result += string(" ");
}
// std::cerr << "DEBUG::Result " << result << "\n";
}
if(result.length() > MAX_COMMAND_LENGTH) {
printf("\nError: Length of command line string is over %d characters\n",MAX_COMMAND_LENGTH);
}
return result;
}
double usedMemory;
int usedMemoryUnit = 0;
char usedMemoryUnitsName[5][5] = {"B", "KB", "MB", "GB", "TB"};
DWORD ExecuteCommand(string& command) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si,0,sizeof(si));
si.cb = sizeof(si);
memset(&pi,0,sizeof(pi));
if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
printf("\nFailed to execute \"%s\":",command.c_str());
printf("\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str());
}
WaitForSingleObject(pi.hProcess, INFINITE); // Wait for it to finish
PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(pi.hProcess, &pmc, sizeof(pmc));
usedMemory = pmc.PeakPagefileUsage;
while (usedMemory >= 1024){
usedMemory /= 1024.0;
usedMemoryUnit++;
}
DWORD result = 0;
GetExitCodeProcess(pi.hProcess, &result);
return result;
}
DWORD ExecuteCommandNoCountMemory(string& command) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si,0,sizeof(si));
si.cb = sizeof(si);
memset(&pi,0,sizeof(pi));
if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
printf("\nFailed to execute \"%s\":",command.c_str());
printf("\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str());
}
WaitForSingleObject(pi.hProcess, INFINITE); // Wait for it to finish
DWORD result = 0;
GetExitCodeProcess(pi.hProcess, &result);
return result;
}
int main(int argc, char* argv[]) {
// std::cerr << "DEBUG::Argc " << argc << "\n";
// std::cerr << "DEBUG::Argv " << argv[1] << "\n";
// First make sure we aren't going to read nonexistent arrays
// if(argc < 2) {
// std::cerr << "Usage: CPPP.exe <options> <filename>\n";
// }
// Make us look like the paused program
SetConsoleTitle(argv[2]);
// Then build the to-run application command
string command = GetCommand(argc-1,argv+1);
// Compile
if (argv[1][0] == '0' || argv[1][0] == '2') {
string compile = "g++ -g -std=c++14 -O2 -lpsapi " + command + ".cpp -o " + command + ".exe";
DWORD returnvalue = ExecuteCommandNoCountMemory(compile);
// std::cerr << "DEBUG::Compile " << compile << "\n";
}
if (argv[1][0] == '0') {
return 0;
}
// Save starting timestamp
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
// Then execute said command
DWORD returnvalue = ExecuteCommand(command);
// Get ending timestamp
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
string tmp = "sample.exe"; ExecuteCommandNoCountMemory(tmp);
std::chrono::time_point<std::chrono::system_clock> end2 = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::chrono::duration<double> deviation = end2 - end;
// Deviation
std::cerr << "DEBUG::System Deviation " << deviation.count() << " s\n";
if (returnvalue) {
return std::cerr << "\033[0;35m"<< "Runtime Error" << "\033[0m" << " ", 0;
}
if (system("fc D:\\code\\.vscode\\sample.out D:\\code\\.vscode\\sample.ans >> sample.diff")) {
std::cerr << "\033[0;31m" << "Wrong Answer" << "\033[0m" << " ";
} else {
std::cerr << "\033[0;32m" << "Accepted"<< "\033[0m" << " ";
}
// Done? Print return value of executed program
std::cerr << "\033[0;34m" << "Return: " << returnvalue << " ";
std::cerr << "Time: " << std::setprecision(4) << elapsed.count() << " s ";
std::cerr << "Memory: " << std::setprecision(4) << usedMemory << " " << usedMemoryUnitsName[usedMemoryUnit];
std::cerr << "\033[0m" << std::endl;
}
7. 效果展示
2024.02.06 考古,图床寄了。
8. 更新日志
2022 09 03 Add ci code
2022 08 27 修复已知 Bug;
2022 08 27 精简 ci 代码,命名为 ci_mini;
2022 08 26 开发功能更完备的 ci;
2022 08 25 更新,加入新功能;
2022 08 ?? 基于 ConsolePauserPlus 二次开发;
9. 后记
其实做到这一步就不能再叫 ConsolePauser
了,现在的她更像是一个工具箱,开箱即用,方便自在,但为了感谢前人的贡献,仍沿用其命名。
所以有了 ci 和 ci_mini。