从 Windows 服务进行 XPS 打印

发布于 2024-11-10 02:01:14 字数 10388 浏览 8 评论 0 原文

我正在尝试从 .net 框架上的 Windows 服务打印 XPS 文档。由于 Microsoft 不支持使用 System.Drawing.Printing 或 System.Printing (WPF) 进行打印,因此我使用本机 XPSPrint API。 Aspose 在 http://www.aspose.com/documentation/.net-components/aspose.words-for-.net/howto-print-服务器上的文档通过-the-xpsprint-api.html

当我尝试从 Windows 服务打印 XPS 文档时,结果包含奇怪的字符而不是我想要的文本。

我尝试使用不同的打印机(包括虚拟打印机,例如 PDFCreator)、服务的不同用户和用户权限、不同的 xps 生成器(aspose、word 2007、word 2010)、不同的平台(windows 7、windows 2008 R2),但所有有相同的结果。

有人知道如何解决这个问题吗?任何帮助将不胜感激!

对于那些想尝试的人,我通过以下方式分享了一些文件:

https:// docs.google.com/leaf?id=0B4J93Ly5WzQKNWU2ZjM0MDYtMjFiMi00NzM0LTg4MTgtYjVlNDA5NWQyMTc3&hl=nl

  • document.xps:用于打印的 XPS 文档
  • document_printed_to_pdfcreator.pdf:演示出现问题的打印文档
  • XpsPrintTest.zip:示例 VS2010解决方案与示例代码

托管 Windows 服务的示例代码为:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;

namespace PrintXpsService
{
public partial class XpsPrintService : ServiceBase
{
    // Change name of printer here
    private String f_printerName = "PDFCreator";

    // path to some file where logging is done
    private String f_logFile = @"C:\temp\testdoc\xps_printing_service_log.txt";

    // path to xps file to print
    private String f_xpsFile = @"C:\temp\testdoc\document.xps";

    public XpsPrintService()
    {
        InitializeComponent();
    }

    private void Log(String fmt, params Object[] args)
    {
        try
        {
            DateTime now = DateTime.Now;

            using (StreamWriter wrt = new StreamWriter(f_logFile, true))
            {
                wrt.Write("{0} {1} - ", now.ToShortDateString(), now.ToShortTimeString());
                wrt.WriteLine(fmt, args);
            }
        }
        catch (Exception ex)
        {
        }
    }

    protected override void OnStart(string[] args)
    {
        // uncomment to allow to connect debugger
        //int i = 0;
        //while (i == 0)
        //{
        //    if (i == 0)
        //    {
        //        Thread.Sleep(1000);
        //    }
        //}

        Log("Starting Service");
        try
        {
            Log("Printing xps file {0}", f_xpsFile);

            using (Stream stream = new FileStream(f_xpsFile, FileMode.Open, FileAccess.Read))
            {
                Log("Starting to print on printer {0}", f_printerName);
                String jobName = f_xpsFile;
                this.Print(stream, jobName);
            }
            Log("Document printed");
        }
        catch (Exception ex)
        {
            Log("Exception during execution: {0}", ex.Message);
            Log("  {0}", ex.StackTrace);
            Exception inner = ex.InnerException;
            while (inner != null)
            {
                Log("=== Inner Exception: {0}", inner.Message);
                Log("    {0}", inner.StackTrace);
                inner = inner.InnerException;
            }
        }
    }

    protected override void OnStop()
    {
    }

    public void Print(Stream stream, String jobName)
    {
        String printerName = f_printerName;
        IntPtr completionEvent = CreateEvent(IntPtr.Zero, true, false, null);
        try
        {
            IXpsPrintJob job;
            IXpsPrintJobStream jobStream;

            StartJob(printerName, jobName, completionEvent, out job, out jobStream);
            CopyJob(stream, job, jobStream);
            WaitForJob(completionEvent, -1);
            CheckJobStatus(job);
        }
        finally
        {
            if (completionEvent != IntPtr.Zero)
                CloseHandle(completionEvent);
        }
    }

    private void StartJob(String printerName,
        String jobName, IntPtr completionEvent,
        out IXpsPrintJob job,
        out IXpsPrintJobStream jobStream)
    {
        int result = StartXpsPrintJob(printerName, jobName, null, IntPtr.Zero, completionEvent,
            null, 0, out job, out jobStream, IntPtr.Zero);
        if (result != 0)
            throw new Win32Exception(result);
    }


    private void CopyJob(Stream stream, IXpsPrintJob job, IXpsPrintJobStream jobStream)
    {
        try
        {
            byte[] buff = new byte[4096];
            while (true)
            {
                uint read = (uint)stream.Read(buff, 0, buff.Length);
                if (read == 0)
                    break;
                uint written;
                jobStream.Write(buff, read, out written);

                if (read != written)
                    throw new Exception("Failed to copy data to the print job stream.");
            }

            // Indicate that the entire document has been copied.
            jobStream.Close();
        }
        catch (Exception)
        {
            // Cancel the job if we had any trouble submitting it.
            job.Cancel();
            throw;
        }
    }

    private void WaitForJob(IntPtr completionEvent, int timeout)
    {
        if (timeout < 0)
            timeout = -1;

        switch (WaitForSingleObject(completionEvent, timeout))
        {
            case WAIT_RESULT.WAIT_OBJECT_0:
                // Expected result, do nothing.
                break;

            case WAIT_RESULT.WAIT_TIMEOUT:
                // timeout expired
                throw new Exception("Timeout expired");

            case WAIT_RESULT.WAIT_FAILED:
                throw new Exception("Wait for the job to complete failed");

            default:
                throw new Exception("Unexpected result when waiting for the print job.");
        }
    }

    private void CheckJobStatus(IXpsPrintJob job)
    {
        XPS_JOB_STATUS jobStatus;
        job.GetJobStatus(out jobStatus);
        switch (jobStatus.completion)
        {
            case XPS_JOB_COMPLETION.XPS_JOB_COMPLETED:
                // Expected result, do nothing.
                break;
            case XPS_JOB_COMPLETION.XPS_JOB_IN_PROGRESS:
                // expected, do nothing, can occur when printer is paused
                break;
            case XPS_JOB_COMPLETION.XPS_JOB_FAILED:
                throw new Win32Exception(jobStatus.jobStatus);
            default:
                throw new Exception("Unexpected print job status.");
        }
    }

    [DllImport("XpsPrint.dll", EntryPoint = "StartXpsPrintJob")]
    private static extern int StartXpsPrintJob(
        [MarshalAs(UnmanagedType.LPWStr)] String printerName,
        [MarshalAs(UnmanagedType.LPWStr)] String jobName,
        [MarshalAs(UnmanagedType.LPWStr)] String outputFileName,
        IntPtr progressEvent,   // HANDLE
        IntPtr completionEvent, // HANDLE
        [MarshalAs(UnmanagedType.LPArray)] byte[] printablePagesOn,
        UInt32 printablePagesOnCount,
        out IXpsPrintJob xpsPrintJob,
        out IXpsPrintJobStream documentStream,
        IntPtr printTicketStream);  // This is actually "out IXpsPrintJobStream", but we don't use it and just want to pass null, hence IntPtr.

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);

    [DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern WAIT_RESULT WaitForSingleObject(IntPtr handle, Int32 milliseconds);

    [DllImport("Kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);
}

/// <summary>
/// This interface definition is HACKED.
/// 
/// It appears that the IID for IXpsPrintJobStream specified in XpsPrint.h as 
/// MIDL_INTERFACE("7a77dc5f-45d6-4dff-9307-d8cb846347ca") is not correct and the RCW cannot return it.
/// But the returned object returns the parent ISequentialStream inteface successfully.
/// 
/// So the hack is that we obtain the ISequentialStream interface but work with it as 
/// with the IXpsPrintJobStream interface. 
/// </summary>
[Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D")]  // This is IID of ISequenatialSteam.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJobStream
{
    // ISequentualStream methods.
    void Read([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbRead);
    void Write([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbWritten);
    // IXpsPrintJobStream methods.
    void Close();
}

[Guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJob
{
    void Cancel();
    void GetJobStatus(out XPS_JOB_STATUS jobStatus);
}

[StructLayout(LayoutKind.Sequential)]
struct XPS_JOB_STATUS
{
    public UInt32 jobId;
    public Int32 currentDocument;
    public Int32 currentPage;
    public Int32 currentPageTotal;
    public XPS_JOB_COMPLETION completion;
    public Int32 jobStatus; // UInt32
};

enum XPS_JOB_COMPLETION
{
    XPS_JOB_IN_PROGRESS = 0,
    XPS_JOB_COMPLETED = 1,
    XPS_JOB_CANCELLED = 2,
    XPS_JOB_FAILED = 3
}

enum WAIT_RESULT
{
    WAIT_OBJECT_0 = 0,
    WAIT_ABANDONED = 0x80,
    WAIT_TIMEOUT = 0x102,
    WAIT_FAILED = -1 // 0xFFFFFFFF
}
}

注意:更多信息的一些链接:

I'm trying to print XPS documents from a windows service on the .net framework. Since Microsoft does not support printing by using System.Drawing.Printing nor by using System.Printing (WPF), I'm using the native XPSPrint API.
This is suggested to me by Aspose in http://www.aspose.com/documentation/.net-components/aspose.words-for-.net/howto-print-a-document-on-a-server-via-the-xpsprint-api.html.

When I try to print an XPS document from a windows service, the result contains strange characters instead of the text I want.

I tried with different printers (including virtual printers like for instance PDFCreator), different users and user-privileges for the service, different xps generators (aspose, word 2007, word 2010), different platforms (windows 7, windows 2008 R2) but all have the same result.

Does anybody knows how to solve this? Any help would be appreciated!

For those who want to try it, I shared some files via:

https://docs.google.com/leaf?id=0B4J93Ly5WzQKNWU2ZjM0MDYtMjFiMi00NzM0LTg4MTgtYjVlNDA5NWQyMTc3&hl=nl

  • document.xps: the XPS document to print
  • document_printed_to_pdfcreator.pdf: the printed document that demonstrates what is going wrong
  • XpsPrintTest.zip: a sample VS2010 solution with the sample code

The sample code for the managed windows service is:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;

namespace PrintXpsService
{
public partial class XpsPrintService : ServiceBase
{
    // Change name of printer here
    private String f_printerName = "PDFCreator";

    // path to some file where logging is done
    private String f_logFile = @"C:\temp\testdoc\xps_printing_service_log.txt";

    // path to xps file to print
    private String f_xpsFile = @"C:\temp\testdoc\document.xps";

    public XpsPrintService()
    {
        InitializeComponent();
    }

    private void Log(String fmt, params Object[] args)
    {
        try
        {
            DateTime now = DateTime.Now;

            using (StreamWriter wrt = new StreamWriter(f_logFile, true))
            {
                wrt.Write("{0} {1} - ", now.ToShortDateString(), now.ToShortTimeString());
                wrt.WriteLine(fmt, args);
            }
        }
        catch (Exception ex)
        {
        }
    }

    protected override void OnStart(string[] args)
    {
        // uncomment to allow to connect debugger
        //int i = 0;
        //while (i == 0)
        //{
        //    if (i == 0)
        //    {
        //        Thread.Sleep(1000);
        //    }
        //}

        Log("Starting Service");
        try
        {
            Log("Printing xps file {0}", f_xpsFile);

            using (Stream stream = new FileStream(f_xpsFile, FileMode.Open, FileAccess.Read))
            {
                Log("Starting to print on printer {0}", f_printerName);
                String jobName = f_xpsFile;
                this.Print(stream, jobName);
            }
            Log("Document printed");
        }
        catch (Exception ex)
        {
            Log("Exception during execution: {0}", ex.Message);
            Log("  {0}", ex.StackTrace);
            Exception inner = ex.InnerException;
            while (inner != null)
            {
                Log("=== Inner Exception: {0}", inner.Message);
                Log("    {0}", inner.StackTrace);
                inner = inner.InnerException;
            }
        }
    }

    protected override void OnStop()
    {
    }

    public void Print(Stream stream, String jobName)
    {
        String printerName = f_printerName;
        IntPtr completionEvent = CreateEvent(IntPtr.Zero, true, false, null);
        try
        {
            IXpsPrintJob job;
            IXpsPrintJobStream jobStream;

            StartJob(printerName, jobName, completionEvent, out job, out jobStream);
            CopyJob(stream, job, jobStream);
            WaitForJob(completionEvent, -1);
            CheckJobStatus(job);
        }
        finally
        {
            if (completionEvent != IntPtr.Zero)
                CloseHandle(completionEvent);
        }
    }

    private void StartJob(String printerName,
        String jobName, IntPtr completionEvent,
        out IXpsPrintJob job,
        out IXpsPrintJobStream jobStream)
    {
        int result = StartXpsPrintJob(printerName, jobName, null, IntPtr.Zero, completionEvent,
            null, 0, out job, out jobStream, IntPtr.Zero);
        if (result != 0)
            throw new Win32Exception(result);
    }


    private void CopyJob(Stream stream, IXpsPrintJob job, IXpsPrintJobStream jobStream)
    {
        try
        {
            byte[] buff = new byte[4096];
            while (true)
            {
                uint read = (uint)stream.Read(buff, 0, buff.Length);
                if (read == 0)
                    break;
                uint written;
                jobStream.Write(buff, read, out written);

                if (read != written)
                    throw new Exception("Failed to copy data to the print job stream.");
            }

            // Indicate that the entire document has been copied.
            jobStream.Close();
        }
        catch (Exception)
        {
            // Cancel the job if we had any trouble submitting it.
            job.Cancel();
            throw;
        }
    }

    private void WaitForJob(IntPtr completionEvent, int timeout)
    {
        if (timeout < 0)
            timeout = -1;

        switch (WaitForSingleObject(completionEvent, timeout))
        {
            case WAIT_RESULT.WAIT_OBJECT_0:
                // Expected result, do nothing.
                break;

            case WAIT_RESULT.WAIT_TIMEOUT:
                // timeout expired
                throw new Exception("Timeout expired");

            case WAIT_RESULT.WAIT_FAILED:
                throw new Exception("Wait for the job to complete failed");

            default:
                throw new Exception("Unexpected result when waiting for the print job.");
        }
    }

    private void CheckJobStatus(IXpsPrintJob job)
    {
        XPS_JOB_STATUS jobStatus;
        job.GetJobStatus(out jobStatus);
        switch (jobStatus.completion)
        {
            case XPS_JOB_COMPLETION.XPS_JOB_COMPLETED:
                // Expected result, do nothing.
                break;
            case XPS_JOB_COMPLETION.XPS_JOB_IN_PROGRESS:
                // expected, do nothing, can occur when printer is paused
                break;
            case XPS_JOB_COMPLETION.XPS_JOB_FAILED:
                throw new Win32Exception(jobStatus.jobStatus);
            default:
                throw new Exception("Unexpected print job status.");
        }
    }

    [DllImport("XpsPrint.dll", EntryPoint = "StartXpsPrintJob")]
    private static extern int StartXpsPrintJob(
        [MarshalAs(UnmanagedType.LPWStr)] String printerName,
        [MarshalAs(UnmanagedType.LPWStr)] String jobName,
        [MarshalAs(UnmanagedType.LPWStr)] String outputFileName,
        IntPtr progressEvent,   // HANDLE
        IntPtr completionEvent, // HANDLE
        [MarshalAs(UnmanagedType.LPArray)] byte[] printablePagesOn,
        UInt32 printablePagesOnCount,
        out IXpsPrintJob xpsPrintJob,
        out IXpsPrintJobStream documentStream,
        IntPtr printTicketStream);  // This is actually "out IXpsPrintJobStream", but we don't use it and just want to pass null, hence IntPtr.

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);

    [DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern WAIT_RESULT WaitForSingleObject(IntPtr handle, Int32 milliseconds);

    [DllImport("Kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);
}

/// <summary>
/// This interface definition is HACKED.
/// 
/// It appears that the IID for IXpsPrintJobStream specified in XpsPrint.h as 
/// MIDL_INTERFACE("7a77dc5f-45d6-4dff-9307-d8cb846347ca") is not correct and the RCW cannot return it.
/// But the returned object returns the parent ISequentialStream inteface successfully.
/// 
/// So the hack is that we obtain the ISequentialStream interface but work with it as 
/// with the IXpsPrintJobStream interface. 
/// </summary>
[Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D")]  // This is IID of ISequenatialSteam.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJobStream
{
    // ISequentualStream methods.
    void Read([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbRead);
    void Write([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbWritten);
    // IXpsPrintJobStream methods.
    void Close();
}

[Guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJob
{
    void Cancel();
    void GetJobStatus(out XPS_JOB_STATUS jobStatus);
}

[StructLayout(LayoutKind.Sequential)]
struct XPS_JOB_STATUS
{
    public UInt32 jobId;
    public Int32 currentDocument;
    public Int32 currentPage;
    public Int32 currentPageTotal;
    public XPS_JOB_COMPLETION completion;
    public Int32 jobStatus; // UInt32
};

enum XPS_JOB_COMPLETION
{
    XPS_JOB_IN_PROGRESS = 0,
    XPS_JOB_COMPLETED = 1,
    XPS_JOB_CANCELLED = 2,
    XPS_JOB_FAILED = 3
}

enum WAIT_RESULT
{
    WAIT_OBJECT_0 = 0,
    WAIT_ABANDONED = 0x80,
    WAIT_TIMEOUT = 0x102,
    WAIT_FAILED = -1 // 0xFFFFFFFF
}
}

Note: some links for more information:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

兮子 2024-11-17 02:01:14

我与微软讨论了这个问题,我们发现该问题与打印机后台处理程序中不正确的字体替换有关。当打印机设置为不后台打印文档时,它们也可以从 Windows 服务正确打印。否则,除了 arial(也许还有其他一些字体)之外的所有字体都将被另一种字体替换。在我提供的示例中,calibri 被 wingdings 替代。

因此,他们承认这是一个错误,但目前他们不会解决它。这将取决于有多少人会遭受这个错误的困扰,以便他们决定是否愿意修复它......

I talked with microsoft about this issue and we discovered the problem is related to incorrect font substitution in the printer-spooler. When the printer is set to not spool the documents, they are printed correctly, also from a windows service. Otherwise, all fonts, except arial (and maybe some others), are substituted by another font. In the sample I provided, calibri is substituted by wingdings.

So, they acknowledge this to be a bug but at the moment they will not resolve it. It will depend on how many people will suffer from this bug in order for them to decide whether are not they are willing to fix it...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文