1 /* 2 * PROJECT: ReactOS Spooler API 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Functions related to Printers and printing 5 * COPYRIGHT: Copyright 2015-2017 Colin Finck (colin@reactos.org) 6 */ 7 8 #include "precomp.h" 9 10 // Local Constants 11 12 /** And the award for the most confusingly named setting goes to "Device", for storing the default printer of the current user. 13 Ok, I admit that this has historical reasons. It's still not straightforward in any way though! */ 14 static const WCHAR wszWindowsKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows"; 15 static const WCHAR wszDeviceValue[] = L"Device"; 16 17 static void 18 _MarshallUpPrinterInfo(PBYTE* ppPrinterInfo, DWORD Level) 19 { 20 // Replace relative offset addresses in the output by absolute pointers and advance to the next structure. 21 if (Level == 0) 22 { 23 PPRINTER_INFO_STRESS pPrinterInfo0 = (PPRINTER_INFO_STRESS)(*ppPrinterInfo); 24 25 pPrinterInfo0->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo0->pPrinterName + (ULONG_PTR)pPrinterInfo0); 26 27 if (pPrinterInfo0->pServerName) 28 pPrinterInfo0->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo0->pServerName + (ULONG_PTR)pPrinterInfo0); 29 30 *ppPrinterInfo += sizeof(PRINTER_INFO_STRESS); 31 } 32 else if (Level == 1) 33 { 34 PPRINTER_INFO_1W pPrinterInfo1 = (PPRINTER_INFO_1W)(*ppPrinterInfo); 35 36 pPrinterInfo1->pName = (PWSTR)((ULONG_PTR)pPrinterInfo1->pName + (ULONG_PTR)pPrinterInfo1); 37 pPrinterInfo1->pDescription = (PWSTR)((ULONG_PTR)pPrinterInfo1->pDescription + (ULONG_PTR)pPrinterInfo1); 38 pPrinterInfo1->pComment = (PWSTR)((ULONG_PTR)pPrinterInfo1->pComment + (ULONG_PTR)pPrinterInfo1); 39 40 *ppPrinterInfo += sizeof(PRINTER_INFO_1W); 41 } 42 else if (Level == 2) 43 { 44 PPRINTER_INFO_2W pPrinterInfo2 = (PPRINTER_INFO_2W)(*ppPrinterInfo); 45 46 pPrinterInfo2->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPrinterName + (ULONG_PTR)pPrinterInfo2); 47 pPrinterInfo2->pShareName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pShareName + (ULONG_PTR)pPrinterInfo2); 48 pPrinterInfo2->pPortName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPortName + (ULONG_PTR)pPrinterInfo2); 49 pPrinterInfo2->pDriverName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pDriverName + (ULONG_PTR)pPrinterInfo2); 50 pPrinterInfo2->pComment = (PWSTR)((ULONG_PTR)pPrinterInfo2->pComment + (ULONG_PTR)pPrinterInfo2); 51 pPrinterInfo2->pLocation = (PWSTR)((ULONG_PTR)pPrinterInfo2->pLocation + (ULONG_PTR)pPrinterInfo2); 52 pPrinterInfo2->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo2->pDevMode + (ULONG_PTR)pPrinterInfo2); 53 pPrinterInfo2->pSepFile = (PWSTR)((ULONG_PTR)pPrinterInfo2->pSepFile + (ULONG_PTR)pPrinterInfo2); 54 pPrinterInfo2->pPrintProcessor = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPrintProcessor + (ULONG_PTR)pPrinterInfo2); 55 pPrinterInfo2->pDatatype = (PWSTR)((ULONG_PTR)pPrinterInfo2->pDatatype + (ULONG_PTR)pPrinterInfo2); 56 pPrinterInfo2->pParameters = (PWSTR)((ULONG_PTR)pPrinterInfo2->pParameters + (ULONG_PTR)pPrinterInfo2); 57 58 if (pPrinterInfo2->pServerName) 59 pPrinterInfo2->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pServerName + (ULONG_PTR)pPrinterInfo2); 60 61 if (pPrinterInfo2->pSecurityDescriptor) 62 pPrinterInfo2->pSecurityDescriptor = (PSECURITY_DESCRIPTOR)((ULONG_PTR)pPrinterInfo2->pSecurityDescriptor + (ULONG_PTR)pPrinterInfo2); 63 64 *ppPrinterInfo += sizeof(PRINTER_INFO_2W); 65 } 66 else if (Level == 3) 67 { 68 PPRINTER_INFO_3 pPrinterInfo3 = (PPRINTER_INFO_3)(*ppPrinterInfo); 69 70 pPrinterInfo3->pSecurityDescriptor = (PSECURITY_DESCRIPTOR)((ULONG_PTR)pPrinterInfo3->pSecurityDescriptor + (ULONG_PTR)pPrinterInfo3); 71 72 *ppPrinterInfo += sizeof(PRINTER_INFO_3); 73 } 74 else if (Level == 4) 75 { 76 PPRINTER_INFO_4W pPrinterInfo4 = (PPRINTER_INFO_4W)(*ppPrinterInfo); 77 78 pPrinterInfo4->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo4->pPrinterName + (ULONG_PTR)pPrinterInfo4); 79 80 if (pPrinterInfo4->pServerName) 81 pPrinterInfo4->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo4->pServerName + (ULONG_PTR)pPrinterInfo4); 82 83 *ppPrinterInfo += sizeof(PRINTER_INFO_4W); 84 } 85 else if (Level == 5) 86 { 87 PPRINTER_INFO_5W pPrinterInfo5 = (PPRINTER_INFO_5W)(*ppPrinterInfo); 88 89 pPrinterInfo5->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo5->pPrinterName + (ULONG_PTR)pPrinterInfo5); 90 pPrinterInfo5->pPortName = (PWSTR)((ULONG_PTR)pPrinterInfo5->pPortName + (ULONG_PTR)pPrinterInfo5); 91 92 *ppPrinterInfo += sizeof(PRINTER_INFO_5W); 93 } 94 else if (Level == 6) 95 { 96 *ppPrinterInfo += sizeof(PRINTER_INFO_6); 97 } 98 else if (Level == 7) 99 { 100 PPRINTER_INFO_7W pPrinterInfo7 = (PPRINTER_INFO_7W)(*ppPrinterInfo); 101 102 if (pPrinterInfo7->pszObjectGUID) 103 pPrinterInfo7->pszObjectGUID = (PWSTR)((ULONG_PTR)pPrinterInfo7->pszObjectGUID + (ULONG_PTR)pPrinterInfo7); 104 105 *ppPrinterInfo += sizeof(PRINTER_INFO_7W); 106 } 107 else if (Level == 8) 108 { 109 PPRINTER_INFO_8W pPrinterInfo8 = (PPRINTER_INFO_8W)(*ppPrinterInfo); 110 111 pPrinterInfo8->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo8->pDevMode + (ULONG_PTR)pPrinterInfo8); 112 113 *ppPrinterInfo += sizeof(PRINTER_INFO_8W); 114 } 115 else if (Level == 9) 116 { 117 PPRINTER_INFO_9W pPrinterInfo9 = (PPRINTER_INFO_9W)(*ppPrinterInfo); 118 119 pPrinterInfo9->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo9->pDevMode + (ULONG_PTR)pPrinterInfo9); 120 121 *ppPrinterInfo += sizeof(PRINTER_INFO_9W); 122 } 123 } 124 125 static DWORD 126 _StartDocPrinterSpooled(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1, PADDJOB_INFO_1W pAddJobInfo1) 127 { 128 DWORD cbNeeded; 129 DWORD dwErrorCode; 130 PJOB_INFO_1W pJobInfo1 = NULL; 131 132 // Create the spool file. 133 pHandle->hSPLFile = CreateFileW(pAddJobInfo1->Path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); 134 if (pHandle->hSPLFile == INVALID_HANDLE_VALUE) 135 { 136 dwErrorCode = GetLastError(); 137 ERR("CreateFileW failed for \"%S\" with error %lu!\n", pAddJobInfo1->Path, dwErrorCode); 138 goto Cleanup; 139 } 140 141 // Get the size of the job information. 142 GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, NULL, 0, &cbNeeded); 143 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 144 { 145 dwErrorCode = GetLastError(); 146 ERR("GetJobW failed with error %lu!\n", dwErrorCode); 147 goto Cleanup; 148 } 149 150 // Allocate enough memory for the returned job information. 151 pJobInfo1 = HeapAlloc(hProcessHeap, 0, cbNeeded); 152 if (!pJobInfo1) 153 { 154 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 155 ERR("HeapAlloc failed!\n"); 156 goto Cleanup; 157 } 158 159 // Get the job information. 160 if (!GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, cbNeeded, &cbNeeded)) 161 { 162 dwErrorCode = GetLastError(); 163 ERR("GetJobW failed with error %lu!\n", dwErrorCode); 164 goto Cleanup; 165 } 166 167 // Add our document information. 168 if (pDocInfo1->pDatatype) 169 pJobInfo1->pDatatype = pDocInfo1->pDatatype; 170 171 pJobInfo1->pDocument = pDocInfo1->pDocName; 172 173 // Set the new job information. 174 if (!SetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, 0)) 175 { 176 dwErrorCode = GetLastError(); 177 ERR("SetJobW failed with error %lu!\n", dwErrorCode); 178 goto Cleanup; 179 } 180 181 // We were successful! 182 pHandle->dwJobID = pAddJobInfo1->JobId; 183 dwErrorCode = ERROR_SUCCESS; 184 185 Cleanup: 186 if (pJobInfo1) 187 HeapFree(hProcessHeap, 0, pJobInfo1); 188 189 return dwErrorCode; 190 } 191 192 static DWORD 193 _StartDocPrinterWithRPC(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1) 194 { 195 DWORD dwErrorCode; 196 WINSPOOL_DOC_INFO_CONTAINER DocInfoContainer; 197 198 DocInfoContainer.Level = 1; 199 DocInfoContainer.DocInfo.pDocInfo1 = (WINSPOOL_DOC_INFO_1*)pDocInfo1; 200 201 RpcTryExcept 202 { 203 dwErrorCode = _RpcStartDocPrinter(pHandle->hPrinter, &DocInfoContainer, &pHandle->dwJobID); 204 } 205 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 206 { 207 dwErrorCode = RpcExceptionCode(); 208 ERR("_RpcStartDocPrinter failed with exception code %lu!\n", dwErrorCode); 209 } 210 RpcEndExcept; 211 212 return dwErrorCode; 213 } 214 215 HANDLE WINAPI 216 AddPrinterW(PWSTR pName, DWORD Level, PBYTE pPrinter) 217 { 218 UNIMPLEMENTED; 219 return NULL; 220 } 221 222 BOOL WINAPI 223 ClosePrinter(HANDLE hPrinter) 224 { 225 DWORD dwErrorCode; 226 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 227 228 // Sanity checks. 229 if (!pHandle) 230 { 231 dwErrorCode = ERROR_INVALID_HANDLE; 232 goto Cleanup; 233 } 234 235 // Do the RPC call. 236 RpcTryExcept 237 { 238 dwErrorCode = _RpcClosePrinter(&pHandle->hPrinter); 239 } 240 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 241 { 242 dwErrorCode = RpcExceptionCode(); 243 ERR("_RpcClosePrinter failed with exception code %lu!\n", dwErrorCode); 244 } 245 RpcEndExcept; 246 247 // Close any open file handle. 248 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE) 249 CloseHandle(pHandle->hSPLFile); 250 251 // Free the memory for the handle. 252 HeapFree(hProcessHeap, 0, pHandle); 253 254 Cleanup: 255 SetLastError(dwErrorCode); 256 return (dwErrorCode == ERROR_SUCCESS); 257 } 258 259 DWORD WINAPI 260 DeviceCapabilitiesA(LPCSTR pDevice, LPCSTR pPort, WORD fwCapability, LPSTR pOutput, const DEVMODEA* pDevMode) 261 { 262 return 0; 263 } 264 265 DWORD WINAPI 266 DeviceCapabilitiesW(LPCWSTR pDevice, LPCWSTR pPort, WORD fwCapability, LPWSTR pOutput, const DEVMODEW* pDevMode) 267 { 268 return 0; 269 } 270 271 LONG WINAPI 272 DocumentPropertiesA(HWND hWnd, HANDLE hPrinter, LPSTR pDeviceName, PDEVMODEA pDevModeOutput, PDEVMODEA pDevModeInput, DWORD fMode) 273 { 274 return 0; 275 } 276 277 LONG WINAPI 278 DocumentPropertiesW(HWND hWnd, HANDLE hPrinter, LPWSTR pDeviceName, PDEVMODEW pDevModeOutput, PDEVMODEW pDevModeInput, DWORD fMode) 279 { 280 return 0; 281 } 282 283 BOOL WINAPI 284 EndDocPrinter(HANDLE hPrinter) 285 { 286 DWORD dwErrorCode; 287 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 288 289 // Sanity checks. 290 if (!pHandle) 291 { 292 dwErrorCode = ERROR_INVALID_HANDLE; 293 goto Cleanup; 294 } 295 296 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE) 297 { 298 // For spooled jobs, the document is finished by calling _RpcScheduleJob. 299 RpcTryExcept 300 { 301 dwErrorCode = _RpcScheduleJob(pHandle->hPrinter, pHandle->dwJobID); 302 } 303 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 304 { 305 dwErrorCode = RpcExceptionCode(); 306 ERR("_RpcScheduleJob failed with exception code %lu!\n", dwErrorCode); 307 } 308 RpcEndExcept; 309 310 // Close the spool file handle. 311 CloseHandle(pHandle->hSPLFile); 312 } 313 else 314 { 315 // In all other cases, just call _RpcEndDocPrinter. 316 RpcTryExcept 317 { 318 dwErrorCode = _RpcEndDocPrinter(pHandle->hPrinter); 319 } 320 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 321 { 322 dwErrorCode = RpcExceptionCode(); 323 ERR("_RpcEndDocPrinter failed with exception code %lu!\n", dwErrorCode); 324 } 325 RpcEndExcept; 326 } 327 328 // A new document can now be started again. 329 pHandle->bStartedDoc = FALSE; 330 331 Cleanup: 332 SetLastError(dwErrorCode); 333 return (dwErrorCode == ERROR_SUCCESS); 334 } 335 336 BOOL WINAPI 337 EndPagePrinter(HANDLE hPrinter) 338 { 339 DWORD dwErrorCode; 340 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 341 342 // Sanity checks. 343 if (!pHandle) 344 { 345 dwErrorCode = ERROR_INVALID_HANDLE; 346 goto Cleanup; 347 } 348 349 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE) 350 { 351 // For spooled jobs, we don't need to do anything. 352 dwErrorCode = ERROR_SUCCESS; 353 } 354 else 355 { 356 // In all other cases, just call _RpcEndPagePrinter. 357 RpcTryExcept 358 { 359 dwErrorCode = _RpcEndPagePrinter(pHandle->hPrinter); 360 } 361 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 362 { 363 dwErrorCode = RpcExceptionCode(); 364 ERR("_RpcEndPagePrinter failed with exception code %lu!\n", dwErrorCode); 365 } 366 RpcEndExcept; 367 } 368 369 Cleanup: 370 SetLastError(dwErrorCode); 371 return (dwErrorCode == ERROR_SUCCESS); 372 } 373 374 BOOL WINAPI 375 EnumPrintersA(DWORD Flags, PSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned) 376 { 377 return FALSE; 378 } 379 380 BOOL WINAPI 381 EnumPrintersW(DWORD Flags, PWSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned) 382 { 383 DWORD dwErrorCode; 384 385 // Dismiss invalid levels already at this point. 386 if (Level == 3 || Level > 5) 387 { 388 dwErrorCode = ERROR_INVALID_LEVEL; 389 goto Cleanup; 390 } 391 392 if (cbBuf && pPrinterEnum) 393 ZeroMemory(pPrinterEnum, cbBuf); 394 395 // Do the RPC call 396 RpcTryExcept 397 { 398 dwErrorCode = _RpcEnumPrinters(Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned); 399 } 400 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 401 { 402 dwErrorCode = RpcExceptionCode(); 403 ERR("_RpcEnumPrinters failed with exception code %lu!\n", dwErrorCode); 404 } 405 RpcEndExcept; 406 407 if (dwErrorCode == ERROR_SUCCESS) 408 { 409 DWORD i; 410 PBYTE p = pPrinterEnum; 411 412 for (i = 0; i < *pcReturned; i++) 413 _MarshallUpPrinterInfo(&p, Level); 414 } 415 416 Cleanup: 417 SetLastError(dwErrorCode); 418 return (dwErrorCode == ERROR_SUCCESS); 419 } 420 421 BOOL WINAPI 422 GetDefaultPrinterA(LPSTR pszBuffer, LPDWORD pcchBuffer) 423 { 424 DWORD dwErrorCode; 425 PWSTR pwszBuffer = NULL; 426 427 // Sanity check. 428 if (!pcchBuffer) 429 { 430 dwErrorCode = ERROR_INVALID_PARAMETER; 431 goto Cleanup; 432 } 433 434 // Check if an ANSI buffer was given and if so, allocate a Unicode buffer of the same size. 435 if (pszBuffer && *pcchBuffer) 436 { 437 pwszBuffer = HeapAlloc(hProcessHeap, 0, *pcchBuffer * sizeof(WCHAR)); 438 if (!pwszBuffer) 439 { 440 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 441 ERR("HeapAlloc failed!\n"); 442 goto Cleanup; 443 } 444 } 445 446 if (!GetDefaultPrinterW(pwszBuffer, pcchBuffer)) 447 { 448 dwErrorCode = GetLastError(); 449 goto Cleanup; 450 } 451 452 dwErrorCode = ERROR_SUCCESS; 453 454 Cleanup: 455 if (pwszBuffer) 456 HeapFree(hProcessHeap, 0, pwszBuffer); 457 458 SetLastError(dwErrorCode); 459 return (dwErrorCode == ERROR_SUCCESS); 460 } 461 462 BOOL WINAPI 463 GetDefaultPrinterW(LPWSTR pszBuffer, LPDWORD pcchBuffer) 464 { 465 DWORD cbNeeded; 466 DWORD cchInputBuffer; 467 DWORD dwErrorCode; 468 HKEY hWindowsKey = NULL; 469 PWSTR pwszDevice = NULL; 470 PWSTR pwszComma; 471 472 // Sanity check. 473 if (!pcchBuffer) 474 { 475 dwErrorCode = ERROR_INVALID_PARAMETER; 476 goto Cleanup; 477 } 478 479 cchInputBuffer = *pcchBuffer; 480 481 // Open the registry key where the default printer for the current user is stored. 482 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_READ, &hWindowsKey); 483 if (dwErrorCode != ERROR_SUCCESS) 484 { 485 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode); 486 goto Cleanup; 487 } 488 489 // Determine the size of the required buffer. 490 dwErrorCode = (DWORD)RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, NULL, &cbNeeded); 491 if (dwErrorCode != ERROR_SUCCESS) 492 { 493 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode); 494 goto Cleanup; 495 } 496 497 // Allocate it. 498 pwszDevice = HeapAlloc(hProcessHeap, 0, cbNeeded); 499 if (!pwszDevice) 500 { 501 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 502 ERR("HeapAlloc failed!\n"); 503 goto Cleanup; 504 } 505 506 // Now get the actual value. 507 dwErrorCode = RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, (PBYTE)pwszDevice, &cbNeeded); 508 if (dwErrorCode != ERROR_SUCCESS) 509 { 510 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode); 511 goto Cleanup; 512 } 513 514 // We get a string "<Printer Name>,winspool,<Port>:". 515 // Extract the printer name from it. 516 pwszComma = wcschr(pwszDevice, L','); 517 if (!pwszComma) 518 { 519 ERR("Found no or invalid default printer: %S!\n", pwszDevice); 520 dwErrorCode = ERROR_INVALID_NAME; 521 goto Cleanup; 522 } 523 524 // Store the length of the Printer Name (including the terminating NUL character!) in *pcchBuffer. 525 *pcchBuffer = pwszComma - pwszDevice + 1; 526 527 // Check if the supplied buffer is large enough. 528 if (cchInputBuffer < *pcchBuffer) 529 { 530 dwErrorCode = ERROR_INSUFFICIENT_BUFFER; 531 goto Cleanup; 532 } 533 534 // Copy the default printer. 535 *pwszComma = 0; 536 CopyMemory(pszBuffer, pwszDevice, *pcchBuffer * sizeof(WCHAR)); 537 538 dwErrorCode = ERROR_SUCCESS; 539 540 Cleanup: 541 if (hWindowsKey) 542 RegCloseKey(hWindowsKey); 543 544 if (pwszDevice) 545 HeapFree(hProcessHeap, 0, pwszDevice); 546 547 SetLastError(dwErrorCode); 548 return (dwErrorCode == ERROR_SUCCESS); 549 } 550 551 BOOL WINAPI 552 GetPrinterA(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded) 553 { 554 return FALSE; 555 } 556 557 BOOL WINAPI 558 GetPrinterDriverA(HANDLE hPrinter, LPSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded) 559 { 560 return FALSE; 561 } 562 563 BOOL WINAPI 564 GetPrinterDriverW(HANDLE hPrinter, LPWSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded) 565 { 566 return FALSE; 567 } 568 569 BOOL WINAPI 570 GetPrinterW(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded) 571 { 572 DWORD dwErrorCode; 573 574 // Dismiss invalid levels already at this point. 575 if (Level > 9) 576 { 577 dwErrorCode = ERROR_INVALID_LEVEL; 578 goto Cleanup; 579 } 580 581 if (cbBuf && pPrinter) 582 ZeroMemory(pPrinter, cbBuf); 583 584 // Do the RPC call 585 RpcTryExcept 586 { 587 dwErrorCode = _RpcGetPrinter(hPrinter, Level, pPrinter, cbBuf, pcbNeeded); 588 } 589 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 590 { 591 dwErrorCode = RpcExceptionCode(); 592 ERR("_RpcGetPrinter failed with exception code %lu!\n", dwErrorCode); 593 } 594 RpcEndExcept; 595 596 if (dwErrorCode == ERROR_SUCCESS) 597 { 598 PBYTE p = pPrinter; 599 _MarshallUpPrinterInfo(&p, Level); 600 } 601 602 Cleanup: 603 SetLastError(dwErrorCode); 604 return (dwErrorCode == ERROR_SUCCESS); 605 } 606 607 BOOL WINAPI 608 OpenPrinterA(LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefault) 609 { 610 BOOL bReturnValue = FALSE; 611 DWORD cch; 612 PWSTR pwszPrinterName = NULL; 613 PRINTER_DEFAULTSW wDefault = { 0 }; 614 615 if (pPrinterName) 616 { 617 // Convert pPrinterName to a Unicode string pwszPrinterName 618 cch = strlen(pPrinterName); 619 620 pwszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR)); 621 if (!pwszPrinterName) 622 { 623 SetLastError(ERROR_NOT_ENOUGH_MEMORY); 624 ERR("HeapAlloc failed!\n"); 625 goto Cleanup; 626 } 627 628 MultiByteToWideChar(CP_ACP, 0, pPrinterName, -1, pwszPrinterName, cch + 1); 629 } 630 631 if (pDefault) 632 { 633 wDefault.DesiredAccess = pDefault->DesiredAccess; 634 635 if (pDefault->pDatatype) 636 { 637 // Convert pDefault->pDatatype to a Unicode string wDefault.pDatatype 638 cch = strlen(pDefault->pDatatype); 639 640 wDefault.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR)); 641 if (!wDefault.pDatatype) 642 { 643 SetLastError(ERROR_NOT_ENOUGH_MEMORY); 644 ERR("HeapAlloc failed!\n"); 645 goto Cleanup; 646 } 647 648 MultiByteToWideChar(CP_ACP, 0, pDefault->pDatatype, -1, wDefault.pDatatype, cch + 1); 649 } 650 651 if (pDefault->pDevMode) 652 wDefault.pDevMode = GdiConvertToDevmodeW(pDefault->pDevMode); 653 } 654 655 bReturnValue = OpenPrinterW(pwszPrinterName, phPrinter, &wDefault); 656 657 Cleanup: 658 if (wDefault.pDatatype) 659 HeapFree(hProcessHeap, 0, wDefault.pDatatype); 660 661 if (wDefault.pDevMode) 662 HeapFree(hProcessHeap, 0, wDefault.pDevMode); 663 664 if (pwszPrinterName) 665 HeapFree(hProcessHeap, 0, pwszPrinterName); 666 667 return bReturnValue; 668 } 669 670 BOOL WINAPI 671 OpenPrinterW(LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSW pDefault) 672 { 673 DWORD dwErrorCode; 674 HANDLE hPrinter; 675 PSPOOLER_HANDLE pHandle; 676 PWSTR pDatatype = NULL; 677 WINSPOOL_DEVMODE_CONTAINER DevModeContainer = { 0 }; 678 ACCESS_MASK AccessRequired = 0; 679 680 // Sanity check 681 if (!phPrinter) 682 { 683 dwErrorCode = ERROR_INVALID_PARAMETER; 684 goto Cleanup; 685 } 686 687 // Prepare the additional parameters in the format required by _RpcOpenPrinter 688 if (pDefault) 689 { 690 pDatatype = pDefault->pDatatype; 691 DevModeContainer.cbBuf = sizeof(DEVMODEW); 692 DevModeContainer.pDevMode = (BYTE*)pDefault->pDevMode; 693 AccessRequired = pDefault->DesiredAccess; 694 } 695 696 // Do the RPC call 697 RpcTryExcept 698 { 699 dwErrorCode = _RpcOpenPrinter(pPrinterName, &hPrinter, pDatatype, &DevModeContainer, AccessRequired); 700 } 701 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 702 { 703 dwErrorCode = RpcExceptionCode(); 704 ERR("_RpcOpenPrinter failed with exception code %lu!\n", dwErrorCode); 705 } 706 RpcEndExcept; 707 708 if (dwErrorCode == ERROR_SUCCESS) 709 { 710 // Create a new SPOOLER_HANDLE structure. 711 pHandle = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(SPOOLER_HANDLE)); 712 if (!pHandle) 713 { 714 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 715 ERR("HeapAlloc failed!\n"); 716 goto Cleanup; 717 } 718 719 pHandle->hPrinter = hPrinter; 720 pHandle->hSPLFile = INVALID_HANDLE_VALUE; 721 722 // Return it as phPrinter. 723 *phPrinter = (HANDLE)pHandle; 724 } 725 726 Cleanup: 727 SetLastError(dwErrorCode); 728 return (dwErrorCode == ERROR_SUCCESS); 729 } 730 731 BOOL WINAPI 732 ReadPrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pNoBytesRead) 733 { 734 DWORD dwErrorCode; 735 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 736 737 // Sanity checks. 738 if (!pHandle) 739 { 740 dwErrorCode = ERROR_INVALID_HANDLE; 741 goto Cleanup; 742 } 743 744 // Do the RPC call 745 RpcTryExcept 746 { 747 dwErrorCode = _RpcReadPrinter(pHandle->hPrinter, pBuf, cbBuf, pNoBytesRead); 748 } 749 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 750 { 751 dwErrorCode = RpcExceptionCode(); 752 ERR("_RpcReadPrinter failed with exception code %lu!\n", dwErrorCode); 753 } 754 RpcEndExcept; 755 756 Cleanup: 757 SetLastError(dwErrorCode); 758 return (dwErrorCode == ERROR_SUCCESS); 759 } 760 761 BOOL WINAPI 762 ResetPrinterW(HANDLE hPrinter, PPRINTER_DEFAULTSW pDefault) 763 { 764 UNIMPLEMENTED; 765 return FALSE; 766 } 767 768 BOOL WINAPI 769 SetDefaultPrinterA(LPCSTR pszPrinter) 770 { 771 BOOL bReturnValue = FALSE; 772 DWORD cch; 773 PWSTR pwszPrinter = NULL; 774 775 if (pszPrinter) 776 { 777 // Convert pszPrinter to a Unicode string pwszPrinter 778 cch = strlen(pszPrinter); 779 780 pwszPrinter = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR)); 781 if (!pwszPrinter) 782 { 783 SetLastError(ERROR_NOT_ENOUGH_MEMORY); 784 ERR("HeapAlloc failed!\n"); 785 goto Cleanup; 786 } 787 788 MultiByteToWideChar(CP_ACP, 0, pszPrinter, -1, pwszPrinter, cch + 1); 789 } 790 791 bReturnValue = SetDefaultPrinterW(pwszPrinter); 792 793 Cleanup: 794 if (pwszPrinter) 795 HeapFree(hProcessHeap, 0, pwszPrinter); 796 797 return bReturnValue; 798 } 799 800 BOOL WINAPI 801 SetDefaultPrinterW(LPCWSTR pszPrinter) 802 { 803 const WCHAR wszDevicesKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Devices"; 804 805 DWORD cbDeviceValueData; 806 DWORD cbPrinterValueData = 0; 807 DWORD cchPrinter; 808 DWORD dwErrorCode; 809 HKEY hDevicesKey = NULL; 810 HKEY hWindowsKey = NULL; 811 PWSTR pwszDeviceValueData = NULL; 812 WCHAR wszPrinter[MAX_PRINTER_NAME + 1]; 813 814 // Open the Devices registry key. 815 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszDevicesKey, 0, KEY_READ, &hDevicesKey); 816 if (dwErrorCode != ERROR_SUCCESS) 817 { 818 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode); 819 goto Cleanup; 820 } 821 822 // Did the caller give us a printer to set as default? 823 if (pszPrinter && *pszPrinter) 824 { 825 // Check if the given printer exists and query the value data size. 826 dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, NULL, &cbPrinterValueData); 827 if (dwErrorCode == ERROR_FILE_NOT_FOUND) 828 { 829 dwErrorCode = ERROR_INVALID_PRINTER_NAME; 830 goto Cleanup; 831 } 832 else if (dwErrorCode != ERROR_SUCCESS) 833 { 834 ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode); 835 goto Cleanup; 836 } 837 838 cchPrinter = wcslen(pszPrinter); 839 } 840 else 841 { 842 // If there is already a default printer, we're done! 843 cchPrinter = _countof(wszPrinter); 844 if (GetDefaultPrinterW(wszPrinter, &cchPrinter)) 845 { 846 dwErrorCode = ERROR_SUCCESS; 847 goto Cleanup; 848 } 849 850 // Otherwise, get us the first printer from the "Devices" key to later set it as default and query the value data size. 851 cchPrinter = _countof(wszPrinter); 852 dwErrorCode = (DWORD)RegEnumValueW(hDevicesKey, 0, wszPrinter, &cchPrinter, NULL, NULL, NULL, &cbPrinterValueData); 853 if (dwErrorCode != ERROR_MORE_DATA) 854 goto Cleanup; 855 856 pszPrinter = wszPrinter; 857 } 858 859 // We now need to query the value data, which has the format "winspool,<Port>:" 860 // and make "<Printer Name>,winspool,<Port>:" out of it. 861 // Allocate a buffer large enough for the final data. 862 cbDeviceValueData = (cchPrinter + 1) * sizeof(WCHAR) + cbPrinterValueData; 863 pwszDeviceValueData = HeapAlloc(hProcessHeap, 0, cbDeviceValueData); 864 if (!pwszDeviceValueData) 865 { 866 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 867 ERR("HeapAlloc failed!\n"); 868 goto Cleanup; 869 } 870 871 // Copy the Printer Name and a comma into it. 872 CopyMemory(pwszDeviceValueData, pszPrinter, cchPrinter * sizeof(WCHAR)); 873 pwszDeviceValueData[cchPrinter] = L','; 874 875 // Append the value data, which has the format "winspool,<Port>:" 876 dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, (PBYTE)&pwszDeviceValueData[cchPrinter + 1], &cbPrinterValueData); 877 if (dwErrorCode != ERROR_SUCCESS) 878 goto Cleanup; 879 880 // Open the Windows registry key. 881 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_SET_VALUE, &hWindowsKey); 882 if (dwErrorCode != ERROR_SUCCESS) 883 { 884 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode); 885 goto Cleanup; 886 } 887 888 // Store our new default printer. 889 dwErrorCode = (DWORD)RegSetValueExW(hWindowsKey, wszDeviceValue, 0, REG_SZ, (PBYTE)pwszDeviceValueData, cbDeviceValueData); 890 if (dwErrorCode != ERROR_SUCCESS) 891 { 892 ERR("RegSetValueExW failed with status %lu!\n", dwErrorCode); 893 goto Cleanup; 894 } 895 896 Cleanup: 897 if (hDevicesKey) 898 RegCloseKey(hDevicesKey); 899 900 if (hWindowsKey) 901 RegCloseKey(hWindowsKey); 902 903 if (pwszDeviceValueData) 904 HeapFree(hProcessHeap, 0, pwszDeviceValueData); 905 906 SetLastError(dwErrorCode); 907 return (dwErrorCode == ERROR_SUCCESS); 908 } 909 910 BOOL WINAPI 911 SetPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pPrinter, DWORD Command) 912 { 913 UNIMPLEMENTED; 914 return FALSE; 915 } 916 917 DWORD WINAPI 918 StartDocPrinterA(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo) 919 { 920 DOC_INFO_1W wDocInfo1 = { 0 }; 921 DWORD cch; 922 DWORD dwErrorCode; 923 DWORD dwReturnValue = 0; 924 PDOC_INFO_1A pDocInfo1 = (PDOC_INFO_1A)pDocInfo; 925 926 // Only check the minimum required for accessing pDocInfo. 927 // Additional sanity checks are done in StartDocPrinterW. 928 if (!pDocInfo1) 929 { 930 dwErrorCode = ERROR_INVALID_PARAMETER; 931 goto Cleanup; 932 } 933 934 if (Level != 1) 935 { 936 dwErrorCode = ERROR_INVALID_LEVEL; 937 goto Cleanup; 938 } 939 940 if (pDocInfo1->pDatatype) 941 { 942 // Convert pDocInfo1->pDatatype to a Unicode string wDocInfo1.pDatatype 943 cch = strlen(pDocInfo1->pDatatype); 944 945 wDocInfo1.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR)); 946 if (!wDocInfo1.pDatatype) 947 { 948 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 949 ERR("HeapAlloc failed!\n"); 950 goto Cleanup; 951 } 952 953 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDatatype, -1, wDocInfo1.pDatatype, cch + 1); 954 } 955 956 if (pDocInfo1->pDocName) 957 { 958 // Convert pDocInfo1->pDocName to a Unicode string wDocInfo1.pDocName 959 cch = strlen(pDocInfo1->pDocName); 960 961 wDocInfo1.pDocName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR)); 962 if (!wDocInfo1.pDocName) 963 { 964 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 965 ERR("HeapAlloc failed!\n"); 966 goto Cleanup; 967 } 968 969 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDocName, -1, wDocInfo1.pDocName, cch + 1); 970 } 971 972 if (pDocInfo1->pOutputFile) 973 { 974 // Convert pDocInfo1->pOutputFile to a Unicode string wDocInfo1.pOutputFile 975 cch = strlen(pDocInfo1->pOutputFile); 976 977 wDocInfo1.pOutputFile = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR)); 978 if (!wDocInfo1.pOutputFile) 979 { 980 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 981 ERR("HeapAlloc failed!\n"); 982 goto Cleanup; 983 } 984 985 MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pOutputFile, -1, wDocInfo1.pOutputFile, cch + 1); 986 } 987 988 dwReturnValue = StartDocPrinterW(hPrinter, Level, (PBYTE)&wDocInfo1); 989 dwErrorCode = GetLastError(); 990 991 Cleanup: 992 if (wDocInfo1.pDatatype) 993 HeapFree(hProcessHeap, 0, wDocInfo1.pDatatype); 994 995 if (wDocInfo1.pDocName) 996 HeapFree(hProcessHeap, 0, wDocInfo1.pDocName); 997 998 if (wDocInfo1.pOutputFile) 999 HeapFree(hProcessHeap, 0, wDocInfo1.pOutputFile); 1000 1001 SetLastError(dwErrorCode); 1002 return dwReturnValue; 1003 } 1004 1005 DWORD WINAPI 1006 StartDocPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo) 1007 { 1008 DWORD cbAddJobInfo1; 1009 DWORD cbNeeded; 1010 DWORD dwErrorCode; 1011 DWORD dwReturnValue = 0; 1012 PADDJOB_INFO_1W pAddJobInfo1 = NULL; 1013 PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo; 1014 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 1015 1016 // Sanity checks. 1017 if (!pHandle) 1018 { 1019 dwErrorCode = ERROR_INVALID_HANDLE; 1020 goto Cleanup; 1021 } 1022 1023 if (!pDocInfo1) 1024 { 1025 dwErrorCode = ERROR_INVALID_PARAMETER; 1026 goto Cleanup; 1027 } 1028 1029 if (Level != 1) 1030 { 1031 dwErrorCode = ERROR_INVALID_LEVEL; 1032 goto Cleanup; 1033 } 1034 1035 if (pHandle->bStartedDoc) 1036 { 1037 dwErrorCode = ERROR_INVALID_PRINTER_STATE; 1038 goto Cleanup; 1039 } 1040 1041 // Check if we want to redirect output into a file. 1042 if (pDocInfo1->pOutputFile) 1043 { 1044 // Do a StartDocPrinter RPC call in this case. 1045 dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1); 1046 } 1047 else 1048 { 1049 // Allocate memory for the ADDJOB_INFO_1W structure and a path. 1050 cbAddJobInfo1 = sizeof(ADDJOB_INFO_1W) + MAX_PATH * sizeof(WCHAR); 1051 pAddJobInfo1 = HeapAlloc(hProcessHeap, 0, cbAddJobInfo1); 1052 if (!pAddJobInfo1) 1053 { 1054 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 1055 ERR("HeapAlloc failed!\n"); 1056 goto Cleanup; 1057 } 1058 1059 // Try to add a new job. 1060 // This only succeeds if the printer is set to do spooled printing. 1061 if (AddJobW((HANDLE)pHandle, 1, (PBYTE)pAddJobInfo1, cbAddJobInfo1, &cbNeeded)) 1062 { 1063 // Do spooled printing. 1064 dwErrorCode = _StartDocPrinterSpooled(pHandle, pDocInfo1, pAddJobInfo1); 1065 } 1066 else if (GetLastError() == ERROR_INVALID_ACCESS) 1067 { 1068 // ERROR_INVALID_ACCESS is returned when the printer is set to do direct printing. 1069 // In this case, we do a StartDocPrinter RPC call. 1070 dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1); 1071 } 1072 else 1073 { 1074 dwErrorCode = GetLastError(); 1075 ERR("AddJobW failed with error %lu!\n", dwErrorCode); 1076 goto Cleanup; 1077 } 1078 } 1079 1080 if (dwErrorCode == ERROR_SUCCESS) 1081 { 1082 pHandle->bStartedDoc = TRUE; 1083 dwReturnValue = pHandle->dwJobID; 1084 } 1085 1086 Cleanup: 1087 if (pAddJobInfo1) 1088 HeapFree(hProcessHeap, 0, pAddJobInfo1); 1089 1090 SetLastError(dwErrorCode); 1091 return dwReturnValue; 1092 } 1093 1094 BOOL WINAPI 1095 StartPagePrinter(HANDLE hPrinter) 1096 { 1097 DWORD dwErrorCode; 1098 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 1099 1100 // Sanity checks. 1101 if (!pHandle) 1102 { 1103 dwErrorCode = ERROR_INVALID_HANDLE; 1104 goto Cleanup; 1105 } 1106 1107 // Do the RPC call 1108 RpcTryExcept 1109 { 1110 dwErrorCode = _RpcStartPagePrinter(pHandle->hPrinter); 1111 } 1112 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 1113 { 1114 dwErrorCode = RpcExceptionCode(); 1115 ERR("_RpcStartPagePrinter failed with exception code %lu!\n", dwErrorCode); 1116 } 1117 RpcEndExcept; 1118 1119 Cleanup: 1120 SetLastError(dwErrorCode); 1121 return (dwErrorCode == ERROR_SUCCESS); 1122 } 1123 1124 BOOL WINAPI 1125 WritePrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pcWritten) 1126 { 1127 DWORD dwErrorCode; 1128 PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter; 1129 1130 // Sanity checks. 1131 if (!pHandle) 1132 { 1133 dwErrorCode = ERROR_INVALID_HANDLE; 1134 goto Cleanup; 1135 } 1136 1137 if (!pHandle->bStartedDoc) 1138 { 1139 dwErrorCode = ERROR_SPL_NO_STARTDOC; 1140 goto Cleanup; 1141 } 1142 1143 if (pHandle->hSPLFile != INVALID_HANDLE_VALUE) 1144 { 1145 // Write to the spool file. This doesn't need an RPC request. 1146 if (!WriteFile(pHandle->hSPLFile, pBuf, cbBuf, pcWritten, NULL)) 1147 { 1148 dwErrorCode = GetLastError(); 1149 ERR("WriteFile failed with error %lu!\n", dwErrorCode); 1150 goto Cleanup; 1151 } 1152 1153 dwErrorCode = ERROR_SUCCESS; 1154 } 1155 else 1156 { 1157 // TODO: This case (for direct printing or remote printing) has bad performance if multiple small-sized WritePrinter calls are performed. 1158 // We may increase performance by writing into a buffer and only doing a single RPC call when the buffer is full. 1159 1160 // Do the RPC call 1161 RpcTryExcept 1162 { 1163 dwErrorCode = _RpcWritePrinter(pHandle->hPrinter, pBuf, cbBuf, pcWritten); 1164 } 1165 RpcExcept(EXCEPTION_EXECUTE_HANDLER) 1166 { 1167 dwErrorCode = RpcExceptionCode(); 1168 ERR("_RpcWritePrinter failed with exception code %lu!\n", dwErrorCode); 1169 } 1170 RpcEndExcept; 1171 } 1172 1173 Cleanup: 1174 SetLastError(dwErrorCode); 1175 return (dwErrorCode == ERROR_SUCCESS); 1176 } 1177 1178 BOOL WINAPI 1179 XcvDataW(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded, PDWORD pdwStatus) 1180 { 1181 return FALSE; 1182 } 1183