PDFCreator COM+ interface used in VB.Net : pb of Release() between calls (effective destruction of pointer to Com+ interface)

2006-07-26 17:26:40 by anonymous

Hello,

I would like to encapsulate PDFCreator into a VB.net 2.0 class and use it just to convert a word file into a pdf file using the COM+ interface.

I found several problems :
1/ PDFCreator is not stopped at the end of the conversion (GC.Collect() is insufficient)
2/ PDFCreator events are not self sufficient (without parameters) so it is necessary to have static class variables (Shared in VB.Net) which could be avoided by passing .Net standard event parameters, typically :

Public Event eReady(ByVal sender As System.Object, ByVal e As PDFCreator.EventArgsReady)

Public Event eError(ByVal sender As System.Object, ByVal e As PDFCreator.EventArgsError)

where :

- PDFCreator.EventArgsReady would inherit of System.EventArgs
- PDFCreator.EventArgsError would inherit of System.EventArgs
- PDFCreator.EventArgsError would contains a field of type : PDFCreator.clsPDFCreatorError


1/ Bad releasing at the end of the job (detailled)
----------------------------------------

All is fine until that but the releasing of PDFCreator at the end of the use of one conversion.
In the example of COM and VB, the GC.collect is used but in reality PDFCreator only stop at the end of the application.

A solution proposed by VB.Net 2.0 is to use the instruction "using" like this :

Using myPDFCreator As New PDFCreator.clsPDFCreator

' use myPDFCreator to convert Word to pdf
'...

End Using ' ensure the device is freed by calling its Dispose() function

unfortunatelly the only constraint for this is that PDFCreator implements IDisposable interface which contains the only function Dispose().

Do you think it would be possible to implement these modifications in a next version or somethink similar to solve these problems ?

Or perhaps it exists other solution ?

Thanks in advance,

Xavier Mirabel


PS : here is the code of the class which has to be call (for the moment ) like this :

dim docFullPath as string = "c:\example\cfile.doc"

ConvertisseurVersPDF.convertirVersPDF(docFullPath)

' and the result is in "c:\example\cfile.pdf"


'-----------------------------------------------


Imports PDFCreator
Imports System.IO
Imports System.Threading

Public Class ConvertisseurVersPDF
'Implements IDisposable

#Region "attributs"

Private Shared _autoEventFinAttentePDFCreator As New AutoResetEvent(False) ' état initial = non signalé
Private Shared _duréeMaximaleDAttente As New TimeSpan(0, 1, 0) ' hh, mm, ss

'Private WithEvents _PDFCreator As PDFCreator.clsPDFCreator
'Private Shared _pErr As PDFCreator.clsPDFCreatorError

Private Shared _étatPrêt As Boolean = False
Private Shared _erreurReçue As Boolean = False

Private Shared _imprimanteParDéfaut As String

#End Region

#Region "constructeur / destructeur"

' constructeur interdit : classe statique
Private Sub New()
'démarrerPDFCreator()
End Sub

' destructeur interdit : classe statique
Private Sub Dispose() 'Implements IDisposable.Dispose
'arrêterPDFCreator()
End Sub

Private Shared Sub démarrerPDFCreator(ByRef _PDFCreator As clsPDFCreator, _
ByRef _pErr As clsPDFCreatorError)
Try
' Instancier
_pErr = New PDFCreator.clsPDFCreatorError
_PDFCreator = New PDFCreator.clsPDFCreator


' Se mettre à l'écoute des événements
AddHandler _PDFCreator.eReady, AddressOf PDFCreator_Ready
AddHandler _PDFCreator.eError, AddressOf PDFCreator_eError

' démarrer PDFCreator
Dim parameters As String = "/NoProcessingAtStartup"

If Not _PDFCreator.cStart(parameters) Then
Throw New Exception("Status: Error[" & _pErr.Number & "]: " & _pErr.Description)
End If

Catch ex As Exception
Debug.Fail(ex.ToString)
Throw
End Try
End Sub

Private Shared Sub arrêterPDFCreator(ByRef _PDFCreator As clsPDFCreator, _
ByRef _pErr As clsPDFCreatorError)
Try
' fermer PDFCreator
If _PDFCreator IsNot Nothing Then
_PDFCreator.cPrinterStop = True
_PDFCreator.cDefaultPrinter = _imprimanteParDéfaut

_PDFCreator.cClose()
'_PDFCreator.Dispose() 'n'existe pas !!!
_PDFCreator = Nothing
End If

_pErr = Nothing

' libérer la mémoire (demande effective et immédiate de delete vers l'interface COM+)
GC.Collect()

Catch ex As Exception
Debug.Fail(ex.ToString)
End Try
End Sub

#End Region

#Region "actions publiques"

' convertit le fichier dans le même répertoire en changeant juste l'extension
Public Shared Sub convertirVersPDF(ByVal nomfichierSource As String)

Dim _pErr As clsPDFCreatorError = Nothing
Dim _PDFCreator As clsPDFCreator = Nothing

Try
'Préconditions
If nomfichierSource Is Nothing OrElse Not File.Exists(nomfichierSource) Then Exit Try

'Préconditions
'If _PDFCreator Is Nothing Then
' démarrerPDFCreator()
'End If

_erreurReçue = False

'Using _PDFCreator As New clsPDFCreator

démarrerPDFCreator(_PDFCreator, _pErr)

' conversion non encore effectuée
_étatPrêt = False

Dim Filetyp As Integer = 0 'pdf

Dim fname As String = ""
Dim fi As New FileInfo(nomfichierSource)
If fi.Name.Length > 0 Then
If InStr(fi.Name, ".", CompareMethod.Text) > 1 Then
fname = Mid(fi.Name, 1, InStr(fi.Name, ".", CompareMethod.Text) - 1)
Else
fname = fi.Name
End If
End If
If Not _PDFCreator.cIsPrintable(fi.FullName) Then
Throw New Exception("File '" & fi.FullName & "' is not printable!")
End If

Dim opt As clsPDFCreatorOptions = _PDFCreator.cOptions
With opt
.UseAutosave = 1
.UseAutosaveDirectory = 1
.AutosaveDirectory = fi.DirectoryName
.AutosaveFormat = Filetyp
If Filetyp = 5 Then ' format destination tiff
.BitmapResolution = 72
End If
opt.AutosaveFilename = fname
End With

With _PDFCreator
.cOptions = opt
.cClearCache()

' mémoriser l'ancienne imprimante par défaut
_imprimanteParDéfaut = .cDefaultPrinter
.cDefaultPrinter = "PDFCreator"

' imprimer le fichier
.cPrintFile(fi.FullName)

.cPrinterStop = False
End With

' attendre la fin du travail
' When autoEvent signals, arrêter d'attendre
_autoEventFinAttentePDFCreator.WaitOne(_duréeMaximaleDAttente, False)

If _PDFCreator IsNot Nothing Then
' lire une éventuelle erreur
If _erreurReçue Then
_pErr = _PDFCreator.cError
'Throw New Exception("Status: Error[" & _pErr.Number & "]: " & _pErr.Description)
End If

' Arrêter l'impression de PDFCreator
_PDFCreator.cPrinterStop = True
_PDFCreator.cDefaultPrinter = _imprimanteParDéfaut
End If

' Signaler une erreur d'impression, si nécessaire
If Not _étatPrêt Then
Throw New Exception("Conversion vers PDF : Erreur dépassement du délai")
End If

'End Using

Catch ex As Exception
Debug.Fail(ex.ToString)
Throw
Finally
' postcondition
If _PDFCreator IsNot Nothing Then
arrêterPDFCreator(_PDFCreator, _pErr)
End If
End Try
End Sub

#End Region

#Region "événements"

Private Shared Sub PDFCreator_Ready()
Try
_étatPrêt = True

'MessageBox.Show("Status: """ & _PDFCreator.cOutputFilename & """ was created!")

' Arrêter l'impression de PDFCreator
'If _PDFCreator IsNot Nothing Then
' _PDFCreator.cPrinterStop = True
' _PDFCreator.cDefaultPrinter = _imprimanteParDéfaut
'End If

' signaler au thread de lancement la fin de l'action de PDF Creator
_autoEventFinAttentePDFCreator.Set()

Catch ex As Exception
Debug.Fail(ex.ToString)
End Try
End Sub

Private Shared Sub PDFCreator_eError()
Try
_erreurReçue = True

'If _PDFCreator IsNot Nothing Then
' _pErr = _PDFCreator.cError
' Throw New Exception("Status: Error[" & _pErr.Number & "]: " & _pErr.Description)
'End If

' signaler au thread de lancement la fin de l'action de PDF Creator
_autoEventFinAttentePDFCreator.Set()

Catch ex As Exception
Debug.Fail(ex.ToString)
Throw
End Try
End Sub

#End Region

End Class






2006-07-26 19:23:06 by anonymous


I found a palliative solution to the problem of non destruction of PDFCreator instance after conversion : to kill its process by program...

Here is the code to add after GC.Collect() in the code of the previous mail :


' Get all instances of PdfCreator running on
' the local computer :

Dim localByName As Process() _
= Process.GetProcessesByName("PDFCreator")

If localByName IsNot Nothing Then
For Each proc As Process In localByName
proc.Kill() ' kill the process
Next
End If


Xavier Mirabel



2006-07-26 19:59:44 by thesmilyface

At first thanks for your comment. I'm a beginner in .Net. The samples are very simple samples and I understand if .Net experts can improve the code. ;-)

1.) I step through the .Net samples and I can see that after a GC.Collect the PDFCreator is removed from the memory. But it is possible that PDFCreator is not removed immediately from the memory. So it is possible if you start PDFCreator immediately after a GC.Collect you get an error. But this a .Net problem. I've read in .Net article that ms will improve the behaviour of the GC in .Net 3.0.

2.)
Public Event eReady(ByVal sender As System.Object, ByVal e As PDFCreator.EventArgsReady)

Public Event eError(ByVal sender As System.Object, ByVal e As PDFCreator.EventArgsError)

How can I do this with VB6?

Frank


2006-07-26 20:12:08 by thesmilyface

>I found a palliative solution to the problem of non
>destruction of PDFCreator instance after conversion : to
>kill its process by program...

:-(
This is not a good solution. Have you the last sample from version 0.9.3. Please test it.

Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
_PDFCreator.cClose()
System.Runtime.InteropServices.Marshal.ReleaseComObject(_PDFCreator)
System.Runtime.InteropServices.Marshal.ReleaseComObject(pErr)
pErr = Nothing
GC.Collect()
End Sub

It is importent that you use the function "ReleaseComObject".

Frank


2006-07-28 17:55:58 by anonymous


Good afternoon,

- I am not able to download sources from http://www.pdfforge.org/products/pdfcreator/download

It seems the zip file is corrupted ?
And there is only v0.9.2.

- If all your code is in VB6 and if there is not too much GUI, you should translate it to VB.Net 2005 (VB8) with the Wizard of Visual Studio 2005 (if you have got it...).
It means that probably you will have to update a part of the code manually, mainly for the GUI, but not too much more.

Then it would be very easy to do what I said and as we would not need to call COM+ interface, numbers of problems would disappear.

Very interesting for a lot of people !

- How to do this in VB 6 :

Public Event eReady(ByVal sender As System.Object, ByVal e As PDFCreator.EventArgsReady)
Public Event eError(ByVal sender As System.Object, ByVal e As PDFCreator.EventArgsError)

In VB6 I do not know how coding COM+, but in C++, I think there is a correspondance between IDispatch (COM+) and Object (.Net).
I do not think it is possible to inherit from System.EventArgs because it is a .Net class.
But you can call it PDFCreator_EventArgsError...

I think the best way is to convert into VB.Net 2005.

- I think it could be very interesting to code a function with the following shape :

Public Function Convert _
(ByVal SourceDocumentFileName As String, _
ByVal DestinationDocumentFileName As String, _
ByVal DestinationFileType As PDFCreator.FileType) _
As Boolean

which responds true if it worked and false else.

Or instead of a function, a subroutine which throw an exception in case of error, in the same thread (it is very easy in .Net to make an asynchronous call if it is necessary to be non-blocking but it is more complicated to synchronise events in different threads).

Kind regards,


Xavier Mirabel


2006-07-28 17:58:41 by anonymous


I tried it, but it did not work neither.


2006-07-28 19:18:36 by thesmilyface

>- I am not able to download sources from >http://www.pdfforge.org/products/pdfcreator/download
>It seems the zip file is corrupted ?
>And there is only v0.9.2.
I found no problems. Works fine with winrar.

And you should use svn to get the current source code. See the sourceforge homepage.

>If all your code is in VB6 and if there is not too much GUI,
>you should translate it to VB.Net 2005 (VB8) with the Wizard
>of Visual Studio 2005 (if you have got it...).
Beleave it I have check it. This is impossible at the moment. ;-) (PDFCreator has ~ 38000 code lines, + TransTool + PDFSpooler + InstalPrinter + Setup etc.)

>Public Function Convert (ByVal SourceDocumentFileName As ...
Better:
Public Function ReadMyMindsAndDoWhatIWant()
;-)
PDFCreatot is "only" a postscript converter using ghostscript. To convert a file this file must printed with a postscript printer driver. This file can PDFCreator convert to ghostscript output format.

This and some other things are the reasons why PDFCreator is NOT thread safe.

Sorry.

Frank


2006-08-02 10:03:31 by anonymous


Hello,

This is a reply to Frank about :
>Public Function Convert (ByVal SourceDocumentFileName As ...
Better:
Public Function ReadMyMindsAndDoWhatIWant()
;-)

I have no problem with your humour, it is just that if you use the code I gave in this forum, it does exactly this function with just these restriction :

Convert (SourceDocumentPathNameWithoutExtension.doc, _
SourceDocumentPathNameWithoutExtension.pdf, _
PdfFileType)

and it is very easy to extends it to what I said.

The only thing I said was to incorporate this code into PDFCreator for everybody, that's all.


About the fact that PDFCreator depends on the singleness of Gostscript, this is just the same problem as a lot of drivers.

The nice solution with .Net is to use .Net Remoting.
That means that PDFCreatorServer should be a Windows Service with a PDFCreator class in it that inherits from MarshalByRefObject (.Net Remoting class) and configurated as a singleton in a configuration file, for instance :





mode="Singleton"
type="PDFCreatorServer.PDFCreator, PDFCreatorServer"
objectUri="PDFCreator"/>








So the service has to start with :
RemotingConfiguration.Configure(serverConfigFile, False)


And any client application which wants to connect to this single server create a .Net Remoting Client with :

RemotingConfiguration.Configure(clientConfigFile, False)
Dim PDFCreator As New PDFCreatorServer
PDFCreator.Convert(...)
etc...

where the client configuration file is like this :





type="PDFCreatorServer.PDFCreator, PDFCreatorServer"
url="tcp://localhost:7000/PDFCreatorService/PDFCreator" />








That's what I wanted to suggest, which is a really .Net solution with no more release or multiclient problems.

I hope that can help.
Regards,

Xavier Mirabel




2006-08-11 17:01:10 by anonymous


I have just seen that configuration file contents are not in the previous message. I think the reason is that they are XML coded and they have been probably filtered by your HTML page of sending response to your forum.

I have not time to rewrite them, but I will do it later (after some imminent hollidays) ;-)

Regards,

Xavier Mirabel