在不预览的情况下打印本地报表 - 流大小超过限制或GDI+发生通用错误 C#

8
我正在使用这篇文章,直接将我的rdlc打印到打印机上,但是当我试图通过传递流来创建Metafile对象时,它会给出错误。(在GDI+中发生了一般性错误
代码:
 using System;
    using System.IO;
    using System.Data;
    using System.Text;
    using System.Drawing.Imaging;
    using System.Drawing.Printing;
    using System.Collections.Generic;
    using System.Windows.Forms;
    using Microsoft.Reporting.WinForms;

    public class Demo : IDisposable
    {
        private int m_currentPageIndex;
        private IList<Stream> m_streams;

        // Routine to provide to the report renderer, in order to
        //    save an image for each page of the report.
 private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            DataSet ds = new DataSet();
            ds.Tables.Add(dsData.Tables[0].Copy());
            using (MemoryStream stream = new MemoryStream())
            {
                IFormatter bf = new BinaryFormatter();
                ds.RemotingFormat = SerializationFormat.Binary;
                bf.Serialize(stream, ds);
                data = stream.ToArray();
            }

            Stream stream1 = new MemoryStream(data);
            m_streams.Add(stream1);
            return stream1;
        }
        // Export the given report as an EMF (Enhanced Metafile) file.
        private void Export(LocalReport report)
        {
            string deviceInfo =
              @"<DeviceInfo>
                    <OutputFormat>EMF</OutputFormat>
                    <PageWidth>8.5in</PageWidth>
                    <PageHeight>11in</PageHeight>
                    <MarginTop>0.25in</MarginTop>
                    <MarginLeft>0.25in</MarginLeft>
                    <MarginRight>0.25in</MarginRight>
                    <MarginBottom>0.25in</MarginBottom>
                </DeviceInfo>";
            Warning[] warnings;
            m_streams = new List<Stream>();
            report.Render("Image", deviceInfo, CreateStream,
               out warnings);
            foreach (Stream stream in m_streams)
                stream.Position = 0;
        }
        // Handler for PrintPageEvents
        private void PrintPage(object sender, PrintPageEventArgs ev)
        {
            Metafile pageImage = new
               Metafile(m_streams[m_currentPageIndex]);

            // Adjust rectangular area with printer margins.
            Rectangle adjustedRect = new Rectangle(
                ev.PageBounds.Left - (int)ev.PageSettings.HardMarginX,
                ev.PageBounds.Top - (int)ev.PageSettings.HardMarginY,
                ev.PageBounds.Width,
                ev.PageBounds.Height);

            // Draw a white background for the report
            ev.Graphics.FillRectangle(Brushes.White, adjustedRect);

            // Draw the report content
            ev.Graphics.DrawImage(pageImage, adjustedRect);

            // Prepare for the next page. Make sure we haven't hit the end.
            m_currentPageIndex++;
            ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
        }

        private void Print()
        {
            if (m_streams == null || m_streams.Count == 0)
                throw new Exception("Error: no stream to print.");
            PrintDocument printDoc = new PrintDocument();
            if (!printDoc.PrinterSettings.IsValid)
            {
                throw new Exception("Error: cannot find the default printer.");
            }
            else
            {
                printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
                m_currentPageIndex = 0;
                printDoc.Print();
            }
        }
        // Create a local report for Report.rdlc, load the data,
        //    export the report to an .emf file, and print it.
        private void Run()
        {
            LocalReport report = new LocalReport();
           LocalReport report = new LocalReport();
            report.ReportPath = @"Reports\InvoiceReportTest.rdlc";
            report.DataSources.Add(
               new ReportDataSource("DataSet1", dsPrintDetails));
            Export(report);
            Print();
        }

        public void Dispose()
        {
            if (m_streams != null)
            {
                foreach (Stream stream in m_streams)
                    stream.Close();
                m_streams = null;
            }
        }

        public static void Main(string[] args)
        {
            using (Demo demo = new Demo())
            {
                demo.Run();
            }
        }
    }

当流大小超过或rdlc静态内容过多时,会导致错误。
我使用的数据集如下所示: enter image description here 我不知道静态内容是否应该影响流大小,但是如果我从rdlc中删除一些内容,则不会出现任何错误。但是当我再次添加它时,会抛出一个错误(GDI+发生了一般性错误)。

@vasek 如何查找元文件的大小?如何将元文件转换为位图?打印机是否会获取位图数据? - 3 rules
@3rules,您可以通过将流的内容转储到某个二进制文件中来确定二进制大小 - 正如jdweng所建议的那样。然后,您应该能够查看元文件大小并尝试确定失败的原因 - 也许您的报告对于Metafile类的实现来说过于复杂了。 - vasek
@3rules 顺便说一下,这个可能是相关的,而这个可能是一个解决方案。 - vasek
@vasek 哦,谢谢,我会研究一下并应用。 - 3 rules
请分享您的堆栈内部异常信息。 - Ramankingdom
显示剩余12条评论
2个回答

4
一个通用的错误异常信息非常不好诊断。它除了“没有成功”以外并不能提供很多信息。当 Graphics 类使用绘图对象或将绘图命令呈现到底层设备上下文时遇到问题时,就会引发此异常。从您对此代码进行的故障排除和分析中可以明显地看出其中的原因:程序用完了内存。
Graphics 类将其底层设备上下文视为非托管资源,这是为什么您无法获得更明显的 OutOfMemoryException 异常的基本原因。通常情况下,例如当您用它渲染到屏幕或打印机上时,这种情况并不会发生,但在此情况下它还是出现了,因为它需要呈现到 MemoryStream 中。一些几率是您可以在 VS 输出窗口中看到第一次机会通知。在任务管理器中添加提交大小列可以提供额外的诊断信息,当它超过 1GB 时,问题便开始出现。
尤其值得注意的是,这段代码的程序总是会因此异常而失败。如果给它一个包含太多页面或数据表格记录的报告,它注定要失败。它始终需要太多内存来存储元文件记录在内存流中。您能做的唯一一件事情就是使程序更加内存高效,以便能够处理生产需求。这里有很多机会。
第一个观察结果是您从 MSDN 代码示例中继承了一些松散的东西。这是常见的,并且应该注意的一般性问题,此类示例侧重于演示编码技巧。使代码防弹会妨碍其执行任务,因此未经测试或留作读者练习的部分并不少见。需要注意的是它忽略了太多的 Dispose() 需求。提供的 Dispose() 方法实际上并没有 accomplishing 任何操作,disposing 内存流只是标记它不可读。它没做到的是正确地处理 Metafile、LocalReport 和 PrintDocument 对象。使用 using 语句纠正这些遗漏。
第二个观察结果是对 CreateStream() 方法的添加非常浪费。同时还是不好的浪费,它会对大对象堆造成很大压力。不需要复制 DataTable,报告不写入它。也不需要将 MemoryStream 转换为数组并再次创建 MemoryStream,第一个 MemorySream 已经可以用了。不要使用 using,将其 Position 设置为 0 即可,这很可能就足以解决问题。
如果您仍然遇到麻烦,则应考虑使用 FileStream 而不是 MemoryStream。它同样高效,操作系统会确保如此,只需要为文件选一个名称。这里不是真正的问题,使用 Path.GetTempFileName() 即可。请注意,Dispose() 方法现在变得有用和必要,您还需要删除该文件。或者更好的方法是,在打开文件时使用 FileOptions.DeleteOnClose 选项,这样它就是自动化的了。

最后但并非最不重要的是,您需要利用操作系统的能力,现代计算机可以提供数千兆字节的地址空间,LOH碎片永远不会成为问题。 项目 > 属性 > 构建选项卡 > 取消选中“优先使用32位”复选框。 对于发布配置也做同样的操作。 当您遇到内存不足问题时,您绝不会优先选择它。


谢谢您的回答,但我没有完全理解清楚。能否请您给我一个简单的解释,以便我能够理解呢?顺便说一下,我已经得到了符合我的问题的解决方案,但我仍然想知道为什么会发生这种情况! - 3 rules
您的程序已耗尽内存,请提供更多内存。 - Hans Passant
哦,但先生,怎么做呢? - 3 rules

1

在我的端口使用与您相同的功能并遇到相同的问题,不知道为什么。我使用提供的函数,但在我的端口运行,所以使用此函数可能能解决您的问题:

private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            Stream stream = new MemoryStream();
            m_streams.Add(stream);
            return stream;
        }

哇,它正在运行,谢谢。但是我有点困惑,如果我们只是在函数中创建空流并返回它,那么这个函数就没有用了,对吧? - 3 rules
@3rules 我也不知道确切的原因,但是当你调用report.Render("Image", deviceInfo, CreateStream,out warnings);时,你必须定义回调函数,就像我所做的那样,它可以运行。但是一旦我知道原因,我肯定会更新答案。 - Mahavirsinh Padhiyar
你可以参考Hans Passant的回答,它提供了与你问题相同的解决方案。仔细阅读后,你可以将其选为最佳答案。 - Mahavirsinh Padhiyar
我不理解它,所以我告诉他用更简单的方式解释,等我理解后再做。而且你的答案正是我正在寻找的答案,人们会直接使用它并且可以通过Hans Passant先生的回答了解为什么会发生这种情况。 - 3 rules

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接