C++/CLI: Generar eventos desde C++ y capturarlos en código manejado, Parte I

Supongamos que queremos generar un evento desde una clase C++ nativa que tiene un wrapper C++/CLI como interfaz hacia .NET. Supongamos el caso general en que deseamos pasar información extra en el evento, y dicha info no es de un tipo primitivo (o sea, un tipo que no conocen tanto C++ como .NET).

  1. En la clase nativa, definimos, por comodidad, un tipo puntero a función:
    typedef <retornoNativo> ( __stdcall * PFOnEventoCallback )( <parametrosNativos> );
  2. En la misma clase, declaramos un atributo del tipo definido en 1):
    PFOnEventoCallback m_FireEvento;
  3. Dentro de la clase nativa, donde querramos disparar el evento:
    m_FireEvento( <parametrosNativos> );
    (Es buena idea inicializar m_FireEvento en NULL y validar que sea distinto de NULL antes de hacer el fire)
  4. Ahora debemos exponer un setter para este atributo, de este estilo:
    void CClaseNativa::registerOnEventoCallback( PFOnEventoCallback newVal ){
       m_FireEvento = newVal;
    }
  5. En la clase C++/CLI que funciona como wrapper de la nativa, definimos un delegate análogo al puntero a función nativo, y un delegate manejado equivalente, y sendos atributos:
    delegate <retornoNativo> DCallbackNativo( <parametrosNativos> );
    delegate <retornoManejado> DCallbackManejado( <parametrosManejados> );
    // ...junto a los demas atributos
    DCallbackNativo ^ m_hDCallbackNativo;
    static DCallbackManejado ^ m_hDCallbackManejado;
    En el punto 7 veremos por qué m_hDCallbackManejado es static.
  6. Publicamos el evento manejado:
    event DCallbackManejado ^ OnEvento{
       void add( DCallbackManejado ^p ){
          m_hDCallbackManejado += p;
       }
    
       void remove( DCallbackManejado ^p ){
          m_hDCallbackManejado -= p;
       }
    }
  7. Definimos la función la clase CLI que conecta el callback nativo y el manejado; debe ser estática para poder obtener un puntero a ella:
    static <retornoManejado> fireEvento( <parametrosNativos> ){
       if ( m_hDCallbackManejado != nullptr )
          return m_hDCallbackManejado( <parametrosNativosConvertidosAManejados> );
    }
  8. En el constructor de la clase CLI, realizamos la conexión final:
    m_hDCallbackNativo = gcnew DCallbackNativo( & fireEvento );
    m_pNativeInstance->registerOnEventoCallback(
       PFOnEventoCallback(
          Marshal::GetFunctionPointerForDelegate( m_hDCallbackNativo ).ToPointer()
       )
    );
  9. No olvidemos cancelar la registración. En el destructor de la clase CLI (y en su Finalize, si cabe):
    m_pNativeInstance->registerOnEventoCallback( NULL );
  10. Finalmente, el evento publicado en CLI puede ser capturado en .NET. Por ejemplo, en VB.NET:
    AddHandler ClaseCLI.OnEvento, AddressOf UnSubConIgualEncabezadoQueDCallbackManejado
    'Al terminar de usar'
    RemoveHandler ClaseCLI.OnEvento, AddressOf UnSubConIgualEncabezadoQueDCallbackManejado

Enrevesado, ¿No? Es lo que funciona por ahora... Si alguien conoce una mejor forma, por favor cuéntela. Continúa en la Parte II

Comments

  1. Gracias a Tomás González por explicarme esto detallada y pacientemente

    ReplyDelete

Post a Comment

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x