2009-03-16 19 views
6

Tengo un componente VB6 heredado que he importado en VS usando tlbimp.exe para generar mi ensamblado de interoperabilidad. El componente VB6 define un evento que me permite pasar mensajes dentro de VB6.levantando un evento vb6 usando interop

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

Realmente me gustaría ser capaz de levantar esto incluso en mi programa en C#, pero su cada vez importan como un evento, no un delegado o algo más útil. Entonces, solo puedo escuchar, pero nunca disparar. ¿Alguien sabe cómo disparar un evento contenido dentro de VB6? El evento C# se parece a

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

Desafortunadamente no puedo cambiar el código VB6. Gracias.

+0

Si un objeto está activando el evento, entonces solo puede escucharlo. ¿En qué sentido quieres dispararlo desde C#? ¿Quién manejaría ese evento? – Groo

+0

ya está suscrito en la aplicación vb6, esperaba poder subirlo ya que es más un delegado que un evento – Steve

Respuesta

3

En VB6 el evento solo se puede generar desde dentro de la clase (o en el Formulario según sea el caso) declarando el Evento. Para forzar un evento a ser levantado en VB6, necesitas exponer un método en la clase para hacer esto. Si no tiene el código fuente, no tiene suerte.

De la documentación

[(ArgumentList)]

nombre_evento requerido RaiseEvent nombre_evento es el nombre de un evento declaradas en el módulo de y sigue la variable básica para nombrar convenciones.

Por ejemplo

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 

siento ser el portador de malas noticias.

+1

gracias, ahora iré a llorar a mi café :( – Steve

6

En realidad, la esperanza no se ha perdido todavía. Es es posible plantear un evento en un objeto COM desde fuera de la clase del objeto. Esta funcionalidad la proporciona en realidad COM, aunque de manera indirecta.

En COM, los eventos funcionan en un modelo de publicación/suscripción. Un objeto COM que tiene eventos (el "origen del evento") publica eventos y uno o más objetos COM se suscriben al evento adjuntando un controlador de eventos al objeto fuente (los controladores se denominan "receptores de eventos"). Normalmente, el objeto fuente plantea un evento simplemente recorriendo todos los receptores de eventos y llamando al método de controlador apropiado.

Entonces, ¿cómo te puede ayudar esto? Ocurre que COM le permite consultar un origen de eventos para obtener una lista de todos los objetos sumideros de eventos actualmente suscritos a los eventos del objeto fuente. Una vez que tenga una lista de objetos sumideros de eventos, puede simular la generación de un evento invocando a cada uno de los controladores de eventos del objeto sumidero.

Nota:estoy sobre-simplificación de los detalles y ser liberal con algunos de los términos, pero esa es la versión corta (y algo políticamente incorrecto) de cómo funcionan los eventos en el COM.

Puede aprovechar este conocimiento para generar eventos en un objeto COM desde un código externo. De hecho, es posible hacer todo esto en C#, con la ayuda del soporte de interoperabilidad COM en los espacios de nombres System.Runtime.Interop y System.Runtime.Interop.ComTypes.


EDITAR

escribí una clase de utilidad que le permitirá a provocar eventos en un objeto COM de .NET. Es bastante fácil de usar. Aquí hay un ejemplo usando la interfaz de eventos de su pregunta:

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

A continuación se muestra el código de la clase ComEventUtils (así como la clase de ayuda, SafeIntPtr, porque soy paranoico y quería una buena manera de hacer frente a la IntPtr S que necesita el código relacionado con COM-):

Aviso: que no han puesto a prueba a fondo el código de abajo. El código realiza la gestión de memoria manual en algunos lugares, y por lo tanto existe la posibilidad de que pueda introducir pérdidas de memoria en el código. Además, no agregué el manejo de errores al código, porque esto es solo un ejemplo. Use con cuidado.

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