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 Print Processors
5 * COPYRIGHT: Copyright 2015 Colin Finck <colin@reactos.org>
12 RTL_GENERIC_TABLE PrintProcessorTable
;
15 * @name _OpenEnvironment
17 * Checks a supplied pEnvironment variable for validity and opens its registry key.
20 * The pEnvironment variable to check. Can be NULL to use the current environment.
23 * On success, this variable will contain a HKEY to the opened registry key of the environment.
24 * You can use it for further tasks and have to close it with RegCloseKey.
27 * TRUE if the environment is valid and a registry key was opened, FALSE otherwise.
28 * In case of failure, Last Error will be set to ERROR_INVALID_ENVIRONMENT.
31 _OpenEnvironment(PCWSTR pEnvironment
, PHKEY hKey
)
33 const WCHAR wszEnvironmentsKey
[] = L
"SYSTEM\\CurrentControlSet\\Control\\Print\\Environments\\";
34 const DWORD cchEnvironmentsKey
= _countof(wszEnvironmentsKey
) - 1;
36 BOOL bReturnValue
= FALSE
;
39 PWSTR pwszEnvironmentKey
= NULL
;
41 // Use the current environment if none was supplied.
43 pEnvironment
= wszCurrentEnvironment
;
45 // Construct the registry key of the demanded environment.
46 cchEnvironment
= wcslen(pEnvironment
);
47 pwszEnvironmentKey
= DllAllocSplMem((cchEnvironmentsKey
+ cchEnvironment
+ 1) * sizeof(WCHAR
));
48 if (!pwszEnvironmentKey
)
50 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
54 CopyMemory(pwszEnvironmentKey
, wszEnvironmentsKey
, cchEnvironmentsKey
* sizeof(WCHAR
));
55 CopyMemory(&pwszEnvironmentKey
[cchEnvironmentsKey
], pEnvironment
, (cchEnvironment
+ 1) * sizeof(WCHAR
));
57 // Open the registry key.
58 lStatus
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
, pwszEnvironmentKey
, 0, KEY_READ
, hKey
);
59 if (lStatus
== ERROR_FILE_NOT_FOUND
)
61 SetLastError(ERROR_INVALID_ENVIRONMENT
);
64 else if (lStatus
!= ERROR_SUCCESS
)
66 ERR("RegOpenKeyExW failed with status %ld!\n", lStatus
);
73 if (pwszEnvironmentKey
)
74 DllFreeSplMem(pwszEnvironmentKey
);
80 * @name _PrinterTableCompareRoutine
82 * RTL_GENERIC_COMPARE_ROUTINE for the Print Processor Table.
83 * Does a case-insensitive comparison, because e.g. LocalEnumPrintProcessorDatatypes doesn't match the case when looking for Print Processors.
85 static RTL_GENERIC_COMPARE_RESULTS NTAPI
86 _PrintProcessorTableCompareRoutine(PRTL_GENERIC_TABLE Table
, PVOID FirstStruct
, PVOID SecondStruct
)
88 PLOCAL_PRINT_PROCESSOR A
= (PLOCAL_PRINT_PROCESSOR
)FirstStruct
;
89 PLOCAL_PRINT_PROCESSOR B
= (PLOCAL_PRINT_PROCESSOR
)SecondStruct
;
91 int iResult
= wcsicmp(A
->pwszName
, B
->pwszName
);
94 return GenericLessThan
;
96 return GenericGreaterThan
;
102 * @name _DatatypeTableCompareRoutine
104 * RTL_GENERIC_COMPARE_ROUTINE for the Datatype Table.
105 * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Datatypes.
107 static RTL_GENERIC_COMPARE_RESULTS NTAPI
108 _DatatypeTableCompareRoutine(PRTL_GENERIC_TABLE Table
, PVOID FirstStruct
, PVOID SecondStruct
)
110 PWSTR A
= (PWSTR
)FirstStruct
;
111 PWSTR B
= (PWSTR
)SecondStruct
;
113 int iResult
= wcsicmp(A
, B
);
116 return GenericLessThan
;
117 else if (iResult
> 0)
118 return GenericGreaterThan
;
124 * @name InitializePrintProcessorTable
126 * Initializes a RTL_GENERIC_TABLE of locally available Print Processors.
127 * The table is searchable by name and returns pointers to the functions of the loaded Print Processor DLL.
130 InitializePrintProcessorTable()
134 DWORD cchPrintProcessorPath
;
136 DWORD cchPrintProcessorName
;
141 HINSTANCE hinstPrintProcessor
;
144 HKEY hSubSubKey
= NULL
;
146 PDATATYPES_INFO_1W pDatatypesInfo1
= NULL
;
147 PLOCAL_PRINT_PROCESSOR pPrintProcessor
= NULL
;
148 PWSTR pwszDatatype
= NULL
;
149 PWSTR pwszPrintProcessorName
= NULL
;
150 WCHAR wszFileName
[MAX_PATH
];
151 WCHAR wszPrintProcessorPath
[MAX_PATH
];
153 // Initialize an empty table for our Print Processors.
154 // We will search it by Print Processor name.
155 RtlInitializeGenericTable(&PrintProcessorTable
, _PrintProcessorTableCompareRoutine
, GenericTableAllocateRoutine
, GenericTableFreeRoutine
, NULL
);
157 // Prepare the path to the Print Processor directory.
158 if (!LocalGetPrintProcessorDirectory(NULL
, NULL
, 1, (PBYTE
)wszPrintProcessorPath
, sizeof(wszPrintProcessorPath
), &cchPrintProcessorPath
))
161 cchPrintProcessorPath
/= sizeof(WCHAR
);
162 wszPrintProcessorPath
[cchPrintProcessorPath
++] = L
'\\';
164 // Open the environment registry key.
165 if (!_OpenEnvironment(NULL
, &hKey
))
168 // Open the "Print Processors" subkey.
169 lStatus
= RegOpenKeyExW(hKey
, L
"Print Processors", 0, KEY_READ
, &hSubKey
);
170 if (lStatus
!= ERROR_SUCCESS
)
172 ERR("RegOpenKeyExW failed with status %ld!\n", lStatus
);
176 // Get the number of Print Processors and maximum sub key length.
177 lStatus
= RegQueryInfoKeyW(hSubKey
, NULL
, NULL
, NULL
, &dwSubKeys
, &cchMaxSubKey
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
178 if (lStatus
!= ERROR_SUCCESS
)
180 ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus
);
184 // Allocate a temporary buffer for the Print Processor names.
185 pwszPrintProcessorName
= DllAllocSplMem((cchMaxSubKey
+ 1) * sizeof(WCHAR
));
186 if (!pwszPrintProcessorName
)
188 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
192 // Loop through all available local Print Processors.
193 for (i
= 0; i
< dwSubKeys
; i
++)
195 // Cleanup tasks from the previous run
198 RegCloseKey(hSubSubKey
);
204 if (pPrintProcessor
->pwszName
)
205 DllFreeSplStr(pPrintProcessor
->pwszName
);
207 DllFreeSplMem(pPrintProcessor
);
208 pPrintProcessor
= NULL
;
213 DllFreeSplMem(pDatatypesInfo1
);
214 pDatatypesInfo1
= NULL
;
217 // Get the name of this Print Processor.
218 cchPrintProcessorName
= cchMaxSubKey
;
219 lStatus
= RegEnumKeyExW(hSubKey
, i
, pwszPrintProcessorName
, &cchPrintProcessorName
, NULL
, NULL
, NULL
, NULL
);
220 if (lStatus
!= ERROR_SUCCESS
)
222 ERR("RegEnumKeyExW failed with status %ld!\n", lStatus
);
226 // Open this Print Processor's registry key.
227 lStatus
= RegOpenKeyExW(hSubKey
, pwszPrintProcessorName
, 0, KEY_READ
, &hSubSubKey
);
228 if (lStatus
!= ERROR_SUCCESS
)
230 ERR("RegOpenKeyExW failed for Print Processor \"%S\" with status %ld!\n", pwszPrintProcessorName
, lStatus
);
234 // Get the file name of the Print Processor.
235 cbFileName
= sizeof(wszFileName
);
236 lStatus
= RegQueryValueExW(hSubSubKey
, L
"Driver", NULL
, NULL
, (PBYTE
)wszFileName
, &cbFileName
);
237 if (lStatus
!= ERROR_SUCCESS
)
239 ERR("RegQueryValueExW failed for Print Processor \"%S\" with status %ld!\n", pwszPrintProcessorName
, lStatus
);
243 // Verify that our buffer is large enough.
244 if (cchPrintProcessorPath
+ cbFileName
/ sizeof(WCHAR
) > MAX_PATH
)
246 ERR("Print Processor directory \"%S\" for Print Processor \"%S\" is too long!\n", wszFileName
, pwszPrintProcessorName
);
250 // Construct the full path to the Print Processor.
251 CopyMemory(&wszPrintProcessorPath
[cchPrintProcessorPath
], wszFileName
, cbFileName
);
254 hinstPrintProcessor
= LoadLibraryW(wszPrintProcessorPath
);
255 if (lStatus
!= ERROR_SUCCESS
)
257 ERR("LoadLibraryW failed for \"%S\" with error %lu!\n", wszPrintProcessorPath
, GetLastError());
261 // Create a new LOCAL_PRINT_PROCESSOR structure for it.
262 pPrintProcessor
= DllAllocSplMem(sizeof(LOCAL_PRINT_PROCESSOR
));
263 pPrintProcessor
->pwszName
= AllocSplStr(pwszPrintProcessorName
);
265 // Get and verify all its function pointers.
266 pPrintProcessor
->pfnClosePrintProcessor
= (PClosePrintProcessor
)GetProcAddress(hinstPrintProcessor
, "ClosePrintProcessor");
267 if (!pPrintProcessor
->pfnClosePrintProcessor
)
269 ERR("Print Processor \"%S\" exports no ClosePrintProcessor!\n", wszPrintProcessorPath
);
273 pPrintProcessor
->pfnControlPrintProcessor
= (PControlPrintProcessor
)GetProcAddress(hinstPrintProcessor
, "ControlPrintProcessor");
274 if (!pPrintProcessor
->pfnControlPrintProcessor
)
276 ERR("Print Processor \"%S\" exports no ControlPrintProcessor!\n", wszPrintProcessorPath
);
280 pPrintProcessor
->pfnEnumPrintProcessorDatatypesW
= (PEnumPrintProcessorDatatypesW
)GetProcAddress(hinstPrintProcessor
, "EnumPrintProcessorDatatypesW");
281 if (!pPrintProcessor
->pfnEnumPrintProcessorDatatypesW
)
283 ERR("Print Processor \"%S\" exports no EnumPrintProcessorDatatypesW!\n", wszPrintProcessorPath
);
287 pPrintProcessor
->pfnGetPrintProcessorCapabilities
= (PGetPrintProcessorCapabilities
)GetProcAddress(hinstPrintProcessor
, "GetPrintProcessorCapabilities");
288 if (!pPrintProcessor
->pfnGetPrintProcessorCapabilities
)
290 ERR("Print Processor \"%S\" exports no GetPrintProcessorCapabilities!\n", wszPrintProcessorPath
);
294 pPrintProcessor
->pfnOpenPrintProcessor
= (POpenPrintProcessor
)GetProcAddress(hinstPrintProcessor
, "OpenPrintProcessor");
295 if (!pPrintProcessor
->pfnOpenPrintProcessor
)
297 ERR("Print Processor \"%S\" exports no OpenPrintProcessor!\n", wszPrintProcessorPath
);
301 pPrintProcessor
->pfnPrintDocumentOnPrintProcessor
= (PPrintDocumentOnPrintProcessor
)GetProcAddress(hinstPrintProcessor
, "PrintDocumentOnPrintProcessor");
302 if (!pPrintProcessor
->pfnPrintDocumentOnPrintProcessor
)
304 ERR("Print Processor \"%S\" exports no PrintDocumentOnPrintProcessor!\n", wszPrintProcessorPath
);
308 // Get all supported datatypes.
309 pPrintProcessor
->pfnEnumPrintProcessorDatatypesW(NULL
, NULL
, 1, NULL
, 0, &cbDatatypes
, &dwDatatypes
);
310 pDatatypesInfo1
= DllAllocSplMem(cbDatatypes
);
311 if (!pDatatypesInfo1
)
313 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
317 if (!pPrintProcessor
->pfnEnumPrintProcessorDatatypesW(NULL
, NULL
, 1, (PBYTE
)pDatatypesInfo1
, cbDatatypes
, &cbDatatypes
, &dwDatatypes
))
319 ERR("EnumPrintProcessorDatatypesW failed for Print Processor \"%S\" with error %lu!\n", wszPrintProcessorPath
, GetLastError());
323 // Add the supported datatypes to the datatype table.
324 RtlInitializeGenericTable(&pPrintProcessor
->DatatypeTable
, _DatatypeTableCompareRoutine
, GenericTableAllocateRoutine
, GenericTableFreeRoutine
, NULL
);
326 for (j
= 0; j
< dwDatatypes
; j
++)
328 pwszDatatype
= AllocSplStr(pDatatypesInfo1
->pName
);
330 if (!RtlInsertElementGenericTable(&pPrintProcessor
->DatatypeTable
, pDatatypesInfo1
->pName
, sizeof(PWSTR
), NULL
))
332 ERR("RtlInsertElementGenericTable failed for iteration %lu with error %lu!\n", j
, GetLastError());
339 // Add the Print Processor to the table.
340 if (!RtlInsertElementGenericTable(&PrintProcessorTable
, pPrintProcessor
, sizeof(LOCAL_PRINT_PROCESSOR
), NULL
))
342 ERR("RtlInsertElementGenericTable failed for iteration %lu with error %lu!\n", i
, GetLastError());
346 // Don't let the cleanup routines free this.
348 pPrintProcessor
= NULL
;
353 DllFreeSplStr(pwszDatatype
);
356 DllFreeSplMem(pDatatypesInfo1
);
360 if (pPrintProcessor
->pwszName
)
361 DllFreeSplStr(pPrintProcessor
->pwszName
);
363 DllFreeSplMem(pPrintProcessor
);
366 if (pwszPrintProcessorName
)
367 DllFreeSplStr(pwszPrintProcessorName
);
370 RegCloseKey(hSubSubKey
);
373 RegCloseKey(hSubKey
);
380 * @name LocalEnumPrintProcessorDatatypes
382 * Obtains an array of all datatypes supported by a particular Print Processor.
383 * Print Provider function for EnumPrintProcessorDatatypesA/EnumPrintProcessorDatatypesW.
386 * Server Name. Ignored here, because every caller of LocalEnumPrintProcessorDatatypes is interested in the local directory.
388 * @param pPrintProcessorName
389 * The (case-insensitive) name of the Print Processor to query.
392 * The level of the structure supplied through pDatatypes. This must be 1.
395 * Pointer to the buffer that receives an array of DATATYPES_INFO_1W structures.
396 * Can be NULL if you just want to know the required size of the buffer.
399 * Size of the buffer you supplied for pDatatypes, in bytes.
402 * Pointer to a variable that receives the required size of the buffer for pDatatypes, in bytes.
403 * This parameter mustn't be NULL!
406 * Pointer to a variable that receives the number of elements of the DATATYPES_INFO_1W array.
407 * This parameter mustn't be NULL!
410 * TRUE if we successfully copied the array into pDatatypes, FALSE otherwise.
411 * A more specific error code can be obtained through GetLastError.
414 LocalEnumPrintProcessorDatatypes(LPWSTR pName
, LPWSTR pPrintProcessorName
, DWORD Level
, LPBYTE pDatatypes
, DWORD cbBuf
, LPDWORD pcbNeeded
, LPDWORD pcReturned
)
416 PLOCAL_PRINT_PROCESSOR pPrintProcessor
;
421 SetLastError(ERROR_INVALID_LEVEL
);
425 // Try to find the Print Processor.
426 pPrintProcessor
= RtlLookupElementGenericTable(&PrintProcessorTable
, pPrintProcessorName
);
427 if (!pPrintProcessor
)
429 SetLastError(ERROR_UNKNOWN_PRINTPROCESSOR
);
433 // Call its EnumPrintProcessorDatatypesW function.
434 return pPrintProcessor
->pfnEnumPrintProcessorDatatypesW(pName
, pPrintProcessorName
, Level
, pDatatypes
, cbBuf
, pcbNeeded
, pcReturned
);
438 * @name LocalEnumPrintProcessors
440 * Obtains an array of all available Print Processors on this computer.
441 * Print Provider function for EnumPrintProcessorsA/EnumPrintProcessorsW.
444 * Server Name. Ignored here, because every caller of LocalEnumPrintProcessors is interested in the local directory.
446 * @param pEnvironment
447 * One of the predefined operating system and architecture "environment" strings (like "Windows NT x86").
448 * Alternatively, NULL to output the Print Processor directory of the current environment.
451 * The level of the structure supplied through pPrintProcessorInfo. This must be 1.
453 * @param pPrintProcessorInfo
454 * Pointer to the buffer that receives an array of PRINTPROCESSOR_INFO_1W structures.
455 * Can be NULL if you just want to know the required size of the buffer.
458 * Size of the buffer you supplied for pPrintProcessorInfo, in bytes.
461 * Pointer to a variable that receives the required size of the buffer for pPrintProcessorInfo, in bytes.
462 * This parameter mustn't be NULL!
465 * Pointer to a variable that receives the number of elements of the PRINTPROCESSOR_INFO_1W array.
466 * This parameter mustn't be NULL!
469 * TRUE if we successfully copied the array into pPrintProcessorInfo, FALSE otherwise.
470 * A more specific error code can be obtained through GetLastError.
473 LocalEnumPrintProcessors(LPWSTR pName
, LPWSTR pEnvironment
, DWORD Level
, LPBYTE pPrintProcessorInfo
, DWORD cbBuf
, LPDWORD pcbNeeded
, LPDWORD pcReturned
)
475 BOOL bReturnValue
= FALSE
;
477 DWORD cchPrintProcessor
;
482 PBYTE pCurrentOutputPrintProcessor
;
483 PBYTE pCurrentOutputPrintProcessorInfo
;
484 PRINTPROCESSOR_INFO_1W PrintProcessorInfo1
;
485 PWSTR pwszTemp
= NULL
;
490 SetLastError(ERROR_INVALID_LEVEL
);
494 if (!pcbNeeded
|| !pcReturned
)
496 // This error must be caught by RPC and returned as RPC_X_NULL_REF_POINTER.
497 ERR("pcbNeeded or pcReturned is NULL!\n");
501 // Verify pEnvironment and open its registry key.
502 if (!_OpenEnvironment(pEnvironment
, &hKey
))
505 // Open the "Print Processors" subkey.
506 lStatus
= RegOpenKeyExW(hKey
, L
"Print Processors", 0, KEY_READ
, &hSubKey
);
507 if (lStatus
!= ERROR_SUCCESS
)
509 ERR("RegOpenKeyExW failed with status %ld!\n", lStatus
);
513 // Get the number of Print Processors and maximum sub key length.
514 lStatus
= RegQueryInfoKeyW(hSubKey
, NULL
, NULL
, NULL
, pcReturned
, &cchMaxSubKey
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
515 if (lStatus
!= ERROR_SUCCESS
)
517 ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus
);
521 // Allocate a temporary buffer to let RegEnumKeyExW succeed.
522 pwszTemp
= DllAllocSplMem((cchMaxSubKey
+ 1) * sizeof(WCHAR
));
525 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
529 // Determine the required size of the output buffer.
532 for (i
= 0; i
< *pcReturned
; i
++)
534 // RegEnumKeyExW sucks! Unlike similar API functions, it only returns the actual numbers of characters copied when you supply a buffer large enough.
535 // So use pwszTemp with its size cchMaxSubKey for this.
536 cchPrintProcessor
= cchMaxSubKey
;
537 lStatus
= RegEnumKeyExW(hSubKey
, i
, pwszTemp
, &cchPrintProcessor
, NULL
, NULL
, NULL
, NULL
);
538 if (lStatus
!= ERROR_SUCCESS
)
540 ERR("RegEnumKeyExW failed with status %ld!\n", lStatus
);
544 *pcbNeeded
+= sizeof(PRINTPROCESSOR_INFO_1W
) + (cchPrintProcessor
+ 1) * sizeof(WCHAR
);
547 // Check if the supplied buffer is large enough.
548 if (cbBuf
< *pcbNeeded
)
550 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
554 // Put the Print Processor strings right after the last PRINTPROCESSOR_INFO_1W structure.
555 pCurrentOutputPrintProcessorInfo
= pPrintProcessorInfo
;
556 pCurrentOutputPrintProcessor
= pPrintProcessorInfo
+ *pcReturned
* sizeof(PRINTPROCESSOR_INFO_1W
);
558 // Copy over all Print Processors.
559 for (i
= 0; i
< *pcReturned
; i
++)
561 // This isn't really correct, but doesn't cause any harm, because we've extensively checked the size of the supplied buffer above.
562 cchPrintProcessor
= cchMaxSubKey
;
564 // Copy the Print Processor name.
565 lStatus
= RegEnumKeyExW(hSubKey
, i
, (PWSTR
)pCurrentOutputPrintProcessor
, &cchPrintProcessor
, NULL
, NULL
, NULL
, NULL
);
566 if (lStatus
!= ERROR_SUCCESS
)
568 ERR("RegEnumKeyExW failed with status %ld!\n", lStatus
);
572 // Fill and copy the PRINTPROCESSOR_INFO_1W structure belonging to this Print Processor.
573 PrintProcessorInfo1
.pName
= (PWSTR
)pCurrentOutputPrintProcessor
;
574 CopyMemory(pCurrentOutputPrintProcessorInfo
, &PrintProcessorInfo1
, sizeof(PRINTPROCESSOR_INFO_1W
));
576 // Advance to the next PRINTPROCESSOR_INFO_1W location and string location in the output buffer.
577 pCurrentOutputPrintProcessor
+= (cchPrintProcessor
+ 1) * sizeof(WCHAR
);
578 pCurrentOutputPrintProcessorInfo
+= sizeof(PRINTPROCESSOR_INFO_1W
);
581 // We've finished successfully!
582 SetLastError(ERROR_SUCCESS
);
587 DllFreeSplMem(pwszTemp
);
590 RegCloseKey(hSubKey
);
599 * @name LocalGetPrintProcessorDirectory
601 * Obtains the path to the local Print Processor directory.
602 * Print Provider function for GetPrintProcessorDirectoryA/GetPrintProcessorDirectoryW.
605 * Server Name. Ignored here, because every caller of LocalGetPrintProcessorDirectory is interested in the local directory.
607 * @param pEnvironment
608 * One of the predefined operating system and architecture "environment" strings (like "Windows NT x86").
609 * Alternatively, NULL to output the Print Processor directory of the current environment.
612 * The level of the (non-existing) structure supplied through pPrintProcessorInfo. This must be 1.
614 * @param pPrintProcessorInfo
615 * Pointer to the buffer that receives the full path to the Print Processor directory.
616 * Can be NULL if you just want to know the required size of the buffer.
619 * Size of the buffer you supplied for pPrintProcessorInfo, in bytes.
622 * Pointer to a variable that receives the required size of the buffer for pPrintProcessorInfo, in bytes.
623 * This parameter mustn't be NULL!
626 * TRUE if we successfully copied the directory into pPrintProcessorInfo, FALSE otherwise.
627 * A more specific error code can be obtained through GetLastError.
630 LocalGetPrintProcessorDirectory(LPWSTR pName
, LPWSTR pEnvironment
, DWORD Level
, LPBYTE pPrintProcessorInfo
, DWORD cbBuf
, LPDWORD pcbNeeded
)
632 const WCHAR wszPath
[] = L
"\\PRTPROCS\\";
633 const DWORD cchPath
= _countof(wszPath
) - 1;
635 BOOL bReturnValue
= FALSE
;
643 SetLastError(ERROR_INVALID_LEVEL
);
649 // This error must be caught by RPC and returned as RPC_X_NULL_REF_POINTER.
650 ERR("pcbNeeded is NULL!\n");
654 // Verify pEnvironment and open its registry key.
655 if (!_OpenEnvironment(pEnvironment
, &hKey
))
658 // Determine the size of the required buffer.
659 lStatus
= RegQueryValueExW(hKey
, L
"Directory", NULL
, NULL
, NULL
, pcbNeeded
);
660 if (lStatus
!= ERROR_SUCCESS
)
662 ERR("RegQueryValueExW failed with status %ld!\n", lStatus
);
666 *pcbNeeded
+= cchSpoolDirectory
;
667 *pcbNeeded
+= cchPath
;
669 // Is the supplied buffer large enough?
670 if (cbBuf
< *pcbNeeded
)
672 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
676 // Copy the path to the "prtprocs" directory into pPrintProcessorInfo
677 CopyMemory(pPrintProcessorInfo
, wszSpoolDirectory
, cchSpoolDirectory
* sizeof(WCHAR
));
678 CopyMemory(&pPrintProcessorInfo
[cchSpoolDirectory
], wszPath
, cchPath
* sizeof(WCHAR
));
680 // Get the directory name from the registry.
681 lStatus
= RegQueryValueExW(hKey
, L
"Directory", NULL
, NULL
, &pPrintProcessorInfo
[cchSpoolDirectory
+ cchPath
], &cbDataWritten
);
682 if (lStatus
!= ERROR_SUCCESS
)
684 ERR("RegQueryValueExW failed with status %ld!\n", lStatus
);
688 // We've finished successfully!
689 SetLastError(ERROR_SUCCESS
);