6 Stimmen

Auslösen eines Vb6-Ereignisses mit Interop

Ich habe eine VB6-Legacy-Komponente, die ich mit tlbimp.exe in VS importiert habe, um meine Interop-Assembly zu erstellen. Die VB6-Komponente definiert ein Ereignis, das es mir ermöglicht, Nachrichten innerhalb von VB6 zu übergeben.

Public Event Message(ByVal iMsg As Variant, oCancel As Variant)

Ich würde wirklich gerne in der Lage sein, dies auch in meinem C#-Programm zu erhöhen, aber seine immer als ein Ereignis importiert, nicht ein Delegat oder etwas anderes nützlich. Also, ich kann nur hören, aber nie Feuer. Weiß jemand, wie man ein Ereignis innerhalb von VB6 auslösen kann? Das C#-Ereignis sieht so aus

[TypeLibType(16)]
[ComVisible(false)]
 public interface __MyObj_Event
 {
     event __MyObj_MessageEventHandler Message;
 }

Leider kann ich den VB6-Code nicht ändern. Danke!

8voto

Mike Spross Punkte 7801

Doch noch ist die Hoffnung nicht verloren. Es est Es ist möglich, ein Ereignis für ein COM-Objekt von außerhalb der Klasse des Objekts auszulösen. Diese Funktionalität wird eigentlich von COM selbst bereitgestellt, wenn auch auf indirekte Weise.

In COM funktionieren Ereignisse nach einem Publish/Subscribe-Modell. Ein COM-Objekt, das über Ereignisse verfügt (die "Ereignisquelle"), veröffentlicht Ereignisse, und ein oder mehrere andere COM-Objekte abonnieren das Ereignis, indem sie einen Ereignishandler an das Quellobjekt anhängen (die Handler werden "Ereignissenken" genannt). Normalerweise löst das Quellobjekt ein Ereignis aus, indem es einfach eine Schleife durch alle Ereignissenken durchläuft und die entsprechende Handler-Methode aufruft.

Wie kann Ihnen das helfen? Zufälligerweise können Sie mit COM eine Ereignisquelle nach einer Liste aller Ereignis-Sink-Objekte abfragen, die derzeit die Ereignisse des Quellobjekts abonnieren. Sobald Sie eine Liste von Ereignis-Sink-Objekten haben, können Sie das Auslösen eines Ereignisses simulieren, indem Sie die Ereignishandler der einzelnen Sink-Objekte aufrufen.

Anmerkung: Ich vereinfache die Details zu sehr und gehe mit einigen Begriffen sehr großzügig um, aber das ist die kurze (und etwas politisch unkorrekte) Version, wie die Ereignisse in der GMO ablaufen.

Sie können dieses Wissen nutzen, um Ereignisse für ein COM-Objekt aus externem Code auszulösen. In der Tat ist es möglich, all dies in C# zu tun, mit Hilfe der COM-Interop-Unterstützung in der System.Runtime.Interop y System.Runtime.Interop.ComTypes Namespaces.


EDITAR

Ich habe eine Dienstleistungsklasse geschrieben, mit der Sie Ereignisse für ein COM-Objekt in .NET auslösen können. Sie ist ziemlich einfach zu benutzen. Hier ist ein Beispiel, das die Ereignisschnittstelle aus Ihrer Frage verwendet:

MyObj legacyComObject = new MyObj();

// The following code assumes other COM objects have already subscribed to the 
// MyObj class's Message event at this point.
//
// NOTE: VB6 objects have two hidden interfaces for classes that raise events:
//
// _MyObj (with one underscore): The default interface.
// __MyObj (with two underscores): The event interface.
//
// We want the second interface, because it gives us a delegate
// that we can use to raise the event.
// The ComEventUtils.GetEventSinks<T> method is a convenience method
// that returns all the objects listening to events from the legacy COM object.

// set up the params for the event
string messageData = "Hello, world!";
bool cancel = false;

// raise the event by invoking the event delegate for each connected object...
foreach(__MyObj sink in ComEventUtils.GetEventSinks<__MyObj>(legacyComObject))
{
    // raise the event via the event delegate
    sink.Message(messageData, ref cancel);

    if(cancel == true)
    {
        // do cancel processing (just an example)
        break;
    }
}

Nachfolgend finden Sie den Code für die ComEventUtils Klasse (sowie die Hilfsklasse, SafeIntPtr weil ich paranoid bin und eine nette Möglichkeit wollte, mit dem IntPtr S, die der COM-bezogene Code benötigt):

Haftungsausschluss : Ich habe den unten stehenden Code nicht gründlich getestet. Der Code führt an einigen Stellen eine manuelle Speicherverwaltung durch, und es besteht daher die Möglichkeit, dass er Speicherlecks in Ihren Code einführt. Außerdem habe ich dem Code keine Fehlerbehandlung hinzugefügt, da dies nur ein Beispiel ist. Verwenden Sie ihn mit Vorsicht.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using COM = System.Runtime.InteropServices.ComTypes;

namespace YourNamespaceHere
{

/// <summary>
/// A utility class for dealing with COM events.
/// Needs error-handling and could potentially be refactored
/// into a regular class. Also, I haven't extensively tested this code;
/// there may be a memory leak somewhere due to the rather
/// low-level stuff going on in the class, but I think I covered everything.
/// </summary>
public static class ComEventUtils
{
    /// <summary>
    /// Get a list of all objects implementing an event sink interface T
    /// that are listening for events on a specified COM object.
    /// </summary>
    /// <typeparam name="T">The event sink interface.</typeparam>
    /// <param name="comObject">The COM object whose event sinks you want to retrieve.</param>
    /// <returns>A List of objects that implement the given event sink interface and which
    /// are actively listening for events from the specified COM object.</returns>
    public static List<T> GetEventSinks<T>(object comObject)
    {
        List<T> sinks = new List<T>();
        List<COM.IConnectionPoint> connectionPoints = GetConnectionPoints(comObject);

        // Loop through the source object's connection points, 
        // find the objects that are listening for events at each connection point,
        // and add the objects we are interested in to the list.
        foreach(COM.IConnectionPoint connectionPoint in connectionPoints)
        {
            List<COM.CONNECTDATA> connections = GetConnectionData(connectionPoint);

            foreach (COM.CONNECTDATA connection in connections)
            {
                object candidate = connection.pUnk;

                // I tried to avoid relying on try/catch for this
                // part, but candidate.GetType().GetInterfaces() kept
                // returning an empty array.
                try
                {
                    sinks.Add((T)candidate);
                }
                catch { }
            }

            // Need to release the interface pointer in each CONNECTDATA instance
            // because GetConnectionData implicitly AddRef's it.
            foreach (COM.CONNECTDATA connection in connections)
            {
                Marshal.ReleaseComObject(connection.pUnk);
            }
        }

        return sinks;
    }

    /// <summary>
    /// Get all the event connection points for a given COM object.
    /// </summary>
    /// <param name="comObject">A COM object that raises events.</param>
    /// <returns>A List of IConnectionPoint instances for the COM object.</returns>
    private static List<COM.IConnectionPoint> GetConnectionPoints(object comObject)
    {
        COM.IConnectionPointContainer connectionPointContainer = (COM.IConnectionPointContainer)comObject;
        COM.IEnumConnectionPoints enumConnectionPoints;
        COM.IConnectionPoint[] oneConnectionPoint = new COM.IConnectionPoint[1];
        List<COM.IConnectionPoint> connectionPoints = new List<COM.IConnectionPoint>();

        connectionPointContainer.EnumConnectionPoints(out enumConnectionPoints);
        enumConnectionPoints.Reset();

        int fetchCount = 0;
        SafeIntPtr pFetchCount = new SafeIntPtr();

        do
        {
            if (0 != enumConnectionPoints.Next(1, oneConnectionPoint, pFetchCount.ToIntPtr()))
            {
                break;
            }

            fetchCount = pFetchCount.Value;

            if (fetchCount > 0)
                connectionPoints.Add(oneConnectionPoint[0]);

        } while (fetchCount > 0);

        pFetchCount.Dispose();

        return connectionPoints;
    }

    /// <summary>
    /// Returns a list of CONNECTDATA instances representing the current
    /// event sink connections to the given IConnectionPoint.
    /// </summary>
    /// <param name="connectionPoint">The IConnectionPoint to return connection data for.</param>
    /// <returns>A List of CONNECTDATA instances representing all the current event sink connections to the 
    /// given connection point.</returns>
    private static List<COM.CONNECTDATA> GetConnectionData(COM.IConnectionPoint connectionPoint)
    {
        COM.IEnumConnections enumConnections;
        COM.CONNECTDATA[] oneConnectData = new COM.CONNECTDATA[1];
        List<COM.CONNECTDATA> connectDataObjects = new List<COM.CONNECTDATA>();

        connectionPoint.EnumConnections(out enumConnections);
        enumConnections.Reset();

        int fetchCount = 0;
        SafeIntPtr pFetchCount = new SafeIntPtr();

        do
        {
            if (0 != enumConnections.Next(1, oneConnectData, pFetchCount.ToIntPtr()))
            {
                break;
            }

            fetchCount = pFetchCount.Value;

            if (fetchCount > 0)
                connectDataObjects.Add(oneConnectData[0]);

        } while (fetchCount > 0);

        pFetchCount.Dispose();

        return connectDataObjects;
    }
} //end class ComEventUtils

/// <summary>
/// A simple wrapper class around an IntPtr that
/// manages its own memory.
/// </summary>
public class SafeIntPtr : IDisposable
{
    private bool _disposed = false;
    private IntPtr _pInt = IntPtr.Zero;

    /// <summary>
    /// Allocates storage for an int and assigns it to this pointer.
    /// The pointed-to value defaults to 0.
    /// </summary>
    public SafeIntPtr()
        : this(0)
    {
        //
    }

    /// <summary>
    /// Allocates storage for an int, assigns it to this pointer,
    /// and initializes the pointed-to memory to known value.
    /// <param name="value">The value this that this <tt>SafeIntPtr</tt> points to initially.</param>
    /// </summary>
    public SafeIntPtr(int value)
    {
        _pInt = Marshal.AllocHGlobal(sizeof(int));
        this.Value = value;
    }

    /// <summary>
    /// Gets or sets the value this pointer is pointing to.
    /// </summary>
    public int Value
    {
        get 
        {
            if (_disposed)
                throw new InvalidOperationException("This pointer has been disposed.");
            return Marshal.ReadInt32(_pInt); 
        }

        set 
        {
            if (_disposed)
                throw new InvalidOperationException("This pointer has been disposed.");
            Marshal.WriteInt32(_pInt, Value); 
        }
    }

    /// <summary>
    /// Returns an IntPtr representation of this SafeIntPtr.
    /// </summary>
    /// <returns></returns>
    public IntPtr ToIntPtr()
    {
        return _pInt;
    }

    /// <summary>
    /// Deallocates the memory for this pointer.
    /// </summary>
    public void Dispose()
    {
        if (!_disposed)
        {
            Marshal.FreeHGlobal(_pInt);
            _disposed = true;
        }
    }

    ~SafeIntPtr()
    {
        if (!_disposed)
            Dispose();
    }

} //end class SafeIntPtr

} //end namespace YourNamespaceHere

3voto

RS Conley Punkte 7136

In VB6 kann das Ereignis nur innerhalb der Klasse (oder des Formulars, je nachdem) ausgelöst werden, die das Ereignis deklariert. Um die Auslösung eines Ereignisses in VB6 zu erzwingen, müssen Sie eine Methode in der Klasse bereitstellen, um dies zu tun. Wenn Sie den Quellcode nicht haben, haben Sie Pech gehabt.

Aus der Dokumentation

RaiseEvent Ereignisname [(Argumentliste)]

Der erforderliche Ereignisname ist der Name von eines Ereignisses innerhalb des Moduls deklariert und folgt der grundlegenden Benennung von Variablen Konventionen.

Zum Beispiel

Option Explicit

Private FText As String

Public Event OnChange(ByVal Text As String)

'This exposes the raising the event

Private Sub Change(ByVal Text As String)
  RaiseEvent OnChange(Text)
End Sub

Public Property Get Text() As String
  Text = FText
End Property

Public Property Let Text(ByVal Value As String)
  FText = Value
  Call Change(Value)
End Property

Es tut mir leid, der Überbringer schlechter Nachrichten zu sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X