You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1409 lines
36 KiB
1409 lines
36 KiB
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
oemui.c
|
|
|
|
Abstract:
|
|
|
|
Support for OEM plugin UI modules
|
|
|
|
Environment:
|
|
|
|
Windows NT printer driver
|
|
|
|
Revision History:
|
|
|
|
02/13/97 -davidx-
|
|
Created it.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
|
|
//
|
|
// User mode helper functions for OEM plugins
|
|
//
|
|
|
|
const OEMUIPROCS OemUIHelperFuncs = {
|
|
(PFN_DrvGetDriverSetting) BGetDriverSettingForOEM,
|
|
(PFN_DrvUpdateUISetting) BUpdateUISettingForOEM,
|
|
};
|
|
|
|
|
|
|
|
BOOL
|
|
BPackOemPluginItems(
|
|
PUIDATA pUiData
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call OEM plugin UI modules to let them add their OPTITEMs
|
|
|
|
Arguments:
|
|
|
|
pUiData - Points to UIDATA structure
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
PCOMMONINFO pci;
|
|
PFN_OEMCommonUIProp pfnOEMCommonUIProp;
|
|
POEMCUIPPARAM pOemCUIPParam;
|
|
POPTITEM pOptItem;
|
|
DWORD dwOptItem;
|
|
|
|
//
|
|
// Check if we're being called for the first time.
|
|
// We assume all OEM plugin items are always packed at the end.
|
|
//
|
|
|
|
if (pUiData->pOptItem == NULL)
|
|
pUiData->dwDrvOptItem = pUiData->dwOptItem;
|
|
else if (pUiData->dwDrvOptItem != pUiData->dwOptItem)
|
|
{
|
|
RIP(("Inconsistent OPTITEM count for driver items\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Quick exit for no OEM plugin case
|
|
//
|
|
|
|
pci = (PCOMMONINFO) pUiData;
|
|
|
|
if (pci->pOemPlugins->dwCount == 0)
|
|
return TRUE;
|
|
|
|
pOptItem = pUiData->pOptItem;
|
|
|
|
FOREACH_OEMPLUGIN_LOOP(pci)
|
|
|
|
if (!HAS_COM_INTERFACE(pOemEntry) &&
|
|
!(pfnOEMCommonUIProp = GET_OEM_ENTRYPOINT(pOemEntry, OEMCommonUIProp)))
|
|
continue;
|
|
|
|
//
|
|
// Compose the input parameter for calling OEMCommonUI
|
|
//
|
|
|
|
pOemCUIPParam = pOemEntry->pParam;
|
|
|
|
if (pOemCUIPParam == NULL)
|
|
{
|
|
//
|
|
// Allocate memory for an OEMUI_PARAM structure
|
|
// during the first pass
|
|
//
|
|
|
|
if (pOptItem != NULL)
|
|
continue;
|
|
|
|
if (! (pOemCUIPParam = HEAPALLOC(pci->hHeap, sizeof(OEMCUIPPARAM))))
|
|
{
|
|
ERR(("Memory allocation failed\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
pOemEntry->pParam = pOemCUIPParam;
|
|
pOemCUIPParam->cbSize = sizeof(OEMCUIPPARAM);
|
|
pOemCUIPParam->poemuiobj = pci->pOemPlugins->pdriverobj;
|
|
pOemCUIPParam->hPrinter = pci->hPrinter;
|
|
pOemCUIPParam->pPrinterName = pci->pPrinterName;
|
|
pOemCUIPParam->hModule = pOemEntry->hInstance;
|
|
pOemCUIPParam->hOEMHeap = pci->hHeap;
|
|
pOemCUIPParam->pPublicDM = pci->pdm;
|
|
pOemCUIPParam->pOEMDM = pOemEntry->pOEMDM;
|
|
}
|
|
|
|
pOemCUIPParam->pDrvOptItems = pUiData->pDrvOptItem;
|
|
pOemCUIPParam->cDrvOptItems = pUiData->dwDrvOptItem;
|
|
pOemCUIPParam->pOEMOptItems = pOptItem;
|
|
dwOptItem = pOemCUIPParam->cOEMOptItems;
|
|
|
|
//
|
|
// Actually call OEMCommonUI entrypoint
|
|
//
|
|
|
|
if (HAS_COM_INTERFACE(pOemEntry))
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = HComOEMCommonUIProp(
|
|
pOemEntry,
|
|
(pUiData->iMode == MODE_DOCUMENT_STICKY) ? OEMCUIP_DOCPROP : OEMCUIP_PRNPROP,
|
|
pOemCUIPParam);
|
|
|
|
if (hr == E_NOTIMPL)
|
|
{
|
|
HeapFree(pci->hHeap, 0, pOemCUIPParam);
|
|
pOemEntry->pParam = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ERR(("OEMCommonUI failed for '%ws': %d\n",
|
|
CURRENT_OEM_MODULE_NAME(pOemEntry),
|
|
GetLastError()));
|
|
|
|
//
|
|
// OEM failure during the first pass is recoverable:
|
|
// we'll simply ignore OEM plugin items
|
|
//
|
|
|
|
if (pOptItem == NULL)
|
|
{
|
|
HeapFree(pci->hHeap, 0, pOemCUIPParam);
|
|
pOemEntry->pParam = NULL;
|
|
continue;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!pfnOEMCommonUIProp(
|
|
(pUiData->iMode == MODE_DOCUMENT_STICKY) ? OEMCUIP_DOCPROP : OEMCUIP_PRNPROP,
|
|
pOemCUIPParam))
|
|
{
|
|
ERR(("OEMCommonUI failed for '%ws': %d\n",
|
|
CURRENT_OEM_MODULE_NAME(pOemEntry),
|
|
GetLastError()));
|
|
#if 0
|
|
(VOID) IDisplayErrorMessageBox(
|
|
NULL,
|
|
0,
|
|
IDS_OEMERR_DLGTITLE,
|
|
IDS_OEMERR_OPTITEM,
|
|
CURRENT_OEM_MODULE_NAME(pOemEntry));
|
|
#endif
|
|
//
|
|
// OEM failure during the first pass is recoverable:
|
|
// we'll simply ignore OEM plugin items
|
|
//
|
|
|
|
if (pOptItem == NULL)
|
|
{
|
|
HeapFree(pci->hHeap, 0, pOemCUIPParam);
|
|
pOemEntry->pParam = NULL;
|
|
continue;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (pOptItem != NULL)
|
|
{
|
|
//
|
|
// second pass - ensure the number of items is consistent
|
|
//
|
|
|
|
if (dwOptItem != pOemCUIPParam->cOEMOptItems)
|
|
{
|
|
RIP(("Inconsistent OPTITEM count reported by OEM plugin: %ws\n",
|
|
CURRENT_OEM_MODULE_NAME(pOemEntry),
|
|
GetLastError()));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
pOptItem += pOemCUIPParam->cOEMOptItems;
|
|
pUiData->pOptItem += pOemCUIPParam->cOEMOptItems;
|
|
}
|
|
|
|
pUiData->dwOptItem += pOemCUIPParam->cOEMOptItems;
|
|
|
|
END_OEMPLUGIN_LOOP
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
LONG
|
|
LInvokeOemPluginCallbacks(
|
|
PUIDATA pUiData,
|
|
PCPSUICBPARAM pCallbackParam,
|
|
LONG lRet
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call OEM plugin module's callback function
|
|
|
|
Arguments:
|
|
|
|
pUiData - Points to UIDATA structure
|
|
pCallbackParam - Points to callback parameter from compstui
|
|
lRet - Return value after the driver has processed the callback
|
|
|
|
Return Value:
|
|
|
|
Return value for compstui
|
|
|
|
--*/
|
|
|
|
{
|
|
PCOMMONINFO pci = (PCOMMONINFO) pUiData;
|
|
POEMCUIPPARAM pOemCUIPParam;
|
|
LONG lNewResult;
|
|
|
|
//
|
|
// Quick exit for no OEM plugin case
|
|
//
|
|
|
|
if (pci->pOemPlugins->dwCount == 0)
|
|
return lRet;
|
|
|
|
//
|
|
// Go through each OEM plugin UI module
|
|
//
|
|
|
|
FOREACH_OEMPLUGIN_LOOP(pci)
|
|
|
|
//
|
|
// Stop when anyone says don't exit
|
|
//
|
|
|
|
if (lRet == CPSUICB_ACTION_NO_APPLY_EXIT)
|
|
{
|
|
ASSERT(pCallbackParam->Reason == CPSUICB_REASON_APPLYNOW);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Get the address of OEM callback function and call it
|
|
//
|
|
|
|
pOemCUIPParam = pOemEntry->pParam;
|
|
|
|
if (pOemCUIPParam == NULL || pOemCUIPParam->OEMCUIPCallback == NULL)
|
|
continue;
|
|
|
|
lNewResult = pOemCUIPParam->OEMCUIPCallback(pCallbackParam, pOemCUIPParam);
|
|
|
|
//
|
|
// Merge the new result with the existing result
|
|
//
|
|
|
|
switch (lNewResult)
|
|
{
|
|
case CPSUICB_ACTION_ITEMS_APPLIED:
|
|
case CPSUICB_ACTION_NO_APPLY_EXIT:
|
|
|
|
ASSERT(pCallbackParam->Reason == CPSUICB_REASON_APPLYNOW);
|
|
lRet = lNewResult;
|
|
break;
|
|
|
|
case CPSUICB_ACTION_REINIT_ITEMS:
|
|
|
|
ASSERT(pCallbackParam->Reason != CPSUICB_REASON_APPLYNOW);
|
|
lRet = lNewResult;
|
|
break;
|
|
|
|
case CPSUICB_ACTION_OPTIF_CHANGED:
|
|
|
|
ASSERT(pCallbackParam->Reason != CPSUICB_REASON_APPLYNOW);
|
|
if (lRet == CPSUICB_ACTION_NONE)
|
|
lRet = lNewResult;
|
|
break;
|
|
|
|
case CPSUICB_ACTION_NONE:
|
|
break;
|
|
|
|
default:
|
|
|
|
RIP(("Invalid return value from OEM callback: '%ws'\n",
|
|
CURRENT_OEM_MODULE_NAME(pOemEntry),
|
|
GetLastError()));
|
|
break;
|
|
}
|
|
|
|
END_OEMPLUGIN_LOOP
|
|
|
|
return lRet;
|
|
}
|
|
|
|
|
|
LRESULT
|
|
OEMDocumentPropertySheets(
|
|
PPROPSHEETUI_INFO pPSUIInfo,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
POEM_PLUGIN_ENTRY pOemEntry;
|
|
|
|
pOemEntry = ((POEMUIPSPARAM)(pPSUIInfo->lParamInit))->pOemEntry;
|
|
|
|
hr = HComOEMDocumentPropertySheets(pOemEntry,
|
|
pPSUIInfo,
|
|
lParam);
|
|
|
|
if (SUCCEEDED(hr))
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
LRESULT
|
|
OEMDevicePropertySheets(
|
|
PPROPSHEETUI_INFO pPSUIInfo,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
POEM_PLUGIN_ENTRY pOemEntry;
|
|
|
|
pOemEntry = ((POEMUIPSPARAM)(pPSUIInfo->lParamInit))->pOemEntry;
|
|
|
|
hr = HComOEMDevicePropertySheets(pOemEntry,
|
|
pPSUIInfo,
|
|
lParam);
|
|
|
|
if (SUCCEEDED(hr))
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
BOOL
|
|
BAddOemPluginPages(
|
|
PUIDATA pUiData,
|
|
DWORD dwFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call OEM plugin UI modules to let them add their own property sheet pages
|
|
|
|
Arguments:
|
|
|
|
pUiData - Points to UIDATA structure
|
|
dwFlags - Flags from DOCUMENTPROPERTYHEADER or DEVICEPROPERTYHEADER
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
PCOMMONINFO pci = (PCOMMONINFO) pUiData;
|
|
FARPROC pfnOEMPropertySheets;
|
|
POEMUIPSPARAM pOemUIPSParam;
|
|
|
|
//
|
|
// Quick exit for no OEM plugin case
|
|
//
|
|
|
|
if (pci->pOemPlugins->dwCount == 0)
|
|
return TRUE;
|
|
|
|
//
|
|
// Add the property sheet for each OEM plugin UI module
|
|
//
|
|
|
|
FOREACH_OEMPLUGIN_LOOP(pci)
|
|
|
|
//
|
|
// get the address of appropriate OEM entrypoint
|
|
//
|
|
|
|
|
|
if (HAS_COM_INTERFACE(pOemEntry))
|
|
{
|
|
if (pUiData->iMode == MODE_DOCUMENT_STICKY)
|
|
pfnOEMPropertySheets = (FARPROC)OEMDocumentPropertySheets;
|
|
else
|
|
pfnOEMPropertySheets = (FARPROC)OEMDevicePropertySheets;
|
|
}
|
|
else
|
|
{
|
|
if (pUiData->iMode == MODE_DOCUMENT_STICKY)
|
|
{
|
|
pfnOEMPropertySheets = (FARPROC)
|
|
GET_OEM_ENTRYPOINT(pOemEntry, OEMDocumentPropertySheets);
|
|
}
|
|
else
|
|
{
|
|
pfnOEMPropertySheets = (FARPROC)
|
|
GET_OEM_ENTRYPOINT(pOemEntry, OEMDevicePropertySheets);
|
|
}
|
|
|
|
if (pfnOEMPropertySheets == NULL)
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Collect input parameters to be passed to OEM plugin
|
|
//
|
|
|
|
if ((pOemUIPSParam = HEAPALLOC(pci->hHeap, sizeof(OEMUIPSPARAM))) == NULL)
|
|
{
|
|
ERR(("Memory allocation failed\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
pOemUIPSParam->cbSize = sizeof(OEMUIPSPARAM);
|
|
pOemUIPSParam->poemuiobj = pci->pOemPlugins->pdriverobj;
|
|
pOemUIPSParam->hPrinter = pci->hPrinter;
|
|
pOemUIPSParam->pPrinterName = pci->pPrinterName;
|
|
pOemUIPSParam->hModule = pOemEntry->hInstance;
|
|
pOemUIPSParam->hOEMHeap = pci->hHeap;
|
|
pOemUIPSParam->pPublicDM = pci->pdm;
|
|
pOemUIPSParam->pOEMDM = pOemEntry->pOEMDM;
|
|
pOemUIPSParam->dwFlags = dwFlags;
|
|
pOemUIPSParam->pOemEntry = pOemEntry;
|
|
|
|
//
|
|
// call compstui to add the OEM plugin property sheets
|
|
//
|
|
|
|
if (pUiData->pfnComPropSheet(pUiData->hComPropSheet,
|
|
CPSFUNC_ADD_PFNPROPSHEETUI,
|
|
(LPARAM) pfnOEMPropertySheets,
|
|
(LPARAM) pOemUIPSParam) <= 0)
|
|
{
|
|
VERBOSE(("Couldn't add property sheet pages for '%ws'\n",
|
|
CURRENT_OEM_MODULE_NAME(pOemEntry),
|
|
GetLastError()));
|
|
}
|
|
|
|
END_OEMPLUGIN_LOOP
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
APIENTRY
|
|
BGetDriverSettingForOEM(
|
|
PCOMMONINFO pci,
|
|
PCSTR pFeatureKeyword,
|
|
PVOID pOutput,
|
|
DWORD cbSize,
|
|
PDWORD pcbNeeded,
|
|
PDWORD pdwOptionsReturned
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Provide OEM plugins access to driver private settings
|
|
|
|
Arguments:
|
|
|
|
pci - Points to basic printer information
|
|
pFeatureKeyword - Specifies the keyword the caller is interested in
|
|
pOutput - Points to output buffer
|
|
cbSize - Size of output buffer
|
|
pcbNeeded - Returns the expected size of output buffer
|
|
pdwOptionsReturned - Returns the number of options selected
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG_PTR dwIndex;
|
|
BOOL bResult;
|
|
|
|
ASSERT(pci->pvStartSign == pci);
|
|
|
|
if ((pci == NULL) || (pci->pvStartSign != pci))
|
|
{
|
|
WARNING(("BGetDriverSettingForOEM: invalid pci"));
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// This is not very portable: If the pointer value for pFeatureKeyword
|
|
// is less than 0x10000, we assume that the pointer value actually
|
|
// specifies a predefined index.
|
|
//
|
|
|
|
//
|
|
// Following ASSERT is removed for Win64
|
|
//
|
|
// ASSERT(sizeof(pFeatureKeyword) == sizeof(DWORD));
|
|
//
|
|
|
|
dwIndex = (ULONG_PTR)pFeatureKeyword;
|
|
|
|
if (dwIndex >= OEMGDS_MIN_DOCSTICKY && dwIndex < OEMGDS_MIN_PRINTERSTICKY)
|
|
{
|
|
if (pci->pdm == NULL)
|
|
goto setting_not_available;
|
|
|
|
bResult = BGetDevmodeSettingForOEM(
|
|
pci->pdm,
|
|
(DWORD)dwIndex,
|
|
pOutput,
|
|
cbSize,
|
|
pcbNeeded);
|
|
|
|
if (bResult)
|
|
*pdwOptionsReturned = 1;
|
|
}
|
|
else if (dwIndex >= OEMGDS_MIN_PRINTERSTICKY && dwIndex < OEMGDS_MAX)
|
|
{
|
|
if (pci->pPrinterData == NULL)
|
|
goto setting_not_available;
|
|
|
|
bResult = BGetPrinterDataSettingForOEM(
|
|
pci->pPrinterData,
|
|
(DWORD)dwIndex,
|
|
pOutput,
|
|
cbSize,
|
|
pcbNeeded);
|
|
|
|
if (bResult)
|
|
*pdwOptionsReturned = 1;
|
|
}
|
|
else
|
|
{
|
|
if (pci->pCombinedOptions == NULL)
|
|
goto setting_not_available;
|
|
|
|
bResult = BGetGenericOptionSettingForOEM(
|
|
pci->pUIInfo,
|
|
pci->pCombinedOptions,
|
|
pFeatureKeyword,
|
|
pOutput,
|
|
cbSize,
|
|
pcbNeeded,
|
|
pdwOptionsReturned);
|
|
}
|
|
|
|
return bResult;
|
|
|
|
setting_not_available:
|
|
|
|
WARNING(("Requested driver setting not available: %d\n", pFeatureKeyword));
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
BUpdateUISettingForOEM(
|
|
PCOMMONINFO pci,
|
|
PVOID pOptItem,
|
|
DWORD dwPreviousSelection,
|
|
DWORD dwMode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Update the UI settings in optionsarray for OEM.
|
|
|
|
Arguments:
|
|
|
|
pci - Points to basic printer information
|
|
pOptItem - Points to the current OPTITEM
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error such as conflict and
|
|
user wants to cancel.
|
|
|
|
--*/
|
|
|
|
{
|
|
POPTITEM pCurItem = pOptItem;
|
|
PUIDATA pUiData = (PUIDATA)pci;
|
|
|
|
ASSERT(pci->pvStartSign == pci);
|
|
|
|
if ((pci == NULL) || (pci->pvStartSign != pci))
|
|
{
|
|
WARNING(("BUpdateUISettingForOEM: invalid pci"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (ICheckConstraintsDlg(pUiData, pCurItem, 1, FALSE) == CONFLICT_CANCEL)
|
|
{
|
|
//
|
|
// If there is a conflict and the user clicked
|
|
// CANCEL to restore the original selection.
|
|
// CONFLICT_CANCEL, restore the old setting
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (dwMode == OEMCUIP_DOCPROP)
|
|
{
|
|
//
|
|
// We use FLAG_WITHIN_PLUGINCALL to indicate we are within the UI helper
|
|
// function call issued by OEM plugin. This is needed to fix bug #90923.
|
|
//
|
|
|
|
pUiData->ci.dwFlags |= FLAG_WITHIN_PLUGINCALL;
|
|
VUnpackDocumentPropertiesItems(pUiData, pCurItem, 1);
|
|
pUiData->ci.dwFlags &= ~FLAG_WITHIN_PLUGINCALL;
|
|
|
|
VPropShowConstraints(pUiData, MODE_DOCANDPRINTER_STICKY);
|
|
}
|
|
else
|
|
{
|
|
VUpdateOptionsArrayWithSelection(pUiData, pCurItem);
|
|
VPropShowConstraints(pUiData, MODE_PRINTER_STICKY);
|
|
}
|
|
|
|
//
|
|
// Record the fact that one of our OPTITEM selection has been changed by plugin's
|
|
// call of helper function DrvUpdateUISetting. This is necessary so that at the
|
|
// APPLYNOW time we know constraints could exist even though user hasn't touched
|
|
// any of our OPTITEMs.
|
|
//
|
|
|
|
pUiData->ci.dwFlags |= FLAG_PLUGIN_CHANGED_OPTITEM;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
BUpgradeRegistrySettingForOEM(
|
|
HANDLE hPrinter,
|
|
PCSTR pFeatureKeyword,
|
|
PCSTR pOptionKeyword
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the Feature.Option request to our options array. OEM will only
|
|
call this function at OEMUpgradeDriver to upgrade their registry setttings
|
|
into our optionsarray saved in our PRINTERDATA
|
|
|
|
Arguments:
|
|
|
|
hPrinter - Handle of the Printer
|
|
pFeatureKeyword - Specifies the keyword the caller is interested in
|
|
pOptionKeyword - Specifies the keyword the caller is interested in
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PFEATURE pFeature;
|
|
POPTION pOption;
|
|
DWORD dwFeatureCount, i, j;
|
|
BOOL bFeatureFound, bOptionFound, bResult = FALSE;
|
|
PCSTR pKeywordName;
|
|
POPTSELECT pOptionsArray = NULL;
|
|
PDRIVER_INFO_3 pDriverInfo3 = NULL;
|
|
PRAWBINARYDATA pRawData = NULL;
|
|
PINFOHEADER pInfoHeader = NULL;
|
|
PUIINFO pUIInfo = NULL;
|
|
PPRINTERDATA pPrinterData = NULL;
|
|
OPTSELECT DocOptions[MAX_PRINTER_OPTIONS];
|
|
|
|
//
|
|
// Get information about the printer driver
|
|
//
|
|
|
|
bResult = bFeatureFound = bOptionFound = FALSE;
|
|
|
|
if ((pDriverInfo3 = MyGetPrinterDriver(hPrinter, NULL, 3)) == NULL)
|
|
{
|
|
ERR(("Cannot get printer driver info: %d\n", GetLastError()));
|
|
goto upgrade_registry_exit;
|
|
}
|
|
|
|
// ENTER_CRITICAL_SECTION();
|
|
|
|
pRawData = LoadRawBinaryData(pDriverInfo3->pDataFile);
|
|
|
|
// LEAVE_CRITICAL_SECTION();
|
|
|
|
if (pRawData == NULL)
|
|
goto upgrade_registry_exit;
|
|
|
|
if (!(pPrinterData = MemAllocZ(sizeof(PRINTERDATA))) ||
|
|
!( BGetPrinterProperties(hPrinter, pRawData, pPrinterData)))
|
|
{
|
|
|
|
ERR(("Cannot get printer data info: %d\n", GetLastError()));
|
|
goto upgrade_registry_exit;
|
|
}
|
|
|
|
//
|
|
// Allocate memory for combined optionsarray
|
|
//
|
|
|
|
if (!(pOptionsArray = MemAllocZ(MAX_COMBINED_OPTIONS * sizeof (OPTSELECT))))
|
|
goto upgrade_registry_exit;
|
|
|
|
if (! InitDefaultOptions(pRawData,
|
|
DocOptions,
|
|
MAX_PRINTER_OPTIONS,
|
|
MODE_DOCUMENT_STICKY))
|
|
{
|
|
goto upgrade_registry_exit;
|
|
}
|
|
|
|
//
|
|
// Combine doc sticky options with printer sticky items
|
|
//
|
|
|
|
CombineOptionArray(pRawData, pOptionsArray, MAX_COMBINED_OPTIONS, DocOptions, pPrinterData->aOptions);
|
|
|
|
//
|
|
// Get an updated instance of printer description data
|
|
//
|
|
|
|
pInfoHeader = InitBinaryData(pRawData,
|
|
NULL,
|
|
pOptionsArray);
|
|
|
|
if (pInfoHeader == NULL)
|
|
{
|
|
ERR(("InitBinaryData failed\n"));
|
|
goto upgrade_registry_exit;
|
|
}
|
|
|
|
if (!(pUIInfo = OFFSET_TO_POINTER(pInfoHeader, pInfoHeader->loUIInfoOffset)))
|
|
goto upgrade_registry_exit;
|
|
|
|
//
|
|
// Look for feature.option index
|
|
//
|
|
|
|
pFeature = PGetIndexedFeature(pUIInfo, 0);
|
|
dwFeatureCount = pRawData->dwDocumentFeatures + pRawData->dwPrinterFeatures;
|
|
|
|
if (pFeature && dwFeatureCount)
|
|
{
|
|
for (i = 0; i < dwFeatureCount; i++)
|
|
{
|
|
pKeywordName = OFFSET_TO_POINTER(pUIInfo->pubResourceData, pFeature->loKeywordName);
|
|
if (strcmp(pKeywordName, pFeatureKeyword) == EQUAL_STRING)
|
|
{
|
|
bFeatureFound = TRUE;
|
|
break;
|
|
}
|
|
pFeature++;
|
|
}
|
|
}
|
|
|
|
if (bFeatureFound)
|
|
{
|
|
pOption = PGetIndexedOption(pUIInfo, pFeature, 0);
|
|
|
|
for (j = 0; j < pFeature->Options.dwCount; j++)
|
|
{
|
|
pKeywordName = OFFSET_TO_POINTER(pUIInfo->pubResourceData, pOption->loKeywordName);
|
|
if (strcmp(pKeywordName, pOptionKeyword) == EQUAL_STRING)
|
|
{
|
|
bOptionFound = TRUE;
|
|
break;
|
|
}
|
|
pOption++;
|
|
}
|
|
}
|
|
|
|
if (bFeatureFound && bOptionFound)
|
|
{
|
|
pOptionsArray[i].ubCurOptIndex = (BYTE)j;
|
|
|
|
//
|
|
// Resolve conflicts
|
|
//
|
|
|
|
if (!ResolveUIConflicts( pRawData,
|
|
pOptionsArray,
|
|
MAX_COMBINED_OPTIONS,
|
|
MODE_DOCANDPRINTER_STICKY))
|
|
{
|
|
VERBOSE(("Resolved conflicting printer feature selections.\n"));
|
|
}
|
|
|
|
|
|
SeparateOptionArray(pRawData,
|
|
pOptionsArray,
|
|
pPrinterData->aOptions,
|
|
MAX_PRINTER_OPTIONS,
|
|
MODE_PRINTER_STICKY
|
|
);
|
|
|
|
if (!BSavePrinterProperties(hPrinter, pRawData, pPrinterData, sizeof(PRINTERDATA)))
|
|
{
|
|
ERR(("BSavePrinterProperties failed\n"));
|
|
bResult = FALSE;
|
|
}
|
|
else
|
|
bResult = TRUE;
|
|
}
|
|
|
|
upgrade_registry_exit:
|
|
|
|
if (pInfoHeader)
|
|
FreeBinaryData(pInfoHeader);
|
|
|
|
if (pRawData)
|
|
UnloadRawBinaryData(pRawData);
|
|
|
|
if (pPrinterData)
|
|
MemFree(pPrinterData);
|
|
|
|
if (pDriverInfo3)
|
|
MemFree(pDriverInfo3);
|
|
|
|
if (pOptionsArray)
|
|
MemFree(pOptionsArray);
|
|
|
|
return bResult;
|
|
}
|
|
|
|
#ifdef PSCRIPT
|
|
|
|
#ifndef WINNT_40
|
|
|
|
|
|
/*++
|
|
|
|
Routine Name:
|
|
|
|
HQuerySimulationSupport
|
|
|
|
Routine Description:
|
|
|
|
In the case of UI replacement, we allows IHV to query for print processor simulation
|
|
support so they can provide simulated features on their UI.
|
|
|
|
We won't enforce hooking out QueryJobAttribute w/o UI replacement here. We will do it
|
|
at DrvQueryJobAttributes.
|
|
|
|
Arguments:
|
|
|
|
hPrinter - printer handle
|
|
dwLevel - interested level of spooler simulation capability info structure
|
|
pCaps - pointer to output buffer
|
|
cbSize - size in bytes of output buffer
|
|
pcbNeeded - buffer size in bytes needed to store the interested info structure
|
|
|
|
Return Value:
|
|
|
|
S_OK if succeeded
|
|
E_OUTOFMEMORY if output buffer is not big enough
|
|
E_NOTIMPL if the interested level is not supported
|
|
E_FAIL if encountered other internal error
|
|
|
|
Last Error:
|
|
|
|
None
|
|
|
|
--*/
|
|
HRESULT
|
|
HQuerySimulationSupport(
|
|
IN HANDLE hPrinter,
|
|
IN DWORD dwLevel,
|
|
OUT PBYTE pCaps,
|
|
IN DWORD cbSize,
|
|
OUT PDWORD pcbNeeded
|
|
)
|
|
{
|
|
PRINTPROCESSOR_CAPS_1 SplCaps;
|
|
PSIMULATE_CAPS_1 pSimCaps;
|
|
DWORD cbNeeded;
|
|
|
|
//
|
|
// currently only Level 1 is supported
|
|
//
|
|
|
|
if (dwLevel != 1)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
cbNeeded = sizeof(SIMULATE_CAPS_1);
|
|
|
|
if (pcbNeeded)
|
|
{
|
|
*pcbNeeded = cbNeeded;
|
|
}
|
|
|
|
if (!pCaps || cbSize < cbNeeded)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
//
|
|
// Since VGetSpoolerEmfCaps doesn't return error code, we
|
|
// are using the dwLevel field to detect if the call succeeds.
|
|
// If succeeds, dwLevel should be set as 1.
|
|
//
|
|
|
|
SplCaps.dwLevel = 0;
|
|
|
|
VGetSpoolerEmfCaps(hPrinter,
|
|
NULL,
|
|
NULL,
|
|
sizeof(PRINTPROCESSOR_CAPS_1),
|
|
&SplCaps
|
|
);
|
|
|
|
if (SplCaps.dwLevel != 1)
|
|
{
|
|
ERR(("VGetSpoolerEmfCaps failed\n"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
//
|
|
// BUGBUG, we should get a new PRINTPROCESSOR_CAPS level to include all
|
|
// these information instead of filling it out here. Need
|
|
// new PRINTPROCESSOR_CAPS
|
|
//
|
|
|
|
pSimCaps = (PSIMULATE_CAPS_1)pCaps;
|
|
|
|
pSimCaps->dwLevel = 1;
|
|
pSimCaps->dwPageOrderFlags = SplCaps.dwPageOrderFlags;
|
|
pSimCaps->dwNumberOfCopies = SplCaps.dwNumberOfCopies;
|
|
pSimCaps->dwNupOptions = SplCaps.dwNupOptions;
|
|
|
|
//
|
|
// PRINTPROCESSOR_CAPS_1 is designed without an explicit field for
|
|
// collate simulation. So before its CAPS_2 is introduced, we have
|
|
// to assume that if reverse printing is supported, then collate
|
|
// simulation is also supported.
|
|
//
|
|
|
|
if (SplCaps.dwPageOrderFlags & REVERSE_PRINT)
|
|
{
|
|
pSimCaps->dwCollate = 1;
|
|
}
|
|
else
|
|
{
|
|
pSimCaps->dwCollate = 0;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#endif // !WINNT_40
|
|
|
|
|
|
/*++
|
|
|
|
Routine Name:
|
|
|
|
HEnumConstrainedOptions
|
|
|
|
Routine Description:
|
|
|
|
enumerate the constrained option keyword name list in the specified feature
|
|
|
|
Arguments:
|
|
|
|
poemuiobj - pointer to driver context object
|
|
dwFlags - flags for the enumeration operation
|
|
pszFeatureKeyword - feature keyword name
|
|
pmszConstrainedOptionList - pointer to output data buffer
|
|
cbSize - output data buffer size in bytes
|
|
pcbNeeded - buffer size in bytes needed to store the output data
|
|
|
|
Return Value:
|
|
|
|
S_OK if succeeds
|
|
E_OUTOFMEMORY if output data buffer size is not big enough
|
|
E_INVALIDARG if feature keyword name is not recognized, or the feature's
|
|
stickiness doesn't match current sticky-mode
|
|
E_FAIL if other internal failures are encountered
|
|
|
|
Last Error:
|
|
|
|
None
|
|
|
|
--*/
|
|
HRESULT
|
|
HEnumConstrainedOptions(
|
|
IN POEMUIOBJ poemuiobj,
|
|
IN DWORD dwFlags,
|
|
IN PCSTR pszFeatureKeyword,
|
|
OUT PSTR pmszConstrainedOptionList,
|
|
IN DWORD cbSize,
|
|
OUT PDWORD pcbNeeded
|
|
)
|
|
{
|
|
PCOMMONINFO pci = (PCOMMONINFO)poemuiobj;
|
|
PUIDATA pUiData;
|
|
PFEATURE pFeature;
|
|
POPTION pOption;
|
|
DWORD dwFeatureIndex, dwIndex;
|
|
PBOOL pabEnabledOptions = NULL;
|
|
PSTR pCurrentOut;
|
|
DWORD cbNeeded;
|
|
INT cbRemain;
|
|
HRESULT hr;
|
|
|
|
pUiData = (PUIDATA)pci;
|
|
|
|
if (!pszFeatureKeyword ||
|
|
(pFeature = PGetNamedFeature(pci->pUIInfo, pszFeatureKeyword, &dwFeatureIndex)) == NULL)
|
|
{
|
|
WARNING(("HEnumConstrainedOptions: invalid feature\n"));
|
|
|
|
//
|
|
// Even though we could return right here, we still use goto to maintain single exit point.
|
|
//
|
|
|
|
hr = E_INVALIDARG;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// pUiData->iMode can have 2 modes: MODE_DOCUMENT_STICKY and MODE_PRINTER_STICKY. See PFillUiData().
|
|
// In MODE_DOCUMENT_STICKY mode, we only support doc-sticky features.
|
|
// In MODE_PRINTER_STICKY mode, we only support printer-sticky features.
|
|
//
|
|
// This is because in function PFillUiData(), it only fills devmode in MODE_DOCUMENT_STICKY mode.
|
|
// Then in BCombineCommonInfoOptionsArray(), if devmode option array is not available, the PPD parser
|
|
// will use OPTION_INDEX_ANY for any doc-sticky features.
|
|
//
|
|
|
|
if ((pUiData->iMode == MODE_DOCUMENT_STICKY && pFeature->dwFeatureType == FEATURETYPE_PRINTERPROPERTY) ||
|
|
(pUiData->iMode == MODE_PRINTER_STICKY && pFeature->dwFeatureType != FEATURETYPE_PRINTERPROPERTY))
|
|
{
|
|
VERBOSE(("HEnumConstrainedOptions: mismatch iMode=%d, dwFeatureType=%d\n",
|
|
pUiData->iMode, pFeature->dwFeatureType)) ;
|
|
|
|
hr = E_INVALIDARG;
|
|
goto exit;
|
|
}
|
|
|
|
if (pFeature->Options.dwCount)
|
|
{
|
|
if ((pabEnabledOptions = MemAllocZ(pFeature->Options.dwCount * sizeof(BOOL))) == NULL)
|
|
{
|
|
ERR(("HEnumConstrainedOptions: memory alloc failed\n"));
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Get the feature's enabled option list.
|
|
//
|
|
// See VPropShowConstraints() in docprop.c and prnprop.c for using different
|
|
// modes to call EnumEnabledOptions().
|
|
//
|
|
|
|
if (pUiData->iMode == MODE_DOCUMENT_STICKY)
|
|
{
|
|
EnumEnabledOptions(pci->pRawData, pci->pCombinedOptions, dwFeatureIndex,
|
|
pabEnabledOptions, MODE_DOCANDPRINTER_STICKY);
|
|
}
|
|
else
|
|
{
|
|
EnumEnabledOptions(pci->pRawData, pci->pCombinedOptions, dwFeatureIndex,
|
|
pabEnabledOptions, MODE_PRINTER_STICKY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RIP(("HEnumConstrainedOptions: feature %s has no options\n", pszFeatureKeyword));
|
|
|
|
//
|
|
// continue so we will output the empty string with only the NUL character
|
|
//
|
|
}
|
|
|
|
pCurrentOut = pmszConstrainedOptionList;
|
|
cbNeeded = 0;
|
|
cbRemain = (INT)cbSize;
|
|
|
|
pOption = OFFSET_TO_POINTER(pci->pInfoHeader, pFeature->Options.loOffset);
|
|
|
|
ASSERT(pOption || pFeature->Options.dwCount == 0);
|
|
|
|
if (pOption == NULL && pFeature->Options.dwCount != 0)
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
for (dwIndex = 0; dwIndex < pFeature->Options.dwCount; dwIndex++)
|
|
{
|
|
if (!pabEnabledOptions[dwIndex])
|
|
{
|
|
DWORD dwNameSize;
|
|
PSTR pszKeywordName;
|
|
|
|
pszKeywordName = OFFSET_TO_POINTER(pci->pUIInfo->pubResourceData, pOption->loKeywordName);
|
|
|
|
ASSERT(pszKeywordName);
|
|
|
|
if (pszKeywordName == NULL)
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// count in the NUL character between constrained option keywords
|
|
//
|
|
|
|
dwNameSize = strlen(pszKeywordName) + 1;
|
|
|
|
if (pCurrentOut && cbRemain >= (INT)dwNameSize)
|
|
{
|
|
CopyMemory(pCurrentOut, pszKeywordName, dwNameSize);
|
|
pCurrentOut += dwNameSize;
|
|
}
|
|
|
|
cbRemain -= dwNameSize;
|
|
cbNeeded += dwNameSize;
|
|
}
|
|
|
|
pOption = (POPTION)((PBYTE)pOption + pFeature->dwOptionSize);
|
|
}
|
|
|
|
//
|
|
// remember the last NUL terminator for the MULTI_SZ output string
|
|
//
|
|
|
|
cbNeeded++;
|
|
|
|
if (pcbNeeded)
|
|
{
|
|
*pcbNeeded = cbNeeded;
|
|
}
|
|
|
|
if (!pCurrentOut || cbRemain < 1)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
*pCurrentOut = NUL;
|
|
|
|
//
|
|
// Succeeded
|
|
//
|
|
|
|
hr = S_OK;
|
|
|
|
exit:
|
|
|
|
MemFree(pabEnabledOptions);
|
|
return hr;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Name:
|
|
|
|
HWhyConstrained
|
|
|
|
Routine Description:
|
|
|
|
get feature/option keyword pair that constrains the given
|
|
feature/option pair
|
|
|
|
Arguments:
|
|
|
|
poemuiobj - pointer to driver context object
|
|
dwFlags - flags for this operation
|
|
pszFeatureKeyword - feature keyword name
|
|
pszOptionKeyword - option keyword name
|
|
pmszReasonList - pointer to output data buffer
|
|
cbSize - output data buffer size in bytes
|
|
pcbNeeded - buffer size in bytes needed to store the output data
|
|
|
|
Return Value:
|
|
|
|
S_OK if succeeds
|
|
E_OUTOFMEMORY if output data buffer size is not big enough
|
|
E_INVALIDARG if the feature keyword name or option keyword name
|
|
is not recognized, or the feature's stickiness
|
|
doesn't match current sticky-mode
|
|
|
|
Last Error:
|
|
|
|
None
|
|
|
|
--*/
|
|
HRESULT
|
|
HWhyConstrained(
|
|
IN POEMUIOBJ poemuiobj,
|
|
IN DWORD dwFlags,
|
|
IN PCSTR pszFeatureKeyword,
|
|
IN PCSTR pszOptionKeyword,
|
|
OUT PSTR pmszReasonList,
|
|
IN DWORD cbSize,
|
|
OUT PDWORD pcbNeeded
|
|
)
|
|
{
|
|
PCOMMONINFO pci = (PCOMMONINFO)poemuiobj;
|
|
PUIDATA pUiData;
|
|
PFEATURE pFeature;
|
|
POPTION pOption;
|
|
DWORD dwFeatureIndex, dwOptionIndex;
|
|
CONFLICTPAIR ConflictPair;
|
|
BOOL bConflictFound;
|
|
PSTR pszConfFeatureName = NULL, pszConfOptionName = NULL;
|
|
CHAR emptyString[1] = {0};
|
|
DWORD cbConfFeatureKeySize = 0, cbConfOptionKeySize = 0;
|
|
DWORD cbNeeded = 0;
|
|
|
|
pUiData = (PUIDATA)pci;
|
|
|
|
if (!pszFeatureKeyword ||
|
|
(pFeature = PGetNamedFeature(pci->pUIInfo, pszFeatureKeyword, &dwFeatureIndex)) == NULL)
|
|
{
|
|
WARNING(("HWhyConstrained: invalid feature\n"));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (!pszOptionKeyword ||
|
|
(pOption = PGetNamedOption(pci->pUIInfo, pFeature, pszOptionKeyword, &dwOptionIndex)) == NULL)
|
|
{
|
|
WARNING(("HWhyConstrained: invalid option\n"));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
//
|
|
// See comments in HEnumConstrainedOptions() for following stickiness mode check.
|
|
//
|
|
|
|
if ((pUiData->iMode == MODE_DOCUMENT_STICKY && pFeature->dwFeatureType == FEATURETYPE_PRINTERPROPERTY) ||
|
|
(pUiData->iMode == MODE_PRINTER_STICKY && pFeature->dwFeatureType != FEATURETYPE_PRINTERPROPERTY))
|
|
{
|
|
VERBOSE(("HWhyConstrained: mismatch iMode=%d, dwFeatureType=%d\n",pUiData->iMode, pFeature->dwFeatureType));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
//
|
|
// Get the feature/option pair that constrains the feature/option pair client is querying for.
|
|
//
|
|
|
|
bConflictFound = EnumNewPickOneUIConflict(pci->pRawData,
|
|
pci->pCombinedOptions,
|
|
dwFeatureIndex,
|
|
dwOptionIndex,
|
|
&ConflictPair);
|
|
|
|
if (bConflictFound)
|
|
{
|
|
PFEATURE pConfFeature;
|
|
POPTION pConfOption;
|
|
DWORD dwConfFeatureIndex, dwConfOptionIndex;
|
|
|
|
//
|
|
// ConflictPair has the feature with higher priority as dwFeatureIndex1.
|
|
//
|
|
|
|
if (dwFeatureIndex == ConflictPair.dwFeatureIndex1)
|
|
{
|
|
dwConfFeatureIndex = ConflictPair.dwFeatureIndex2;
|
|
dwConfOptionIndex = ConflictPair.dwOptionIndex2;
|
|
}
|
|
else
|
|
{
|
|
dwConfFeatureIndex = ConflictPair.dwFeatureIndex1;
|
|
dwConfOptionIndex = ConflictPair.dwOptionIndex1;
|
|
}
|
|
|
|
pConfFeature = PGetIndexedFeature(pci->pUIInfo, dwConfFeatureIndex);
|
|
ASSERT(pConfFeature);
|
|
|
|
pConfOption = PGetIndexedOption(pci->pUIInfo, pConfFeature, dwConfOptionIndex);
|
|
|
|
//
|
|
// We don't expect pConfOption to be NULL here. Use the ASSERT to catch cases we missed.
|
|
//
|
|
|
|
ASSERT(pConfOption);
|
|
|
|
pszConfFeatureName = OFFSET_TO_POINTER(pci->pUIInfo->pubResourceData, pConfFeature->loKeywordName);
|
|
ASSERT(pszConfFeatureName);
|
|
|
|
if (pConfOption)
|
|
{
|
|
pszConfOptionName = OFFSET_TO_POINTER(pci->pUIInfo->pubResourceData, pConfOption->loKeywordName);
|
|
ASSERT(pszConfOptionName);
|
|
}
|
|
else
|
|
{
|
|
pszConfOptionName = &(emptyString[0]);
|
|
}
|
|
|
|
//
|
|
// count in the 2 NUL characters: one after feature name, one after option name.
|
|
//
|
|
|
|
cbConfFeatureKeySize = strlen(pszConfFeatureName) + 1;
|
|
cbConfOptionKeySize = strlen(pszConfOptionName) + 1;
|
|
}
|
|
|
|
//
|
|
// count in the last NUL characters at the end.
|
|
//
|
|
|
|
cbNeeded = cbConfFeatureKeySize + cbConfOptionKeySize + 1;
|
|
|
|
if (pcbNeeded)
|
|
{
|
|
*pcbNeeded = cbNeeded;
|
|
}
|
|
|
|
if (!pmszReasonList || cbSize < cbNeeded)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (bConflictFound)
|
|
{
|
|
ASSERT(pszConfFeatureName && pszConfOptionName);
|
|
|
|
CopyMemory(pmszReasonList, pszConfFeatureName, cbConfFeatureKeySize);
|
|
pmszReasonList += cbConfFeatureKeySize;
|
|
|
|
CopyMemory(pmszReasonList, pszConfOptionName, cbConfOptionKeySize);
|
|
pmszReasonList += cbConfOptionKeySize;
|
|
}
|
|
|
|
//
|
|
// Now the NUL at the end to finish the MULTI_SZ output string.
|
|
//
|
|
|
|
*pmszReasonList = NUL;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#endif // PSCRIPT
|