Pasar Tipos Definidos a Objetos ActiveX

Como pasar Estructuras como Parámetros o Propiedades a Objetos

Por Harvey Triana

Este articulo muestra como pasar Tipos Definido por el Usuario (UDTs) entre objetos ActiveX. En módulos locales recuerde que puede pasar los UDT a cualquier lugar siempre que haga los métodos y propiedades Friend. No obstante, también recuerde que Friend dará alcance dentro del proyecto, y no será visible entre Componentes.

Los programadores de Visual Basic 5, y versiones anteriores, no pueden pasar estructuras a objetos COM de manera natural, tal y como se ha hecho siempre en C++ o ahora con Visual Basic 6 (SP3) por ejemplo. Sin embargo hay soluciones, pero de hecho laboriosas, y de las cuales daré ejemplos.

Visual Basic 6 cambio las cosas, es posible hacer publico un UDT en un objeto publico. Este UDT hará parte de la Type Library del objeto ActiveX creado, y los clientes pueden acceder a el como un tipo más disponible. También colocare un ejemplo para este caso. No obstante hay detalles que deberá saber.


Pasar UDTs entre Objetos con Visual Basic 5

Si deseamos pasar UDTs a Componentes tenemos una solución inteligente, y es convertir cada UDT en una Clase y orientar los módulos a objetos. Sin embargo imagínese un proyecto antiguo demasiado grande en el que no deseamos hacer tal reingenieria ya que el precio sería muy alto.

Ciertamente existen soluciones lógicas al asunto. Sin embargo, es demasiado trabajo para algo que debe ser natural. No obstante, existen casos en que seria absolutamente necesario. Recuerdo un proyecto que desarrolle hace un par de años o más, y requería imperiosamente dividirlo en componente por su gran tamaño, sin embargo este proyecto usaba muchos UDTs, por lo que tuve que emplear algunos de los artificios que expondré a continuación.

Conozco tres formas de pasar UDTs entre componentes de Visual Basic 5: (1) Pasar los datos en matrices de Bytes a través de la API CopyMemory, (2) Pasar los datos en matrices de Bytes a través de cadenas Binarias en variables, y (3) Escribir / Leer cadenas Binarias a través de archivos Binary (blobs). Dispondré ejemplos para los dos primeros casos.

Pasar los datos en matrices de Bytes a través de la API CopyMemory

Aunque laboriosa, representa la manera más sencilla de resolver el problema. Sin embargo, si no tiene cuidado con la API CopyMemory su programa puede caerse con falla de protección de Windows.

Expondré el asunto desde la perspectiva de un solo módulo y luego explicaré que se debe hacer en el entorno Componente - Cliente.

Ejemplo:

'// MODULE      : frmTest
'// DESCRIPTION : Ejemplo de pasar UDT en con CopyMemory
'// AUTHOR      : Harvey T.
'// UPDATE      : 20/10/99

Option Explicit

Private Declare Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" ( _
                    ByRef Destination As Any, _
                    ByRef Source As Any, _
                    ByVal Bytes As Long)

'//UDT de muestra
Private Type udt_Address
    Street As String * 24
    City   As String * 12
    Zip    As String * 12
End Type

Private Sub Form_Load()
    Dim Address As udt_Address
    ReDim b(1 To Len(Address)) As Byte

    Show
    With Address
        .City = "Bogotá"
        .Street = "Crr 28 No 91-29"
        .Zip = "-"
        Print .City, .Street, .Zip
    End With

    '//Pasa del UDT a la matriz de bytes
    Call UDTAddressToBytes(b(), Address)

    '//Borra el UDT para mostrar luego que si se recupera
    Address.City = ""

    '//Recupera el UDT de la matriz de bytes
    Address = BytesToUDTAddress(b())
    With Address
        Print .City, .Street, .Zip
    End With
End Sub

Private Sub UDTAddressToBytes( _
    ByRef a() As Byte, _
    ByRef u As udt_Address _
    )
    Call CopyMemory(a(1), u, Len(u))
End Sub

Private Function BytesToUDTAddress( _
    a() As Byte) As udt_Address
    Call CopyMemory(BytesToUDTAddress, a(1), Len(BytesToUDTAddress))
End Function

 

Para pasar entre Cliente- Componente tenga en cuanta estos requerimientos:

  • Declarar la API CopyMemory en el Cliente y en el Componente
  • Declarar el UDT como Private en el Cliente y en el Componente
  • Escribir y hacer copia de los procedimientos para transferir la memoria tanto en el Cliente como en ele Componente. En el ejemplo anterior son UDTAddressToBytes (Pasa del UDT a la matriz de bytes) y BytesToUDTAddress (recupera el UDT de la matriz de bytes)
  • Declarar una matriz de bytes, con base 1 y tamaño igual a la UDT. Recuerde usar: ReDim MiMatriz(1 To Len(MiUDT)) As Byte
  • Llamar al procedimiento de transferencia UDT a Bytes o viceversa.

Si estudia el procedimiento Form_Load del ejemplo anterior, quedará claro.


Pasar los datos en matrices de Bytes a través de cadenas Binarias en Variables

Esta técnica es similar en labor a la anterior, posiblemente más segura,  ya que mantiene todo en código plano de Visual Basic. Es algo engorroso usar un UDT adicional para servir de Stream binario del tipo de datos que deseamos transferir.

Veamos un ejemplo en donde se trabaja con un Cliente (Formulario) y una Clase de un Componente.

'// MODULE      : frmTest
'// DESCRIPTION : Ejemplo de pasar UDT en con LSet
'// AUTHOR      : Harvey T.
'// UPDATE      : -

Option Explicit

'//UDT a pasar
Private Type udt_Any
    dDate As Date
    nInt  As Integer
    nLng  As Long
End Type
Private u As udt_Any

'//Stream binario del UDT anterior (udt_Any)
Private Type udt_Stream
    sBytes As String * 14 '//tamaño de udt_Any
End Type
Private b As udt_Stream

Private Sub Form_Load()
    Dim obj As New Class1 '//Clase de componente
    Dim s As String

    '// Datos de ejemplo para el UDT
    u.dDate = Now()
    u.nInt = -999
    u.nLng = 1234567890
    Debug.Print "En el cliente:"
    Debug.Print u.dDate, u.nInt, u.nLng

    LSet b = u '//copia en memoria !
   
s = b.sBytes
    obj.GetUDT s
End Sub

 Codigo en la clase:

'// MODULE      : Class1
'// DESCRIPTION : Ejemplo de pasar UDT en con LSet
'// AUTHOR      : Harvey T.
'// UPDATE      : -

Option Explicit

'//Espejo de los UDTs del cliente
Private Type udt_Any
    dDate As Date
    nInt  As Integer
    nLng  As Long
End Type

Private Type udt_Stream
    sBytes As String * 14
End Type

Public Sub GetUDT(ByRef Stream As String)
    Dim u As udt_Any
    Dim b As udt_Stream

    b.sBytes = Stream
    LSet u = b '//Recuperación en memoria !

    Debug.Print "En objeto:"
    Debug.Print u.dDate, u.nInt, u.nLng
End Sub

 

Tenga en cuanta estos requerimientos:

  • Declarar el UDT como Private en el Cliente y en el Componente
  • Declarar un UDT adicional tanto en el Cliente como en el Componente, el cual tiene un único miembro de tipo Byte de longitud igual a UDT primero.
  • Escribir los procedimientos para transferir la memoria. En el ejemplo anterior se muestra GetUDT
  • Llamar al procedimiento de transferencia UDT a Bytes o en sentido contrario.


Escribir / Leer cadenas Binarias a través de archivos Binary (blobs)

Esta es la técnica menos eficiente ya que escribe en el disco. No será atractiva en entornos distribuidos o donde se desee alto rendimiento. Creo que a nadie le parecerá atractiva, por lo que no expondré este caso con ejemplos. Simplemente se deriva que no usaremos CopyMemory ni un UDT Stream, solo escribiremos / leeremos en un archivo Binary. Resulta muy laborioso el mantenimiento.

NOTA.

A pesar de existir soluciones, existe una limitación en las técnicas que expuse a continuación: los tipos cadenas (Strings) deben ser de longitud constante. Es decir, deberá declarar los tipos de cadena con String * N. Se podría salvaguradar esta limitación al crear un tipo Integer adicional en el UDT para cada tipo String, con el objetivo de almacenar la longitud de la cadena. Esto impone bastante complejidad al problema, por lo que recomiendo mantener el precio de cadenas de longitud fija.


Pasar UDT con Visual Basic 6

En Visual Basic 6 el asunto se logra con limpieza y eficiencia. Es flexible aun para entornos distribuidos y componentes para MTS.

Ejemplo:

'// MODULE      : frmTest
'// DESCRIPTION : Ejemplo de pasar UDT
'// AUTHOR      : Harvey T.
'// UPDATE      : -

Option Explicit

Private oc As cls_Sample '//Objeto en componente

Private Sub LetAddress()
    Dim a As udt_Address

    a.City = "Bogotá"
    a.Street = "Crr 29 No 91-29"
    a.Zip = "-"

    oc.Address = a

    '//test
    Debug.Print "from client: "; a.City
End Sub

Private Sub GetAddress()
    Dim a As udt_Address

    a = oc.Address
End Sub

Private Sub Form_Load()
    Set oc = New cls_Sample

    LetAddress
    GetAddress
End Sub

 

En el componente (puede ser remoto):

 

'// CLASS       : cls_Sample
'// DESCRIPTION : Ejemplo de pasar UDT
'// AUTHOR      : Harvey T.
'// UPDATE      : -

Option Explicit

Public Type udt_Address
    Street As String
    City As String
    Zip As String
End Type

Private m_Address As udt_Address

Public Property Get Address() As udt_Address
    Address = m_Address
End Property

Public Property Let Address(ByRef v As udt_Address) '//KEY: ByRef
    m_Address = v

    '//test
    Debug.Print "from component: "; m_Address.City
End Property

Es importante que note la clausula ByRef del procedimiento de propiedad Let Adress. Es un requerimiento.