13

With the sharpPDF library I generate a pdf memory stream, and I want to send it directly via email. But the line ms.Seek(.... gives an ObjectDisposedException;

Cannot access a closed Stream.

The pdf.CreatePDF method takes either an (output) fileName string, or an (out)Stream. But I guess it also closes the stream? I'm not used to work much with streams, so if you could please advise how it should be done?

The sharpPDF source code of the CreatePDF method can be found here:

http://www.java2s.com/Open-Source/CSharp/PDF/SharpPDF/sharpPDF/pdfDocument.cs.htm

Public Sub SendPDF()
   Dim pdf As New sharpPDF.pdfDocument("Title", "Author")

   '....Generate pdf content

   Dim ms As New IO.MemoryStream
   pdf.CreatePDF(ms)

   Dim email As New EmailService
   email.Send(ms)

End Sub

Public Class EmailService
   Public Sub Send(Byval ms as Stream)
        ms.Seek(0, IO.SeekOrigin.Begin)

        Dim atc As New Attachment(ms, "Report.pdf")
        mail.Attachments.Add(atc)

        '....set other email parameters

        client.SendAsync(mail, mail.Subject)
  End Sub 
End Class
bretddog
  • 5,411
  • 11
  • 63
  • 111

1 Answers1

38

One simple approach is to get the byte array out of the closed MemoryStream and create another one:

pdf.CreatePDF(ms)
ms = new MemoryStream(ms.ToArray())

Dim email As New EmailService
email.Send(ms)

Note that it's fine to call MemoryStream.ToArray on a closed / disposed instance of MemoryStream. It's even documented:

Note
This method works when the MemoryStream is closed.

jao
  • 18,273
  • 15
  • 63
  • 96
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Jon, if the OP is getting an ObjectDisposedException, wouldn't that likely make it impossible to access any members (assuming a good implementation of the Disposable pattern)? – Igby Largeman Nov 11 '11 at 22:25
  • 2
    @Charles: Nope - you can still call ToArray on a disposed MemoryStream. Will edit to make this clear. – Jon Skeet Nov 11 '11 at 22:33
  • @Jon, thanks! this works. For my understanding though; Is it considered strange that a method taking an out-stream parameter closes the stream? – bretddog Nov 11 '11 at 22:37
  • @bretddog: Yes - I wouldn't personally have expected it to do that. That code looks pretty grim in terms of .NET conventions too. I would look for alternative libraries, to be honest... it doesn't bode well. – Jon Skeet Nov 11 '11 at 22:38
  • 1
    Oh, cool. (I shoulda known Jon Skeet wouldn't make a dumb mistake like that anyway.) – Igby Largeman Nov 11 '11 at 22:42
  • @Jon, cheers! I'm also testing PDFSharp/MigraDoc, so may go with that. Just looking for something efficient for typewriter style output. – bretddog Nov 11 '11 at 22:51
  • However, I don't think that it also works on a disposed MemoryStream as you state. – NoOne Jun 05 '13 at 16:53
  • @NoOne: I'm not sure what you mean. `ToArray` *definitely* works on a disposed MemoryStream. – Jon Skeet Jun 05 '13 at 16:56
  • The documentation mentions only for when it is 'closed'. Isn't 'disposed' a little diferent? Couldn't the GC collect it after it's disposed? – NoOne Jun 05 '13 at 17:08
  • @NoOne: No, absolutely not. You've still got a reference to the object, so it can't be collected. Really and truly, it *does* work on a disposed MemoryStream. Do you have any *evidence* to the contrary? – Jon Skeet Jun 05 '13 at 17:11
  • No, it just sounded strange to me. But you may be right. However, it's not documented so perhaps it's not so safe to do sth like that. E.g. I was thinking that the internal buffer of `ms` might be freed on Dispose(). – NoOne Jun 05 '13 at 17:16
  • @NoOne: It really isn't. For streams, `Dispose` and `Close` is the same thing - and for `MemoryStream`, it basically just remembers that it's closed to stop `Read` and `Write` (etc) calls. Even though it's not explicitly documented, it really is safe - if they changed that now, it would break a *lot* of code. – Jon Skeet Jun 05 '13 at 17:18
  • Is creating a duplicate buffer the only way to get the length of the closed data stream? Seems kinda kludgy... (I've already got 3 copies of data I'm working with in a piece of code that, oddly enough, is invoking PDFSharp) Guess I need to write a wrapper for PDFSharp: `class UncloseableMemoryStream: MemoryStream {};` copies are: DB read byte[], working memory stream after changes, non-seekable HTTP output stream for the result... ugh. –  Apr 28 '14 at 20:27
  • @ebyrob: As far as I know, yes. Do you actually have a *problem* with there being three copies, if the garbage collector takes care of them reasonably quickly? – Jon Skeet Apr 29 '14 at 05:42
  • @JonSkeet *Do you actually have a problem with there being three copies?* How could I not? Using small chunks helps, but it's not efficient... gauranteed 1/3 of max speed, large sizes I'm already limited by available main-memory. (Note: apparently there is now a "don't close" flag in the PDFSharp `.Save()` method call. Thank goodness I won't need 4 copies. 2 should be enough for anyone.) –  Apr 29 '14 at 13:28
  • @ebyrob: You could "not have a problem" if your application performs perfectly adequately without doing anything else. I didn't have the information that your application *was* limited by memory. – Jon Skeet Apr 29 '14 at 13:41
  • @JonSkeet Well, due to limiting working set and smaller file sizes, I do OK, but the overall performance is very slow... I'm zipping and outputting a book full of PDF images (500 pages) on a web server. I'd like the operation to take 15 seconds, not 2 minutes... (3 buffers could easily raise 15 seconds to 30 seconds in this case and it all adds up. Actually right now PDFSharp is raising 15 seconds to over a minute unfortunately. I might have to write my own meta-data tool for PDF.) The postscript pages I can just string-search for `\n%%` thank goodness. –  Apr 29 '14 at 13:45