/*++ Copyright (c) 1990-1996 Microsoft Corporation All rights reserved Module Name: kmspool.c Abstract: Spooler API entry points for Kernel Clients. Author: Steve Wilson (NT) (swilson) 1-Jun-95 (Ported from client\winspool.c) Environment: User Mode -Win32 Revision History: Matthew Felton (mattfe) May-96 Driver Hooks --*/ #include #include #include #include #include #include #include #include "winspl.h" #include #include "server.h" #include "client.h" #include #include #include "yspool.h" #include "kmspool.h" #include "splr.h" extern LPWSTR InterfaceAddress; // // Globals // #define ENTER_WAIT_LIST() EnterCriticalSection(&ThreadCriticalSection) #define EXIT_WAIT_LIST() LeaveCriticalSection(&ThreadCriticalSection) LPWSTR szEnvironment = LOCAL_ENVIRONMENT; // // Printer Attributes // #define SPLPRINTER_USER_MODE_PRINTER_DRIVER TEXT("SPLUserModePrinterDriver") BOOL ValidatePrinterHandle( HANDLE hPrinter ); BOOL DriverEndPageHook( PSPOOL pSpool ); BOOL DriverStartPageHook( PSPOOL pSpool ); BOOL DriverWritePrinterHook( PSPOOL pSpool, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten ); VOID DriverEndDocHook( PSPOOL pSpool ); DWORD DriverStartDocHook( PSPOOL pSpool, DWORD JobId ); VOID DriverClosePrinterHook( PSPOOL pSpool ); VOID DriverAbortPrinterHook( PSPOOL pSpool ); // Simple for Now !!! DWORD TranslateExceptionCode( DWORD ExceptionCode ) { switch (ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_DATATYPE_MISALIGNMENT: case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: case EXCEPTION_FLT_DENORMAL_OPERAND: case EXCEPTION_FLT_DIVIDE_BY_ZERO: case EXCEPTION_FLT_INEXACT_RESULT: case EXCEPTION_FLT_INVALID_OPERATION: case EXCEPTION_FLT_OVERFLOW: case EXCEPTION_FLT_STACK_CHECK: case EXCEPTION_FLT_UNDERFLOW: case EXCEPTION_INT_DIVIDE_BY_ZERO: case EXCEPTION_INT_OVERFLOW: case EXCEPTION_PRIV_INSTRUCTION: case ERROR_NOACCESS: case RPC_S_INVALID_BOUND: return ERROR_INVALID_PARAMETER; break; default: return ExceptionCode; } } BOOL KMOpenPrinterW( LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTS pDefault ) { BOOL ReturnValue; DEVMODE_CONTAINER DevModeContainer; HANDLE hPrinter; PSPOOL pSpool; DWORD dwSize = 0; if (pDefault && pDefault->pDevMode) { dwSize = pDefault->pDevMode->dmSize + pDefault->pDevMode->dmDriverExtra; if (dwSize) { DevModeContainer.cbBuf = pDefault->pDevMode->dmSize + pDefault->pDevMode->dmDriverExtra; DevModeContainer.pDevMode = (LPBYTE)pDefault->pDevMode; } else { DevModeContainer.cbBuf = 0; DevModeContainer.pDevMode = NULL; } } else { DevModeContainer.cbBuf = 0; DevModeContainer.pDevMode = NULL; } try { if (ReturnValue = YOpenPrinter(pPrinterName, &hPrinter, pDefault ? pDefault->pDatatype : NULL, &DevModeContainer, pDefault ? pDefault->DesiredAccess : 0, NATIVE_CALL )) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = TRUE; } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } if (ReturnValue) { pSpool = AllocSplMem(sizeof(SPOOL)); if (pSpool) { memset(pSpool, 0, sizeof(SPOOL)); pSpool->signature = SP_SIGNATURE; pSpool->hPrinter = hPrinter; pSpool->cThreads = -1; // // This is to fix passing a bad pHandle to OpenPrinter!! // try { *phPrinter = pSpool; } except(1) { YClosePrinter(&hPrinter, NATIVE_CALL); FreeSplMem(pSpool); SetLastError(TranslateExceptionCode(GetExceptionCode())); return FALSE; } } else { YClosePrinter(&hPrinter, NATIVE_CALL); ReturnValue = FALSE; } } return ReturnValue; } BOOL KMGetFormW( HANDLE hPrinter, LPWSTR pFormName, DWORD Level, LPBYTE pForm, DWORD cbBuf, LPDWORD pcbNeeded ) { BOOL ReturnValue; DWORD *pOffsets; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } switch (Level) { case 1: pOffsets = FormInfo1Offsets; break; default: SetLastError(ERROR_INVALID_LEVEL); return FALSE; } try { if (pForm) memset(pForm, 0, cbBuf); if (ReturnValue = YGetForm(pSpool->hPrinter, pFormName, Level, pForm, cbBuf, pcbNeeded, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else { ReturnValue = TRUE; } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL KMEnumFormsW( HANDLE hPrinter, DWORD Level, LPBYTE pForm, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned ) { BOOL ReturnValue; DWORD cbStruct; DWORD *pOffsets; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } switch (Level) { case 1: pOffsets = FormInfo1Offsets; cbStruct = sizeof(FORM_INFO_1); break; default: SetLastError(ERROR_INVALID_LEVEL); return FALSE; } try { if (pForm) memset(pForm, 0, cbBuf); if (ReturnValue = YEnumForms(pSpool->hPrinter, Level, pForm, cbBuf, pcbNeeded, pcReturned, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else { ReturnValue = TRUE; } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL KMGetPrinterW( HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded ) { BOOL ReturnValue; DWORD *pOffsets; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } switch (Level) { case 1: pOffsets = PrinterInfo1Offsets; break; case 2: pOffsets = PrinterInfo2Offsets; break; case 3: pOffsets = PrinterInfo3Offsets; break; case 4: pOffsets = PrinterInfo4Offsets; break; case 5: pOffsets = PrinterInfo5Offsets; break; default: SetLastError(ERROR_INVALID_LEVEL); return FALSE; } if (pPrinter) memset(pPrinter, 0, cbBuf); try { if (ReturnValue = YGetPrinter(pSpool->hPrinter, Level, pPrinter, cbBuf, pcbNeeded, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else { ReturnValue = TRUE; } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL KMGetPrinterDriverW( HANDLE hPrinter, LPWSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded ) { BOOL ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; DWORD dwServerMajorVersion; DWORD dwServerMinorVersion; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } if (Level < 1 || Level > 3) { SetLastError(ERROR_INVALID_LEVEL); return FALSE; } try { if (pDriverInfo) memset(pDriverInfo, 0, cbBuf); if (!pEnvironment || !*pEnvironment) pEnvironment = szEnvironment; if (ReturnValue = YGetPrinterDriver2(pSpool->hPrinter, pEnvironment, Level, pDriverInfo, cbBuf, pcbNeeded, (DWORD)-1, (DWORD)-1, &dwServerMajorVersion, &dwServerMinorVersion, NATIVE_CALL )) { SetLastError(ReturnValue); ReturnValue = FALSE; } else { ReturnValue = TRUE; } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } DWORD KMGetPrinterDataW( HANDLE hPrinter, LPWSTR pValueName, LPDWORD pType, LPBYTE pData, DWORD nSize, LPDWORD pcbNeeded ) { DWORD ReturnValue = 0; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return ERROR_INVALID_HANDLE; } // // The user should be able to pass in NULL for buffer, and // 0 for size. However, the RPC interface specifies a ref pointer, // so we must pass in a valid pointer. Pass in a pointer to // ReturnValue (this is just a dummy pointer). // if( !pData && !nSize ){ pData = (PBYTE)&ReturnValue; } try { ReturnValue = YGetPrinterData(pSpool->hPrinter, pValueName, pType, pData, nSize, pcbNeeded, NATIVE_CALL); } except(1) { ReturnValue = TranslateExceptionCode(GetExceptionCode()); } return ReturnValue; } DWORD KMSetPrinterDataW( HANDLE hPrinter, LPWSTR pValueName, DWORD Type, LPBYTE pData, DWORD cbData ) { DWORD ReturnValue = 0; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return ERROR_INVALID_HANDLE; } try { ReturnValue = YSetPrinterData(pSpool->hPrinter, pValueName, Type, pData, cbData, NATIVE_CALL); } except(1) { ReturnValue = TranslateExceptionCode(GetExceptionCode()); } return ReturnValue; } DWORD KMStartDocPrinterW( HANDLE hPrinter, DWORD Level, LPBYTE pDocInfo ) { BOOL ReturnValue; DWORD JobId; GENERIC_CONTAINER DocInfoContainer; PSPOOL pSpool = (PSPOOL)hPrinter; try { if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } DBGMSG(DBG_TRACE,("Entered KMStartDocPrinterW side hPrinter = %x\n", hPrinter)); if (Level != 1) { SetLastError(ERROR_INVALID_LEVEL); return FALSE; } DocInfoContainer.Level = Level; DocInfoContainer.pData = pDocInfo; try { if (ReturnValue = YStartDocPrinter(pSpool->hPrinter, (LPDOC_INFO_CONTAINER)&DocInfoContainer, &JobId, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = JobId; } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } if (ReturnValue) { ReturnValue = DriverStartDocHook( pSpool, JobId ); if ( ReturnValue ) pSpool->Status |= SPOOL_STATUS_STARTDOC; } return ReturnValue; } except (1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); return(FALSE); } } BOOL KMEndDocPrinter( HANDLE hPrinter ) { BOOL ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; try { if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } pSpool->Status &= ~SPOOL_STATUS_STARTDOC; DriverEndDocHook( pSpool ); try { if (ReturnValue = YEndDocPrinter(pSpool->hPrinter, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = TRUE; } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } DBGMSG(DBG_TRACE, ("Exit EndDocPrinter - client side hPrinter %x\n", hPrinter)); return ReturnValue; } except (1) { SetLastError(ERROR_INVALID_HANDLE); return(FALSE); } } BOOL KMWritePrinter( HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten ) { BOOL ReturnValue = TRUE; PSPOOL pSpool = (PSPOOL)hPrinter; DBGMSG(DBG_TRACE, ("WritePrinter - hPrinter %x pBuf %x cbBuf %d pcWritten %x\n", hPrinter, pBuf, cbBuf, pcWritten)); if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } *pcWritten = 0; if ( !(pSpool->Status & SPOOL_STATUS_STARTDOC) ) { SetLastError(ERROR_SPL_NO_STARTDOC); return FALSE; } // // Call Printer Drivers User Mode WritePrinter Hook // if ( pSpool->hDriver ) { return DriverWritePrinterHook( pSpool, pBuf, cbBuf, pcWritten ); } try { if (ReturnValue = YWritePrinter(pSpool->hPrinter, (LPBYTE) pBuf, cbBuf, pcWritten, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; DBGMSG(DBG_WARNING, ("KMWritePrinter - YWritePrinter Failed Error %d\n",GetLastError() )); } else { ReturnValue = TRUE; DBGMSG(DBG_TRACE, ("KMWritePrinter - YWritePrinter Success hPrinter %x pBuffer %x cbBuffer %x cbWritten %x\n", pSpool->hPrinter, (LPBYTE) pBuf, cbBuf, *pcWritten)); } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; DBGMSG(DBG_WARNING, ("YWritePrinter Exception Error %d\n",GetLastError())); } // Return the number of bytes written. DBGMSG(DBG_TRACE, ("KMWritePrinter cbWritten %d ReturnValue %d\n",*pcWritten, ReturnValue)); return ReturnValue; } BOOL KMStartPagePrinter( HANDLE hPrinter ) { BOOL ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } try { ReturnValue = DriverStartPageHook( pSpool ); if ( ReturnValue ) { if (ReturnValue = YStartPagePrinter(pSpool->hPrinter, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = TRUE; } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL KMEndPagePrinter( HANDLE hPrinter ) { BOOL ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } try { ReturnValue = DriverEndPageHook( pSpool ); if ( ReturnValue ) { if (ReturnValue = YEndPagePrinter(pSpool->hPrinter, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = TRUE; } } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL KMAbortPrinter( HANDLE hPrinter ) { BOOL ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; DWORD dwNumWritten = 0; DWORD dwPointer = 0; if (!ValidatePrinterHandle(hPrinter)){ return(FALSE); } pSpool->Status &= ~SPOOL_STATUS_STARTDOC; try { DriverAbortPrinterHook( pSpool ); if (ReturnValue = YAbortPrinter(pSpool->hPrinter, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = TRUE; } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL KMClosePrinter( HANDLE hPrinter) { BOOL ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; HANDLE hPrinterKM; if (!ValidatePrinterHandle(hPrinter)) { return(FALSE); } try { DriverClosePrinterHook( pSpool ); } except(1) { DBGMSG(DBG_WARNING, ("DrvClosePrinter Exception Error %d\n",TranslateExceptionCode(GetExceptionCode()))); } ENTER_WAIT_LIST(); hPrinterKM = pSpool->hPrinter; FreeSplMem(pSpool); EXIT_WAIT_LIST(); try { if (ReturnValue = YClosePrinter(&hPrinterKM, NATIVE_CALL)) { SetLastError(ReturnValue); ReturnValue = FALSE; } else ReturnValue = TRUE; } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; } return ReturnValue; } BOOL ValidatePrinterHandle( HANDLE hPrinter ) { PSPOOL pSpool = hPrinter; BOOL bReturnValue = FALSE; try { if ( pSpool && (pSpool->signature == SP_SIGNATURE)) { bReturnValue = TRUE; } } except (1) { } if ( !bReturnValue ) { SetLastError( ERROR_INVALID_HANDLE ); } return bReturnValue; } BOOL DriverWritePrinterHook( PSPOOL pSpool, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten ) { BOOL ReturnValue; // Some Printer Drivers want to push functionality out of kernel mode // to achieve that we allow them to hook the calls to writeprinter from // their Kernel Mode DLL to their User Mode Dll SPLASSERT( pSpool->hModule && pSpool->pfnWrite && pSpool->hDriver && pSpool->hPrinter ); try { ReturnValue = (*pSpool->pfnWrite)( pSpool->hDriver, pBuf, cbBuf, pcWritten ); } except(1) { SetLastError(TranslateExceptionCode(GetExceptionCode())); ReturnValue = FALSE; DBGMSG(DBG_ERROR, ("DrvWritePrinter Exception Error %d pSpool %x\n",GetLastError(), pSpool)); } if ( !ReturnValue ) { SPLASSERT( GetLastError() ); } return ReturnValue; } HANDLE LoadPrinterDriver( PSPOOL pSpool, PWCHAR pUserModeDriverName ) { PDRIVER_INFO_2 pDriverInfo; DWORD cbNeeded; HANDLE hModule = NULL; PWCHAR pFileName; fnWinSpoolDrv fnList; if (!GetPrinterDriver(pSpool->hPrinter, NULL, 2, NULL, 0, &cbNeeded)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { // Allow for the size of the string passed in. cbNeeded += ( wcslen( pUserModeDriverName ) + 1 )* sizeof(WCHAR); if (pDriverInfo = (PDRIVER_INFO_2)AllocSplMem( cbNeeded )) { if (GetPrinterDriver(pSpool->hPrinter, NULL, 2, (LPBYTE)pDriverInfo, cbNeeded, &cbNeeded)) { // // Driver Info 2 doesn't have the fully Qualified Path // to the UserModePrinterDriver. // So form it by taking replacing the UI DLL name with the // UseModePrinterDriverName pFileName = wcsrchr( pDriverInfo->pConfigFile, L'\\'); pFileName++; wcscpy( pFileName, pUserModeDriverName ); pFileName = pDriverInfo->pConfigFile; DBGMSG( DBG_WARNING, ("UserModeDriverPath %ws\n", pFileName )); if (SplInitializeWinSpoolDrv(&fnList)) { hModule = (* (fnList.pfnRefCntLoadDriver))(pFileName, LOAD_WITH_ALTERED_SEARCH_PATH, 0, FALSE); } if ( !hModule ) { DBGMSG( DBG_WARNING, ("Failed to load %ws error %d\n", pFileName, GetLastError() )); } } FreeSplMem(pDriverInfo); } } } return hModule; } VOID UnloadPrinterDriver( PSPOOL pSpool ) { fnWinSpoolDrv fnList; if ( pSpool->hModule ) { SPLASSERT( pSpool->hDriver == NULL ); if (SplInitializeWinSpoolDrv(&fnList)) { (* (fnList.pfnRefCntUnloadDriver))(pSpool->hModule, TRUE); } pSpool->hModule = NULL; pSpool->pfnWrite = NULL; pSpool->pfnStartDoc = NULL; pSpool->pfnEndDoc = NULL; pSpool->pfnClose = NULL; pSpool->pfnStartPage = NULL; pSpool->pfnEndPage = NULL; } } DWORD DriverStartDocHook( PSPOOL pSpool, DWORD JobId ) { DWORD dwReturn; WCHAR UserModeDriverName[MAX_PATH]; DWORD dwNeeded; INT cDriverName; BOOL ReturnValue = FALSE; DWORD Type = 0; // // Determine if there is a UserMode Printer Driver // dwReturn = GetPrinterDataW( pSpool->hPrinter, SPLPRINTER_USER_MODE_PRINTER_DRIVER, &Type, (LPBYTE)&UserModeDriverName, MAX_PATH, &dwNeeded ); if ( dwReturn != ERROR_SUCCESS ) { SPLASSERT( dwReturn != ERROR_INSUFFICIENT_BUFFER ); ReturnValue = TRUE; goto Complete; } if ( Type != REG_SZ ) { SPLASSERT( Type == REG_SZ ); goto Complete; } // No String treat as success cDriverName = wcslen( UserModeDriverName ); if ( !cDriverName ) { ReturnValue = TRUE; goto Complete; } // // Load the UM Driver DLL // if ( pSpool->hModule == NULL ) { pSpool->hModule = LoadPrinterDriver( pSpool, UserModeDriverName ); if ( pSpool->hModule == NULL ) goto Complete; } // // Get Function Pointers // // Required // pSpool->pfnWrite = (DWORD (*)()) GetProcAddress( pSpool->hModule, "DrvSplWritePrinter" ); pSpool->pfnStartDoc = (HANDLE (*)()) GetProcAddress( pSpool->hModule, "DrvSplStartDoc" ); pSpool->pfnClose = (VOID (*)()) GetProcAddress( pSpool->hModule, "DrvSplClose" ); pSpool->pfnEndDoc = (VOID (*)()) GetProcAddress( pSpool->hModule, "DrvSplEndDoc" ); // Optional // pSpool->pfnEndPage = (BOOL (*)()) GetProcAddress( pSpool->hModule, "DrvSplEndPage" ); pSpool->pfnStartPage = (BOOL (*)()) GetProcAddress( pSpool->hModule, "DrvSplStartPage" ); pSpool->pfnAbort = (VOID (*)()) GetProcAddress( pSpool->hModule, "DrvSplAbort" ); if (!( pSpool->pfnWrite) || !( pSpool->pfnStartDoc) || !( pSpool->pfnClose) || !( pSpool->pfnEndDoc)) { goto Complete; } // // Ask the Driver for a Handle for this print job // SPLASSERT( pSpool->hDriver == NULL ); SPLASSERT( pSpool->hPrinter ); SPLASSERT( JobId ); pSpool->hDriver = (HANDLE)(*pSpool->pfnStartDoc)( pSpool->hPrinter, JobId ); if ( pSpool->hDriver != NULL ) { ReturnValue = TRUE; } Complete: if (!ReturnValue) { UnloadPrinterDriver( pSpool ); // Cancel the outstanding job // // In the direct case // AbortPrinter doesn't work // SetJob _CANCEL doesn't work // EndDocPrinter does work EndDocPrinter( pSpool->hPrinter ); JobId = 0; } pSpool->JobId = JobId; return JobId; } VOID DriverEndDocHook( PSPOOL pSpool ) { if ( pSpool->hDriver ) { (*pSpool->pfnEndDoc)( pSpool->hDriver ); (*pSpool->pfnClose)(pSpool->hDriver ); pSpool->hDriver = NULL; } } BOOL DriverStartPageHook( PSPOOL pSpool ) { if ( pSpool->hDriver && pSpool->pfnStartPage ){ return (*pSpool->pfnStartPage)( pSpool->hDriver ); } else { return TRUE; } } BOOL DriverEndPageHook( PSPOOL pSpool ) { if ( pSpool->hDriver && pSpool->pfnEndPage ){ return (*pSpool->pfnEndPage)( pSpool->hDriver ); } else { return TRUE; } } VOID DriverAbortPrinterHook( PSPOOL pSpool ) { if ( pSpool->hDriver && pSpool->pfnAbort ) (*pSpool->pfnAbort)( pSpool->hDriver ); } VOID DriverClosePrinterHook( PSPOOL pSpool ) { if ( pSpool->hDriver ) { SPLASSERT( pSpool->pfnClose ); (*pSpool->pfnClose)(pSpool->hDriver); pSpool->hDriver = NULL; } UnloadPrinterDriver( pSpool ); }