1250 lines
28 KiB
C++
1250 lines
28 KiB
C++
|
//+------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Microsoft Windows
|
|||
|
// Copyright (C) Microsoft Corporation, 1993.
|
|||
|
//
|
|||
|
// File: procswap.cxx
|
|||
|
//
|
|||
|
// Contents: Program for measuring task switching performance
|
|||
|
// between two windows programs. The program creates
|
|||
|
// CTaskSwitch objects...
|
|||
|
//
|
|||
|
// Each Object can wait on one of three things...
|
|||
|
// 1. GetMessage
|
|||
|
// 2. MsgWaitForMultipleObjects
|
|||
|
// 3. WaitForSingleObject (event)
|
|||
|
//
|
|||
|
// and when awoken, will signal another Object in one
|
|||
|
// of three ways...
|
|||
|
// 1. PostMessage
|
|||
|
// 2. SendMessage
|
|||
|
// 3. SetEvent
|
|||
|
//
|
|||
|
// These cases can be combined in any manner to obtain
|
|||
|
// a maxtrix of possible scenarios.
|
|||
|
//
|
|||
|
// The CTaskSwitch objects can be in the same process on
|
|||
|
// different threads, or in different processes.
|
|||
|
//
|
|||
|
// Classes: CEvent - event handling class
|
|||
|
// CTaskSwitch - main task switch class
|
|||
|
//
|
|||
|
//
|
|||
|
// Functions: WinMain - entry point of process
|
|||
|
// ThreadEntry - entry point of spawned threads
|
|||
|
// ThreadWndProc - processes windows messages
|
|||
|
//
|
|||
|
//
|
|||
|
// History: 08-Feb-94 Rickhi Created
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
#include <benchmrk.hxx>
|
|||
|
#include <tchar.h>
|
|||
|
|
|||
|
|
|||
|
// execution parameter structure
|
|||
|
|
|||
|
typedef struct tagSExecParms
|
|||
|
{
|
|||
|
int oloop; // outer loop count
|
|||
|
int iloop; // inner loop count
|
|||
|
HWND hWndOther; // HWND of other process
|
|||
|
HANDLE hEventOther; // Event Handle of other process
|
|||
|
WNDPROC pfnWndProc; // ptr to WndProc function
|
|||
|
TCHAR szFile[20]; // output file name
|
|||
|
TCHAR szWaitEvent[20]; // event name to wait on
|
|||
|
TCHAR szSignalEvent[20]; // event name to signal
|
|||
|
} SExecParms;
|
|||
|
|
|||
|
|
|||
|
typedef enum tagWAITTYPES
|
|||
|
{
|
|||
|
WAIT_EVENT = 1,
|
|||
|
WAIT_MSGWAITFORMULTIPLE = 2,
|
|||
|
WAIT_GETMESSAGE = 3,
|
|||
|
WAIT_SYNCHRONOUS = 4
|
|||
|
} WAITTYPES;
|
|||
|
|
|||
|
typedef enum tagSIGNALTYPES
|
|||
|
{
|
|||
|
SIGNAL_EVENT = 1,
|
|||
|
SIGNAL_POSTMESSAGE = 2,
|
|||
|
SIGNAL_SENDMESSAGE = 3,
|
|||
|
SIGNAL_SYNCHRONOUS = 4
|
|||
|
} SIGNALTYPES;
|
|||
|
|
|||
|
// input names corresponding to the wait types
|
|||
|
LPSTR aszWait[] = {"", "event", "msgwait", "getmsg", "sync", NULL};
|
|||
|
|
|||
|
// input names corresponding to the signal types
|
|||
|
LPSTR aszSignal[] = {"", "event", "postmsg", "sendmsg", "sync", NULL};
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// Name of window class for dispatching messages.
|
|||
|
|
|||
|
#define MY_WINDOW_CLASS TEXT("ProcSwapWindowClass")
|
|||
|
|
|||
|
#define MAX_OLOOP 100
|
|||
|
|
|||
|
|
|||
|
// globals
|
|||
|
|
|||
|
DWORD g_fFullInfo = 0; // write full info or not
|
|||
|
HINSTANCE g_hInst = NULL; // misc windows junk.
|
|||
|
ATOM g_MyClass = 0;
|
|||
|
UINT g_MyMessage = WM_USER;
|
|||
|
|
|||
|
|
|||
|
// function prototype
|
|||
|
LRESULT ThreadWndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam);
|
|||
|
DWORD ThreadEntry(void *param);
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Class: CEvent
|
|||
|
//
|
|||
|
// Purpose: class for blocking & starting threads.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
class CEvent
|
|||
|
{
|
|||
|
public:
|
|||
|
CEvent(LPTSTR szName, HRESULT &hr) { Init(szName, hr); }
|
|||
|
CEvent(void) {m_hdl = NULL; }
|
|||
|
|
|||
|
~CEvent() { CloseHandle(m_hdl); }
|
|||
|
|
|||
|
void Signal(void) { SetEvent(m_hdl); }
|
|||
|
void Reset(void) { ; } // ResetEvent(m_hdl); }
|
|||
|
void BlockS(void) { WaitForSingleObject(m_hdl, 60000); }
|
|||
|
void BlockM(void) { WaitForMultipleObjects(1, &m_hdl, FALSE, 60000); }
|
|||
|
HANDLE *GetHdl(void) { return &m_hdl; };
|
|||
|
|
|||
|
void Init(LPTSTR szName, HRESULT &hr);
|
|||
|
|
|||
|
private:
|
|||
|
|
|||
|
HANDLE m_hdl;
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
void CEvent::Init(LPTSTR szName, HRESULT &hr)
|
|||
|
{
|
|||
|
hr = S_OK;
|
|||
|
|
|||
|
// first try opening the event
|
|||
|
m_hdl = OpenEvent(EVENT_ALL_ACCESS,
|
|||
|
FALSE,
|
|||
|
szName);
|
|||
|
|
|||
|
if (m_hdl == NULL)
|
|||
|
{
|
|||
|
// doesnt exist yet so create it.
|
|||
|
m_hdl = CreateEvent(NULL, // security
|
|||
|
FALSE, // auto reset
|
|||
|
FALSE, // initially not signalled
|
|||
|
szName);
|
|||
|
|
|||
|
if (m_hdl == NULL)
|
|||
|
{
|
|||
|
_tprintf (TEXT("Error Creating CEvent (%s)\n"), szName);
|
|||
|
hr = GetLastError();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_tprintf (TEXT("Created CEvent (%s)\n"), szName);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_tprintf (TEXT("Opened CEvent (%s)\n"), szName);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Class: CTaskSwitch
|
|||
|
//
|
|||
|
// Purpose: class for timing task switches.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
class CTaskSwitch
|
|||
|
{
|
|||
|
public:
|
|||
|
CTaskSwitch(LPSTR lpszCmdLine, HRESULT &hr);
|
|||
|
~CTaskSwitch(void);
|
|||
|
|
|||
|
int MainProcessLoop(void);
|
|||
|
LRESULT ThreadWndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam);
|
|||
|
HRESULT SpawnOtherSide(void);
|
|||
|
|
|||
|
private:
|
|||
|
|
|||
|
// initialization / cleanup methods
|
|||
|
|
|||
|
HRESULT ParseCmdLine(LPSTR lpszCmdLine, SExecParms &execp);
|
|||
|
HRESULT WindowInitialize(WNDPROC pfnWndProc, HWND &hWnd);
|
|||
|
void WindowUninitialize(HWND hWnd);
|
|||
|
void CreateOtherParms(void);
|
|||
|
void WriteExecParms(void);
|
|||
|
void WriteResults(void);
|
|||
|
void Help(void);
|
|||
|
DWORD GetWaitType(LPSTR pszCmd);
|
|||
|
DWORD GetSignalType(LPSTR pszCmd);
|
|||
|
DWORD CreateProc(void);
|
|||
|
|
|||
|
// processing methods
|
|||
|
|
|||
|
HRESULT SendOrWaitFirstSignal(void);
|
|||
|
void ProcessMsgWaitForMultiple(DWORD dwRet);
|
|||
|
void ProcessIncommingEvent(void);
|
|||
|
void UpdateLoopCounters(void);
|
|||
|
void SignalOtherSide(void);
|
|||
|
|
|||
|
// data
|
|||
|
|
|||
|
BOOL g_fDone; // when to exit the loop
|
|||
|
BOOL g_fKicker; // we kick the other guy
|
|||
|
BOOL g_fThreadSwitch; // thread or process switching?
|
|||
|
BOOL g_fWaitMultiple; // wait single or multiple
|
|||
|
|
|||
|
ULONG g_oloop; // outer loop counter
|
|||
|
ULONG g_iloop; // inner loop counter
|
|||
|
|
|||
|
DWORD g_WaitType; // what to wait on
|
|||
|
DWORD g_SignalType; // what to signal
|
|||
|
|
|||
|
// used only for parameter parseing
|
|||
|
DWORD g_WaitType1; // what to wait on
|
|||
|
DWORD g_SignalType1; // what to signal
|
|||
|
DWORD g_WaitType2; // what to wait on
|
|||
|
DWORD g_SignalType2; // what to signal
|
|||
|
|
|||
|
|
|||
|
HWND g_hWndOther; // hWnd of other side
|
|||
|
HWND g_hWndMe; // my hWnd
|
|||
|
|
|||
|
CEvent g_WaitEvent; // event to wait on
|
|||
|
CEvent g_SignalEvent; // event to signal
|
|||
|
|
|||
|
ULONG g_time[MAX_OLOOP]; // place to store the timings.
|
|||
|
CStopWatch g_timer; // global timer proc 1
|
|||
|
|
|||
|
SExecParms g_execp; // execution parameters
|
|||
|
CTestOutput * g_output; // output log
|
|||
|
|
|||
|
HRESULT g_hr; // result code
|
|||
|
|
|||
|
CHAR g_szOtherParms[MAX_PATH]; // parm string for other guy
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// task switch objects - must be global for ThreadWndProc
|
|||
|
|
|||
|
CTaskSwitch *g_pTaskSwitch1 = NULL;
|
|||
|
CTaskSwitch *g_pTaskSwitch2 = NULL;
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// WinMain - main entry point of program. May just call ThreadEntry, or
|
|||
|
// may spawn another thread in the case of thread switching.
|
|||
|
//
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
int WinMain(HINSTANCE hinst, HINSTANCE hPrev, LPSTR lpszCmdLine, int CmdShow)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
// create the first task switch object for this process
|
|||
|
g_pTaskSwitch1 = new CTaskSwitch(lpszCmdLine, hr);
|
|||
|
|
|||
|
if (hr == S_OK)
|
|||
|
{
|
|||
|
// spawn a new thread or a new process to do task switching with.
|
|||
|
hr = g_pTaskSwitch1->SpawnOtherSide();
|
|||
|
|
|||
|
if (hr == S_OK)
|
|||
|
{
|
|||
|
// enter the main processing loop
|
|||
|
g_pTaskSwitch1->MainProcessLoop();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// print the results
|
|||
|
delete g_pTaskSwitch1;
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// ThreadEntry - main entry point for a thread spawned by CreateThread
|
|||
|
// in the case of task switching between threads.
|
|||
|
//
|
|||
|
// Creates an instance of the CTaskSwitch class and invokes it
|
|||
|
// main function.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
DWORD ThreadEntry(void *param)
|
|||
|
{
|
|||
|
LPSTR lpszCmdLine = (LPSTR) param;
|
|||
|
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
// create the second task switch object for this process
|
|||
|
g_pTaskSwitch2 = new CTaskSwitch(lpszCmdLine, hr);
|
|||
|
|
|||
|
if (hr == S_OK)
|
|||
|
{
|
|||
|
// enter the main processing loop
|
|||
|
g_pTaskSwitch2->MainProcessLoop();
|
|||
|
}
|
|||
|
|
|||
|
// print the results
|
|||
|
delete g_pTaskSwitch2;
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Dipatch to the correct CTaskSwitch object if the message is our
|
|||
|
// special message.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
LRESULT ThreadWndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|||
|
{
|
|||
|
if (msg == g_MyMessage)
|
|||
|
{
|
|||
|
// its my special message, go handle it.
|
|||
|
// here i have to select which object to dispatch to for the
|
|||
|
// multithreaded case. i base that decision on lparam.
|
|||
|
|
|||
|
if (lparam == 0)
|
|||
|
{
|
|||
|
// use the first task switch object
|
|||
|
return g_pTaskSwitch1->ThreadWndProc(hWnd, msg, wparam, lparam);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// use the second task switch object
|
|||
|
return g_pTaskSwitch2->ThreadWndProc(hWnd, msg, wparam, lparam);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// let the default window procedure have the message.
|
|||
|
return DefWindowProc(hWnd, msg, wparam, lparam);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Constructor : parse the command line, create the events, create the
|
|||
|
// window, and open a log file.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
CTaskSwitch::CTaskSwitch(LPSTR lpszCmdLine, HRESULT &hr) :
|
|||
|
g_fDone(FALSE),
|
|||
|
g_fKicker(FALSE),
|
|||
|
g_fThreadSwitch(FALSE),
|
|||
|
g_fWaitMultiple(FALSE),
|
|||
|
g_oloop(10),
|
|||
|
g_iloop(100),
|
|||
|
g_WaitType(WAIT_EVENT),
|
|||
|
g_SignalType(SIGNAL_EVENT),
|
|||
|
g_hWndMe(NULL),
|
|||
|
g_hWndOther(NULL),
|
|||
|
g_output(NULL),
|
|||
|
g_hr(S_OK)
|
|||
|
{
|
|||
|
// parse command line and write the parms to log file.
|
|||
|
g_hr = ParseCmdLine(lpszCmdLine, g_execp);
|
|||
|
|
|||
|
if (g_hr == S_OK)
|
|||
|
{
|
|||
|
// Create a log file & write execution parameters
|
|||
|
g_output = new CTestOutput(g_execp.szFile);
|
|||
|
WriteExecParms();
|
|||
|
|
|||
|
// create the window for this thread
|
|||
|
g_hr = WindowInitialize(g_execp.pfnWndProc, g_hWndMe);
|
|||
|
if (g_hr == S_OK)
|
|||
|
{
|
|||
|
// Create the Wait event.
|
|||
|
g_WaitEvent.Init(g_execp.szWaitEvent, g_hr);
|
|||
|
if (g_hr == S_OK)
|
|||
|
{
|
|||
|
// Create the Signal event.
|
|||
|
g_SignalEvent.Init(g_execp.szSignalEvent, g_hr);
|
|||
|
|
|||
|
if (g_hr == S_OK)
|
|||
|
{
|
|||
|
// create paramters to send to other side.
|
|||
|
CreateOtherParms();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// return the results
|
|||
|
hr = g_hr;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Desructor
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
CTaskSwitch::~CTaskSwitch(void)
|
|||
|
{
|
|||
|
// write the results
|
|||
|
WriteResults();
|
|||
|
|
|||
|
// cleanup window registration
|
|||
|
WindowUninitialize(g_hWndMe);
|
|||
|
|
|||
|
// close the log file
|
|||
|
delete g_output;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Spawns either another process or another thread to perform the task
|
|||
|
// switching with.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
HRESULT CTaskSwitch::SpawnOtherSide(void)
|
|||
|
{
|
|||
|
if (g_fKicker)
|
|||
|
{
|
|||
|
// i'm already the second entry, dont spawn anything.
|
|||
|
// sleep for a bit to make sure both sides are ready
|
|||
|
// when i kick things off.
|
|||
|
|
|||
|
Sleep(1000);
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (g_fThreadSwitch)
|
|||
|
{
|
|||
|
// spawn a thread
|
|||
|
|
|||
|
HANDLE hdl;
|
|||
|
DWORD dwId;
|
|||
|
hdl = CreateThread(NULL, // default security
|
|||
|
0, // default stack size
|
|||
|
ThreadEntry, // entry point
|
|||
|
g_szOtherParms, // command line parms
|
|||
|
0, // flags
|
|||
|
&dwId); // threadid
|
|||
|
|
|||
|
if (hdl)
|
|||
|
{
|
|||
|
// dont need the handle
|
|||
|
CloseHandle(hdl);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// what went wrong?
|
|||
|
return GetLastError();
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// spawn a process
|
|||
|
DWORD dwRet = CreateProc();
|
|||
|
if (dwRet != S_OK)
|
|||
|
{
|
|||
|
return dwRet;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// MainProcessLoop - does the main wait & process the event
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
int CTaskSwitch::MainProcessLoop(void)
|
|||
|
{
|
|||
|
MSG msg;
|
|||
|
DWORD dwRet;
|
|||
|
|
|||
|
|
|||
|
// Send, or wait on, the first signal.
|
|||
|
SendOrWaitFirstSignal();
|
|||
|
|
|||
|
|
|||
|
// Reset the timer and enter the main loop.
|
|||
|
g_timer.Reset();
|
|||
|
|
|||
|
|
|||
|
// wait loop - based on the type of event we should receive, we
|
|||
|
// wait here until such an event occurs. Then we send a signal
|
|||
|
// to the other side based on what it expects from us.
|
|||
|
|
|||
|
while (!g_fDone)
|
|||
|
{
|
|||
|
switch (g_WaitType)
|
|||
|
{
|
|||
|
|
|||
|
case WAIT_MSGWAITFORMULTIPLE:
|
|||
|
|
|||
|
// wait here for a message or an event to be signalled
|
|||
|
dwRet = MsgWaitForMultipleObjects(1,
|
|||
|
g_WaitEvent.GetHdl(),
|
|||
|
FALSE,
|
|||
|
600000,
|
|||
|
QS_ALLINPUT);
|
|||
|
|
|||
|
// Dispatch to ThreadWndProc if a message, or
|
|||
|
// to ProcessEvent if an event was signalled
|
|||
|
ProcessMsgWaitForMultiple(dwRet);
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case WAIT_GETMESSAGE:
|
|||
|
|
|||
|
// wait for a windows message
|
|||
|
if (GetMessage(&msg, NULL, 0, 0))
|
|||
|
{
|
|||
|
// dispatches to my ThreadWndProc
|
|||
|
DispatchMessage(&msg);
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case WAIT_EVENT:
|
|||
|
|
|||
|
// wait for the event to be signalled
|
|||
|
if (g_fWaitMultiple)
|
|||
|
g_WaitEvent.BlockM();
|
|||
|
else
|
|||
|
g_WaitEvent.BlockS();
|
|||
|
|
|||
|
// process the event
|
|||
|
ProcessIncommingEvent();
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case WAIT_SYNCHRONOUS:
|
|||
|
|
|||
|
// we have a synchronous singal to the other side, so there is
|
|||
|
// nothing to wait on, we will just go make another synchronous
|
|||
|
// call. this is valid only if the g_SignalType is
|
|||
|
// SIGNAL_SENDMESSAGE.
|
|||
|
|
|||
|
ProcessIncommingEvent();
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
default:
|
|||
|
|
|||
|
// unknown event
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
} // while
|
|||
|
|
|||
|
return msg.wParam; // Return value from PostQuitMessage
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// processes a wakeup from MsgWaitForMultiple. Determines if the event was
|
|||
|
// a message arrival (in which case it Peeks it and Dispatches it, or if it
|
|||
|
// was an event signalled, in which case it calls the event handler.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::ProcessMsgWaitForMultiple(DWORD dwRet)
|
|||
|
{
|
|||
|
MSG msg;
|
|||
|
|
|||
|
if (dwRet == WAIT_OBJECT_0)
|
|||
|
{
|
|||
|
// our event got signalled, update the counters
|
|||
|
ProcessIncommingEvent();
|
|||
|
}
|
|||
|
|
|||
|
else if (dwRet == WAIT_OBJECT_0 + 1)
|
|||
|
{
|
|||
|
// some windows message was received. dispatch it.
|
|||
|
|
|||
|
if (PeekMessage(&msg, g_hWndMe, 0, 0, PM_REMOVE))
|
|||
|
{
|
|||
|
DispatchMessage(&msg);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// our event timed out or our event was abandoned or
|
|||
|
// an error occurred.
|
|||
|
|
|||
|
g_fDone = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// processes an incomming event. Just updates the counters and
|
|||
|
// signals the other side.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::ProcessIncommingEvent(void)
|
|||
|
{
|
|||
|
// update the loop counters
|
|||
|
UpdateLoopCounters();
|
|||
|
|
|||
|
// Signal the other side
|
|||
|
SignalOtherSide();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// process the incomming message
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
LRESULT CTaskSwitch::ThreadWndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|||
|
{
|
|||
|
// save the callers hWnd
|
|||
|
g_hWndOther = (HWND) wparam;
|
|||
|
|
|||
|
// process as usual
|
|||
|
ProcessIncommingEvent();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// updates the global loop counters, reseting the time when the inner
|
|||
|
// loop counter expires, and setting the fDone when the outer and inner
|
|||
|
// loop counters expire.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::UpdateLoopCounters(void)
|
|||
|
{
|
|||
|
if (g_iloop == 0)
|
|||
|
{
|
|||
|
// get time for latest outer loop
|
|||
|
g_time[g_oloop] = g_timer.Read();
|
|||
|
|
|||
|
if (g_oloop == 0)
|
|||
|
{
|
|||
|
// that was the last outerloop, we're done.
|
|||
|
g_fDone = TRUE;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// update the counters
|
|||
|
g_iloop = g_execp.iloop;
|
|||
|
--g_oloop;
|
|||
|
|
|||
|
// restart the timer
|
|||
|
g_timer.Reset();
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// just update the inner loop count
|
|||
|
--g_iloop;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// signals the other process or thread according to the SendType (either
|
|||
|
// signals an event or posts a message).
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::SignalOtherSide(void)
|
|||
|
{
|
|||
|
switch (g_SignalType)
|
|||
|
{
|
|||
|
|
|||
|
case SIGNAL_EVENT:
|
|||
|
|
|||
|
// signal the other sides event
|
|||
|
g_SignalEvent.Signal();
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case SIGNAL_POSTMESSAGE:
|
|||
|
|
|||
|
// post a message to the other sides window handle.
|
|||
|
// lparam tells ThreadWndProc which object to dispatch to, either
|
|||
|
// g_pTaskSwitch1 or g_pTaskSwitch2. We only go to 2 if we are
|
|||
|
// doing thread switches AND the poster is not the kicker.
|
|||
|
|
|||
|
PostMessage(g_hWndOther,
|
|||
|
g_MyMessage,
|
|||
|
(WPARAM)g_hWndMe,
|
|||
|
(g_fThreadSwitch && !g_fKicker));
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case SIGNAL_SENDMESSAGE:
|
|||
|
|
|||
|
// send a message to the other side. this is a synchronous
|
|||
|
// event. see comment in PostMessage above regarding lparam.
|
|||
|
|
|||
|
SendMessage(g_hWndOther,
|
|||
|
g_MyMessage,
|
|||
|
(WPARAM)g_hWndMe,
|
|||
|
(g_fThreadSwitch && !g_fKicker));
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case SIGNAL_SYNCHRONOUS:
|
|||
|
|
|||
|
// the event we received is a synchronous event. there is no need
|
|||
|
// to do anything to wake the other side.
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
default:
|
|||
|
|
|||
|
// unknown signal type
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// signals the other process or thread that it can begin the test. this
|
|||
|
// avoids timings skewed due to process startup latency.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
HRESULT CTaskSwitch::SendOrWaitFirstSignal(void)
|
|||
|
{
|
|||
|
if (g_fKicker)
|
|||
|
{
|
|||
|
// send a signal to drop the otherside into its wait loop, and
|
|||
|
// then call SignalOtherSide to start the cycle, kicking the
|
|||
|
// other side out of his first wait.
|
|||
|
|
|||
|
printf ("Initial Signal to Other Side\n");
|
|||
|
g_SignalEvent.Signal();
|
|||
|
SignalOtherSide();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
printf ("Waiting for Signal From Other Side\n");
|
|||
|
g_WaitEvent.BlockS();
|
|||
|
}
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// initializes the window with the specified window proc.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
HRESULT CTaskSwitch::WindowInitialize(WNDPROC pfnWndProc, HWND &hWnd)
|
|||
|
{
|
|||
|
|
|||
|
if (!g_MyMessage)
|
|||
|
{
|
|||
|
// Register my message type
|
|||
|
g_MyMessage = RegisterWindowMessage(
|
|||
|
TEXT("Component Object Model Remote Request Arrival") );
|
|||
|
}
|
|||
|
|
|||
|
if (!g_MyClass)
|
|||
|
{
|
|||
|
// Register my window class.
|
|||
|
WNDCLASS wcls;
|
|||
|
|
|||
|
wcls.style = 0;
|
|||
|
wcls.lpfnWndProc = pfnWndProc;
|
|||
|
wcls.cbClsExtra = 0;
|
|||
|
wcls.cbWndExtra = 0;
|
|||
|
wcls.hInstance = g_hInst;
|
|||
|
wcls.hIcon = NULL;
|
|||
|
wcls.hCursor = NULL;
|
|||
|
wcls.hbrBackground = (HBRUSH) COLOR_BACKGROUND + 1;
|
|||
|
wcls.lpszMenuName = NULL;
|
|||
|
wcls.lpszClassName = MY_WINDOW_CLASS;
|
|||
|
|
|||
|
g_MyClass = RegisterClass( &wcls );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (g_MyClass)
|
|||
|
{
|
|||
|
// Create a hidden window.
|
|||
|
hWnd = CreateWindowEx( 0,
|
|||
|
(LPCTSTR) g_MyClass,
|
|||
|
TEXT("Task Switcher"),
|
|||
|
WS_DISABLED,
|
|||
|
CW_USEDEFAULT,
|
|||
|
CW_USEDEFAULT,
|
|||
|
CW_USEDEFAULT,
|
|||
|
CW_USEDEFAULT,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
g_hInst,
|
|||
|
NULL );
|
|||
|
|
|||
|
|
|||
|
if (hWnd)
|
|||
|
{
|
|||
|
printf ("Created Window with hWnd %x\n", hWnd);
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// destroys the window and unregisters the class.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::WindowUninitialize(HWND hWnd)
|
|||
|
{
|
|||
|
if (hWnd != NULL)
|
|||
|
{
|
|||
|
DestroyWindow(hWnd);
|
|||
|
}
|
|||
|
|
|||
|
if (g_MyClass != 0)
|
|||
|
{
|
|||
|
UnregisterClass(MY_WINDOW_CLASS, g_hInst);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// parses the command line and returns the execution parameters
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
HRESULT CTaskSwitch::ParseCmdLine(LPSTR lpszCmdLine, SExecParms &execp)
|
|||
|
{
|
|||
|
BOOL fFile = FALSE;
|
|||
|
|
|||
|
// set the default values for execution parameters.
|
|||
|
|
|||
|
execp.oloop = 10;
|
|||
|
execp.iloop = 100;
|
|||
|
execp.hWndOther = NULL;
|
|||
|
execp.pfnWndProc = ::ThreadWndProc;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// check the input parameters
|
|||
|
|
|||
|
LPSTR pszCmd = lpszCmdLine;
|
|||
|
LPSTR pszCmdNext = NULL;
|
|||
|
|
|||
|
while (pszCmd)
|
|||
|
{
|
|||
|
pszCmdNext = strchr(pszCmd, ' ');
|
|||
|
if (pszCmdNext)
|
|||
|
{
|
|||
|
*pszCmdNext = '\0';
|
|||
|
pszCmdNext++;
|
|||
|
}
|
|||
|
|
|||
|
// check for outer loop count
|
|||
|
if (!_strnicmp(pszCmd, "/o:", 3))
|
|||
|
{
|
|||
|
execp.oloop = atoi (pszCmd+3);
|
|||
|
if (execp.oloop > MAX_OLOOP)
|
|||
|
execp.oloop = MAX_OLOOP;
|
|||
|
}
|
|||
|
|
|||
|
// check for inner loop count
|
|||
|
else if (!_strnicmp(pszCmd, "/i:", 3))
|
|||
|
{
|
|||
|
execp.iloop = atoi (pszCmd+3);
|
|||
|
if (execp.iloop < 1)
|
|||
|
execp.iloop = 1;
|
|||
|
}
|
|||
|
|
|||
|
// check for window handle
|
|||
|
else if (!_strnicmp(pszCmd, "/hwnd:", 6))
|
|||
|
{
|
|||
|
execp.hWndOther = (HWND) atoi (pszCmd+6);
|
|||
|
}
|
|||
|
|
|||
|
// check for waiter or Kicker
|
|||
|
else if (!_strnicmp(pszCmd, "/k", 2))
|
|||
|
{
|
|||
|
g_fKicker = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
// check for thread or process switch
|
|||
|
else if (!_strnicmp(pszCmd, "/p", 2))
|
|||
|
{
|
|||
|
g_fThreadSwitch = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
// check for thread or process switch
|
|||
|
else if (!_strnicmp(pszCmd, "/t", 2))
|
|||
|
{
|
|||
|
g_fThreadSwitch = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
// check for wait single or multiple
|
|||
|
else if (!_strnicmp(pszCmd, "/m", 2))
|
|||
|
{
|
|||
|
g_fWaitMultiple = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
// check for wait event name
|
|||
|
else if (!_strnicmp(pszCmd, "/e1:", 4))
|
|||
|
{
|
|||
|
#ifdef UNICODE
|
|||
|
mbstowcs(execp.szWaitEvent, pszCmd+4, strlen(pszCmd+4)+1);
|
|||
|
#else
|
|||
|
strcpy(execp.szWaitEvent, pszCmd+4);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
// check for signal event name
|
|||
|
else if (!_strnicmp(pszCmd, "/e2:", 4))
|
|||
|
{
|
|||
|
#ifdef UNICODE
|
|||
|
mbstowcs(execp.szSignalEvent, pszCmd+4, strlen(pszCmd+4)+1);
|
|||
|
#else
|
|||
|
strcpy(execp.szSignalEvent, pszCmd+4);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
// check for output file name
|
|||
|
else if (!_strnicmp(pszCmd, "/f:", 3))
|
|||
|
{
|
|||
|
fFile = TRUE;
|
|||
|
#ifdef UNICODE
|
|||
|
mbstowcs(execp.szFile, pszCmd+3, strlen(pszCmd+3)+1);
|
|||
|
#else
|
|||
|
strcpy(execp.szFile, pszCmd+3);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
// check for wait type
|
|||
|
else if (!_strnicmp(pszCmd, "/w1:", 4))
|
|||
|
{
|
|||
|
g_WaitType1 = GetWaitType(pszCmd+4);
|
|||
|
}
|
|||
|
else if (!_strnicmp(pszCmd, "/w2:", 4))
|
|||
|
{
|
|||
|
g_WaitType2 = GetWaitType(pszCmd+4);
|
|||
|
}
|
|||
|
|
|||
|
// check for signal type
|
|||
|
else if (!_strnicmp(pszCmd, "/s1:", 4))
|
|||
|
{
|
|||
|
g_SignalType1 = GetSignalType(pszCmd+4);
|
|||
|
}
|
|||
|
else if (!_strnicmp(pszCmd, "/s2:", 4))
|
|||
|
{
|
|||
|
g_SignalType2 = GetSignalType(pszCmd+4);
|
|||
|
}
|
|||
|
|
|||
|
// check for help request
|
|||
|
else if ((!_strnicmp(pszCmd, "/?", 2)) || (!_strnicmp(pszCmd, "/h", 2)))
|
|||
|
{
|
|||
|
Help();
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
pszCmd = pszCmdNext;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
g_iloop = execp.iloop;
|
|||
|
g_oloop = execp.iloop;
|
|||
|
g_hWndOther = execp.hWndOther;
|
|||
|
|
|||
|
|
|||
|
if (g_fKicker)
|
|||
|
{
|
|||
|
g_WaitType = g_WaitType2;
|
|||
|
g_SignalType = g_SignalType2;
|
|||
|
if (!fFile)
|
|||
|
_tcscpy(execp.szFile, TEXT("kicker"));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
g_WaitType = g_WaitType1;
|
|||
|
g_SignalType = g_SignalType1;
|
|||
|
if (!fFile)
|
|||
|
_tcscpy(execp.szFile, TEXT("waiter"));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
DWORD CTaskSwitch::GetWaitType(LPSTR pszCmd)
|
|||
|
{
|
|||
|
ULONG i=0;
|
|||
|
|
|||
|
while (aszWait[++i]) // slot 0 is not used
|
|||
|
{
|
|||
|
if (!_stricmp(pszCmd, aszWait[i]))
|
|||
|
return i;
|
|||
|
}
|
|||
|
|
|||
|
Help();
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD CTaskSwitch::GetSignalType(LPSTR pszCmd)
|
|||
|
{
|
|||
|
ULONG i=0;
|
|||
|
|
|||
|
while (aszSignal[++i]) // slot 0 is not used
|
|||
|
{
|
|||
|
if (!_stricmp(pszCmd, aszSignal[i]))
|
|||
|
return i;
|
|||
|
}
|
|||
|
|
|||
|
Help();
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// creates the command line parameters for the other guy
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::CreateOtherParms(void)
|
|||
|
{
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// write the formatted parms to the parm string
|
|||
|
|
|||
|
sprintf(g_szOtherParms, "/k %s %s /i:%d /o:%d /hWnd:%ld "
|
|||
|
"/e1:%hs /e2:%hs /w1:%s /s1:%s /w2:%s /s2:%s",
|
|||
|
(g_fThreadSwitch) ? "/t" : "/p",
|
|||
|
(g_fWaitMultiple) ? "/m" : " ",
|
|||
|
g_execp.iloop, // same loop counts as me
|
|||
|
g_execp.oloop,
|
|||
|
g_hWndMe, // posts to my window
|
|||
|
g_execp.szSignalEvent, // it waits on my signal event
|
|||
|
g_execp.szWaitEvent, // it signals my wait event
|
|||
|
aszWait[g_WaitType1], // signal what i wait on
|
|||
|
aszSignal[g_SignalType1], // wait on what i signal
|
|||
|
aszWait[g_WaitType2], // signal what i wait on
|
|||
|
aszSignal[g_SignalType2]); // wait on what i signal
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// writes the execution parameters to a log file.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::WriteExecParms()
|
|||
|
{
|
|||
|
// write the run parameters to the output file
|
|||
|
|
|||
|
g_output->WriteString(TEXT("Using Parametes:\n"));
|
|||
|
g_output->WriteResult(TEXT("\tInner Loop Count = "), g_execp.iloop);
|
|||
|
g_output->WriteResult(TEXT("\tOuter Loop Count = "), g_execp.oloop);
|
|||
|
g_output->WriteString(TEXT("\n\n"));
|
|||
|
|
|||
|
// flush to avoid disk io during the test
|
|||
|
g_output->Flush();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// writes the results to a log file.
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::WriteResults(void)
|
|||
|
{
|
|||
|
if (g_hr == S_OK)
|
|||
|
{
|
|||
|
// compute the averages
|
|||
|
|
|||
|
ULONG tTotal = 0;
|
|||
|
|
|||
|
// skip the first & last value as they are sometimes skewed
|
|||
|
for (int i=0; i<g_execp.oloop; i++)
|
|||
|
{
|
|||
|
tTotal += g_time[i];
|
|||
|
}
|
|||
|
|
|||
|
// compute average for 1 call/response
|
|||
|
tTotal /= (g_execp.oloop * g_execp.iloop);
|
|||
|
|
|||
|
|
|||
|
// display the results
|
|||
|
|
|||
|
g_output->WriteResults(TEXT("Times "), g_execp.oloop, g_time);
|
|||
|
g_output->WriteResult(TEXT("\nAverage "), tTotal);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// writes the help info to the screen
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
void CTaskSwitch::Help()
|
|||
|
{
|
|||
|
printf ("msgtask\n");
|
|||
|
printf ("\t/o:<nnn> - outer loop count def 10\n");
|
|||
|
printf ("\t/i:<nnn> - inner loop count def 100\n");
|
|||
|
printf ("\t/f:<name> - name of output file. def [kick | wait]\n");
|
|||
|
printf ("\t/w1:<event|getmsg|msgwait> - what to wait on\n");
|
|||
|
printf ("\t/s1:<event|postmsg> - what to signal\n");
|
|||
|
printf ("\t/w2:<event|getmsg|msgwait> - what to wait on\n");
|
|||
|
printf ("\t/s2:<event|postmsg> - what to signal\n");
|
|||
|
printf ("\t/e1:<name> - name of wait event\n");
|
|||
|
printf ("\t/e2:<name> - name of signal event\n");
|
|||
|
printf ("\t/k - kicker (as opposed to waiter)\n");
|
|||
|
printf ("\t/t - use thread switching\n");
|
|||
|
printf ("\t/p - use process switching\n");
|
|||
|
printf ("\t/m - use WaitMultiple vs WaitSingle\n");
|
|||
|
printf ("\t/hWnd:<nnnn> - window handle of other side\n");
|
|||
|
|
|||
|
printf ("\n");
|
|||
|
printf ("timings are given for the inner loop count calls\n");
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// creates a process
|
|||
|
//
|
|||
|
//--------------------------------------------------------------------------
|
|||
|
|
|||
|
DWORD CTaskSwitch::CreateProc(void)
|
|||
|
{
|
|||
|
// create the command line
|
|||
|
|
|||
|
TCHAR szCmdLine[256];
|
|||
|
|
|||
|
_stprintf(szCmdLine, TEXT("ntsd procswap %hs"), g_szOtherParms);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// build the win32 startup info structure
|
|||
|
|
|||
|
STARTUPINFO startupinfo;
|
|||
|
startupinfo.cb = sizeof(STARTUPINFO);
|
|||
|
startupinfo.lpReserved = NULL;
|
|||
|
startupinfo.lpDesktop = NULL;
|
|||
|
startupinfo.lpTitle = TEXT("Task Switcher");
|
|||
|
startupinfo.dwX = 40;
|
|||
|
startupinfo.dwY = 40;
|
|||
|
startupinfo.dwXSize = 80;
|
|||
|
startupinfo.dwYSize = 40;
|
|||
|
startupinfo.dwFlags = 0;
|
|||
|
startupinfo.wShowWindow = SW_SHOWNORMAL;
|
|||
|
startupinfo.cbReserved2 = 0;
|
|||
|
startupinfo.lpReserved2 = NULL;
|
|||
|
|
|||
|
PROCESS_INFORMATION ProcInfo;
|
|||
|
|
|||
|
|
|||
|
BOOL fRslt = CreateProcess(NULL, // app name
|
|||
|
szCmdLine, // command line
|
|||
|
NULL, // lpsaProcess
|
|||
|
NULL, // lpsaThread
|
|||
|
FALSE, // inherit handles
|
|||
|
CREATE_NEW_CONSOLE,// creation flags
|
|||
|
NULL, // lpEnvironment
|
|||
|
NULL, // curr Dir
|
|||
|
&startupinfo, // Startup Info
|
|||
|
&ProcInfo); // process info
|
|||
|
|
|||
|
if (fRslt)
|
|||
|
{
|
|||
|
// we dont need the handles
|
|||
|
CloseHandle(ProcInfo.hProcess);
|
|||
|
CloseHandle(ProcInfo.hThread);
|
|||
|
printf ("Created Process (%ws) pid=%x\n", szCmdLine, ProcInfo.dwProcessId);
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// what went wrong?
|
|||
|
DWORD dwRet = GetLastError();
|
|||
|
printf ("CreateProcess (%ws) failed %x\n", szCmdLine, dwRet);
|
|||
|
return dwRet;
|
|||
|
}
|
|||
|
}
|
|||
|
|