2025-11-28 00:35:46 +09:00

382 lines
11 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 2004 Microsoft Corporation. All Rights Reserved.
//
// Module Name: asyncselect.cpp
//
// Description:
// This file contains the routines necessary to implement the WSPAsyncSelect
// API. In order for our LSP to count the bytes sent and received by an
// application when the app uses WSAAsyncSelect we need to intercept all
// of the app's send and receive calls. To do this we implement a hidden
// window. When the app makes a send/recv call we intercept this and post
// the operation with the provider's socket to our own window. This allows
// the LSP to receive the completion notification. At this point we will
// update the statistics and then post the completion to the app's window
// so that it may continue as expected.
//
// Note that any LSP which uses WPUCreateSocketHandle to return socket handles
// to the upper layer must intercept all WSAAsyncSelect calls to use this
// hidden window method. This is required since the wParam parameter passed
// into the window's async handler is the socket handle on which the event
// occured. If the LSP doesn't capture this then the wrong socket handle
// will be passed directly to the upper layer's window handler.
//
// This file contains the I/O manager for all WSAAsyncselect I/O operations
//
#include "lspdef.h"
#include <windows.h>
#define STRSAFE_NO_DEPRECATE
#include <strsafe.h>
//
// We must register a window class with a unique name. To do so we'll
// use PROVIDER_CLASS string to format one which includes the current
// process ID. This string will be formatted into a buffer of
// PROVIDER_CLASS_CHAR_LEN length.
//
#define PROVIDER_CLASS TEXT("Layered WS2 Provider 0x%08x")
#define PROVIDER_CLASS_CHAR_LEN 32
//
// When handing window messages it is possible we'll get a message for a
// socket not in our list of created sockets. This can occur on an accepted
// socket since some messages may be posted to the accepted socket before
// the SOCK_INFO structure is inserted into the socket list in WSPAccept.
// So if we don't find a SOCK_INFO structure, we'll sleep and try again
// (up to MAX_ASYNC_RETRIES).
//
#define MAX_ASYNC_RETRIES 7
#define ASYNC_TIMEOUT 500 // half a second
#pragma warning(disable:4127) // Disable conditional expression is constant warning
#pragma warning(disable:4706) // Disable assignment within conditional warning (message pump)
//
// Function Prototypes
//
// Creates the hidden window and is the message pump for it
static DWORD WINAPI
AsyncMsgHandler(
LPVOID lpParameter
);
// Handler function for our Winsock FD_* events which posts completions to upper layer
static LRESULT CALLBACK
AsyncWndProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
//
// Globals to this file
//
static HANDLE WorkerThreadHandle = NULL; // Dispatch thread for handing window messages
static HWND AsyncWindow = NULL; // Handle to the hidden window
static TCHAR AsyncProviderClassName[ PROVIDER_CLASS_CHAR_LEN ];
//
// Function: StopAsyncWindowManager
//
// Description:
// This function cleans up the subsystem that handles asynchronous
// window IO (e.g. WSAAsyncSelect). Basically, it destroys the the
// hidden window if its been created. Note that this function is
// only called from WSPCleanup when the DLL is about to be unloaded,
// and we've already entered the critical section (gCriticalSection).
//
int
StopAsyncWindowManager(
)
{
int rc,
code;
if ( NULL != AsyncWindow )
{
// Post a quit message to the thread
PostMessage( AsyncWindow, WM_DESTROY, 0, 0 );
// Wait for the thread to cleanup and exit
rc = WaitForSingleObject( WorkerThreadHandle, 10000 );
if ( WAIT_TIMEOUT == rc )
{
dbgprint("StopAsyncWindowManager: Timed out waiting for async thread!");
goto cleanup;
}
// Retrieve the exit code and display a simple message
rc = GetExitCodeThread( WorkerThreadHandle, (LPDWORD) &code );
if ( 0 == rc )
dbgprint("StopAsyncWindowManager: Unable to retrieve thread exit code: %d",
GetLastError() );
else if ( 0 != code )
dbgprint("StopAsyncWindowManager: Async window thread exited abnormally!");
cleanup:
CloseHandle( WorkerThreadHandle );
WorkerThreadHandle = NULL;
}
return 0;
}
//
// Function: GetWorkerWindow
//
// Description:
// This returns a handle to our hidden window that acts as an
// intermediary between the apps window and winsock. If the window
// hasn't already been created, then create it.
//
HWND
GetWorkerWindow(
)
{
HANDLE ReadyEvent = NULL;
int rc;
EnterCriticalSection( &gCriticalSection );
if ( NULL == WorkerThreadHandle )
{
// Create an event which the worker thread will signal when its ready
ReadyEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
if ( NULL == ReadyEvent )
{
dbgprint("GetWorkerWindow: CreateEvent failed: %d", GetLastError() );
goto cleanup;
}
// Create the asyn window message thread
WorkerThreadHandle = CreateThread(
NULL,
0,
AsyncMsgHandler,
(LPVOID)ReadyEvent,
0,
NULL
);
if ( NULL == WorkerThreadHandle )
{
dbgprint( "GetWorkerWindow: CreateThread failed: %d", GetLastError() );
goto cleanup;
}
// Wait for the window to become initialized
rc = WaitForSingleObject( ReadyEvent, INFINITE );
if ( ( WAIT_FAILED == rc ) || ( WAIT_TIMEOUT == rc ) )
dbgprint( "GetWorkerWindow: WaitForSingleObject failed: %d! (error = %d)",
rc, GetLastError() );
}
cleanup:
if ( NULL != ReadyEvent )
{
// Close the ready event
rc = CloseHandle( ReadyEvent );
if ( 0 == rc )
{
dbgprint("GetWorkerWindow: CloseHandle failed: %d", GetLastError() );
}
}
LeaveCriticalSection( &gCriticalSection );
return AsyncWindow;
}
//
// Function: AsyncMsgHandler
//
// Description:
// This is the message pump for our hidden window.
//
static DWORD WINAPI
AsyncMsgHandler(
LPVOID lpParameter
)
{
MSG msg;
DWORD Ret;
HANDLE readyEvent;
HRESULT hr;
WNDCLASS wndclass;
// Event to signal when window is ready
readyEvent = (HANDLE) lpParameter;
hr = StringCchPrintf(
AsyncProviderClassName,
PROVIDER_CLASS_CHAR_LEN,
PROVIDER_CLASS,
GetCurrentProcessId()
);
if ( FAILED( hr ) )
{
dbgprint("AsyncMsgHandler: StringCchPrintf failed: %d", GetLastError());
goto cleanup;
}
dbgprint("AsyncMsgHandler: Class name is '%s'", AsyncProviderClassName );
memset( &wndclass, 0, sizeof( wndclass ) );
wndclass.lpfnWndProc = (WNDPROC)AsyncWndProc;
wndclass.hInstance = gDllInstance;
wndclass.lpszClassName = AsyncProviderClassName;
if ( 0 == RegisterClass( &wndclass ) )
{
dbgprint("AsyncMsgHandle: RegisterClass failed: %d", GetLastError());
goto cleanup;
}
// Create a window.
AsyncWindow = CreateWindow(
AsyncProviderClassName,
TEXT("Layered Hidden Window"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
gDllInstance,
NULL
);
if ( NULL == AsyncWindow )
{
dbgprint("AsyncMessageHandler: CreateWindow failed: %d", GetLastError() );
goto cleanup;
}
// Indicate the window is ready
SetEvent( readyEvent );
// Message pump
while ( ( Ret = GetMessage( &msg, NULL, 0, 0 ) ) )
{
if ( -1 == Ret )
{
dbgprint("AsyncMessageHandler: GetMessage returned -1, exiting loop");
break;
}
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Clean up the window and window class which were created in this thread
if ( NULL != AsyncWindow )
{
DestroyWindow( AsyncWindow );
AsyncWindow = NULL;
}
UnregisterClass( AsyncProviderClassName, gDllInstance );
ExitThread(0);
cleanup:
SetEvent( readyEvent );
ExitThread( (DWORD) -1);
}
//
// Function: AsyncWndProc
//
// Description:
// This is the window proc for our hidden window. Once we receive a message
// on our hidden window we must translate our lower provider's socket handle
// to the app's socket handle and then complete the notification to the user.
//
static LRESULT CALLBACK
AsyncWndProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
SOCK_INFO *si = NULL;
int retries,
rc;
if ( WM_SOCKET == uMsg )
{
retries = 0;
//
// Given the lower provider's socket handle (the wParam), do a reverse lookup
// to find the socket context structure. It is possible we received a message
// for a socket not in our list of sockets. This usually occurs after WSPAccept
// completes and we immediately get a message for the accepted socket but the
// LSP code to insert the new context object hasn't executed yet. If this is
// the case wait on an event until a new context object is added (which
// signals the event). Note we'll wait only MAX_ASYNC_RETRIES times before
// bailing.
//
while ( retries < MAX_ASYNC_RETRIES )
{
dbgprint("hWnd 0x%p, uMsg 0x%x, WPARAM 0x%p, LPARAM 0x%p (retries %d)",
hWnd, uMsg, wParam, lParam, retries);
// Find the context for this lower provider socket handle
si = GetCallerSocket( NULL, wParam );
if ( NULL == si )
{
dbgprint("Unable to find socket context for 0x%p", wParam);
// Wait on an event which is signaled when context is added
rc = WaitForSingleObject( gAddContextEvent, ASYNC_TIMEOUT );
if ( WAIT_FAILED == rc )
{
dbgprint("AsyncWndProc: WaitForSingleObject failed: %d",
GetLastError());
break;
}
else // timeout or success
{
// Reset the event if it was signaled
if ( WAIT_OBJECT_0 == rc )
ResetEvent( gAddContextEvent );
// If signaled, hopefully next lookup will succeed
retries++;
continue;
}
}
gMainUpCallTable.lpWPUPostMessage(
si->hWnd,
si->uMsg,
si->LayeredSocket,
lParam
);
return 0;
}
}
else if ( WM_DESTROY == uMsg )
{
// Post a quit message to exit our async message pump thread
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}