Programación de LabVIEW orientada a objetos: Las decisiones detrás del diseño

Visión General

LabVIEW: ¿Es una herramienta de diseño? ¿Es un lenguaje de programación? Es ambas cosas, y debido a que es ambas, ha sido una gran ayuda para científicos e ingenieros que necesitan programar la PC sin involucrar a los científicos informáticos. Cuando nosotros, los desarrolladores de LabVIEW, queremos agregar nuevas características, debemos considerar que la mayoría de nuestros clientes no son programadores. En la versión 8.2, introdujimos la programación de LabVIEW orientada a objetos (LVOOP). La orientación a objetos (OO) es un estilo de programación lleno de conceptos abstractos y vocabulario técnico. La mayoría de las explicaciones requieren un conocimiento profundo de programación o una gran curva de aprendizaje. Pretendemos optimizar esa complejidad con el objetivo de que el poder de la OO sea accesible para gran variedad de nuestros usuarios. El resultado puede sorprender a los partidarios de la OO que están familiarizados con otros lenguajes de programación. Este documento expone las decisiones de diseño y el razonamiento detrás de esas decisiones para crear LVOOP.

Este documento asume cierta familiaridad con LVOOP. Considere revisar las secciones relevantes en la Ayuda de LabVIEW y los programas de ejemplo antes de continuar.

Este documento ha sido actualizado para LabVIEW 2009.

Contents

El diseño de alto nivel de LVOOP

¿Por qué LabVIEW necesita programación orientada a objetos?
El objetivo de LabVIEW es poner el poder de programar la PC en manos de ingenieros y científicos que no están formalmente entrenados en programación. Queremos estructurar LabVIEW para que la interfaz se sienta intuitiva para aquellos usuarios que no tienen un entrenamiento formal en programación.

La programación orientada a objetos ha demostrado su superioridad sobre la programación de procedimientos como una opción de arquitectura en varios lenguajes de programación. Fomenta las divisiones claras entre las secciones del código, es más fácil de depurar y se adapta mejor a grandes equipos de programación. El equipo de I&D de LabVIEW quería que nuestros clientes tuvieran acceso este poder. Queríamos que el lenguaje pudiera reforzar algunas de las mejores prácticas de software.

La programación OO requiere más planificación previa que la programación por procedimientos. Si escribe un conjunto de VIs para usarlo un par de veces, tal vez para calcular algún valor o para obtener un valor particular de una tarjeta DAQ, entonces desarrollar una clase para agrupar esos VIs probablemente sea una exageración. Pero si esos VIs forman una aplicación que planea mantener durante unos años, la OO puede ser la opción correcta. Queríamos que LabVIEW tuviera las herramientas del diseño de software moderno sin que esas herramientas se interpusieran en el camino de la generación de prototipos de ingeniería por la que LabVIEW es conocido.

Las secciones de este documento actualizadas para LabVIEW 2009 se indican con [LV2009] en el texto.

 

Para aquellos que eligen diseños de objetos,
queremos los cables y nodos
se transformen de forma natural
en clase y método.


¿Qué decidimos que significaba "programación orientada a objetos"?
C++ es un lenguaje orientado a objetos. Java también. Y Smalltalk. Y la lista sigue. Cada uno de estos lenguajes incluye un conjunto de funciones único. Cuando decidimos agregar OO a LabVIEW, tuvimos que decidir lo que eso significaba. ¿Las plantillas de C++ fueron una característica de C++ o una característica de OO? ¿Qué tal la sobrecarga del operador? ¿O la palabra clave "sincronizado" de Java?

Decidimos que OO significa dos cosas: encapsulación y herencia. Estos son los pilares gemelos que tuvimos en mente a la hora de decidir qué teníamos que argumentar para llamarnos un lenguaje OO. Los usuarios de LabVIEW tenían que poder "encapsular" un tipo de datos de clase - definir un bloque de datos, como un clúster, y decirle a LabVIEW que permita el acceso a esos datos solo en funciones especificadas por el usuario. Los usuarios también tenían que poder "heredar" una nueva clase; elegir una clase existente y crear una nueva usando la existente como punto de partida y anular los métodos de la clase principal. Centrarse en estos dos principios ayudó a prevenir el exceso de características.

¿Cómo encaja la OO con el flujo de datos? (El gran debate entre Por Valor vs. Por Referencia)
LabVIEW utiliza sintaxis de flujo de datos. Un nodo en un diagrama opera en sus entradas y la mayor parte, opera únicamente en esas entradas, sin afectar los valores en otros cables en otras partes del diagrama. Cuando un alambre se bifurca, su valor se duplica. Estos cables utilizan una sintaxis "por valor" coherente con el flujo de datos. Las excepciones son los tipos de datos refnum. Un refnum es una referencia a una ubicación compartida en la memoria que puede ser operada por varios nodos a través de algún mecanismo de protección. Los tipos de refnum en LabVIEW incluyen las colas, E/S de archivos, tipos de servidores de VI, etc. Cuando un cable se bifurca, la memoria compartida no se duplica. Lo único que se bifurca es el número que le permite a LabVIEW buscar esa memoria compartida. Estos cables utilizan una sintaxis "por referencia". Al desarrollar las clases de LabVIEW, nos enfrentamos a una decisión prematura sobre si los cables de la clase deberían ser por valor o por referencia.

Considere C++. Ese lenguaje tiene dos maneras de declarar un objeto: en la pila o como un puntero en el montón. Cuando usted pasa el objeto a una función o lo asigna a otra variable, debe estar siempre consciente de si está pasando el objeto por valor o por referencia. ¿Acaba de hacer una copia de sus datos o está compartiendo sus datos con otra persona? Java y C#, por otro lado, solo tienen una sintaxis por referencia. Las cambiantes asignaciones de los parámetros de función siempre hacen referencia al objeto original. Para hacer un duplicado, usted crea explícitamente un nuevo objeto utilizando el original como fuente.

¿Qué sería apropiado hacer en LabVIEW? En un lenguaje de flujo de datos, usar la sintaxis por valor significa que el valor de cada cable individual es independiente de todos los demás cables. Esto permite que varios hilos se ejecuten en el código sin preocuparse de que una sección del código afecte los valores en otra sección. La sintaxis por referencia rompe esa independencia. De repente, el usuario es responsable de rastrear cuántas veces se ha compartido una referencia y de asegurarse de que ninguna sección del código acceda a la referencia al mismo tiempo que cualquier otra sección. El usuario debe comprender las secciones protegidas, las exclusiones mutuas y las condiciones de carrera. La parte positiva es que construir ciertas estructuras de datos, en particular gráficas cíclicas o bases de datos de relación, es más sencillo con referencias, y estas estructuras de datos a menudo complejas son una gran razón por la que alguien podría usar OO.

LabVIEW ya tiene mecanismos para compartir datos en un cable. Aunque esos mecanismos son insuficientes según algunos estándares, existen y mejoran en cada nueva versión de LabVIEW. LabVIEW no necesitaba otra manera de compartir datos. Necesitaba una manera de aislar los datos. Es difícil garantizar la coherencia de los datos cuando usted no puede restringir los cambios en los datos. Para soportar la encapsulación, decidimos que una clase en LabVIEW debería ser básicamente un clúster que no se puede desagrupar por todos los VIs. Por lo tanto, a diferencia de Java o C# y a diferencia de C++, LabVIEW tiene una sintaxis pura por valor para sus objetos. Cuando un cable se bifurca, el objeto puede duplicarse, según lo decida el compilador de LabVIEW.

Elegir por valor en lugar de por referencia afecta todas las demás decisiones del diseño. Muchos de los patrones de diseño OO estándar se basan en que un objeto puede indicar: "Me importa ese objeto de allí". LabVIEW tiene mecanismos para crear referencias a datos; registros de cambios no inicializados, variables globales, colas de un solo elemento, pero el usuario debe trabajar más para utilizar estos mecanismos de lo que tendría que hacer si tuviéramos una sintaxis nativa por referencia. Y además, estos otros mecanismos han demostrado ser suficientes para construir muchas estructuras de datos complejas. Pero hay un problema con las soluciones por referencia: estas estructuras no son consistentes con el flujo de datos, por lo que no se benefician de una de las mayores fortalezas de LabVIEW: el paralelismo natural. Seguir los patrones de diseño de otros lenguajes produce ineficiencias y errores extraños. Con el tiempo, han surgido nuestros propios patrones de diseño, lo que demuestra cuán poderosa puede ser la sintaxis por valor.

Esta decisión es el mayor motivo de discusión cuando las personas se topan por primera vez con LVOOP, pero el equipo de I&D de LabVIEW ha pasado años revisándolo y estamos bastante seguros de que esta es la elección correcta para LabVIEW. Se considerará la mejora de nuestras capacidades de referencia en el futuro, pero una implementación sólida por valor es fundamental para cualquier desarrollo real y consistente con el flujo de datos.

[LV2009] En LabVIEW 2009, introdujimos referencias de valor de datos. Estos son un nuevo tipo de datos refnum capaz de servir como referencia a cualquier dato de LabVIEW, ya sea un entero puro, un arreglo, un clúster o un objeto de LabVIEW. Se habla sobre esta nueva adición al lenguaje más adelante en este documento.

¿Qué consideraciones se tuvieron para la elección de vocabulario?
El nombre elegido para un concepto afecta cómo las personas piensan sobre ese concepto y qué tan fácil es para ellos aprender el concepto. Cuando llevamos la programación orientada a objetos a LabVIEW, se discutió mucho sobre cuánta "informática" introducir. LabVIEW tiene como objetivo hacer que la programación sea accesible para los usuarios sin capacitación en informática. Nuestros clientes son científicos e ingenieros que realizan pruebas y medidas, control industrial y diseño de hardware. El lenguaje de LabVIEW es accesible para muchos de estos clientes, en primer lugar debido a su parecido con un esquema de cableado. Intentamos encontrar metáforas que fueran igualmente accesibles para los diversos conceptos de la OO.

Decidimos usar términos familiares para la jerarquía de herencia: padre e hijo, hermano y primo. Estos son conceptos que los clientes van a tener en su vocabulario. Cuando un orador se refiere al padre de una clase determinada, los oyentes comprenden la relación y pueden seguir la conversación fácilmente. Podríamos haber usado términos como "super clase" o "clase base", pero estos no establecen la misma relación inmediata en la mente de una persona como lo hacen los términos familiares. Al equipo de localización le gustaron estos términos por la facilidad de traducción. También decidimos que las interfaces de usuario y la documentación siempre dibujarían árboles de herencia con los ancestros arriba y los descendientes abajo. En otros lenguajes, se ha perdido demasiado tiempo en reuniones de diseño con desarrolladores que no se dan cuenta de que están mirando los árboles al revés. Queríamos fomentar la coherencia entre los desarrolladores de LabVIEW.

Con la terminología de ámbito, decidimos que lo mejor era seguir los estándares de la industria de "privado", "protegido" y "público". En un inicio algunos desarrolladores se opusieron a la palabra "protegido". Los datos privados y los datos públicos son conceptos intuitivos. La palabra "protegido" no es tan significativa - ¿protegido de qué?; y hubo la tentación de elegir una palabra que identificara realmente el ámbito, como "familia", de acuerdo con los conceptos que habíamos elegido para la herencia. Sin embargo, el término "protegido" se usa constantemente en toda la industria, independientemente del idioma, y es probable que los usuarios encuentren ese término en cualquier ayuda o libro de texto de terceros. Dado que el término no entraba en conflicto con ningún concepto existente en LabVIEW, decidimos apegarnos a los términos estándares. [LV2009] Cuando agregamos el ámbito de "comunidad", no había un concepto análogo en la mayoría de los otros lenguajes de programación. En este caso creamos nuestro propio término. Si su árbol de herencia era "familia", entonces sus amigos formaron su "comunidad". Sentimos que esto explicaba más claramente cómo "comunidad" encajaba con los otros ámbitos, aunque para ser honesto, algunos de nosotros nos decepcionó que no pudiéramos encontrar un término que comenzara con la letra P, solo para encajar con los tres ámbitos originales.

La palabra "clase" provocó el debate más grande. "Clase" es obviamente el estándar de la industria para "un bloque de datos y las funciones que operan en esos datos agrupados". Es la idea fundamental en la programación OO. Pero debatimos si otra palabra sería más accesible para nuestros usuarios. LabVIEW ya tenía la idea de un typedef; un VI de control que define un nuevo tipo de datos basado en tipos existentes. Una propuesta evitaba la palabra clave "clase" y en su lugar hablaba de un nuevo tipo de typedef: el objeto typedef. Aunque esto podría ser más accesible conceptualmente para el usuario de LabVIEW que está viendo la OO por primera vez, era probable que los primeros en adoptarlo fueran aquellos que habían visto la OO en otros lenguajes. No utilizar el término "clase" habría confundido a esas personas; ¿cómo puede un lenguaje ser orientado a objetos sin clases? Entonces, al igual que con "protegido", nos decidimos por el estándar de la industria. Tuvimos cuidado en la documentación para diferenciar las "clases de LabVIEW" de las "clases del servidor de VI".

Los conceptos "virtual" y "despacho virtual" plantearon un problema para LabVIEW. Estos términos también son estándares en la industria. En C#, C++ o Java, las funciones virtuales son funciones que invocan diferentes versiones en base al tipo de datos en tiempo de ejecución de una de las entradas (el objeto "este"). Las funciones de LabVIEW se denominan "instrumentos virtuales". Hablar de "instrumentos virtuales" sería complicado. Además, "virtual" nunca ha parecido un término particularmente preciso; es difícil comprender qué aspecto es exactamente "virtual" en estas funciones. Así que elegimos "dinámico" y "despacho dinámico". Estos contrastan bien con el concepto de despacho estático, que es el tipo de llamada de subVI que LabVIEW ha utilizado durante décadas. Usar "dinámico" y "estático" identifica claramente lo que separa a estos dos grupos de VIs. La palabra "estático" tiene todo tipo de significados en otros lenguajes de OO, lo que generó problemas para LabVIEW; se habla sobre este término más adelante en este documento.

Hubo otras pequeñas discusiones sobre otros términos. El vocabulario elegido de la OO es una característica como cualquier aspecto de la interfaz de usuario. Para hacer accesible la OO, cada nuevo concepto tenía que ser desafiado. No podríamos asumir que el nombre de C++ o Java para una característica fuera el nombre correcto para LabVIEW.

El diseño de una clase de LabVIEW


¿Cuál es el propósito de la clase "Objeto de LabVIEW"?
"Objeto de LabVIEW" es el nombre de una clase particular de LabVIEW. Esta clase especial es el antepasado principal para todas las clases de LabVIEW. JAVA tiene una clase similar (java.lang.object). C++ no la tiene. C# tiene un tipo de "objeto" híbrido que es la raíz de todos los tipos de clase y tipos integrados, como los numéricos.

Tener una clase de ancestro común brinda una manera de escribir funciones que pueden tomar cualquier clase y operar en esas clases de una manera común. C++ no necesita tal clase porque tiene plantillas, pero agregar plantillas a un lenguaje aumenta drásticamente la complejidad del lenguaje. Aunque LabVIEW algún día podría tener plantillas de clase, consideramos que era necesaria una solución menos compleja. Una clase de ancestro común brinda un tipo que los nodos como Build Array pueden usar para almacenar objetos de varias clases en un solo arreglo. Puede decirse que LabVIEW no necesita esta "clase genérica" porque tiene el tipo de datos variante que puede contener cualquier dato de LabVIEW. Pero Objeto de LabVIEW puede conectarse a la función To More Specific Class, algo que el tipo variante no puede hacer (y sería extraño si realizáramos cambios de modo que LabVIEW permitiera que la variante lo hiciera).

Un caso de uso específico para la clase de Objeto de LabVIEW es para clases de carga dinámica en un framework. Hay dos funciones notables que devuelven el Objeto LabVIEW: la propiedad "Default Instance" de una referencia LVClassLibrary y el "Get LV Class Default Value.vi". Ambas funciones buscan una clase (una usando un refnum, la otra usando una ruta) y devuelve una instancia de la clase con los valores predeterminados para todos los campos, según lo establecido en el control de datos privados. Estas funciones le permiten cargar y ejemplificar de manera dinámica cualquier clase. Los usuarios confían en esta técnica común en aplicaciones "plug-in". Debido a que Objeto de LabVIEW no tiene ningún método definido en él, usted querrá usar la función To More Specific Class para convertir el cable a un tipo de clase que tenga métodos definidos. Para una arquitectura plug-in, definiría una clase llamada "Generic Plugin.lvclass" o un nombre similar, con todos los métodos definidos. Utilice esta clase como padre para todos sus elementos específicos conectados. Luego, puede cargar clases hijas de manera dinámica. Los refnums de clase no son soportados por el run-time engine de LabVIEW, por lo que cualquier cosa que se construya como un DLL o EXE o se descargue a un dispositivo deberá usar el VI.

 

Método original LV 8.2 (no funciona en el run-time engine )

  1. Abra referencia al archivo .lvclass en el disco. Esto devuelve un refnum LVClassLibrary.
  2. Obtenga la instancia predeterminada de la clase.
  3. Transmita el Objeto de LabVIEW que se devuelve a su tipo de datos de complemento.

 

LV 8.5 y método posterior (funciona en el run-time engine)

  1. Cargue la clase en la memoria usando Get LV Class Default Value.vi, que se encuentra en la paleta Cluster, Class & Variant.
  2. Transmita el Objeto de LabVIEW que se devuelve a su tipo de datos de complemento.


¿Por qué las clases tienen únicamente datos privados? ¿Por qué no usar datos protegidos o públicos?
Así como desafiamos los nombres usados en otros lenguajes, también desafiamos características completas. LabVIEW tiene únicamente datos privados. Usted no puede crear datos públicos o protegidos. Únicamente los VIs pueden ser públicos o protegidos. 

La razón más convincente por la que todos los datos son privados es la corrección del código para los usuarios. Intentamos establecer el lenguaje y el entorno de LabVIEW de tal manera que los usuarios elijan la mejor arquitectura, incluso cuando no están capacitados para elegir arquitecturas, y eso significa disminuir la cantidad de conceptos de informática que deben comprender. Acceder a los datos de una clase solo a través de un método, proporciona un cuello de botella para depurar cambios de valor. Crea un lugar donde se puede agregar verificación de rango y otros códigos de verificación de estado. Y se asegura de que el código fuera de la clase no tenga ninguna dependencia binaria en la clase, por lo que las nuevas revisiones de la clase se pueden difundir incluso en las aplicaciones creadas. Estas son las principales ventajas de la encapsulación de datos de clase y queremos alentar a las personas a diseñar software que, naturalmente, tenga esta ventaja. Al no usar datos públicos o protegidos, eliminamos del lenguaje un concepto informático potencialmente confuso (al menos, eliminamos una cosa más que necesita explicación) e impulsamos a las personas hacia una mejor arquitectura.

[LV2009] Los datos tampoco se pueden colocar en el ámbito de comunidad, por las mismas razones discutidas anteriormente.

Esta elección provoca la proliferación de métodos de accesor. Crear métodos de "lectura" y "escritura" para cada valor de datos en una clase es un proceso que sabemos es engorroso. LabVIEW 8.5 introdujo la capacidad de crear VIs de acceso desde un diálogo de asistente y LV2009 agregó más opciones a ese diálogo.

Nos preocupaba la sobrecarga de rendimiento al realizar una llamada de subVI en lugar de simplemente desagrupar los datos. Descubrimos que nuestra sobrecarga actual del compilador para la llamada de subVI es insignificante (medida entre 6 y 10 microsegundos en una PC promedio). Una operación para agrupar/desagrupar directamente en el VI que llama o en un subVI es casi la misma cantidad de tiempo. Tenemos un problema con las copias de datos para elementos desagrupados porque gran parte del algoritmo de reemplazo de LabVIEW no opera a través de los límites del subVI. Como solución alternativa, también puede optar por evitar los métodos de accesor a favor de métodos que realmente hacen el trabajo en el subVI para evitar devolver elementos del subVI, y así evitar las copias de datos. De todos modos, esta elección suele ser un mejor diseño de OO, ya que evita exponer una implementación privada de una clase como parte de su API pública. En versiones futuras, esperamos que la inserción de subVI sea posible, lo que eliminará la sobrecarga por completo.  

Creemos que a largo plazo es mejor perfeccionar la experiencia del editor y la eficiencia del compilador para estos VIs en lugar de introducir datos públicos/protegidos/comunitarios. 

¿Dónde están los constructores?
Esta pregunta generalmente se hace con tono de frustración por alguien que conoce OO por otro lenguaje de programación y ha estado usando LVOOP durante un par de horas. Algo tan fundamental para el lenguaje no puede estar tan oculto, y la persona se siente frustrada por no poder encontrarlo o enojada con nosotros por hacer que sea tan difícil de encontrar. Pero la respuesta es simple: No hay constructores.

Consideremos los casos de uso para constructores:

  1. Establecer los valores iniciales de un objeto.
  2. Calcular los valores iniciales del objeto a partir de un conjunto de parámetros.
  3. Calcular los valores iniciales del objeto a partir de los datos del entorno (como registrar la estampa de tiempo cuando se construyó el objeto).
  4. Reservar recursos del sistema (memoria, canales de comunicación, hardware, etc.) para su uso por este objeto (para ser liberado posteriormente por el destructor).

LabVIEW tiene la capacidad de establecer el valor predeterminado para su clase. Los valores que usted establece en su control de datos privados son los valores predeterminados para su clase. Ahora, ¿estos son valores calculados? No. Son estáticos. Usted no tiene ningún lugar para colocar el código en ejecución como parte de su valor predeterminado. Esto es lo mismo que un simple constructor predeterminado en otros lenguajes de OO. El valor inicial para todas las instancias de la clase es solo este valor predeterminado, por lo que se cumple el caso de uso del constructor #1.

¿Cómo declara un número entero en C++?

int k = 1;

¿Cómo declara un número entero en LabVIEW?

Se suelta un control numérico, se establece su representación en un número entero, se escribe un 1 y se establece el valor actual como predeterminado.


Considere esta clase de C++ ...

class Thing {
privado:
int mx;
double mz;
std::string ms;
público:
Thing() : mx(1), mz(2.5), ms("abc") { }
Thing(int x) : mx(x), ms("def") {
mz = (x > 1) ? 3.5 : 2.5;
}
~Thing() { }
void Init(int x) {
mx = x; mz = (x > 1) ? 3.5 : 2.5; ms = "def";
}
};


¿Cómo invoca un constructor no predeterminado en C++?

Thing item(k+3);

¿Cómo invoca un constructor no predeterminado en LabVIEW?

No puede. Esto no es realmente una pregunta importante para LabVIEW. Usted no pasa parámetros a los controles para inicializar sus valores. Obtienen su valor de un parámetro que se paso al VI o de un valor escrito por el usuario en el panel frontal.


La idea de un constructor es un problema en el mundo del flujo de datos. Todo comienza con un valor (calculado completamente sin entradas externas) o los valores fluyen hacia él y se calcula cuando llegan esos valores. Una clase de LabVIEW tiene un valor inicial en la creación definida por el tipo de datos en el control de datos privados. Ese es el caso de uso del constructor #1. Todas las instancias se inicializan con este mismo valor. Si desea un comportamiento m[as extenso, lo puede definir en el diagrama de bloques creando un subVI que toma entradas que no son de clase y genera una clase. Ese es el caso de uso del constructor #2.

El caso de uso del constructor #3 es un concepto más avanzado para programadores de software. LabVIEW tiene soporte para este concepto, pero no de una manera obvia. Contar cuántas instancias de la clase existen (usando un campo estático de clase) o registrar la estampa de tiempo de una instancia determinada es una funcionalidad que la mayoría de las clases no necesitan. Dejemos esa funcionalidad para una herramienta más avanzada. Usted construye un XControl de su tipo de clase. El XControl tiene código que se ejecuta en tiempo de edición y en tiempo de ejecución para inicializar valores de la clase. En el Façade VI puede incluir cualquier código que usted necesite para establecer los valores para la instancia de sus datos. Los creadores de la clase hoy en día pueden encontrar esto un poco engorroso. Es algo que ciertamente esperamos hacer más sencillo con el tiempo. Pero es el lugar correcto para colocar esta funcionalidad, en el código que está detrás del control.

El caso de uso del constructor #4 solamente se puede discutir en combinación con la siguiente pregunta.

¿Dónde están los destructores?
¿Cuál es la vida útil del entero de C++?

Existe desde el punto en que se declara hasta la llave de cierre.

¿Cuál es la vida útil del entero de LabVIEW?

Desconocida. ¿Hasta que donde termina el cable? ¿Hasta que se cierra el panel frontal? ¿Hasta que el VI deja de ejecutarse? Básicamente, es solo un valor en un cable o en un control/indicador. Estará ahí hasta que ese artículo ya no sea necesario.


LabVIEW no tiene "vida útil espacial". LabVIEW tiene una "vida útil temporal". Un dato existe mientras sea necesario y desaparece cuando ya no se necesita. Si se copia en el control del panel frontal, permanece ahí, incluso después de que finaliza la ejecución. Las copias en los cables existen hasta la siguiente ejecución de ese cable. Se hará otra copia para cualquier punta de prueba en el cable. En un lenguaje de flujo de datos teórico puro, habría una copia aparte en cada cable individual, porque cada cable es una unidad de cálculo independiente. Por supuesto, eso sería ineficiente de implementar, por lo que el compilador de LabVIEW optimiza el número de copias. Pero el principio es el mismo: los datos viven durante mucho tiempo, algunas veces viven más que el programa que generó esos datos.

En dicho entorno, considere el caso de uso del constructor #4. ¿Cuándo reservaría recursos el constructor? ¿Qué pasaría cada vez que se hiciera una copia? ¿Necesitaría diferentes constructores para la copia realizada en una bifurcación del cable y para la copia en un indicador y para la copia en una punta de prueba? Si intenta responder estas preguntas, terminará enumerando muchos casos especiales y excepciones. Decidir exactamente cuándo ejecutar estos constructores implícitos para reservar recursos es muy difícil y si encontró respuestas satisfactorias, aún tiene que decidir cuándo debe ejecutarse el destructor para liberar esos recursos.

La reserva de recursos en LabVIEW se maneja a través de tipos de datos refnum. Los tipos refnum copian el número de referencia por valor en el cable, y usted debe invocar explícitamente una función para realizar una "copia profunda" o liberarlos. El tipo de datos de cola, por ejemplo, cuenta con obtener cola que reserva la referencia y con liberar cola que la libera. A menos que llame liberar cola, la cola permanecerá en la memoria hasta que el VI finalice la ejecución, momento en el que LabVIEW lo liberará implícitamente.

Sin un mejor concepto de "vida útil", el caso de uso #4 no es viable.

En resumen, LabVIEW, como lenguaje de flujo de datos, normalmente no tiene variables. Los cables no son variables. Los controles del panel frontal no son variables. Incluso a las variables locales o globales no se les asigna una vida útil específica, como ocurre con las variables en otros lenguajes. Las variables son parte de un modelo que entra en conflicto con el flujo de datos. Sin variables y sin una manera de definir la vida útil de esas variables, la construcción y la destrucción son conceptos sin sentido y por lo tanto, los dejamos fuera del diseño inicial del lenguaje.

[LV2009] Las referencias de valor de datos proporcionan una solución a este problema. Debido a que son tipos de datos refnum, tienen una vida útil bien definida. Se pueden crear DVR para cualquier tipo de datos de LabVIEW, pero hicimos algo especial para las clases de LabVIEW. En el cuadro de diálogo Propiedades de la clase, hay una opción en una clase, habilitada por defecto, para limitar la creación y destrucción de DVR de objetos de esa clase únicamente para los VIs miembros de la clase. O, para decirlo de otra manera, únicamente los VIs miembros de la clase pueden crear referencias para la clase. Cualquier otro VI que intente conectar un cable del tipo de clase a la nueva referencia de valor de datos primitiva, se romperá. Esta restricción significa que una clase puede poner código de inicialización en sus VIs miembros para garantizar que nunca se cree ninguna referencia a la clase sin que se le asigne un valor adecuado (según lo define el autor de la clase). De manera similar, ninguna referencia puede ser destruida excepto por un VI miembro, que proporciona una manera para que el autor de la clase tenga un código de desasignación. Este código de desasignación no se invoca si la referencia se libera implícitamente mediante una detención del VI, pero siempre que usted destruya explícitamente cualquier referencia que cree, los DVRs brindan un alcance de por vida para los objetos de LabVIEW y, por lo tanto, son compatibles con el caso de uso # 4.

Tenga en cuenta: Los tipos de referencia deben usarse rara vez en sus VI. En base a muchos análisis de los VI de clientes, en I&D estimamos que de todas las clases de LabVIEW que haya creado, menos del 5% deberían requerir mecanismos de referencia. Si usted termina haciendo más que eso, piense en maneras de utilizar más clases por valor, porque es casi seguro que está gastando su desempeño personal innecesariamente.

¿Cuál es el diseño en memoria de una clase?
Almacenar clases de manera eficiente en LabVIEW fue un desafío interesante.

Los datos de una clase se definen en dos partes:

  1. La porción de datos heredada de su padre
  2. Su propio clúster de datos privados


Puede pensar en cualquier clase dada como un clúster de clústeres: cada uno de los clústeres internos proviene de uno de sus ancestros, excepto el último, que es el clúster de datos privados de la clase en sí. El ancestro final, Objeto de LabVIEW, no aporta campos de datos.


LabVIEW no tiene una pila de funciones. El flujo de datos, donde los nodos en diferentes diagramas que se ejecutan en paralelo pueden entrelazarse entre sí, hace que una arquitectura de pila sea problemática. En cambio, cuando un VI se compila, LabVIEW asigna un "espacio de datos" para ese VI. El espacio de datos es una asignación de todos los datos necesarios para ejecutar ese VI. Cualquier hilo sabe que es libre de escribir en su región del espacio de datos sin tener que preocuparse de que otro hilo esté escribiendo allí, por lo que no es necesario bloquear el mutex.

Para implementar clases, necesitábamos poder asignar una clase en el espacio de datos. Un cable que es del tipo padre debe poder transportar datos de su propio tipo o de cualquier tipo descendiente, por lo que la asignación que hacemos en el espacio de datos debe poder contener cualquiera de las clases. Eso significa que no podemos simplemente asignar estos clústeres de clústeres directamente.

Para complicar aún más el diseño, un objeto debe llevar consigo su información de clase. Un objeto es un dato consciente de sí mismo. Conoce su propio tipo (es por eso que los objetos pueden realizar operaciones como una clase más específica y despacho dinámico). La información de la clase tiene que ser parte del objeto en algún momento.

Entonces, la estructura final en la memoria se ve así:



LabVIEW asigna una sola instancia del valor predeterminado de la clase. Cualquier objeto que sea el valor predeterminado comparte ese puntero. Este intercambio significa que si tiene un arreglo muy grande como valor predeterminado de su clase, inicialmente solo habrá una copia de ese arreglo en la memoria. Cuando usted escribe en los datos de un objeto, si ese objeto comparte actualmente el puntero de datos predeterminado, LabVIEW duplica el valor predeterminado y luego realiza la escritura en el duplicado. Al compartir la instancia predeterminada todo el sistema use mucha menos memoria y hace que abrir un VI sea mucho más rápido.

¿Puedo definir clases de forma recurrente?
Los datos en el control de datos privados pueden ser de cualquier tipo de LabVIEW bien definido, incluyendo otras clases de LabVIEW. El clúster puede incluso dejarse vacío para las clases que definen métodos únicamente y no tienen datos propios.

Una clase recurrente es una clase que usa su propia definición como parte de sí misma. Este tipo de clase aparece en otros lenguajes de objetos. Por ejemplo, en C++, puede encontrar:

class XYZ {
int data;
XYZ *anotherInstanceOfThisType;
};

Por tanto, parece que la clase utiliza su propio tipo para definirse a sí misma. Esto no es exactamente correcto. Técnicamente, el tipo de campo es en realidad "puntero a XYZ", no "XYZ" en sí. Es importante de comprender esta distinción técnica, cómo estos otros lenguajes pueden asignar memoria para un objeto definido de forma recurrente. En realidad, no reservan espacio para el objeto. Reservan espacio para una referencia a tal objeto.

Si incluye un control de clase de LabVIEW en el clúster de datos privados, toda la clase se incluye en los datos privados. No es una referencia a la clase. Por lo tanto, una clase no puede contenerse a sí misma en sus propios datos privados.

LV2009] LabVIEW tiene controles refnum. El refnum de cola o los nuevos controles de refnum de valor de datos se pueden usar para dar a los datos privados de una clase una referencia a otra clase. Muchos usuarios podrían esperar que esta técnica permitiría a la Clase X incluir un refnum a la Clase X. Técnicamente, sería posible, pero LabVIEW lo prohíbe. Esta fue una fuente de mucho debate en I&D. Tratar de explicar qué otras clases podrían usarse legalmente en una clase determinada y bajo qué condiciones podría resultar confuso para las personas menos familiarizadas con la programación en general o específicamente con OO. Finalmente nos decidimos por una regla estricta y rápida: La clase X puede contener cualquier clase que no utilice en sí misma la clase X. Muy sencillo. Una clase padre no puede incluir su propia clase hijo porque el hijo usa el padre. Pero un hijo puede usar al padre porque el padre no usa al niño. Tener esta regla permite que los comportamientos de carga y descarga sean muy limpios, sin preocuparse por las dependencias circulares.

Si necesita que la clase X incluya una referencia a la clase X, en su lugar use una referencia al padre de X. Debido a que siempre es válido que un objeto hijo tenga valor padre (en un control padre o en un cable de tipo padre), usted puede crear sus referencias de esta manera.

Hay un aspecto bastante curioso de LabVIEW OOP que puede ser contradictorio para los usuarios de otros lenguajes de programación. Una clase hija puede incluir la clase padre como un miembro de datos de la clase hija. La clase padre está completamente definida sin la clase hija y, por lo tanto, puede usarla como parte del clúster de datos privados de la clase hija sin crear una definición de tipo recurrente. En tiempo de ejecución, puede almacenar cualquier valor en esa posición, incluyendo otra instancia de la propia clase secundaria, sin necesidad de tener un tipo de datos de referencia. Por lo tanto, puede crear una cadena de objetos del mismo tipo, terminados por una instancia de la clase padre. De hecho, cualquier lista vinculada o estructura de datos de árbol se puede construir de esta manera; y esas estructuras de datos son completamente seguras para el flujo de datos. Se han publicado ejemplos de esto en varios lugares de ni.com y lavag.org.

¿Cómo hago datos de clase (también conocidos como datos estáticos)?
Los datos de clase son datos que no forman parte de las instancias del objeto. Solamente existe una copia de los datos de la clase en la memoria y el acceso a esa única instancia puede tener un ámbito público, protegido o privado. Hay varias formas de crear datos de clase.

El más fácil es el VI global. Agregar el VI global a su clase y luego configurar el ámbito para que sea privado o protegido limitará qué otros VIs pueden acceder a esos datos. Pero los VI globales, incluso cuando están dentro de clases, por lo general son inaceptables; no son seguros porque operan fuera del flujo de datos y tienen una sobrecarga de rendimiento significativa (se requiere una copia incluso para una operación de solo lectura).

Una mejor solución es un VI con un registro de cambios no inicializado, conocido como "global de estilo LV2" en la ayuda en línea. Este método de crear datos globales ha existido en LV desde LabVIEW 2.0 (de ahí el nombre). Puede hacer que dicho VI sea miembro de una clase y luego establecer el ámbito para que sea privado o protegido. Esos globales le permiten definir el conjunto de operaciones seguras disponibles en sus datos. Para más detalles, vea la Ayuda de LabVIEW o los VIs de ejemplo. Incluimos un ejemplo específico que muestra una posible manera de implementar el patrón de diseño "singleton" estándar en la industria.

El diseño de métodos de clase


¿Qué cable es el objeto "este"? ¿Cómo hago métodos estáticos de clase?
Tanto C++ como Java tienen el concepto de un objeto "este". Cuando usted define el método de un objeto en estos lenguajes, especifica los parámetros explícitos de la función. El lenguaje asume un parámetro implícito adicional, el objeto sobre el que opera el método. El parámetro implícito se llama el objeto "este" y se usa una sintaxis especial para acceder a partes de ese objeto.

En estos lenguajes, un "método estático de clase" es una función que forma parte de la clase pero no tiene un parámetro implícito. No puede utilizar la sintaxis especial "este" en un método estático de clase porque no hay un objeto implícito.

LabVIEW no incluye ninguno de estos conceptos. Usted declara explícitamente todas las entradas a sus VIs miembros en el panel del conector. LabVIEW nunca agrega una entrada implícita. Debido a que no existe una entrada implícita, no existe diferencia entre un VI que tiene dicha entrada y uno que no la tiene.

LabVIEW incluye métodos que describimos como "estáticos", pero usamos el término para un significado diferente al de los otros lenguajes. LabVIEW distingue entre dos tipos de métodos: métodos dinámicos y métodos estáticos. Un método estático es una simple llamada de subVI, que LabVIEW ha tenido desde sus inicios. Un método se llama "estático" porque el nodo de subVI siempre llama al mismo subVI. Por el contrario, un método dinámico es un conjunto de VIs. Un nodo de subVI dinámico usa despacho dinámico para llamar a uno de los VIs en el conjunto, pero no se conoce exactamente cuál hasta el momento de la ejecución.

¿Cómo funciona el despacho dinámico? ¿Cuál es la sobrecarga de esto en comparación con una llamada de subVI regular?
El despacho dinámico es la característica por la cual un nodo que se parece a una llamada de subVI en realidad llama a uno de los varios subVIs en tiempo de ejecución, dependiendo del valor en el cable en la terminal de entrada de despacho dinámico. Conceptualmente, es como un VI polimórfico que elige qué VI ejecutar en tiempo de ejecución en lugar de en tiempo de compilación.

Debido al servidor de VI, que soporta llamadas dinámicas a cualquier VI, los probadores muchas veces asumen que el despacho dinámico es lento en comparación con las llamadas de subVI. Sin embargo, el compilador tiene mucha más información sobre el conjunto específico de VIs que pueden invocarse para una llamada de despacho dinámico que para una llamada de VI Server genérico y por lo tanto puede proporcionar un mejor rendimiento.

Cada objeto que viaja por el cable lleva un puntero a la información de su clase (consulte la sección anterior en este documento: "¿Cuál es el diseño en memoria de una clase?"). Esa información de clase incluye la "tabla de despacho dinámico", que es una tabla de referencias de VI. Cada clase copia la tabla de sus padres exactamente. Luego reemplaza la referencia del VI para cualquier función padre con sus propios VIs de anulación. La clase agrega a su tabla cualquiera de sus VIs de despacho dinámico que no sean anulaciones de su padre.

  • Parent.lvclass define dos VIs de despacho dinámico: A.vi y B.vi.
  • Child.lvclass hereda del padre. Define dos VIs de despacho dinámico: B.vi y C.vi. B.vi anula la segunda entrada de la tabla.
  • Junior.lvclass hereda del hijo. Define dos VIs de despacho dinámico: A.vi y D.vi. A.vi anula la primera entrada de la tabla.

Un nodo de subVI de despacho dinámico en el diagrama registra un número de índice particular cuando se compila. Un nodo que representa una invocación de A.vi, por ejemplo, registra el índice 0. Un nodo para una invocación de B.vi registraría el índice 1. En tiempo de ejecución, siempre que un objeto desciende por el cable, el nodo accederá a la tabla de despacho dinámico de ese objeto. Recuperará el VI en el índice registrado e invocará ese VI. El nodo no crea sobrecarga por búsquedas de nombres o listas de búsqueda para determinar a qué subVI llamar. La complejidad de tiempo es O(1) sin importar que tan profundo sea el árbol de herencia o cuántos VIs de despacho dinámico definan las clases.

A partir de ahí, es principalmente una llamada de subVI. Puede haber un impacto en el rendimiento ya que LV no puede optimizar el reemplazo (duplicación de memoria) tan bien en una llamada de despacho dinámico en comparación con una llamada de despacho estática. LabVIEW minimiza esto optimizando el reemplazo del ancestro más antiguo, por lo que una invocación de B.vi usaría la versión Parent.lvclass de B.vi para decidir si se necesitan copias de las entradas o no. En la mayoría de los casos, encontramos que debido a que los VIs hijos de anulación usan el mismo panel de conector y cumplen la misma función que el VI padre, tienden a necesitar los mismos patrones de reemplazo. Cuando los patrones de reemplazo coinciden, la sobrecarga es la misma que una llamada de subVI. Esto significa que puede obtener un beneficio en rendimiento al conectar las entradas a las salidas en las implementaciones de ancestro, incluso cuando no espera que las implementaciones de ancestro se invoquen directamente (como cuando está utilizando el ancestro como una clase abstracta).

¿Puedo sobrecargar los nombres del VI en clases?
No. La sobrecarga es una característica en otros lenguajes de OO donde dos funciones tienen exactamente el mismo nombre pero una lista de parámetros diferente. El compilador averigua qué función pretendía llamar el programador basándose en los parámetros dados en la invocación de la función. Esta característica significa que puede tener, por ejemplo, múltiples funciones llamadas "Init", una que inicializa el objeto desde un archivo (y por lo tanto toma una ruta como su único parámetro) y otra que realiza la inicialización desde otro objeto (y así toma ese objeto como su parámetro).

Esta característica es la fuente de algunos errores terribles en estos otros lenguajes, particularmente cuando se trata de un despacho dinámico. Si alguien cambia la lista de parámetros en la clase padre pero se olvida de cambiar la lista de parámetros en la clase hija, el compilador los trata como dos declaraciones de función diferentes, no como un error del compilador. Todos los errores ocurren en tiempo de ejecución y puede ser muy difícil deducir exactamente qué es lo que está fallando. En LabVIEW no permitimos que dos VIs con el mismo nombre estén en la memoria bajo ninguna circunstancia. Agregar sobrecarga, una característica que crea una nueva clase de errores difíciles de depurar, no fue una buena razón para que cambiáramos esa regla.

Soporte para funciones avanzadas de OO


¿Hay alguna forma de declarar una clase amiga de otra clase?
[LV2009] Sí, en LabVIEW 2009 y posteriores. Cualquier biblioteca (.lvlib, .lvclass, .xctl, .lvsc) puede declarar una lista de VIs amigos o bibliotecas amigas. Al declarar que un VI es un amigo, la biblioteca le otorga a ese otro VI permiso especial (discutiremos cuál es ese permiso en un momento). Al declarar que otra biblioteca es amiga, esta biblioteca está otorgando a todos los VI miembros de esa otra biblioteca el permiso especial. 

El permiso especial es el derecho a llamar a los VIs miembros de ámbito comunitario. Suponga que la Biblioteca X tiene varios VIs miembros, A.vi, B.vi y C.vi. Ahora, A.vi está marcado como ámbito privado. Eso significa que solo otros VIs que son miembros de X pueden llamar a A.vi. B.vi está en ámbito público, por lo que cualquier otro VI puede llamarlo. Pero C.vi está en el ámbito comunitario. Eso significa que solo pueden llamarlo miembros de la biblioteca X y aquellos VIs fuera de la biblioteca X que sean amigos pueden llamarlo.

Para aquellos que no estén familiarizados con esta idea, declarar a otra clase como "amiga" de esta clase le da permiso a esa otra clase para acceder a las partes privadas de esta clase. La sintaxis "amigo de" es importante para algunas APIs. El ejemplo clásico es una clase Matriz y una clase Vector; la función que multiplica una matriz por un vector necesita acceso a las partes internas privadas de ambas clases.

¿Por qué creamos un nuevo ámbito "comunidad"?
[LV2009] Otros lenguajes de programación que incluyen el concepto de "amigo" generalmente les dan a los amigos un amplio acceso a todas las partes privadas. Darle a un amigo acceso de clase a todos sus métodos y datos privados abre demasiadas puertas traseras para que los datos cambien sin pasar por los VI definidos. Incluso darles acceso solo a los métodos privados es casi siempre excesivo. La mayoría de las veces, los amigos solamente necesitan acceder a un método específico. Al tener un ámbito comunitario, las partes privadas de la biblioteca siguen siendo libres de cambiar sin romper las dependencias externas. Además, la lista de VIs de ámbito comunitario proporciona un mejor registro de por qué un VI o biblioteca determinada se nombró como amigo. Con demasiada frecuencia en los lenguajes de programación, un elemento es amigo de otro y nadie recuerda por qué esa amistad era necesaria. 

¿Por qué las clases descendientes no pueden acceder a los VIs de ámbito comunitario?
[LV2009] Los VIs que están en el ámbito comunitario pueden ser llamados por la biblioteca propietaria y los amigos de esa biblioteca. Si la biblioteca es una clase (a diferencia de una biblioteca de proyecto, XControl o StateChart), entonces la clase puede tener clases descendientes. Los VIs miembros de las clases descendientes no pueden llamar a los VIs de ámbito comunitario. A veces desea que la misma funcionalidad sea utilizada por sus amigos y su familia, y esta división lleva a tener que escribir dos VIs, uno en el ámbito comunitario y otro en el ámbito protegido.

I&D no considera que esto sea un error. Es nuestro comportamiento previsto. Decidimos no complicar la interfaz al tener otro nuevo ámbito que combinaba "protegido y comunidad" en uno. Y no podíamos permitir que las clases hijas tuvieran acceso general a los VIs de ámbito comunitario sin proporcionar un agujero en la protección del ámbito. Solo los amigos deberían poder llamar a los VIs de ámbito comunitario, y si un programador pudiera crear fácilmente un nuevo VI hijo y luego agrupar el VI de ámbito comunitario en su propio VI público, el ámbito comunitario también podría no existir. La lista de amigos es una lista finita y explícita de quiénes pueden usar este VI, de modo que si el panel del conector de ese VI cambia, un usuario sabe exactamente qué VIs de llamada deben ser editados, sin preocuparse de que muchos otros VIs en la jerarquía descendiente puedan también estar usando el VI.

¿Puedo crear una clase interna?
No en esta versión. Una biblioteca de clases no puede contener ningún otro tipo de biblioteca (biblioteca simple, XControl o clase de LabVIEW). Esto es algo que nos gustaría abrir en el futuro, pero la prioridad dependerá de la demanda.

¿Tiene soporte para herencia privada?
Cualquier control de un tipo padre puede contener datos de cualquier tipo descendiente. Los datos que fluyen por un cable padre pueden ser de cualquier tipo descendiente. Esta es la clave de cómo funciona el despacho dinámico y por qué la OO puede ayudarlo a escribir algoritmos genéricos que realizan pasos personalizados para cada tipo de clase. Durante el desarrollo, un cliente beta dio su opinión de que esto era perjudicial para la posición de LabVIEW como un lenguaje fuertemente tipado. Quería una forma de heredar una clase hija de una clase padre pero evitar que la clase hija se convirtiera en un tipo padre. De esa manera, si accidentalmente conectaba un cable hijo a una terminal padre, obtendría un cable roto.

Efectivamente, el cliente estaba pidiendo una característica que otros lenguajes llaman "herencia privada". Con la herencia privada, una clase aún heredaría todos los atributos de su padre, pero ningún VI fuera de la clase sabría acerca de esa herencia y por lo tanto no permitiría que los tipos se conectaran entre sí. LabVIEW solo tiene herencia pública. La herencia privada es un aspecto de la programación OO poco utilizado y que agregaría una complejidad innecesaria. Cualquier intento de apoyarlo requeriría respuestas a algunas preguntas difíciles sobre cómo funcionaría la herencia privada en nuestro lenguaje. A veces, tendría que poder cablear datos hijos a terminales padres para invocar métodos heredados de la clase padre. Pero eso pondría los datos hijos dentro de un control padre en el FP del VI padre. ¿Qué pasaría si el valor de ese control padre fuera accedido, a través de un nodo de propiedad, por un VI que no está en la clase? ¿Obtendríamos un error? ¿O dejaríamos que se devolvieran los datos hijos, aunque se supone que nadie fuera de la clase debe saber que el hijo y el padre tienen alguna relación? Preguntas como estas hacen que la idea de la herencia privada sea difícil de manejar. Es poco probable que LabVIEW alguna vez incluya esta característica.

¿Tiene soporte para herencia múltiple?
La herencia múltiple (tener varias clases padre que reúnen todos los datos y el comportamiento de dos clases) es una gran teoría que en la práctica crea tantos problemas como lo que puede resolver. "Quiero tener todos los atributos de A y de B combinados en C" suena realmente bien, hasta que empiezas a considerar cómo manejar las colisiones de nombres, cómo componer los datos, cómo resolver la herencia diamante (donde D hereda de B y C, que ambos heredan de A) y otros casos feos. LabVIEW no tiene herencia múltiple y probablemente nunca la tendrá. En LabVIEW 2020, agregamos interfaces de LabVIEW, que son un nuevo tipo de datos similar a las clases pero sin datos privados. Las interfaces resuelven la mayoría de los casos de uso de herencia múltiple. Consulte la documentación incluida en LabVIEW 2020 para aprender más sobre las interfaces. Los detalles detrás de su implementación se pueden encontrar en Interfaces de LabVIEW: Las decisiones detrás del diseño

¿Por qué LabVIEW prohíbe las clases de LabVIEW en la interfaz de DLLs construidas en LV (bibliotecas compartidas)?  ¿Por qué no puedo pasar objetos de LabVIEW a un nodo de biblioteca de llamada?
Cuando usa Application Builder para construir una DLL (o una biblioteca compartida en otras plataformas), LabVIEW no permitirá que ningún VI sea exportado desde esa DLL si el panel de conector del VI usa alguna clase de LabVIEW. La interfaz entre LabVIEW y el código externo debe estar en tipos de datos planos.

Si mira hacia atrás en el diseño en memoria de una instancia de clase, verá que hay muchos punteros y asignaciones muy específicos de LabVIEW. Ningún otro EXE o DLL tendrá la capacidad de usar esa estructura de datos u obtener acceso a las partes de la clase necesarias para soportar despacho dinámico o para implementar ámbito de clase. De manera similar, la implementación de clases en C++ varía de un compilador a otro. La mayoría de los usuarios de C++ no aconsejarían poner clases de C++ en la interfaz de las DLLs debido a esta variación. La naturaleza de OO es tal que la estructura de implementación que es mejor para un compilador no es buena para otro, por lo que rara vez pueden interactuar entre sí.

¿Puedo importar/exportar mi clase a/desde .NET?
[LV2009] LabVIEW 2009 le permite crear ensambles .NET desde sus VIs. Para hacerlo, cree una nueva especificación de compilación en su proyecto para "Ensamble .NET". Como parte de sus VIs fuente, puede incluir clases de LabVIEW. Esas clases se pueden exportar en la interfaz de ensamble y exponerse como clases .NET. Si luego intenta usar ese ensamble .NET en LabVIEW, la clase aparecerá como un refnum .NET y será manejada por referencia como cualquier refnum.

Conclusión


¿LVOOP revolucionará todos los VI de usuario y algún día se llamará "la mejor característica de LabVIEW"?
A algunos de nosotros nos gusta soñar con una revolución de OO. Pero la verdad es que OO no es para todos. Es una herramienta, una más en un conjunto de herramientas en constante expansión que LabVIEW brinda a los usuarios. Para que un solo VI rápido realice una medida, las clases de LabVIEW son excesivas. Pero para aplicaciones completas, incluso pequeñas utilidades, la programación orientada a objetos puede ayudar a organizar el código, mejorar la capacidad de mantenimiento y, en general, hacer que la experiencia de LabVIEW sea más manejable. En la primera versión hubo algunos aspectos ásperos, y sabíamos que solo nuestros clientes avanzados se meterían de lleno. Sin embargo, con el tiempo, hemos visto que las características de OO se han convertido en un elemento básico del desarrollo de LabVIEW. Incluso hemos visto a algunos usuarios de LabVIEW que comienzan a usar OO para su primer proyecto, tal como ocurre en otros lenguajes de programación.

El equipo de LVOOP todavía tiene esperanzas en esa parte de la "mejor característica". ¡Háganos saber lo que piensa!