2009-03-16 5 views
6

J'ai un composant VB6 hérité que j'ai importé dans VS en utilisant tlbimp.exe pour générer mon assembly interop. Le composant VB6 définit un événement qui me permet de passer des messages dans VB6.Élever un événement vb6 en utilisant interop

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

Je voudrais vraiment pouvoir soulever ce même dans mon programme C#, mais ça devient importé comme un événement, pas un délégué ou quelque chose d'autre utile. Donc, je peux seulement écouter, mais ne 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 modifier le code VB6. Merci.

+0

Si un objet déclenche l'événement, vous pouvez uniquement l'écouter. Dans quel sens voulez-vous le tirer de C#? Qui gérerait cet événement? – Groo

+0

son déjà souscrit dans l'application vb6, j'espérais être en mesure de l'élever depuis son vraiment plus d'un délégué qu'un événement – Steve

Répondre

3

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

De la documentation

RaiseEvent eventName [(argumentlist)]

eventName requis est le nom de un événement déclaré dans le module et suit variables de base de nommage conventions.

Par exemple

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 

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

+1

merci, je vais maintenant aller pleurer dans mon café :( – Steve

6

En fait, l'espoir n'est pas encore perdu. Il est possible de déclencher un événement sur un objet COM à l'extérieur de la classe de l'objet. Cette fonctionnalité est en fait fournie par COM elle-même, mais de manière indirecte.

Dans COM, les événements fonctionnent sur un modèle de publication/abonnement. Un objet COM qui a des événements (la "source d'événement") publie des événements et un ou plusieurs autres objets COM s'abonnent à l'événement en attachant un gestionnaire d'événement à l'objet source (les gestionnaires sont appelés "récepteurs d'événements"). Normalement, l'objet source déclenche un événement en bouclant simplement 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 juste que COM vous permet d'interroger une source d'événement pour une liste de tous les objets de récepteur d'événements actuellement souscrits aux événements de l'objet source. Une fois que vous avez une liste d'objets de récepteur d'événements, vous pouvez simuler la levée d'un événement en appelant chacun des gestionnaires d'événements de l'objet récepteur.

Note:Je suis trop simplifier les détails et être libéral avec une partie de la terminologie, mais c'est court (et un peu politiquement incorrect) version de la façon dont les événements travaillent dans COM.

Vous pouvez tirer parti de cette connaissance pour générer des événements sur un objet COM à partir d'un code externe. En fait, il est possible de faire tout cela en C#, avec l'aide de la prise en charge COM interop dans les espaces de noms System.Runtime.Interop et System.Runtime.Interop.ComTypes.


EDIT

j'ai écrit une classe utilitaire qui vous permettra de déclencher des événements sur un objet COM à partir de .NET. C'est assez facile à utiliser. Voici un exemple en 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<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; 
    } 
} 

est le code ci-dessous pour la classe ComEventUtils (ainsi que la classe d'aide, SafeIntPtr, parce que je suis paranoïaque et je voulais une belle façon de faire face à la IntPtr S requis par le code lié COM):

Disclaimer:Je n'ai pas testé le code ci-dessous. Le code effectue la gestion manuelle de la mémoire dans quelques endroits, et il est donc possible qu'il introduise des fuites de mémoire dans votre code. De plus, je n'ai pas ajouté de gestion des erreurs au code, car ce n'est qu'un exemple. Utiliser avec précaution.

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 
Questions connexes