From 14b61400955bfb802b692d2da2c09ccdd6caf890 Mon Sep 17 00:00:00 2001 From: David Hall Date: Wed, 11 Dec 2019 11:11:22 -0700 Subject: [PATCH] Finished inital release work for PInvoke.Printing --- PInvoke/Printing/WinSpool.Enums.cs | 49 +++- PInvoke/Printing/WinSpool.Funcs.cs | 378 ++++++++++++++-------------- PInvoke/Printing/WinSpool.Structs.cs | 48 +++- UnitTests/PInvoke/Printing/PrintingTests.cs | 111 ++++++-- 4 files changed, 367 insertions(+), 219 deletions(-) diff --git a/PInvoke/Printing/WinSpool.Enums.cs b/PInvoke/Printing/WinSpool.Enums.cs index e4bedba1..004b4aa1 100644 --- a/PInvoke/Printing/WinSpool.Enums.cs +++ b/PInvoke/Printing/WinSpool.Enums.cs @@ -8,6 +8,53 @@ namespace Vanara.PInvoke /// Indicates that some notifications had to be discarded. public const uint PRINTER_NOTIFY_INFO_DISCARDED = 0x01; + /// Printer, job and print server access rights. + [PInvokeData("winspool.h")] + [Flags] + public enum AccessRights : uint + { + /// Printing-specific authorization to cancel, pause, resume, or restart the job ([MS-DTYP] ACCESS_MASK Bit 27). + JOB_ACCESS_ADMINISTER = 0x00000010, + /// Printing-specific read rights for the spool file ([MS-DTYP] ACCESS_MASK Bit 26).<127> + JOB_ACCESS_READ = 0x00000020, + /// Access rights for jobs combining RC (Read Control) of ACCESS_MASK with printing-specific JOB_ACCESS_ADMINISTER. + /// This value MUST NOT be passed over the wire. If it is, the server SHOULD return ERROR_ACCESS_DENIED. + JOB_EXECUTE = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE | JOB_ACCESS_ADMINISTER, + /// Access rights for jobs combining RC (Read Control) of ACCESS_MASK with printing-specific JOB_ACCESS_READ. + JOB_READ = ACCESS_MASK.STANDARD_RIGHTS_READ | JOB_ACCESS_READ, + /// Access rights for jobs combining RC (Read Control) of ACCESS_MASK with printing-specific JOB_ACCESS_ADMINISTER. + /// This value MUST NOT be passed over the wire. If it is, the server SHOULD return ERROR_ACCESS_DENIED. + JOB_WRITE = ACCESS_MASK.STANDARD_RIGHTS_WRITE | JOB_ACCESS_ADMINISTER, + /// Access rights for printers to perform all administrative tasks and basic printing operations except SYNCHRONIZE ([MS-DTYP] ACCESS_MASK Bit 'SY'). Combines STANDARD_RIGHTS_REQUIRED (ACCESS_MASK Bits 'RC', 'DE', 'WD', 'WO'), JOB_ACCESS_ADMINISTER (ACCESS_MASK Bit 27), and JOB_ACCESS_READ (ACCESS_MASK Bit 26). + JOB_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | JOB_ACCESS_ADMINISTER | JOB_ACCESS_READ, + /// Printing-specific access rights for printers to perform administrative tasks ([MS-DTYP] ACCESS_MASK Bit 29). + PRINTER_ACCESS_ADMINISTER = 0x00000004, + /// Printing-specific access rights for printers to perform basic printing operations ([MS-DTYP] ACCESS_MASK Bit 28). + PRINTER_ACCESS_USE = 0x00000008, + /// Printing-specific access rights for printers to perform printer data management operations ([MS-DTYP] ACCESS_MASK Bit 25).<128> + PRINTER_ACCESS_MANAGE_LIMITED = 0x00000040, + /// Access rights for printers to perform all administrative tasks and basic printing operations except synchronization. Combines WO (Write Owner), WD (Write DACL), RC (Read Control), and DE (Delete) of ACCESS_MASK with printing-specific PRINTER_ACCESS_ADMINISTER and printing-specific PRINTER_ACCESS_USE. + PRINTER_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE, + /// Access rights for printers combining RC (Read Control) of ACCESS_MASK with printing-specific PRINTER_ACCESS_USE. + PRINTER_EXECUTE = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE | PRINTER_ACCESS_USE, + /// Access rights for printers combining RC (Read Control) of ACCESS_MASK with printing-specific PRINTER_ACCESS_USE. + PRINTER_READ = ACCESS_MASK.STANDARD_RIGHTS_READ | PRINTER_ACCESS_USE, + /// Access rights for printers combining RC (Read Control) of ACCESS_MASK with printing-specific PRINTER_ACCESS_USE. + PRINTER_WRITE = ACCESS_MASK.STANDARD_RIGHTS_WRITE | PRINTER_ACCESS_USE, + /// Printing-specific access rights to administer print servers ([MS-DTYP] ACCESS_MASK Bit 31). + SERVER_ACCESS_ADMINISTER = 0x00000001, + /// Printing-specific access rights to enumerate print servers ([MS-DTYP] ACCESS_MASK Bit 30). + SERVER_ACCESS_ENUMERATE = 0x00000002, + /// Access rights for print servers to perform all administrative tasks and basic printing operations except synchronization. Combines WO (Write Owner), WD (Write DACL), RC (Read Control), and DE (Delete) of ACCESS_MASK with printing-specific SERVER_ACCESS_ADMINISTER and printing-specific SERVER_ACCESS_ENUMERATE. + SERVER_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | SERVER_ACCESS_ADMINISTER | SERVER_ACCESS_ENUMERATE, + /// Access rights for print servers combining RC (Read Control) of ACCESS_MASK with printing-specific SERVER_ACCESS_ENUMERATE. + SERVER_EXECUTE = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE | SERVER_ACCESS_ENUMERATE, + /// Access rights for print servers combining RC (Read Control) of ACCESS_MASK with printing-specific SERVER_ACCESS_ENUMERATE. + SERVER_READ = ACCESS_MASK.STANDARD_RIGHTS_READ | SERVER_ACCESS_ENUMERATE, + /// Access rights for print servers combining RC (Read Control) of ACCESS_MASK with printing-specific SERVER_ACCESS_ADMINISTER and printing-specific SERVER_ACCESS_ENUMERATE. + SERVER_WRITE = ACCESS_MASK.STANDARD_RIGHTS_WRITE | SERVER_ACCESS_ADMINISTER | SERVER_ACCESS_ENUMERATE, + } + /// Specifies additional information about the print job. [PInvokeData("wingdi.h", MSDNShortId = "329bf0d9-399b-4f64-a029-361ef7558aeb")] public enum DI @@ -891,7 +938,7 @@ namespace Vanara.PInvoke PRINTER_CHANGE_TIMEOUT = 0x80000000, /// Notify if any of the preceding changes occur. - PRINTER_CHANGE_ALL, + PRINTER_CHANGE_ALL = 0x7F77FFFF, } /// Specifies the caching of a handle for a printer opened with OpenPrinter2. diff --git a/PInvoke/Printing/WinSpool.Funcs.cs b/PInvoke/Printing/WinSpool.Funcs.cs index 17da4988..26357f62 100644 --- a/PInvoke/Printing/WinSpool.Funcs.cs +++ b/PInvoke/Printing/WinSpool.Funcs.cs @@ -2888,6 +2888,187 @@ namespace Vanara.PInvoke [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FindNextPrinterChangeNotification(HPRINTERCHANGENOTIFICATION hChange, out PRINTER_CHANGE pdwChange, in PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions, out SafePRINTER_NOTIFY_INFO ppPrinterNotifyInfo); + /// + /// + /// The FindNextPrinterChangeNotification function retrieves information about the most recent change notification for a + /// change notification object associated with a printer or print server. Call this function when a wait operation on the change + /// notification object is satisfied. + /// + /// + /// The function also resets the change notification object to the not-signaled state. You can then use the object in another wait + /// operation to continue monitoring the printer or print server. The operating system will set the object to the signaled state the + /// next time one of a specified set of changes occurs to the printer or print server. The FindFirstPrinterChangeNotification + /// function creates the change notification object and specifies the set of changes to be monitored. + /// + /// + /// + /// A handle to a change notification object associated with a printer or print server. You obtain such a handle by calling the + /// FindFirstPrinterChangeNotification function. The operating system sets this change notification object to the signaled + /// state when it detects one of the changes specified in the object's change notification filter. + /// + /// + /// + /// A pointer to a variable whose bits are set to indicate the changes that occurred to cause the most recent notification. The bit + /// flags that might be set correspond to those specified in the fdwFilter parameter of the + /// FindFirstPrinterChangeNotification call. The system sets one or more of the following bit flags. + /// + /// + /// + /// Value + /// Meaning + /// + /// + /// PRINTER_CHANGE_ADD_FORM + /// A form was added to the server. + /// + /// + /// PRINTER_CHANGE_ADD_JOB + /// A print job was sent to the printer. + /// + /// + /// PRINTER_CHANGE_ADD_PORT + /// A port or monitor was added to the server. + /// + /// + /// PRINTER_CHANGE_ADD_PRINT_PROCESSOR + /// A print processor was added to the server. + /// + /// + /// PRINTER_CHANGE_ADD_PRINTER + /// A printer was added to the server. + /// + /// + /// PRINTER_CHANGE_ADD_PRINTER_DRIVER + /// A printer driver was added to the server. + /// + /// + /// PRINTER_CHANGE_CONFIGURE_PORT + /// A port was configured on the server. + /// + /// + /// PRINTER_CHANGE_DELETE_FORM + /// A form was deleted from the server. + /// + /// + /// PRINTER_CHANGE_DELETE_JOB + /// A job was deleted. + /// + /// + /// PRINTER_CHANGE_DELETE_PORT + /// A port or monitor was deleted from the server. + /// + /// + /// PRINTER_CHANGE_DELETE_PRINT_PROCESSOR + /// A print processor was deleted from the server. + /// + /// + /// PRINTER_CHANGE_DELETE_PRINTER + /// A printer was deleted. + /// + /// + /// PRINTER_CHANGE_DELETE_PRINTER_DRIVER + /// A printer driver was deleted from the server. + /// + /// + /// PRINTER_CHANGE_FAILED_CONNECTION_PRINTER + /// A printer connection has failed. + /// + /// + /// PRINTER_CHANGE_SET_FORM + /// A form was set on the server. + /// + /// + /// PRINTER_CHANGE_SET_JOB + /// A job was set. + /// + /// + /// PRINTER_CHANGE_SET_PRINTER + /// A printer was set. + /// + /// + /// PRINTER_CHANGE_SET_PRINTER_DRIVER + /// A printer driver was set. + /// + /// + /// PRINTER_CHANGE_WRITE_JOB + /// Job data was written. + /// + /// + /// PRINTER_CHANGE_TIMEOUT + /// The job timed out. + /// + /// + /// PRINTER_CHANGE_SERVER + /// Windows 7: A change occurred on the server. + /// + /// + /// + /// + /// A pointer to a PRINTER_NOTIFY_OPTIONS structure. Set the Flags member of this structure to + /// PRINTER_NOTIFY_OPTIONS_REFRESH, to cause the function to return the current data for all monitored printer information + /// fields. The function ignores all other members of the structure. This parameter can be NULL. + /// + /// + /// + /// A pointer to a pointer variable that receives a pointer to a system-allocated, read-only buffer. Call the + /// FreePrinterNotifyInfo function to free the buffer when you are finished with it. This parameter can be NULL if no + /// information is required. + /// + /// + /// The buffer contains a PRINTER_NOTIFY_INFO structure, which contains an array of PRINTER_NOTIFY_INFO_DATA + /// structures. Each element of the array contains information about one of the fields specified in the pPrinterNotifyOptions + /// parameter of the FindFirstPrinterChangeNotification call. Typically, the function provides data only for the fields that + /// changed to cause the most recent notification. However, if the structure pointed to by the pPrinterNotifyOptions parameter + /// specifies PRINTER_NOTIFY_OPTIONS_REFRESH, the function provides data for all monitored fields. + /// + /// + /// If the PRINTER_NOTIFY_INFO_DISCARDED bit is set in the Flags member of the PRINTER_NOTIFY_INFO structure, + /// an overflow or error occurred, and notifications may have been lost. In this case, no additional notifications will be sent + /// until you make a second FindNextPrinterChangeNotification call that specifies PRINTER_NOTIFY_OPTIONS_REFRESH. + /// + /// + /// + /// If the function succeeds, the return value is a nonzero value. + /// If the function fails, the return value is zero. + /// + /// + /// + /// Call the FindNextPrinterChangeNotification function after a wait operation on a notification object created by + /// FindFirstPrinterChangeNotification has been satisfied. Calling FindNextPrinterChangeNotification lets you obtain + /// information about the change that satisfied the wait operation, and resets the notification object so it can be signaled when + /// the next change occurs. + /// + /// + /// With one exception, do not call the FindNextPrinterChangeNotification function if the change notification object is not + /// in the signaled state. If a wait function returns the value WAIT_TIMEOUT, the change object is not in the signaled state. + /// Call the FindNextPrinterChangeNotification function only if the wait function succeeds without timing out. The exception + /// is when FindNextPrinterChangeNotification is called with the PRINTER_NOTIFY_OPTIONS_REFRESH bit set in the + /// pPrinterNotifyOptions parameter. Note that even when this flag is set, it is still possible for the + /// PRINTER_NOTIFY_INFO_DISCARDED flag to be set in the ppPrinterNotifyInfo parameter. + /// + /// + /// To continue monitoring the printer or print server for changes, repeat the cycle of calling one of the wait functions , and then + /// calling the FindNextPrinterChangeNotification function to examine the change and reset the notification object. + /// + /// + /// FindNextPrinterChangeNotification may combine multiple changes to the same printer information field into a single + /// notification. When this occurs, the function typically collapses all changes for the field into a single entry in the array of + /// PRINTER_NOTIFY_INFO_DATA structures in ppPrinterNotifyInfo; the single entry reports only the most current information. + /// However, for some job and printer information fields, the function can return multiple array entries for the same field. In this + /// case, the last array entry for the field reports the current data, and the earlier entries contain the data for the intermediate stages. + /// + /// + /// When you no longer need the change notification object, close it by calling the FindClosePrinterChangeNotification function. + /// + /// + // https://docs.microsoft.com/en-us/windows/win32/printdocs/findnextprinterchangenotification BOOL + // FindNextPrinterChangeNotification( _In_ HANDLE hChange, _Out_opt_ PDWORD pdwChange, _In_opt_ LPVOID pPrinterNotifyOptions, + // _Out_opt_ LPVOID *ppPrinterNotifyInfo ); + [DllImport(Lib.Winspool, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winspool.h", MSDNShortId = "ea7774ae-361f-41e4-bbc6-3f100028b22a")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FindNextPrinterChangeNotification(HPRINTERCHANGENOTIFICATION hChange, out PRINTER_CHANGE pdwChange, [Optional] IntPtr pPrinterNotifyOptions, out SafePRINTER_NOTIFY_INFO ppPrinterNotifyInfo); + /// The FlushPrinter function sends a buffer to the printer in order to clear it from a transient state. /// /// A handle to the printer object. This should be the same handle that was used, in a prior WritePrinter call, by the @@ -3008,7 +3189,7 @@ namespace Vanara.PInvoke [DllImport(Lib.Winspool, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("winspool.h", MSDNShortId = "8ec06743-43ce-4fac-83c4-f09eac7ee333")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetDefaultPrinter(StringBuilder pszBuffer, ref uint pcchBuffer); + public static extern bool GetDefaultPrinter(StringBuilder pszBuffer, ref int pcchBuffer); /// The GetForm function retrieves information about a specified form. /// @@ -4482,104 +4663,7 @@ namespace Vanara.PInvoke [DllImport(Lib.Winspool, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("winspool.h", MSDNShortId = "96763220-d851-46f0-8be8-403f3356edb9")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool OpenPrinter(string pPrinterName, out SafeHPRINTER phPrinter, in PRINTER_DEFAULTS pDefault); - - /// - /// The OpenPrinter function retrieves a handle to the specified printer or print server or other types of handles in the - /// print subsystem. - /// - /// - /// - /// A pointer to a null-terminated string that specifies the name of the printer or print server, the printer object, the - /// XcvMonitor, or the XcvPort. - /// - /// - /// For a printer object use: PrinterName, Job xxxx. For an XcvMonitor, use: ServerName, XcvMonitor MonitorName. For an XcvPort, - /// use: ServerName, XcvPort PortName. - /// - /// If NULL, it indicates the local printer server. - /// - /// - /// A pointer to a variable that receives a handle (not thread safe) to the open printer or print server object. - /// - /// The phPrinter parameter can return an Xcv handle for use with the XcvData function. For more information about XcvData, see the DDK. - /// - /// - /// A pointer to a PRINTER_DEFAULTS structure. This value can be NULL. - /// - /// If the function succeeds, the return value is a nonzero value. - /// If the function fails, the return value is zero. - /// - /// - /// Do not call this method in DllMain. - /// - /// The handle pointed to by phPrinter is not thread safe. If callers need to use it concurrently on multiple threads, they must - /// provide custom synchronization access to the printer handle using the Synchronization Functions. To avoid writing custom code - /// the application can open a printer handle on each thread, as needed. - /// - /// - /// The pDefault parameter enables you to specify the data type and device mode values that are used for printing documents - /// submitted by the StartDocPrinter function. However, you can override these values by using the SetJob function - /// after a document has been started. - /// - /// - /// The DEVMODE settings defined in the PRINTER_DEFAULTS structure of the pDefault parameter are not used when the - /// value of the pDatatype member of the DOC_INFO_1 structure that was passed in the pDocInfo parameter of the - /// StartDocPrinter call is "RAW". When a high-level document (such as an Adobe PDF or Microsoft Word file) or other printer - /// data (such PCL, PS, or HPGL) is sent directly to a printer with pDatatype set to "RAW", the document must fully describe the - /// DEVMODE-style print job settings in the language understood by the hardware. - /// - /// - /// You can call the OpenPrinter function to open a handle to a print server or to determine the access rights that a client - /// has to a print server. To do so, specify the name of the print server in the pPrinterName parameter, set the pDatatype - /// and pDevMode members of the PRINTER_DEFAULTS structure to NULL, and set the DesiredAccess member to - /// specify a server access mask value such as SERVER_ALL_ACCESS. When you finish with the handle, pass it to the - /// ClosePrinter function to close it. - /// - /// - /// Use the DesiredAccess member of the PRINTER_DEFAULTS structure to specify the access rights that you need to the - /// printer. The access rights can be one of the following. (If pDefault is NULL, then the access rights are PRINTER_ACCESS_USE.) - /// - /// - /// - /// Desired Access value - /// Meaning - /// - /// - /// PRINTER_ACCESS_ADMINISTER - /// To perform administrative tasks, such as those provided by SetPrinter. - /// - /// - /// PRINTER_ACCESS_USE - /// To perform basic printing operations. - /// - /// - /// PRINTER_ALL_ACCESS - /// To perform all administrative tasks and basic printing operations except for SYNCHRONIZE (see Standard Access Rights. - /// - /// - /// PRINTER_ACCESS_MANAGE_LIMITED - /// - /// To perform administrative tasks, such as those provided by SetPrinter and SetPrinterData. This value is available starting from - /// Windows 8.1. - /// - /// - /// - /// generic security values, such as WRITE_DAC - /// To allow specific control access rights. See Standard Access Rights. - /// - /// - /// - /// If a user does not have permission to open a specified printer or print server with the desired access, the OpenPrinter - /// call will fail with a return value of zero and GetLastError will return the value ERROR_ACCESS_DENIED. - /// - /// - // https://docs.microsoft.com/en-us/windows/win32/printdocs/openprinter BOOL OpenPrinter( _In_ LPTSTR pPrinterName, _Out_ LPHANDLE - // phPrinter, _In_ LPPRINTER_DEFAULTS pDefault ); - [DllImport(Lib.Winspool, SetLastError = true, CharSet = CharSet.Auto)] - [PInvokeData("winspool.h", MSDNShortId = "96763220-d851-46f0-8be8-403f3356edb9")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool OpenPrinter(string pPrinterName, out SafeHPRINTER phPrinter, IntPtr pDefault = default); + public static extern bool OpenPrinter(string pPrinterName, out SafeHPRINTER phPrinter, [In, Optional, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PRINTER_DEFAULTS_Marshaler))] PRINTER_DEFAULTS pDefault); /// /// Retrieves a handle to the specified printer, print server, or other types of handles in the print subsystem, while setting some @@ -4671,99 +4755,7 @@ namespace Vanara.PInvoke [DllImport(Lib.Winspool, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("winspool.h", MSDNShortId = "e2370ae4-4475-4ccc-a6f9-3d33d1370054")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool OpenPrinter2(string pPrinterName, out SafeHPRINTER phPrinter, in PRINTER_DEFAULTS pDefault, in PRINTER_OPTIONS pOptions); - - /// - /// Retrieves a handle to the specified printer, print server, or other types of handles in the print subsystem, while setting some - /// of the printer options. - /// - /// - /// - /// A pointer to a constant null-terminated string that specifies the name of the printer or print server, the printer object, the - /// XcvMonitor, or the XcvPort. - /// - /// - /// For a printer object, use: PrinterName,Job xxxx. For an XcvMonitor, use: ServerName,XcvMonitor MonitorName. For an XcvPort, use: - /// ServerName,XcvPort PortName. - /// - /// Windows Vista: If NULL, it indicates the local print server. - /// - /// A pointer to a variable that receives a handle to the open printer or print server object. - /// A pointer to a PRINTER_DEFAULTS structure. This value can be NULL. - /// A pointer to a PRINTER_OPTIONS structure. This value can be NULL. - /// - /// If the function succeeds, the return value is a nonzero value. - /// If the function fails, the return value is zero. For extended error information, call GetLastError. - /// - /// - /// Do not call this method in DllMain. - /// The ANSI version of this function is not implemented and returns ERROR_NOT_SUPPORTED. - /// - /// The pDefault parameter enables you to specify the data type and device mode values that are used for printing documents - /// submitted by the StartDocPrinter function. However, you can override these values by using the SetJob function - /// after a document has been started. - /// - /// - /// You can call the OpenPrinter2 function to open a handle to a print server or to determine client access rights to a print - /// server. To do this, specify the name of the print server in the pPrinterName parameter, set the pDatatype and - /// pDevMode members of the PRINTER_DEFAULTS structure to NULL, and set the DesiredAccess member to - /// specify a server access mask value such as SERVER_ALL_ACCESS. When you are finished with the handle, pass it to the - /// ClosePrinter function to close it. - /// - /// - /// Use the DesiredAccess member of the PRINTER_DEFAULTS structure to specify the necessary access rights. The access - /// rights can be one of the following. - /// - /// - /// - /// Desired Access value - /// Meaning - /// - /// - /// PRINTER_ACCESS_ADMINISTER - /// To perform administrative tasks, such as those provided by SetPrinter. - /// - /// - /// PRINTER_ACCESS_USE - /// To perform basic printing operations. - /// - /// - /// PRINTER_ALL_ACCESS - /// To perform all administrative tasks and basic printing operations except SYNCHRONIZE. See Standard Access Rights. - /// - /// - /// PRINTER_ACCESS_MANAGE_LIMITED - /// - /// To perform administrative tasks, such as those provided by SetPrinter and SetPrinterData. This value is available starting from - /// Windows 8.1. - /// - /// - /// - /// generic security values, such as WRITE_DAC - /// To allow specific control access rights. See Standard Access Rights. - /// - /// - /// - /// If a user does not have permission to open a specified printer or print server with the desired access, the OpenPrinter2 - /// call will fail, and GetLastError will return the value ERROR_ACCESS_DENIED. - /// - /// - /// When pPrinterName is a local printer, then OpenPrinter2 ignores all values of the dwFlags that the - /// PRINTER_OPTIONS structure pointed to using pOptions, except PRINTER_OPTION_CLIENT_CHANGE. If the latter is passed, then - /// OpenPrinter2 will return ERROR_ACCESS_DENIED. Accordingly, when opening a local printer, OpenPrinter2 provides no - /// advantage over OpenPrinter. - /// - /// - /// Windows Vista: The printer data returned by OpenPrinter2 is retrieved from a local cache unless the - /// PRINTER_OPTION_NO_CACHE flag is set in the dwFlags field of the PRINTER_OPTIONS structure referenced by pOptions. - /// - /// - // https://docs.microsoft.com/en-us/windows/win32/printdocs/openprinter2 BOOL OpenPrinter2( _In_ LPCTSTR pPrinterName, _Out_ - // LPHANDLE phPrinter, _In_ LPPRINTER_DEFAULTS pDefault, _In_ PPRINTER_OPTIONS pOptions ); - [DllImport(Lib.Winspool, SetLastError = true, CharSet = CharSet.Auto)] - [PInvokeData("winspool.h", MSDNShortId = "e2370ae4-4475-4ccc-a6f9-3d33d1370054")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool OpenPrinter2(string pPrinterName, out SafeHPRINTER phPrinter, [Optional] IntPtr pDefault, [Optional] IntPtr pOptions); + public static extern bool OpenPrinter2(string pPrinterName, out SafeHPRINTER phPrinter, [In, Optional, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PRINTER_DEFAULTS_Marshaler))] PRINTER_DEFAULTS pDefault, in PRINTER_OPTIONS pOptions); /// The PrinterProperties function displays a printer-properties property sheet for the specified printer. /// A handle to the parent window of the property sheet. @@ -4857,7 +4849,7 @@ namespace Vanara.PInvoke [DllImport(Lib.Winspool, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("winspool.h", MSDNShortId = "9efc6629-dbb7-4320-90b9-07c66f0add47")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool ResetPrinter(HPRINTER hPrinter, in PRINTER_DEFAULTS pDefault); + public static extern bool ResetPrinter(HPRINTER hPrinter, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PRINTER_DEFAULTS_Marshaler))] PRINTER_DEFAULTS pDefault); /// The ScheduleJob function requests that the print spooler schedule a specified print job for printing. /// diff --git a/PInvoke/Printing/WinSpool.Structs.cs b/PInvoke/Printing/WinSpool.Structs.cs index 4d2aa78f..1c77862b 100644 --- a/PInvoke/Printing/WinSpool.Structs.cs +++ b/PInvoke/Printing/WinSpool.Structs.cs @@ -1998,17 +1998,15 @@ namespace Vanara.PInvoke // https://docs.microsoft.com/en-us/windows/win32/printdocs/printer-defaults typedef struct _PRINTER_DEFAULTS { LPTSTR pDatatype; // LPDEVMODE pDevMode; ACCESS_MASK DesiredAccess; } PRINTER_DEFAULTS, *PPRINTER_DEFAULTS; [PInvokeData("winspool.h", MSDNShortId = "df29c3a6-b1d1-4d40-887d-5ffc032a5871")] - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct PRINTER_DEFAULTS + public class PRINTER_DEFAULTS { /// Pointer to a null-terminated string that specifies the default data type for a printer. - [MarshalAs(UnmanagedType.LPTStr)] public string pDatatype; /// - /// Pointer to a DEVMODE structure that identifies the default environment and initialization data for a printer. + /// A DEVMODE structure that identifies the default environment and initialization data for a printer. /// - public IntPtr pDevMode; + public DEVMODE? pDevMode; /// /// @@ -2049,9 +2047,6 @@ namespace Vanara.PInvoke /// /// public ACCESS_MASK DesiredAccess; - - /// A DEVMODE structure that contains device-initialization and environment data for the printer driver. - public DEVMODE DevMode => pDevMode.ToStructure(); } /// @@ -3483,6 +3478,9 @@ namespace Vanara.PInvoke /// by other functions. /// public PRINTER_OPTION_FLAGS dwFlags; + + /// An instance with the size preset. + public static readonly PRINTER_OPTIONS Default = new PRINTER_OPTIONS { cbSize = 8 }; } /// @@ -3702,7 +3700,7 @@ namespace Vanara.PInvoke } /// Provides a for that is disposed using . - public class SafeHPRINTERCHANGENOTIFICATION : SafeHANDLE + public class SafeHPRINTERCHANGENOTIFICATION : SafeHANDLE, ISyncHandle { /// /// Initializes a new instance of the class and assigns an existing handle. @@ -3779,6 +3777,38 @@ namespace Vanara.PInvoke protected override bool InternalReleaseHandle() => FreePrinterNotifyInfo(handle); } + internal class PRINTER_DEFAULTS_Marshaler : ICustomMarshaler + { + /// Gets the instance. + /// The cookie. + /// An instance of this class. + public static ICustomMarshaler GetInstance(string _) => new PRINTER_DEFAULTS_Marshaler(); + + public void CleanUpManagedData(object ManagedObj) => throw new NotImplementedException(); + + public void CleanUpNativeData(IntPtr pNativeData) => Marshal.FreeCoTaskMem(pNativeData); + + public int GetNativeDataSize() => -1; + + public IntPtr MarshalManagedToNative(object ManagedObj) + { + if (!(ManagedObj is PRINTER_DEFAULTS pd)) throw new ArgumentException("Type of managed object must be PRINTER_DEFAULTS."); + + var sz = IntPtr.Size * 2 + 4 + StringHelper.GetByteCount(pd.pDatatype) + (pd.pDevMode?.dmSize ?? 0); + var mem = new SafeCoTaskMemHandle(sz); + using (var str = new NativeMemoryStream(mem)) + { + str.WriteReference(pd.pDatatype); + str.WriteReferenceObject(pd.pDevMode.HasValue ? (object)pd.pDevMode.Value : null); + str.Write((uint)pd.DesiredAccess); + } + + return mem.TakeOwnership(); + } + + public object MarshalNativeToManaged(IntPtr pNativeData) => throw new NotImplementedException(); + } + /* DOCEVENT_CREATEDPRE DOCEVENT_ESCAPE diff --git a/UnitTests/PInvoke/Printing/PrintingTests.cs b/UnitTests/PInvoke/Printing/PrintingTests.cs index fab036cf..4603b916 100644 --- a/UnitTests/PInvoke/Printing/PrintingTests.cs +++ b/UnitTests/PInvoke/Printing/PrintingTests.cs @@ -2,8 +2,13 @@ using NUnit.Framework.Constraints; using System; using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; using Vanara.Extensions; +using Vanara.InteropServices; using static Vanara.PInvoke.WinSpool; namespace Vanara.PInvoke.Tests @@ -22,6 +27,21 @@ namespace Vanara.PInvoke.Tests [OneTimeTearDown] public void _TearDown() => hprnt?.Dispose(); + [Test] + public void AddPrinterConnection2Test() + { + var drv = GetPrinter(hprnt).pDriverName; + Assert.That(AddPrinterConnection2(default, connPtrName, PRINTER_CONNECTION_FLAGS.PRINTER_CONNECTION_MISMATCH, drv), ResultIs.Successful); + Assert.That(DeletePrinterConnection(connPtrName), ResultIs.Successful); + } + + [Test] + public void AddPrinterConnectionTest() + { + Assert.That(AddPrinterConnection(connPtrName), ResultIs.Successful); + Assert.That(DeletePrinterConnection(connPtrName), ResultIs.Successful); + } + [Test] public void AddPrinterTest() { @@ -71,22 +91,6 @@ namespace Vanara.PInvoke.Tests Assert.That(DeletePrinterKey(p2, key), ResultIs.Successful); } } - - [Test] - public void AddPrinterConnectionTest() - { - Assert.That(AddPrinterConnection(connPtrName), ResultIs.Successful); - Assert.That(DeletePrinterConnection(connPtrName), ResultIs.Successful); - } - - [Test] - public void AddPrinterConnection2Test() - { - var drv = GetPrinter(hprnt).pDriverName; - Assert.That(AddPrinterConnection2(default, connPtrName, PRINTER_CONNECTION_FLAGS.PRINTER_CONNECTION_MISMATCH, drv), ResultIs.Successful); - Assert.That(DeletePrinterConnection(connPtrName), ResultIs.Successful); - } - [Test] public void AdvancedDocumentPropertiesTest() { @@ -198,6 +202,22 @@ namespace Vanara.PInvoke.Tests } } + [Test] + public void GetDefaultPrinterTest() + { + var sz = 260; + var sb = new StringBuilder(sz); + Assert.That(GetDefaultPrinter(sb, ref sz), ResultIs.Successful); + TestContext.WriteLine(sb); + } + + [Test] + public void GetPrintExecutionDataTest() + { + Assert.That(GetPrintExecutionData(out var data), ResultIs.Successful); + TestHelper.WriteValues(data); + } + [Test] public void JobTest() { @@ -222,6 +242,13 @@ namespace Vanara.PInvoke.Tests } } + [Test] + public void OpenPrinter2Test() + { + Assert.That(OpenPrinter2(defaultPrinterName, out var hprnt2, new PRINTER_DEFAULTS { DesiredAccess = (uint)AccessRights.PRINTER_ALL_ACCESS }, PRINTER_OPTIONS.Default), ResultIs.Successful); + hprnt2.Dispose(); + } + [Test] public void PortTest() { @@ -234,6 +261,12 @@ namespace Vanara.PInvoke.Tests Assert.That(SetPort(null, port, 0, 0), ResultIs.Successful); } + [Test] + public void PrinterPropertiesTest() + { + Assert.That(PrinterProperties(HWND.NULL, hprnt), ResultIs.Successful); + } + [Test] public void SpoolFileTest() { @@ -244,5 +277,51 @@ namespace Vanara.PInvoke.Tests Assert.That(CommitSpoolData(hprnt, hspf, (uint)bytes.Length), ResultIs.Successful); Assert.That(() => hspf.Dispose(), Throws.Nothing); } + + [Test] + public void StartWriteEndDocPagePrinterTest() + { + var log = new List(); + var cancel = false; + new Thread(ChangeThread).Start(); + var job = StartDocPrinter(hprnt, 1, new DOC_INFO_1 { pDatatype = "RAW", pDocName = "My Document" }); + Assert.That(job, ResultIs.Not.Value(0U)); + try + { + Assert.That(StartPagePrinter(hprnt), ResultIs.Successful); + try + { + using var s = new SafeCoTaskMemString("Testing this printer.", CharSet.Unicode); + Assert.That(WritePrinter(hprnt, s, s.Size, out var written), ResultIs.Successful); + Assert.That(written, Is.EqualTo((uint)s.Size)); + } + finally + { + Assert.That(EndPagePrinter(hprnt), ResultIs.Successful); + } + } + finally + { + Assert.That(EndDocPrinter(hprnt), ResultIs.Successful); + cancel = true; + TestContext.WriteLine(string.Join("\r\n", log)); + } + + void ChangeThread() + { + using var hChange = FindFirstPrinterChangeNotification(hprnt, PRINTER_CHANGE.PRINTER_CHANGE_ALL, PRINTER_NOTIFY_CATEGORY.PRINTER_NOTIFY_CATEGORY_2D); + while (!cancel) + { + if (Kernel32.WaitForSingleObject(hChange, 200) == Kernel32.WAIT_STATUS.WAIT_OBJECT_0) + { + if (FindNextPrinterChangeNotification(hChange, out var chg, default, out var ppi) && !ppi.IsInvalid) + { + PRINTER_NOTIFY_INFO pi = ppi; + log.Add($"{chg}: {string.Join(",", pi.aData?.Select(d => d.Field))}"); + } + } + } + } + } } } \ No newline at end of file