2002-02-05 19:11:17 +01:00
|
|
|
/*
|
|
|
|
* (Local) RPC Stuff
|
|
|
|
*
|
|
|
|
* Copyright 2002 Marcus Meissner
|
2002-03-10 00:29:33 +01:00
|
|
|
*
|
|
|
|
* 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
|
2002-02-05 19:11:17 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
2003-09-06 01:08:26 +02:00
|
|
|
#include <stdarg.h>
|
2002-02-05 19:11:17 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
2003-01-07 21:36:20 +01:00
|
|
|
#define NONAMELESSUNION
|
|
|
|
#define NONAMELESSSTRUCT
|
2002-02-05 19:11:17 +01:00
|
|
|
#include "windef.h"
|
2003-09-06 01:08:26 +02:00
|
|
|
#include "winbase.h"
|
2003-09-09 21:39:31 +02:00
|
|
|
#include "winuser.h"
|
2002-02-05 19:11:17 +01:00
|
|
|
#include "objbase.h"
|
|
|
|
#include "ole2.h"
|
|
|
|
#include "ole2ver.h"
|
|
|
|
#include "rpc.h"
|
|
|
|
#include "winerror.h"
|
|
|
|
#include "winreg.h"
|
|
|
|
#include "wownt32.h"
|
|
|
|
#include "wtypes.h"
|
|
|
|
#include "wine/unicode.h"
|
|
|
|
#include "wine/winbase16.h"
|
|
|
|
#include "compobj_private.h"
|
|
|
|
#include "ifs.h"
|
|
|
|
|
|
|
|
#include "compobj_private.h"
|
|
|
|
|
2002-03-10 00:29:33 +01:00
|
|
|
#include "wine/debug.h"
|
2002-02-05 19:11:17 +01:00
|
|
|
|
2002-03-10 00:29:33 +01:00
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(ole);
|
2002-02-05 19:11:17 +01:00
|
|
|
|
2004-07-23 01:44:54 +02:00
|
|
|
#define REQTYPE_REQUEST 0
|
|
|
|
typedef struct _wine_rpc_request_header {
|
|
|
|
DWORD reqid;
|
|
|
|
wine_marshal_id mid;
|
|
|
|
DWORD iMethod;
|
|
|
|
DWORD cbBuffer;
|
|
|
|
} wine_rpc_request_header;
|
|
|
|
|
|
|
|
#define REQTYPE_RESPONSE 1
|
|
|
|
typedef struct _wine_rpc_response_header {
|
|
|
|
DWORD reqid;
|
|
|
|
DWORD cbBuffer;
|
|
|
|
DWORD retval;
|
|
|
|
} wine_rpc_response_header;
|
|
|
|
|
2004-07-30 01:58:12 +02:00
|
|
|
/* used when shutting down a pipe, e.g. at the end of a process */
|
|
|
|
#define REQTYPE_DISCONNECT 2
|
|
|
|
typedef struct _wine_rpc_disconnect_header {
|
|
|
|
DWORD reqid;
|
|
|
|
wine_marshal_id mid; /* mid of stub to delete */
|
|
|
|
} wine_rpc_disconnect_header;
|
|
|
|
|
|
|
|
|
2004-07-23 01:44:54 +02:00
|
|
|
#define REQSTATE_START 0
|
|
|
|
#define REQSTATE_REQ_QUEUED 1
|
|
|
|
#define REQSTATE_REQ_WAITING_FOR_REPLY 2
|
|
|
|
#define REQSTATE_REQ_GOT 3
|
|
|
|
#define REQSTATE_INVOKING 4
|
|
|
|
#define REQSTATE_RESP_QUEUED 5
|
|
|
|
#define REQSTATE_RESP_GOT 6
|
|
|
|
#define REQSTATE_DONE 6
|
|
|
|
|
2002-02-05 19:11:17 +01:00
|
|
|
typedef struct _wine_rpc_request {
|
|
|
|
int state;
|
|
|
|
HANDLE hPipe; /* temp copy of handle */
|
|
|
|
wine_rpc_request_header reqh;
|
|
|
|
wine_rpc_response_header resph;
|
|
|
|
LPBYTE Buffer;
|
|
|
|
} wine_rpc_request;
|
|
|
|
|
|
|
|
static wine_rpc_request **reqs = NULL;
|
|
|
|
static int nrofreqs = 0;
|
|
|
|
|
2004-07-29 04:43:36 +02:00
|
|
|
/* This pipe is _thread_ based, each thread which talks to a remote
|
|
|
|
* apartment (mid) has its own pipe */
|
2002-02-05 19:11:17 +01:00
|
|
|
typedef struct _wine_pipe {
|
|
|
|
wine_marshal_id mid; /* target mid */
|
2004-07-29 04:43:36 +02:00
|
|
|
DWORD tid; /* thread which owns this outgoing pipe */
|
2002-02-05 19:11:17 +01:00
|
|
|
HANDLE hPipe;
|
|
|
|
|
|
|
|
int pending;
|
|
|
|
HANDLE hThread;
|
|
|
|
CRITICAL_SECTION crit;
|
|
|
|
} wine_pipe;
|
|
|
|
|
|
|
|
static wine_pipe *pipes = NULL;
|
|
|
|
static int nrofpipes = 0;
|
|
|
|
|
|
|
|
typedef struct _PipeBuf {
|
2004-08-13 01:00:51 +02:00
|
|
|
IRpcChannelBufferVtbl *lpVtbl;
|
2002-02-05 19:11:17 +01:00
|
|
|
DWORD ref;
|
|
|
|
|
|
|
|
wine_marshal_id mid;
|
|
|
|
wine_pipe *pipe;
|
|
|
|
} PipeBuf;
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
2004-08-02 20:47:31 +02:00
|
|
|
read_pipe(HANDLE hf, LPVOID ptr, DWORD size) {
|
2002-02-05 19:11:17 +01:00
|
|
|
DWORD res;
|
|
|
|
if (!ReadFile(hf,ptr,size,&res,NULL)) {
|
2002-11-22 05:43:02 +01:00
|
|
|
FIXME("Failed to read from %p, le is %lx\n",hf,GetLastError());
|
2002-02-05 19:11:17 +01:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
if (res!=size) {
|
2002-11-22 05:43:02 +01:00
|
|
|
FIXME("Read only %ld of %ld bytes from %p.\n",res,size,hf);
|
2002-02-05 19:11:17 +01:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drs(LPCSTR where) {
|
2003-02-12 02:23:17 +01:00
|
|
|
#if 0
|
|
|
|
static int nrofreaders = 0;
|
2002-02-05 19:11:17 +01:00
|
|
|
|
2003-02-12 02:23:17 +01:00
|
|
|
int i, states[10];
|
2002-02-05 19:11:17 +01:00
|
|
|
|
|
|
|
memset(states,0,sizeof(states));
|
|
|
|
for (i=nrofreqs;i--;)
|
|
|
|
states[reqs[i]->state]++;
|
|
|
|
FIXME("%lx/%s/%d: rq %d, w %d, rg %d, rsq %d, rsg %d, d %d\n",
|
|
|
|
GetCurrentProcessId(),
|
|
|
|
where,
|
|
|
|
nrofreaders,
|
|
|
|
states[REQSTATE_REQ_QUEUED],
|
|
|
|
states[REQSTATE_REQ_WAITING_FOR_REPLY],
|
|
|
|
states[REQSTATE_REQ_GOT],
|
|
|
|
states[REQSTATE_RESP_QUEUED],
|
|
|
|
states[REQSTATE_RESP_GOT],
|
|
|
|
states[REQSTATE_DONE]
|
|
|
|
);
|
2003-02-12 02:23:17 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return ;
|
2002-02-05 19:11:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
2004-08-02 20:47:31 +02:00
|
|
|
write_pipe(HANDLE hf, LPVOID ptr, DWORD size) {
|
2002-02-05 19:11:17 +01:00
|
|
|
DWORD res;
|
|
|
|
if (!WriteFile(hf,ptr,size,&res,NULL)) {
|
2002-11-22 05:43:02 +01:00
|
|
|
FIXME("Failed to write to %p, le is %lx\n",hf,GetLastError());
|
2002-02-05 19:11:17 +01:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
if (res!=size) {
|
2002-11-22 05:43:02 +01:00
|
|
|
FIXME("Wrote only %ld of %ld bytes to %p.\n",res,size,hf);
|
2002-02-05 19:11:17 +01:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DWORD WINAPI _StubReaderThread(LPVOID);
|
|
|
|
|
|
|
|
static HRESULT
|
|
|
|
PIPE_RegisterPipe(wine_marshal_id *mid, HANDLE hPipe, BOOL startreader) {
|
|
|
|
int i;
|
|
|
|
char pipefn[100];
|
2002-06-04 02:53:21 +02:00
|
|
|
wine_pipe *new_pipes;
|
2002-02-05 19:11:17 +01:00
|
|
|
|
|
|
|
for (i=0;i<nrofpipes;i++)
|
|
|
|
if (pipes[i].mid.processid==mid->processid)
|
|
|
|
return S_OK;
|
|
|
|
if (pipes)
|
2002-06-04 02:53:21 +02:00
|
|
|
new_pipes=(wine_pipe*)HeapReAlloc(GetProcessHeap(),0,pipes,sizeof(pipes[0])*(nrofpipes+1));
|
2002-02-05 19:11:17 +01:00
|
|
|
else
|
2002-06-04 02:53:21 +02:00
|
|
|
new_pipes=(wine_pipe*)HeapAlloc(GetProcessHeap(),0,sizeof(pipes[0]));
|
|
|
|
if (!new_pipes) return E_OUTOFMEMORY;
|
|
|
|
pipes = new_pipes;
|
2002-02-05 19:11:17 +01:00
|
|
|
sprintf(pipefn,OLESTUBMGR"_%08lx",mid->processid);
|
|
|
|
memcpy(&(pipes[nrofpipes].mid),mid,sizeof(*mid));
|
|
|
|
pipes[nrofpipes].hPipe = hPipe;
|
|
|
|
InitializeCriticalSection(&(pipes[nrofpipes].crit));
|
|
|
|
nrofpipes++;
|
|
|
|
if (startreader) {
|
2002-06-04 02:53:21 +02:00
|
|
|
pipes[nrofpipes-1].hThread = CreateThread(NULL,0,_StubReaderThread,(LPVOID)(pipes+(nrofpipes-1)),0,&(pipes[nrofpipes-1].tid));
|
2002-02-05 19:11:17 +01:00
|
|
|
} else {
|
|
|
|
pipes[nrofpipes-1].tid = GetCurrentThreadId();
|
|
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HANDLE
|
|
|
|
PIPE_FindByMID(wine_marshal_id *mid) {
|
|
|
|
int i;
|
|
|
|
for (i=0;i<nrofpipes;i++)
|
|
|
|
if ((pipes[i].mid.processid==mid->processid) &&
|
|
|
|
(GetCurrentThreadId()==pipes[i].tid)
|
|
|
|
)
|
|
|
|
return pipes[i].hPipe;
|
|
|
|
return INVALID_HANDLE_VALUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static wine_pipe*
|
|
|
|
PIPE_GetFromMID(wine_marshal_id *mid) {
|
|
|
|
int i;
|
|
|
|
for (i=0;i<nrofpipes;i++) {
|
|
|
|
if ((pipes[i].mid.processid==mid->processid) &&
|
|
|
|
(GetCurrentThreadId()==pipes[i].tid)
|
|
|
|
)
|
|
|
|
return pipes+i;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2002-06-01 01:06:46 +02:00
|
|
|
static HRESULT
|
2002-02-05 19:11:17 +01:00
|
|
|
RPC_GetRequest(wine_rpc_request **req) {
|
|
|
|
static int reqid = 0xdeadbeef;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i=0;i<nrofreqs;i++) { /* try to reuse */
|
|
|
|
if (reqs[i]->state == REQSTATE_DONE) {
|
|
|
|
reqs[i]->reqh.reqid = reqid++;
|
|
|
|
reqs[i]->resph.reqid = reqs[i]->reqh.reqid;
|
|
|
|
reqs[i]->hPipe = INVALID_HANDLE_VALUE;
|
|
|
|
*req = reqs[i];
|
|
|
|
reqs[i]->state = REQSTATE_START;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* create new */
|
|
|
|
if (reqs)
|
|
|
|
reqs = (wine_rpc_request**)HeapReAlloc(
|
|
|
|
GetProcessHeap(),
|
|
|
|
HEAP_ZERO_MEMORY,
|
|
|
|
reqs,
|
|
|
|
sizeof(wine_rpc_request*)*(nrofreqs+1)
|
|
|
|
);
|
|
|
|
else
|
|
|
|
reqs = (wine_rpc_request**)HeapAlloc(
|
|
|
|
GetProcessHeap(),
|
|
|
|
HEAP_ZERO_MEMORY,
|
|
|
|
sizeof(wine_rpc_request*)
|
|
|
|
);
|
|
|
|
if (!reqs)
|
|
|
|
return E_OUTOFMEMORY;
|
|
|
|
reqs[nrofreqs] = (wine_rpc_request*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(wine_rpc_request));
|
|
|
|
reqs[nrofreqs]->reqh.reqid = reqid++;
|
|
|
|
reqs[nrofreqs]->resph.reqid = reqs[nrofreqs]->reqh.reqid;
|
|
|
|
reqs[nrofreqs]->hPipe = INVALID_HANDLE_VALUE;
|
|
|
|
*req = reqs[nrofreqs];
|
|
|
|
reqs[nrofreqs]->state = REQSTATE_START;
|
|
|
|
nrofreqs++;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
RPC_FreeRequest(wine_rpc_request *req) {
|
|
|
|
req->state = REQSTATE_DONE; /* Just reuse slot. */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
|
|
|
PipeBuf_QueryInterface(
|
|
|
|
LPRPCCHANNELBUFFER iface,REFIID riid,LPVOID *ppv
|
|
|
|
) {
|
|
|
|
*ppv = NULL;
|
|
|
|
if (IsEqualIID(riid,&IID_IRpcChannelBuffer) || IsEqualIID(riid,&IID_IUnknown)) {
|
|
|
|
*ppv = (LPVOID)iface;
|
|
|
|
IUnknown_AddRef(iface);
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ULONG WINAPI
|
|
|
|
PipeBuf_AddRef(LPRPCCHANNELBUFFER iface) {
|
2004-09-09 23:03:58 +02:00
|
|
|
PipeBuf *This = (PipeBuf *)iface;
|
2002-02-05 19:11:17 +01:00
|
|
|
This->ref++;
|
|
|
|
return This->ref;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ULONG WINAPI
|
|
|
|
PipeBuf_Release(LPRPCCHANNELBUFFER iface) {
|
2004-09-09 23:03:58 +02:00
|
|
|
PipeBuf *This = (PipeBuf *)iface;
|
2004-07-30 01:58:12 +02:00
|
|
|
wine_rpc_disconnect_header header;
|
|
|
|
HANDLE pipe;
|
|
|
|
DWORD reqtype = REQTYPE_DISCONNECT;
|
|
|
|
|
2002-02-05 19:11:17 +01:00
|
|
|
This->ref--;
|
|
|
|
if (This->ref)
|
|
|
|
return This->ref;
|
2004-07-30 01:58:12 +02:00
|
|
|
|
|
|
|
FIXME("Free all stuff\n");
|
|
|
|
|
|
|
|
memcpy(&header.mid, &This->mid, sizeof(wine_marshal_id));
|
|
|
|
|
|
|
|
pipe = PIPE_FindByMID(&This->mid);
|
|
|
|
|
2004-08-02 20:47:31 +02:00
|
|
|
write_pipe(pipe, &reqtype, sizeof(reqtype));
|
|
|
|
write_pipe(pipe, &header, sizeof(wine_rpc_disconnect_header));
|
2004-07-30 01:58:12 +02:00
|
|
|
|
|
|
|
TRACE("written disconnect packet\n");
|
|
|
|
|
2002-02-05 19:11:17 +01:00
|
|
|
HeapFree(GetProcessHeap(),0,This);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
|
|
|
PipeBuf_GetBuffer(
|
|
|
|
LPRPCCHANNELBUFFER iface,RPCOLEMESSAGE* msg,REFIID riid
|
|
|
|
) {
|
2004-09-09 23:03:58 +02:00
|
|
|
/*PipeBuf *This = (PipeBuf *)iface;*/
|
2002-02-05 19:11:17 +01:00
|
|
|
|
2004-07-29 04:43:36 +02:00
|
|
|
TRACE("(%p,%s)\n",msg,debugstr_guid(riid));
|
2002-02-05 19:11:17 +01:00
|
|
|
/* probably reuses IID in real. */
|
|
|
|
if (msg->cbBuffer && (msg->Buffer == NULL))
|
|
|
|
msg->Buffer = HeapAlloc(GetProcessHeap(),0,msg->cbBuffer);
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT
|
2004-08-02 20:47:31 +02:00
|
|
|
COM_InvokeAndRpcSend(wine_rpc_request *req) {
|
2002-02-05 19:11:17 +01:00
|
|
|
IRpcStubBuffer *stub;
|
|
|
|
RPCOLEMESSAGE msg;
|
|
|
|
HRESULT hres;
|
|
|
|
DWORD reqtype;
|
|
|
|
|
|
|
|
hres = MARSHAL_Find_Stub_Buffer(&(req->reqh.mid),&stub);
|
|
|
|
if (hres) {
|
|
|
|
ERR("Stub not found?\n");
|
|
|
|
return hres;
|
|
|
|
}
|
|
|
|
msg.Buffer = req->Buffer;
|
|
|
|
msg.iMethod = req->reqh.iMethod;
|
|
|
|
msg.cbBuffer = req->reqh.cbBuffer;
|
2004-07-24 00:58:13 +02:00
|
|
|
msg.dataRepresentation = NDR_LOCAL_DATA_REPRESENTATION;
|
2002-02-05 19:11:17 +01:00
|
|
|
req->state = REQSTATE_INVOKING;
|
|
|
|
req->resph.retval = IRpcStubBuffer_Invoke(stub,&msg,NULL);
|
2004-07-22 21:43:27 +02:00
|
|
|
IUnknown_Release(stub);
|
2002-02-05 19:11:17 +01:00
|
|
|
req->Buffer = msg.Buffer;
|
|
|
|
req->resph.cbBuffer = msg.cbBuffer;
|
|
|
|
reqtype = REQTYPE_RESPONSE;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = write_pipe(req->hPipe,&reqtype,sizeof(reqtype));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) return hres;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = write_pipe(req->hPipe,&(req->resph),sizeof(req->resph));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) return hres;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = write_pipe(req->hPipe,req->Buffer,req->resph.cbBuffer);
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) return hres;
|
|
|
|
req->state = REQSTATE_DONE;
|
|
|
|
drs("invoke");
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2004-08-02 20:47:31 +02:00
|
|
|
static HRESULT COM_RpcReceive(wine_pipe *xpipe);
|
2002-02-05 19:11:17 +01:00
|
|
|
|
|
|
|
static HRESULT
|
|
|
|
RPC_QueueRequestAndWait(wine_rpc_request *req) {
|
|
|
|
int i;
|
|
|
|
wine_rpc_request *xreq;
|
|
|
|
HRESULT hres;
|
|
|
|
DWORD reqtype;
|
|
|
|
wine_pipe *xpipe = PIPE_GetFromMID(&(req->reqh.mid));
|
|
|
|
|
|
|
|
if (!xpipe) {
|
|
|
|
FIXME("no pipe found.\n");
|
|
|
|
return E_POINTER;
|
|
|
|
}
|
|
|
|
if (GetCurrentProcessId() == req->reqh.mid.processid) {
|
|
|
|
ERR("In current process?\n");
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
req->hPipe = xpipe->hPipe;
|
|
|
|
req->state = REQSTATE_REQ_WAITING_FOR_REPLY;
|
|
|
|
reqtype = REQTYPE_REQUEST;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = write_pipe(req->hPipe,&reqtype,sizeof(reqtype));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) return hres;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = write_pipe(req->hPipe,&(req->reqh),sizeof(req->reqh));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) return hres;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = write_pipe(req->hPipe,req->Buffer,req->reqh.cbBuffer);
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) return hres;
|
|
|
|
|
2004-07-29 04:43:36 +02:00
|
|
|
/* This loop is about allowing re-entrancy. While waiting for the
|
|
|
|
* response to one RPC we may receive a request starting another. */
|
2004-07-30 20:43:07 +02:00
|
|
|
while (!hres) {
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = COM_RpcReceive(xpipe);
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) break;
|
|
|
|
|
|
|
|
for (i=0;i<nrofreqs;i++) {
|
|
|
|
xreq = reqs[i];
|
|
|
|
if ((xreq->state==REQSTATE_REQ_GOT) && (xreq->hPipe==req->hPipe)) {
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = COM_InvokeAndRpcSend(xreq);
|
2004-07-30 20:43:07 +02:00
|
|
|
if (hres) break;
|
2002-02-05 19:11:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (req->state == REQSTATE_RESP_GOT)
|
|
|
|
return S_OK;
|
|
|
|
}
|
2004-07-30 20:43:07 +02:00
|
|
|
if (FAILED(hres))
|
|
|
|
WARN("-- 0x%08lx\n", hres);
|
2002-02-05 19:11:17 +01:00
|
|
|
return hres;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
|
|
|
PipeBuf_SendReceive(
|
|
|
|
LPRPCCHANNELBUFFER iface,RPCOLEMESSAGE* msg,ULONG *status
|
|
|
|
) {
|
2004-09-09 23:03:58 +02:00
|
|
|
PipeBuf *This = (PipeBuf *)iface;
|
2002-02-05 19:11:17 +01:00
|
|
|
wine_rpc_request *req;
|
|
|
|
HRESULT hres;
|
|
|
|
|
|
|
|
TRACE("()\n");
|
|
|
|
|
|
|
|
if (This->mid.processid == GetCurrentProcessId()) {
|
|
|
|
ERR("Need to call directly!\n");
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
hres = RPC_GetRequest(&req);
|
|
|
|
if (hres) return hres;
|
|
|
|
req->reqh.iMethod = msg->iMethod;
|
|
|
|
req->reqh.cbBuffer = msg->cbBuffer;
|
|
|
|
memcpy(&(req->reqh.mid),&(This->mid),sizeof(This->mid));
|
|
|
|
req->Buffer = msg->Buffer;
|
|
|
|
hres = RPC_QueueRequestAndWait(req);
|
|
|
|
if (hres) {
|
|
|
|
RPC_FreeRequest(req);
|
|
|
|
return hres;
|
|
|
|
}
|
|
|
|
msg->cbBuffer = req->resph.cbBuffer;
|
|
|
|
msg->Buffer = req->Buffer;
|
|
|
|
*status = req->resph.retval;
|
|
|
|
RPC_FreeRequest(req);
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
|
|
|
PipeBuf_FreeBuffer(LPRPCCHANNELBUFFER iface,RPCOLEMESSAGE* msg) {
|
|
|
|
FIXME("(%p), stub!\n",msg);
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
|
|
|
PipeBuf_GetDestCtx(
|
|
|
|
LPRPCCHANNELBUFFER iface,DWORD* pdwDestContext,void** ppvDestContext
|
|
|
|
) {
|
|
|
|
FIXME("(%p,%p), stub!\n",pdwDestContext,ppvDestContext);
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT WINAPI
|
|
|
|
PipeBuf_IsConnected(LPRPCCHANNELBUFFER iface) {
|
|
|
|
FIXME("(), stub!\n");
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2004-08-13 01:00:51 +02:00
|
|
|
static IRpcChannelBufferVtbl pipebufvt = {
|
2002-02-05 19:11:17 +01:00
|
|
|
PipeBuf_QueryInterface,
|
|
|
|
PipeBuf_AddRef,
|
|
|
|
PipeBuf_Release,
|
|
|
|
PipeBuf_GetBuffer,
|
|
|
|
PipeBuf_SendReceive,
|
|
|
|
PipeBuf_FreeBuffer,
|
|
|
|
PipeBuf_GetDestCtx,
|
|
|
|
PipeBuf_IsConnected
|
|
|
|
};
|
|
|
|
|
|
|
|
HRESULT
|
|
|
|
PIPE_GetNewPipeBuf(wine_marshal_id *mid, IRpcChannelBuffer **pipebuf) {
|
|
|
|
wine_marshal_id ourid;
|
|
|
|
DWORD res;
|
|
|
|
HANDLE hPipe;
|
|
|
|
HRESULT hres;
|
|
|
|
PipeBuf *pbuf;
|
|
|
|
|
|
|
|
hPipe = PIPE_FindByMID(mid);
|
|
|
|
if (hPipe == INVALID_HANDLE_VALUE) {
|
|
|
|
char pipefn[200];
|
|
|
|
sprintf(pipefn,OLESTUBMGR"_%08lx",mid->processid);
|
|
|
|
hPipe = CreateFileA(
|
|
|
|
pipefn,
|
|
|
|
GENERIC_READ|GENERIC_WRITE,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
OPEN_EXISTING,
|
|
|
|
0,
|
2002-11-22 05:43:02 +01:00
|
|
|
0
|
2002-02-05 19:11:17 +01:00
|
|
|
);
|
|
|
|
if (hPipe == INVALID_HANDLE_VALUE) {
|
|
|
|
FIXME("Could not open named pipe %s, le is %lx\n",pipefn,GetLastError());
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
hres = PIPE_RegisterPipe(mid, hPipe, FALSE);
|
|
|
|
if (hres) return hres;
|
|
|
|
memset(&ourid,0,sizeof(ourid));
|
|
|
|
ourid.processid = GetCurrentProcessId();
|
|
|
|
if (!WriteFile(hPipe,&ourid,sizeof(ourid),&res,NULL)||(res!=sizeof(ourid))) {
|
|
|
|
ERR("Failed writing startup mid!\n");
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pbuf = (PipeBuf*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(PipeBuf));
|
|
|
|
pbuf->lpVtbl = &pipebufvt;
|
|
|
|
pbuf->ref = 1;
|
|
|
|
memcpy(&(pbuf->mid),mid,sizeof(*mid));
|
|
|
|
*pipebuf = (IRpcChannelBuffer*)pbuf;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT
|
|
|
|
create_server(REFCLSID rclsid) {
|
2004-07-23 21:10:13 +02:00
|
|
|
static const WCHAR embedding[] = { ' ', '-','E','m','b','e','d','d','i','n','g',0 };
|
2002-02-05 19:11:17 +01:00
|
|
|
HKEY key;
|
|
|
|
char buf[200];
|
|
|
|
HRESULT hres = E_UNEXPECTED;
|
|
|
|
char xclsid[80];
|
2004-07-23 21:10:13 +02:00
|
|
|
WCHAR exe[MAX_PATH+1];
|
|
|
|
DWORD exelen = sizeof(exe);
|
|
|
|
WCHAR command[MAX_PATH+sizeof(embedding)/sizeof(WCHAR)];
|
2002-02-05 19:11:17 +01:00
|
|
|
STARTUPINFOW sinfo;
|
|
|
|
PROCESS_INFORMATION pinfo;
|
|
|
|
|
|
|
|
WINE_StringFromCLSID((LPCLSID)rclsid,xclsid);
|
|
|
|
|
|
|
|
sprintf(buf,"CLSID\\%s\\LocalServer32",xclsid);
|
|
|
|
hres = RegOpenKeyExA(HKEY_CLASSES_ROOT, buf, 0, KEY_READ, &key);
|
|
|
|
|
2004-07-23 21:10:13 +02:00
|
|
|
if (hres != ERROR_SUCCESS) {
|
|
|
|
WARN("CLSID %s not registered as LocalServer32\n", xclsid);
|
2002-05-29 21:20:32 +02:00
|
|
|
return REGDB_E_READREGDB; /* Probably */
|
2004-07-23 21:10:13 +02:00
|
|
|
}
|
2002-02-05 19:11:17 +01:00
|
|
|
|
2004-07-23 21:10:13 +02:00
|
|
|
memset(exe,0,sizeof(exe));
|
|
|
|
hres= RegQueryValueExW(key, NULL, NULL, NULL, (LPBYTE)exe, &exelen);
|
2002-05-29 21:20:32 +02:00
|
|
|
RegCloseKey(key);
|
2004-07-23 21:10:13 +02:00
|
|
|
if (hres) {
|
|
|
|
WARN("No default value for LocalServer32 key\n");
|
|
|
|
return REGDB_E_CLASSNOTREG; /* FIXME: check retval */
|
|
|
|
}
|
|
|
|
|
2002-02-05 19:11:17 +01:00
|
|
|
memset(&sinfo,0,sizeof(sinfo));
|
|
|
|
sinfo.cb = sizeof(sinfo);
|
2004-07-23 21:10:13 +02:00
|
|
|
|
|
|
|
/* EXE servers are started with the -Embedding switch. MSDN also claims /Embedding is used,
|
|
|
|
9x does -Embedding, perhaps an 9x/NT difference? */
|
|
|
|
|
|
|
|
strcpyW(command, exe);
|
|
|
|
strcatW(command, embedding);
|
|
|
|
|
|
|
|
TRACE("activating local server '%s' for %s\n", debugstr_w(command), xclsid);
|
|
|
|
|
|
|
|
if (!CreateProcessW(exe, command, NULL, NULL, FALSE, 0, NULL, NULL, &sinfo, &pinfo)) {
|
|
|
|
WARN("failed to run local server %s\n", debugstr_w(exe));
|
2002-02-05 19:11:17 +01:00
|
|
|
return E_FAIL;
|
2004-07-23 21:10:13 +02:00
|
|
|
}
|
|
|
|
|
2002-02-05 19:11:17 +01:00
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
/* http://msdn.microsoft.com/library/en-us/dnmsj99/html/com0199.asp, Figure 4 */
|
|
|
|
HRESULT create_marshalled_proxy(REFCLSID rclsid, REFIID iid, LPVOID *ppv) {
|
|
|
|
HRESULT hres;
|
|
|
|
HANDLE hPipe;
|
|
|
|
char pipefn[200];
|
|
|
|
DWORD res,bufferlen;
|
|
|
|
char marshalbuffer[200];
|
|
|
|
IStream *pStm;
|
|
|
|
LARGE_INTEGER seekto;
|
|
|
|
ULARGE_INTEGER newpos;
|
|
|
|
int tries = 0;
|
|
|
|
#define MAXTRIES 10000
|
|
|
|
|
2004-07-23 21:10:13 +02:00
|
|
|
TRACE("rclsid=%s, iid=%s\n", debugstr_guid(rclsid), debugstr_guid(iid));
|
|
|
|
|
2002-02-05 19:11:17 +01:00
|
|
|
strcpy(pipefn,PIPEPREF);
|
|
|
|
WINE_StringFromCLSID(rclsid,pipefn+strlen(PIPEPREF));
|
|
|
|
|
|
|
|
while (tries++<MAXTRIES) {
|
2004-07-16 00:07:44 +02:00
|
|
|
WaitNamedPipeA( pipefn, NMPWAIT_WAIT_FOREVER );
|
2002-02-05 19:11:17 +01:00
|
|
|
hPipe = CreateFileA(
|
|
|
|
pipefn,
|
|
|
|
GENERIC_READ|GENERIC_WRITE,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
OPEN_EXISTING,
|
|
|
|
0,
|
2002-11-22 05:43:02 +01:00
|
|
|
0
|
2002-02-05 19:11:17 +01:00
|
|
|
);
|
|
|
|
if (hPipe == INVALID_HANDLE_VALUE) {
|
|
|
|
if (tries == 1) {
|
|
|
|
if ((hres = create_server(rclsid)))
|
|
|
|
return hres;
|
|
|
|
Sleep(1000);
|
|
|
|
} else {
|
|
|
|
WARN("Could not open named pipe to broker %s, le is %lx\n",pipefn,GetLastError());
|
|
|
|
Sleep(1000);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
bufferlen = 0;
|
|
|
|
if (!ReadFile(hPipe,marshalbuffer,sizeof(marshalbuffer),&bufferlen,NULL)) {
|
|
|
|
FIXME("Failed to read marshal id from classfactory of %s.\n",debugstr_guid(rclsid));
|
|
|
|
Sleep(1000);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
CloseHandle(hPipe);
|
|
|
|
break;
|
|
|
|
}
|
2002-06-01 01:06:46 +02:00
|
|
|
if (tries>=MAXTRIES)
|
2002-02-05 19:11:17 +01:00
|
|
|
return E_NOINTERFACE;
|
|
|
|
hres = CreateStreamOnHGlobal(0,TRUE,&pStm);
|
|
|
|
if (hres) return hres;
|
|
|
|
hres = IStream_Write(pStm,marshalbuffer,bufferlen,&res);
|
|
|
|
if (hres) goto out;
|
2004-01-23 02:51:33 +01:00
|
|
|
seekto.u.LowPart = 0;seekto.u.HighPart = 0;
|
2002-02-05 19:11:17 +01:00
|
|
|
hres = IStream_Seek(pStm,seekto,SEEK_SET,&newpos);
|
|
|
|
hres = CoUnmarshalInterface(pStm,&IID_IClassFactory,ppv);
|
|
|
|
out:
|
|
|
|
IStream_Release(pStm);
|
|
|
|
return hres;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void WINAPI
|
|
|
|
PIPE_StartRequestThread(HANDLE xhPipe) {
|
|
|
|
wine_marshal_id remoteid;
|
|
|
|
HRESULT hres;
|
|
|
|
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe,&remoteid,sizeof(remoteid));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) {
|
|
|
|
ERR("Failed to read remote mid!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PIPE_RegisterPipe(&remoteid,xhPipe, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT
|
2004-08-02 20:47:31 +02:00
|
|
|
COM_RpcReceive(wine_pipe *xpipe) {
|
2002-02-05 19:11:17 +01:00
|
|
|
DWORD reqtype;
|
|
|
|
HRESULT hres = S_OK;
|
|
|
|
HANDLE xhPipe = xpipe->hPipe;
|
|
|
|
|
|
|
|
/*FIXME("%lx %d reading reqtype\n",GetCurrentProcessId(),xhPipe);*/
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe,&reqtype,sizeof(reqtype));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) goto end;
|
|
|
|
EnterCriticalSection(&(xpipe->crit));
|
|
|
|
/*FIXME("%lx got reqtype %ld\n",GetCurrentProcessId(),reqtype);*/
|
|
|
|
|
2004-07-30 01:58:12 +02:00
|
|
|
if (reqtype == REQTYPE_DISCONNECT) { /* only received by servers */
|
|
|
|
wine_rpc_disconnect_header header;
|
|
|
|
IRpcStubBuffer *stub;
|
|
|
|
ULONG ret;
|
|
|
|
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe, &header, sizeof(header));
|
2004-07-30 01:58:12 +02:00
|
|
|
if (hres) {
|
|
|
|
ERR("could not read disconnect header\n");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
TRACE("read disconnect header\n");
|
|
|
|
|
|
|
|
hres = MARSHAL_Find_Stub_Buffer(&header.mid, &stub);
|
|
|
|
if (hres) {
|
|
|
|
ERR("could not locate stub to disconnect, mid.objectid=%p\n", (void*)header.mid.objectid);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* release reference added by MARSHAL_Find_Stub_Buffer call */
|
|
|
|
IRpcStubBuffer_Release(stub);
|
|
|
|
/* release it for real */
|
|
|
|
ret = IRpcStubBuffer_Release(stub);
|
|
|
|
/* FIXME: race */
|
|
|
|
if (ret == 0)
|
|
|
|
MARSHAL_Invalidate_Stub_From_MID(&header.mid);
|
|
|
|
goto end;
|
|
|
|
} else if (reqtype == REQTYPE_REQUEST) {
|
2002-02-05 19:11:17 +01:00
|
|
|
wine_rpc_request *xreq;
|
|
|
|
RPC_GetRequest(&xreq);
|
|
|
|
xreq->hPipe = xhPipe;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe,&(xreq->reqh),sizeof(xreq->reqh));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) goto end;
|
|
|
|
xreq->resph.reqid = xreq->reqh.reqid;
|
|
|
|
xreq->Buffer = HeapAlloc(GetProcessHeap(),0, xreq->reqh.cbBuffer);
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe,xreq->Buffer,xreq->reqh.cbBuffer);
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) goto end;
|
|
|
|
xreq->state = REQSTATE_REQ_GOT;
|
|
|
|
goto end;
|
2004-07-30 01:58:12 +02:00
|
|
|
} else if (reqtype == REQTYPE_RESPONSE) {
|
2002-02-05 19:11:17 +01:00
|
|
|
wine_rpc_response_header resph;
|
|
|
|
int i;
|
|
|
|
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe,&resph,sizeof(resph));
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) goto end;
|
|
|
|
for (i=nrofreqs;i--;) {
|
|
|
|
wine_rpc_request *xreq = reqs[i];
|
|
|
|
if (xreq->state != REQSTATE_REQ_WAITING_FOR_REPLY)
|
|
|
|
continue;
|
|
|
|
if (xreq->reqh.reqid == resph.reqid) {
|
|
|
|
memcpy(&(xreq->resph),&resph,sizeof(resph));
|
2003-10-14 07:24:20 +02:00
|
|
|
|
|
|
|
if (xreq->Buffer)
|
|
|
|
xreq->Buffer = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,xreq->Buffer,xreq->resph.cbBuffer);
|
|
|
|
else
|
|
|
|
xreq->Buffer = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,xreq->resph.cbBuffer);
|
|
|
|
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = read_pipe(xhPipe,xreq->Buffer,xreq->resph.cbBuffer);
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) goto end;
|
|
|
|
xreq->state = REQSTATE_RESP_GOT;
|
|
|
|
/*PulseEvent(hRpcChanged);*/
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ERR("Did not find request for id %lx\n",resph.reqid);
|
|
|
|
hres = S_OK;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
ERR("Unknown reqtype %ld\n",reqtype);
|
|
|
|
hres = E_FAIL;
|
|
|
|
end:
|
|
|
|
LeaveCriticalSection(&(xpipe->crit));
|
|
|
|
return hres;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DWORD WINAPI
|
|
|
|
_StubReaderThread(LPVOID param) {
|
|
|
|
wine_pipe *xpipe = (wine_pipe*)param;
|
|
|
|
HANDLE xhPipe = xpipe->hPipe;
|
2004-07-30 20:43:07 +02:00
|
|
|
HRESULT hres = S_OK;
|
2002-02-05 19:11:17 +01:00
|
|
|
|
|
|
|
TRACE("STUB reader thread %lx\n",GetCurrentProcessId());
|
2004-07-30 20:43:07 +02:00
|
|
|
while (!hres) {
|
2002-02-05 19:11:17 +01:00
|
|
|
int i;
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = COM_RpcReceive(xpipe);
|
2002-02-05 19:11:17 +01:00
|
|
|
if (hres) break;
|
|
|
|
|
|
|
|
for (i=nrofreqs;i--;) {
|
|
|
|
wine_rpc_request *xreq = reqs[i];
|
|
|
|
if ((xreq->state == REQSTATE_REQ_GOT) && (xreq->hPipe == xhPipe)) {
|
2004-08-02 20:47:31 +02:00
|
|
|
hres = COM_InvokeAndRpcSend(xreq);
|
2004-07-30 20:43:07 +02:00
|
|
|
if (!hres) break;
|
2002-02-05 19:11:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FIXME("Failed with hres %lx\n",hres);
|
|
|
|
CloseHandle(xhPipe);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DWORD WINAPI
|
|
|
|
_StubMgrThread(LPVOID param) {
|
|
|
|
char pipefn[200];
|
|
|
|
HANDLE listenPipe;
|
|
|
|
|
|
|
|
sprintf(pipefn,OLESTUBMGR"_%08lx",GetCurrentProcessId());
|
|
|
|
TRACE("Stub Manager Thread starting on (%s)\n",pipefn);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
listenPipe = CreateNamedPipeA(
|
|
|
|
pipefn,
|
|
|
|
PIPE_ACCESS_DUPLEX,
|
|
|
|
PIPE_TYPE_BYTE|PIPE_WAIT,
|
|
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
|
|
4096,
|
|
|
|
4096,
|
|
|
|
NMPWAIT_USE_DEFAULT_WAIT,
|
|
|
|
NULL
|
|
|
|
);
|
|
|
|
if (listenPipe == INVALID_HANDLE_VALUE) {
|
|
|
|
FIXME("pipe creation failed for %s, le is %lx\n",pipefn,GetLastError());
|
|
|
|
return 1; /* permanent failure, so quit stubmgr thread */
|
|
|
|
}
|
2003-02-15 00:30:50 +01:00
|
|
|
if (!ConnectNamedPipe(listenPipe,NULL)) {
|
|
|
|
ERR("Failure during ConnectNamedPipe %lx!\n",GetLastError());
|
|
|
|
CloseHandle(listenPipe);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
PIPE_StartRequestThread(listenPipe);
|
2002-02-05 19:11:17 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
STUBMGR_Start() {
|
|
|
|
static BOOL stubMgrRunning = FALSE;
|
|
|
|
DWORD tid;
|
|
|
|
|
|
|
|
if (!stubMgrRunning) {
|
|
|
|
stubMgrRunning = TRUE;
|
|
|
|
CreateThread(NULL,0,_StubMgrThread,NULL,0,&tid);
|
|
|
|
Sleep(2000); /* actually we just try opening the pipe until it succeeds */
|
|
|
|
}
|
|
|
|
}
|