lever un événement vb6 en utilisant interop

J’ai un composant VB6 existant que j’ai importé dans VS à l’aide de tlbimp.exe pour générer mon assemblage d’interopérabilité. Le composant VB6 définit un événement qui me permet de transmettre des messages dans VB6.

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

J’aimerais vraiment pouvoir évoquer cela même dans mon programme C #, mais il est importé en tant qu’événement, et non en tant que délégué ou autre chose utile. Donc, je ne peux qu’écouter, mais jamais tirer. Est-ce que quelqu’un sait comment déclencher un événement contenu dans VB6? L’événement C # ressemble à

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

Je ne peux malheureusement pas changer le code VB6. Merci.

Dans VB6, l’événement ne peut être déclenché que dans la classe (ou le formulaire, selon le cas) déclarant l’événement. Pour forcer la levée d’un événement dans VB6, vous devez exposer une méthode sur la classe. Si vous n’avez pas le code source, vous n’avez pas de chance.

De la documentation

RaiseEvent eventname [(liste d’arguments)]

Le nom d’événement requirejs est le nom d’un événement déclaré dans le module et suit les conventions de dénomination des variables de base.

Par exemple

 Option Explicit Private FText As Ssortingng Public Event OnChange(ByVal Text As Ssortingng) 'This exposes the raising the event Private Sub Change(ByVal Text As Ssortingng) RaiseEvent OnChange(Text) End Sub Public Property Get Text() As Ssortingng Text = FText End Property Public Property Let Text(ByVal Value As Ssortingng) FText = Value Call Change(Value) End Property 

Désolé d’être le porteur de mauvaises nouvelles.

En réalité, l’espoir n’est pas encore perdu. Il est possible de déclencher un événement sur un object COM en dehors de la classe de l’object. Cette fonctionnalité est en réalité fournie par COM, bien que de manière indirecte.

Dans COM, les événements fonctionnent sur un modèle de publication / abonnement. Un object COM ayant des événements (la “source d’événement”) publie des événements et un ou plusieurs autres objects COM s’abonnent à l’événement en attachant un gestionnaire d’événement à l’object source (les gestionnaires sont appelés “récepteurs d’événement”). Normalement, l’object source déclenche un événement en effectuant une boucle sur tous les récepteurs d’événements et en appelant la méthode de gestionnaire appropriée.

Alors, comment cela vous aide-t-il? Il se trouve que COM vous permet d’interroger une source d’événements pour obtenir une liste de tous les objects récepteurs d’événements actuellement abonnés aux événements de l’object source. Une fois que vous avez une liste d’objects de récepteur d’événements, vous pouvez simuler la création d’un événement en appelant chacun des gestionnaires d’événements de l’object de récepteur.

Remarque: je simplifie excessivement les détails et suis libéral avec une partie de la terminologie, mais c’est la version courte (et quelque peu politiquement incorrecte) de la façon dont les événements fonctionnent dans COM.

Vous pouvez tirer parti de ces connaissances pour générer des événements sur un object COM à partir de code externe. En fait, il est possible de faire tout cela en C #, à l’aide du support COM interop dans les espaces de noms System.Runtime.Interop et System.Runtime.Interop.ComTypes .


MODIFIER

J’ai écrit une classe utilitaire qui vous permettra de déclencher des événements sur un object COM à partir de .NET. C’est assez facile à utiliser. Voici un exemple utilisant l’interface d’événement de votre question:

 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 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 ssortingng 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; } } 

Vous trouverez ci-dessous le code de la classe ComEventUtils (ainsi que de la classe d’assistance SafeIntPtr , car je suis paranoïaque et je voulais un moyen agréable de gérer le IntPtr S nécessaire au code lié à COM):

Disclaimer : Je n’ai pas testé le code ci-dessous. Le code effectue la gestion manuelle de la mémoire à quelques endroits. Il est donc possible qu’il introduise des memory leaks dans votre code. De plus, je n’ai pas ajouté de traitement d’erreur au code, car il ne s’agit que d’un exemple. Utilisez avec soin.

 using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using COM = System.Runtime.InteropServices.ComTypes; namespace YourNamespaceHere { ///  /// 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. ///  public static class ComEventUtils { ///  /// Get a list of all objects implementing an event sink interface T /// that are listening for events on a specified COM object. ///  /// The event sink interface. /// The COM object whose event sinks you want to resortingeve. /// A List of objects that implement the given event sink interface and which /// are actively listening for events from the specified COM object. public static List GetEventSinks(object comObject) { List sinks = new List(); List 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 interestd in to the list. foreach(COM.IConnectionPoint connectionPoint in connectionPoints) { List connections = GetConnectionData(connectionPoint); foreach (COM.CONNECTDATA connection in connections) { object candidate = connection.pUnk; // I sortinged 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; } ///  /// Get all the event connection points for a given COM object. ///  /// A COM object that raises events. /// A List of IConnectionPoint instances for the COM object. private static List GetConnectionPoints(object comObject) { COM.IConnectionPointContainer connectionPointContainer = (COM.IConnectionPointContainer)comObject; COM.IEnumConnectionPoints enumConnectionPoints; COM.IConnectionPoint[] oneConnectionPoint = new COM.IConnectionPoint[1]; List connectionPoints = new List(); 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; } ///  /// Returns a list of CONNECTDATA instances representing the current /// event sink connections to the given IConnectionPoint. ///  /// The IConnectionPoint to return connection data for. /// A List of CONNECTDATA instances representing all the current event sink connections to the /// given connection point. private static List GetConnectionData(COM.IConnectionPoint connectionPoint) { COM.IEnumConnections enumConnections; COM.CONNECTDATA[] oneConnectData = new COM.CONNECTDATA[1]; List connectDataObjects = new List(); 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 ///  /// A simple wrapper class around an IntPtr that /// manages its own memory. ///  public class SafeIntPtr : IDisposable { private bool _disposed = false; private IntPtr _pInt = IntPtr.Zero; ///  /// Allocates storage for an int and assigns it to this pointer. /// The pointed-to value defaults to 0. ///  public SafeIntPtr() : this(0) { // } ///  /// Allocates storage for an int, assigns it to this pointer, /// and initializes the pointed-to memory to known value. /// The value this that this SafeIntPtr points to initially. ///  public SafeIntPtr(int value) { _pInt = Marshal.AllocHGlobal(sizeof(int)); this.Value = value; } ///  /// Gets or sets the value this pointer is pointing to. ///  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); } } ///  /// Returns an IntPtr representation of this SafeIntPtr. ///  ///  public IntPtr ToIntPtr() { return _pInt; } ///  /// Deallocates the memory for this pointer. ///  public void Dispose() { if (!_disposed) { Marshal.FreeHGlobal(_pInt); _disposed = true; } } ~SafeIntPtr() { if (!_disposed) Dispose(); } } //end class SafeIntPtr } //end namespace YourNamespaceHere