| 

.NET C# Java Javascript Exception

3
Ich habe für eine VB6-Anwendung eine COM-Komponente in VB.NET geschrieben, die verschiedene Aufgaben asynchron erledigt. In VB6-Code will ich wissen, wenn eine asynchron gestartete Aufgabe von der COM-Komponente erledigt wurde. Dies wird per Event signalisiert.

Problem: Auf meinem Rechner funktioniert dieses Szenario einwandfrei. Auf allen anderen Rechner stürzt die ganze Applikation mit dem folgenden Fehler ab:

Application: TestDotNetEventsInVB6.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Reflection.TargetException
Stack:
at System.RuntimeType.InvokeDispMethod(System.String, System.Reflection.BindingFlags, System.Object, System.Object[], Boolean[], Int32, System.String[])
at System.RuntimeType.InvokeMember(System.String, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object, System.Object[], System.Reflection.ParameterModifier[], System.Globalization.CultureInfo, System.String[])
at System.RuntimeType.ForwardCallToInvokeMember(System.String, System.Reflection.BindingFlags, System.Object, Int32[], System.Runtime.Remoting.Proxies.MessageData ByRef)
at TestEvents.WorkerEvents.ProgressChanged(Int32)
at TestEvents.Worker+_Closure$__1+_Closure$__2._Lambda$__4()
at TestEvents.Worker+_Closure$__1+_Closure$__2._Lambda$__3(System.Object)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()


Ursache (vermutlich): Die VB6-Andendung stürzt erst dann ab, wenn ein Event aus der COM-Komponente gefeuert wird. Ich vermute, dass es daran liegt, dass dieser Event aus einem ganz anderen Thread gefeuert wird. Das kann man schön beim Debuggen beobachten: BackgroundWorker in der DoSomething-Funktion wird aus dem VB6-Thread (STA) instanziiert und gestartet; DoWork wird, wie erwartet, vom BackgroundWorker-Thread ausgeführt (MTA); RunWorkerCompleted wird jedoch wiederum von einem anderem Thread (MTA) ausgeführt. Die COM-Komponente kann nicht mehr auf VB6-Thread „zugreifen“ und verabschiedet sich mit einem lauten Knall. Die Anwendung stürzt ab. Die Fehlermeldung findet man im Windows Ereignisprotokoll.

Workaround: Wenn ich jedoch auf dem Rechner, wo es grade geknallt hat, den VB6-Projekt aufmache, die COM-Komponente neu anbinde und EXE neu erstelle, funktioniert das Ganze seltsamer Weise einwandfrei. Das ist auch der Grund, warum es auf meinem Rechner immer funktioniert hat. Leider darf ich diesen Workaround den Kunden nicht präsentieren :)

VB.NET (COM)
Imports System.ComponentModel
Imports System.Runtime.InteropServices

<ComClass(Worker.ClassId, Worker.InterfaceId, Worker.EventsId)>
Public Class Worker

#Region "COM-GUIDs"
Public Const ClassId As String = "2EC0248C-C6FB-4855-A699-450035324BA5"
Public Const InterfaceId As String = "D28A8664-FD68-4878-9216-5A1BC586B7CE"
Public Const EventsId As String = "43ECFBDD-A60E-41A6-8F6A-50C82EAF8872"
#End Region

Public Event WorkDone(result As Object)

Public Sub New()
MyBase.New()
End Sub

Public Sub DoSomething()
Dim bgWorker As New BackgroundWorker

AddHandler bgWorker.DoWork,
Sub(sender As Object, e As DoWorkEventArgs)
Dim worker As BackgroundWorker = sender
Threading.Thread.Sleep(500)
e.Result = "Done!"
End Sub

AddHandler bgWorker.RunWorkerCompleted,
Sub(sender As Object, e As RunWorkerCompletedEventArgs)
RaiseEvent WorkDone(e.Result)
End Sub

If Not bgWorker.IsBusy Then
bgWorker.RunWorkerAsync()
End If
End Sub
End Class


Visual Basic 6.0
Public WithEvents w As Worker

Private Sub Command1_Click()
Label1.Caption = "DoSomething"

Set w = New Worker
w.DoSomething
End Sub

Private Sub w_WorkDone(ByVal result As Variant)
Label1.Caption = result
End Sub


Momentan habe ich das Problem gelöst, indem ich eine statische Property an der COM-Komponente aus dem VB6-Code periodisch abfrage. An dieser Property hängen der aktuelle Job-Status und das Ergebnis. Diese Lösung finde ich jedoch nicht schön. Das macht den Code unnötig komplizierter.

Hat jemand eine Idee, wie ich dieses "RaiseEvent-Problem" löse?
24.04.2013
KolobokPunk 33 5
4 Antworten
1
Dein Workaround könnte darauf hindeuten, dass deine COM-Komponente auf den Rechnern, wo es kracht, nicht korrekt registriert wurde. Hast du dies schon überprüft?

Wir hatten ein ähnliches Problem mit einer in Managed C++ erstellten dll, die in Excel eingebunden wurde (zwar mit Events, aber ohne asynchrone Verarbeitung). Diese wurde nur dann korrekt von Excel erkannt, wenn wird diese mit RegAsm.exe registriert hatten:

regasm.exe /tlb <deine dll>.dll


Auf die .tlb-Datei haben wir dann in VBA verwiesen, um die dll anzusprechen.

Gruß
Klaus
25.04.2013
luedi 2,2k 1 9
0
In der MSDN gibt es eine Artikel zu diesem Thema: How to: Raise Events Handled by a COM Sink
The .NET Framework provides a delegate-based event system to connect an event sender (source) to an event receiver (sink). When the sink is a COM client, the source must include additional elements to simulate connection points. With these modifications, a COM client can register its event sink interface in the traditional way by calling the IConnectionPoint::Advise method. (Visual Basic hides connection point details, so you do not have to call these methods directly.)


In diesem Artikel wird auch beschrieben wie man vorgehen muss:

  • Step 1: Defines an event sink interface (ButtonEvents) to be implemented by the COM
    sink.
  • Step 2: Connects the event sink interface to a class by passing the namespace and event sink interface ("EventSource.ButtonEvents, EventSrc").

Man muss also das ComSourceInterfacesAttribute setzten welches eine Liste an Interfaces enthällt die per COM als Event zur Verfügung gestellt werden.

Beispiel aus der MSDN:
Option Explicit
Option Strict

Imports System
Imports System.Runtime.InteropServices

Namespace EventSource
Public Delegate Sub ClickDelegate(x As Integer, y As Integer)
Public Delegate Sub ResizeDelegate()
Public Delegate Sub PulseDelegate()

' Step 1: Defines an event sink interface (ButtonEvents) to be
' implemented by the COM sink.
<GuidAttribute("1A585C4D-3371-48dc-AF8A-AFFECC1B0967"), _
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface ButtonEvents
Sub Click(x As Integer, y As Integer)
Sub Resize()
Sub Pulse()
End Interface

' Step 2: Connects the event sink interface to a class
' by passing the namespace and event sink interface
' ("EventSource.ButtonEvents, EventSrc").
<ComSourceInterfaces(GetType(ButtonEvents))> _
Public Class Button
Public Event Click As ClickDelegate
Public Event Resize As ResizeDelegate
Public Event Pulse As PulseDelegate


Public Sub CauseClickEvent(x As Integer, y As Integer)
RaiseEvent Click(x, y)
End Sub

Public Sub CauseResizeEvent()
RaiseEvent Resize()
End Sub

Public Sub CausePulse()
RaiseEvent Pulse()
End Sub
End Class
End Namespace
24.04.2013
Floyd 14,6k 3 9
0
Floyd, Danke für Deine Antwort. Das habe ich bereits ausprobiert. Dieses Beispiel funktioniert, weil alle Funtionen synchron ausgeführt werden. Wenn ich "meine" DoSomething-Funktion ebenso synchron ausführe, funktioniert es auch. Siehe Beispiel unten:

VB.NET (COM)
Imports System.ComponentModel
Imports System.Runtime.InteropServices

<ComClass(Worker.ClassId, Worker.InterfaceId, Worker.EventsId)>
Public Class Worker

#Region "COM-GUIDs"
Public Const ClassId As String = "2EC0248C-C6FB-4855-A699-450035324BA5"
Public Const InterfaceId As String = "D28A8664-FD68-4878-9216-5A1BC586B7CE"
Public Const EventsId As String = "43ECFBDD-A60E-41A6-8F6A-50C82EAF8872"
#End Region

Public Event WorkDone(result As Object)

Public Sub New()
MyBase.New()
End Sub

Public Sub DoSomething()
Threading.Thread.Sleep(500)
RaiseEvent WorkDone("Done!")
End Sub
End Class

Die Herausforderung besteht jedoch darin, dass die DoSomething-Funktion asynchron arbeiten muss und per Event den VB6-Client benachritigen soll, wenn sie fertig ist. Das klappt nicht.

Es ist, wie wenn in diesem MSDN-Beispiel, bevor Resize-Event gefeuert wird, eine Berechnung asynchron ausgeführt wird. Und erst dann wird Resize-Event gefeuert.
24.04.2013
KolobokPunk 33 5
0
Danke Klaus! Das ist es!

Die COM-DLLs müssen (auch auf Produktivrechner) mit dem Parameter /tlb registriert werden.

MSDN Library:
Eine Typbibliothek ist die COM-Entsprechung der Metadaten, die in einer .NET-Assembly enthalten sind. Typbibliotheken befinden sich in der Regel in Dateien mit der Erweiterung .tlb. Sie enthalten die erforderlichen Informationen, mit denen ein COM-Client ermitteln kann, welche Klassen sich auf einem bestimmten Server befinden und welche Methoden, Eigenschaften und Ereignisse von diesen Klassen unterstützt werden.

Quelle: http://msdn.microsoft.com/de-de/library/ms973802.aspx
25.04.2013
KolobokPunk 33 5

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH