2 * PROJECT: ReactOS Local Spooler
3 * LICENSE: GNU LGPL v2.1 or any later version as published by the Free Software Foundation
4 * PURPOSE: Functions related to Printers and printing
5 * COPYRIGHT: Copyright 2015 Colin Finck <colin@reactos.org>
11 RTL_GENERIC_TABLE PrinterTable
;
15 * @name _PrinterTableCompareRoutine
17 * RTL_GENERIC_COMPARE_ROUTINE for the Printer Table.
18 * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Printers.
20 static RTL_GENERIC_COMPARE_RESULTS NTAPI
21 _PrinterTableCompareRoutine(PRTL_GENERIC_TABLE Table
, PVOID FirstStruct
, PVOID SecondStruct
)
23 PLOCAL_PRINTER A
= (PLOCAL_PRINTER
)FirstStruct
;
24 PLOCAL_PRINTER B
= (PLOCAL_PRINTER
)SecondStruct
;
26 int iResult
= wcsicmp(A
->pwszPrinterName
, B
->pwszPrinterName
);
29 return GenericLessThan
;
31 return GenericGreaterThan
;
37 * @name InitializePrinterTable
39 * Initializes a RTL_GENERIC_TABLE of locally available Printers.
40 * The table is searchable by name and returns information about the printers, including their job queues.
41 * During this process, the job queues are also initialized.
44 InitializePrinterTable()
46 const WCHAR wszPrintersKey
[] = L
"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers";
55 PLOCAL_PRINT_PROCESSOR pPrintProcessor
;
56 PLOCAL_PRINTER pPrinter
= NULL
;
57 PWSTR pwszPrintProcessor
= NULL
;
58 WCHAR wszPrinterName
[MAX_PRINTER_NAME
+ 1];
60 // Initialize an empty table for our printers.
61 // We will search it by printer name.
62 RtlInitializeGenericTable(&PrinterTable
, _PrinterTableCompareRoutine
, GenericTableAllocateRoutine
, GenericTableFreeRoutine
, NULL
);
64 // Open our printers registry key. Each subkey is a local printer there.
65 lStatus
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
, wszPrintersKey
, 0, KEY_READ
, &hKey
);
66 if (lStatus
!= ERROR_SUCCESS
)
68 ERR("RegOpenKeyExW failed with status %ld!\n", lStatus
);
72 // Get the number of subkeys.
73 lStatus
= RegQueryInfoKeyW(hKey
, NULL
, NULL
, NULL
, &dwSubKeys
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
74 if (lStatus
!= ERROR_SUCCESS
)
76 ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus
);
80 // Loop through all available local printers.
81 for (i
= 0; i
< dwSubKeys
; i
++)
83 // Cleanup tasks from the previous run
92 if (pPrinter
->pwszDefaultDatatype
)
93 DllFreeSplStr(pPrinter
->pwszDefaultDatatype
);
95 DllFreeSplMem(pPrinter
);
99 if (pwszPrintProcessor
)
101 DllFreeSplStr(pwszPrintProcessor
);
102 pwszPrintProcessor
= NULL
;
105 // Get the name of this printer.
106 cchPrinterName
= _countof(wszPrinterName
);
107 lStatus
= RegEnumKeyExW(hKey
, i
, wszPrinterName
, &cchPrinterName
, NULL
, NULL
, NULL
, NULL
);
108 if (lStatus
== ERROR_MORE_DATA
)
110 // This printer name exceeds the maximum length and is invalid.
113 else if (lStatus
!= ERROR_SUCCESS
)
115 ERR("RegEnumKeyExW failed for iteration %lu with status %ld!\n", i
, lStatus
);
119 // Open this Printer's registry key.
120 lStatus
= RegOpenKeyExW(hKey
, wszPrinterName
, 0, KEY_READ
, &hSubKey
);
121 if (lStatus
!= ERROR_SUCCESS
)
123 ERR("RegOpenKeyExW failed for Printer \"%S\" with status %ld!\n", wszPrinterName
, lStatus
);
127 // Get the Print Processor.
128 pwszPrintProcessor
= AllocAndRegQueryWSZ(hSubKey
, L
"Print Processor");
129 if (!pwszPrintProcessor
)
132 // Try to find it in the Print Processor Table.
133 pPrintProcessor
= RtlLookupElementGenericTable(&PrintProcessorTable
, pwszPrintProcessor
);
134 if (!pPrintProcessor
)
136 ERR("Invalid Print Processor \"%S\" for Printer \"%S\"!\n", pwszPrintProcessor
, wszPrinterName
);
140 // Create a new LOCAL_PRINTER structure for it.
141 pPrinter
= DllAllocSplMem(sizeof(LOCAL_PRINTER
));
144 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
148 pPrinter
->pwszPrinterName
= AllocSplStr(wszPrinterName
);
149 pPrinter
->pPrintProcessor
= pPrintProcessor
;
150 InitializeListHead(&pPrinter
->JobQueue
);
152 // Get the printer driver.
153 pPrinter
->pwszPrinterDriver
= AllocAndRegQueryWSZ(hSubKey
, L
"Printer Driver");
154 if (!pPrinter
->pwszPrinterDriver
)
157 // Get the description.
158 pPrinter
->pwszDescription
= AllocAndRegQueryWSZ(hSubKey
, L
"Description");
159 if (!pPrinter
->pwszDescription
)
162 // Get the default datatype.
163 pPrinter
->pwszDefaultDatatype
= AllocAndRegQueryWSZ(hSubKey
, L
"Datatype");
164 if (!pPrinter
->pwszDefaultDatatype
)
167 // Verify that it's valid.
168 if (!RtlLookupElementGenericTable(&pPrintProcessor
->DatatypeTable
, pPrinter
->pwszDefaultDatatype
))
170 ERR("Invalid default datatype \"%S\" for Printer \"%S\"!\n", pPrinter
->pwszDefaultDatatype
, wszPrinterName
);
174 // Get the default DevMode.
175 cbDevMode
= sizeof(DEVMODEW
);
176 lStatus
= RegQueryValueExW(hSubKey
, L
"Default DevMode", NULL
, NULL
, (PBYTE
)&pPrinter
->DefaultDevMode
, &cbDevMode
);
177 if (lStatus
!= ERROR_SUCCESS
|| cbDevMode
!= sizeof(DEVMODEW
))
179 ERR("Couldn't query DevMode for Printer \"%S\", status is %ld, cbDevMode is %lu!\n", wszPrinterName
, lStatus
, cbDevMode
);
183 // Add this printer to the printer table.
184 if (!RtlInsertElementGenericTable(&PrinterTable
, pPrinter
, sizeof(LOCAL_PRINTER
), NULL
))
186 ERR("RtlInsertElementGenericTable failed with error %lu!\n", GetLastError());
190 // Don't let the cleanup routines free this.
195 if (pwszPrintProcessor
)
196 DllFreeSplStr(pwszPrintProcessor
);
200 if (pPrinter
->pwszDefaultDatatype
)
201 DllFreeSplStr(pPrinter
->pwszDefaultDatatype
);
203 DllFreeSplMem(pPrinter
);
207 RegCloseKey(hSubKey
);
215 _LocalEnumPrintersLevel1(DWORD Flags
, LPWSTR Name
, LPBYTE pPrinterEnum
, DWORD cbBuf
, LPDWORD pcbNeeded
, LPDWORD pcReturned
)
217 const WCHAR wszComma
[] = L
",";
222 DWORD cchComputerName
= 0;
225 PBYTE pPrinterString
;
226 PLOCAL_PRINTER pPrinter
;
227 PRINTER_INFO_1W PrinterInfo1
;
228 PVOID pRestartKey
= NULL
;
229 WCHAR wszComputerName
[2 + MAX_COMPUTERNAME_LENGTH
+ 1 + 1];
231 DWORD dwOffsets
[] = {
232 FIELD_OFFSET(PRINTER_INFO_1W
, pName
),
233 FIELD_OFFSET(PRINTER_INFO_1W
, pDescription
),
234 FIELD_OFFSET(PRINTER_INFO_1W
, pComment
),
238 if (Flags
& PRINTER_ENUM_NAME
)
242 // The user supplied a Computer Name (with leading double backslashes) or Print Provider Name.
243 // Only process what's directed at us and dismiss every other request with ERROR_INVALID_NAME.
244 if (Name
[0] == L
'\\' && Name
[1] == L
'\\')
246 // Prepend slashes to the computer name.
247 wszComputerName
[0] = L
'\\';
248 wszComputerName
[1] = L
'\\';
250 // Get the local computer name for comparison.
251 cchComputerName
= MAX_COMPUTERNAME_LENGTH
+ 1;
252 if (!GetComputerNameW(&wszComputerName
[2], &cchComputerName
))
254 ERR("GetComputerNameW failed with error %lu!\n", GetLastError());
258 // Add the leading slashes to the total length.
259 cchComputerName
+= 2;
261 // Now compare this with the local computer name and reject if it doesn't match.
262 if (wcsicmp(&Name
[2], &wszComputerName
[2]) != 0)
264 SetLastError(ERROR_INVALID_NAME
);
268 // Add a trailing backslash to wszComputerName, which will later be prepended in front of the printer names.
269 wszComputerName
[cchComputerName
++] = L
'\\';
270 wszComputerName
[cchComputerName
] = 0;
272 else if (wcsicmp(Name
, wszPrintProviderInfo
[0]) != 0)
274 // The user supplied a name that cannot be processed by the local print provider.
275 SetLastError(ERROR_INVALID_NAME
);
281 // The caller wants information about this Print Provider.
282 // spoolss packs this into an array of information about all Print Providers.
283 *pcbNeeded
= sizeof(PRINTER_INFO_1W
);
285 for (i
= 0; i
< 3; i
++)
286 *pcbNeeded
+= (wcslen(wszPrintProviderInfo
[i
]) + 1) * sizeof(WCHAR
);
290 // Check if the supplied buffer is large enough.
291 if (cbBuf
< *pcbNeeded
)
293 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
297 // Copy over the print processor information.
298 ((PPRINTER_INFO_1W
)pPrinterEnum
)->Flags
= 0;
299 PackStrings(wszPrintProviderInfo
, pPrinterEnum
, dwOffsets
, &pPrinterEnum
[*pcbNeeded
]);
304 // Count the required buffer size and the number of printers.
305 for (pPrinter
= RtlEnumerateGenericTableWithoutSplaying(&PrinterTable
, &pRestartKey
); pPrinter
; pPrinter
= RtlEnumerateGenericTableWithoutSplaying(&PrinterTable
, &pRestartKey
))
307 // This looks wrong, but is totally right. PRINTER_INFO_1W has three members pName, pComment and pDescription.
308 // But pComment equals the "Description" registry value while pDescription is concatenated out of pName and pComment.
309 // On top of this, the computer name is prepended to the printer name if the user supplied the local computer name during the query.
310 cbName
= (wcslen(pPrinter
->pwszPrinterName
) + 1) * sizeof(WCHAR
);
311 cbComment
= (wcslen(pPrinter
->pwszDescription
) + 1) * sizeof(WCHAR
);
312 cbDescription
= cchComputerName
* sizeof(WCHAR
) + cbName
+ cbComment
+ sizeof(WCHAR
);
314 *pcbNeeded
+= sizeof(PRINTER_INFO_1W
) + cchComputerName
* sizeof(WCHAR
) + cbName
+ cbComment
+ cbDescription
;
318 // Check if the supplied buffer is large enough.
319 if (cbBuf
< *pcbNeeded
)
321 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
325 // Put the strings right after the last PRINTER_INFO_1W structure.
326 // Due to all the required string processing, we can't just use PackStrings here :(
327 pPrinterInfo
= pPrinterEnum
;
328 pPrinterString
= pPrinterEnum
+ *pcReturned
* sizeof(PRINTER_INFO_1W
);
330 // Copy over the printer information.
331 for (pPrinter
= RtlEnumerateGenericTableWithoutSplaying(&PrinterTable
, &pRestartKey
); pPrinter
; pPrinter
= RtlEnumerateGenericTableWithoutSplaying(&PrinterTable
, &pRestartKey
))
333 // FIXME: As for now, the Flags member returns no information.
334 PrinterInfo1
.Flags
= 0;
336 // Copy the printer name.
337 PrinterInfo1
.pName
= (PWSTR
)pPrinterString
;
338 CopyMemory(pPrinterString
, wszComputerName
, cchComputerName
* sizeof(WCHAR
));
339 pPrinterString
+= cchComputerName
* sizeof(WCHAR
);
340 cbName
= (wcslen(pPrinter
->pwszPrinterName
) + 1) * sizeof(WCHAR
);
341 CopyMemory(pPrinterString
, pPrinter
->pwszPrinterName
, cbName
);
342 pPrinterString
+= cbName
;
344 // Copy the printer comment (equals the "Description" registry value).
345 PrinterInfo1
.pComment
= (PWSTR
)pPrinterString
;
346 cbComment
= (wcslen(pPrinter
->pwszDescription
) + 1) * sizeof(WCHAR
);
347 CopyMemory(pPrinterString
, pPrinter
->pwszDescription
, cbComment
);
348 pPrinterString
+= cbComment
;
350 // Copy the description, which for PRINTER_INFO_1W has the form "Name,Comment,"
351 PrinterInfo1
.pDescription
= (PWSTR
)pPrinterString
;
352 CopyMemory(pPrinterString
, wszComputerName
, cchComputerName
* sizeof(WCHAR
));
353 pPrinterString
+= cchComputerName
* sizeof(WCHAR
);
354 CopyMemory(pPrinterString
, pPrinter
->pwszPrinterName
, cbName
- sizeof(WCHAR
));
355 pPrinterString
+= cbName
- sizeof(WCHAR
);
356 CopyMemory(pPrinterString
, wszComma
, sizeof(WCHAR
));
357 pPrinterString
+= sizeof(WCHAR
);
358 CopyMemory(pPrinterString
, pPrinter
->pwszDescription
, cbComment
- sizeof(WCHAR
));
359 pPrinterString
+= cbComment
- sizeof(WCHAR
);
360 CopyMemory(pPrinterString
, wszComma
, sizeof(wszComma
));
361 pPrinterString
+= sizeof(wszComma
);
363 // Finally copy the structure and advance to the next one in the output buffer.
364 CopyMemory(pPrinterInfo
, &PrinterInfo1
, sizeof(PRINTER_INFO_1W
));
365 pPrinterInfo
+= sizeof(PRINTER_INFO_1W
);
372 LocalEnumPrinters(DWORD Flags
, LPWSTR Name
, DWORD Level
, LPBYTE pPrinterEnum
, DWORD cbBuf
, LPDWORD pcbNeeded
, LPDWORD pcReturned
)
374 // Do no sanity checks here. This is verified by localspl_apitest!
381 SetLastError(ERROR_SUCCESS
);
383 if (Flags
& PRINTER_ENUM_LOCAL
)
385 // The function behaves quite differently for each level.
387 return _LocalEnumPrintersLevel1(Flags
, Name
, pPrinterEnum
, cbBuf
, pcbNeeded
, pcReturned
);
389 // TODO: Handle other levels.
391 // The caller supplied an invalid level.
395 // Treat it as success if the caller queried no information and we don't need to return any.
400 LocalOpenPrinter(PWSTR lpPrinterName
, HANDLE
* phPrinter
, PPRINTER_DEFAULTSW pDefault
)
402 BOOL bReturnValue
= ROUTER_UNKNOWN
;
403 DWORD cchComputerName
;
404 DWORD cchPrinterName
;
406 PWSTR p
= lpPrinterName
;
407 PWSTR pwszPrinterName
= NULL
;
409 PLOCAL_HANDLE pHandle
;
410 PLOCAL_PRINTER pPrinter
;
411 PLOCAL_PRINTER_HANDLE pPrinterHandle
= NULL
;
413 WCHAR wszComputerName
[MAX_COMPUTERNAME_LENGTH
+ 1];
416 if (!lpPrinterName
|| !phPrinter
)
418 SetLastError(ERROR_INVALID_PARAMETER
);
422 // Does lpPrinterName begin with two backslashes to indicate a server name?
423 if (lpPrinterName
[0] == L
'\\' && lpPrinterName
[1] == L
'\\')
425 // Skip these two backslashes.
428 // Look for the closing backslash.
429 p
= wcschr(lpPrinterName
, L
'\\');
432 // We didn't get a proper server name.
433 SetLastError(ERROR_INVALID_PRINTER_NAME
);
437 // Null-terminate the string here to enable comparison.
440 // Get the local computer name for comparison.
441 cchComputerName
= _countof(wszComputerName
);
442 if (!GetComputerNameW(wszComputerName
, &cchComputerName
))
444 ERR("GetComputerNameW failed with error %lu!\n", GetLastError());
448 // Now compare this with the local computer name and reject if it doesn't match, because this print provider only supports local printers.
449 if (wcsicmp(lpPrinterName
, wszComputerName
) != 0)
451 SetLastError(ERROR_INVALID_PRINTER_NAME
);
455 // We have checked the server name and don't need it anymore.
456 lpPrinterName
= p
+ 1;
459 // Look for a comma. If it exists, it indicates the end of the printer name.
460 p
= wcschr(lpPrinterName
, L
',');
462 cchPrinterName
= p
- lpPrinterName
;
464 cchPrinterName
= wcslen(lpPrinterName
);
466 // No printer name and no comma? This is invalid!
467 if (!cchPrinterName
&& !p
)
469 SetLastError(ERROR_INVALID_PRINTER_NAME
);
473 // Do we have a printer name?
477 pwszPrinterName
= DllAllocSplMem((cchPrinterName
+ 1) * sizeof(WCHAR
));
478 CopyMemory(pwszPrinterName
, lpPrinterName
, cchPrinterName
* sizeof(WCHAR
));
479 pwszPrinterName
[cchPrinterName
] = 0;
481 // Retrieve the associated printer from the table.
482 pPrinter
= RtlLookupElementGenericTable(&PrinterTable
, pwszPrinterName
);
485 // The printer does not exist.
486 SetLastError(ERROR_INVALID_PRINTER_NAME
);
490 // Create a new printer handle.
491 pPrinterHandle
= DllAllocSplMem(sizeof(LOCAL_PRINTER_HANDLE
));
492 pPrinterHandle
->Printer
= pPrinter
;
494 // Check if a datatype was given.
495 if (pDefault
&& pDefault
->pDatatype
)
497 // Use the datatype if it's valid.
498 if (!RtlLookupElementGenericTable(&pPrinter
->pPrintProcessor
->DatatypeTable
, pDefault
->pDatatype
))
500 SetLastError(ERROR_INVALID_DATATYPE
);
504 pPrinterHandle
->pwszDatatype
= AllocSplStr(pDefault
->pDatatype
);
508 // Use the default datatype.
509 pPrinterHandle
->pwszDatatype
= AllocSplStr(pPrinter
->pwszDefaultDatatype
);
512 // Check if a DevMode was given, otherwise use the default.
513 if (pDefault
&& pDefault
->pDevMode
)
514 CopyMemory(&pPrinterHandle
->DevMode
, pDefault
->pDevMode
, sizeof(DEVMODEW
));
516 CopyMemory(&pPrinterHandle
->DevMode
, &pPrinter
->DefaultDevMode
, sizeof(DEVMODEW
));
518 // Did we have a comma? Then the user may want a handle to an existing job instead of creating a new job.
530 // The "Job " string has to follow now.
531 if (wcscmp(p
, L
"Job ") != 0)
533 SetLastError(ERROR_INVALID_PRINTER_NAME
);
537 // Skip the "Job " string.
538 p
+= sizeof("Job ") - 1;
540 // Skip even more whitespace.
544 // Finally extract the desired Job ID.
545 dwJobID
= wcstoul(p
, NULL
, 10);
546 if (!IS_VALID_JOB_ID(dwJobID
))
548 // The user supplied an invalid Job ID.
549 SetLastError(ERROR_INVALID_PRINTER_NAME
);
553 // Look for this job in the job queue of the printer.
554 pEntry
= pPrinter
->JobQueue
.Flink
;
558 if (pEntry
== &pPrinter
->JobQueue
)
560 // We have reached the end of the list without finding the desired Job ID.
561 SetLastError(ERROR_INVALID_PRINTER_NAME
);
565 // Get our job structure.
566 pJob
= CONTAINING_RECORD(pEntry
, LOCAL_JOB
, Entry
);
568 if (pJob
->dwJobID
== dwJobID
)
570 // We have found the desired job. Give the caller a printer handle referencing it.
571 pPrinterHandle
->StartedJob
= pJob
;
575 pEntry
= pEntry
->Flink
;
579 // Create a new handle that references a printer.
580 pHandle
= DllAllocSplMem(sizeof(LOCAL_HANDLE
));
581 pHandle
->HandleType
= Printer
;
582 pHandle
->SpecificHandle
= pPrinterHandle
;
586 // No printer name, but we have a comma!
587 // This may be a request to a XcvMonitor or XcvPort handle.
597 // Check if this is a request to a XcvMonitor.
598 if (wcscmp(p
, L
"XcvMonitor ") == 0)
600 // Skip the "XcvMonitor " string.
601 p
+= sizeof("XcvMonitor ") - 1;
603 ///////////// TODO /////////////////////
604 pHandle
= DllAllocSplMem(sizeof(LOCAL_HANDLE
));
605 pHandle
->HandleType
= Monitor
;
606 //pHandle->SpecificHandle = pMonitorHandle;
608 else if (wcscmp(p
, L
"XcvPort ") == 0)
610 // Skip the "XcvPort " string.
611 p
+= sizeof("XcvPort ") - 1;
613 //////////// TODO //////////////////////
614 pHandle
= DllAllocSplMem(sizeof(LOCAL_HANDLE
));
615 pHandle
->HandleType
= Port
;
616 //pHandle->SpecificHandle = pPortHandle;
620 SetLastError(ERROR_INVALID_PRINTER_NAME
);
625 *phPrinter
= (HANDLE
)pHandle
;
626 bReturnValue
= ROUTER_SUCCESS
;
628 // Don't let the cleanup routines free this.
629 pPrinterHandle
= NULL
;
630 pwszPrinterName
= NULL
;
635 if (pPrinterHandle
->pwszDatatype
)
636 DllFreeSplStr(pPrinterHandle
->pwszDatatype
);
638 DllFreeSplMem(pPrinterHandle
);
642 DllFreeSplMem(pwszPrinterName
);
648 LocalStartDocPrinter(HANDLE hPrinter
, DWORD Level
, LPBYTE pDocInfo
)
650 DWORD dwReturnValue
= 0;
651 PDOC_INFO_1W pDocumentInfo1
;
652 PLOCAL_HANDLE pHandle
;
653 PLOCAL_PRINTER_HANDLE pPrinterHandle
;
659 SetLastError(ERROR_INVALID_PARAMETER
);
665 SetLastError(ERROR_INVALID_HANDLE
);
669 // Check if this is a printer handle.
670 pHandle
= (PLOCAL_HANDLE
)hPrinter
;
671 if (pHandle
->HandleType
!= Printer
)
673 SetLastError(ERROR_INVALID_HANDLE
);
677 pPrinterHandle
= (PLOCAL_PRINTER_HANDLE
)pHandle
->SpecificHandle
;
679 // Check if this is the right document information level.
682 SetLastError(ERROR_INVALID_LEVEL
);
686 pDocumentInfo1
= (PDOC_INFO_1W
)pDocInfo
;
689 pJob
= DllAllocSplMem(sizeof(LOCAL_JOB
));
690 pJob
->Printer
= pPrinterHandle
->Printer
;
692 // Check if a datatype was given.
693 if (pDocumentInfo1
->pDatatype
)
695 // Use the datatype if it's valid.
696 if (!RtlLookupElementGenericTable(&pJob
->Printer
->pPrintProcessor
->DatatypeTable
, pDocumentInfo1
->pDatatype
))
698 SetLastError(ERROR_INVALID_DATATYPE
);
702 pJob
->pwszDatatype
= AllocSplStr(pDocumentInfo1
->pDatatype
);
706 // Use the printer handle datatype.
707 pJob
->pwszDatatype
= AllocSplStr(pPrinterHandle
->pwszDatatype
);
710 // Copy over printer defaults.
711 CopyMemory(&pJob
->DevMode
, &pPrinterHandle
->DevMode
, sizeof(DEVMODEW
));
713 // Copy over supplied information.
714 if (pDocumentInfo1
->pDocName
)
715 pJob
->pwszDocumentName
= AllocSplStr(pDocumentInfo1
->pDocName
);
717 if (pDocumentInfo1
->pOutputFile
)
718 pJob
->pwszOutputFile
= AllocSplStr(pDocumentInfo1
->pOutputFile
);
721 ///////////// TODO /////////////////////
727 return dwReturnValue
;
731 LocalStartPagePrinter(HANDLE hPrinter
)
733 ///////////// TODO /////////////////////
738 LocalWritePrinter(HANDLE hPrinter
, LPVOID pBuf
, DWORD cbBuf
, LPDWORD pcWritten
)
740 ///////////// TODO /////////////////////
745 LocalEndPagePrinter(HANDLE hPrinter
)
747 ///////////// TODO /////////////////////
752 LocalEndDocPrinter(HANDLE hPrinter
)
754 ///////////// TODO /////////////////////
759 LocalClosePrinter(HANDLE hPrinter
)
761 PLOCAL_HANDLE pHandle
;
765 SetLastError(ERROR_INVALID_HANDLE
);
769 pHandle
= (PLOCAL_HANDLE
)hPrinter
;
771 ///////////// TODO /////////////////////
772 /// Check the handle type, do thoroughful checks on all data fields and clean them.
773 ////////////////////////////////////////
775 DllFreeSplMem(pHandle
);