Sección de tutoriales y manuales vb

Interacción entre Visual Basic y C++ (Aumentando la eficiencia de nuestros programas)

Volver



 

Autor: Iván La Malfa.


Índice de contenido

Sumario

1 - Introducción

2 - Breve resumen de C++ (Diferencias con VB)

2.1 Conceptos generales de C++

2.2 Variables

2.2.1 Tipos de variables

2.2.2 Modificadores

2.2.3 Punteros

2.2.4 Matrices

2.2.5 Estructuras y Clases

2.2.6 Variables públicas

3 Primera interacción entre VB y C++

3.1 Nuestro primer programa

3.1.1 La librería Dll

3.1.2 La interacción con VB

4. Pasajes más complejos entre VB y C++

4.1 Pasaje de matrices

4.3 Pasaje de variables String

5 Depuración de las librerías Dll

5.1 Depurando con mensajes

ANEXO: Respuesta a los ejercicios

Respuesta ejercicio 1

Respuesta ejercicio 2

Respuesta ejercicio 3


Esta documentación fue escrita con el propósito que le sea de utilidad, pero no tiene ningún tipo de garantía. La misma está sujeta a los términos de la Licencia de Documentación Libre GNU (www.gnu.org).

Si el lector encuentra algún error (conceptual, gramatical, ortográfico, en el código, etc...), desea que explaye más un tema o quiere que incluya algún apartado más, le ruego me escriba a: lamalfa@gmail.com (también serán muy bienvenidas todas las críticas que el lector tenga del tutorial sean estas buenas... o de las otras ;-) ).


Sumario

Este tutorial se verá cómo se puede aumentar el rendimiento de los programas hechos en Visual Basic (en adelante VB) mediante el uso de librerías dinámicas hechas en C++.

1. Introducción

En esta memoria no se detallará el uso del lenguaje C++ (ni tampoco de VB). Para eso en la web
existen excelentes manuales. Tan sólo se explicará cómo sacarles el mejor provecho a ambos.

El lector probablemente se pregunte por qué el utilizar C++ aumentará el rendimiento de los programas hechos en Visual Basic. La respuesta es simple: Mientras que VB es un lenguaje interpretado, C++ es un lenguaje compilado.

Esto significa que el ordenador, cuando ejecuta un programa hecho en VB debe traducir al código del ordenador la instrucción actual la cual, luego de ser finalizada, el ordenado pasa a traducir la siguiente y se olvida de lo que hizo en el paso anterior.

Si tenemos una estructura que se repite constantemente (ya sea una función, o un bucle) esto constituirá una gran pérdida de rendimiento, ya que cada vez que se ejecute deberá traducir el código.

VB cuenta con un compilador. Sin embargo este proceso no logra escribir directamente el código de la máquina, sino que es un intermedio entre el código fuente y el código de máquina que, si bien aumenta grandemente el rendimiento, no deja de ser un lenguaje interpretado. Un programa compilado con VB siempre dependerá de librerías y nunca podrá ser un stand-alone-proyect (programa que se pueda ejecutar en cualquier PC)

El lenguaje C/C++, por el contrario es un lenguaje compilado. Esto significa que para que pueda ser ejecutado, antes deberá haber sido compilado. Vale decir que todo el código fue traducido al lenguaje del ordenador, consiguiendo importantes optimizaciones en velocidad, en espacio e incluso pudiendo aprovechar las ventajas de cada arquitectura de PC en particular.

Otro de los factores que hacen que los programas hechos con C/C++ sea más veloces que los hechos en VB es que C/C++ es un lenguaje de mucho más bajo nivel que VB.

Si el lector se pregunta qué significa ser de bajo o alto nivel le propongo este simple ejercicio. Es importante que haga lo que le pida inmediatamente sin pensarlo.

Tome una hoja y dibuje una línea horizontal en la mitad de la hoja. Esa línearepresenta el ordenador.

Ahora Ud. el usuario se representará como un punto. Rápido y sin pensar dibuje el punto donde crea conveniente (sobre o bajo la línea).

Lo dibujó por arriba de la línea ¿Verdad? Esto significa que Ud. domina al ordenador. (Si lo dibujó bajo la línea significa que Windows está dominando al ordenador y a Ud. ;-) ).

Los lenguajes pueden estar más cerca de la línea horizontal (ordenador) o más cerca del usuario. Los lenguajes que están cerca del lenguaje del ordenador se los conoce como de bajo nivel. Son extremadamente potentes, pero difíciles de manejar (Assembler o un poco más arriba C/C++).

Los lenguajes que están cerca del lenguaje del usuario (habla inglesa) se los conoce como de alto nivel. Es muy fácil hacer cosas con ellos, pero están limitados (SQL o un poco más abajo VB).

NOTA: Este tutorial fue hecho en mis ratos libres en mi casa, donde tengo Debian GNU/Linux como sistema operativo. Esto puede significar que los resultados que se muestran en este tutorial pueden ser diferentes a los obtenidos por el lector.


 

2 Breve resumen de C++ (Diferencias con VB)

Cómo se dijo en la introducción, este no es un tutorial de C++. En este apartado me limitaré a refrescarle la memoria a nuestro lector de las cosas que ya sabía de C++.

Si nuestro lector recuerda bien el manejo de C++, entonces le recomiendo que se saltee este apartado.

Si, por el contrario, nuestro lector no conoce C++, entonces le recomiendo que antes de continuar lea algún tutorial de los que podrá encontrar en la web.

2.1 Conceptos generales de C++

Lo primero que se deberá recordar es que C/C++ son lenguajes case-sensitive, esto significa que, a diferencia de VB, las mayúsculas son diferentes a las minúsculas. Por ejemplo las variables Pepe, pepe, PEPE, pEpe o cualquier otra combinación, son variables diferentes. Todas las funciones en C/C++ están escritas en minúsculas. Esto significa que si queremos declarar una variable de este tipo, el compilador nos dará un error:

Char Caracter;

Esto es porque Char no es lo mismo que char.

Otra característica de C/C++ es que son de formato libre: El compilador generalmente ignora los espacios, tabuladores y enters. ¿Entonces cómo sabe el compilador dónde termina una instrucción? (VB utiliza el enter). Para definir el fin de línea se utiliza el carácter punto y coma (;).

Los comentarios en C tradicionalmente estaban encerrados entre /* y */. Este sistema de comentarios continuó siendo soportado por C++. Sin embargo C++ adoptó un nuevo sistema (luego adoptado también por C en versiones recientes) que consiste en tomar como comentario todo lo que esté a la derecha del símbolo //.

Este tipo de comentario (conceptualmente idéntico a la comilla simple (') en VB) es preferible, ya que los comentarios /* */ no se pueden anidar. Ej.

/* Esto será
un comentario
de múltiples líneas*/
// Esto es un comentario de una sola línea
/* Esto
dará /* un */
error */

Siendo que los comentarios /* */ en C/C++ no pueden ser anidados, en el último ejemplo anterior solo se tomará como comentario lo que está pintado en verde:

/* Esto dará /* un */ <- Aquí finaliza el comentario
error */ <- Esta línea dará error

Vale decir que el comentario se cierra con el primer */ que encuentre.

Una gran diferencia que tiene C/C++ con VB es la declaración de variables. Mientras que si en VB ponemos:

Dim A as Integer, B, C

Definirá las variables A como Integer, pero a B y C como Variant. Todas las variables estarán iniciadas con valor 0 (o “” si es String o Variant) Mientras que en C/C++ si se pone:

int A, B, C;

Las variables A, B, C serán todas tipo int y ninguna de ellas está inicializada. Esto significa que tendrán el valor que tenía la memoria en el momento de crearlas. Este valor se lo conoce como garbage o basura.

Para inicializar una variable en C++ podemos hacer:


int A = 0, B = 0, C= 1; //Declaro e inicializo las variables.
int D, E, F; //Sólamente declaro variables
D = E = F = 0; //Asignación múltiple de valores. La asignación es de derecha a izquierda.

La asignación múltiple no existe en VB. Por el contrario, cuando VB se encuentra con ellas trata de evaluarla lógicamente. Ej:

D = E = 5

Esa línea en C/C++ hará que tanto D como E valgan 5, mientras que VB sólo asignará valores a D y este será True, si E vale 5 o False si E es distinto de 5.

NOTA: Aunque en el lenguaje C tradicional es necesario declarar las variables al inicio del procedimiento, en C++ se las puede declara en cualquier lado. (Esta práctica es aceptada también por compiladores de C recientes.)

Finalmente recordamos que los programas en C/C++ comienzan la ejecución en la rutina main. Generalmente veremos la rutina main definida de esta manera:

int main(int argc, char *argv[])

o la análoga:

int main(int argc, char **argv)

aunque también podremos encontrarla de otras formas. Una última diferencia notaremos entre C/C++ y VB. Mientras que en VB definimos y declaramos las funciones en el mismo lugar; en C/C++ pueden hacerse en diferentes lugares, pero todas las funciones deberán estar declaradas antes de comenzar la ejecución del programa (int main (void))

NOTA: En VB existen las funciones y los procedimiento. La diferencia entre ellos es que el procedimiento no retorna ningún valor. En C/C++ no existen los procedimiento, sino que son todas funciones. Si se quiere que la función no retorne ningún valor, entonces se declarará la función que retorne un valor void (vacío) Ej.

#include <iostream>
using namespace std;
void salude(void) //Defino y declaro la función en el mismo lugar
{
cout << "¡Hola mundo!" << endl;
}
int main(int argc, char *argv[])
{
salude();
return EXIT_SUCCESS;
}

En el ejemplo anterior, siendo que la función salude está antes que main, la función se declara y define en el mismo lugar. Si, por el contrario, ponemos la función salude luego que main, el compilador nos dará un error diciendo que la función salude está usada antes de ser declarada. Esto se soluciona muy fácilmente definiendo la función antes que la función main.

#include <iostream>
using namespace std;
void salude(void); //Aquí solo declaro la función salude.
int main(int argc, char *argv[])
{
salude();
return 0;
}
void salude(void) //Aquí defino la función que ya había declarado
{
cout << "Hello, world!" << endl;
}

NOTA: Si este programa finaliza su ejecución antes de que el lector haya podido observar el resultado, pruebe poner antes de la línea return, la siguiente instrucción:

system(“pause”);

Declarar una función es simplemente decirle al compilador que va a existir en algún lado una función que va a tomar los parámetros definidos y retornará el tipo de variable especificado. Definir una función es escribir el procedimiento que hará esa función.

NOTA: VB utiliza el mismo concepto cuando se utilizan librerías dinámicas. Ej.

Public Declare Sub salude Lib “c:\Libreria.dll” ()

Estamos indicando a VB (declaramos) que utilizaremos una función definida en otra parte.

 

2.2 Variables

2.2.1 Tipos de variables

El lector recordará que C++ cuenta con los siguientes tipos básicos de datos:

El lector deberá recordar que el tamaño de las variables no es fija; sino que depende del compilador y de la arquitectura del ordenador. Para saber cuantos bytes ocupan los tipos de variables en nuestro C++, debemos utilizar la función sizeof().

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
cout << "Tamaño de char: " << sizeof(char) << " bytes" << endl;
cout << "Tamaño de int: " << sizeof(int) << " bytes" << endl;
cout << "Tamaño de float: " << sizeof(float) << " bytes" <<
endl;
cout << "Tamaño de double: " << sizeof(double) << " bytes" <<
endl;
}

Este programa, utilizando el compilador g++ para GNU/Linux de x86 da el siguiente resultado:

Tamaño de char: 1 bytes
Tamaño de int: 4 bytes
Tamaño de float: 4 bytes
Tamaño de double: 8 bytes

2.2.2 Modificadores

En el punto anterior recordamos los tipos de variables básicos. Sin embargo C++ cuenta con modificadores. A saber:

Signed y unsigned no modifican la cantidad de bytes que ocupa una variable, pero si el tamaño máximo y mínimo que puedan almacenar.

En contraste tenemos que short y long si pueden (dependiendo del compilador) modificar el tamaño en bytes ocupado por la variable.

NOTA: No todos los modificadores pueden ser aplicados a todos los tipos de variables.

 

2.2.3 Punteros

Una diferencia notable con VB es que C/C++ utiliza punteros. Este concepto realmente útil generalmente lleva algún tiempo para que un usuario de VB lo logre incorporar del todo.

La idea es la siguiente: Existen 2 formas de poder acceder a una variable: Por su nombre o por su dirección. El puntero es justamente eso: Acceder a la variable a través de su dirección. La idea del puntero probablemente haya nacido de la imposibilidad de algunos lenguajes de pasar valores de una función por referencia.

Siendo que en C solamente se puede pasar parámetros por valor, es de importancia crucial el uso de punteros ya que permite modificar el valor de una variable fuera de esa función. C++ suple esa carencia e introduce el pasaje de valores por referencia, pero aun sigue siendo más claro la utilización de punteros.

Un dato de mucha importancia es que en C/C++, al contrario de lo que podría pensarse, el siguiente código:

int* pVal1, pVal2, pVal3;

No declara 3 punteros int, sino sólo 1: pVal1, los otros 2 son sólo variables int. Esto hace que sea altamente desaconsejable declarara así las variables. Podría hacerse de la siguiente manera:

int *pVal1, *pVal2, *pVal3;

Esto sí crea 3 variables puntero int. Veamos los siguientes ejemplos.

#include <iostream>
using namespace std;
void SumarUno(int); //Notar que no es necesario especificar el nombre de la variable, sólo el tipo
int main(int argc, char *argv[])
{
int valor = 5;
cout << "La variable, antes de la función, tiene el valor: "
<< valor << endl;
SumarUno(valor);
cout << "La variable, luego de la función tiene el valor: " <<
valor << endl;
}
void SumarUno(int numero)
{
cout << "La variable llega a la función con el valor: " <<
numero << endl;
numero++;
cout << "La variable sale de la función con el valor: " <<
numero << endl;
}

El resultado es este:

La variable, antes de la función, tiene el valor: 5
La variable llega a la función con el valor: 5
La variable sale de la función con el valor: 6
La variable, luego de la función tiene el valor: 5

Significa que la modificación solamente tuvo lugar dentro de la función SumeUno. Tradicionalmente (y es la opción que utilizaremos a lo largo de este tutorial), si se quería hacer que se modifique permanentemente el valor de la variable se debía recurrir al uso de punteros. Ej:

void SumarUno(int*); //Notar que no es necesario especificar el nombre de la variable, sólo el tipo
int main(int argc, char *argv[])
{
int valor = 5;
cout << "La variable, antes de la función, tiene el valor: "
<< valor << endl;
SumarUno(&valor); //Notar que se pasa la dirección y no el
valor

cout << "La variable, luego de la función tiene el valor: " <<
valor << endl;
}
void SumarUno(int* numero) //Se espera un puntero y no un valor
{
cout << "La variable llega a la función con el valor: " <<
*numero << endl;
*numero = *numero +1;
cout << "La variable sale de la función con el valor: " <<
*numero << endl;
//Notar que en esta función sólo se trabaja con el valor al
que apunta el puntero
}

Otra forma de hacer lo mismo es pasando valores por referencia. Esta opción sólo es válida con C++

void SumarUno(int &numero); //Especificamos que se pasará por referencia
int main(int argc, char *argv[])
{
int valor = 5;
cout << "La variable, antes de la función, tiene el valor: "
<< valor << endl;
SumarUno(valor); //Ahora pasamos valor y no su ubicación
cout << "La variable, luego de la función tiene el valor: " <<
valor << endl;
}
void SumarUno(int &numero)
{
cout << "La variable llega a la función con el valor: " <<
numero << endl;
numero++;
cout << "La variable sale de la función con el valor: " <<
numero << endl;
}

Notar que hacer esto es idéntico a lo que se hace en VB especificando ByRef. Lamentablemente el autor de este tutorial prefiere la opción anterior y de este tipo de pasaje no se hablará más, quedando a juicio del lector el utilizarlo.

 

2.2.4 Matrices

Una matriz es un arreglo de elementos. Es una sucesión de valores.

Internamente en C/C++ no existe un concepto de matriz como el que tiene VB. C/C++ en realidad utiliza los punteros para emular un arreglo.

Cuando creamos una matriz en C/C++ estamos simplemente reservando espacios en memoria, pero de ningún modo establecemos límites.

En VB podemos hacer:

Dim Matriz(2 to 10) As Integer

Esto nos reservará 9 espacios en la memoria y VB nos impedirá llamar a elementos menores a 2 y mayores que 10

Si en C/C++ hacemos

int Matriz[10];

El programa reservará 10 espacios en la memoria (del 0 al 9), pero nada nos impedirá de llamar a un elemento fuera de la matriz, lo que podría provocar errores en el programa difíciles de depurar.

Notar que en VB las matrices se especifican por límites, mientras que en C/C++ se hacer por número de elementos.

¿Qué es lo que hace el programa cuando se encuentra con la orden anterior?
Podríamos resumirlo como:

¿Qué hace el programa cuando asigna un valor al elemento 5 por ejemplo?

Matriz[4] = 12; //Recordar que el 0 es el 1º elemento

Lo que hace es averiguar la posición del 1º elemento de la matriz. A esa posición le suma 5 (posiciones) * 4 (bytes). En esa posición almacena el valor 12 (o el que hayamos especificado)

Si hacemos

Matriz[10] = 1;

El programa no nos dará ninguna advertencia ni error y ejecutará la orden tal como lo pedimos, pero estamos escribiendo en un espacio que no fue inicialmente reservado para el arreglo Matriz.

Dicho en otras palabras: Es posible que sin querer estemos cambiando el valor de otras variables.

Todo esto es simplemente porque Matriz es simplemente un puntero (y no un arreglo).

Tanto es así que si queremos averiguar la dirección de memoria de Matriz podemos hacer:

cout << "La dirección de Matriz[0] es: " << &Matriz[0] << endl;

o

cout << "La dirección de Matriz[0] es: " << Matriz << endl;

Notar que la segunda forma de averiguar la dirección sólo es posible si Matriz es un puntero.

El siguiente ejemplo muestra cómo actúa internamente un arreglo:

#include 
using namespace std;
int main(int argc, char *argv[])
{
    int Matriz[10]; //Reservamos espacio en memoria
    int *pMatriz = Matriz; //Creamos un puntero apuntando a Matriz
    //El siguiente bucle será para cargar datos a Matriz
    for (int i = 0; i <= 9; i++)
    {
	*pMatriz = i; //Equivalente a Matriz[i] = i
	pMatriz++; //Ver nota
    }
    //Mostrar los valores
    for (int i=0; i <= 9 ; i++)
	cout << "Matriz[" << i << "] = " << Matriz[i] << endl;
}

NOTA: pMatriz++ hace que el puntero pase a la siguiente posición. Si pMatriz es un puntero de int, significa que, para mi compilador, ocupará 4 bytes. (Ej. Si pMatriz apunta a 0x10000; luego del operador ++, apuntará a 0x10004)

2.2.5 Estructuras y Clases

En VB tenemos la posibilidad de utilizar tipos definidos por el usuario mediante Type/ End Type. También podemos utilizar Clases a partir de agregar a nuestro proyecto un módulo de clase.

Estas tipos se pueden materializar en C mediante estructuras struct. En C++, además de struct, existe class. No existe otra diferencia entre struct y class que, mientras que en struct es predeterminado que las definiciones sean públicas, en class son privadas. En este tutorial sólo se utilizarán las clases.

Como ejemplo pondré una clase no muy útil, pero si explicativa que sirve para obtener la media geométrica (hipotenusa) entre dos valores:

#include 
#include 

using namespace std;

class clsCoordenadas
{
private: //Es predeterminado pero igual lo pongo
     float X;
     float Y;
public:
     clsCoordenadas(void); //constructor
     clsCoordenadas(float valX, float valY); //constructor sobrecargado
    ~clsCoordenadas(void); //destructor

    float Hipotenusa(void);
    float Hipotenusa(float valX, float valY); //Funcion sobrecargada

    void AsignarX(float X);
    void AsignarY(float Y);
    void AsignarXY(float X, float Y);
};

int main(int argc, char *argv[])
{
	
    clsCoordenadas c1, c2, c3(1,2);
    c1.AsignarXY(3,4);
    c2.AsignarX(5);
    c2.AsignarY(6);
    
    cout << "Hipotenusa de c1: " << c1.Hipotenusa() << endl;
    cout << "Hipotenusa de c2: " << c2.Hipotenusa() << endl;
    cout << "Hipotenusa de c3: " << c3.Hipotenusa() << endl;
    cout << "\nNueva Hipotenusa de c1: " << c1.Hipotenusa(7,8) <<
endl;
}

clsCoordenadas::clsCoordenadas(void) //Constructor
{
}

clsCoordenadas::clsCoordenadas(float valX, float valY)
//Constructor sobrecargado
{
    AsignarXY(valX, valY); //Llamada a una función interna
}
clsCoordenadas::~clsCoordenadas(void) //Destructor
{
}

float clsCoordenadas::Hipotenusa(void)
{
    float f;
    f = sqrt( pow(X,2) + pow(Y,2) );
    return f;
}

float clsCoordenadas::Hipotenusa(float valX, float valY)
{
    AsignarXY(valX, valY);
    return Hipotenusa();
}

void clsCoordenadas::AsignarX(float valX)
{
    X = valX;
}

void clsCoordenadas::AsignarY(float valY)
{
    Y = valY;
}
void clsCoordenadas::AsignarXY(float valX, float valY)
{
    X = valX;
    Y = valY;
} 

Algunas cosa dignas de notar:

1º Todas los clases debe tener, al menos un constructor y un destructor. En el ejemplo mostrado hemos puesto 2 constructores.

Los constructores y destructores son una funciones muy características que no retornan ningún valor (ni siquiera un valor void como se puede observar)

El constructor sirve para asignar valores y hacer los procedimientos necesarios al iniciar la clase. El destructor, por el contrario, se encarga de hacer los procedimientos necesarios al terminar la clase. Uno de los procedimientos más importantes consiste en quitar de memoria ciertos tipos de variables que, de otro modo, quedarían ocupando espacio hasta que se reinicie el ordenador.

En el ejemplo vemos cómo en la siguiente instrucción:

clsCoordenadas c1, c2, c3(1,2);

el programa, dependiendo de los parámetros utiliza el constructor correspondiente.

2º En C++ podemos tener funciones con el mismo nombre. Siempre que, al menos uno del tipo de variables de los parámetros sea diferente, C++ los tomará como funciones diferentes. A esto se lo conoce como funciones sobrecargadas (u overloaded). En nuestra clase tenemos que el constructor y la función Hipotenusa están sobrecargadas.

3º Vemos como si bien hemos definido las funciones de la clase bajo la función main, la declaración de las mismas se hizo antes. Esto pide a gritos que sea puesto en un archivo header (extensión .h) y que se lo incluya, pero eso no es tema de este tutorial. El lector ya debería saber hacer eso.

 

2.2.6 Variables públicas

Si en VB poníamos un módulo y en el escribíamos:

Public A as Integer

Automáticamente esa variable A pasaría a estar accesible desde cualquier parte del programa. Este comportamiento no existe en C/C++.

Todas las variables declaradas antes que la definición de cualquier función en C/C++ significa que dicha variable será accesible desde cualquier parte de ese archivo. Esto sería comparable a cambiar el código anterior de VB por

Dim A as Integer

Donde la variable A sólo está disponible dentro del módulo.

¿Cómo hacer entonces para que en C/C++ podamos tener variables accesibles desde cualquier parte del proyecto y no sólo del archivo actual? (Utilizar con cuidado ya que no es una buena práctica utilizar variables globales)

Se debe declarar en todos los archivos donde se hará referencia a la variable como pública, pero todas las definiciones menos una deberá tener la palabra extern. Ej.

[Archivo: main.cpp]
int VariableGlobal;
[Archivo: modulo.cpp]
extern int VariableGlobal;
[Archivo: otro.cpp]
extern int VariableGlobal;

Esto indica al compilador que en el archivo modulo.cpp haga de cuenta que crea una variable, pero que en realidad estará utilizando otra variable que debió haber sido definida en alguna otra parte del proyecto.


 

3 Primera interacción entre VB y C++

Si bien C/C++ no necesita nada más que un editor y un compilador, suele ser muy útil utilizar una IDE, ya que ayudan mucho al desarrollo de nuestras aplicaciones.

La IDE que se utilizará en este proyecto es (por razones de tradición) Dev C++ de Bloodshed, que es libre y puede ser descargada de (www.bloodshed.net). Lamentablemente el proyecto Dev C++ está muerto. Desde febrero del 2005 que no hay una nueva versión. Probablemente el próximo tutorial que haga se base en la IDE, también libre, Code::Blocks (www.codeblocks.org).

Code::Blocks es un proyecto muy activo (todos los días hay nuevas versiones), pero todavía el Dev C++ sigue siendo más robusto.

3.1 Nuestro primer programa

Ahora que tenemos los conceptos generales de ambos lenguajes procuraremos crear un ejemplo de un proyecto que utilice ambos lenguajes.

Nuestro primer programa consistirá, simplemente en sumar una unidad a un número o valor dado. Esta función la haremos tanto con pasaje de variables por valor como por referencia (punteros)

3.1.1 La librería Dll

Iniciemos Dev C++. Luego ejecutemos Archivo -> Nuevo -> Proyecto.

Dev C++ nos mostrará una nueva ventana donde se verán varios “templates”. Elijamos el que dice Dll, pongámosle un nombre coherente al proyecto y especifiquemos (si es que no lo está) que el proyecto será en C++. Luego demos Aceptar.

Luego de especificarle dónde guardará el archivo de proyecto (es aconsejable un proyecto por directorio) tendremos cargado en memoria 2 archivos: dllmain.cpp y dll.h

Ahora definiremos las funciones que utilizaremos desde VB. (Copiar las siguiente funciones al final del archivo dllmain.dll)

void SumeUnoPorReferencia (long *A)
{
     *A = *A + 1;
}
long SumeUnoPorValor (long A)
{
     return ++A;
} 

 

El lector no debiera tener problemas en saber exactamente lo que hace el código anterior por lo que no lo explicaré.

Notemos que cuando pasamos el valor por referencia, entonces no necesitamos retornar ningún valor (ya que el valor mismo está en la variable), por ello hemos definido un procedimiento (función que no retorna valores). Por el contrario, en el pasaje por valor debemos devolver un valor, ya que el cambio que hicimos en la variable se pierde al finalizar la función.

Este comportamiento no es siempre así, pero simplemente quise aprovechar la oportunidad para definir una función y un procedimiento para explicar las diferencias cuando llegue a la parte de VB.

Bien, ya hemos definido las funciones, ahora queda declararlas. El problema es que estas funciones podrán ser utilizadas desde afuera de la librería (y de hecho ese es el fin de las mismas), por lo que la convención para declararlas difiere de lo que el lector estaba acostumbrado. (Por supuesto que las funciones internas que sólo se utilizarán dentro de la librería siguen la convención que conocía el lector)

El siguiente es la declaración de las funciones (para que sean públicas)

extern "C" DLLIMPORT void __stdcall SumeUnoPorReferencia (long
*A);
extern "C" DLLIMPORT long __stdcall SumeUnoPorValor (long A);

 

NOTA: En la declaración hemos utilizado DLLIMPORT que el template de Dev C++ ya tenía definido.

En realidad poco importa que en la declaración se especifique el nombre de la variable (lo mismo que en las declaraciones tradicionales). Lo único que interesa es el tipo de variable.

Notemos que la diferencia entre las declaraciones tradicionales y este nuevo tipo es que delante de la declaración debemos poner extern "C" DLLIMPORT, y entre el tipo devuelto y el nombre de la función __stdcall.

El extern “C” se debe a que las funciones son exportadas como funciones de C. Esto significa que si bien C++ diferencia funciones que difieran en la cantidad de parámetros (funciones sobrecargadas u overloaded), C no lo hace y, por tanto, no podremos hacerlo (por suerte, ya que sería un verdadero problema el utilizar funciones sobrecargadas en VB) El __stdcall se debe a que para utilizar en VB una función, esta no debe ser __cstd como es predeterminado, sino __stdcall

Ahora sólo resta compilar la librería. Para esto debemos ejecutar el menú Ejecutar -> Compilar (o presionar su acceso rápido: Ctrl + F9) De modo predeterminado, Dev C++ crea la librería, archivos relacionados y el código objeto en el mismo directorio donde se encuentre el proyecto (archivo .dev). Para cambiar esto se debe ejecutar Proyecto -> Opciones de Proyecto (o Alt + P) y en la solapa opciones se deben especificar las rutas deseadas. Esto es importante porque en VB deberemos especificar la ubicación exacta de la librería.

3.1.2 La interacción con VB

En VB no se puede definir una función en un lugar y declararla en otro, ya que la declaración y definición van en el mismo lugar. Ej.

Private Sub Funcion1() 'Declaro y defino en el mismo lugar.

End Sub

Pero… qué pasa cuando la función si se definió en otro lado (en un archivo ajeno a VB como la librería). En ese caso VB procede de la misma forma que C/C++ y se debe declarar la función antes de utilizarla.

Para hacer esto, VB cuenta con la instrucción Declare. Al igual que cualquier función, las declaradas con Declare pueden ser públicas (public) o privadas (private).

Si las declaramos en un formulario (ventana), entonces no puede ser otra cosa que privado. (sólo pueden ser llamadas desde el mismo formulario. Si las declaramos en un módulo, pueden ser publicas (globales: pueden ser llamadas desde cualquier parte del proyecto) o privadas (sólo se las puede llamar dentro del mismo módulo).

Es digno de recordar que las funciones que en C++ no retornaban valores (void), en VB serán Sub; mientras que las que sí lo hacían, en VB serán Function. La sintaxis de Declare, dependiendo si retorna o no valores es la siguiente (Tomado de la ayuda de e VB sobre el comando Declare, por lo que es altamente aconsejable su lectura)

Sintaxis 1

[Public | Private] Declare Sub nombre Lib "nombre_biblioteca" [Alias "nombre_alias"] [([lista_argumentos])]

Sintaxis 2

[Public | Private] Declare Function nombre Lib "nombre_biblioteca" [Alias "nombre_alias"] [([lista_argumentos])] [As tipo]

El lector debiera estar familiarizado con todas las palabras claves que figuran en el código anterior, tal vez con excepción de Lib y de Alias

En Lib, especificamos la ruta completa a la librería (puede ser una ruta relativa) Definir Alias, no es tan simple, ya que existe un estrecho paralelismo con nombre. Si se omite Alias (que es opcional), entonces VB toma que Alias es nombre.

Una definición rápida sería: nombre es cómo se llamará la función desde VB. Alias es cómo se llama la función en C++. Si se omite Alias, en nombre, debería poner el nombre exacto de la función (o procedimiento de la librería). Recordar que, aunque VB no lo es, C++ es case sensitive. Por lo tanto se debe tener cuidado con el uso de mayúsculas.

Ahora surge un problema. ¿Qué pasa si el nombre de la función en C++ es una palabra clave en VB o cuando en C++ hemos definido dos funciones diferentes por ser case-sensitive, pero en VB no lo son? Para eso viene en nuestra ayuda el parámetro Alias.

Supongamos que en C++ hayamos definido dos funciones para ser utilizadas desde VB: Dim y dim. En ese caso tenemos 2 problemas. El primero es que las dos funciones para VB son las mismas (VB no es case-sensitive); El segundo es que ambas se llaman como una palabra clave en VB. Cómo solucionar eso entonces.

Declare Sub cDim Lib “../Librerias/Prueba.dll” Alias “Dim” ()
Declare Sub cdim1 Lib “../Librerias/Prueba.dll” Alias “dim” ()

NOTA: Fijarse que hemos definido rutas relativas.

IMPORTANTE: Hay que tener cuidado con la definición de rutas relativas, ya que estas no están referidas a donde está el proyecto, sino desde donde se está ejecutando. Vale decir que si bien en el compilado ambas direcciones coinciden, si se corre el programa desde VB, todo va a estar referido a la ubicación de Visual Basic.

Con la ayuda de Alias y nombre hemos podido salir del paso. Por supuesto, no es una práctica remendada definir funciones en C++ que vayamos a tener problemas en VB, pero logramos utilizar ambas funciones en VB (eso si, se deberán llamar a las como cDim o cdim1 en vez de Dim o dim).

El nuevo desafío ahora consiste en poder reproducir los parámetros de las funciones en C++ en VB. El primer problema es el del tamaño de bytes. Como se mencionó anteriormente (punto 2.2.1), la cantidad de bytes de C++ no es fija. Por lo tanto es recomendable que el lector haga una tabla con las equivalencias de bytes entre las variables (enteras y con punto flotante) entre C++ y VB. El modo de hacerlo está en el punto 2.2.1. Para obtener cuánto ocupan en VB utilice Len o directamente lea la ayuda.

El segundo problema es establecer si el pasaje de variables era por valor o por referencia. Aunque el pasaje por referencia es predeterminado y, por lo tanto podría omitirse el ByRef, yo no lo aconsejo y siempre escribo ByVal o ByRef según corresponda.

Regla de oro: Si en C++ utilizamos un valor, entonces debemos anteponer ByVal. Si utilizamos un puntero, entonces podemos (altamente aconsejable) anteponer ByRef. El no cumplir esa regla ocasionará problemas difíciles de depurar.

NOTA: Las declaraciones, al igual que en C++, se deben hacer antes de cualquier función. Esto significa que debe ir en el encabezado (ya sea del formulario o del módulo)


Ejercicio 1:

Declarar y utilizar con VB las funciones que hemos hecho en C++

(Respuestas al final tutorial)


 

4. Pasajes más complejos entre VB y C++

Con lo que hemos visto hasta ahora el lector no tendrá ninguna dificultad en hacer funciones en C++ que utilicen simples números (o punteros) para utilizarlas en VB, pero ¿Qué pasará cuando el lector se entusiasme e intente pasar matrices, tipos definidos por el usuario (UDT), o una cadena de texto?

Se requiere que el lector haya asimilado bien el punto 2 de este tutorial, ya que en esa sección se hará constante mención a él.

4.1 Pasaje de matrices

Tal como fue explicado en el punto 2, existen grandes diferencias entre una matriz de VB y una de C++. Si el lector recuerda, se hizo expresa mención que en C++ las matrices no son otra cosa que punteros, por lo tanto esa será la solución al problema. Ej,

En VB

Dim Mat() As Integer
ReDim Mat (4)

Funcion Mat (0), UBound(Mat)

En C++

void Funcion(short* Mat, short Rango)

NOTA: Al especificar Mat(0) como primer parámetro en la función de VB estamos haciendo que el puntero de C++ apunte al primer elemento de la matriz.

El modo de acceder a los elementos de la matriz Mat fue descriro en el apartado 2.2.4 Existen otros modos de acceder a una matriz hecha en VB desde C++. Una es directamente creando la clase SAFEARRAY que es la que utiliza VB, pero realmente por sencillez y velocidad, la que más conviene es la que he comentado.


Ejercicio 2:

Crear una matriz dinámica en VB y asignarle valores. Crear una función en C++ devuelva la sumatoria de todos los elementos.

(Respuestas al final tutorial)


4.2 Pasaje de UDT

La solución en este caso viene de la mano de las intrucciones Type/End Type en VB y class o struct en C++

Sabiendo que lo que se envía desde VB debe ser interpretado de exactamente la misma forma en C++ podemos decir que no existe ninguna traba en utilizar UDT en funciones de C++ con datos de VB siempre y cuando en ambos lados exista la misma definición del tipo.

Aunque el lector, llegado a este punto, se podría bien imaginar la solución del problema, me temo que hay algunos puntos que deberé explicar:

En primer lugar, el pasaje de valores será siempre por referencia en VB y punteros en C++ En segundo lugar, el lector deberá recordar la diferencia que existe entre trabajar un una clase (o estructura) y un puntero a una clase (o estructura)

Si en C++ defino esta clase:

class clsPrueba
{
    int A;
    int B;
}; 

Cuando trabajo con valores se debe operar de la siguiente manera:

clsPrueba Variable;
Variable.A = 1;
Variable.B = 2;

Sin embargo, cuando se trabaja con punteros la cosa cambia.

clsPrueba *pVariable = Variable;
Variable->A = 3;
Variable->B = 4;

Notar que cuando se trabaja con valores, se separan las variables por el signo “.”. Cuando es un puntero, no se especifica el signo “*” al principio para designar valor y no dirección y además se cambia el signo “.” por “->”

C/C++ hace optimizaciones en la velocidad que, a veces, entorpecen la comunicación con VB.

ANSI C no especifica cuanto debe ocupar un determinado tipo de variable, sino entre qué rangos se debe encontrar. Esto es porque implícitamente está permitiendo al compilador utilizar variables que ocupen más bytes de lo que realmente necesitan para crear un ejecutable más veloz.

VB desconoce de estas optimizaciones y será imposible trabajar conjuntamente si no se toman medidas en C++.

El problema viene dado cuando en VB definimos un UDT con diversos tipos de variables.

En VB:

 Type typPrueba
     A As Double  '8 bytes
     B As Double  '8 bytes
     C As Integer '2 bytes
End Type

La variable typPrueba debiera ocupar 18 bytes (8 + 8 + 2) y realmente eso es lo que ocupa. Sin embargo la misma definición en C++ puede traernos alguna sorpresa.

En C++

class clsPrueba
{
    double A;  //8 bytes
    double B;  //8 bytes
    short C;   //2 bytes
}; 

Aunque se esperaría que la clase clsPrueba ocupe 18 bytes, puede ocupar más. C++ puede conseguir importantes mejoras en la velocidad de ejecución haciendo que la variable short C ocupe 4 bytes o incluso 8 (dependiendo del compilador) y, por ende, clsPrueba ocupará 20 o 22 bytes.

Sin embargo podemos especificarle a C/C++ que no realice esa optimización, ya que no nos importa el rendimiento y en vez nos interesa que ocupe la menor cantidad de memoria posible... o simplemente porque vamos a intereactuar con VB donde esas optimizaciones generarán errores. Para ello debemos utilizar el atributo packed. Con este atributo las clases ocuparán exactamente lo mismo que en VB.

class __attribute__ ((__packed__)) clsPrueba
{
     double A; //8 bytes
     double B; //8 bytes
     short C; //2 bytes
}; 

Ahora la clase clsPrueba ocupará realmente los 18 bytes esperados.

NOTA: delate y detrás de las palabras attibute y packed hay doble guion bajo (“_”)


Ejercicio 3:

Crear un UDT donde se carguen 2 valores (Val1 y Val2. No utilizar matrices) en VB. En C++ deberá hacer una función que retorne la media de ambos valores ((Valor1 + Valor2)/2)

(Respuestas al final tutorial)

Si el lector quiere poner matrices dentro de su variable UDT, entonces no le quedará más remedio que investigar e implementar en C++ el tipo SAFEARRAY con que cuenta VB.


4.3 Pasaje de variables String

Si bien C++ cuenta con un nuevo tipo de variables (no existe en C) llamado string, este no es otra cosa que una clase y no es la misma que la que cuenta VB como tipo básico de variables. El paralelo a String de VB en C++ es LPCSTR Ej.

En VB

Private Declare Sub Mensaje Lib "Temp.dll" (ByVal Texto As String)

Private Sub Command1_Click()
Mensaje "Hola"
End Sub

En C++

void Mensaje (LPCSTR Texto)
{
     MessageBox(0, Texto, "DLL Message", MB_OK |
MB_ICONINFORMATION);
} 

NOTA: La definición de la clase LPCSTR está dentro de la librería “windows.h”, por lo tanto, cuando se escriba en la cabecera de nuestro proyecto (dll.h) la declaración de la variable habrá que agregar (si es que no está) una inclusión al mencionado fichero.

5 Depuración de las librerías Dll

Si bien este no es tema estrictamente del presente tutorial, será una guía de las metodologías que podrá utilizar para cuando necesite depurar una librería.

El lector probablemente cuando haya trabajado con C++ haya tenido la necesidad de utilizar un depurador y si trabajó desde alguna IDE muy probablemente haya hecho una depuración paso a paso. El hecho que en las librerías no exista un punto de comienzo (función main) hace que sea imposible el modo de depuración descrito (a menos que cree una aplicación en C++ que la utilice, pero no creo que valga la pena)

Viéndonos desprovistos de tan útil herramienta, debemos buscar soluciones alternativas.

5.1 Depurando con mensajes

Depurar un programa no es otra cosa que ir indagando cuánto valen determinadas variables en determinado momento.

Una buena forma de hacerlo es a través de las cajas de mensajes (MessageBox) La función que nos brinda esa posibilidad se llama MessageBox y para poder utilizarla debemos incluir la librería “windows.h” (por lo obviamente no es una función del ANSI C)

La función MessageBox tiene la siguiente sintaxis.

int MessageBox(

    HWND hWnd, //Ventana (Si es NULL, entonces la ventana de MessageBox no tiene dueño)
    LPCTSTR lpText, // Texto a mostrar
    LPCTSTR lpCaption, // Título de la ventana
    UINT uType // Estilo.

); 

Utilizaremos esta función con la ayuda de la función sptrinf (definida con la librería C++ estándar cstdio (en C llamada stdio.h))

Para mostrar un ejemplo del uso de la misma, depuraré el ejemplo número 2.

short SumarElementos (short *Mat, short Rango)
{
    short sumatoria=0;
    char cad[100]; //Defino una cadena con cantidad suficiente de espacio
	
    sprintf(cad, "Inicio\nRango = %i", Rango); //Escribo los datos necesarios
    MessageBox(0, cad, "Depurando", MB_OK | MB_ICONINFORMATION);

    for (short i = 0; i <= Rango; i++)
    {
        sumatoria += *Mat;
        sprintf(cad, "Iteración: %i\nMatriz: %i\nSumatoria: %i", i, *Mat, sumatoria);
        MessageBox(0, cad, "Depurando", MB_OK | MB_ICONINFORMATION);
        Mat++;
    }

    return sumatoria;
} 

 

5.2 Utilizando archivos de texto

El concepto aquí es el mismo que en el punto anterior, pero utilizar archivos de texto suele ser conveniente para no llenarnos de mensajes (lo que dificultaría aun más el depurar el código).

En este caso utilizaremos el tipo de datos FILE*, la función fopen y fclose y la función fprintf. (librería C++ estándar cstdio (en C llamada stdio.h))


Ejercicio 4:

Depurar cualquier ejercicio anterior utilizando esta forma.


 

ANEXO: Respuesta a los ejercicios

IMPORTANTE: En las respuestas que incluyan código en C++, no se pondrá la declaración de las funciones, ya que el lector perfectamente podrá reconstruir la misma a partir de la definición de las funciones que se escriban. Ante cualquier duda, releer el punto 3.1.1 “La librería Dll”


 

Respuesta ejercicio 1:

Private Declare Sub SumeUnoPorReferencia Lib "Ejercicio.dll"
(ByRef A As Long)

Private Declare Function SumeunoPorValor Lib "Ejercicio.dll"
(ByVal A As Long) As Long

Private Sub Command1_Click()

Dim B As Long
B = 5
SumeUnoPorReferencia B
MsgBox B

End Sub

Private Sub Command2_Click()
Dim B As Long

B = SumeunoPorValor(5)
MsgBox B

End Sub

Respuesta ejercicio 2:

En VB

Private Declare Function SumarElementos Lib "..\Lib\Ultimo.dll"
(ByRef Mat As Integer, ByVal Rango As Integer) As Integer

Private Sub Command1_Click()

Dim Mat() As Integer, i%
ReDim Mat(4) For i = 0 To UBound(Mat) Mat(i) = i Next MsgBox SumarElementos(Mat(0), UBound(Mat))

End Sub 

En C++

short SumarElementos (short *Mat, short Rango)
{
short sumatoria=0;
for (short i = 0; i <= Rango; i++, Mat++)
sumatoria += *Mat;
return sumatoria;


Respuesta ejercicio 3:

En VB

Private Declare Function ObtenerMedia Lib
"C:\VisualBasic\PROYECTOS\Lib\Ultimo.dll" (ByRef Valores As
typEjemplo) As Single

Private Type typEjemplo Val1 As Integer Val2 As Integer End Type

Private Sub Command1_Click() Dim Valores As typEjemplo
Valores.Val1 = 2 Valores.Val2 = 9 MsgBox ObtenerMedia(Valores)
End Sub 

En C++

class clsEjercicio //poner en el archivo de cabecera (dll.h)
{
    short Val1;
    short Val2;
};
float ObtenerMedia (clsEjercicio *Valores)
{
    return (Valores->Val1 + Valores->Val2)/2.;
} 

 


Descargar tutorial en formato .Pdf y formato .Odt

 

 


Buscar en Recursos vb