总体概述
开源推动了IT技术的高速发展,日新月异持续创新。越来越多的企业使用开源技术构建自己的商业软件,用"“如果我能看得更远一点的话,是因为我站在巨人的肩膀上"来说,"我的产品如果能持续高效生产价值,是得到了开源的帮助"。一个应用也是类似,为完成需求,经常需要借助外部代码片段、库、程序。对于交互外部程序,有几种方式,通过命令执行调用、IPC通信等,本文对Windows下命令执行调用封装展开描述。
备注:C语言下编码,由于篇幅关系不对Linux做跨平台支持实现。
接口实现
C下命令执行函数有不少,Windows下有winExec, CreateProcess, System, pipopen, ShellExecute等,具体用法需要到Microsoft官网看开发手册。下面就CreateProcess,辅助其他API实现可返回结果、指定命令执行时间的命令执行封装。
实现思路为:
创建子进程负责命令的执行
创建线程负责输出的读取
创建管道联通子进程的输出和线程的输入
父进程根据时间和子进程状态做执行超时的处理
具体实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define w_create_thread(x, y, z, a, b, c) ({HANDLE hd; if (!(hd = CreateThread(x,y,z,a,b,c))) exit(0); hd;})
#define WM_ERROR_TIMEOUT 1
#define WM_BUFFER_MAX 1024
#define WM_STRING_MAX 6108864
#define WM_POOL_SIZE 8
int wm_task_nice = 0;
typedef char CHAR;
typedef void* HANDLE;
typedef struct ThreadInfo {
CHAR *output;
HANDLE pipe;
} ThreadInfo;
#include <namedpipeapi.h>
#include <winerror.h>
#include <handleapi.h>
#include <processthreadsapi.h>
#include <windows.h>
#include <synchapi.h>
#include <fileapi.h>
static DWORD WINAPI Reader(LPVOID args);
// Execute command with timeout of secs
int wm_exec(char *command, char **output, int *status, int secs) {
printf("windows ...\n");
HANDLE hThread = NULL;
DWORD dwCreationFlags;
STARTUPINFO sinfo = { 0 };
PROCESS_INFORMATION pinfo = { 0 };
ThreadInfo tinfo = { 0 };
int retval = 0;
int winerror = 0;
sinfo.cb = sizeof(STARTUPINFO);
if (output) {
sinfo.dwFlags = STARTF_USESTDHANDLES;
if (!CreatePipe(&tinfo.pipe, &sinfo.hStdOutput, NULL, 0)) {
winerror = GetLastError();
printf("at wm_exec(): CreatePipe(%d)", winerror);
return -1;
}
sinfo.hStdError = sinfo.hStdOutput;
if (!SetHandleInformation(sinfo.hStdOutput, HANDLE_FLAG_INHERIT, 1)) {
winerror = GetLastError();
printf("at wm_exec(): SetHandleInformation(%d)", winerror);
return -1;
}
}
// Create child process and close inherited pipes
dwCreationFlags = wm_task_nice < -10 ? HIGH_PRIORITY_CLASS :
wm_task_nice < 0 ? ABOVE_NORMAL_PRIORITY_CLASS :
wm_task_nice == 0 ? NORMAL_PRIORITY_CLASS :
wm_task_nice < 10 ? BELOW_NORMAL_PRIORITY_CLASS :
IDLE_PRIORITY_CLASS;
if (!CreateProcess(NULL, command, NULL, NULL, TRUE, dwCreationFlags, NULL, NULL, &sinfo, &pinfo)) {
winerror = GetLastError();
printf("at wm_exec(): CreateProcess(%d)", winerror);
return -1;
}
if (output) {
CloseHandle(sinfo.hStdOutput);
hThread = w_create_thread(NULL, 0, Reader, &tinfo, 0, NULL);
}
switch (WaitForSingleObject(pinfo.hProcess, secs ? (unsigned)(secs * 1000) : INFINITE)) {
case 0:
if (status) {
DWORD exitcode;
GetExitCodeProcess(pinfo.hProcess, &exitcode);
*status = exitcode;
}
break;
case WAIT_TIMEOUT:
TerminateProcess(pinfo.hProcess, 1);
retval = WM_ERROR_TIMEOUT;
break;
default:
winerror = GetLastError();
printf("at wm_exec(): WaitForSingleObject(%d)", winerror);
TerminateProcess(pinfo.hProcess, 1);
retval = -1;
}
if (output) {
if (WaitForSingleObject(hThread, 1000) == WAIT_TIMEOUT) {
TerminateThread(hThread, 1);
WaitForSingleObject(hThread, INFINITE);
}
if (retval >= 0) {
*output = tinfo.output ? tinfo.output : strdup("");
} else {
free(tinfo.output);
}
CloseHandle(hThread);
CloseHandle(tinfo.pipe);
}
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
return retval;
}
// Reading thread's start point
DWORD WINAPI Reader(LPVOID args) {
ThreadInfo *tinfo = (ThreadInfo *)args;
CHAR buffer[WM_BUFFER_MAX + 1];
DWORD length = 0;
DWORD nbytes;
while (ReadFile(tinfo->pipe, buffer, 1024, &nbytes, NULL), nbytes > 0) {
int nextsize = length + nbytes;
if (nextsize <= WM_STRING_MAX) {
tinfo->output = (char*)realloc(tinfo->output, nextsize + 1);
memcpy(tinfo->output + length, buffer, nbytes);
length = nextsize;
tinfo->output[length] = '\0';
} else {
printf("String limit reached.");
break;
}
}
return 0;
}
int main()
{
char *out = NULL;
int ret = 0;
wm_exec("systeminfo.exe", &out, &ret, 10);
printf("result: %d£º%s\n", ret, out?out:"None");
if (out)
{
free(out);
out = NULL;
}
return 0;
}
备注:代码在Windows 10 X86 下编译通过
参考文献
[1] microsoft的开发者文档
[2] wazuh源码