从Windows服务打印Xps

时间:2011-05-25 11:10:51

标签: printing windows-services xps

我正在尝试从.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-a-document-on-a-server-via-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
}
}

注意:有些链接可以获得更多信息:

1 个答案:

答案 0 :(得分:5)

我与microsoft谈到了这个问题,我们发现问题与打印机假脱机程序中不正确的字体替换有关。当打印机设置为不对齐文档时,它们也可以从Windows服务中正确打印。否则,除arial(以及其他一些)之外的所有字体都将被另一种字体替换。在我提供的样本中,calibri由wingdings代替。

所以,他们承认这是一个错误,但目前他们不会解决它。这将取决于有多少人会受到这个bug的影响,以便他们决定是否愿意修复它...