如何将参数包装在 C 或 C++ 中并将它们传递给系统或 exec*

How to wrap arguments in C or C++ and pass them to system or exec*

本文关键字:系统 exec 包装 参数 C++      更新时间:2023-10-16

我想写一个包装器,用argv做一些简单的事情并调用一些脚本。我有以下要求:

  • 包装器必须是.exe文件
  • 包装器必须能够正确处理空格和引号
  • 包装器将在用户端生成
  • 生成过程必须很小(如使用 https://bellard.org/tcc(

我最初的方法:

编写一个 c 程序以首先清理参数,然后将它们括在引号中,然后调用system。 不幸的是,我无法从systemexec*函数中获得结构良好的行为。 我希望以下所有示例都输出类似arg1=1; arg2=2; arg3=3; arg4=的内容(引号换行中有一些差异(,但它在某些示例中出错,并在 execl 上暂停:

输入文件:

@:: test.bat
@echo off
echo arg1=%1; arg2=%2; arg3=%3; arg4=%4
//minimal-example.c
#include <Windows.h>
#include <stdio.h>
int main( int argc, char ** argv ) {
puts("nExample 1:");
system(""test.bat" "1" "2" "3" ");
puts("nExample 2:");
system("test.bat "1" "2" "3" ");
puts("nExample 3:");
system("test.bat 1 2 "3" ");
puts("nExample 4:");
system(""test.bat" 1 "2" 3 ");
puts("nExample 5:");
system(""test.bat" 1 2 3 ");
puts("nExample 6:");
execl(argv[0], "test.bat", "1", "2", "3", NULL);
return 0;
}

输出运行:

Example 1:
'test.bat" "1" "2" "3' is not recognized as an internal or external command,
operable program or batch file.
Example 2:
arg1="1"; arg2="2"; arg3="3"; arg4=
Example 3:
arg1=1; arg2=2; arg3="3"; arg4=
Example 4:
'test.bat" 1 "2' is not recognized as an internal or external command,
operable program or batch file.
Example 5:
arg1=1; arg2=2; arg3=3; arg4=
Example 6:
arg1=1; arg2=2; arg3=3; arg4=

(示例 6 暂停,直到我按Enter(

问题:

  1. 有没有办法正确地包装路径/参数,以便在system中允许空格?
  2. 我可以转义参数中的引号以system吗?
  3. 有没有一种非阻塞的方式来运行exec*
  4. exec*的方法能否确保包装程序的 stdin stdout 和 stderr 行为正确(没有奇怪的溢出或奇怪的阻塞?

这样的事情应该可以工作:

string cmd = "test.bat";
for(int i = 1; i < argc; i++) {
cmd += " ";
cmd += argv[i]
}
system(cmd.c_str());

当然,包含空格的参数需要通过添加引号来进一步处理,而带有引号的参数可能需要转义,并且在 args 包含不直接处理的内容的情况下,还有许多其他复杂性(

作为替代方法,您可以查看使用创建进程运行批处理文件

实现

我最终为两种不同的场景编写了两个包装器:一个用于保持终端窗口打开,另一个用于静默运行。

它的工作原理是将exe文件重命名为name-of-script.exe,然后包装器将尝试运行<exedir>binname-of-script.bat并传递所有命令行参数。

我使用程序资源黑客在exe中放置漂亮的图标或版本信息,使其看起来像一个合适的程序。

法典

tcc.exe -D_UNICODE -DNOSHELL launcher.c -luser32 -lkernel32 -mwindows -o launcher-noshell.exe
tcc.exe -D_UNICODE launcher.c -luser32 -lkernel32 -o launcher.exe
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdbool.h>
#include <tchar.h>
#ifdef NOSHELL
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
#else
int main( int argc, char ** argv ) 
#endif
{
//*******************************************
//Get commanline string as a whole
//*******************************************
TCHAR* cmdArgs = GetCommandLineW();
TCHAR* cmdPath;
cmdPath = (TCHAR*) malloc((_tcslen(cmdArgs)+1)*2);
_tcscpy(cmdPath, cmdArgs);

//*******************************************
//Split filepath, filename, and commandline
//*******************************************
bool inQuote = false;
bool isArgs = false;
int j = 0;
for(int i=0; i<_tcslen(cmdArgs)+1; i++){
//must be easier way to index unicode string
TCHAR c = *(TCHAR *)(&cmdArgs[i*2]);
if(c == L'"'){inQuote = !inQuote;}
if(c == L' ' && !inQuote){ isArgs = true;}
if(isArgs){
cmdPath[i*2]   = '';
cmdPath[i*2+1] = '';
}
//do for both unicode bits
cmdArgs[j*2  ] = cmdArgs[i*2  ];
cmdArgs[j*2+1] = cmdArgs[i*2+1];
//sync j with i after filepath
if(isArgs){ j++; }
}

//*******************************************
//Remove quotes around filepath
//*******************************************
if(*(TCHAR *)(&cmdPath[0]) == L'"'){
cmdPath = &cmdPath[2];
}
int cmdPathEnd = _tcslen(cmdPath);
if(*(TCHAR *)(&cmdPath[(cmdPathEnd-1)*2]) == L'"'){
cmdPath[(cmdPathEnd-1)*2]='';
cmdPath[(cmdPathEnd-1)*2+1]='';
}

//*******************************************
//Find basedir of cmdPath
//*******************************************
TCHAR* cmdBaseDir;
cmdBaseDir = (TCHAR*) malloc((_tcslen(cmdPath)+1)*2);
_tcscpy(cmdBaseDir, cmdPath);

int nrOfSlashed = 0;
int slashLoc = 0;
for(int i=0; i<_tcslen(cmdBaseDir); i++){
//must be easier way to index unicode string
TCHAR c = *(TCHAR *)(&cmdBaseDir[i*2]);
if(c == L'' || c == L'//'){
nrOfSlashed+=1;
slashLoc=i;
}
}
if(nrOfSlashed==0){
_tcscpy(cmdBaseDir, L".");
}else{
cmdBaseDir[2*slashLoc] = '';
cmdBaseDir[2*slashLoc+1] = '';  
}

//*******************************************
//Find filename without .exe
//*******************************************
TCHAR* cmdName;
cmdName = (TCHAR*) malloc((_tcslen(cmdPath)+1)*2);
_tcscpy(cmdName, cmdPath);
cmdName = &cmdPath[slashLoc==0?0:slashLoc*2+2];
int fnameend = _tcslen(cmdName);
if(0 < fnameend-4){
cmdName[(fnameend-4)*2]   = '';
cmdName[(fnameend-4)*2+1] = '';
}
//_tprintf(L"%sn", cmdName);
//********************************************
//Bat name to be checked
//********************************************
int totlen;
TCHAR* batFile1  = cmdBaseDir;
TCHAR* batFile2  = L"\bin\";
TCHAR* batFile3  = cmdName;
TCHAR* batFile4  = L".bat";
totlen = (_tcslen(batFile1)+ _tcslen(batFile2)+ _tcslen(batFile3)+ _tcslen(batFile4));
TCHAR* batFile;
batFile = (TCHAR*) malloc((totlen+1)*2);
_tcscpy(batFile, batFile1);
_tcscat(batFile, batFile2);
_tcscat(batFile, batFile3);
_tcscat(batFile, batFile4);
if(0 != _waccess(batFile, 0)){
system("powershell -command "[reflection.assembly]::LoadWithPartialName('System.Windows.Forms')|out-null;[windows.forms.messagebox]::Show('Could not find the launcher .bat in bin directory.', 'Execution error')" ");
};
//_tprintf(L"%sn", batFile);
//*******************************************
//Get into this form: cmd.exe /c ""c:path...bat" arg1 arg2 ... "
//*******************************************
TCHAR* cmdLine1  = L"cmd.exe /c "";
TCHAR* cmdLine2  = L""";
TCHAR* cmdLine3  = batFile;
TCHAR* cmdLine4  = L"" "; 
TCHAR* cmdLine5  = cmdArgs;
TCHAR* cmdLine6 = L""";
totlen = (_tcslen(cmdLine1)+_tcslen(cmdLine2)+_tcslen(cmdLine3)+_tcslen(cmdLine4)+_tcslen(cmdLine5)+_tcslen(cmdLine6));
TCHAR* cmdLine;
cmdLine = (TCHAR*) malloc((totlen+1)*2);
_tcscpy(cmdLine, cmdLine1);
_tcscat(cmdLine, cmdLine2);
_tcscat(cmdLine, cmdLine3);
_tcscat(cmdLine, cmdLine4);
_tcscat(cmdLine, cmdLine5);
_tcscat(cmdLine, cmdLine6);
//_tprintf(L"%sn", cmdLine);
//************************************
//Prepare and run CreateProcessW
//************************************
PROCESS_INFORMATION pi;
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
#ifdef NOSHELL
CreateProcessW(NULL, cmdLine, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
#else
CreateProcessW(NULL, cmdLine, NULL, NULL, TRUE, NULL,             NULL, NULL, &si, &pi);
#endif
//************************************
//Return ErrorLevel
//************************************
DWORD result = WaitForSingleObject(pi.hProcess,15000);
if(result == WAIT_TIMEOUT){return -2;} //Timeout error
DWORD exitCode=0;
if(!GetExitCodeProcess(pi.hProcess, &exitCode) ){return -1;} //Cannot get exitcode
return exitCode; //Correct exitcode
}