windows-nt/Source/XPSP1/NT/printscan/print/spooler/spoolss/dll/spooler.c
2020-09-26 16:20:57 +08:00

873 lines
22 KiB
C

/*++
Copyright (c) 1990-1994 Microsoft Corporation
All rights reserved
Module Name:
spooler.c
Abstract:
Author:
Environment:
User Mode -Win32
Revision History:
--*/
#include "precomp.h"
#pragma hdrstop
#include "winsprlp.h"
//
// RPC Buffer size 64K
//
#define BUFFER_SIZE 0x10000
DWORD
StartDocPrinterW(
HANDLE hPrinter,
DWORD Level,
LPBYTE pDocInfo)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpStartDocPrinter)
(pPrintHandle->hPrinter,
Level, pDocInfo);
}
BOOL
StartPagePrinter(
HANDLE hPrinter
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpStartPagePrinter)
(pPrintHandle->hPrinter);
}
BOOL
SplCommitSpoolData(
HANDLE hPrinter,
HANDLE hAppProcess,
DWORD cbCommit,
DWORD dwLevel,
LPBYTE pSpoolFileInfo,
DWORD cbBuf,
LPDWORD pcbNeeded
)
/*++
Function Description: Commits data written into the spool file. creates a new temp
file handle for remote printing.
Parameters: hPrinter - printer handle
hAppProcess - application process handle
cbCommit - number of bytes to commit (incremental)
dwLevel - spoolfileinfo level
pSpoolFileInfo - pointer to buffer
cbBuf - buffer size
pcbNeeded - pointer to return required buffer size
Return Values: TRUE if sucessful;
FALSE otherwise
--*/
{
BOOL bReturn = FALSE;
DWORD cbTotalWritten, cbWritten, cbRead, cbToRead;
BYTE *Buffer = NULL;
HANDLE hFile, hSpoolerProcess = NULL, hFileApp = INVALID_HANDLE_VALUE;
PSPOOL_FILE_INFO_1 pSpoolFileInfo1;
LPPRINTHANDLE pPrintHandle = (LPPRINTHANDLE)hPrinter;
// Check Handle validity
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return bReturn;
}
// Check for valid level and sufficient buffer
switch (dwLevel) {
case 1:
if (cbBuf < sizeof(SPOOL_FILE_INFO_1)) {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
*pcbNeeded = sizeof(SPOOL_FILE_INFO_1);
goto CleanUp;
}
pSpoolFileInfo1 = (PSPOOL_FILE_INFO_1)pSpoolFileInfo;
break;
default:
SetLastError(ERROR_INVALID_LEVEL);
goto CleanUp;
}
// Initialize spoolfileinfo1 struct
pSpoolFileInfo1->dwVersion = 1;
pSpoolFileInfo1->hSpoolFile = INVALID_HANDLE_VALUE;
pSpoolFileInfo1->dwAttributes = SPOOL_FILE_PERSISTENT;
if (pPrintHandle->pProvidor == pLocalProvidor) {
bReturn = (pLocalProvidor->PrintProvidor.fpCommitSpoolData)(pPrintHandle->hPrinter,
cbCommit);
return bReturn;
}
// For remote printing send the temp file across the wire using WritePrinter
if (pPrintHandle->hFileSpooler == INVALID_HANDLE_VALUE) {
SetLastError(ERROR_INVALID_HANDLE);
return bReturn;
}
hFile = pPrintHandle->hFileSpooler;
if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == 0xffffffff) {
goto CleanUp;
}
//
// Use a Buffer to send Data over RPC.
//
Buffer = AllocSplMem(BUFFER_SIZE);
if ( !Buffer ) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto CleanUp;
}
while ((cbToRead = min(cbCommit, BUFFER_SIZE)) &&
ReadFile(hFile, Buffer, cbToRead, &cbRead, NULL)) {
cbCommit -= cbRead;
for (cbTotalWritten = 0;
cbTotalWritten < cbRead;
cbTotalWritten += cbWritten) {
if (!(*pPrintHandle->pProvidor->PrintProvidor.fpWritePrinter)
(pPrintHandle->hPrinter,
(LPBYTE)Buffer + cbTotalWritten,
cbRead - cbTotalWritten,
&cbWritten)) {
goto CleanUp;
}
}
}
if (Buffer) {
FreeSplMem(Buffer);
Buffer = NULL;
}
if ((cbToRead != 0) ||
(SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == 0xffffffff)) {
goto CleanUp;
}
if ((hSpoolerProcess = GetCurrentProcess()) &&
DuplicateHandle(hSpoolerProcess,
pPrintHandle->hFileSpooler,
hAppProcess,
&hFileApp,
0,
TRUE,
DUPLICATE_SAME_ACCESS)) {
pSpoolFileInfo1->dwVersion = 1;
pSpoolFileInfo1->hSpoolFile = hFileApp;
pSpoolFileInfo1->dwAttributes = SPOOL_FILE_TEMPORARY;
bReturn = TRUE;
}
CleanUp:
if (Buffer) {
FreeSplMem(Buffer);
}
if (hSpoolerProcess) {
CloseHandle(hSpoolerProcess);
}
return bReturn;
}
BOOL
SplCloseSpoolFileHandle(
HANDLE hPrinter
)
/*++
Function Description: Closes the remote spool file handle for remote printing.
Parameters: hPrinter - printer handle
Return Values: TRUE if sucessful;
FALSE otherwise
--*/
{
LPPRINTHANDLE pPrintHandle = (LPPRINTHANDLE)hPrinter;
// Check Handle validity
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
if (pPrintHandle->pProvidor == pLocalProvidor) {
return (pLocalProvidor->PrintProvidor.fpCloseSpoolFileHandle)(pPrintHandle->hPrinter);
} else if ((pPrintHandle->hFileSpooler != INVALID_HANDLE_VALUE)) {
// close temp files for remote printing
CloseHandle(pPrintHandle->hFileSpooler);
pPrintHandle->hFileSpooler = INVALID_HANDLE_VALUE;
if (pPrintHandle->szTempSpoolFile) {
HANDLE hToken = RevertToPrinterSelf();
if (!DeleteFile(pPrintHandle->szTempSpoolFile)) {
MoveFileEx(pPrintHandle->szTempSpoolFile, NULL,
MOVEFILE_DELAY_UNTIL_REBOOT);
}
if (hToken)
{
ImpersonatePrinterClient(hToken);
}
FreeSplMem(pPrintHandle->szTempSpoolFile);
pPrintHandle->szTempSpoolFile = NULL;
}
}
return TRUE;
}
BOOL
SplGetSpoolFileInfo(
HANDLE hPrinter,
HANDLE hAppProcess,
DWORD dwLevel,
LPBYTE pSpoolFileInfo,
DWORD cbBuf,
LPDWORD pcbNeeded
)
/*++
Function Description: Get spool file info for the job in hPrinter. For local jobs
localspl returns the hFile. For remote jobs a temp file is created
by the router. The file handle is dupped into the application.
Parameters: hPrinter - printer handle
hAppProcess - application process handle
dwLevel - spool file info level
pSpoolFileInfo - pointer to buffer
cbBuf - buffer size
pcbNeeded - pointer to return required buffer size
Return Values: TRUE if sucessful;
FALSE otherwise
--*/
{
HANDLE hFileSpooler = NULL, hFileApp = NULL;
HANDLE hSpoolerProcess = NULL;
BOOL bReturn = FALSE;
DWORD dwSpoolerProcessID;
LPWSTR pSpoolDir = NULL;
PSPOOL_FILE_INFO_1 pSpoolFileInfo1;
LPPRINTHANDLE pPrintHandle = (LPPRINTHANDLE)hPrinter;
// Check Handle validity
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
goto CleanUp;
}
// Check for valid level and sufficient buffer
switch (dwLevel) {
case 1:
if (cbBuf < sizeof(SPOOL_FILE_INFO_1)) {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
*pcbNeeded = sizeof(SPOOL_FILE_INFO_1);
goto CleanUp;
}
pSpoolFileInfo1 = (PSPOOL_FILE_INFO_1)pSpoolFileInfo;
break;
default:
SetLastError(ERROR_INVALID_LEVEL);
goto CleanUp;
}
if (!(hSpoolerProcess = GetCurrentProcess())) {
// Cant get a pseudo handle to the spooler
goto CleanUp;
}
if ((pPrintHandle->pProvidor != pLocalProvidor) &&
(pPrintHandle->hFileSpooler != INVALID_HANDLE_VALUE)) {
// Return cached temp file handle.
bReturn = DuplicateHandle(hSpoolerProcess,
pPrintHandle->hFileSpooler,
hAppProcess,
&hFileApp,
0,
TRUE,
DUPLICATE_SAME_ACCESS);
if (bReturn) {
pSpoolFileInfo1->dwVersion = 1;
pSpoolFileInfo1->hSpoolFile = hFileApp;
pSpoolFileInfo1->dwAttributes = SPOOL_FILE_TEMPORARY;
}
goto CleanUp;
}
if (pPrintHandle->pProvidor == pLocalProvidor) {
bReturn = (pLocalProvidor->PrintProvidor.fpGetSpoolFileInfo)(pPrintHandle->hPrinter,
NULL,
&hFileApp,
hSpoolerProcess,
hAppProcess);
if (bReturn) {
pSpoolFileInfo1->dwVersion = 1;
pSpoolFileInfo1->hSpoolFile = hFileApp;
pSpoolFileInfo1->dwAttributes = SPOOL_FILE_PERSISTENT;
}
goto CleanUp;
} else {
bReturn = (pLocalProvidor->PrintProvidor.fpGetSpoolFileInfo)(NULL, &pSpoolDir,
NULL, NULL, NULL);
}
// Remote Printing, create a temp file in the spool directory
if (bReturn) {
HANDLE hToken;
//
// Revert to system context to ensure that we can open the file.
//
hToken = RevertToPrinterSelf();
if ((pPrintHandle->szTempSpoolFile = AllocSplMem(MAX_PATH * sizeof(WCHAR))) &&
GetTempFileName(pSpoolDir, L"SPL", 0, pPrintHandle->szTempSpoolFile) &&
((pPrintHandle->hFileSpooler = CreateFile(pPrintHandle->szTempSpoolFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
0, NULL)) != INVALID_HANDLE_VALUE) &&
DuplicateHandle(hSpoolerProcess,
pPrintHandle->hFileSpooler,
hAppProcess,
&hFileApp,
0,
TRUE,
DUPLICATE_SAME_ACCESS)) {
pSpoolFileInfo1->dwVersion = 1;
pSpoolFileInfo1->hSpoolFile = hFileApp;
pSpoolFileInfo1->dwAttributes = SPOOL_FILE_TEMPORARY;
} else {
bReturn = FALSE;
}
if (hToken)
{
ImpersonatePrinterClient(hToken);
}
}
CleanUp:
if (hSpoolerProcess) {
CloseHandle(hSpoolerProcess);
}
if (pSpoolDir) {
FreeSplMem(pSpoolDir);
}
return bReturn;
}
BOOL
WritePrinter(
HANDLE hPrinter,
LPVOID pBuf,
DWORD cbBuf,
LPDWORD pcWritten
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle ||
(pPrintHandle->signature != PRINTHANDLE_SIGNATURE) ||
(pPrintHandle->hFileSpooler != INVALID_HANDLE_VALUE)) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpWritePrinter) (pPrintHandle->hPrinter,
pBuf, cbBuf, pcWritten);
}
BOOL
SeekPrinter(
HANDLE hPrinter,
LARGE_INTEGER liDistanceToMove,
PLARGE_INTEGER pliNewPointer,
DWORD dwMoveMethod,
BOOL bWritePrinter
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
LARGE_INTEGER liNewPointer;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
//
// Allow a NULL pliNewPointer to be passed in.
//
if( !pliNewPointer ){
pliNewPointer = &liNewPointer;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpSeekPrinter) (
pPrintHandle->hPrinter,
liDistanceToMove,
pliNewPointer,
dwMoveMethod,
bWritePrinter );
}
BOOL
FlushPrinter(
HANDLE hPrinter,
LPVOID pBuf,
DWORD cbBuf,
LPDWORD pcWritten,
DWORD cSleep
)
/*++
Function Description: FlushPrinter is typically used by the driver to send a burst of zeros
to the printer and introduce a delay in the i/o line to the printer.
The spooler does not schedule any job for cSleep milliseconds.
Parameters: hPrinter - printer handle
pBuf - buffer to be sent to the printer
cbBuf - size of the buffer
pcWritten - pointer to return the number of bytes written
cSleep - sleep time in milliseconds.
Return Values: TRUE if successful;
FALSE otherwise
--*/
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
//
// Check for valid printer handle
//
if (!pPrintHandle ||
(pPrintHandle->signature != PRINTHANDLE_SIGNATURE) ||
(pPrintHandle->hFileSpooler != INVALID_HANDLE_VALUE))
{
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpFlushPrinter) (pPrintHandle->hPrinter,
pBuf,
cbBuf,
pcWritten,
cSleep);
}
BOOL
EndPagePrinter(
HANDLE hPrinter
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpEndPagePrinter) (pPrintHandle->hPrinter);
}
BOOL
AbortPrinter(
HANDLE hPrinter
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpAbortPrinter) (pPrintHandle->hPrinter);
}
BOOL
ReadPrinter(
HANDLE hPrinter,
LPVOID pBuf,
DWORD cbBuf,
LPDWORD pRead
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpReadPrinter)
(pPrintHandle->hPrinter, pBuf, cbBuf, pRead);
}
BOOL
SplReadPrinter(
HANDLE hPrinter,
LPBYTE *pBuf,
DWORD cbBuf
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpSplReadPrinter)
(pPrintHandle->hPrinter, pBuf, cbBuf);
}
BOOL
EndDocPrinter(
HANDLE hPrinter
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpEndDocPrinter) (pPrintHandle->hPrinter);
}
HANDLE
CreatePrinterIC(
HANDLE hPrinter,
LPDEVMODEW pDevMode
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
HANDLE ReturnValue;
PGDIHANDLE pGdiHandle;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
pGdiHandle = AllocSplMem(sizeof(GDIHANDLE));
if (!pGdiHandle) {
DBGMSG(DBG_WARN, ("Failed to alloc GDI handle."));
return FALSE;
}
ReturnValue = (HANDLE)(*pPrintHandle->pProvidor->PrintProvidor.fpCreatePrinterIC)
(pPrintHandle->hPrinter,
pDevMode);
if (ReturnValue) {
pGdiHandle->signature = GDIHANDLE_SIGNATURE;
pGdiHandle->pProvidor = pPrintHandle->pProvidor;
pGdiHandle->hGdi = ReturnValue;
return pGdiHandle;
}
FreeSplMem(pGdiHandle);
return FALSE;
}
BOOL
PlayGdiScriptOnPrinterIC(
HANDLE hPrinterIC,
LPBYTE pIn,
DWORD cIn,
LPBYTE pOut,
DWORD cOut,
DWORD ul
)
{
PGDIHANDLE pGdiHandle=(PGDIHANDLE)hPrinterIC;
if (!pGdiHandle || pGdiHandle->signature != GDIHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pGdiHandle->pProvidor->PrintProvidor.fpPlayGdiScriptOnPrinterIC)
(pGdiHandle->hGdi, pIn, cIn, pOut, cOut, ul);
}
BOOL
DeletePrinterIC(
HANDLE hPrinterIC
)
{
LPGDIHANDLE pGdiHandle=(LPGDIHANDLE)hPrinterIC;
if (!pGdiHandle || pGdiHandle->signature != GDIHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
if ((*pGdiHandle->pProvidor->PrintProvidor.fpDeletePrinterIC) (pGdiHandle->hGdi)) {
FreeSplMem(pGdiHandle);
return TRUE;
}
return FALSE;
}
DWORD
PrinterMessageBox(
HANDLE hPrinter,
DWORD Error,
HWND hWnd,
LPWSTR pText,
LPWSTR pCaption,
DWORD dwType
)
{
LPPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (!pPrintHandle || pPrintHandle->signature != PRINTHANDLE_SIGNATURE) {
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return (*pPrintHandle->pProvidor->PrintProvidor.fpPrinterMessageBox)
(hPrinter, Error, hWnd, pText, pCaption, dwType);
}
DWORD
SendRecvBidiData(
IN HANDLE hPrinter,
IN LPCTSTR pAction,
IN PBIDI_REQUEST_CONTAINER pReqData,
OUT PBIDI_RESPONSE_CONTAINER* ppResData
)
{
DWORD dwRet = ERROR_SUCCESS;
LPPRINTHANDLE pPrintHandle = (LPPRINTHANDLE)hPrinter;
//
// Check for valid printer handle
//
if (!pPrintHandle ||
(pPrintHandle->signature != PRINTHANDLE_SIGNATURE) ||
(pPrintHandle->hFileSpooler != INVALID_HANDLE_VALUE))
{
dwRet = ERROR_INVALID_HANDLE;
}
else
{
dwRet = (*pPrintHandle->pProvidor->PrintProvidor.fpSendRecvBidiData)(pPrintHandle->hPrinter,
pAction,
pReqData,
ppResData);
}
return (dwRet);
}
/*++
Routine Name:
SplPromptUIInUsersSession
Routine Description:
Pops message boxes in the user's session.
For Whistler this function shows only message boxes in Spoolsv.exe.
Arguments:
hPrinter -- printer handle
JobId -- job ID
pUIParams -- UI parameters
pResponse -- user's response
Return Value:
TRUE if succeeded
Last Error:
Win32 error
--*/
BOOL
SplPromptUIInUsersSession(
IN HANDLE hPrinter,
IN DWORD JobId,
IN PSHOWUIPARAMS pUIParams,
OUT DWORD *pResponse
)
{
typedef BOOL (*FPPROMPT_UI)(HANDLE, DWORD, PSHOWUIPARAMS, DWORD*);
FPPROMPT_UI fpPromptUIPerSessionUser;
BOOL bRetValue = FALSE;
PPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (pPrintHandle && pPrintHandle->signature == PRINTHANDLE_SIGNATURE)
{
if (pPrintHandle->pProvidor == pLocalProvidor &&
(fpPromptUIPerSessionUser = (FPPROMPT_UI)GetProcAddress(pLocalProvidor->hModule,
"LclPromptUIPerSessionUser")))
{
bRetValue = (*fpPromptUIPerSessionUser)(pPrintHandle->hPrinter, JobId, pUIParams, pResponse);
}
else
{
SetLastError(ERROR_NOT_SUPPORTED);
}
}
else
{
SetLastError(ERROR_INVALID_HANDLE);
}
return bRetValue;
}
/*++
Routine Name:
SplIsSessionZero
Routine Description:
Determine is user that submitted a certain job runs in Session 0.
It is used by Canon monitor to determine when to show
resource template base UI versus calling SplPromptUIInUsersSession.
Arguments:
hPrinter -- printer handle
JobId -- job ID
pIsSessionZero -- TRUE if user runs in Session 0
Return Value:
Win32 last error
Last Error:
--*/
DWORD
SplIsSessionZero(
IN HANDLE hPrinter,
IN DWORD JobId,
OUT BOOL *pIsSessionZero
)
{
typedef DWORD (*FPISSESSIONZERO)(HANDLE, DWORD, BOOL*);
FPISSESSIONZERO fpIsSessionZero;
DWORD dwRetValue = ERROR_SUCCESS;
PPRINTHANDLE pPrintHandle=(LPPRINTHANDLE)hPrinter;
if (pPrintHandle && pPrintHandle->signature == PRINTHANDLE_SIGNATURE)
{
if (pPrintHandle->pProvidor == pLocalProvidor &&
(fpIsSessionZero = (FPISSESSIONZERO)GetProcAddress(pLocalProvidor->hModule,
"LclIsSessionZero")))
{
dwRetValue = (*fpIsSessionZero)(pPrintHandle->hPrinter, JobId, pIsSessionZero);
}
else
{
dwRetValue = ERROR_NOT_SUPPORTED;
}
}
else
{
dwRetValue = ERROR_INVALID_HANDLE;
}
return dwRetValue;
}