Conexión entre Visual BASIC
y Visual C++
Por: Luis M Perez Llera [email protected]
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
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.
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.
El análisis se ha efectuado para las versiones
de ambos entornos de programación siguientes:
|
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.
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
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.
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
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.
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
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.
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)))
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.
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); }
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))
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 <[email protected]>
Actualizado: 25/Abr/97