Conexión entre Visual BASIC y Visual C++



Por: Luis M Perez Llera luisma@ic.vel.indra.es

Madrid, 23 de abril de 1997

Índice

1.	Introducción
1.1.	Hipótesis previas
1.2.	Entornos de trabajo
1.3.	Otros condicionantes
2.	Generación de la librería y acceso a la misma
2.1.	Exportación de funciones
2.2.	Importación de funciones
3.	Acceso a datos
3.1.	Paso de enteros
3.2.	Paso de cadenas de caracteres
3.3.	Paso de punteros a datos
3.4.	Paso de estructuras
3.4.1.	Paso de enteros cortos dentro de estructuras
3.4.2.	Paso de enteros largos dentro de estructuras
3.4.3.	Paso de cadenas de caracteres dentro de estructuras

Introducción

Como parte de los trabajos del proyecto CARES se ha realizado un análisis de las posibilidades de conexión y paso de datos entre una aplicación desarrollada en Visual BASIC y una DLL escrita en Visual C++. Este informe describe los resultados de dicho análisis y muestra ejemplos que ilustrarán todas las posibilidades descritas.

Este informe utiliza aproximaciones al problema distintas y más sencillas de las recomendadas por Microsoft (tanto en el fichero VB4DLL.TXT, que se incluye en las instalaciones de Visual BASIC, como en su Base de Conocimiento). No obstante, todas las opciones aquí documentadas han sido probadas con éxito.

  1. 1. Hipótesis previas

La comunicación entre ambos lenguajes de programación, en el sentido de que una aplicación Visual BASIC pueda usar funciones escritas en Visual C++, exige encapsular estas funciones en una Librería de Enlace Dinámico (DLL) similar a las ya existentes propias del entorno operativo Windows.

Los problemas que es necesario resolver son, entonces, los relativos a la manera de generar DLLs en C++ y de comunicar variables entre uno y otro entorno sin errores debidos a confusión de tipos o a un mal alineamiento de los mismos debido a su distinta longitud.

  1. 2. Entornos de trabajo

El análisis se ha efectuado para las versiones de ambos entornos de programación siguientes:

  • Visual C++ versión 4.2
  • Visual BASIC Profesional versión 4.0
  1. 3. Otros condicionantes

La solución a que se llegase debe ser compatible con el gestor de base de datos que se está utilizando (Object Store).

Esto trae como consecuencia que no se pueda utilizar el entorno que integra a Visual C++ (Microsoft Developers' Studio), sino que la compilación deba hacerse desde la línea de comandos. El Developer's Studio ha sido así utilizado solamente como editor y fuente de ayuda on-line.

 

  1. Generación de la librería y acceso a la misma

Se ha generado una librería DLL, de nombre Query, compuesta por un fichero fuente (Query.cpp) y uno de cabecera (Query.hh). Además de definiciones generales y enlaces (#include) a otros módulos, en este último fichero se contienen fundamentalmente los prototipos de las funciones exportadas.

Se ha generado asimismo un fichero make para la compilación de la DLL. El contenido del fichero es como sigue :

#
# Visual C++ version 4.2 Makefile for ObjectStore dll called from Visual Basic 4.0
#
# Fecha ultima modificacion: 03-03-1997 / 17-03-1997
#

API_PATH=C:\CARES\API_DB
APP_SCHEMA_OBJ= $(API_PATH)\Schema.obj
API_OBJECTS= $(API_PATH)\Modulo.obj $(API_PATH)\Programa.obj $(API_PATH)\ClasesGF.obj $(API_PATH)\ClasesGG.obj $(API_PATH)\GetAtrib.obj

all:  Query.dll

Query.obj: Query.cpp 
  cl -c -W3 -G4 -D_X86_=1 -DDLL -DWIN32 -GX -MD -Zi -Od -I$(OS_ROOTDIR)\include Query.cpp

##Query.lib: Query.obj Query.def
##  lib -machine:i386 -def:Query.def Query.obj -out:Query.lib

##Query.dll: Query.obj Query.def $(API_OBJECTS) $(APP_SCHEMA_OBJ)
Query.dll: Query.obj $(API_OBJECTS) $(APP_SCHEMA_OBJ)
  link @<<
-machine:i386 -DLL -debug -debugtype:cv -subsystem:console -out:Query.dll
Query.obj $(API_OBJECTS) $(APP_SCHEMA_OBJ)
$(OS_ROOTDIR)\lib\osstatic.lib
$(OS_ROOTDIR)\lib\ostore.lib
$(OS_ROOTDIR)\lib\oscoll.lib
kernel32.lib gdi32.lib user32.lib msvcrt.lib comdlg32.lib advapi32.lib
<<


Este fichero es utilizado mediante el comando

nmake /f fichero
  1. 1. Exportación de funciones

El mecanismo a través del cual cualquier aplicación que utilice una DLL puede usar las funciones que la forman es el denominado "exportación". En Visual C++, las funciones son exportadas al generarse la DLL si en el código fuente que las define se incluyen determinadas instrucciones clave.

// habilita la exportacion e importacion de funciones, datos y objetos desde y
// a una DLL
#ifdef DLL
#define MY_IMPORT extern "C" _declspec(dllexport)
#define CCONV _stdcall
#else
#define MY_IMPORT __declspec(dllimport)
#define CCONV _cdecl
#endif

Estas instrucciones son usadas al definir cada una de las funciones que se desea exportar, como sigue:

MY_IMPORT int   CCONV Prueba1(int num);

Al generar la librería, Visual C++ modifica el nombre de cada una de las funciones exportadas (Prueba1 en el ejemplo anterior), añadiendo determinados prefijos y sufijos. La mejor manera de determinar los nombres realmente exportados es mediante el comando:

dumpbin /exports nombreDLL

La salida de dicho comando incluye líneas de la forma siguiente:

                  7    6   _Prueba1@0  (0000118B)
                  8    7   _Prueba2@4  (00001ED3)
                  9    8   _Prueba3@4  (0000180C)

de la cual se puede deducir que los nombres exportados son, respectivamente, _Prueba1@0, _Prueba2@4 y _Prueba3@4.

  1. 2. Importación de funciones

Además de ser exportadas al compilar la librería, las funciones C++ han de ser importadas por la aplicación Visual BASIC que las utilizará.

Para ello han de ser declaradas con una sentencia BASIC como la que sigue:

Private Declare Function Pr1 Lib "Path\NombreDLL.dll" Alias "_Prueba1@0" () As Integer

En esta declaración se define una función que será invocada por Visual BASIC bajo el nombre Pr1. Esta función forma parte de la librería NombreDLL.dll, de la cual ha sido exportada al compilar bajo el nombre _Prueba1@0

 

  1. Acceso a datos

En este capítulo se definen los procedimientos que permiten acceder a datos de todos los tipos que se ha analizado. No se pretende mostrar piezas de código se gran complejidad, sino que el trabajo se ha centrado en pasar variables y comprobar su contenido.

  1. 1. Paso de enteros

Este fragmento de código C++ recoge un entero generado en BASIC y lo devuelve, en el ejemplo sin manipulación alguna:

MY_IMPORT int   CCONV Prueba2(int num) {
	return num;
}

La sentencia BASIC que utiliza esta función es:

Dim PrInteger As Integer
Resultado.Text = LTrim$(Str(Prueba2(PrInteger)))

en donde la función Prueba2 ha sido definida como:

Private Declare Function Prueba2 Lib "path\query.dll" Alias "_Prueba2@4" (ByVal Num As Integer) As Integer
  1. 2. Paso de cadenas de caracteres

Para pasar cadenas de caracteres se puede utilizar un programa BASIC como éste:

Dim Text1 As String
Text1 = String(50, "*")
Prueba3 (Text1)
Resultado.Text = LTrim$(Text1)

en donde la función Prueba3 se define:

Private Declare Function Prueba3 Lib "path\query.dll" Alias "_Prueba3@4" (ByVal texto As String) As Integer

y está programada en C++ de la manera siguiente:

MY_IMPORT int CCONV Prueba3(char *texto){
	strcpy (texto, "Resultado de la prueba");
	return (0);
}

Obsérvese que la función no devuelve una variable char(ésta se pasa a través de la lista de argumentos), sino una variable int que puede ser de utilidad para, por ejemplo, un código de error. Asimismo, puede observarse que en BASIC se ha realizado una asignación dinámica de longitud a la variable cadena antes de enviarla al módulo C++ ; enviar una variable de longitud no definida ha originado errores.

  1. 3. Paso de punteros a datos

Se han escrito dos funciones C++ para este ejemplo. Una de ellas reserva memoria para una variable y devuelve el puntero a dicha variable. La segunda recibe dicho puntero, extrae el contenido de la memoria en una ubicación temporal, la libera y, por último, devuelve el valor antes recuperado.

Las funciones son:

MY_IMPORT long  *CCONV Prueba4_1(long num){
	long *puntero;
	puntero  = (long *)malloc (sizeof(long));
	*puntero = num;
	return (puntero);
}


MY_IMPORT long   CCONV Prueba4_2(long *pointer){
	long num;

	num = *pointer;
	free (pointer);
	FILE *fp;

	return (num);
}

Obsérvese que el puntero se devuelve como un entero largo (long) y que C++ realiza la conversión de tipos (casting) de modo automático).

Las funciones han de ser declaradas en BASIC como:

Private Declare Function Prueba4_1 Lib "path\query.dll" Alias "_Prueba4_1@4" (ByVal Num As Long) As Long
Private Declare Function Prueba4_2 Lib "path\query.dll" Alias "_Prueba4_2@4" (ByVal Pointer As Long) As Long

y después son utilizadas de este modo:

Dim Puntero As Long
Puntero = Prueba4_1(PrInteger + 7)
Resultado.Text = LTrim$(Str(Prueba4_2(Puntero)))
  1. 4. Paso de estructuras

Se ha probado asimismo la transferencia de una estructura (en C++) o Tipo Definido por el Usuario (en BASIC). La definición de las mismas es:

Private Type Estructura
    num1 As Integer
    num2 As Integer
    largo1 As Long
    num3 As Integer
    largo2 As Long
    texto_in As String * 50
    texto_out As String * 50
End Type

y

class Estruct {
public:
	short numero1;
	short numero2;
	long largo1;
	short numero3;
	long largo2;
	char texto_in[50];
	char texto_out[50];
};

Por razones que se verán más adelante, las cadenas de caracteres han de definirse de longitud fija. Asimismo, en C++ se han usado los tipos short y long para obviar la indefinición inherente al tipo int.

Para comprobar el paso de estructuras se ha experimentado con tres casos distintos. En los dos primeros, se enviaba un número desde Visual BASIC que el programa C++ copiaba en algún elemento de la estructura. En el tercero, la estructura transportaba una cadena de caracteres en ambos sentidos.

  1. 4.1. Paso de enteros cortos dentro de estructuras

Obsérvese que, para detectar posibles desalineamientos de las variables, el valor devuelto se sitúa en algún lugar intermedio de la estructura. El programa de prueba es:

Dim UDT As Estructura
UDT = Prueba5(PrInteger)
Resultado.Text = LTrim$(Str(UDT.num3))

La declaración en BASIC es:

Private Declare Function Prueba5 Lib "path\query.dll" Alias "_Prueba5@4" (ByVal Num As Integer) As Estructura

El correspondiente programa C++ es tan sencillo como:

MY_IMPORT Estruct CCONV Prueba5(short num){
	Estruct Ejemplo;
	Ejemplo.numero3 = num;
	return (Ejemplo);
}
  1. 4.2 Paso de enteros largos dentro de estructuras

Para analizar también si los enteros tipo long son pasados correctamente se puso en práctica esta segunda prueba.

La función C++ que se invocaba era en este caso:

MY_IMPORT Estruct CCONV Prueba6(long num){
	Estruct Ejemplo;
	Ejemplo.largo2 = num;
	return (Ejemplo);
}

que estaba declarada en Visual BASIC como:

Private Declare Function Prueba6 Lib "path\query.dll" Alias "_Prueba6@4" (ByVal NLong As Long) As Estructura

Por último, el programa BASIC que explotaba esta función era:

Dim PrLong As Long
UDT = Prueba6(PrLong)
Resultado.Text = LTrim$(Str(UDT.largo2))
  1. 4.3 Paso de cadenas de caracteres dentro de estructuras

Al resolver este problema se ha encontrado que las cadenas de caracteres debían ser de longitud fija y predeterminada. No solamente no funcionaba el paso de variables cadena de longitud indeterminada (como tampoco era aceptable en el caso anterior de paso de cadenas), sino que tampoco parece funcionar la asignación dinámica de longitudes.

La función de prueba se declaraba en Visual BASIC como:

Private Declare Function Prueba7 Lib "path\query.dll" Alias "_Prueba7@4" (ByRef UDT As Estructura) As Integer

y se utilizaba así:

Res = Prueba7(UDT)
Resultado.Text = LTrim$(UDT.texto_out)

El código C++ de dicha función era:

MY_IMPORT int CCONV Prueba7(Estruct *Estr){
strncpy (Estr->texto_out, Estr->texto_in, 50);
return (0);
}

Obsérvese que, nuevamente, la rutina devuelve un valor numérico, estando la cadena de caracteres incluida en la secuencia de comandos de la misma.


Autor: Luis M Perez Llera <luisma@ic.vel.indra.es>
Actualizado: 25/Abr/97

Para ir al índice principal