mirror of
git://source.winehq.org/git/wine.git
synced 2024-10-31 10:41:12 +00:00
556 lines
18 KiB
C
556 lines
18 KiB
C
/*
|
|
* RPCSS named pipe server
|
|
*
|
|
* Copyright (C) 2002 Greg Turner
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <assert.h>
|
|
|
|
#include "ntstatus.h"
|
|
#include "rpcss.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(ole);
|
|
|
|
static HANDLE np_server_end;
|
|
static HANDLE np_server_work_event;
|
|
static CRITICAL_SECTION np_server_cs;
|
|
static LONG srv_thread_count;
|
|
static BOOL server_live;
|
|
|
|
LONG RPCSS_SrvThreadCount(void)
|
|
{
|
|
return srv_thread_count;
|
|
}
|
|
|
|
BOOL RPCSS_UnBecomePipeServer(void)
|
|
{
|
|
BOOL rslt = TRUE;
|
|
DWORD wait_result;
|
|
HANDLE master_mutex = RPCSS_GetMasterMutex();
|
|
|
|
WINE_TRACE("\n");
|
|
|
|
wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT);
|
|
|
|
switch (wait_result) {
|
|
case WAIT_ABANDONED: /* ? */
|
|
case WAIT_OBJECT_0:
|
|
/* we have ownership */
|
|
break;
|
|
case WAIT_FAILED:
|
|
case WAIT_TIMEOUT:
|
|
default:
|
|
WINE_ERR("This should never happen: couldn't enter mutex.\n");
|
|
/* this is totally unacceptable. no graceful out exists */
|
|
assert(FALSE);
|
|
}
|
|
|
|
/* now that we have the master mutex, we can safely stop
|
|
listening on the pipe. Before we proceed, we do a final
|
|
check that it's OK to shut down to ensure atomicity */
|
|
|
|
if (!RPCSS_ReadyToDie())
|
|
rslt = FALSE;
|
|
else {
|
|
WINE_TRACE("shutting down pipe.\n");
|
|
server_live = FALSE;
|
|
if (!CloseHandle(np_server_end))
|
|
WINE_WARN("Failed to close named pipe.\n");
|
|
if (!CloseHandle(np_server_work_event))
|
|
WINE_WARN("Failed to close the event handle.\n");
|
|
DeleteCriticalSection(&np_server_cs);
|
|
}
|
|
|
|
if (!ReleaseMutex(master_mutex))
|
|
WINE_ERR("Unable to leave master mutex!??\n");
|
|
|
|
return rslt;
|
|
}
|
|
|
|
static void RPCSS_ServerProcessRANMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply)
|
|
{
|
|
WINE_TRACE("\n");
|
|
/* we do absolutely nothing, but on the server end,
|
|
the lazy timeout is reset as a result of our connection. */
|
|
RPCSS_SetMaxLazyTimeout(pMsg->message.ranmsg.timeout);
|
|
RPCSS_SetLazyTimeRemaining(RPCSS_GetMaxLazyTimeout());
|
|
pReply->as_uint = 0;
|
|
}
|
|
|
|
static void RPCSS_ServerProcessREGISTEREPMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply,
|
|
char *vardata)
|
|
{
|
|
WINE_TRACE("\n");
|
|
|
|
RPCSS_RegisterRpcEndpoints(
|
|
pMsg->message.registerepmsg.iface,
|
|
pMsg->message.registerepmsg.object_count,
|
|
pMsg->message.registerepmsg.binding_count,
|
|
pMsg->message.registerepmsg.no_replace,
|
|
vardata,
|
|
pMsg->vardata_payload_size
|
|
);
|
|
|
|
/* no reply */
|
|
pReply->as_uint = 0;
|
|
}
|
|
|
|
static void RPCSS_ServerProcessUNREGISTEREPMessage(PRPCSS_NP_MESSAGE pMsg,
|
|
PRPCSS_NP_REPLY pReply, char *vardata)
|
|
{
|
|
WINE_TRACE("\n");
|
|
|
|
RPCSS_UnregisterRpcEndpoints(
|
|
pMsg->message.unregisterepmsg.iface,
|
|
pMsg->message.unregisterepmsg.object_count,
|
|
pMsg->message.unregisterepmsg.binding_count,
|
|
vardata,
|
|
pMsg->vardata_payload_size
|
|
);
|
|
|
|
/* no reply */
|
|
pReply->as_uint = 0;
|
|
}
|
|
|
|
static void RPCSS_ServerProcessRESOLVEEPMessage(PRPCSS_NP_MESSAGE pMsg,
|
|
PRPCSS_NP_REPLY pReply, char *vardata)
|
|
{
|
|
WINE_TRACE("\n");
|
|
|
|
/* for now, reply is placed into *pReply.as_string, on success, by RPCSS_ResolveRpcEndpoints */
|
|
ZeroMemory(pReply->as_string, MAX_RPCSS_NP_REPLY_STRING_LEN);
|
|
RPCSS_ResolveRpcEndpoints(
|
|
pMsg->message.resolveepmsg.iface,
|
|
pMsg->message.resolveepmsg.object,
|
|
vardata,
|
|
pReply->as_string
|
|
);
|
|
}
|
|
|
|
static void RPCSS_ServerProcessMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply, char *vardata)
|
|
{
|
|
WINE_TRACE("\n");
|
|
switch (pMsg->message_type) {
|
|
case RPCSS_NP_MESSAGE_TYPEID_RANMSG:
|
|
RPCSS_ServerProcessRANMessage(pMsg, pReply);
|
|
break;
|
|
case RPCSS_NP_MESSAGE_TYPEID_REGISTEREPMSG:
|
|
RPCSS_ServerProcessREGISTEREPMessage(pMsg, pReply, vardata);
|
|
break;
|
|
case RPCSS_NP_MESSAGE_TYPEID_UNREGISTEREPMSG:
|
|
RPCSS_ServerProcessUNREGISTEREPMessage(pMsg, pReply, vardata);
|
|
break;
|
|
case RPCSS_NP_MESSAGE_TYPEID_RESOLVEEPMSG:
|
|
RPCSS_ServerProcessRESOLVEEPMessage(pMsg, pReply, vardata);
|
|
break;
|
|
default:
|
|
WINE_ERR("Message type unknown!! No action taken.\n");
|
|
}
|
|
}
|
|
|
|
/* each message gets its own thread. this is it. */
|
|
static VOID HandlerThread(LPVOID lpvPipeHandle)
|
|
{
|
|
RPCSS_NP_MESSAGE msg, vardata_payload_msg;
|
|
char *c, *vardata = NULL;
|
|
RPCSS_NP_REPLY reply;
|
|
DWORD bytesread, written;
|
|
BOOL success, had_payload = FALSE;
|
|
HANDLE mypipe;
|
|
|
|
mypipe = (HANDLE) lpvPipeHandle;
|
|
|
|
WINE_TRACE("mypipe: %p\n", mypipe);
|
|
|
|
success = ReadFile(
|
|
mypipe, /* pipe handle */
|
|
(char *) &msg, /* message buffer */
|
|
sizeof(RPCSS_NP_MESSAGE), /* message buffer size */
|
|
&bytesread, /* receives number of bytes read */
|
|
NULL /* not overlapped */
|
|
);
|
|
|
|
if (msg.vardata_payload_size) {
|
|
had_payload = TRUE;
|
|
/* this fudge space allows us not to worry about exceeding the buffer space
|
|
on the last read */
|
|
vardata = LocalAlloc(LPTR, (msg.vardata_payload_size) + VARDATA_PAYLOAD_BYTES);
|
|
if (!vardata) {
|
|
WINE_ERR("vardata memory allocation failure.\n");
|
|
success = FALSE;
|
|
} else {
|
|
for ( c = vardata; (c - vardata) < msg.vardata_payload_size;
|
|
c += VARDATA_PAYLOAD_BYTES) {
|
|
success = ReadFile(
|
|
mypipe,
|
|
(char *) &vardata_payload_msg,
|
|
sizeof(RPCSS_NP_MESSAGE),
|
|
&bytesread,
|
|
NULL
|
|
);
|
|
if ( (!success) || (bytesread != sizeof(RPCSS_NP_MESSAGE)) ||
|
|
(vardata_payload_msg.message_type != RPCSS_NP_MESSAGE_TYPEID_VARDATAPAYLOADMSG) ) {
|
|
WINE_ERR("vardata payload read failure! (s=%s,br=%ld,exp_br=%d,mt=%u,mt_exp=%u\n",
|
|
success ? "TRUE" : "FALSE", bytesread, sizeof(RPCSS_NP_MESSAGE),
|
|
vardata_payload_msg.message_type, RPCSS_NP_MESSAGE_TYPEID_VARDATAPAYLOADMSG);
|
|
success = FALSE;
|
|
break;
|
|
}
|
|
CopyMemory(c, vardata_payload_msg.message.vardatapayloadmsg.payload, VARDATA_PAYLOAD_BYTES);
|
|
WINE_TRACE("payload read.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (success && (bytesread == sizeof(RPCSS_NP_MESSAGE))) {
|
|
WINE_TRACE("read success.\n");
|
|
/* process the message and send a reply, serializing requests. */
|
|
EnterCriticalSection(&np_server_cs);
|
|
WINE_TRACE("processing message.\n");
|
|
RPCSS_ServerProcessMessage(&msg, &reply, vardata);
|
|
LeaveCriticalSection(&np_server_cs);
|
|
|
|
if (had_payload) LocalFree(vardata);
|
|
|
|
WINE_TRACE("message processed, sending reply....\n");
|
|
|
|
success = WriteFile(
|
|
mypipe, /* pipe handle */
|
|
(char *) &reply, /* reply buffer */
|
|
sizeof(RPCSS_NP_REPLY), /* reply buffer size */
|
|
&written, /* receives number of bytes written */
|
|
NULL /* not overlapped */
|
|
);
|
|
|
|
if ( (!success) || (written != sizeof(RPCSS_NP_REPLY)) )
|
|
WINE_WARN("Message reply failed. (successs=%s, br=%ld, exp_br=%d)\n",
|
|
success ? "TRUE" : "FALSE", written, sizeof(RPCSS_NP_REPLY));
|
|
else
|
|
WINE_TRACE("Reply sent successfully.\n");
|
|
} else
|
|
WINE_WARN("Message receipt failed.\n");
|
|
|
|
FlushFileBuffers(mypipe);
|
|
DisconnectNamedPipe(mypipe);
|
|
CloseHandle(mypipe);
|
|
InterlockedDecrement(&srv_thread_count);
|
|
}
|
|
|
|
static VOID NPMainWorkThread(LPVOID ignored)
|
|
{
|
|
BOOL connected;
|
|
HANDLE hthread, master_mutex = RPCSS_GetMasterMutex();
|
|
DWORD threadid, wait_result;
|
|
|
|
WINE_TRACE("\n");
|
|
|
|
while (server_live) {
|
|
connected = ConnectNamedPipe(np_server_end, NULL) ?
|
|
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
|
|
|
|
if (connected) {
|
|
/* is "work" the act of connecting pipes, or the act of serving
|
|
requests successfully? for now I will make it the former. */
|
|
if (!SetEvent(np_server_work_event))
|
|
WINE_WARN("failed to signal np_server_work_event.\n");
|
|
|
|
/* Create a thread for this client. */
|
|
InterlockedIncrement(&srv_thread_count);
|
|
hthread = CreateThread(
|
|
NULL, /* no security attribute */
|
|
0, /* default stack size */
|
|
(LPTHREAD_START_ROUTINE) HandlerThread,
|
|
(LPVOID) np_server_end, /* thread parameter */
|
|
0, /* not suspended */
|
|
&threadid /* returns thread ID (not used) */
|
|
);
|
|
|
|
if (hthread) {
|
|
WINE_TRACE("Spawned handler thread: %p\n", hthread);
|
|
CloseHandle(hthread);
|
|
|
|
/* for safety's sake, hold the mutex while we switch the pipe */
|
|
|
|
wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT);
|
|
|
|
switch (wait_result) {
|
|
case WAIT_ABANDONED: /* ? */
|
|
case WAIT_OBJECT_0:
|
|
/* we have ownership */
|
|
break;
|
|
case WAIT_FAILED:
|
|
case WAIT_TIMEOUT:
|
|
default:
|
|
/* huh? */
|
|
wait_result = WAIT_FAILED;
|
|
}
|
|
|
|
if (wait_result == WAIT_FAILED) {
|
|
WINE_ERR("Couldn't enter master mutex. Expect prolems.\n");
|
|
} else {
|
|
/* now create a new named pipe instance to listen on */
|
|
np_server_end = CreateNamedPipe(
|
|
NAME_RPCSS_NAMED_PIPE, /* pipe name */
|
|
PIPE_ACCESS_DUPLEX, /* pipe open mode */
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, /* pipe-specific modes */
|
|
PIPE_UNLIMITED_INSTANCES, /* maximum instances */
|
|
sizeof(RPCSS_NP_REPLY), /* output buffer size */
|
|
sizeof(RPCSS_NP_MESSAGE), /* input buffer size */
|
|
2000, /* time-out interval */
|
|
NULL /* SD */
|
|
);
|
|
|
|
if (np_server_end == INVALID_HANDLE_VALUE) {
|
|
WINE_ERR("Failed to recreate named pipe!\n");
|
|
/* not sure what to do? */
|
|
assert(FALSE);
|
|
}
|
|
|
|
if (!ReleaseMutex(master_mutex))
|
|
WINE_ERR("Uh oh. Couldn't leave master mutex. Expect deadlock.\n");
|
|
}
|
|
} else {
|
|
WINE_ERR("Failed to spawn handler thread!\n");
|
|
DisconnectNamedPipe(np_server_end);
|
|
InterlockedDecrement(&srv_thread_count);
|
|
}
|
|
}
|
|
}
|
|
WINE_TRACE("Server thread shutdown.\n");
|
|
}
|
|
|
|
static HANDLE RPCSS_NPConnect(void)
|
|
{
|
|
HANDLE the_pipe = NULL;
|
|
DWORD dwmode, wait_result;
|
|
HANDLE master_mutex = RPCSS_GetMasterMutex();
|
|
|
|
WINE_TRACE("\n");
|
|
|
|
while (TRUE) {
|
|
|
|
wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT);
|
|
switch (wait_result) {
|
|
case WAIT_ABANDONED:
|
|
case WAIT_OBJECT_0:
|
|
break;
|
|
case WAIT_FAILED:
|
|
case WAIT_TIMEOUT:
|
|
default:
|
|
WINE_ERR("This should never happen: couldn't enter mutex.\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* try to open the client side of the named pipe. */
|
|
the_pipe = CreateFileA(
|
|
NAME_RPCSS_NAMED_PIPE, /* pipe name */
|
|
GENERIC_READ | GENERIC_WRITE, /* r/w access */
|
|
0, /* no sharing */
|
|
NULL, /* no security attributes */
|
|
OPEN_EXISTING, /* open an existing pipe */
|
|
0, /* default attributes */
|
|
NULL /* no template file */
|
|
);
|
|
|
|
if (the_pipe != INVALID_HANDLE_VALUE)
|
|
break;
|
|
|
|
if (GetLastError() != ERROR_PIPE_BUSY) {
|
|
WINE_WARN("Unable to open named pipe %s (assuming unavailable).\n",
|
|
wine_dbgstr_a(NAME_RPCSS_NAMED_PIPE));
|
|
the_pipe = NULL;
|
|
break;
|
|
}
|
|
|
|
WINE_WARN("Named pipe busy (will wait)\n");
|
|
|
|
if (!ReleaseMutex(master_mutex))
|
|
WINE_ERR("Failed to release master mutex. Expect deadlock.\n");
|
|
|
|
/* wait for the named pipe. We are only
|
|
willing to wait only 5 seconds. It should be available /very/ soon. */
|
|
if (! WaitNamedPipeA(NAME_RPCSS_NAMED_PIPE, MASTER_MUTEX_WAITNAMEDPIPE_TIMEOUT))
|
|
{
|
|
WINE_ERR("Named pipe unavailable after waiting. Something is probably wrong.\n");
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
if (the_pipe) {
|
|
dwmode = PIPE_READMODE_MESSAGE;
|
|
/* SetNamedPipeHandleState not implemented ATM, but still seems to work somehow. */
|
|
if (! SetNamedPipeHandleState(the_pipe, &dwmode, NULL, NULL))
|
|
WINE_WARN("Failed to set pipe handle state\n");
|
|
}
|
|
|
|
if (!ReleaseMutex(master_mutex))
|
|
WINE_ERR("Uh oh, failed to leave the RPC Master Mutex!\n");
|
|
|
|
return the_pipe;
|
|
}
|
|
|
|
static BOOL RPCSS_SendReceiveNPMsg(HANDLE np, PRPCSS_NP_MESSAGE msg, PRPCSS_NP_REPLY reply)
|
|
{
|
|
DWORD count;
|
|
|
|
WINE_TRACE("(np == %p, msg == %p, reply == %p)\n", np, msg, reply);
|
|
|
|
if (! WriteFile(np, msg, sizeof(RPCSS_NP_MESSAGE), &count, NULL)) {
|
|
WINE_ERR("write failed.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (count != sizeof(RPCSS_NP_MESSAGE)) {
|
|
WINE_ERR("write count mismatch.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (! ReadFile(np, reply, sizeof(RPCSS_NP_REPLY), &count, NULL)) {
|
|
WINE_ERR("read failed.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (count != sizeof(RPCSS_NP_REPLY)) {
|
|
WINE_ERR("read count mismatch. got %ld, expected %u.\n", count, sizeof(RPCSS_NP_REPLY));
|
|
return FALSE;
|
|
}
|
|
|
|
/* message execution was successful */
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL RPCSS_BecomePipeServer(void)
|
|
{
|
|
RPCSS_NP_MESSAGE msg;
|
|
RPCSS_NP_REPLY reply;
|
|
BOOL rslt = TRUE;
|
|
HANDLE client_handle, hthread, master_mutex = RPCSS_GetMasterMutex();
|
|
DWORD threadid, wait_result;
|
|
|
|
WINE_TRACE("\n");
|
|
|
|
wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT);
|
|
|
|
switch (wait_result) {
|
|
case WAIT_ABANDONED: /* ? */
|
|
case WAIT_OBJECT_0:
|
|
/* we have ownership */
|
|
break;
|
|
case WAIT_FAILED:
|
|
case WAIT_TIMEOUT:
|
|
default:
|
|
WINE_ERR("Couldn't enter master mutex.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* now we have the master mutex. during this time we will
|
|
*
|
|
* o check if an rpcss already listens on the pipe. If so,
|
|
* we will tell it we were invoked, which will cause the
|
|
* other end to update its timeouts. After, we just return
|
|
* false.
|
|
*
|
|
* o otherwise, we establish the pipe for ourselves and get
|
|
* ready to listen on it
|
|
*/
|
|
|
|
if ((client_handle = RPCSS_NPConnect()) != NULL) {
|
|
msg.message_type = RPCSS_NP_MESSAGE_TYPEID_RANMSG;
|
|
msg.message.ranmsg.timeout = RPCSS_GetMaxLazyTimeout();
|
|
msg.vardata_payload_size = 0;
|
|
if (!RPCSS_SendReceiveNPMsg(client_handle, &msg, &reply))
|
|
WINE_ERR("Something is amiss: RPC_SendReceive failed.\n");
|
|
rslt = FALSE;
|
|
}
|
|
if (rslt) {
|
|
np_server_work_event = CreateEventA(NULL, FALSE, FALSE, "RpcNpServerWorkEvent");
|
|
if (np_server_work_event == NULL) {
|
|
/* dunno what we can do then */
|
|
WINE_ERR("Unable to create the np_server_work_event\n");
|
|
assert(FALSE);
|
|
}
|
|
InitializeCriticalSection(&np_server_cs);
|
|
|
|
np_server_end = CreateNamedPipe(
|
|
NAME_RPCSS_NAMED_PIPE, /* pipe name */
|
|
PIPE_ACCESS_DUPLEX, /* pipe open mode */
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, /* pipe-specific modes */
|
|
PIPE_UNLIMITED_INSTANCES, /* maximum number of instances */
|
|
sizeof(RPCSS_NP_REPLY), /* output buffer size */
|
|
sizeof(RPCSS_NP_MESSAGE), /* input buffer size */
|
|
2000, /* time-out interval */
|
|
NULL /* SD */
|
|
);
|
|
|
|
if (np_server_end == INVALID_HANDLE_VALUE) {
|
|
WINE_ERR("Failed to create named pipe!\n");
|
|
DeleteCriticalSection(&np_server_cs);
|
|
if (!CloseHandle(np_server_work_event)) /* we will leak the handle... */
|
|
WINE_WARN("Failed to close np_server_work_event handle!\n");
|
|
np_server_work_event = NULL;
|
|
np_server_end = NULL;
|
|
rslt = FALSE;
|
|
}
|
|
}
|
|
|
|
server_live = rslt;
|
|
|
|
if (rslt) {
|
|
/* OK, now spawn the (single) server thread */
|
|
hthread = CreateThread(
|
|
NULL, /* no security attribute */
|
|
0, /* default stack size */
|
|
(LPTHREAD_START_ROUTINE) NPMainWorkThread,
|
|
NULL, /* thread parameter */
|
|
0, /* not suspended */
|
|
&threadid /* returns thread ID (not used) */
|
|
);
|
|
if (hthread) {
|
|
WINE_TRACE("Created server thread.\n");
|
|
CloseHandle(hthread);
|
|
} else {
|
|
WINE_ERR("Serious error: unable to create server thread!\n");
|
|
if (!CloseHandle(np_server_work_event)) /* we will leak the handle... */
|
|
WINE_WARN("Failed to close np_server_work_event handle!\n");
|
|
if (!CloseHandle(np_server_end)) /* we will leak the handle... */
|
|
WINE_WARN("Unable to close named pipe handle!\n");
|
|
DeleteCriticalSection(&np_server_cs);
|
|
np_server_end = NULL;
|
|
np_server_work_event = NULL;
|
|
rslt = FALSE;
|
|
server_live = FALSE;
|
|
}
|
|
}
|
|
if (!ReleaseMutex(master_mutex))
|
|
WINE_ERR("Unable to leave master mutex!??\n");
|
|
|
|
return rslt;
|
|
}
|
|
|
|
BOOL RPCSS_NPDoWork(void)
|
|
{
|
|
DWORD waitresult = WaitForSingleObject(np_server_work_event, 1000);
|
|
|
|
if (waitresult == WAIT_TIMEOUT)
|
|
return FALSE;
|
|
if (waitresult == WAIT_OBJECT_0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|