Originally Posted by
Salem
Great minds... I tried that approach yesterday. The only problem was that CreateProcess returns the handle to the command interpreter rather than the process executing the target command, giving incorrect results for user and kernel times (if you didn't need the command interpreter, you could just launch the target process directly). Today, I used a job object which seems to work well:
Code:
#define _WIN32_WINNT 0x0500
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
typedef union
{
LARGE_INTEGER ul;
FILETIME ft;
} TIME;
#define FT_SECONDS (10000000) /* Number of filetime units in a second. */
#define FT_MINUTES (60 * FT_SECONDS) /* Number of filetime units in a minute. */
#define FT_MILLI (FT_SECONDS / 1000) /* Number of filetime units in a millisecond. */
/* Get a TCHAR version of WinMain's lpCmdLine. That is the command
* line excluding the program name. szCmdArgs should be 4096 TCHARs.
*/
void GetCommandArgs(LPTSTR szCmdArgs)
{
LPTSTR szEndOfProgramName;
LPTSTR szCompleteCmdLine = GetCommandLine();
if (szCompleteCmdLine[0] == TEXT('\"'))
szEndOfProgramName = _tcschr(&szCompleteCmdLine[1], TEXT('\"'));
else
szEndOfProgramName = _tcschr(szCompleteCmdLine, TEXT(' '));
_tcsncpy(szCmdArgs, (szEndOfProgramName ? &szEndOfProgramName[1] : TEXT("")), 4090);
// Terminate as _tcsncpy will not always do so...
szCmdArgs[4090] = TEXT('\0');
}
/* Run a command with the default shell and associate with a job object.
*/
HANDLE RunCommand(LPCTSTR szCommand, HANDLE hJob)
{
TCHAR szComSpec[MAX_PATH];
TCHAR szCmd[2048];
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if ( SUCCEEDED(StringCchPrintf(szCmd, ARRAY_SIZE(szCmd), TEXT("/c %s"), szCommand)) &&
GetEnvironmentVariable(TEXT("COMSPEC"), szComSpec, ARRAY_SIZE(szComSpec)) &&
CreateProcess(szComSpec, szCmd, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) )
{
AssignProcessToJobObject(hJob, pi.hProcess);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
return pi.hProcess;
}
return NULL;
}
/* main
*/
int main(void)
{
HANDLE hProcess;
HANDLE hJob;
TIME tmCreate, tmExit, tmKernel, tmUser, tmElapsed, tmDummy[2];
TCHAR szCommand[4096];
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jbai;
GetCommandArgs(szCommand);
hJob = CreateJobObject(NULL, NULL);
hProcess = RunCommand(szCommand, hJob);
/* Use GetProcessTimes to get the real time. */
GetProcessTimes(hProcess, &tmCreate.ft, &tmExit.ft, &tmDummy[0].ft, &tmDummy[1].ft);
tmElapsed.ul.QuadPart = tmExit.ul.QuadPart - tmCreate.ul.QuadPart;
/* Query the job object to get user and kernel time for all processes in the job object. */
QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation, &jbai, sizeof(jbai), NULL);
tmUser.ul = jbai.TotalUserTime;
tmKernel.ul = jbai.TotalKernelTime;
CloseHandle(hProcess);
CloseHandle(hJob);
printf("real %lum%lu.%03lus\n",
(ULONG) ((tmElapsed.ul.QuadPart / FT_MINUTES)),
(ULONG) ((tmElapsed.ul.QuadPart % FT_MINUTES) / FT_SECONDS),
(ULONG) ((tmElapsed.ul.QuadPart % FT_SECONDS) / FT_MILLI));
printf("user %lum%lu.%03lus\n",
(ULONG) ((tmUser.ul.QuadPart / FT_MINUTES)),
(ULONG) ((tmUser.ul.QuadPart % FT_MINUTES) / FT_SECONDS),
(ULONG) ((tmUser.ul.QuadPart % FT_SECONDS) / FT_MILLI));
printf("sys %lum%lu.%03lus\n",
(ULONG) ((tmKernel.ul.QuadPart / FT_MINUTES)),
(ULONG) ((tmKernel.ul.QuadPart % FT_MINUTES) / FT_SECONDS),
(ULONG) ((tmKernel.ul.QuadPart % FT_SECONDS) / FT_MILLI));
return 0;
}
And, for the record, here is the original:
Code:
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
typedef union
{
ULARGE_INTEGER ul;
FILETIME ft;
} TIME;
#define FT_SECONDS (10000000) /* Number of filetime units in a second. */
#define FT_MINUTES (60 * FT_SECONDS) /* Number of filetime units in a minute. */
#define FT_MILLI (FT_SECONDS / 1000) /* Number of filetime units in a millisecond. */
/* Get a TCHAR version of WinMain's lpCmdLine. That is the command
* line excluding the program name. szCmdArgs should be 4096 TCHARs.
*/
void GetCommandArgs(LPTSTR szCmdArgs)
{
LPTSTR szEndOfProgramName;
LPTSTR szCompleteCmdLine = GetCommandLine();
if (szCompleteCmdLine[0] == TEXT('\"'))
szEndOfProgramName = _tcschr(&szCompleteCmdLine[1], TEXT('\"'));
else
szEndOfProgramName = _tcschr(szCompleteCmdLine, TEXT(' '));
_tcsncpy(szCmdArgs, (szEndOfProgramName ? &szEndOfProgramName[1] : TEXT("")), 4090);
// Terminate as _tcsncpy will not always do so...
szCmdArgs[4090] = TEXT('\0');
}
/* Run a command with the default shell.
*/
HANDLE RunCommand(LPCTSTR szCommand)
{
TCHAR szComSpec[MAX_PATH];
TCHAR szCmd[2048];
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if ( SUCCEEDED(StringCchPrintf(szCmd, ARRAY_SIZE(szCmd), TEXT("/c %s"), szCommand)) &&
GetEnvironmentVariable(TEXT("COMSPEC"), szComSpec, ARRAY_SIZE(szComSpec)) &&
CreateProcess(szComSpec, szCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) )
{
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
return pi.hProcess;
}
return NULL;
}
/* main
*/
int main(void)
{
HANDLE hProcess;
TIME tmCreate, tmExit, tmKernel, tmUser, tmElapsed;
TCHAR szCommand[4096];
GetCommandArgs(szCommand);
hProcess = RunCommand(szCommand);
GetProcessTimes(hProcess, &tmCreate.ft, &tmExit.ft, &tmKernel.ft, &tmUser.ft);
CloseHandle(hProcess);
tmElapsed.ul.QuadPart = tmExit.ul.QuadPart - tmCreate.ul.QuadPart;
printf("real %lum%lu.%03lus\n",
(ULONG) ((tmElapsed.ul.QuadPart / FT_MINUTES)),
(ULONG) ((tmElapsed.ul.QuadPart % FT_MINUTES) / FT_SECONDS),
(ULONG) ((tmElapsed.ul.QuadPart % FT_SECONDS) / FT_MILLI));
printf("user %lum%lu.%03lus\n",
(ULONG) ((tmUser.ul.QuadPart / FT_MINUTES)),
(ULONG) ((tmUser.ul.QuadPart % FT_MINUTES) / FT_SECONDS),
(ULONG) ((tmUser.ul.QuadPart % FT_SECONDS) / FT_MILLI));
printf("sys %lum%lu.%03lus\n",
(ULONG) ((tmKernel.ul.QuadPart / FT_MINUTES)),
(ULONG) ((tmKernel.ul.QuadPart % FT_MINUTES) / FT_SECONDS),
(ULONG) ((tmKernel.ul.QuadPart % FT_SECONDS) / FT_MILLI));
return 0;
}