ComWrapper - COM Instanz übernehmen


#1

Hallo PDFCreator-Freunde,

ich nutze die COMWrapper.dll für mein VB.net-Projekt. Alles läuft wunderbar. Nur wenn ich ein weiteres mal eine PDF-Datei konvertieren möchte, erhalte ich die Fehlermeldung, dass nur eine Instanz erlaubt sei. In dem Forum gesucht und gefunden: Nutze nun IsInstanceRunning(), wenn die diese True zurückgibt Kill ich die Prozesse die "PDFCreator" lauten.

Ich finde das keine schöne Lösung. Wäre es möglich, die bereits gestartete Instanz zu übernehmen?

Bei dem Kill-Vorgang habe ich außerdem das Problem, dass er teilweise die Queue nicht mehr prüft, obwohl was gedruckt wurde; es kommt ein Timeout-Fehler. Wenn man es anschließend nochmal versucht, funktioniert mein Programmablauf wieder.

Hier mein Code:
[PDFPrinting]

Imports System.Runtime.InteropServices
Imports pdfforge.PDFCreator.UI.ComWrapper

Namespace PDF
    ''' <summary>
    ''' Enthält Methoden, um eine PDF Datei zu drucken.
    ''' </summary>
    Public Class PDFPrinting
        Implements IDisposable

        Private _settings As PDFSettings = Nothing
        Private _queue As Queue = Nothing

        ''' <summary>
        ''' Gibt zurück, ob min. 1 PDF-Drucker installiert ist, welcher
        ''' verwendet werden kann.
        ''' </summary>
        ''' <returns><c>True</c> wenn min. 1 verwendbarer PDF-Drucker installiert ist; <c>False</c> andernfalls.</returns>
        Public ReadOnly Property PDFPrinterInstalled As Boolean
            Get
                Return IsPDFPrinterInstalled()
            End Get
        End Property

        ''' <summary>
        ''' Gibt die Einstellungen zurück, welche für den Druck gelten sollen.
        ''' </summary>
        ''' <returns></returns>
        Public ReadOnly Property PDFEinstellungen As PDFSettings
            Get
                Return _settings
            End Get
        End Property

        ''' <summary>
        ''' Erstellt eine neue Instanz, um PDF-Dateien zu drucken.
        ''' </summary>
        Public Sub New()
            Me.Init()
        End Sub

        ''' <summary>
        ''' Wartet auf einen Druckauftrag in der Warteschlange und 
        ''' wandelt den ersten Druckauftrag, welche auftritt in eine PDF-Datei um.
        ''' </summary>
        ''' <param name="FullPath">Vollständiger Pfad inkl. Dateiname, wo die Datei gespeichert werden soll.</param>
        ''' <exception cref="PDFException">Wenn ein Fehler auftritt beim Umwandeln.</exception>
        ''' <exception cref="TimeoutException">Wenn nach 15 Sekunden kein Druckauftrag in der Warteschlange eingegangen ist.</exception>
        ''' <remarks>
        ''' Befindet sich nicht innerhalb 15 Sekunden der Druckauftrag in der Warteschlange, wird der
        ''' Vorgang abgebrochen und eine Exception geworfen.
        ''' </remarks>
        Public Sub ConvertToPDF(ByVal FullPath As String)
            'Variablendeklaration
            Dim job As PrintJob = Nothing

            Try
                'Auf Druckjob in der Warteschlange warten
                If _queue.WaitForJob(15) = False Then
                    Throw New TimeoutException("Es konnte kein Druckauftrag in der Warteschlange festgestellt werden. Vorgang abgebrochen.")
                Else
                    'Druckauftrag wurde in der Warteschlange empfangen
                    'Diesen übernehmen wir nun
                    job = _queue.NextJob

                    'Einstellungen übernehmen
                    job.SetProfileByGuid("DefaultGuid")
                    For Each s As KeyValuePair(Of String, String) In _settings.GetSettings()
                        job.SetProfileSetting(s.Key, s.Value)
                    Next

                    'Umwandeln starten
                    job.ConvertTo(FullPath)

                    'Umwandeln erfolgreich beendet?
                    If (Not job.IsFinished Or Not job.IsSuccessful) Then
                        Throw New PDFException("Der Druckjob konnte nicht in eine PDF-Datei umgewandelt werden. Der Auftrag war fehlerhaft oder nicht abgeschlossen.")
                    End If
                End If
            Catch ex As Exception
                'PDF-Exception als Wrapper werfen.
                Throw New PDFException("Fehler beim Umwandeln in eine PDF-Datei. " & ex.Message, ex)
            Finally
                'Variablen wieder freigeben, weil COM-Objekt
                job = Nothing
            End Try
        End Sub

        ''' <summary>
        ''' Fügt einen Druckauftrag einer bestehenden PDF-Datei hinzu.
        ''' </summary>
        ''' <param name="PDFFile">Bestehende PDF-Datei, an welche eine weitere Seite hinzugefügt werden soll.</param>
        ''' <exception cref="PDFException">Wenn beim Hinzufügen der Seite ein Fehler auftritt.</exception>
        ''' <exception cref="TimeoutException">Wenn nach 15 Sekunden der Druckauftrag nicht im Spooler ist.</exception>
        Public Sub AddToExistsPDF(ByVal PDFFile As String)
            Me.AddToExistsPDF(PDFFile, 1)
        End Sub

        ''' <summary>
        ''' Fügt mehrere Druckaufträge zu einer bestehenden PDF-Datei hinzu.
        ''' </summary>
        ''' <param name="PDFFile">Bestehende PDF-Datei, an welche weitere Seiten hinzugefügt werden soll.</param>
        ''' <param name="CountJobs">Anzahl der Druckaufträge, auf welche gewartet werden soll.</param>
        ''' <exception cref="PDFException">Wenn beim Hinzufügen der Seiten ein Fehler auftritt.</exception>
        ''' <exception cref="TimeoutException">Wenn nach 15 Sekunden nicht die Anzahl an Druckaufträgen eingegangen ist.</exception>
        Public Sub AddToExistsPDF(ByVal PDFFile As String, ByVal CountJobs As Integer)

        End Sub

        ''' <summary>
        ''' Wartet auf eine gewisse Anzahl Druckaufträge in der Warteschlange und
        ''' wandelt alle Druckaufträge um in eine PDF-Datei.
        ''' </summary>
        ''' <param name="FullPath">Vollständiger Pfad, inkl. Dateiname, wo die Datei gespeichert werden soll.</param>
        ''' <param name="CountJobs">Anzahl der Druckaufträge, auf welche gewartet werden soll.</param>
        ''' <exception cref="PDFException">Wenn ein Fehler auftritt beim Umwandeln.</exception>
        ''' <exception cref="TimeoutException">Wenn nach 15 Sekunden nicht die Anzahl an Druckaufträgen eingegangen ist.</exception>
        ''' <remarks>
        ''' Befinden sich nicht innerhalb 15 Sekunden alle Druckaufträge in der Warteschlange, wird
        ''' der Vorgang abgebrochen und eine Exception geworfen.
        ''' </remarks>
        Public Sub MergeToPDF(ByVal FullPath As String, ByVal CountJobs As Integer)
            'Variablendeklaration
            Dim job As PrintJob = Nothing

            Try
                'Auf Druckjob in der Warteschlange warten
                If _queue.WaitForJobs(CountJobs, 15) = False Then
                    Throw New TimeoutException("Es wurden nicht alle nötigen Druckaufträge in der Warteschlange festgestellt. Vorgang abgebrochen.")
                Else
                    'Druckauftrag wurde in der Warteschlange empfangen
                    'Diesen übernehmen wir nun
                    _queue.MergeAllJobs()
                    job = _queue.NextJob

                    'Einstellungen übernehmen
                    job.SetProfileByGuid("DefaultGuid")
                    For Each s As KeyValuePair(Of String, String) In _settings.GetSettings()
                        job.SetProfileSetting(s.Key, s.Value)
                    Next

                    'Umwandeln starten
                    job.ConvertTo(FullPath)

                    'Umwandeln erfolgreich beendet?
                    If (Not job.IsFinished Or Not job.IsSuccessful) Then
                        Throw New PDFException("Der Druckjob konnte nicht in eine PDF-Datei umgewandelt werden. Der Auftrag war fehlerhaft oder nicht abgeschlossen.")
                    End If
                End If
            Catch ex As Exception
                'PDF-Exception als Wrapper werfen.
                Throw New PDFException("Fehler beim Umwandeln in eine PDF-Datei.", ex)
            Finally
                'Variablen wieder freigeben, weil COM-Objekt
                job = Nothing
            End Try
        End Sub

        ''' <summary>
        ''' Initialisiert das PDF-Drucken.
        ''' </summary>
        Private Sub Init()
            'Variablendeklaration
            Dim queueType As Type = Nothing

            Try
                'Default-Einstellungen 
                _settings = New PDFSettings()

                'Wenn bereits eine Instanz läuft, killen wir den Prozess
                'Denn eigentlich sollte vom PDF-Creator gar nichts laufen.
                If Me.IsInstanceRunning() = True Then
                    For Each proc As Process In Process.GetProcessesByName("PDFCreator")
                        proc.Kill()
                    Next

                    'Drei Sekunden warten, bis wirklich alle Dienste beendet sind
                    Threading.Thread.Sleep(3000)
                End If

                'Queue initialisieren
                _queue = New Queue()
                _queue.Initialize()
            Catch ex As Exception
                Throw New PDFException("Fehler beim Initialisieren des PDF-Druckers. " &
                                       "PDF-Creator ist nicht installiert oder die COM-Schnittstelle registriert. " &
                                       "Bitte installieren Sie die Anwendung erneut. Fehlermeldung: " &
                                       ex.Message, ex)
            End Try
        End Sub

        ''' <summary>
        ''' Gibt zurück, ob bereits eine Instanz von PDFCreator läuft.
        ''' </summary>
        ''' <returns><c>True</c>, wenn bereits eine Instanz läuft; <c>False</c> andernfalls.</returns>
        Private Function IsInstanceRunning() As Boolean
            'Variablendeklaration
            Dim pdfobj As PdfCreatorObj = Nothing

            Try
                pdfobj = New PdfCreatorObj()
                Return pdfobj.IsInstanceRunning
            Catch ex As Exception
                Throw New PDFException("Fehler beim prüfen, ob PDFCreater-Instanz bereits läuft. " & ex.Message)
            Finally
                'PDF-Objekt freigeben
                pdfobj = Nothing
            End Try
        End Function

        ''' <summary>
        ''' Gibt den Drucker-Namen zurück, welcher für diesen PDF-Druck
        ''' geeignet ist.
        ''' </summary>
        ''' <returns>Namen des installierten und verwendbaren PDF-Druckers</returns>
        ''' <exception cref="PDFException">Wenn ein Fehler beim Ermitteln des Druckers auftritt oder kein Drucker installiert ist.</exception>
        Public Shared Function GetPDFPrinter() As String
            'Variablendeklaration
            Dim PDFCreator As PdfCreatorObj = Nothing

            'Wurde ein Drucker installiert?
            If IsPDFPrinterInstalled() = False Then
                Throw New PDFException("Es ist kein PDF-Drucker installiert. Bitte installieren Sie die Anwendung erneut.")
            End If

            Try
                'Instanz von PDF-Creator erzeugen
                PDFCreator = New PdfCreatorObj()

                'Wir nehmen den ersten immer zu findenden Drucker!
                'Aufgrunddessen, weil wir bereits geprüft haben, ob überhaupt ein 
                'Drucker installiert ist, können wir hier gleich auf den Index losgehen.
                Return PDFCreator.GetPDFCreatorPrinters.GetPrinterByIndex(0)
            Catch ex As Exception
                'PDF-Exception als Wrapper werfen.
                Throw New PDFException("Der PDF-Drucker konnte nicht ermittelt werden.", ex)
            Finally
                'PDF-Creator Instanz freigeben.
                PDFCreator = Nothing
            End Try
        End Function

        ''' <summary>
        ''' Prüft, ob PDF-Creator installiert ist und ob min. 1
        ''' PDF-Drucker verfügbar ist.
        ''' </summary>
        ''' <returns><c>True</c> wenn PDF-Creator installiert ist und min. 1 Drucker verfügbar ist; <c>False</c> andernfalls.</returns>
        Private Shared Function IsPDFPrinterInstalled() As Boolean
            'Variablendeklaration
            Dim PDFCreator As PdfCreatorObj = Nothing

            Try
                PDFCreator = New PdfCreatorObj()
                If PDFCreator.GetPDFCreatorPrinters.Count > 0 Then
                    Return True
                End If
                Return False
            Catch ex As Exception
                Console.WriteLine("Ausnahme bei PDF-Drucker installiert: {0}", ex.Message)
                Return False
            Finally
                PDFCreator = Nothing
            End Try
        End Function

#Region "IDisposable Support"
        Private disposedValue As Boolean ' Dient zur Erkennung redundanter Aufrufe.

        ' IDisposable
        Protected Overridable Sub Dispose(disposing As Boolean)
            If Not disposedValue Then
                If disposing Then
                    If _queue IsNot Nothing Then
                        _queue.ReleaseCom()
                    End If

                    'Prüfen ob der Prozess noch läuft und dann abwürgen
                    For Each proc As Process In Process.GetProcessesByName("PDFCreator")
                        proc.Kill()
                    Next
                End If

                _queue = Nothing
                _settings = Nothing
            End If
            disposedValue = True
        End Sub

        ' Dieser Code wird von Visual Basic hinzugefügt, um das Dispose-Muster richtig zu implementieren.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in Dispose(disposing As Boolean) weiter oben ein.
            Dispose(True)
        End Sub
#End Region
    End Class
End Namespace

[Aufruf]

'Ist die Datei bereits eine PDF?
                If IO.Path.GetExtension(org_dateiname).ToLower().Equals(".pdf") = True Then
                    'Es handel sich bereits um eine PDF-Datei.
                    'Die schreiben wir einfach da hin, wo der Anwender sie hinhaben will.
                    Utilities.FileSystem.Dateien.WriteStreamToFile(dbAkten.GetDokument(aktendatei_id), filename, True)
                Else
                    'Datei im TempPfad speichern
                    Utilities.FileSystem.Dateien.WriteStreamToFile(dbAkten.GetDokument(aktendatei_id), tempFile, True)

                    'Es ist keine PDF! Wir müssen die Datei mit dem Standard-Programm
                    'öffnen und dann versuchen zu drucken
                    proc = New Process() With {
                        .StartInfo = New ProcessStartInfo(tempFile) With {
                            .Verb = "printto",
                            .WindowStyle = ProcessWindowStyle.Hidden,
                            .Arguments = Utilities.PDF.PDFPrinting.GetPDFPrinter()
                        }
                    }
                    proc.Start()
                    proc.WaitForExit(60000)

                    'PDF-Datei erzeugen
                    Using pdf As New Utilities.PDF.PDFPrinting()
                        pdf.ConvertToPDF(filename)
                    End Using
                End If

#2

Hi,

reicht es ggf., wenn Du statt den Prozess abzuschießen die Queue nur dann initialisierst, wenn keine Instanz läuft?

Beste Grüße

Robin


#3

Hallo Robin,

danke für deine Antwort. Leider nein. Mein Programm soll eine Datei aus einer Datenbank in eine PDF-Datei umwandeln können (z.B. Word oder Excel, Text-Dateien, Bilder, usw.). Das können völlig unterschiedliche Dateien seien; auch OpenOffice-Dateien.

Der Anwender weiß eigentlich gar nicht, wann eine Instanz noch offen ist. Und leider scheint die COM-Schnittstelle diese nicht sauber oder schnell genug zu schließen. Deswegen war ja die Frage, ob es möglich ist, auf eine bereits gestartete Instanz zuzugreifen; diese vielleicht kurz zu übernehmen?!

Mir viel auch auf, dass wenn ich die Initialisierung durchführe, obwohl noch eine Instanz läuft, die COM-Schnittstelle eine Exception wirft (Instance currently running), und dann ein Dialog erscheint, der quasi vom PDFCreator-Programm ist.

Also reagiert die Instanz anscheinend?!
LG, Danny

Edit:
Ist es möglich in naher Zukunft der COM-Schnittstelle eine Close()- oder End()-Anweisung hinzuzufügen, die PDFCreator sauber beendet?