mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1386 lines
35 KiB
1386 lines
35 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
winyield.c
|
|
|
|
Abstract:
|
|
|
|
This file contains Win16 implementations of the yielding routines
|
|
routines. For more information, see the specification entitled
|
|
"Windows RPC Yielding Support."
|
|
|
|
Author:
|
|
|
|
Danny Glasser (dannygl) - 6-May-1992
|
|
|
|
Revision History:
|
|
|
|
Danny Glasser (dannygl) - 8-Jun-1992
|
|
Fixed exit-list handling (NT bug 10341) by using Windows task-exit
|
|
notification instead of C run-time atexit.
|
|
|
|
Danny Glasser (dannygl) - 14-Aug-1992
|
|
Added code to protect against a second RPC call being made while
|
|
one is in progress.
|
|
|
|
Danny Glasser (dannygl) - 21-Aug-1992
|
|
Fix customized yielding so that completion message is posted only
|
|
when the custom yield function has been called.
|
|
|
|
Danny Glasser (dannygl) - 18-Sep-1992
|
|
Fixed the case where a yielding app comes in, followed by a non
|
|
yielding app, and then the yielding app completes first.
|
|
|
|
Danny Glasser (dannygl) - 22-Sep-1992
|
|
Performance improvements, pass 1: Set and used <latest_task> in
|
|
FindYieldInfo and added <hTaskNoYield> to make task lookup in
|
|
I_RpcWinAsyncCallBegin faster (for the yielding and non-yielding
|
|
cases, respectively).
|
|
|
|
Danny Glasser (dannygl) - 22-Sep-1992
|
|
Performance improvements, pass 2: Removed the page-locking code
|
|
(according to BaratS, I_RpcWinAsyncCallComplete is called only at
|
|
ring 3) and moved the yield data into the local heap.
|
|
|
|
Mario Goertzel (mariogo) - Feb 1995.
|
|
Added user defined timeout's. Allowed winsock to run as blocking
|
|
by always doing a PeekMessage in blocking loop.
|
|
|
|
Mario Goertzel (mariogo) - Mar 1995.
|
|
Removed hTaskNoYield and loNoYieldContext since more then one
|
|
blocking call is possible with winsock.
|
|
|
|
Mario Goerzel (mariogo) - Nov 10, 1995.
|
|
Added NoTaskYield flag to keep 16bit exchange happy.
|
|
--*/
|
|
|
|
#include <windows.h>
|
|
#include <toolhelp.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sysinc.h>
|
|
#include <rpc.h>
|
|
#include <rpcwin.h>
|
|
#include <rpctran.h>
|
|
#include <regapi.h>
|
|
|
|
// Some inline assembler for trival windows functions.
|
|
#define WIN_ENTER_CRITICAL() _asm cli
|
|
#define WIN_EXIT_CRITICAL() _asm sti
|
|
|
|
// Name of default (RPC r/t-provided) dialog box template
|
|
#define DEFAULT_DIALOG_NAME "RPCYIELD"
|
|
|
|
// Time (in milliseconds) to wait before invoking yielding code
|
|
#define DEFAULT_YIELD_TIMEOUT 500
|
|
|
|
// Data types and structures used to store per-task yielding info
|
|
typedef enum {YIELD_EMPTY = 0, YIELD_NONE, YIELD_DIALOG, YIELD_CUSTOM}
|
|
YIELDTYPE;
|
|
|
|
typedef struct _tagYIELDINFO
|
|
{
|
|
// Static, per-app information
|
|
HTASK hTask;
|
|
HWND hWnd;
|
|
WORD wMsg;
|
|
YIELDTYPE tyClass;
|
|
unsigned int iTimeout;
|
|
union
|
|
{
|
|
FARPROC lpYieldFunction;
|
|
HANDLE hDialogTemplate;
|
|
|
|
} dwOtherInfo;
|
|
|
|
// Dynamic, per-call information
|
|
LPVOID lpContext;
|
|
DWORD dwCallTime;
|
|
|
|
volatile unsigned fActive : 1;
|
|
volatile unsigned fYielding : 1;
|
|
volatile unsigned fAborted : 1;
|
|
volatile unsigned fTaskYield : 1;
|
|
|
|
HWND hDialog; // only needed for Class 2
|
|
BOOL fDialogueBoxVisible;
|
|
unsigned CompletionCount;
|
|
|
|
} YIELDINFO, *PYIELDINFO;
|
|
|
|
|
|
#define YIELD_TASK_INCREMENT 16
|
|
|
|
INTERNAL_VARIABLE PYIELDINFO yield_info_array = NULL;
|
|
INTERNAL_VARIABLE int num_yield_tasks = 0;
|
|
|
|
// Most recent task to make a call; used as a 1-entry cache
|
|
INTERNAL_VARIABLE PYIELDINFO latest_task = NULL;
|
|
|
|
|
|
|
|
// ********************** INTERNAL RPC FUNCTIONS **********************
|
|
BOOL PAPI PASCAL
|
|
CreateYieldInfo(void)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the RPC run-time DLL initialization code
|
|
to allocate the initial memory for per-task yielding info.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The memory was allocated successfully.
|
|
|
|
FALSE - The memory could not be allocated.
|
|
|
|
--*/
|
|
{
|
|
ASSERT(yield_info_array == NULL);
|
|
|
|
if (yield_info_array == NULL)
|
|
{
|
|
yield_info_array = (PYIELDINFO)
|
|
LocalAlloc(LPTR,
|
|
sizeof(YIELDINFO) *
|
|
YIELD_TASK_INCREMENT);
|
|
|
|
if (yield_info_array == NULL)
|
|
return FALSE;
|
|
|
|
num_yield_tasks = YIELD_TASK_INCREMENT;
|
|
|
|
// We set the <latest_task> to point to the first entry so that
|
|
// we need not bother with NULL pointer tests later on
|
|
latest_task = yield_info_array;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void PAPI PASCAL
|
|
DeleteYieldInfo(void)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the RPC run-time DLL exit code (WEP)
|
|
to free the memory used for per-task yielding info.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
if (yield_info_array != NULL)
|
|
{
|
|
EVAL_AND_ASSERT( LocalFree((HLOCAL) yield_info_array) == NULL);
|
|
yield_info_array = NULL;
|
|
num_yield_tasks = 0;
|
|
latest_task = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
INTERNAL_FUNCTION PYIELDINFO PASCAL
|
|
FindYieldInfo(
|
|
IN BOOL fNewEntry,
|
|
IN HTASK hTask
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used by other functions in this file to retrieve
|
|
the yielding info for the current task.
|
|
|
|
Arguments:
|
|
|
|
fNewEntry - Set to TRUE if we should allocate a new entry for a
|
|
task if none exists yet; FALSE if we should perform look-up
|
|
only.
|
|
|
|
hTask - Handle of the current task (or NULL if the caller doesn't
|
|
know). Saves us from unnecessarily calling GetCurrentTask if
|
|
the caller already has.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the yielding info structure for the current task.
|
|
Returns NULL if there is no entry for the current task (or if
|
|
one could not be allocated).
|
|
|
|
--*/
|
|
{
|
|
int i;
|
|
PYIELDINFO current_entry;
|
|
PYIELDINFO task_entry = NULL;
|
|
PYIELDINFO blank_entry = NULL;
|
|
HLOCAL hTemp;
|
|
|
|
// Get the task handle, if necessary
|
|
if (hTask == NULL)
|
|
hTask = GetCurrentTask();
|
|
|
|
ASSERT(hTask != NULL);
|
|
|
|
// See if we're accessing the most recently used task (and it's
|
|
// valid for our purposes)
|
|
if (latest_task->hTask == hTask
|
|
&& (latest_task->tyClass != YIELD_EMPTY || fNewEntry))
|
|
{
|
|
task_entry = latest_task;
|
|
}
|
|
else
|
|
{
|
|
// Search the array for the current task and for an empty entry
|
|
for (i = 0, current_entry = yield_info_array;
|
|
i < num_yield_tasks;
|
|
i++, current_entry++)
|
|
{
|
|
if (current_entry->hTask == hTask)
|
|
{
|
|
// Check if the entry is empty
|
|
if (current_entry->tyClass != YIELD_EMPTY || fNewEntry)
|
|
{
|
|
task_entry = current_entry;
|
|
}
|
|
break;
|
|
}
|
|
else if (fNewEntry &&
|
|
blank_entry == NULL &&
|
|
current_entry->tyClass == YIELD_EMPTY)
|
|
{
|
|
blank_entry = current_entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't find the task, we use the blank entry
|
|
if (fNewEntry && task_entry == NULL)
|
|
{
|
|
if (blank_entry != NULL)
|
|
{
|
|
task_entry = blank_entry;
|
|
}
|
|
else
|
|
{
|
|
WIN_ENTER_CRITICAL();
|
|
|
|
// There wasn't a blank entry, so we need to enlarge the array
|
|
num_yield_tasks += YIELD_TASK_INCREMENT;
|
|
|
|
hTemp = LocalReAlloc((HLOCAL) yield_info_array,
|
|
sizeof(YIELDINFO) * num_yield_tasks,
|
|
LMEM_MOVEABLE | LMEM_ZEROINIT);
|
|
|
|
if (hTemp != NULL)
|
|
{
|
|
// Reallocation succeeded
|
|
yield_info_array = (PYIELDINFO) hTemp;
|
|
task_entry = yield_info_array + i;
|
|
}
|
|
else
|
|
{
|
|
// Reallocation failed
|
|
num_yield_tasks -= YIELD_TASK_INCREMENT;
|
|
}
|
|
|
|
WIN_EXIT_CRITICAL();
|
|
}
|
|
}
|
|
|
|
// If we found an entry, cache it
|
|
if (task_entry != NULL)
|
|
latest_task = task_entry;
|
|
|
|
return task_entry;
|
|
}
|
|
|
|
|
|
void CALLBACK
|
|
WinYieldCleanup(HTASK htask)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the function that cleans up the yielding info entry
|
|
for the current task. It is registered via a call to WinDLLAtExit()
|
|
|
|
Arguments:
|
|
|
|
htask - The handle of the current task.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
// Find the task and set its status to empty
|
|
PYIELDINFO yield_entry;
|
|
|
|
yield_entry = FindYieldInfo(FALSE, NULL);
|
|
|
|
ASSERT(yield_entry != NULL);
|
|
|
|
if (yield_entry != NULL)
|
|
yield_entry->tyClass = YIELD_EMPTY;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOL PAPI PASCAL _loadds
|
|
I_RpcYieldDialogFunction(
|
|
IN HWND hDialog,
|
|
IN WORD wMessage,
|
|
IN WORD wParam,
|
|
IN DWORD lParam)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the Windows dialog function that handles dialog-box
|
|
based yielding (for Class 2 applications). See the description of
|
|
the DialogProc function in the Win 3.1 SDK for more info.
|
|
|
|
--*/
|
|
{
|
|
PYIELDINFO task_entry;
|
|
RECT rectParent, rectDialog;
|
|
|
|
switch(wMessage)
|
|
{
|
|
case WM_INITDIALOG:
|
|
// Set dialog handle field
|
|
task_entry = FindYieldInfo(FALSE, NULL);
|
|
|
|
ASSERT(task_entry != NULL);
|
|
|
|
// Set dialog box handle (for use by I_RpcWinAsyncCallComplete);
|
|
// do this in a crit-sec to preserve atomicity
|
|
WIN_ENTER_CRITICAL();
|
|
task_entry->hDialog = hDialog;
|
|
WIN_EXIT_CRITICAL();
|
|
|
|
// It's possible that the RPC call completed before the dialog
|
|
// box was created (and this function was called).
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// If we're using the default dialog box, and both of its
|
|
// dimensions are smaller than its parent's, center it
|
|
if (task_entry->dwOtherInfo.hDialogTemplate == 0)
|
|
{
|
|
GetWindowRect(GetParent(hDialog), &rectParent);
|
|
GetWindowRect(hDialog, &rectDialog);
|
|
rectParent.top += GetSystemMetrics(SM_CYCAPTION);
|
|
|
|
if (rectDialog.right - rectDialog.left <
|
|
rectParent.right - rectParent.left
|
|
&&
|
|
rectDialog.bottom - rectDialog.top <
|
|
rectParent.bottom - rectParent.top)
|
|
{
|
|
SetWindowPos(hDialog,
|
|
NULL,
|
|
(rectParent.left +
|
|
rectParent.right +
|
|
rectDialog.left -
|
|
rectDialog.right)
|
|
/ 2,
|
|
(rectParent.top +
|
|
rectParent.bottom +
|
|
rectDialog.top -
|
|
rectDialog.bottom)
|
|
/ 2,
|
|
0,
|
|
0,
|
|
SWP_NOSIZE | SWP_NOZORDER);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
switch(wParam)
|
|
{
|
|
case IDCANCEL:
|
|
{
|
|
task_entry = FindYieldInfo(FALSE, NULL);
|
|
|
|
ASSERT(task_entry != NULL);
|
|
|
|
task_entry->fAborted = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_USER:
|
|
#ifdef DEBUGRPC
|
|
task_entry = FindYieldInfo(FALSE, NULL);
|
|
|
|
ASSERT(task_entry != NULL);
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
RPC_STATUS FAR PASCAL
|
|
I_RpcWinCallInProgress(void)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used by other parts of the runtime to determine if
|
|
a call is already in progress (primarily to prevent another call from
|
|
being made).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OUT_OF_MEMORY - If unable to allocate YIELDINFO.
|
|
|
|
RPC_S_CALL_IN_PROCESS - If an RPC call is already in process
|
|
in this task.
|
|
|
|
--*/
|
|
{
|
|
PYIELDINFO task_entry;
|
|
|
|
task_entry = FindYieldInfo(TRUE, NULL);
|
|
|
|
if (0 == task_entry)
|
|
{
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
if (task_entry->tyClass == YIELD_EMPTY)
|
|
{
|
|
EVAL_AND_ASSERT(
|
|
WinDLLAtExit(WinYieldCleanup)
|
|
);
|
|
|
|
// Since it didn't already exist, this task must
|
|
// not have set yield info yet.
|
|
|
|
task_entry->hTask = GetCurrentTask();
|
|
task_entry->tyClass = YIELD_NONE;
|
|
task_entry->fTaskYield = TRUE;
|
|
task_entry->fActive = FALSE;
|
|
|
|
ASSERT(task_entry->lpContext == 0);
|
|
}
|
|
|
|
if (task_entry->fActive)
|
|
{
|
|
return(RPC_S_CALL_IN_PROGRESS);
|
|
}
|
|
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
void
|
|
SafeIncrement(
|
|
unsigned __RPC_FAR * Counter
|
|
)
|
|
{
|
|
_asm
|
|
{
|
|
les bx, Counter
|
|
inc word ptr es:[bx]
|
|
}
|
|
}
|
|
|
|
void
|
|
SafeDecrement(
|
|
unsigned __RPC_FAR * Counter
|
|
)
|
|
{
|
|
_asm
|
|
{
|
|
les bx, Counter
|
|
dec word ptr es:[bx]
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ******************* APPLICATION-CALLABLE FUNCTIONS *******************
|
|
RPC_STATUS RPC_ENTRY
|
|
RpcWinSetYieldInfo(
|
|
IN HWND hWnd,
|
|
IN BOOL fCustomYield,
|
|
IN WORD wMsg OPTIONAL,
|
|
IN DWORD dwOtherInfo OPTIONAL)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by a (Class 2 or 3) application to set info
|
|
about how it wishes to yield.
|
|
|
|
Arguments:
|
|
hWnd - The handle of the application window that is making the RPC
|
|
call. Typically this is the application's top-level window.
|
|
|
|
fCustomYield - Set to TRUE if the application is providing its own
|
|
customized yielding mechanism (i.e. Class 3) or to FALSE if the
|
|
application is using the default, modal dialog-box-based yielding
|
|
mechanism (i.e. Class 2).
|
|
|
|
wMsg - The type of message that RPC posts to notify the application
|
|
of RPC events. This value can be zero for a Class 2 application,
|
|
in which case messages will not be posted.
|
|
|
|
dwOtherInfo - Specifies additional application-specific information.
|
|
If fCustomYield is TRUE, this contains the pointer to the
|
|
application-specified yielding function. If fCustomYield is FALSE,
|
|
this contains the (optional) handle of the application-supplied
|
|
dialog box resource; if this handle is zero, the default
|
|
(RPC run-time supplied) dialog box is used.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - The information was set successfully.
|
|
|
|
RPC_S_OUT_OF_MEMORY - Memory could not be allocated to store the
|
|
information for this task.
|
|
|
|
--*/
|
|
{
|
|
HTASK hTask = GetCurrentTask();
|
|
PYIELDINFO task_entry;
|
|
|
|
ASSERT(hTask != NULL);
|
|
|
|
task_entry = FindYieldInfo(TRUE, hTask);
|
|
|
|
if (task_entry == NULL)
|
|
return RPC_S_OUT_OF_MEMORY;
|
|
|
|
if (task_entry->tyClass == YIELD_EMPTY)
|
|
{
|
|
// a new task
|
|
//
|
|
EVAL_AND_ASSERT(
|
|
WinDLLAtExit(WinYieldCleanup)
|
|
);
|
|
|
|
task_entry->hTask = hTask;
|
|
task_entry->fActive = FALSE;
|
|
task_entry->iTimeout = DEFAULT_YIELD_TIMEOUT;
|
|
}
|
|
|
|
task_entry->hWnd = hWnd;
|
|
task_entry->wMsg = wMsg;
|
|
task_entry->fTaskYield = TRUE;
|
|
|
|
if (fCustomYield)
|
|
{
|
|
ASSERT(task_entry->wMsg != 0);
|
|
|
|
task_entry->tyClass = YIELD_CUSTOM;
|
|
task_entry->dwOtherInfo.lpYieldFunction = (FARPROC) dwOtherInfo;
|
|
}
|
|
else
|
|
{
|
|
task_entry->tyClass = YIELD_DIALOG;
|
|
task_entry->dwOtherInfo.hDialogTemplate = (HANDLE) dwOtherInfo;
|
|
|
|
if (dwOtherInfo)
|
|
{
|
|
if (0 == GlobalLock((HANDLE) dwOtherInfo))
|
|
{
|
|
return RPC_S_INVALID_ARG;
|
|
}
|
|
|
|
GlobalUnlock((HANDLE) dwOtherInfo);
|
|
}
|
|
}
|
|
|
|
return RPC_S_OK;
|
|
}
|
|
|
|
|
|
RPC_STATUS RPC_ENTRY
|
|
RpcWinSetYieldTimeout(
|
|
IN unsigned int Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by an application to set the period
|
|
of time it wishes to block before yielding code is called.
|
|
|
|
Arguments:
|
|
|
|
Timeout - Milliseconds to wait before yielding.
|
|
|
|
If this value is too low, a class 1/class 2 app's dialog
|
|
box will flash on and off too much when making large RPC
|
|
calls. Performance may suffer.
|
|
|
|
If the value is too large, the system will appear to hang
|
|
for the timeout period. Performance will be good.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OK - The information was set successfully.
|
|
|
|
--*/
|
|
{
|
|
HTASK hTask = GetCurrentTask();
|
|
PYIELDINFO task_entry;
|
|
|
|
task_entry = FindYieldInfo(FALSE, hTask);
|
|
|
|
if (0 == task_entry)
|
|
{
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
task_entry->iTimeout = Timeout;
|
|
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
|
|
RPC_STATUS RPC_ENTRY
|
|
I_RpcWinSetTaskYielding(
|
|
IN unsigned fYield
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Nasty win16-ism for Exchange. The MAPI provider promises NOT to yield
|
|
to other tasks while blocked. The applications written on top of MAPI
|
|
are no reentrant (parts of the provider are not, too).
|
|
|
|
Calling this is known to BREAK on some Windows Sockets implementations.
|
|
(LM 2.2c, Trumpet, any other which runs a background task). Exchange
|
|
will "do the right thing" on these
|
|
|
|
Arguments:
|
|
|
|
fYield -
|
|
TRUE - The task may yield to other tasks
|
|
while blocked. This allows task focus, close
|
|
and quit sendmessages to be sent to this
|
|
task in some cases.
|
|
|
|
FALSE - The task may NOT yield to otehr tasks
|
|
while blocked. This prevents any sendmessages
|
|
for being dispatched while blocked.
|
|
|
|
Return Value:
|
|
|
|
RPC_S_OUT_OF_MEMORY - Couldn't allocate a task data structure
|
|
|
|
RPC_S_CALL_IN_PROGRESS - This task already has an RPC call
|
|
in progress.
|
|
|
|
RPC_S_OK - Usually.
|
|
|
|
--*/
|
|
{
|
|
PYIELDINFO task_entry;
|
|
|
|
task_entry = FindYieldInfo(TRUE, NULL);
|
|
|
|
if (0 == task_entry)
|
|
{
|
|
return(RPC_S_OUT_OF_MEMORY);
|
|
}
|
|
|
|
if (task_entry->fActive)
|
|
{
|
|
return(RPC_S_CALL_IN_PROGRESS);
|
|
}
|
|
|
|
if (task_entry->tyClass == YIELD_EMPTY)
|
|
{
|
|
EVAL_AND_ASSERT(
|
|
WinDLLAtExit(WinYieldCleanup)
|
|
);
|
|
// Just allocated a task_entry for this task.
|
|
// They must not want to not yield at all.
|
|
task_entry->hTask = GetCurrentTask();
|
|
task_entry->tyClass = YIELD_NONE;
|
|
task_entry->fActive = FALSE;
|
|
}
|
|
|
|
task_entry->fTaskYield = fYield;
|
|
return(RPC_S_OK);
|
|
}
|
|
|
|
// ******************* TRANSPORT-CALLABLE FUNCTIONS *******************
|
|
HANDLE RPC_ENTRY
|
|
I_RpcWinAsyncCallBegin(
|
|
IN LPVOID lpContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the context for an asynchronous call. It
|
|
should be called by the transport immediately before the transport
|
|
makes an asynchronous call.
|
|
|
|
Arguments:
|
|
|
|
lpContext - A transport-supplied context pointer. This pointer is
|
|
opaque to RPC and is used only to determine at interrupt-time
|
|
which call has completed. The only requirements for this value
|
|
are 1) that it is unique and 2) that it is the same value as
|
|
the one passed to I_RpcWinAsyncCallComplete when the call
|
|
completes.
|
|
|
|
Return Value:
|
|
|
|
A handle to be passed to the subsequent I_RpcWinAsyncCallWait and
|
|
I_RpcWinAsyncCallEnd calls.
|
|
|
|
--*/
|
|
{
|
|
HTASK hTask = GetCurrentTask();
|
|
PYIELDINFO task_entry;
|
|
|
|
ASSERT(hTask != NULL);
|
|
|
|
task_entry = FindYieldInfo(FALSE, hTask);
|
|
|
|
ASSERT(task_entry && task_entry->lpContext == NULL);
|
|
// Fill in the fields for a registered task
|
|
|
|
task_entry->lpContext = lpContext;
|
|
task_entry->dwCallTime = GetCurrentTime();
|
|
task_entry->CompletionCount = 0;
|
|
task_entry->hDialog = 0;
|
|
|
|
task_entry->fActive = TRUE;
|
|
task_entry->fYielding = FALSE;
|
|
task_entry->fAborted = FALSE;
|
|
|
|
return (HANDLE) task_entry;
|
|
}
|
|
|
|
|
|
void __export CALLBACK
|
|
YieldTimerCallback(
|
|
HWND hwnd,
|
|
UINT msg,
|
|
UINT idTimer,
|
|
DWORD dwTime
|
|
)
|
|
{
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called for a class 3 application when the Windows timer goes off.
|
|
|
|
Arguments:
|
|
|
|
hwnd - the app's window
|
|
|
|
msg = WM_TIMER
|
|
|
|
idTimer - the HTASK of the class 3 application
|
|
|
|
dwTime - the moment when the timer went off (very imprecise)
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
PYIELDINFO task_entry;
|
|
|
|
task_entry = FindYieldInfo(FALSE, (HTASK) idTimer);
|
|
|
|
ASSERT(task_entry);
|
|
|
|
if (task_entry)
|
|
{
|
|
ASSERT(hwnd == task_entry->hWnd);
|
|
|
|
//
|
|
// This can fail, but if so the timer will go off again later.
|
|
// BUGBUG I can't think of a better recovery strategy.
|
|
//
|
|
PostMessage(task_entry->hWnd, task_entry->wMsg, 0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
int RPC_ENTRY
|
|
I_RpcWinAsyncCallWait(
|
|
IN HANDLE hCall,
|
|
IN HWND hDallyWnd,
|
|
IN unsigned long Timeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function waits for the asynchronous call to complete, yielding
|
|
as appropriate for the particular task. It should be called by the
|
|
transport after issuing the asynchronous call (unless there is an
|
|
immediate failure in this call).
|
|
|
|
Arguments:
|
|
|
|
hCall - The handle returned by the preceding call to
|
|
I_RpcWinAsyncCallBegin.
|
|
|
|
hDallyWnd - Optionally supplies a window to peek messages from; this is
|
|
necessary because some transports (winsockets in particular), need
|
|
yielding behavior inorder to run correctly.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a packet was received, FALSE if the call aborted
|
|
(normally due to user intervention). If this function returns FALSE,
|
|
the transport should cancel the pending operation and return an error
|
|
(e.g. RPC_P_SEND_FAILED) to the above RPC layer.
|
|
|
|
--*/
|
|
{
|
|
MSG wMsg;
|
|
PYIELDINFO task_entry;
|
|
int fCallComplete = RPC_WIN_WAIT_TIMEOUT;
|
|
unsigned Mask;
|
|
|
|
unsigned long StartTime = GetCurrentTime();
|
|
|
|
ASSERT(hCall);
|
|
|
|
task_entry = (PYIELDINFO) hCall;
|
|
|
|
ASSERT(task_entry == FindYieldInfo(FALSE, NULL));
|
|
|
|
//
|
|
// Yielding slows us down if the server responds quickly.
|
|
// Let's give the transport a little time to complete the receive
|
|
// before we begin yielding.
|
|
//
|
|
if (0 == task_entry->fYielding && task_entry->tyClass != YIELD_NONE)
|
|
{
|
|
unsigned long dwYieldTime;
|
|
|
|
dwYieldTime = task_entry->dwCallTime + task_entry->iTimeout;
|
|
if (!dwYieldTime)
|
|
{
|
|
dwYieldTime = 1;
|
|
}
|
|
|
|
if (hDallyWnd != 0)
|
|
{
|
|
Mask = PM_REMOVE;
|
|
if (task_entry->fTaskYield == FALSE)
|
|
{
|
|
Mask |= PM_NOYIELD;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (PeekMessage(&wMsg, hDallyWnd, 0, 0, Mask))
|
|
{
|
|
TranslateMessage(&wMsg);
|
|
DispatchMessage(&wMsg);
|
|
}
|
|
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
}
|
|
while (GetCurrentTime() < dwYieldTime &&
|
|
GetCurrentTime() - StartTime <= Timeout);
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
Yield();
|
|
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
}
|
|
while (GetCurrentTime() < dwYieldTime &&
|
|
GetCurrentTime() - StartTime <= Timeout);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Time to really yield.
|
|
//
|
|
task_entry->fYielding = TRUE;
|
|
|
|
switch (task_entry->tyClass)
|
|
{
|
|
case YIELD_NONE:
|
|
{
|
|
if (hDallyWnd != 0)
|
|
{
|
|
Mask = PM_REMOVE;
|
|
if (task_entry->fTaskYield == FALSE)
|
|
{
|
|
Mask |= PM_NOYIELD;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (PeekMessage(&wMsg, hDallyWnd, 0, 0, Mask))
|
|
{
|
|
TranslateMessage(&wMsg);
|
|
DispatchMessage(&wMsg);
|
|
}
|
|
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
}
|
|
while (GetCurrentTime() - StartTime <= Timeout);
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
}
|
|
while (GetCurrentTime() - StartTime <= Timeout);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case YIELD_DIALOG:
|
|
{
|
|
if (NULL == task_entry->hDialog)
|
|
{
|
|
//
|
|
// First-time yielding stuff. Note that Windows will
|
|
// free the dialogue box when the app terminates, so we don't
|
|
// need to worry about it.
|
|
//
|
|
if (task_entry->dwOtherInfo.hDialogTemplate)
|
|
{
|
|
void __far * pTemplate = GlobalLock(task_entry->dwOtherInfo.hDialogTemplate);
|
|
|
|
task_entry->hDialog =
|
|
CreateDialogIndirect(GetWindowWord(task_entry->hWnd,
|
|
GWW_HINSTANCE),
|
|
pTemplate,
|
|
task_entry->hWnd,
|
|
I_RpcYieldDialogFunction
|
|
);
|
|
}
|
|
else
|
|
{
|
|
task_entry->hDialog =
|
|
CreateDialog(hInstanceDLL,
|
|
DEFAULT_DIALOG_NAME,
|
|
task_entry->hWnd,
|
|
I_RpcYieldDialogFunction
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!task_entry->hDialog)
|
|
{
|
|
//
|
|
// Wimp out. The caller can call us again if he likes.
|
|
//
|
|
task_entry->fYielding = FALSE;
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (!IsWindowVisible(task_entry->hDialog))
|
|
{
|
|
ShowWindow(task_entry->hDialog, SW_SHOW);
|
|
|
|
if (task_entry->wMsg)
|
|
{
|
|
EVAL_AND_ASSERT(PostMessage(task_entry->hWnd,
|
|
task_entry->wMsg,
|
|
1,
|
|
0) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Run the dialogue box until the user aborts the call, someone
|
|
// sends us a packet, or we timeout.
|
|
//
|
|
Mask = PM_REMOVE;
|
|
if (task_entry->fTaskYield == FALSE)
|
|
{
|
|
Mask |= PM_NOYIELD;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (PeekMessage(&wMsg, hDallyWnd, 0, 0, Mask))
|
|
{
|
|
if (!IsDialogMessage(task_entry->hDialog, &wMsg))
|
|
{
|
|
TranslateMessage(&wMsg);
|
|
DispatchMessage(&wMsg);
|
|
}
|
|
}
|
|
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
else if (task_entry->fAborted)
|
|
{
|
|
return RPC_WIN_WAIT_ABORTED;
|
|
}
|
|
}
|
|
while (GetCurrentTime() - StartTime <= Timeout);
|
|
|
|
break;
|
|
}
|
|
|
|
case YIELD_CUSTOM:
|
|
{
|
|
if (RPC_WIN_INFINITE_TIMEOUT == Timeout)
|
|
{
|
|
//
|
|
// Wait forever.
|
|
//
|
|
fCallComplete = (*task_entry->dwOtherInfo.lpYieldFunction)();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Set up a Windows timer for <Timeout> milliseconds.
|
|
// For more details on SetTimer, see the KnowledgeBase article
|
|
// "Timers and Timing in Microsoft Windows" (Q81592), because
|
|
// the Windows SDK description is not all that accurate.
|
|
//
|
|
// If we can't create a timer, we busy-wait.
|
|
//
|
|
UINT hTimer;
|
|
|
|
if (Timeout > 0xFFFFUL)
|
|
{
|
|
Timeout = 0xFFFFU;
|
|
}
|
|
|
|
hTimer = SetTimer(task_entry->hWnd,
|
|
(UINT) GetCurrentTask(),
|
|
(UINT) Timeout,
|
|
YieldTimerCallback
|
|
);
|
|
if (hTimer)
|
|
{
|
|
fCallComplete = (*task_entry->dwOtherInfo.lpYieldFunction)();
|
|
|
|
EVAL_AND_ASSERT(0 != KillTimer(task_entry->hWnd, hTimer));
|
|
}
|
|
else
|
|
{
|
|
while (GetCurrentTime() - StartTime <= Timeout)
|
|
{
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// We should never get here
|
|
ASSERT(task_entry->tyClass == YIELD_CUSTOM);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (task_entry->CompletionCount)
|
|
{
|
|
SafeDecrement(&task_entry->CompletionCount);
|
|
return RPC_WIN_WAIT_SUCCESS;
|
|
}
|
|
#ifdef DEBUGRPC
|
|
OutputDebugString("AsyncCallWait: timeout");
|
|
#endif
|
|
return RPC_WIN_WAIT_TIMEOUT;
|
|
}
|
|
|
|
|
|
void RPC_ENTRY
|
|
I_RpcWinAsyncCallEnd(
|
|
IN HANDLE hCall)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function cleans up the context of the preceding RPC call. It
|
|
should be called by the transport after the call has completed (i.e.
|
|
normally after I_RpcWinAsyncCallWait). This function must be called
|
|
subsequent to a I_RpcWinAsyncCallBegin call, regardless of this
|
|
success or failure of the intervening asynchronous call.
|
|
|
|
Arguments:
|
|
|
|
hCall - The handle returned by the preceding call to
|
|
I_RpcWinAsyncCallBegin.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PYIELDINFO task_entry;
|
|
|
|
ASSERT(hCall);
|
|
|
|
task_entry = (PYIELDINFO) hCall;
|
|
|
|
ASSERT(task_entry == FindYieldInfo(FALSE, NULL));
|
|
|
|
if (YIELD_DIALOG == task_entry->tyClass)
|
|
{
|
|
// Post "end yield" message
|
|
if (task_entry->wMsg)
|
|
{
|
|
EVAL_AND_ASSERT( PostMessage(task_entry->hWnd,
|
|
task_entry->wMsg,
|
|
0,
|
|
0) );
|
|
}
|
|
|
|
if (task_entry->hDialog && task_entry->fYielding)
|
|
{
|
|
ShowWindow(task_entry->hDialog, SW_HIDE);
|
|
}
|
|
|
|
if (task_entry->dwOtherInfo.hDialogTemplate)
|
|
{
|
|
GlobalUnlock(task_entry->dwOtherInfo.hDialogTemplate);
|
|
}
|
|
}
|
|
|
|
task_entry->lpContext = NULL;
|
|
task_entry->fActive = FALSE;
|
|
}
|
|
|
|
|
|
unsigned RPC_ENTRY
|
|
I_RpcWinIsTaskYielding(
|
|
IN HANDLE hCall
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Used by the transport interface to determine if the
|
|
NoTaskYield bit is set for the current task. The
|
|
bit is set by an application call to RpcWinNoTaskYield().
|
|
|
|
Arguments:
|
|
|
|
hCall - The handle returned by the preceding call to
|
|
I_RpcWinAsyncCallBegin. Can be NULL.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PYIELDINFO task_entry;
|
|
|
|
task_entry = (PYIELDINFO)hCall;
|
|
|
|
if (task_entry == 0)
|
|
{
|
|
task_entry = FindYieldInfo(FALSE, NULL);
|
|
ASSERT(task_entry);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(task_entry == FindYieldInfo(FALSE, NULL));
|
|
}
|
|
|
|
return(task_entry->fTaskYield);
|
|
}
|
|
|
|
|
|
#pragma code_seg("RPC_FIXED", "FIXED_CODE")
|
|
|
|
void RPC_ENTRY
|
|
I_RpcWinAsyncCallComplete(
|
|
IN LPVOID lpContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function signals to the RPC run-time that a particular call
|
|
has completed and yielding can end. It should be called by the
|
|
transport at interrupt-time when the asynchronous operation has
|
|
completed.
|
|
|
|
Arguments:
|
|
|
|
lpContext - The opaque context pointer supplied in the preceding
|
|
call to I_RpcWinAsyncCallBegin.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PYIELDINFO task_entry = 0;
|
|
int i;
|
|
|
|
// Verify that the context pointer is not NULL
|
|
ASSERT(lpContext != NULL);
|
|
|
|
// See if the latest task is completing; this saves us a look-up
|
|
if (latest_task->lpContext == lpContext)
|
|
{
|
|
i = -1;
|
|
task_entry = latest_task;
|
|
}
|
|
else
|
|
{
|
|
// Search the array for the context pointer
|
|
for (i = 0, task_entry = yield_info_array;
|
|
i < num_yield_tasks;
|
|
i++, task_entry++)
|
|
{
|
|
if (task_entry->lpContext == lpContext)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (task_entry)
|
|
{
|
|
SafeIncrement(&task_entry->CompletionCount);
|
|
|
|
// Perform appropriate notification via PostMessage
|
|
switch(task_entry->tyClass)
|
|
{
|
|
case YIELD_DIALOG:
|
|
// Post "operation complete" message (i.e. WM_USER) to the
|
|
// dialog box (assuming that the dialog box handle has
|
|
// already been set)
|
|
if (task_entry->hDialog)
|
|
{
|
|
EVAL_AND_ASSERT( PostMessage(task_entry->hDialog,
|
|
WM_USER,
|
|
0,
|
|
0) );
|
|
}
|
|
break;
|
|
|
|
case YIELD_CUSTOM:
|
|
// Post "operation complete" message to the application if
|
|
// it's yielding
|
|
if (task_entry->fActive)
|
|
{
|
|
EVAL_AND_ASSERT( PostMessage(task_entry->hWnd,
|
|
task_entry->wMsg,
|
|
0,
|
|
0) );
|
|
}
|
|
break;
|
|
|
|
case YIELD_NONE:
|
|
// Nothing to cleanup.
|
|
break;
|
|
|
|
default:
|
|
// We should never get here
|
|
ASSERT(task_entry->tyClass == YIELD_CUSTOM);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
RPC_CLIENT_RUNTIME_INFO RpcClientRuntimeInfo =
|
|
{
|
|
RPC_WIN_CALLBACK_INFO_VERSION,
|
|
I_RpcTransClientReallocBuffer,
|
|
I_RpcWinAsyncCallBegin,
|
|
I_RpcWinAsyncCallWait,
|
|
I_RpcWinAsyncCallEnd,
|
|
I_RpcWinAsyncCallComplete,
|
|
I_RpcWinIsTaskYielding,
|
|
I_RpcAllocate,
|
|
I_RpcFree,
|
|
RpcRegOpenKey,
|
|
RpcRegCloseKey,
|
|
RpcRegQueryValue,
|
|
FALSE,
|
|
WinDLLAtExit
|
|
};
|
|
|
|
|