Visual C

++
Programación Avanzada en Win32
Fco. Javier Ceballos Sierra
Profesor titular de la
Escuela Politécnica Superior
Universidad de Alcalá




http://www.fjceballos.es








Visual C++. Programación Avanzada en Win32
© Fco. J avier Ceballos Sierra
© De la edición: RA-MA 1999

MARCAS COMERCIALES: Las designaciones utilizadas por las empresas para distinguir
sus productos suelen ser marcas registradas. RA-MA ha intentado a lo largo de este libro distinguir
las marcas comerciales de los términos descriptivos, siguiendo el estilo de mayúsculas que utiliza
el fabricante, sin intención de infringir la marca y sólo en beneficio del propietario de la misma.

RA-MA es una marca comercial registrada.

Se ha puesto el máximo empeño en ofrecer al lector una información completa y precisa.
Sin embargo, RA-MA Editorial no asume ninguna responsabilidad derivada de su uso,
ni tampoco por cualquier violación de patentes ni otros derechos de terceras partes que pudieran
ocurrir. Esta publicación tiene por objeto proporcionar unos conocimientos precisos y acreditados
sobre el tema tratado. Su venta no supone para el editor ninguna forma de asistencia legal,
administrativa ni de ningún otro tipo. Caso de precisarse asesoría legal u otra forma de ayuda
experta, deben buscarse los servicios de un profesional competente.

Reservados todos los derechos de publicación en cualquier idioma.

Ninguna parte de este libro puede ser reproducida, grabada en sistema
de almacenamiento o transmitida en forma alguna ni por cualquier procedimiento, ya
sea electrónico, mecánico, reprográfico, magnético o cualquier otro, sin autorización
previa y por escrito de RA-MA; según lo dispuesto en el artículo 534-bis del Código Penal
vigente serán castigados con la pena de arresto mayor y multa quienes intencionadamente,
reprodujeren o plagiaren, en todo o en parte, una obra literaria, artística o científica.

Editado por:
RA-MA Editorial
Ctra. Canillas, 144
28043 MADRID
Teléfono: 91 381 03 00
Telefax: 91 381 03 72
Correo electrónico: rama@arrakis.es
Servidor Web: http://www.ra-ma.es
ISBN: 84-7897-344-3
Depósito Legal:
Autoedición: Fco. J avier Ceballos
Filmación e impresión: Albadalejo, S.L.
Impreso en España
Primera impresión: Enero 1999








ÍNDICE

PRÓLOGO .............................................................................................................. XXI
CAPÍTULO 1. AÑADIR CARACTERÍSTICAS A UNA APLICACIÓN ........ 1
VENTANA DE PRESENTACIÓN .................................................................... 1
CARGAR UNA APLICACIÓN UNA SOLA VEZ ............................................ 5
INFORMACIÓN DEL SISTEMA ...................................................................... 9
GetSystemInfo ............................................................................................... 9
GetVersionEx ................................................................................................. 12
GlobalMemoryStatus ..................................................................................... 13
GetDiskFreeSpace .......................................................................................... 15
GetSystemDirectory ....................................................................................... 15
Acerca de ........................................................................................................ 16
FORMULARIOS FLOTANTES ........................................................................ 20
SALIR DE WINDOWS DE UNA FORMA CONTROLADA ........................... 23
EJ ECUTAR UNA APLICACIÓN WINDOWS O DE CONSOLA ................... 24
ABRIR O IMPRIMIR UN DETERMINADO FICHERO .................................. 30
AÑADIR UN ICONO A LA BARRA DE TAREAS ......................................... 31
MENÚS CONTEXTUALES .............................................................................. 36
AÑADIR UN SISTEMA DE AYUDA A UNA APLICACIÓN ........................ 38
Soporte de ayuda proporcionado por AppWizard .......................................... 39
Compilar los ficheros de ayuda ...................................................................... 40
Diseñando el sistema de ayuda ...................................................................... 41
Construir el fichero de ayuda ......................................................................... 46
Ayuda sensible al contexto ............................................................................ 48
Función WinHelp ........................................................................................... 49
Propiedad Context help de las ventanas ......................................................... 49
HTML Help Workshop .................................................................................. 49
VIII VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Convertir un fichero de ayuda WinHelp ........................................................ 50
Vincular el sistema de ayuda HTML a una aplicación .................................. 51
Ayuda HTML sensible al contexto ................................................................ 52
DISTRIBUCIÓN DE UNA APLICACIÓN ........................................................ 54
Colocar el icono de la aplicación en el menú Inicio ...................................... 58
Asignación de grupos a componentes ............................................................ 59
Asignar componentes a cada tipo de instalación ............................................ 60
Ventana de presentación y fichero leame.txt ................................................. 60
Asignar ficheros a los grupos de ficheros ...................................................... 61
Recursos ......................................................................................................... 62
Construir las imágenes de los discos de distribución ..................................... 62

CAPÍTULO 2. HILOS ........................................................................................... 65
CONCEPTO DE PROCESO .............................................................................. 65
HILOS ................................................................................................................. 66
Estados de un hilo .......................................................................................... 67
Crear de un hilo .............................................................................................. 68
CWinThread .............................................................................................. 69
Finalizar un hilo ............................................................................................. 70
Planificación de hilos ..................................................................................... 70
Asignación de prioridades .............................................................................. 71
Prioridad relativa de un hilo ...................................................................... 71
HILOS UTILIZANDO LA BIBLIOTECA MFC ............................................... 72
COMUNICACIÓN ENTRE HILOS ................................................................... 78
Comunicación utilizando variables globales .................................................. 79
Comunicación utilizando mensajes ................................................................ 80
SINCRONIZACIÓN DE HILOS ........................................................................ 82
Secciones críticas ........................................................................................... 82
Creación de una sección crítica ................................................................. 87
Exclusión mutua ............................................................................................. 89
Semáforos ....................................................................................................... 93
Problema del productor-consumidor con semáforos ................................. 95
Eventos ........................................................................................................... 102
Eventos con inicialización manual ............................................................ 104
Eventos de inicialización automática ........................................................ 104
Problema del productor-consumidor con eventos ..................................... 105
Espera activa y pasiva .................................................................................... 109
ELEGIR EL TIPO DE SINCRONIZACIÓN ...................................................... 113


ÍNDICE IX

CAPÍTULO 3. COMUNICACIONES .................................................................. 115
COMUNICACIONES POR EL PUERTO SERIE.............................................. 117
Aplicación Win32 para comunicaciones vía RS232 ...................................... 120
Registro de Windows ..................................................................................... 123
Interfaz de comunicaciones ............................................................................ 124
Función Iniciar .......................................................................................... 126
Función Terminar ...................................................................................... 126
Función EstablecerConexion .................................................................... 127
Función MensajeDeError .......................................................................... 130
Función ConfigurarDisCom ...................................................................... 131
Controlar eventos ...................................................................................... 134
Función LeerCaracteresPuerto .................................................................. 138
Función EscribirCarsPuerto ...................................................................... 141
Función CortarConexion ........................................................................... 142
INTERFAZ DEL USUARIO .............................................................................. 143
ENVIAR Y RECIBIR DATOS ........................................................................... 147
CONTROL DE COMUNICACIONES ............................................................... 148
Tipo VARIANT ............................................................................................. 153
Manipular las comunicaciones ....................................................................... 156
Interfaz de comunicaciones ............................................................................ 159
Función Iniciar .......................................................................................... 161
Función Terminar ...................................................................................... 161
Función EstablecerConexion .................................................................... 162
Función ConfigurarDisCom ...................................................................... 163
Controlar eventos ...................................................................................... 164
Función LeerCaracteresPuerto .................................................................. 166
Función EscribirCarsPuerto ...................................................................... 166
Función CortarConexion ........................................................................... 167
INTERFAZ DEL USUARIO .............................................................................. 168
ENVIAR Y RECIBIR DATOS ........................................................................... 172

CAPÍTULO 4. CONTROLES ............................................................................... 175
CONTROL IMAGE LIST .................................................................................. 177
Crear una lista de imágenes ............................................................................ 177
CONTROL LIST ................................................................................................ 178
CListCtrl y CListView ................................................................................... 179
Añadir elementos a un control list ................................................................. 182
Añadir columnas a un control list .................................................................. 184
Añadir los subelementos ................................................................................ 185
Vistas del control list ...................................................................................... 185
X VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Iconos grandes .......................................................................................... 187
Iconos pequeños ........................................................................................ 187
Lista .......................................................................................................... 187
Detalles ..................................................................................................... 188
Elemento seleccionado ................................................................................... 188
CONTROL TREE ............................................................................................... 189
CTreeCtrl y CTreeView ................................................................................. 190
Elementos de un control tree .......................................................................... 192
Elementos padre e hijo ................................................................................... 193
Añadir elementos a un control tree ................................................................ 193
Seleccionar un elemento ................................................................................ 197
Sincronización de los controles tree y list ...................................................... 198

CAPÍTULO 5. COMPONENTES SOFTWARE ................................................. 203
COMPONENTES FRENTE A BIBLIOTECAS ................................................ 204
MODELOS DE COMPONENTES ..................................................................... 204
OLE ..................................................................................................................... 205
COM .................................................................................................................... 206
Fundamentos de los objetos COM ................................................................. 206
DCOM ................................................................................................................. 209
OLE 2 .................................................................................................................. 212
ActiveX ............................................................................................................... 213
CONTENEDOR ActiveX ................................................................................... 214
Incrustar un objeto ......................................................................................... 215
Vincular un objeto .......................................................................................... 216
Esqueleto de la aplicación .............................................................................. 217
Activar un objeto utilizando el ratón .............................................................. 219
Eliminar un objeto ActiveX ........................................................................... 227
SERVIDOR ActiveX .......................................................................................... 228
Esqueleto de la aplicación .............................................................................. 229
Completar el servidor ActiveX ...................................................................... 232
SERVIDOR ActiveX AUTOMATIZADO ......................................................... 236
Esqueleto de la aplicación .............................................................................. 237
Completar el servidor ActiveX automatizado ................................................ 241
Añadir una interfaz al servidor ....................................................................... 245
CLIENTE AUTOMATIZADO ........................................................................... 249
INTERFACES ..................................................................................................... 256
Interfaz personalizada .................................................................................... 256
Interfaz de tipo dispinterface .......................................................................... 257
Interfaz dual ................................................................................................... 260
Añadir una interfaz dual ................................................................................. 262
ÍNDICE XI

Implementar la interfaz dual .......................................................................... 264
CONTROL ActiveX ........................................................................................... 269
Crear un objeto ActiveX ................................................................................ 270
Añadir una interfaz al control ActiveX .......................................................... 271
Completar el control ActiveX ........................................................................ 273
Propiedades de un control ActiveX ................................................................ 277
Añadir una propiedad normal ................................................................... 278
Añadir una propiedad parametrizada ........................................................ 280
Añadir una propiedad común .................................................................... 281
Añadir una propiedad ambiental ............................................................... 282
Hoja de propiedades .................................................................................. 283
Añadir una nueva página de propiedades ................................................. 286
Añadir una página de propiedades común ................................................ 287
Añadir métodos .............................................................................................. 288
Añadir eventos ............................................................................................... 289
Persistencia ..................................................................................................... 292
CONTENEDOR PARA UN CONTROL ActiveX ............................................. 293

CAPÍTULO 6. ATL ................................................................................................ 297
QUÉ ES ATL ...................................................................................................... 297
VISUAL C++Y ATL ......................................................................................... 298
CONTROL ActiveX ........................................................................................... 300
Crear un proyecto para un control ActiveX ................................................... 300
Crear un control ActiveX ............................................................................... 301
Clase del control ............................................................................................. 304
Visualizar datos en el control ActiveX .......................................................... 305
Añadir una interfaz al control ActiveX .......................................................... 309
Completar el control ActiveX ........................................................................ 313
Propiedades de un control ActiveX ................................................................ 317
Añadir una propiedad definida por el usuario ........................................... 318
Añadir una propiedad común .................................................................... 322
Añadir una propiedad ambiental ............................................................... 324
Hoja de propiedades .................................................................................. 324
Añadir una página de propiedades común ................................................ 330
Añadir métodos .............................................................................................. 330
Añadir eventos ............................................................................................... 332
Persistencia ..................................................................................................... 336
CONTENEDOR PARA UN CONTROL ActiveX ............................................. 336
PROGRAMACIÓN AVANZADA CON ATL ................................................... 338
Propiedades asíncronas .................................................................................. 338
Utilización del portapapeles ........................................................................... 345
XII VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Funcionalidad necesaria para utilizar el portapapeles ............................... 348
El control pone datos en el portapapeles ................................................... 353
El control obtiene datos del portapapeles ................................................. 355
Arrastrar y soltar ............................................................................................ 359
Control como fuente de datos ................................................................... 359
Control como destino de los datos ............................................................ 361
Controles con interfaz dual ............................................................................ 365

CAPÍTULO 7. MULTIMEDIA ............................................................................. 367
ARQUITECTURA MULTIMEDIA ................................................................... 368
TIPOS DE DATOS MULTIMEDIA .................................................................. 369
MULTIMEDIA MCI........................................................................................... 370
CD de audio ................................................................................................... 375
Audio por forma de onda ............................................................................... 381
Audio y vídeo entrelazado ............................................................................. 383
Ejemplo de multimedia MCI .......................................................................... 386
MULTIMEDIA UTILIZANDO LA API DE WINDOWS ................................. 393
Servicios de audio .......................................................................................... 393
Interfaz de control de medios ......................................................................... 396
Dispositivos MCI ........................................................................................... 397
Órdenes MCI .................................................................................................. 398
Abrir un dispositivo ....................................................................................... 399
Tipos de dispositivos ...................................................................................... 399
Reproducir un fichero .................................................................................... 400
Detener un dispositivo ................................................................................... 400
Cerrar un dispositivo ...................................................................................... 400
CD de audio ................................................................................................... 400
Audio por forma de onda ............................................................................... 407
Audio y vídeo entrelazado ............................................................................. 409
PONER SONIDO A UNA APLICACIÓN ......................................................... 412
HIPERMEDIA .................................................................................................... 415
Cargar una imagen ......................................................................................... 420
Establecer y probar zonas activas .................................................................. 422
Guardar y recuperar las zonas activas ............................................................ 427
ANIMACIÓN DE GRÁFICOS........................................................................... 431
Un ejemplo de animación ............................................................................... 433
ANIMACIÓN CON CDIB .................................................................................. 439
Un ejemplo de animación con CDIB ............................................................. 441


ÍNDICE XIII

CAPÍTULO 8. BIBLIOTECAS DINÁMICAS .................................................... 459
CREACIÓN DE UNA DLL EN Win32 .............................................................. 460
Fichero de cabecera (.h) ................................................................................. 462
Fichero fuente (.c o .cpp) ............................................................................... 462
Fichero de definición de módulos (.def) ........................................................ 465
LLAMANDO A LAS FUNCIONES DE LA DLL ............................................. 465
Enlace estático ................................................................................................ 467
Enlace dinámico ............................................................................................. 468
RECURSOS EN UNA DLL ............................................................................... 471
Acceso a los recursos en una DLL ................................................................. 474
OBJ ETOS COM COMO ALTERNATIVA A LAS DLLs ................................. 480
Esqueleto de la aplicación .............................................................................. 481
Añadir métodos .............................................................................................. 482
Utilización del servidor COM ........................................................................ 484
Obtener un puntero a una interfaz ............................................................. 485
Llamando a las funciones de la interfaz .................................................... 488
Clase _com_ptr_t ...................................................................................... 489
Directriz #import ....................................................................................... 491
Añadir otra interfaz ................................................................................... 494
Aplicación de tipo consola ........................................................................ 499

CAPÍTULO 9. BASES DE DATOS ...................................................................... 503
CLASES ODBC PARA ACCESO A BASES DE DATOS ................................ 504
ACCESO A UNA BASE DE DATOS UTILIZANDO ODBC .......................... 510
Integridad referencial ..................................................................................... 511
Características de la aplicación ...................................................................... 511
Registrar la base de datos ............................................................................... 512
Crear una aplicación con soporte ODBC para BD ......................................... 513
Diseño de la plantilla de diálogo .................................................................... 517
Asociar los controles con los campos del conjunto de registros .................... 518
Ejecutar la aplicación ..................................................................................... 519
Añadir otro conjunto de registros ................................................................... 520
Llenar la lista desplegable con la lista de idiomas ......................................... 521
Establecer un filtro ......................................................................................... 524
Establecer un parámetro ................................................................................. 525
Editar, añadir y borrar registros ..................................................................... 528
Editar un registro....................................................................................... 529
Añadir un registro ..................................................................................... 530
Borrar un registro ...................................................................................... 533
Abandonar una operación de edición o de adición ................................... 533
XIV VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Registros borrados .......................................................................................... 534
Conjunto de registros vacío ............................................................................ 535
OBJ ETOS ACTIVEX PARA ACCESO A DATOS ........................................... 536
Modelo de objeto ADO .................................................................................. 537
Acceso a los datos con ADO .......................................................................... 539
ADO Comparado con RDO y DAO ............................................................... 540
ACCESO A UNA BASE DE DATOS UTILIZANDO ADO ............................. 541
Crear una aplicación con soporte ADO para BD ........................................... 542
Asistente ADO Data Bound Dialog .......................................................... 542
Extensiones Visual C++para ADO .......................................................... 543
Diseño de la plantilla de diálogo ............................................................... 547
Abrir una conexión ................................................................................... 550
Moverse por la base de datos .................................................................... 553
Ejecutar la aplicación ..................................................................................... 554
Añadir nuevos controles al diálogo Recordset ............................................... 555
Añadir otro conjunto de registros ................................................................... 556
Acceder a los campos de un registro .............................................................. 558
Llenar la lista desplegable con la lista de idiomas ......................................... 559
Establecer un filtro ......................................................................................... 560
Habilitar o inhabilitar controles ..................................................................... 561
Actualizar los datos ........................................................................................ 563
Actualización inmediata ................................................................................. 564
Actualización por lotes ................................................................................... 565
Cancelar modificaciones ................................................................................ 566
Añadir un nuevo registro ................................................................................ 567
Borrar un registro ........................................................................................... 569
Conjunto de registros vacío ............................................................................ 570
Características soportadas .............................................................................. 571
Número de registros ....................................................................................... 571

CAPÍTULO 10. INTERNET ................................................................................. 573
¿QUÉ ES INTERNET? ....................................................................................... 573
Intranet ........................................................................................................... 574
Extranet .......................................................................................................... 574
Terminología Internet .................................................................................... 574
SERVICIOS EN INTERNET ............................................................................. 577
Correo electrónico .......................................................................................... 578
Conexión remota (telnet)................................................................................ 579
Transferencia de ficheros (ftp) ....................................................................... 580
Noticias (news) .............................................................................................. 582
Conversaciones .............................................................................................. 583
ÍNDICE XV

Herramientas para búsqueda de información ................................................. 584
World Wide Web (WWW) ....................................................................... 584
Gopher ...................................................................................................... 586
Archie ........................................................................................................ 587
La información en Internet ............................................................................. 589
PÁGINAS WEB .................................................................................................. 589
Qué es HTML ................................................................................................ 590
Etiquetas básicas HTML ................................................................................ 590
Etiquetas de formato de texto ......................................................................... 591
URL ................................................................................................................ 593
Enlaces entre páginas ..................................................................................... 594
Gráficos .......................................................................................................... 595
Marcos ............................................................................................................ 596
Páginas dinámicas .......................................................................................... 597
VBScript en una página Web .................................................................... 599
Insertar un control ActiveX en una página Web ....................................... 600
ActiveX Control Pad ...................................................................................... 602
El editor de textos ..................................................................................... 603
El editor de objetos ................................................................................... 604
El asistente de VBScript o J Script ............................................................ 605
El editor de plantillas ................................................................................ 607
Distribución y licencia de ActiveX Control Pad ............................................ 611
Controles ActiveX para páginas Web ............................................................ 611
Control Marquee ....................................................................................... 612
Control HotSpot ........................................................................................ 612
Control ActiveMovie ................................................................................ 612
Documentos ActiveX ..................................................................................... 613
Objetos de Internet Explorer .......................................................................... 613
Objeto window .......................................................................................... 614
Objeto frames ............................................................................................ 616
Objeto history ........................................................................................... 617
Objeto navigator........................................................................................ 617
Objeto location .......................................................................................... 618
Objeto script .............................................................................................. 618
Objeto document ....................................................................................... 618
Objeto link ................................................................................................ 620
Objeto anchor ............................................................................................ 620
Objeto form ............................................................................................... 620
Objeto element .......................................................................................... 620
Microsoft FrontPage Express ......................................................................... 621


XVI VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

CAPÍTULO 11. VISUAL INTERDEV ................................................................. 623
ENTORNO DE PROGRAMACIÓN DE VISUAL INTERDEV ....................... 623
Esquema html ................................................................................................. 624
Cuadro de herramientas .................................................................................. 624
Esquema de secuencias de comandos ............................................................ 625
Vista Diseño ................................................................................................... 625
Vista Código .................................................................................................. 625
Vista rápida .................................................................................................... 626
DISEÑO DE UN SITIO WEB ............................................................................ 626
Trabajar sin conexión ..................................................................................... 628
Crear un proyecto Web .................................................................................. 628
Crear y organizar páginas .............................................................................. 630
Incluir gráficos ............................................................................................... 632
Establecer vínculos ........................................................................................ 632
Tema y diseño ................................................................................................ 633
Barra de exploración ...................................................................................... 634
FrontPage y Visual InterDev .......................................................................... 635
Distribución de la aplicación Web ................................................................. 635
INTEGRAR MULTIMEDIA .............................................................................. 635
PÁGINAS ASP ................................................................................................... 639
Modelo ASP ................................................................................................... 640
Secuencias de órdenes .................................................................................... 640
Lenguajes de secuencias de órdenes .............................................................. 642
Objetos ASP predefinidos .............................................................................. 643
Objeto Response ....................................................................................... 643
Objeto Request .......................................................................................... 643
Objeto Server ............................................................................................ 643
Objeto Session .......................................................................................... 644
Objeto Application .................................................................................... 644
Objeto ObjectContext ............................................................................... 644
OBTENER INFORMACIÓN MEDIANTE FORMULARIOS .......................... 644
Diseño de un formulario HTML .................................................................... 646
Procesar la información del formulario en el cliente ..................................... 647
Procesar formularios en el servidor ................................................................ 648
ACCESO A UNA BASE DE DATOS ................................................................ 649
Diseño del acceso a bases de datos ................................................................ 650

CAPÍTULO 12. APLICACIONES DE INTERNET ........................................... 655
CREAR UN EXPLORADOR WEB ................................................................... 655

ÍNDICE XVII

WININET ............................................................................................................ 658
Acceso a un servidor HTTP ........................................................................... 659
Acceso a un servidor FTP .............................................................................. 662
SOCKETS ........................................................................................................... 672
WinSock ......................................................................................................... 674
Comunicación orientada a conexión .............................................................. 674
Servidor ..................................................................................................... 676
Cliente ....................................................................................................... 685
Enviar mensajes ............................................................................................. 694

CAPÍTULO 13. DIRECTX.................................................................................... 701
COMPONENTES DE DIRECTX ....................................................................... 701
DIRECTX Y COM.............................................................................................. 702
CÓMO TRABAJ A DIRECTX ............................................................................ 704
CREAR UNA APLICACIÓN DIRECTDRAW ................................................. 706
Crear el objeto DirectDraw ............................................................................ 708
Fijar el nivel de cooperación .......................................................................... 709
Fijar la resolución de la tarjeta gráfica ........................................................... 710
Crear las superficies de visualización ............................................................ 710
Crear los objetos de recorte ............................................................................ 714
Crear la paleta de colores ............................................................................... 717
Asociar la paleta con las superficies de visualización .................................... 718
Crear los sprites .............................................................................................. 719
Acceso directo a la memoria de la superficie ............................................ 721
Emplear la GDI y hacer una copia desde un DIB ..................................... 722
Bucle de animación ........................................................................................ 723
Salir de la aplicación ...................................................................................... 725
DIRECTDRAW Y LA BIBLIOTECA MFC ...................................................... 726
APLICACIÓN DIRECTDRAW ......................................................................... 727
Arquitectura de la aplicación ......................................................................... 728
Crear superficie .............................................................................................. 733
Cargar mapa de bits ........................................................................................ 735
Crear paleta de colores ................................................................................... 737
Obtener la profundidad de color .................................................................... 739
Objeto de recorte ............................................................................................ 740
Creación del entorno DirectDraw .................................................................. 741
Fijar el modo de vídeo ................................................................................... 743
Mostrar la nueva escena generada .................................................................. 749
Dibujar en una ventana .................................................................................. 752
La ventana principal recibe el foco ................................................................ 753
La paleta de colores cambió ........................................................................... 754
XVIII VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

La resolución de vídeo cambió ...................................................................... 754
La posición de la ventana se modificó ........................................................... 755
El tamaño de la ventana cambió ..................................................................... 756
Menús a pantalla completa ............................................................................. 758
Destruir los objetos DirectDraw ..................................................................... 761
Cambiar a pantalla completa .......................................................................... 761
Sincronización con el espacio vertical ........................................................... 763
Cerrar la aplicación pulsando la tecla Esc ...................................................... 763
Posición del ratón ........................................................................................... 764
Animación de imágenes ................................................................................. 764
Interfaz de programación ............................................................................... 766
Variables y recursos .................................................................................. 766
Funciones de la interfaz de programación ................................................ 767
Función OnUsrCreaEscena ....................................................................... 768
Función OnUsrCambiaResolucion ........................................................... 769
Función OnUsrRestauraSuperficies .......................................................... 770
Función OnUsrTick .................................................................................. 771
Función OnUsrDestruyeEscena ................................................................ 773
Función TamSuperficie ............................................................................. 773
Función UsrDibujaMosaico ...................................................................... 774
Función UsrDibuja .................................................................................... 775
DIRECT3D ......................................................................................................... 775
Interfaces Direct3DRM utilizadas .................................................................. 776
Pasos para crear una aplicación Direct3DRM ................................................ 778
Objetos DirectDraw y Direct3DRM, superficies y modo de vídeo ............... 779
Enumerar los drivers de dispositivo ............................................................... 783
Crear el dispositivo y la superficie de proyección ......................................... 788
Crear los objetos de la escena ........................................................................ 792
Notificar mensajes a Direct3DRM ................................................................. 796
Crear el bucle de animación ........................................................................... 798
API de Direct3DRM ...................................................................................... 801
Luces ......................................................................................................... 801
Secuencias de animación .......................................................................... 802
Marcos ...................................................................................................... 802
Objetos visibles ......................................................................................... 803
Crear objetos 3D ....................................................................................... 803
Cargar un objeto 3D .................................................................................. 805
Perspectivas ............................................................................................... 806
Transformaciones ...................................................................................... 806
Clase Direct3DRMObject ......................................................................... 809
Crear la escena ............................................................................................... 809
DIRECTSOUND ................................................................................................. 816
Interfaces a utilizar ......................................................................................... 817
ÍNDICE XIX

Aplicación DirectSound ................................................................................. 817
API de DirectSound ....................................................................................... 819
Crear el objeto DirectSound ...................................................................... 819
Nivel de cooperación de la aplicación ...................................................... 820
Crear un buffer de sonido ......................................................................... 820
Efecto Doppler .......................................................................................... 821
Reproducir un buffer de sonido ................................................................ 822
Detener un sonido ..................................................................................... 822
Volumen .................................................................................................... 822
Balance ...................................................................................................... 823
Conos de sonido ........................................................................................ 823
Crear los objetos de sonido para aplicación ................................................... 826
Interfaz de programación ............................................................................... 827
Cargar sonidos .......................................................................................... 828
Reproducir un buffer de sonido ................................................................ 829
Detener un sonido ..................................................................................... 829
Sonidos utilizados .......................................................................................... 830

APÉNDICE A. CÓDIGOS DE CARACTERES .................................................. 835
UTILIZACIÓN DE CARACTERES ANSI CON WINDOWS .......................... 835
J UEGO DE CARACTERES ANSI ..................................................................... 836
UTILIZACIÓN DE CARACTERES ASCII ....................................................... 837
J UEGO DE CARACTERES ASCII .................................................................... 838
CÓDIGOS EXTENDIDOS ................................................................................. 839
CÓDIGOS DEL TECLADO ............................................................................... 840

APÉNDICE B. ÍNDICE ALFABÉTICO .............................................................. 841










PRÓLOGO

Para facilitar el desarrollo de aplicaciones para Windows escritas en C, Microsoft
introduce en 1992, C/C++7.0 y la biblioteca de clases MFC 1.0. Las investiga-
ciones demostraron que debido al nivel de dificultad de aprender y utilizar no sólo
C++, sino el conjunto de clases de las MFC, muchos desarrolladores se quedaban
en el intento de migrar a C++. Por este motivo fue creado en febrero de 1993 Vi-
sual C++1.0, para facilitar a los desarrolladores la migración a C++.
Con Visual C++se introdujo una tecnología de desarrollo innovadora a base
de asistentes con una nueva versión de las MFC más potente; la 2.0. La biblioteca
MFC 2.0, compatible con la versión anterior, permitió implementar una arquitec-
tura de aplicación que facilitaba enormemente el desarrollo de aplicaciones. Los
asistentes que permitían generar esta nueva arquitectura basada en las clases
MFC, evitaban escribir muchas de las líneas de código necesarias para construir
una aplicación. Esto es, los asistentes generaban aplicaciones para Windows, sin
necesidad de escribir líneas y líneas de código. Por ello, Visual C++se convirtió
en el camino más corto para el desarrollo de aplicaciones C++para Windows,
combinando, además, un alto rendimiento con una comodidad en el uso.
Posteriormente, en abril de 1993, Microsoft presentó OLE 2.0. Con OLE 2.0
los desarrolladores podían implementar objetos que interactuaban entre sí sin im-
portar cómo actuaba cada objeto específicamente. Más aún, podían utilizarse apli-
caciones enteras como componentes, lo que hacía más fácil la integración de
aplicaciones y como consecuencia la combinación de información. No obstante,
hasta que Visual C++1.5 y la biblioteca MFC 2.5 no estuvieron disponibles, no
fue cómodo desarrollar aplicaciones OLE 2.0. A partir de este momento, los des-
arrolladores tuvieron asistentes para crear objetos OLE cliente, servidor o conte-
nedor con pocos esfuerzos. Asimismo, esta biblioteca también incluía soporte
XXII VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

completo de ODBC para facilitar la programación y el acceso a bases de datos lo-
cales o remotas.
El fuerte interés de los desarrolladores por crear aplicaciones de 32 bits (basa-
das en Win32 y OLE) así como tener flexibilidad para dirigirse a múltiples plata-
formas (Windows y Windows NT para Intel y RISC, Windows 9x y Macintosh)
condujo a Microsoft a crear Visual C++2.0 y la biblioteca MFC 3.0 totalmente
compatible con las versiones anteriores. De esta forma los desarrolladores podían
continuar manteniendo sus aplicaciones de 16 bits con Visual C++1.5x y los des-
arrolladores de 32 bits las suyas con Visual C++2.x.
El compilador C++de Visual C++2.0 ya incorporaba las últimas característi-
cas de C++; esto es, plantillas de C++y los manipuladores de excepciones que sus
antecesores incorporaban a base de macros. Visual C++2.0 aportaba entre otras
características: la creación de aplicaciones de 32 bits, hilos (threads) y un espacio
de memoria plana (eliminando los segmentos de 64K). Asimismo, la biblioteca
MFC 3.0 añadía soporte OLE de 32 bits y ODBC. En realidad Visual C++2.0 fue
un trampolín para sus predecesores (Visual C++n.x, Visual C++6.0).
Visual C++6.0 presenta la tecnología de compilación más avanzada para
producir aplicaciones de 32 bits más rápidas y de menor tamaño. También incor-
pora las características y palabras clave más actuales del estándar ANSI. Las nue-
vas MFC&T combinan la fiabilidad y productividad de la biblioteca MFC con la
biblioteca ATL (Active Template Library). Hace más fácil el desarrollo de softwa-
re basado en componentes (soporte COM). Incorpora un gran número de nuevos
elementos diseñados para explotar las tecnologías de Internet.
Visual C++6.0 proporciona varias formas de trabajo con bases de datos. Por
ejemplo, utilizando la biblioteca de clases MFC, podemos recurrir a las clases
DAO (Data Access Objects - objetos de acceso a datos) o a las clases ODBC
(Open DataBase Connectivity - conectividad abierta de bases de datos). Pero las
tecnologías actuales de acceso a datos tienen que satisfacer los nuevos escenarios
demandados por las empresas, tales como los sistemas de información basados en
la Web. En esta línea, Microsoft ofrece utilizar OLE DB como un proveedor de
datos y objetos ADO (ActiveX Data Objects - objetos ActiveX para acceso a da-
tos), como tecnología de acceso a datos, argumentando que el acceso a datos ba-
sado en OLE DB y ADO es adecuado para una gama amplia de aplicaciones,
desde pequeños procesos en estaciones de trabajo a aplicaciones Web a gran esca-
la. OLE DB es un conjunto de interfaces COM que puede proporcionar acceso
uniforme a los datos guardados en diversas fuentes de información. El modelo de
objeto ADO define una colección de objetos programables, que soportan el mode-
lo de objeto componente (COM) y la automatización OLE, que pueden interac-
cionar con la tecnología OLE DB. El modelo de objeto de ADO, comparado con
PRÓLOGO XXIII

otros objetos de acceso a datos como RDO o DAO, tiene menos objetos y es más
simple de utilizar.
Este libro, escrito con la versión 6 de Visual C++, es continuación del publi-
cado anteriormente, Visual C++ Aplicaciones para Win32. Por lo tanto, para
abordar su contenido el autor ha supuesto que el lector conoce todo lo expuesto en
el título anteriormente citado. Este libro trata temas más avanzados, como el sis-
tema de ayuda HTML, hilos, comunicaciones RS-232, controles ActiveX, tecno-
logía COM, biblioteca ATL, dispositivos MCI, bibliotecas dinámicas, acceso a
bases de datos, Internet, Visual Interdev, aplicaciones de Internet, DirectDraw,
Direct3DRM y DirectSound. Todos los temas se han documentando con abundan-
tes ejemplos resueltos, lo que le facilitará el aprendizaje.
Este libro es el cuarto de una colección de cuatro libros orientados al desarro-
llo de aplicaciones con C/C++. Entre los cuatro, y en el orden especificado, cu-
bren los siguientes aspectos: programación con C, programación orientada a
objetos con C++, desarrollo de aplicaciones para Windows basadas en objetos, y
programación avanzada en Windows incluyendo Internet.
El primero, Curso de programación C/C++, abarca todo lo relativo a la pro-
gramación estructurada con C. También incluye diversos algoritmos de uso
común así como estructuras dinámicas de datos.
El segundo, Programación orientada a objetos con C++, estudia como su
nombre indica el desarrollo de aplicaciones orientadas a objetos. Esta tecnología
es imprescindible conocerla si queremos desarrollar aplicaciones utilizando bi-
bliotecas de clases como las MFC&ATL de Microsoft Visual C++.
El tercero, Visual C++ - Aplicaciones para Win32, le enseña fundamental-
mente cómo desarrollar aplicaciones para Windows (aplicaciones con una interfaz
gráfica basada en ventanas).
Y éste, el cuarto, Visual C++ - Programación avanzada, complementa al li-
bro anterior abordando temas más complejos, a los que me he referido anterior-
mente.
Agradecimientos
He recibido ideas y sugerencias de algunas personas durante la preparación de es-
te libro, entre las que se encuentran, cómo no, mis alumnos, que con su interés por
aprender me hacen reflexionar sobre objetivos que a primera vista parecen inal-
canzables, pero que una vez logrados sirven para que todos aprendamos; a todos
ellos les estoy francamente agradecido.
XXIV VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

En especial, quiero expresar mi agradecimiento a Alfons González por sus
ideas que siempre son bienvenidas, a Oscar García Población y Rafael Torcida
por sus buenas recomendaciones y aportaciones, y a David Jurado González por
su participación en la corrección de esta obra, por sus aportaciones a la misma y
en especial, porque sin su empeño y colaboración este libro posiblemente no habr-
ía incluido DirectX.
También, quiero agradecer a Microsoft Ibérica la cesión de los programas in-
cluidos en el CD-ROM.
Francisco Javier Ceballos Sierra
http://www.fjceballos.es/

Faltan páginas...






CAPÍTULO 3
© F.J.Ceballos/RA-MA

COMUNICACIONES

Este capítulo presenta técnicas de comunicación con otras máquinas utilizando el
puerto serie. Antes de empezar el desarrollo de una aplicación que implemente
comunicaciones serie, resulta útil hacer una breve descripción del funcionamiento
básico de la propia interconexión RS-232.
Las señales disponibles en un conector RS-232 están pensadas únicamente pa-
ra asegurar la correcta transmisión y recepción de datos desde un equipo denomi-
nado DTE (Data Terminal Equipment - Equipo terminal de datos) a un DCE
(Data Communication Equipment - Equipo de comunicación de datos). Un DTE
es generalmente un ordenador y un DCE un módem. El enlace estándar entre un
DTE y un DCE se puede ver en la figura siguiente.



116 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

La función de cada una de las señales es como sigue:
Señal Nombre Dirección Función
TxD Transmitted Data hacia DCE Salida de datos DTE
RxD Received Data hacia DTE Entrada de datos DTE
RTS Request To Send hacia DCE DTE desea cambiar a modo transmisión
CTS Clear To Send hacia DTE DCE listo para transmitir
DSR Data Set Ready hacia DTE DCE listo para comunicar con DTE
Signal Common Línea común del circuito (masa)
DCD Data Carrier Detect hacia DTE Detectar si está conectado
DTR Data Terminal Ready hacia DCE Pone a trabajar al módem
RI Ring Indicator hacia DTE Anuncia una llamada
TxD se encarga de transportar los datos serie hasta el módem. Para ello, han
tenido que activarse RTS, CTS, DSR y DTR.
RxD, recepción de datos, no depende de ninguna otra función RS-232.
RTS tiene como misión conmutar un módemsemi-duplex entre modos de re-
cepción y transmisión. Cuando el DTE quiere transmitir, informa al módem de su
deseo activando esta patilla. Cuando el módem conmuta para transmisión, lo in-
forma al DTE activando la patilla CTS, indicando que ya puede enviar los datos.
El módem origen no transmite ni activa su DSR hasta recibir el tono de res-
puesta del módem remoto.
DCD, detección de señal de línea recibida, se activa cuando el módem recibe
una portadora remota. En módems semi-duplex, DCD se activa únicamente en el
módem receptor.
Una vez que el módem esté conectado a la línea, DTR deberá permanecer ac-
tiva mientras dure la conexión; si se inhibe, se produce la desconexión, interrum-
piendo bruscamente el enlace.
Además del enlace estándar, existen otros, como la conexión denominada
módem nulo (cable de seis hilos), utilizada generalmente para transferir ficheros
entre dos ordenadores. Esta conexión, como su nombre indica, no es en absoluto
un módem, sino una conexión directa entre dos ordenadores (DTE) para comuni-
carse siguiendo las reglas lógicas del protocolo RS-232. Otra solución para la co-
municación DTE-DTE más sencilla todavía, es la conexión de dos hilos (TxD y
RxD).
CAPÍTULO 3: COMUNICACIONES 117

De lo expuesto puede deducirse que para que exista una comunicación entre
dos equipos tiene que haber un acoplamiento entre ellos, y dicho acoplamiento
puede realizarse por software o por hardware.
El acoplamiento hardware sólo es posible si ambos equipos están físicamente
conectados mediante un cable. Se suele realizar mediante las señales DTR/DSR o
bien utilizando simplemente las señales secundarias RTS/CTS.
El acoplamiento software no siempre es posible, ya que para que pueda darse,
los equipos deben reconocer caracteres de control. En un acoplamiento software
es el receptor el que controla el acoplamiento. Lo hace de la forma siguiente:
• Cuando su cola de entrada está llena, envía un carácter de desconexión (nor-
malmente ASCII_XOFF - 0x13).
• Cuando el transmisor recibe este carácter se detiene.
• Cuando la cola de entrada del receptor puede recibir más caracteres, envía un
carácter de conexión (normalmente ASCII_XON - 0x11).
• Cuando el transmisor recibe este carácter reinicia el envío de caracteres.
COMUNICACIONES POR EL PUERTO SERIE
La gestión de los puertos de comunicación no es una tarea fácil. Lo primero que
hay que pensar es que los datos llegan a los puertos de forma asíncrona; es decir,
su llegada es imprevisible. Esto sugiere que el dato que llega tiene que procesarse
inmediatamente, puesto que pueden llegar otros datos. De esta tarea se encarga el
hardware del PC, de forma que cuando detecta la llegada de un dato, interrumpe
el flujo normal del proceso para ceder el control a la rutina de proceso de comuni-
caciones. Esta rutina tiene que ser una rutina de Windows en lugar de una rutina
de la aplicación. Esto es así por dos razones:
### Windows debe mantener el control de la multitarea. En efecto, si la llegada de
un dato hiciera que se transfiriera el control del procesador a su aplicación,
Windows perdería su habilidad para gestionar la multitarea. Esto quiere decir
que Windows tiene que estar entre la aplicación y el hardware.
### Windows no puede dirigir el dato recibido directamente a la aplicación. La
razón es que los datos que se reciben en el puerto de comunicaciones no lle-
gan con la identidad de la aplicación que los tiene que recibir. Por lo tanto,
Windows tiene que guardar en un buffer los datos que llegan para una aplica-
ción. Para qué aplicación, debe determinarse por adelantado; esto es, su apli-
118 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

cación debe haberle pedido a Windows la propiedad del puerto utilizando la
función CreateFile.
Cuando una aplicación solicita a Windows la propiedad de un puerto, Win-
dows sólo se lo dará si ninguna otra aplicación lo tiene. Por el mismo motivo,
mientras su aplicación tiene el control de un puerto, Windows se lo prohíbe a las
demás aplicaciones que lo soliciten. Cuando su aplicación finalice la operación de
E/S con un puerto, debe dejar el control del mismo para que otras aplicaciones
puedan utilizarlo, lo cual requiere llamar a la función CloseHandle.
Como ejemplo, vamos a realizar una aplicación que permita transferir datos
entre dos ordenadores personales. Para probar la aplicación, debe conectar vía
puerto de comunicaciones los dos ordenadores. Una vez realizada la conexión,
asegúrese de que está bien hecha utilizando un paquete de comunicaciones co-
mercial, como el programa HiperTerminal de Windows.
Una forma de realizar esta aplicación sería utilizando las funciones de la API
de Win32 que se indican en la tabla siguiente:
Función Descripción
CreateFile Abrir un puerto de comunicaciones
SetCommMask Eventos que serán atendidos
SetupComm Tamaño de las colas de E/S
PurgeComm Terminar operaciones pendientes y limpiar colas
GetCommState Obtener las características del puerto (estructura DCB)
SetCommState Establecer las características del puerto
ReadFile Leer datos
WriteFile Enviar datos
CloseHandle Cerrar un puerto de comunicaciones
Otras funciones disponibles en la API de 32 bits son: SetCommTimeouts,
EscapeCommFunction, WaitCommEvent, GetLastError, ClearCommError,
BuildCommDCB, etc. No obstante, la forma más sencilla de trabajar con el puer-
to serie en aplicaciones de 32 bits es utilizando el control de comunicaciones
mscomm32.ocx (Microsoft Communications Control), cuestión que veremos más
adelante en este mismo capítulo.
Para establecer una comunicación utilizando la API de 32 bits, siga estos pasos:
1. Abra el puerto de comunicación. Para realizar esta operación, llame a la fun-
ción CreateFile con los argumentos: puerto de comunicaciones (COM1,
COM2, etc.), modo de acceso (leer y/o escribir), modo de compartición, atri-
butos de seguridad, acción a tomar tanto si existe el fichero como si no existe,
CAPÍTULO 3: COMUNICACIONES 119

y atributos del fichero. Esta función devuelve un handle que identifica el
puerto de comunicaciones abierto, o el valor ERROR_FILE_NOT_FOUND si
el puerto no está disponible.
2. Establezca la máscara de comunicaciones para especificar los eventos que
serán atendidos. Para realizar esta operación, llame a la función SetComm-
Mask.
3. Defina el tamaño de los buffers de las colas de entrada y salida. Utilice para
ello la función SetupComm. No obstante, esta operación no siempre es nece-
saria, puesto que existen dos buffers definidos por omisión. A continuación,
limpie estos buffers invocando a la función PurgeComm.
4. Construya una estructura de tipo DCB que especifique la configuración del
puerto (DCB - device control block). Para ello, llame a la función GetComm-
State para obtener la configuración inicial del puerto y, a partir de estos valo-
res iniciales, modifique los miembros de interés de la estructura DCB. Otra
posibilidad es utilizar la función BuildCommDCB con los argumentos: defi-
nición del puerto y estructura DCB. La definición del puerto es una cadena de
caracteres con un formato igual al utilizado por los argumentos de la orden
mode (“com2:9600,n,8,1”). Por lo tanto esta función sólo modifica los miem-
bros velocidad de transmisión, paridad, bits por carácter y bits de parada de la
estructura DCB especificada.
5. Establezca la configuración del puerto. Para realizar esta operación, llame a la
función SetCommState pasando como argumento la estructura DCB, en la
que previamente se almacenó dicha configuración.
6. Cuando quiera enviar datos al puerto de comunicaciones, utilice la función
WriteFile, que tiene los siguientes parámetros: el handle que identifica el
dispositivo de comunicaciones (este valor es devuelto por la función Create-
File), una cadena de caracteres que contiene los caracteres enviados, el núme-
ro de caracteres enviados, un puntero a una variable que almacena el número
de caracteres escritos y un puntero a una estructura OVERLAPPED. La fun-
ción WriteFile devuelve un valor de tipo BOOL; un valor FALSE significa
que ha ocurrido un error.
7. Establezca un proceso que permita estar a la espera de los datos que nuestra
aplicación espera recibir. Cuando se reciban datos, utilice la función Read-
File que tiene los siguientes parámetros: el handle que identifica el dispositi-
vo de comunicaciones, una cadena de caracteres que almacenará los
caracteres recibidos, el número de caracteres a leer, un puntero a una variable
que almacena el número de caracteres leídos y un puntero a una estructura
120 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

OVERLAPPED. La función ReadFile devuelve un valor de tipo BOOL; un
valor FALSE significa que ha ocurrido un error.
8. Cuando ocurre un error durante una operación de comunicaciones, Windows
bloquea el puerto correspondiente, el cual permanecerá bloqueado hasta que
se llame a la función ClearCommError. Los parámetros de esta función son:
un handle al dispositivo de comunicaciones, un puntero a una variable que re-
cibe el código de error y un puntero a una variable que recibe el estado del
dispositivo de comunicaciones.
9. Utilice la función CloseHandle para cerrar el puerto de comunicaciones
cuando éstas finalicen. Si ocurre un error, esta función devuelve un cero; in-
voque a GetLastError si quiere saber de qué error se trata.
Aplicación Win32 para comunicaciones vía RS232
Como ejemplo, cree una nueva aplicación SDI denominada Comm que utilice una
caja de diálogo como ventana principal. Para ello, ejecute AppWizard y haga que
la clase CCommView sea una clase derivada de CFormView.
A continuación, abra el editor de recursos y sitúe sobre la plantilla de diálogo
creada por omisión los controles con las propiedades que se especifican a conti-
nuación:
Objeto Propiedad Valor
Etiqueta ID
Caption
IDC_STATIC
Texto a transmitir:
Caja de texto ID
Multiline
Vertical scroll
Want return
IDC_TX



Etiqueta ID
Caption
IDC_STATIC
Texto recibido:
Caja de texto ID
Multiline
Vertical scroll
Want return
IDC_RX



Botón de pulsación ID
Caption
IDC_ENVIAR
&Enviar

El resultado que obtendrá será similar al mostrado en la figura siguiente:
CAPÍTULO 3: COMUNICACIONES 121


Ejecute ClassWizard y vincule las cajas de texto con las variables miembro
m_tx y m_rx de la clase CCommView, y el botón con la variable m_botonEnviar
miembro de la misma clase.
class CCommView : public CFormView
{
// ...
public:
//{{AFX_DATA(CCommView)
enum { IDD = IDD_COMM_FORM };
CButton m_botonEnviar;
CString m_rx;
CString m_tx;
//}}AFX_DATA
// ...
};
A continuación, modifique la barra de menús con los menús y las órdenes que
se especifican en la tabla siguiente:
Objeto Propiedad Valor
Menú Conexión Caption
Popup
Cone&xión

Orden Establecer ID
Caption
ID_CONEXION_ESTABLECER
&Establecer
Orden Cortar ID
Caption
ID_CONEXION_CORTAR
&Cortar
Separador Separator Sí
Orden Salir ID
Caption
ID_APP_EXIT
&Salir
122 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Menú Configuración Caption
Popup
&Configuración

Orden Parámetros COM ID
Caption
ID_CONFIG_PARAM
&Parámetros COM
Menú Ayuda Caption
Popup
&Ayuda

Orden Acerca de ID
Caption
ID_APP_ABOUT
&Acerca de Comm...

La orden Parámetros COM visualizará una caja de diálogo, que permitirá al
usuario establecer las características bajo las que se realizará la comunicación.
Según esto, vamos a diseñar una caja de diálogo (IDD_PARAMETROSCOM) con
los controles que se indican en la tabla siguiente:
Objeto Propiedad Valor
Etiqueta Caption Puerto:
Lista desplegable ID
Items
IDC_PUERTO
COM1, COM2, COM3, COM4
Etiqueta Caption Baudios:
Lista desplegable ID
Items
IDC_BAUDIOS
300, 600, 1200, 2400, ..., 256000
Etiqueta Caption Paridad:
Lista desplegable ID
Items
IDC_PARIDAD
Ninguna, Par, Impar, Marca, Espacio
Etiqueta Caption Bits por carácter:
Lista desplegable ID
Items
IDC_BITSCAR
4, 5, 6, 7, 8
Etiqueta Caption Bits de parada:
Lista desplegable ID
Items
IDC_BITSPARADA
1, 1.5, 2
Etiqueta Caption Control de flujo:
Lista desplegable ID
Items
IDC_CONTROLFLUJ O
Ninguno, Xon/Xoff, Hardware
(DTR/DSR), Hardware (RTS/CTS)
Botón de pulsación ID
Caption
Default button
IDOK
&Aceptar

Botón de pulsación ID
Caption
IDCANCEL
&Cancelar
Botón de pulsación ID
Caption
IDC_DEFAULT
&Restaurar

CAPÍTULO 3: COMUNICACIONES 123

La caja de diálogo diseñada será similar a la siguiente:

A continuación, desde el editor de recursos, seleccione la caja de diálogo e
invoque a ClassWizard. Esto le permitirá añadir a la aplicación una clase CPa-
ramCom derivada de CDialog, basada en la plantilla IDD_PARAMETROSCOM
que acaba de diseñar. Guarde la declaración y la definición de esta clase en los fi-
cheros paramcom.h y paramcom.cpp. Después, añada a la clase CParamCom las
variables miembro m_nPuerto, m_nBaudios, m_nParidad, m_nBitsCar,
m_nBitsParada y m_nControlFlujo vinculadas a cada una de las listas desplega-
bles correspondientes.
class CParamCom : public CDialog
{
// ...
// Dialog Data
//{{AFX_DATA(CParamCom)
enum { IDD = IDD_PARAMETROSCOM };
int m_nBitsCar;
int m_nBaudios;
int m_nBitsParada;
int m_nControlFlujo;
int m_nPuerto;
int m_nParidad;
//}}AFX_DATA
// ...
};
Registro de Windows
Vamos a almacenar en el registro de Windows las características por omisión bajo
las que se realizará la comunicación. Lo que pretendemos es almacenar la última
configuración utilizada, como configuración por omisión, para la siguiente vez
124 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

que se utilice la aplicación. La función SetRegistryKey de la clase CWinApp
permite almacenar las características de la aplicación en el registro de Windows
con la clave HKEY_CURRENT_USER\Software\... SetRegistryKey es llamada
por la función InitInstance de la aplicación. Tiene un parámetro que permite es-
pecificar el nombre de la clave. Según lo expuesto, abra el fichero comm.cpp y
modifique en la función InitInstance la llamada a SetRegistryKey como se indi-
ca a continuación:
SetRegistryKey(_T("App Comm"));
Cuando posteriormente invoquemos a las funciones miembro GetProfileInt,
WriteProfileInt, GetProfileString, y WriteProfileString de la clase CWinApp,
éstas operaran sobre el registro de Windows en lugar de hacerlo sobre un fichero
con extensión ini.
Interfaz de comunicaciones
Para facilitar la implementación de las comunicaciones vía RS232, vamos a im-
plementar una interfaz con las operaciones más comunes. Según hemos desarro-
llado nuestra aplicación, integraremos esta interfaz que resumimos en la tabla
siguiente, en la clase de la vista:
Función Descripción
Iniciar Lee del registro de Windows la configuración inicial.
Terminar Guarda en el registro de Windows la configuración
actual.
EstablecerConexion Abre el puerto de comunicaciones.
ConfigurarDisCom Establece los parámetros con los que se realizarán las
comunicaciones.
LeerCaracteresPuerto Lee un byte de la cola de entrada del puerto de co-
municaciones.
EscribirCarsPuerto Escribe un byte en la cola de salida del puerto de
comunicaciones.
CortarConexion Cierra el puerto de comunicaciones.
MensajeDeError Convierte un código de error en el mensaje corres-
pondiente.
ControlarEventos Hilo para notificar a la aplicación el evento que ha
ocurrido sobre el puerto de comunicaciones.
CAPÍTULO 3: COMUNICACIONES 125

Añada la declaración de estas funciones y de las variables necesarias para su
implementación a la declaración de la clase CCommView.
#define WM_EVENTO_COM WM_USER + 100 // mensaje de notificación
UINT ControlarEventos(LPVOID p); // hilo

class CCommView : public CFormView
{
// ...

// Operations
public:
/////////////////////////////////////////////////
// Interfaz para comunicaciones
static int m_indPuerto;
static int m_indBaudios;
static int m_indParidad;
static int m_indBitsCar;
static int m_indBitsParada;
static int m_indControlFlujo;

HANDLE m_hDisCom; // handle al dispositivo de comunicaciones
OVERLAPPED m_sOverRead; // utilizada en una entrada asíncrona
OVERLAPPED m_sOverWrite; // utilizada en una salida asíncrona
UINT m_wTablaBaudios[13]; // tabla de velocidades
BYTE m_TablaParidad[5]; // tabla de paridades
BYTE m_TablaBitsParada[3]; // tabla bits de parada
BOOL m_ConexionEstablecida; // TRUE si el puerto fue abierto
BOOL m_bHiloActivo; // TRUE si el hilo está activo

static void Iniciar();
static void Terminar();
BOOL EstablecerConexion();
BOOL ConfigurarDisCom();
BOOL CortarConexion();
int LeerCaracteresPuerto(BYTE *pBytesLeidos, int BloqueMax);
BOOL EscribirCarsPuerto(BYTE *pBytesAEscribir, DWORD dwBytes);
void MensajeDeError(DWORD nError);
/////////////////////////////////////////////////
// ...
};
Las variables m_ind... contienen el índice del elemento seleccionado de las
listas de la caja de diálogo Configuración.
A continuación escribiremos cada una de las funciones descritas. Antes, inicie
las variables estáticas y defina las cadenas de caracteres necesarias para el registro
de Windows en la implementación de la clase CCommView.
126 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

int CCommView::m_indPuerto = 1; // COM2
int CCommView::m_indBaudios = 6; // 9600
int CCommView::m_indParidad = 0; // ninguna
int CCommView::m_indBitsCar = 4; // 8
int CCommView::m_indBitsParada = 0; // 1
int CCommView::m_indControlFlujo = 1; // Xon/Xoff

static char szComu[] = "Comunicaciones";
static char szPuerto[] = "Puerto";
static char szBaudios[] = "Baudios";
static char szParidad[] = "Paridad";
static char szBitsCar[] = "BitsCar";
static char szBitsParada[] = "BitsParada";
static char szControlFlujo[] = "ControlFlujo";
Finalmente, inicie la variable m_hDisCom a NULL y las variables m_Cone-
xionEstablecida y m_bHiloActivo a FALSE, en el constructor de su clase.
Función Iniciar
La función Iniciar obtiene del registro de Windows la configuración por omisión
del dispositivo de comunicaciones.
void CCommView::Iniciar()
{
CWinApp *pApp= AfxGetApp();
// Recuperar configuración del registro de Windows
m_indPuerto = pApp->GetProfileInt(szComu, szPuerto, 1);
m_indBaudios = pApp->GetProfileInt(szComu, szBaudios, 6);
m_indParidad = pApp->GetProfileInt(szComu, szParidad, 0);
m_indBitsCar = pApp->GetProfileInt(szComu, szBitsCar, 4);
m_indBitsParada = pApp->GetProfileInt(szComu, szBitsParada, 0);
m_indControlFlujo = pApp->GetProfileInt(szComu, szControlFlujo, 1);
}
La función GetProfileInt de la clase CWinApp recupera del registro de
Windows el entero asociado con la cadena sz... (segundo argumento) correspon-
diente a la sección szComu de la clave especificada por la función SetRegistry-
Key. Si la entrada especificada por sz... no se encuentra, la función devuelve el
valor especificado por el argumento tercero.
Función Terminar
La función Terminar será invocada cuando se corta la comunicación para guardar
en el registro de Windows la configuración actual del dispositivo de comunica-
ciones.
CAPÍTULO 3: COMUNICACIONES 127

void CCommView::Terminar()
{
CWinApp *pApp= AfxGetApp();
// Guardar configuración en el registro de Windows
pApp->WriteProfileInt(szComu, szPuerto, m_indPuerto);
pApp->WriteProfileInt(szComu, szBaudios, m_indBaudios);
pApp->WriteProfileInt(szComu, szParidad, m_indParidad);
pApp->WriteProfileInt(szComu, szBitsCar, m_indBitsCar);
pApp->WriteProfileInt(szComu, szBitsParada, m_indBitsParada);
pApp->WriteProfileInt(szComu, szControlFlujo, m_indControlFlujo);
}
La función WriteProfileInt de la clase CWinApp guarda en el registro de
Windows el entero especificado por el argumento tercero, asociado con la cadena
sz... (segundo argumento), en la sección szComu de la clave especificada por la
función SetRegistryKey.
Función EstablecerConexion
La función EstablecerConexion permite abrir el puerto de comunicaciones especi-
ficado por la variable miembro m_indPuerto. Para ello:
1. Invoca a la función CreateFile para abrir el puerto de comunicaciones. Hay
dos formas de abrir un puerto de comunicaciones: solapada (overlapped) y no
solapada (nonoverlapped). La documentación del SDK de Win32 utiliza los
términos asíncrono y síncrono. Un puerto abierto para operaciones solapadas
permite múltiples hilos realizando operaciones de E/S (ReadFile o WriteFi-
le) simultáneas, así como ejecutar otra tarea mientras las operaciones estén
pendientes. Además, el comportamiento de las operaciones solapadas permite
a un único hilo realizar peticiones diferentes y ejecutar tareas en segundo pla-
no mientras las operaciones estén pendientes. Si el puerto se abre para opera-
ciones no solapadas, el hilo queda bloqueado mientras la operación de E/S
solicitada no esté completada; una vez completada, el hilo puede seguir traba-
jando. En este caso, si un hilo está bloqueado esperando a que su operación de
E/S finalice, cualquier otro hilo que requiera una operación de E/S quedará
bloqueado. Esta última forma de abrir un puerto es útil cuando el sistema ope-
rativo no soporta operaciones de E/S solapadas. El código siguiente indica la
forma apropiada de abrir un puerto de comunicaciones de forma solapada:
m_hDisCom = CreateFile(szPuerto,
GENERIC_READ | GENERIC_WRITE,
0, // acceso exclusivo
NULL, // sin atributos de seguridad
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
128 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Cuando se especifica el parámetro FILE_FLAG_OVERLAPPED, las funcio-
nes ReadFile y WriteFile deben especificar una estructura OVERLAPPED.
2. Invoca a la función SetCommMask para especificar los eventos que serán
atendidos. Por ejemplo:
SetCommMask( m_hDisCom, EV_RXCHAR | EV_TXEMPTY |
EV_RX80FULL | EV_ERR);
El parámetro m_hDisCom es un handle al dispositivo de comunicaciones de-
vuelto por la función CreateFile. El otro parámetro especifica los eventos que
son habilitados. Un valor cero inhabilita todos los eventos. Por ejemplo,
EV_RXCHAR se produce cuando se recibe un carácter en la cola de entrada;
EV_TXEMPTY se produce cuando se envía el último carácter de la cola de sa-
lida; EV_RX80FULL se produce cuando la cola de entrada está llena al 80%;
EV_ERR sucede cuando se produce alguno de los siguientes errores:
CE_FRAME, CE_OVERRUN, o CE_RXPARITY.
3. Invoca a la función SetupComm para especificar el tamaño en bytes de las
colas de recepción y de transmisión.
SetupComm(m_hDisCom, COLARX, COLATX);
4. Invoca a la función PurgeComm para terminar las operaciones de lectura y
escritura pendientes y limpiar las colas de recepción y de transmisión.
PurgeComm( m_hDisCom, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR );
5. Construye una estructura DCB e invoca a la función SetCommState para
configurar el dispositivo de comunicaciones con los valores almacenados en
dicha estructura.
6. Invoca a la función SetCommTimeouts para establecer los tiempos límites
para las operaciones de recepción y transmisión.
7. Si la comunicación va a ser controlada por eventos, lanza un hilo dedicado a
controlar cada evento de interés que se produzca en el puerto de comunica-
ciones.
8. Invoca a la función EscapeCommFunction para activar la señal DTR mien-
tras dure la conexión.
BOOL CCommView::EstablecerConexion()
{
CAPÍTULO 3: COMUNICACIONES 129

char szPuerto[10];
BOOL bExito = FALSE;

// Formar la cadena "COM" más el número de dispositivo
wsprintf(szPuerto, "COM%d", m_indPuerto + 1);

// Cerrar el puerto si estuviera abierto
if (m_hDisCom) CloseHandle(m_hDisCom);

// Abrir el puerto de comunicaciones
m_hDisCom = CreateFile(szPuerto,
GENERIC_READ | GENERIC_WRITE,
0, // acceso exclusivo
NULL, // sin atributos de seguridad
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);

if( m_hDisCom == INVALID_HANDLE_VALUE )
{
// Visualizar el error ocurrido.
MensajeDeError( GetLastError() );
MessageBeep(0xFFFFFFFF);
return FALSE;
}

// Especificar los eventos que serán atendidos
SetCommMask( m_hDisCom, EV_RXCHAR | EV_TXEMPTY | EV_RX80FULL | EV_ERR);

// Establecer el tamaño de las colas de recepción y de transmisión
SetupComm(m_hDisCom, COLARX, COLATX);

// Terminar las operaciones de lectura y escritura pendientes
// y limpiar las colas Rx y Tx
PurgeComm( m_hDisCom, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR );

// Establecer los parámetros de la comunicación
bExito = ConfigurarDisCom();

if ( bExito )
{
m_ConexionEstablecida = TRUE;
// Crear un hilo secundario para ver qué evento ocurre
if ( AfxBeginThread(ControlarEventos, this) == NULL )
{
AfxMessageBox( "Error: No se puede iniciar el hilo",
MB_OK | MB_ICONEXCLAMATION );
m_ConexionEstablecida = FALSE;
CloseHandle(m_hDisCom);
return FALSE;
130 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

}
m_bHiloActivo = TRUE;
// Enviar la señal DTR (data-terminal-ready).
EscapeCommFunction(m_hDisCom, SETDTR);
}
else
{
AfxMessageBox( "Error: No se puede configurar el dispositivo",
MB_OK | MB_ICONEXCLAMATION );
m_ConexionEstablecida = FALSE;
CloseHandle(m_hDisCom);
}
return bExito;
}
Defina las constantes COLARX y COLATX, que definen los tamaños de las
colas de recepción y transmisión respectivamente, en el fichero commview.cpp:
#define COLARX 4096
#define COLATX 4096
Función MensajeDeError
La función MensajeDeError convierte un código de error en el correspondiente
mensaje obtenido del sistema, y visualiza un diálogo con dicho mensaje. La con-
versión la hace invocando a la función FormatMessage de la API, que obtiene el
mensaje de la tabla de mensajes del sistema y lo almacena en un buffer en memo-
ria que crea automáticamente invocando a LocalAlloc. Una vez visualizado el
mensaje, la función MensajeDeError invoca a LocalFree para liberar el buffer
asignado por LocalAlloc desde FormatMessage (para obtener más información
sobre esta función, consulte la ayuda en línea).
void CCommView::MensajeDeError( DWORD nError )
{
LPVOID lpMsg;

::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
nError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsg,
0,
NULL
);

// Mostrar el error
AfxMessageBox( (LPCTSTR)lpMsg, MB_OK | MB_ICONEXCLAMATION );
CAPÍTULO 3: COMUNICACIONES 131


// Liberar el buffer
::LocalFree( lpMsg );
}
Función ConfigurarDisCom
Para construir la estructura DCB y configurar el dispositivo de comunicaciones
así como para establecer los tiempos límites para las operaciones de recepción y
de transmisión, EstablecerConexion invoca a la función ConfigurarDisCom.
BOOL CCommView::ConfigurarDisCom()
{
BYTE bEstablecer;
DCB dcb;

dcb.DCBlength = sizeof(DCB);
GetCommState(m_hDisCom, &dcb);

dcb.BaudRate = m_wTablaBaudios[m_indBaudios];
dcb.Parity = m_TablaParidad[m_indParidad];
dcb.ByteSize = (BYTE)(m_indBitsCar + 4);
dcb.StopBits = m_TablaBitsParada[m_indBitsParada];

// Establecer el control de flujo software
bEstablecer = (BYTE)(m_indControlFlujo == 1); // Xon/Xoff
dcb.fInX = dcb.fOutX = bEstablecer;
dcb.XonChar = 0x11; // ASCII_XON
dcb.XoffChar = 0x13; // ASCII_XOFF
dcb.XonLim = 100;
dcb.XoffLim = 100;

// Establecer el control de flujo hardware
bEstablecer = (BYTE)(m_indControlFlujo == 2); // DTR/DSR
dcb.fOutxDsrFlow = bEstablecer;
if (bEstablecer)
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
else
dcb.fDtrControl = DTR_CONTROL_ENABLE;

bEstablecer = (BYTE)(m_indControlFlujo == 3); // RTS/CTS
dcb.fOutxCtsFlow = bEstablecer;
if (bEstablecer)
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
else
dcb.fRtsControl = RTS_CONTROL_ENABLE;

// Otras especificaciones
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
132 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32


if(SetCommState(m_hDisCom, &dcb) == 0)
{
// Visualizar el error ocurrido.
MensajeDeError( GetLastError() );
MessageBeep(0xFFFFFFFF);
return FALSE;
}

// Establecer los tiempos límites para las operaciones de E/S
COMMTIMEOUTS CommTimeOuts;

CommTimeOuts.ReadIntervalTimeout = MAXDWORD;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
CommTimeOuts.ReadTotalTimeoutConstant = 1000;
// CBR_9600 es aproximadamente 1 byte/ms. Para nuestros
// propósitos permitiremos un tiempo de espera por carácter
// doble al necesario
CommTimeOuts.WriteTotalTimeoutMultiplier = 2*CBR_9600/dcb.BaudRate;
CommTimeOuts.WriteTotalTimeoutConstant = 0;

SetCommTimeouts( m_hDisCom, &CommTimeOuts);

return TRUE;
}
La iniciación de un puerto se hace llamando a la función de la API de Win-
dows SetCommState. El primer argumento hace referencia al puerto de comuni-
caciones y el segundo a una estructura de tipo DCB que almacena la
configuración del puerto. DCB está definido en windows.h y recoge todos los
parámetros relacionados con la configuración de un puerto.
Si observa la definición de la función ConfigurarDisCom, verá que es necesa-
rio asignar valores a los arrays m_wTablaBaudios, m_TablaParidad y m_Tabla-
BitsParada miembros de la clase CCommView. Realice esta asignación en el
constructor de su clase, como se indica a continuación:
CCommView::CCommView()
: CFormView(CCommView::IDD)
{
m_wTablaBaudios[0] = CBR_110;
m_wTablaBaudios[1] = CBR_300;
m_wTablaBaudios[2] = CBR_600;
m_wTablaBaudios[3] = CBR_1200;
m_wTablaBaudios[4] = CBR_2400;
m_wTablaBaudios[5] = CBR_4800;
m_wTablaBaudios[6] = CBR_9600;
m_wTablaBaudios[7] = CBR_14400;
m_wTablaBaudios[8] = CBR_19200;
CAPÍTULO 3: COMUNICACIONES 133

m_wTablaBaudios[9] = CBR_38400;
m_wTablaBaudios[10] = CBR_56000;
m_wTablaBaudios[11] = CBR_128000;
m_wTablaBaudios[12] = CBR_256000;

m_TablaParidad[0] = NOPARITY;
m_TablaParidad[1] = EVENPARITY;
m_TablaParidad[2] = ODDPARITY;
m_TablaParidad[3] = MARKPARITY;
m_TablaParidad[4] = SPACEPARITY;

m_TablaBitsParada[0] = ONESTOPBIT;
m_TablaBitsParada[1] = ONE5STOPBITS;
m_TablaBitsParada[2] = TWOSTOPBITS;

//{{AFX_DATA_INIT(CCommView)
m_rx = _T("");
m_tx = _T("");
//}}AFX_DATA_INIT
// TODO: add construction code here
m_hDisCom = NULL;
m_pHiloEv = NULL;
}
Finalmente hemos establecido los tiempos límite invocando a la función Set-
CommTimeouts. El primer argumento hace referencia al puerto de comunicacio-
nes y el segundo a una estructura COMMTIMEOUTS que almacena los tiempos
límites. El significado de cada uno de estos miembros se expone a continuación.
ReadIntervalTimeout especifica el tiempo máximo, en milisegundos, que
puede transcurrir entre la llegada de dos caracteres en la línea de comunicaciones.
Durante una operación ReadFile, el lapso de tiempo empieza cuando se recibe el
primer carácter. Si el intervalo de tiempo entre la llegada de dos caracteres cua-
lesquiera excede esta cantidad, la operación ReadFile se da por finalizada devol-
viendo cualquier carácter que haya en la cola de entrada. Un valor cero indica que
no se usan tiempos límites.
Un valor MAXDWORD, combinado con valores cero para los miembros Re-
adTotalTimeoutConstant y ReadTotalTimeoutMultiplier, especifica que la opera-
ción de lectura tiene que retornar inmediatamente con los caracteres que ya se han
recibido, aun cuando no se haya recibido ningún carácter.
ReadTotalTimeoutMultiplier especifica el multiplicador, en milisegundos, uti-
lizado para calcular el tiempo límite total para las operaciones de lectura. Para ca-
da operación de lectura, este valor es multiplicado por el número de bytes que se
quieren leer.
134 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

ReadTotalTimeoutConstant especifica la constante, en milisegundos, utilizada
para calcular el tiempo límite total para las operaciones de lectura. Para cada ope-
ración de lectura, este valor se agrega al producto de ReadTotalTimeoutMultiplier
por el número de bytes solicitados. Un valor cero para ReadTotalTimeoutMulti-
plier y ReadTotalTimeoutConstant indican que el tiempo límite total no se utili-
zará para operaciones de lectura.
WriteTotalTimeoutMultiplier especifica el multiplicador, en milisegundos,
utilizado para calcular el tiempo límite total para las operaciones de escritura. Para
cada operación de escritura, este valor es multiplicado por el número de bytes que
se quieren escribir.
WriteTotalTimeoutConstant especifica la constante, en milisegundos, utiliza-
da para calcular el tiempo límite total para operaciones de escritura. Para cada
operación de escritura, este valor se agrega al producto de WriteTotalTimeoutMul-
tiplier por el número de bytes escritos. Un valor cero para WriteTotalTimeoutMul-
tiplier y WriteTotalTimeoutConstant indica que el tiempo límite total no se
utilizará para operaciones de escritura.
Si una aplicación pone ReadIntervalTimeout y ReadTotalTimeoutMultiplier a
MAXDWORD y ReadTotalTimeoutConstant a un valor mayor que cero y menor
que MAXDWORD, cuando la función ReadFile sea invocada, puede ocurrir que:
• Si hay caracteres en la cola de entrada, ReadFile retorna inmediatamente con
los caracteres de la cola.
• Si no hay caracteres en la cola de entrada, ReadFile espera hasta que llegue
un carácter y entonces retorna inmediatamente.
• Si ningún carácter llega dentro del tiempo especificado por ReadTotalTime-
outConstant, ReadFile retornará pasado el tiempo límite.
Controlar eventos
Vimos que la función EstablecerConexion, después que abre el puerto de comuni-
caciones, lanza un hilo ControlarEventos. El hilo controlará los eventos que ocu-
rren sobre el puerto invocando a la función WaitCommEvent. Esta función
informa del evento ocurrido sobre el dispositivo de comunicaciones a través de su
segundo argumento. Los eventos a controlar fueron establecidos por la función
SetCommMask. También puede utilizar GetCommMask para ver los eventos
que fueron establecidos.
DWORD dwMascEvt;
OVERLAPPED sOver = {0, 0, 0, 0, 0};
CAPÍTULO 3: COMUNICACIONES 135


sOver.hEvent = CreateEvent( NULL, // sin seguridad
TRUE, // iniciación manual
FALSE, // inicialmente ocupado
NULL ); // sin nombre
// ...
WaitCommEvent( m_hDisCom, &dwMascEvt, &sOver );
// ...
Si un proceso intenta cambiar la máscara de eventos del dispositivo utilizando
SetCommMask mientras una operación WaitCommEvent está en curso, Wait-
CommEvent retorna inmediatamente y la variable dwMascEvt es puesta a 0.
Si m_hDisCom no se abriera con FILE_FLAG_OVERLAPPED, WaitCom-
mEvent no retorna hasta que ocurra uno de los eventos especificados, o un error.
Si m_hDisCom se abre con FILE_FLAG_OVERLAPPED, el parámetro terce-
ro de WaitCommEvent no debe ser NULL. Debe apuntar a una estructura
OVERLAPPED válida que contenga un handle a un evento con iniciación ma-
nual. Si fuera NULL, la función puede informar incorrectamente de que la opera-
ción está finalizada.
En este último caso, si una operación de E/S solapada no puede completarse
inmediatamente, la función WaitCommEvent devuelve FALSE y la función Ge-
tLastError devuelve ERROR_IO_PENDING, indicando que la operación se está
ejecutando en segundo plano. Cuando esto ocurre, el sistema pone el objeto refe-
renciado por el miembro hEvent de la estructura OVERLAPPED en estado ocu-
pado antes de que WaitCommEvent retorne. Cuando posteriormente ocurra un
evento de los especificados o un error, el sistema liberará dicho objeto. El proceso
que llama puede utilizar una de las funciones de espera (WaitForSingleObject,
WaitForMultipleObjects, etc.) para determinar el estado del objeto evento y
después invocar a GetOverlappedResult para determinar los resultados de la
operación WaitCommEvent. GetOverlappedResult informa del éxito o fracaso
de la operación, y la variable dwMascEvt indica el evento que ocurrió.
UINT ControlarEventos(LPVOID p)
{
DWORD dwMascEvt;
CCommView *const pView = (CCommView *)p;
OVERLAPPED sOver = {0, 0, 0, 0, 0};

// Crear un evento de E/S utilizado para lecturas solapadas
sOver.hEvent = CreateEvent( NULL, // sin seguridad
TRUE, // iniciación manual
FALSE, // inicialmente ocupado
NULL ); // sin nombre
136 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

if (sOver.hEvent == NULL)
{
AfxMessageBox( "Fallo al crear el evento para el hilo",
MB_OK | MB_ICONEXCLAMATION );
return 0;
}

// Restablecer los eventos por si hubieran cambiado
if (!SetCommMask( pView->m_hDisCom, EV_RXCHAR | EV_TXEMPTY |
EV_RX80FULL | EV_ERR ))
return 0;

while ( pView->m_ConexionEstablecida )
{
dwMascEvt = 0;
WaitCommEvent( pView->m_hDisCom, &dwMascEvt, &sOver );
if ((dwMascEvt & EV_RXCHAR) == EV_RXCHAR)
::PostMessage(pView->m_hWnd, WM_EVENTO_COM, EV_RXCHAR, 0);
else if ((dwMascEvt & EV_TXEMPTY) == EV_TXEMPTY)
::PostMessage(pView->m_hWnd, WM_EVENTO_COM, EV_TXEMPTY, 0);
else if ((dwMascEvt & EV_RX80FULL) == EV_RX80FULL)
::PostMessage(pView->m_hWnd, WM_EVENTO_COM, EV_RX80FULL, 0);
else if ((dwMascEvt & EV_ERR) == EV_ERR)
::PostMessage(pView->m_hWnd, WM_EVENTO_COM, EV_ERR, 0);
}
pView->m_bHiloActivo = FALSE;
// Liberar el manipulador del evento
CloseHandle( sOver.hEvent );
return 1;
}
Observe que cuando ocurre un evento sobre el dispositivo de comunicaciones,
el hilo secundario se lo notifica al hilo principal (a la aplicación) enviándole un
mensaje WM_EVENTO_COM. Este mensaje fue definido anteriormente en el fi-
chero CommView.h. La información que acompaña a dicho mensaje permite co-
nocer a la aplicación cuál fue el evento ocurrido. Por lo tanto, lo siguiente es
añadir dicho mensaje al mapa de mensajes de la vista. Para ello, abra el fichero
CommView.cpp, localice el mapa de mensajes y añada la línea que se muestra a
continuación:
BEGIN_MESSAGE_MAP(CCommView, CFormView)
//{{AFX_MSG_MAP(CCommView)
// ...
//}}AFX_MSG_MAP
ON_MESSAGE(WM_EVENTO_COM, OnEventoCom)
END_MESSAGE_MAP()
En el capítulo de hilos vimos las posibles formas de comunicación entre hilos.
No es correcto invocar desde un hilo a una función miembro de una clase de la
CAPÍTULO 3: COMUNICACIONES 137

aplicación. Por ejemplo, si procede de la forma siguiente, tendrá problemas, ya
que un hilo, en nuestro caso ControlarEventos, además de no ser un miembro de
una clase de la aplicación, se ejecuta con concurrentemente con ella (con el hilo
principal):
if ((dwMascEvt & EV_RXCHAR) == EV_RXCHAR)
nBytes = pView->LeerCaracteresPuerto( BytesLeidos, BLOQUEMAX );
La respuesta al mensaje WM_EVENTO_COM es la función OnEventoCom,
que realizará un proceso u otro en función del evento ocurrido. Por ejemplo, si el
evento es que se recibió información en la cola de recepción del dispositivo de
comunicaciones, esta función leerá dicha información y la procesará; en nuestro
caso la visualizará en la caja de texto correspondiente. Por lo tanto, añada esta
función a la clase CCommView y edítela como se indica a continuación:
long CCommView::OnEventoCom(UINT wParam, long lParam)
{
BYTE BytesLeidos[BLOQUEMAX + 1];
int nBytes;

// Mensajes recibidos desde el hilo
switch (wParam)
{
case EV_RXCHAR:
if (nBytes = LeerCaracteresPuerto( BytesLeidos, BLOQUEMAX ))
OnVisualizarCars( BytesLeidos, nBytes );
break;
case EV_TXEMPTY:
// ...
break;
case EV_RX80FULL:
// ...
break;
case EV_ERR:
// ...
break;
}
return 0;
}
Vemos que si el evento que se produjo fue EV_RXCHAR, la función OnEven-
toCom invoca primero a la función LeerCaracteresPuerto para leer los caracteres
recibidos y después a OnVisualizarCars para añadir dicha información a la caja
de texto referenciada por m_rx de la interfaz de usuario.
138 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Función LeerCaracteresPuerto
La función LeerCaracteresPuerto lee un bloque del puerto COM y lo almacena
en un array. El primer parámetro es el array donde se almacenarán los bytes leídos
y el segundo es el número máximo de bytes que se van a leer.
int LeerCaracteresPuerto(BYTE *pBytesLeidos, int BloqueMax );
Defina la constante BLOQUEMAX en el fichero CommView.cpp, así:
#define BLOQUEMAX 80
La función LeerCaracteresPuerto leerá como máximo BLOQUEMAX carac-
teres. Para ello invocará a la función ReadFile.
bLeer = ReadFile( m_hDisCom, pBytesLeidos, dwNumBytes,
&dwNumBytes, &m_sOverRead );
pBytesLeidos[dwNumBytes] = 0; // finalizar con el carácter nulo
La función ReadFile lee dwNumBytes caracteres del dispositivo de comuni-
caciones referenciado por m_hDisCom y los almacena en el array pBytesLeidos.
Si m_hDisCom se abrió con FILE_FLAG_OVERLAPPED, el último paráme-
tro de ReadFile no debe ser NULL; debe apuntar a una estructura OVERLAP-
PED válida que contenga un handle a un evento con iniciación manual. En este
caso, ReadFile puede retornar antes de que la operación de lectura se haya com-
pletado, en cuyo caso devolverá FALSE y la función GetLastError
ERROR_IO_PENDING. Esto permite continuar la ejecución del proceso que hizo
la llamada mientras la operación de lectura finaliza. Cuando la operación finaliza,
el evento especificado en la estructura OVERLAPPED, que está ocupado, se po-
ne en el estado libre. Para obtener información del éxito o fracaso de la operación,
podemos invocar a la función GetOverlappedResult; si esta función devuelve
cero significa que la operación no se ha completado o que ha fracaso. Para obtener
información de lo ocurrido, podemos invocar a la función GetLastError.
Si el último parámetro de ReadFile fuera NULL, la función puede informar
incorrectamente de que la operación está finalizada.
La información obtenida a través de la función GetOverlappedResult co-
rresponde a la última operación solapada sobre el dispositivo especificado, para la
cual fue proporcionada la estructura OVERLAPPED especificada, y para la que
los resultados de la operación estaban pendientes.
if (!bLeer)
{
CAPÍTULO 3: COMUNICACIONES 139

if (GetLastError() == ERROR_IO_PENDING)
{
while(!GetOverlappedResult( m_hDisCom, &m_sOverRead,
&dwNumBytes, FALSE ))
{
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE)
// ...
}
Si la función que inicia la operación devuelve FALSE y GetLastError de-
vuelve ERROR_IO_PENDING, es porque esa operación queda pendiente. Cuan-
do una operación de E/S está pendiente, la función que inició la operación pone el
evento referenciado por el miembro hEvent de la estructura OVERLAPPED en el
estado ocupado. Y cuando la operación pendiente finaliza, el sistema pone el ob-
jeto evento en el estado libre.
Si el último parámetro de GetOverlappedResult es TRUE, la función no re-
torna hasta que la operación pendiente finalice. También, si posteriormente ocurre
un evento de los especificados o un error, el sistema liberará el objeto evento. Si
es FALSE y la operación está todavía pendiente, la función devuelve FALSE y
GetLastError devuelve ERROR_IO_INCOMPLETE.
int CCommView::LeerCaracteresPuerto(BYTE *pBytesLeidos, int Bloque-
Max)
{
BOOL bLeer;
COMSTAT EstadoCom;
DWORD dwCodsError;
DWORD dwNumBytes;
DWORD dwError;
char szError[10];

// Leer cada vez como máximo BloqueMax caracteres
ClearCommError( m_hDisCom, &dwCodsError, &EstadoCom );
dwNumBytes = min( (DWORD)BloqueMax, EstadoCom.cbInQue );

if (dwNumBytes > 0)
{
bLeer = ReadFile( m_hDisCom, pBytesLeidos, dwNumBytes,
&dwNumBytes, &m_sOverRead );
pBytesLeidos[dwNumBytes] = 0; // finalizar con el carácter nulo

if (!bLeer)
{
if (GetLastError() == ERROR_IO_PENDING)
{
// Tenemos que esperar a que la lectura se complete. Esta
140 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

// función será interrumpida cuando transcurra un tiempo
// CommTimeOuts.ReadTotalTimeoutConstant.

// Chequear errores en el puerto
while(!GetOverlappedResult( m_hDisCom,
&m_sOverRead, &dwNumBytes, FALSE ))
{
dwError = GetLastError();
// Si no terminó, dwError vale ERROR_IO_INCOMPLETE
if(dwError == ERROR_IO_INCOMPLETE)
continue;
else
{
// Ocurrió un error, intentar recuperarlo
MensajeDeError( dwError );
ClearCommError( m_hDisCom, &dwCodsError, &EstadoCom );
if ( dwCodsError > 0 )
{
wsprintf( szError, "Com: <CE-%u>", dwCodsError );
// Los errores IE son < 0 (winbase.h)
AfxMessageBox( szError, MB_OK | MB_ICONEXCLAMATION );
}
break;
}
} // fin while
}
else // error distinto de ERROR_IO_PENDING
{
dwNumBytes = 0;
ClearCommError( m_hDisCom, &dwCodsError, &EstadoCom );
if ( dwCodsError > 0 )
{
wsprintf( szError, "Com: <CE-%u>", dwCodsError );
AfxMessageBox( szError, MB_OK | MB_ICONEXCLAMATION );
}
}
}
}
return ( dwNumBytes );
}
La función ClearCommError obtiene información sobre el error de comuni-
caciones ocurrido y sobre el estado del dispositivo de comunicaciones. Asimismo,
desactiva cualquier indicador de error que haya sido activado para habilitar opera-
ciones E/S adicionales.
CAPÍTULO 3: COMUNICACIONES 141

Función EscribirCarsPuerto
La función EscribirCarsPuerto escribe un bloque en el puerto COM procedente
de un array pasado como parámetro. El primer parámetro es el array donde se al-
macenarán los bytes leídos y el segundo es el número de bytes que se quieren es-
cribir. Las explicaciones con respecto a algunas de las funciones utilizadas ya han
sido descritas en apartados anteriores.
BOOL CCommView::EscribirCarsPuerto(BYTE *pBytesAEscribir, DWORD dwBytes)
{
COMSTAT EstadoCom;
BOOL bEscribir;
DWORD dwNumBytes;
DWORD dwCodsError;
DWORD dwError;
DWORD dwBytesEnviados=0;
char szError[128];

bEscribir = WriteFile( m_hDisCom, pBytesAEscribir, dwBytes,
&dwNumBytes, &m_sOverWrite );

// Normalmente el código siguiente no se ejecutará porque
// el driver cachea las operaciones de escritura. Por lo
// tanto, pequeñas peticiones de E/S (hasta algunos cientos
// de bytes) serán normalmente aceptadas inmediatamente
// y WriteFile devolverá TRUE aunque el puerto de
// comunicaciones permita operaciones solapadas.

if (!bEscribir)
{
if( GetLastError() == ERROR_IO_PENDING )
{
// Debemos esperar a que la operación de escritura termine
// para conocer el éxito o no de la misma.

// Podría ser beneficioso colocar la operación de escritura
// en un hilo separado para que un bloqueo durante la
// realización de dicha operación no afecte negativamente
// la sensibilidad de la interfaz de usuario.

// Si la operación de escritura tarda demasiado en finalizar,
// esta función será interrumpida cuando transcurra un tiempo
// CommTimeOuts.WriteTotalTimeoutMultiplier. Este código toma
// nota de la interrupción pero no reintenta la escritura.


while(!GetOverlappedResult( m_hDisCom,
&m_sOverWrite, &dwNumBytes, FALSE ))
{
142 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

dwError = GetLastError();
// Si no terminó, dwError vale ERROR_IO_INCOMPLETE
if(dwError == ERROR_IO_INCOMPLETE)
{
dwBytesEnviados += dwNumBytes;
continue;
}
else
{
// ocurrió un error, intentar recuperarlo
MensajeDeError( dwError );
ClearCommError( m_hDisCom, &dwCodsError, &EstadoCom );
if ( dwCodsError > 0 )
{
wsprintf( szError, "Com: <CE-%u>", dwCodsError );
// Los errores IE son < 0 (winbase.h)
AfxMessageBox( szError, MB_OK | MB_ICONEXCLAMATION );
}
break;
}
}
dwBytesEnviados += dwNumBytes;
if( dwBytesEnviados != dwBytes )
wsprintf(szError,"\nProbablemente se ha sobrepasado el "
"tiempo límite.\nBytes enviados %ld", dwBytesEnviados);
else
wsprintf(szError,"\n%ld bytes escritos", dwBytesEnviados);
}
else // error distinto de ERROR_IO_PENDING
{
ClearCommError( m_hDisCom, &dwCodsError, &EstadoCom );
if ( dwCodsError > 0 )
{
wsprintf( szError, "Com: <CE-%u>", dwCodsError );
AfxMessageBox( szError, MB_OK | MB_ICONEXCLAMATION );
}
return FALSE;
}
}
return TRUE;
}
Función CortarConexion
La función CortarConexion cierra el puerto de comunicaciones. Para ello, pone la
variable m_ConexionEstablecida a valor FALSE para que el hilo ControlarEven-
tos finalice, inhabilita los eventos, desactiva la línea DTR, termina las operaciones
de E/S, limpia las colas de recepción y de transmisión, y cierra el puerto de comu-
nicaciones.
CAPÍTULO 3: COMUNICACIONES 143

BOOL CCommView::CortarConexion()
{
m_ConexionEstablecida = FALSE; // finaliza el hilo

// Inhabilitar todos los eventos
SetCommMask( m_hDisCom, 0 );

// Esperar hasta que el hilo finalice
while( m_bHiloActivo );

// Desactivar DTR
EscapeCommFunction( m_hDisCom, CLRDTR );

// Terminar las operaciones de lectura y escritura pendientes
// y limpiar las colas Rx y Tx
PurgeComm( m_hDisCom, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR );

CloseHandle( m_hDisCom );
m_hDisCom = NULL;

return TRUE;
}
INTERFAZ DEL USUARIO
Cuando se ejecute la aplicación, una de las primeras tareas que hay que realizar es
leer la configuración almacenada en el registro de Windows. Este proceso lo rea-
lizaremos desde la función OnInitialUpdate de la clase CCommView.
void CCommView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

// Ajustar el tamaño de la ventana marco a la vista
ResizeParentToFit( FALSE );

// Iniciar el puerto y los controles de la IU
UpdateData(FALSE);
m_botonEnviar.EnableWindow(FALSE);
Iniciar(); // configuración inicial del puerto COM
}
Asimismo, la función OnInitialUpdate, además de ajustar el marco de la
ventana al tamaño de la vista, inhabilita el botón Enviar.
Cuando se establece una conexión entre dos equipos, previamente hay que
especificar los siguientes parámetros: puerto (COM1, COM2, COM3, COM4),
144 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

baudios (110, 300, 600, 1200, 2400, 4800, 9600, ...), paridad (par, impar, ningu-
na), bits de datos (normalmente 7 u 8 bits por carácter), bits de parada (1 o 2) y
control de flujo (Xon/Xoff, hardware, ninguna).
Para establecer los parámetros anteriormente especificados la aplicación pro-
porciona la orden Parámetros COM. Cuando el usuario ejecute esta orden se vi-
sualizar la caja de diálogo Configuración que le permitirá establecer los
parámetros con los que se realizarán las comunicaciones. La respuesta a esta ac-
ción del usuario será la función OnConfigParam. Ejecute ClassWizard, añada esta
función y edítela como se indica a continuación:
void CCommView::OnConfigParam()
{
// Crea el objeto de diálogo
CParamCom dlg;

// Actualizar los datos del diálogo
dlg.m_nPuerto = m_indPuerto;
dlg.m_nBaudios = m_indBaudios;
dlg.m_nParidad = m_indParidad;
dlg.m_nBitsCar = m_indBitsCar;
dlg.m_nBitsParada = m_indBitsParada;
dlg.m_nControlFlujo = m_indControlFlujo;

// Mostrar el cuadro de diálogo y verificar el botón pulsado
if (dlg.DoModal() != IDOK) return;

// Actualizar la configuración
m_indPuerto = dlg.m_nPuerto;
m_indBaudios = dlg.m_nBaudios;
m_indParidad = dlg.m_nParidad;
m_indBitsCar = dlg.m_nBitsCar;
m_indBitsParada = dlg.m_nBitsParada;
m_indControlFlujo = dlg.m_nControlFlujo;

if( EstablecerConexion() )
{
m_ConexionEstablecida = TRUE;
AfxMessageBox( "Puerto de comunicaciones abierto",
MB_OK | MB_ICONEXCLAMATION );
m_botonEnviar.EnableWindow(TRUE);
}
}
Una vez visualizada la caja de diálogo Configuración, si el usuario hace clic
en el botón Aceptar (IDOK) se actualizarán los parámetros de configuración con
los valores seleccionados y se invocará a la función EstablecerConexion para
abrir el puerto de comunicaciones especificado. Si este proceso se ejecuta satisfac-
CAPÍTULO 3: COMUNICACIONES 145

toriamente, ponemos la variable m_ConexionEstablecida a valor TRUE, variable
que utilizaremos posteriormente para identificar si el puerto está o no abierto. Esta
variable fue declarada anteriormente como miembro de la clase CCommView e
iniciada en el constructor con el valor FALSE.
Como la función anterior hace referencia a la clase CParamCom, es necesario
añadir la línea siguiente al fichero CommView.cpp.
#include "ParamCom.h"
Para no permitir modificar la configuración del puerto de comunicaciones
cuando esté abierto, ejecute ClassWizard y añada la siguiente función miembro de
CCommView, controladora del mensaje UPDATE_COMMAND_UI.
void CCommView::OnUpdateConfigParam(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_ConexionEstablecida);
}
Si el usuario hace clic en el botón Cancelar (IDCANCEL) de la caja de diálo-
go Configuración, no se realizará ninguna acción.
En cambio, si el usuario hace clic en el botón Restaurar (IDC_DEFAULT) se
establecerán como valores por omisión los últimos valores que fueron almacena-
dos en el registro de Windows.
void CParamCom::OnPorOmision()
{
// Parámetros por defecto de la comunicación
m_nPuerto = 1; // COM2
m_nBaudios = 6; // 9600
m_nParidad = 0; // Ninguna
m_nBitsCar = 4; // 8
m_nBitsParada = 0; // 1
m_nControlFlujo = 1; // Xon/Xoff

UpdateData(FALSE); // Refrescar las listas desplegables
}
Ejecute ClassWizard y vincule la función OnPorOmision miembro de la clase
CParamCom al botón Restaurar.
La orden Establecer del menú Conexión tiene como función abrir el puerto de
comunicaciones con los parámetros actualmente seleccionados. Cuando el usuario
ejecute esta orden, como respuesta será invocada la función OnConexionEstable-
cer miembro de la clase CCommView, que a su vez invocará a la función Estable-
146 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

cerConexion. Por lo tanto, ejecute ClassWizard, vincule OnConexionEstablecer
con la orden Establecer y edítela como se indica a continuación:
void CCommView::OnConexionEstablecer()
{
if ( EstablecerConexion() )
{
UpdateData(TRUE);
m_botonEnviar.EnableWindow(TRUE);
UpdateData(FALSE);
}
}
Si el dispositivo de comunicaciones se abre satisfactoriamente, la función
OnConexionEstablecer, además, actualiza las variables miembro m_tx y m_rx li-
gadas con las cajas de texto de transmisión y de recepción, respectivamente, y
habilita el botón Enviar.
Para no permitir abrir el puerto de comunicaciones cuando ya esté abierto,
ejecute ClassWizard y añada la siguiente función miembro de CCommView, con-
troladora del mensaje UPDATE_COMMAND_UI.
void CCommView::OnUpdateConexionEstablecer(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_ConexionEstablecida);
}
Para cerrar el puerto de comunicaciones, la interfaz de la aplicación propor-
ciona la orden Cortar del menú Conexión. Para hacer operativa esta orden, vincú-
lela con la función OnConexionCortar y edítela así:
void CCommView::OnConexionCortar()
{
Terminar(); // guardar la configuración
CortarConexion();
m_botonEnviar.EnableWindow(FALSE);
}
Observe que la función OnConexionCortar primero llama a la función Termi-
nar para guardar la configuración actual en el registro de Windows, después invo-
ca a la función CortarConexion y finalmente inhabilita el botón Enviar.
Para no permitir cerrar el puerto de comunicaciones cuando no esté abierto,
ejecute ClassWizard y añada la siguiente función miembro de CCommView, con-
troladora del mensaje UPDATE_COMMAND_UI.
void CCommView::OnUpdateConexionCortar(CCmdUI* pCmdUI)
CAPÍTULO 3: COMUNICACIONES 147

{
pCmdUI->Enable(m_ConexionEstablecida);
}
Para prever el caso de que estando un dispositivo de comunicaciones abierto,
el usuario cierre la aplicación sin haber ejecutado previamente la orden Cortar,
defina el destructor de la clase CCommView así:
CCommView::~CCommView()
{
if ( m_ConexionEstablecida )
CortarConexion();
}
ENVIAR Y RECIBIR DATOS
Para enviar datos, el usuario arrancará la aplicación, establecerá las comunicacio-
nes, escribirá el texto a enviar en la caja de Texto a transmitir y pulsará el botón
Enviar. Por lo tanto, añada la función OnEnviar controladora del mensaje
WM_COMMAND que Windows envía al hacer clic en el botón Enviar y edítela
como se muestra a continuación:
void CCommView::OnEnviar()
{
int vr, n;
BYTE *pszBytes;

UpdateData(TRUE);
// Enviar los datos que hay en la caja transmisión
if ( n = m_tx.GetLength() )
{
pszBytes = (BYTE *)m_tx.GetBuffer(n + 1);
vr = EscribirCarsPuerto(pszBytes, n );
m_tx.ReleaseBuffer();
// Eliminar los caracteres transmitidos
if ( vr )
{
m_tx = "";
UpdateData( FALSE );
}
}
}
La función OnEnviar envía la información a la cola de salida invocando a la
función EscribirCarsPuerto y después limpia la caja Texto a transmitir.
148 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Cuando los caracteres enviados desde otra máquina se reciben en la cola de
recepción, el dispositivo de comunicaciones lo notifica por medio del evento
EV_RXCHAR. Este evento es capturado por el hilo ControlarEventos por medio
de la función WaitCommEvent. Entonces, el hilo envía un mensaje WM_EVEN-
TO_COM al hilo principal (a la aplicación) notificándole el evento ocurrido en el
dispositivo de comunicaciones. Como respuesta a este mensaje se ejecuta la fun-
ción OnEventoCom que en este caso invoca a la función LeerCaracteresPuerto
para obtener los datos del puerto, y después a la función OnVisualizarCars para
visualizarlos en la caja Texto recibido. Por lo tanto, ejecute ClassWizard, añada la
función OnVisualizarCars como miembro de CCommView y edítela como se in-
dica a continuación:
void CCommView::OnVisualizarCars(BYTE *pszBytes, int nBytes)
{
m_rx += pszBytes; // añadir los caracteres recibidos a los ya existentes
UpdateData( FALSE ); // visualizarlos
GetDlgItem(IDC_TX)->SetFocus(); // enfocar la caja de transmisión
}
La aplicación está finalizada. Ahora puede compilarla y ejecutarla. Para reali-
zar las pruebas en un solo ordenador, puede unir los hilos numerados dos y tres de
su puerto serie. De esta forma lo que trasmita, lo recibirá de nuevo. Otra solución,
es conectar un módem. Si envía una orden ATZ más CR, el módem le devolverá
OK. Esta aplicación no soporta la transmisión de ficheros binarios, sólo soporta la
transmisión de ficheros de texto ASCII. Se deja como ejercicio para el lector mo-
dificar la aplicación para que soporte ficheros texto y binarios (observe que el
problema deriva de haber utilizado para manipular la información, cadenas de ca-
racteres finalizadas con el carácter ASCII nulo).
CONTROL DE COMUNICACIONES
Visual C++incluye un control personalizado, Microsoft Communications Con-
trol, que permite establecer una comunicación serie entre máquinas, basada en el
estándar RS232, de una forma rápida y sencilla. Para poder utilizar este control en
una aplicación, hay que añadir al proyecto el control ActiveX MSCOMM32.OCX
para aplicaciones de 32 bits. Este control tiene los eventos y propiedades siguien-
tes:
Eventos
OnComm
Propiedades
Break CDHolding CommEvent CommID
CAPÍTULO 3: COMUNICACIONES 149

CommPort CTSHolding DSRHolding DTREnable
EOFEnable Handshaking InBufferCount InBufferSize
Index Input InputLen InputMode
Name NullDiscard Object OutBufferCount
OutBufferSize Output Parent ParityReplace
PortOpen RThreshold RTSEnable Settings
SThreshold Tag
Para obtener una amplia información sobre cada una de estas propiedades, re-
curra a la ayuda en línea de Visual Basic.
Como ejemplo, vamos a realizar la misma aplicación anterior, pero utilizando
ahora un control de comunicaciones. Por lo tanto, cree una nueva aplicación SDI
denominada ControlCom que utilice una caja de diálogo como ventana principal.
Para ello, ejecute AppWizard y haga que la clase CControlComView sea una clase
derivada de CFormView. A diferencia de la aplicación anterior, permita que Con-
trolCom tenga una barra de estado que utilizaremos para visualizar mensajes.
A continuación, abra el editor de recursos y sitúe sobre la plantilla de diálogo
creada por omisión, los controles con las propiedades que se especificaron en la
aplicación anterior. Añada ahora el control ActiveX, Microsoft Communications
Control. El resultado que obtendrá será similar al mostrado en la figura siguiente:

Seleccione el control de comunicaciones (IDC_MSCOMM1), invoque a
ClassWizard, y vincule con el control una variable m_MSComm1. En este instante
será informado de que el control aún no ha sido insertado en el proyecto. Al pul-
sar el botón Aceptar, Developer Studio hará este trabajo automáticamente por us-
ted insertando una o más clases que encapsulan el control; en este caso, añadirá al
proyecto la clase CMSComm. Esta clase tiene el aspecto siguiente:
class CMSComm : public CWnd
{
// ...
150 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

// Operations
public:
// ...
void SetCommPort(short nNewValue);
short GetCommPort();
// ...
void SetPortOpen(BOOL bNewValue);
BOOL GetPortOpen();
void SetRThreshold(short nNewValue);
short GetRThreshold();
void SetRTSEnable(BOOL bNewValue);
BOOL GetRTSEnable();
void SetSettings(LPCTSTR lpszNewValue);
CString GetSettings();
void SetSThreshold(short nNewValue);
short GetSThreshold();
void SetOutput(const VARIANT& newValue);
VARIANT GetOutput();
void SetInput(const VARIANT& newValue);
VARIANT GetInput();
void SetCommEvent(short nNewValue);
short GetCommEvent();
// ...
};
Observamos que la funcionalidad de la clase da acceso a cada una de las pro-
piedades que expusimos anteriormente para este control. Por ejemplo, para esta-
blecer el número de puerto que deseamos utilizar escribiríamos:
m_MSComm1.SetCommPort(2);
Si abre ClassWizard y selecciona IDC_MSCOMM1 (objeto control de comu-
nicaciones) puede observar en la lista de mensajes el evento OnComm. El evento
OnComm se genera siempre que cambia el valor de la propiedad CommEvent
para indicar que se ha producido un evento o un error en la comunicación.
La propiedad CommEvent contiene la constante numérica correspondiente al
evento o al error que se ha generado. A continuación indicamos estas constantes.
Constantes de eventos:
Constante Valor Descripción
comEvSend 1 Evento “enviar datos”.
comEvReceive 2 Evento “recibir datos”.
comEvCTS 3 Cambio en la línea “preparado para enviar”
(CTS).
CAPÍTULO 3: COMUNICACIONES 151

comEvDSR 4 Cambio en la línea “equipo de datos prepara-
do” (DSR).
comEvCD 5 Cambio en la línea “detección de portadora”
(CD).
comEvRing 6 Detección de llamada.
comEvEOF 7 Fin de fichero.
Constantes de errores:
Constante Valor Descripción
comEventBreak 1001 Señal de interrupción recibida.
comEventCTSTO 1002 Tiempo de espera de “preparado para enviar”
sobrepasado.
comEventDSRTO 1003 Tiempo de espera de “equipo de datos prepa-
rado” sobrepasado.
comEventFrame 1004 Error de trama.
comEventOverrun 1006 Pérdida de información en el puerto.
comEventCDTO 1007 Tiempo de espera de “detección de portadora”
sobrepasado.
comEventRxOver 1008 Desbordamiento del buffer de recepción.
comEventRxParity 1009 Error de paridad.
comEventTxFull 1010 Buffer de transmisión lleno.
comEventDCB 1011 Error inesperado al recuperar el “bloque de
control de dispositivos” (DCB) para el puerto.
Constantes de InputMode:
Constante Valor Descripción
comInputModeText 0 (Predeterminado) Los datos se recuperan como
texto mediante la propiedad Input.
comInputModeBinary 1 Los datos se recuperan como datos binarios
mediante la propiedad Input.
Constantes de protocolos:
Constante Valor Descripción
comNone 0 Sin protocolo.
comXonXoff 1 Protocolo XON/XOFF.
comRTS 2 Protocolo RTS/CTS (Petición de envío/prepa-
rado para enviar).
comRTSXOnXOff 3 Ambos protocolos (RTS y XON/XOFF).
152 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Tenga en cuenta que si establece las propiedades RThreshold o SThreshold
a 0 (valor predeterminado para ambas propiedades), se desactiva la interceptación
de los eventos comEvReceive y comEvSend, respectivamente.
Continuando con la aplicación, modifique la barra de menús con los menús y
las órdenes que se especificaron en la aplicación anterior.
Asimismo, implemente también la caja de diálogo Configuración exactamente
igual que lo hizo en la aplicación anterior, excepto los datos de la caja de texto
Control de flujo que ahora serán: Ninguno, Xon/Xoff, Hardware (RTS/CTS) y Am-
bos (RTS y XON/XOFF). Añada también una nueva lista desplegable identificada
por IDC_INPUTMODE y asígnele los datos Modo texto y Modo binario.

Recuerde que tiene que añadir a la aplicación una clase CParamCom derivada
de CDialog, basada en la plantilla IDD_PARAMETROSCOM que acaba de dise-
ñar. Guarde la declaración y la definición de esta clase en los ficheros param-
com.h y paramcom.cpp. Después, añada a la clase CParamCom las variables
miembro m_nPuerto, m_nBaudios, m_nParidad, m_nBitsCar, m_nBitsParada y
m_nControlFlujo vinculadas a cada una de las listas desplegables del diálogo.
Invoque a ClassWizard y añada a la clase CControlComView la función
miembro OnInitialUpdate y edítela como se indica a continuación:
void CControlComView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
ResizeParentToFit( FALSE );
}
CAPÍTULO 3: COMUNICACIONES 153

Si ahora compila y ejecuta la aplicación, obtendrá un resultado similar al mos-
trado en la figura siguiente:

Tipo VARIANT
Si nos fijamos en las funciones miembro de la clase CMSComm que encapsula la
funcionalidad del control de comunicaciones, en muchas de ellas aparece un tipo
identificado por VARIANT. El tipo VARIANT no es más que un tipo de datos
genérico. Permite declarar variables capaces de almacenar datos de cualquier tipo
predefinido. La declaración de este tipo se basa en una estructura en la que cabe
destacar dos miembros: vt para almacenar el tipo del dato (por ejemplo, VT_I4,
VT_BOOL, VT_BSTR) y una unión con todos los posibles tipos de datos que pue-
den ser descritos.
typedef struct tagVARIANT
{
VARTYPE vt;
// ...
union
{
unsigned char bVal; // VT_UI1.
short iVal; // VT_I2.
long lVal; // VT_I4.
float fltVal; // VT_R4.
double dblVal; // VT_R8.
VARIANT_BOOL boolVal; // VT_BOOL.
SCODE scode; // VT_ERROR.
CY cyVal; // VT_CY.
DATE date; // VT_DATE.
BSTR bstrVal; // VT_BSTR.
// ...
};
};
154 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

El siguiente ejemplo muestra como se utiliza un VARIANT para pasar valo-
res a una función.
#include <OleAuto.h> // constantes y macros

bool LongCadena(VARIANT *vLong, VARIANT vBstr)
{
// Validar los parámetros
if ( vLong->vt != VT_I4 || vBstr.vt != VT_BSTR)
return false;

// Calcular la longitud
V_I4(vLong) = ::SysStringByteLen(V_BSTR(&vBstr));

return true;
}

void MiFuncion()
{
bool vr;
long lon;
char *cad1 = "Cadena de caracteres", cad2[30];
BSTR bstrCadena; // un puntero de 32 bits a char
bstrCadena = ::SysAllocString((const unsigned short *)cad1);

// Declarar las variables
VARIANT vLong;
VARIANT vBstr;

// Especificar los tipos
vLong.vt = VT_I4; // long
vBstr.vt = VT_BSTR;

// Asignar valores.
// La razón de utilizar un & en las líneas siguientes,
// es porque las macros requieren punteros.
V_I4(&vLong) = 0L;
V_BSTR(&vBstr) = bstrCadena;

// Llamar a la función
vr = LongCadena(&vLong, vBstr);
// ...
// Acceder a los valores
lon = V_I4(&vLong);
strcpy(cad2, (const char *)V_BSTR(&vBstr));
// ...

// Liberar la memoria asignada a la cadena
::SysFreeString(bstrCadena);
}
CAPÍTULO 3: COMUNICACIONES 155

Como puede observar, hay que asignar el tipo de los datos manualmente y uti-
lizar macros para asignar los datos. Cada tipo de datos tiene una macro asociada
para acceder a los datos. Por ejemplo, el tipo VT_BSTR tiene asociada la macros
V_BSTR.
Visual C++proporciona tres clases diseñadas para facilitar la utilización de
datos de tipo VARIANT: COleVariant (MFC), CComVariant (ATL) y
_variant_t. Esta última clase fue incorporada a Visual C++a partir de la versión
5 y es mucho más funcional que las otras dos.
Un objeto _variant_t encapsula el tipo de datos VARIANT. La clase maneja
la asignación y desasignación del recurso, y llama a VariantInit (para iniciar vt a
VT_EMPTY) y a VariantClear (para limpiar la estructura VARIANT) cuando es
necesario. Esta clase, además de otras funciones, define varios constructores que
permiten construir un objeto _variant_t a partir de cualquier dato de un tipo pre-
definido (incluyendo cadenas de caracteres), la función miembro SetString que
permite asignar una cadena de caracteres a un objeto _variant_t y los operadores
de asignación (=), de comparación (==, !=) y extractores u operadores de conver-
sión de _variant_t a cada uno de los tipos predefinidos.
Otra clase de interés es _bstr_t, que encapsula el tipo de datos BSTR. La cla-
se maneja la asignación y desasignación del recurso llamando a SysAllocString y
SysFreeString. Esta clase, además de otras funciones, define varios constructores
que permiten construir un objeto _bstr_t a partir de: char *, BSTR, etc., la fun-
ción miembro length que permite obtener la longitud del objeto BSTR encapsu-
lado, y los operadores de asignación (=, +=), de concatenación (+, –), de negación
(!) para verificar si el objeto BSTR encapsulado es NULL, de comparación (==,
!=, <, >, <=, >=) y extractores u operadores de conversión de _bstr_t a wchar_t *
y char *.
Ambas clases utilizan el fichero de cabecera comdef.h.
Veamos el ejemplo anterior utilizando ahora esta clase:
#include <comdef.h> // definiciones

bool LongCadena(_variant_t *vLong, _variant_t vBstr)
{
// Validar los parámetros
if ( vLong->vt != VT_I4 || vBstr.vt != VT_BSTR)
return false;

// Calcular la longitud
_bstr_t bstr = vBstr;
*vLong = (long)bstr.length();
156 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

return true;
}

void MiFuncion()
{
bool vr;
long lon;
char *cad1 = "Cadena de caracteres", cad2[30];

// Declarar las variables
_variant_t vLong;
_variant_t vBstr;

// Asignar valores
vLong = 0L;
vBstr = cad1;

// Llamar a la función
vr = LongCadena(&vLong, vBstr);
// ...
// Acceder a los valores
lon = vLong;
strcpy(cad2, (_bstr_t)vBstr);
// ...
}
Manipular las comunicaciones
El control de comunicaciones proporciona dos formas de manipular las comunica-
ciones:
1. Notificando cuándo ocurre un evento; por ejemplo, ha llegado un carácter o
ha ocurrido un cambio en la línea DCD (detección de portadora) o RTS (peti-
ción de envío). Para manipular estos eventos y los posibles errores en las co-
municaciones, implementaremos el procedimiento conducido por el evento
OnComm del control de comunicaciones.
2. Verificando el valor de la propiedad CommEvent del control de comunica-
ciones después de cada función crítica en la aplicación, para saber qué evento
se ha dado o qué error ha ocurrido. Esta alternativa es preferible cuando la
aplicación es pequeña; por ejemplo, un marcador de llamadas telefónicas, ya
que no tiene sentido generar un evento después de recibir cada carácter puesto
que los únicos caracteres que se recibirán son las respuestas del módem.
CAPÍTULO 3: COMUNICACIONES 157

Cada control de comunicaciones que utilice se corresponde con un único
puerto serie. Esto es, si necesitamos acceder a más de un puerto serie, hay que uti-
lizar más de un control de comunicaciones.
Por ejemplo, para establecer una comunicación a través del puerto COM2 uti-
lizando un control de comunicaciones m_MSComm1, los pasos son los siguientes:
1. Especifique el puerto que va a abrir. Para realizar esta operación, asigne a la
propiedad CommPort de m_MSComm1 el valor correspondiente a ese puerto.
m_MSComm1.SetCommPort(2);
Puede asignar a la propiedad CommPort cualquier número entre 1 y 16 (el
valor predeterminado es 1). Cualquier otro valor producirá un error.
2. Especifique las características de comunicación. Para ello, asigne a la propie-
dad Settings de m_MSComm1 los valores que definen las mismas:
' 19200 baudios, paridad ninguna, 8 bits por carácter
' y 1 bit de parada
m_MSComm1.SetSettings("19200,N,8,1");
La propiedad Settings permite especificar la velocidad en baudios, la paridad
y el número de bits de datos y de parada. De forma predeterminada, la veloci-
dad en baudios es de 9600. La paridad sirve para la validación de los datos.
Normalmente no se utiliza y se establece a “N”. El valor de bits de datos indi-
ca el número de bits que representan un bloque de datos. El bit de parada in-
dica cuándo se ha recibido un bloque de datos.
3. Abra el puerto de comunicación. Para realizar esta operación, asigne el valor
True a la propiedad PortOpen de m_MSComm1:
m_MSComm1.SetPortOpen(true);
Una vez especificado el puerto que se desea abrir y la forma en que se reali-
zará la transferencia de los datos, establecemos la conexión poniendo la pro-
piedad PortOpen a true. No obstante, si la propiedad CommPort no se ha
establecido correctamente o si el dispositivo no admite la configuración espe-
cificada, se producirá un error, o bien puede ocurrir que el dispositivo externo
no funcione correctamente.
4. Cuando quiera enviar datos al puerto de comunicaciones, utilice la propiedad
Output de m_MSComm1. Esta propiedad permite escribir caracteres en el
buffer de transmisión:
158 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

CString str;
// ...
_variant_t var(str);
m_MSComm1.SetOutput(var);
5. Para recibir datos a través del puerto, implemente la función que responda al
evento OnComm y utilice la propiedad Input para obtener los datos. Esta
propiedad retorna una cadena de caracteres tomados del buffer de recepción.
Los caracteres leídos son eliminados automáticamente.
void CControlComView::OnComm1() // responde al evento OnComm
{ // de m_MSComm1
_bstr_t bs;
CString s;

switch(m_MSComm1.GetCommEvent())
{
case 1: // vbMSCommEvSend:
// ...
break;
case 2: // vbMSCommEvReceive:
bs = m_MSComm1.GetInput();
s = (char *)bs;
AfxMessageBox(s);
OnVisualizarCars(s);
break;
// ...
}
// ...
}
La propiedad CommEvent retorna el evento o el error más reciente ocurrido
durante un proceso de comunicaciones.
6. Utilice la propiedad PortOpen de m_MSComm1, para cerrar el puerto de co-
municaciones cuando éstas finalicen. Para ello, asigne a esta propiedad el va-
lor false.
m_MSComm1.SetPortOpen(false);
Interfaz de comunicaciones
Siguiendo los pasos de la aplicación anterior, vamos a implementar una interfaz
con las operaciones más comunes. Dicha interfaz, que resumimos en la tabla si-
guiente, la integraremos en la clase CControlComView:
Función Descripción
CAPÍTULO 3: COMUNICACIONES 159

Iniciar Lee del registro de Windows la configuración inicial.
Terminar Guarda en el registro de Windows la configuración
actual.
EstablecerConexion Abre el puerto de comunicaciones.
ConfigurarDisCom Establece los parámetros con los que se realizarán las
comunicaciones.
LeerCaracteresPuerto Lee un byte de la cola de entrada del puerto de co-
municaciones.
EscribirCarsPuerto Escribe un byte en la cola de salida del puerto de
comunicaciones.
CortarConexion Cierra el puerto de comunicaciones.
OnComm1 Función que responde a los eventos que ocurren so-
bre el puerto de comunicaciones.
Añada la declaración de estas funciones y de las variables necesarias para su
implementación a la declaración de la clase CControlComView.
class CControlComView : public CFormView
{
// ...
// Operations
public:
/////////////////////////////////////////////////
// Interfaz para comunicaciones
static int m_indPuerto;
static int m_indBaudios;
static int m_indParidad;
static int m_indBitsCar;
static int m_indBitsParada;
static int m_indControlFlujo;
static int m_indModoLectura;

UINT m_wTablaBaudios[13]; // tabla de velocidades
BYTE m_TablaParidad[5]; // tabla de paridades
BYTE m_TablaBitsParada[3]; // tabla bits de parada
bool m_ConexionEstablecida; // true si el puerto fue abierto

static void Iniciar();
static void Terminar();
bool EstablecerConexion();
void ConfigurarDisCom();
bool CortarConexion();
int LeerCaracteresPuerto(_bstr_t *bstr);
bool EscribirCarsPuerto(CString str);
/////////////////////////////////////////////////
// ...
160 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

};
Las variables m_ind... contienen el índice del elemento seleccionado de las
listas de la caja de diálogo Configuración.
Inicie la variable m_ConexionEstablecida a false en el constructor de su cla-
se.
Asigne valores a los arrays m_wTablaBaudios, m_TablaParidad y m_Tabla-
BitsParada miembros de la clase CControlComView. Realice esta asignación en
el constructor de su clase, como se indica a continuación:
CControlComView::CControlComView()
: CFormView(CControlComView::IDD)
{
m_wTablaBaudios[0] = 110;
m_wTablaBaudios[1] = 300;
m_wTablaBaudios[2] = 600;
m_wTablaBaudios[3] = 1200;
m_wTablaBaudios[4] = 2400;
m_wTablaBaudios[5] = 4800;
m_wTablaBaudios[6] = 9600;
m_wTablaBaudios[7] = 14400;
m_wTablaBaudios[8] = 19200;
m_wTablaBaudios[9] = 38400;
m_wTablaBaudios[10] = 56000;
m_wTablaBaudios[11] = 128000;
m_wTablaBaudios[12] = 256000;

m_TablaParidad[0] = 'N'; // ninguna
m_TablaParidad[1] = 'E'; // par
m_TablaParidad[2] = 'O'; // impar
m_TablaParidad[3] = 'M'; // marca
m_TablaParidad[4] = 'S'; // espacio
//{{AFX_DATA_INIT(CControlComView)
m_rx = _T("");
m_tx = _T("");
//}}AFX_DATA_INIT
// TODO: add construction code here
}
A continuación escribiremos cada una de las funciones descritas. Antes, inicie
las variables estáticas y defina las cadenas de caracteres necesarias para el registro
de Windows en la implementación de la clase CControlComView.
int CControlComView::m_indPuerto = 1; // COM2
int CControlComView::m_indBaudios = 6; // 9600
int CControlComView::m_indParidad = 0; // ninguna
CAPÍTULO 3: COMUNICACIONES 161

int CControlComView::m_indBitsCar = 4; // 8
int CControlComView::m_indBitsParada = 0; // 1
int CControlComView::m_indControlFlujo = 1; // Xon/Xoff
int CControlComView::m_indModoLectura = 0; // texto/binario

static char szComu[] = "Comunicaciones";
static char szPuerto[] = "Puerto";
static char szBaudios[] = "Baudios";
static char szParidad[] = "Paridad";
static char szBitsCar[] = "BitsCar";
static char szBitsParada[] = "BitsParada";
static char szControlFlujo[] = "ControlFlujo";
Función Iniciar
La función Iniciar obtiene del registro de Windows la configuración por omisión
del dispositivo de comunicaciones.
void CControlComView::Iniciar()
{
CWinApp *pApp = AfxGetApp();
// Recuperar configuración del registro de Windows
m_indPuerto = pApp->GetProfileInt(szComu, szPuerto, 1);
m_indBaudios = pApp->GetProfileInt(szComu, szBaudios, 6);
m_indParidad = pApp->GetProfileInt(szComu, szParidad, 0);
m_indBitsCar = pApp->GetProfileInt(szComu, szBitsCar, 4);
m_indBitsParada = pApp->GetProfileInt(szComu, szBitsParada, 0);
m_indControlFlujo = pApp->GetProfileInt(szComu, szControlFlujo, 1);
}
El lugar de donde se obtiene esta información del registro de Windows se es-
pecifica de forma predeterminada en CControlCom.cpp así:
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
Función Terminar
La función Terminar será invocada cuando se corta la comunicación para guardar
en el registro de Windows la configuración actual del dispositivo de comunica-
ciones. Esta configuración es la que será utilizada por omisión la próxima vez que
se ejecute la aplicación.
void CControlComView::Terminar()
{
CWinApp *pApp= AfxGetApp();
// Guardar configuración en el registro de Windows
pApp->WriteProfileInt(szComu, szPuerto, m_indPuerto);
pApp->WriteProfileInt(szComu, szBaudios, m_indBaudios);
162 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

pApp->WriteProfileInt(szComu, szParidad, m_indParidad);
pApp->WriteProfileInt(szComu, szBitsCar, m_indBitsCar);
pApp->WriteProfileInt(szComu, szBitsParada, m_indBitsParada);
pApp->WriteProfileInt(szComu, szControlFlujo, m_indControlFlujo);
}
Función EstablecerConexion
La función EstablecerConexion permite abrir el puerto de comunicaciones especi-
ficado por la variable miembro m_indPuerto. Según todo lo expuesto anterior-
mente, la función EstablecerConexion puede ser así:
bool CControlComView::EstablecerConexion()
{
// Cerrar el puerto si estuviera abierto
if (m_MSComm1.GetPortOpen())
m_MSComm1.SetPortOpen(false);

// Especificar el puerto COM que se desea abrir
m_MSComm1.SetCommPort(m_indPuerto + 1);

// Establecer el tamaño de las colas de recepción y de transmisión
m_MSComm1.SetInBufferSize(COLARX);
m_MSComm1.SetOutBufferSize(COLATX);

// Limpiar las colas Rx y Tx
m_MSComm1.SetInBufferCount(0);
m_MSComm1.SetOutBufferCount(0);

// Establecer los parámetros de la comunicación
ConfigurarDisCom();

// Caracteres que puede admitir el buffer de transmisión antes de que
// el control genere el evento OnComm. Su valor predeterminado 0.
m_MSComm1.SetSThreshold(1);

// Caracteres que se van a recibir antes de que el control
// genere el evento OnComm. Su valor predeterminado 0.
m_MSComm1.SetRThreshold(1);

// Abrir el puerto de comunicaciones
m_MSComm1.SetPortOpen(true);

if (!m_MSComm1.GetPortOpen())
{
AfxMessageBox( "Error: No se puede abrir el puerto COM",
MB_OK | MB_ICONEXCLAMATION );
MessageBeep(0xFFFFFFFF);
return false;
}
CAPÍTULO 3: COMUNICACIONES 163

m_ConexionEstablecida = true;
return true;
}
Defina las constantes COLARX y COLATX, que definen los tamaños de las
colas de recepción y transmisión respectivamente, en el fichero ControlCom-
View.cpp:
#define COLARX 4096
#define COLATX 4096
Función ConfigurarDisCom
Para construir el bloque de control del dispositivo (DCB) y configurar el disposi-
tivo de comunicaciones, EstablecerConexion invoca a la función ConfigurarDis-
Com, que se muestra a continuación:
void CControlComView::ConfigurarDisCom()
{
bool bEstablecer;
char Settings[20];
char BitsParada[4];
switch(m_indBitsParada)
{
case 0: strcpy(BitsParada, "1"); break;
case 1: strcpy(BitsParada, "1.5"); break;
case 2: strcpy(BitsParada, "2"); break;
}
// Baudios, paridad, número de bits de datos y de parada
wsprintf(Settings, "%ld,%c,%d,%s",
m_wTablaBaudios[m_indBaudios],
m_TablaParidad[m_indParidad],
m_indBitsCar + 4,
BitsParada);
m_MSComm1.SetSettings(Settings);

// Establecer el control de flujo software
bEstablecer = (m_indControlFlujo == 1 ||
m_indControlFlujo == 3); // Xon/Xoff
if (bEstablecer)
m_MSComm1.SetHandshaking(1); // comXOnXOff

// Establecer el control de flujo hardware
bEstablecer = (m_indControlFlujo == 2 ||
m_indControlFlujo == 3); // RTS/CTS
if (bEstablecer)
m_MSComm1.SetHandshaking(2); // comRTS

// Cómo se leerán los datos del puerto
164 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

m_MSComm1.SetInputMode(m_indModoLectura); // texto/binario
}
Controlar eventos
Vimos que la función EstablecerConexion, antes de abrir el puerto de comunica-
ciones, establece las propiedades SetSThreshold y SetRThreshold, que indican,
respectivamente, los caracteres que puede admitir el buffer de transmisión antes
de que el control genere el evento OnComm y los caracteres que se van a recibir
antes de que el control genere el evento OnComm. El valor predeterminado para
estas propiedades es cero. Si SetSThreshold es cero, no se generará el evento
OnComm en la transmisión, y si SetRThreshold es cero, no se generará el even-
to OnComm en la recepción.
Según lo expuesto, ejecute ClassWizard, seleccione el control IDC_MS-
COMM1 y añada la función OnComm1 miembro de CControlCom que respon-
derá al evento (mensaje) OnComm.
OnComm1 será invocada automáticamente cada vez que se produzca sobre el
puerto de comunicaciones un evento o un error de los expuestos anteriormente.
Para saber de qué evento o error se trata y aplicar el tratamiento correspondiente,
la función OnComm implementará básicamente una sentencia switch. Al mismo
tiempo visualizará sobre la barra de estado el mensaje correspondiente al evento o
error ocurrido. Estos mensajes los definiremos como recursos en la tabla de cade-
nas de caracteres de la aplicación.
Por ejemplo, si el evento es que se recibió información en la cola de recepción
del dispositivo de comunicaciones (comEvReceive), esta función leerá dicha in-
formación y la procesará; en nuestro caso la visualizará en la caja de texto corres-
pondiente. Por lo tanto, edite esta función como se indica a continuación:
void CControlComView::OnComm1()
{
CString strEvento, strError;
_bstr_t bstrRecibida; // inluir <comdef.h>

switch(m_MSComm1.GetCommEvent())
{
case 1: // comEvSend:
strEvento.LoadString(IDS_COMEVSEND);
break;
case 2: // comEvReceive:
strEvento.LoadString(IDS_COMEVRECEIVE);
// Leer datos del puerto
if ( LeerCaracteresPuerto(&bstrRecibida) )
OnVisualizarCars( (char *)bstrRecibida );
CAPÍTULO 3: COMUNICACIONES 165

break;
case 3: // comEvCTS:
strEvento.LoadString(IDS_COMMEVCTS);
break;
case 4: // comEvDSR:
strEvento.LoadString(IDS_COMEVDSR);
break;
case 5: // comEvCD:
strEvento.LoadString(IDS_COMEVCD);
break;
case 6: // comEvRing:
strEvento.LoadString(IDS_COMEVRING);
break;
case 7: // comEvEOF:
strEvento.LoadString(IDS_COMEVEOF);
break;
case 1001: // comErBreak:
strError.LoadString(IDS_COMERBREAK);
break;
case 1002: // comErCTSTO:
strError.LoadString(IDS_COMERCTSTO);
break;
case 1003: // comErDSRTO:
strError.LoadString(IDS_COMERDSRTO);
break;
case 1004: // comErFrame:
strError.LoadString(IDS_COMERFRAME);
break;
case 1006: // comErOverrun:
strError.LoadString(IDS_COMEROVERRUN);
break;
case 1007: // comErCDTO:
strError.LoadString(IDS_COMERCDTO);
break;
case 1008: // comErRxOver:
strError.LoadString(IDS_COMERRXOVER);
break;
case 1009: // comErRxParity:
strError.LoadString(IDS_COMERRXPARITY);
break;
case 1010: // comErTxFull:
strError.LoadString(IDS_COMERTXFULL);
break;
}

if (!strEvento.IsEmpty())
{
CWnd *pMainWnd = AfxGetApp()->m_pMainWnd;
CStatusBar *pStatusbar =
(CStatusBar *)pMainWnd-
>GetDescendantWindow(AFX_IDW_STATUS_BAR);
166 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

pStatusbar->SetPaneText(0, strEvento);
}
else
if (!strError.IsEmpty())
{
MessageBeep(MB_ICONEXCLAMATION);
strError += _T("\nAceptar para ignorar, Cancelar para salir");
int vr = AfxMessageBox(strError,
MB_OKCANCEL | MB_ICONEXCLAMATION);
if (vr == IDCANCEL)
m_MSComm1.SetPortOpen(false); // cerrar el puerto
}
}
Vemos que si el evento que se produjo fue comEvReceive, la función On-
Comm invoca primero a la función LeerCaracteresPuerto para leer los caracteres
recibidos y después a OnVisualizarCars para añadir dicha información a la caja
de texto referenciada por m_rx de la interfaz de usuario.
Función LeerCaracteresPuerto
La función LeerCaracteresPuerto lee datos del puerto COM, los almacena en un
array de tipo _bstr_t y devuelve el número de caracteres leídos.
int CControlComView::LeerCaracteresPuerto(_bstr_t *bstrRecibida)
{
*bstrRecibida = m_MSComm1.GetInput();
return bstrRecibida->length();
}
Función EscribirCarsPuerto
La función EscribirCarsPuerto escribe un bloque en el puerto COM procedente
de un array pasado como parámetro.
bool CControlComView::EscribirCarsPuerto(CString str)
{
_variant_t var(str);
m_MSComm1.SetOutput(var);

return true;
}
CAPÍTULO 3: COMUNICACIONES 167

Función CortarConexion
La función CortarConexion cierra el puerto de comunicaciones. Antes de cerrarlo,
verifica si quedan datos en la cola de transmisión. Si hay datos intenta enviarlos, y
si no, cierra el puerto de comunicaciones.
bool CControlComView::CortarConexion()
{
if (m_ConexionEstablecida)
{
// Establecer un periodo de 10 segundos a partir de la
// hora actual
bool bTiempoSobrepasado = false;
CTime TiempoLimite = CTime::GetCurrentTime() + CTimeSpan(0,0,0,10);

while (m_MSComm1.GetOutBufferCount())
{
// Procesar todos los mensajes pendientes
DoEvents();

if ( (CTime::GetCurrentTime() > TiempoLimite) || bTiempoSobrepasado)
{
int vr = AfxMessageBox("Datos no enviados", MB_ABORTRETRYIGNORE);
switch (vr)
{
// Intentar enviar los datos durante otros 10 segs.
case IDRETRY:
TiempoLimite = CTime::GetCurrentTime() + CTimeSpan(0,0,0,10);
break;
// Ignorar el tiempo límite
case IDIGNORE:
bTiempoSobrepasado = true;
break;
// Abortar el intento
case IDABORT:
return false;
}
}
}
m_MSComm1.SetPortOpen(false);
m_ConexionEstablecida = false;
}
return true;
}
La función CortarConexión, puesto que establece un lazo que impediría que
otras aplicaciones se ejecuten, invoca a la función DoEvents para permitir ejecutar
cualquier otro mensaje que estuviera esperando en alguna cola de cualquier otra
aplicación.
168 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

void CControlComView::DoEvents()
{
MSG msg;

// ¿Hay mensajes esperando en alguna cola de mensajes?
while (::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
{
// Procesar el mensaje. Si no es posible, retornar.
if (!AfxGetThread()->PumpMessage())
return;
}
}
INTERFAZ DEL USUARIO
Cuando se ejecute la aplicación, una de las primeras tareas que hay que hacer es
leer la configuración almacenada en el registro de Windows. Este proceso lo rea-
lizaremos desde la función OnInitialUpdate de la clase CControlComView.
void CControlComView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
ResizeParentToFit( false );

// Iniciar el puerto y los controles de la IU
UpdateData(false);
m_botonEnviar.EnableWindow(false);
Iniciar(); // configuración inicial del puerto COM
}
Asimismo, la función OnInitialUpdate, además de ajustar el marco de la
ventana al tamaño de la vista, inhabilita el botón Enviar.
Cuando se establece una conexión entre dos equipos, previamente hay que
especificar los parámetros bajos los cuales se realizará la misma. Para realizar esta
operación la aplicación proporciona la orden Parámetros COM. Cuando el usua-
rio ejecute esta orden se visualizará la caja de diálogo Configuración que le per-
mitirá establecer los parámetros con los que se realizarán las comunicaciones. La
respuesta a esta acción del usuario será la función OnConfigParam. Ejecute
ClassWizard, añada esta función y edítela como se indica a continuación:
void CControlComView::OnConfigParam()
{
// Crea el objeto de diálogo
CAPÍTULO 3: COMUNICACIONES 169

CParamCom dlg;

// Actualizar los datos del diálogo
dlg.m_nPuerto = m_indPuerto;
dlg.m_nBaudios = m_indBaudios;
dlg.m_nParidad = m_indParidad;
dlg.m_nBitsCar = m_indBitsCar;
dlg.m_nBitsParada = m_indBitsParada;
dlg.m_nControlFlujo = m_indControlFlujo;
dlg.m_nModoLectura = m_indModoLectura;

// Mostrar el cuadro de diálogo y verificar el botón pulsado
if (dlg.DoModal() != IDOK) return;

// Actualizar la configuración
m_indPuerto = dlg.m_nPuerto;
m_indBaudios = dlg.m_nBaudios;
m_indParidad = dlg.m_nParidad;
m_indBitsCar = dlg.m_nBitsCar;
m_indBitsParada = dlg.m_nBitsParada;
m_indControlFlujo = dlg.m_nControlFlujo;
m_indModoLectura = dlg.m_nModoLectura;

if( EstablecerConexion() )
{
m_ConexionEstablecida = true;
AfxMessageBox( "Puerto de comunicaciones abierto",
MB_OK | MB_ICONEXCLAMATION );
m_botonEnviar.EnableWindow(TRUE);
}
}
Una vez visualizada la caja de diálogo Configuración, si el usuario hace clic
en el botón Aceptar (IDOK) se actualizarán los parámetros de configuración con
los valores seleccionados y se invocará a la función EstablecerConexion para
abrir el puerto de comunicaciones especificado.
Como la función anterior hace referencia a la clase CParamCom, es necesario
añadir la línea siguiente al fichero ControlComView.cpp.
#include "ParamCom.h"
Para no permitir modificar la configuración del puerto de comunicaciones
cuando esté abierto, ejecute ClassWizard y añada la siguiente función miembro de
CControlComView, controladora del mensaje UPDATE_COMMAND_UI.
void CControlComView::OnUpdateConfigParam(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_ConexionEstablecida);
170 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

}
Si el usuario hace clic en el botón Cancelar (IDCANCEL) de la caja de diálo-
go Configuración, no se realizará ninguna acción.
En cambio, si el usuario hace clic en el botón Restaurar (IDC_DEFAULT) se
establecerán como valores por omisión los últimos que fueron almacenados en el
registro de Windows.
void CParamCom::OnPorOmision()
{
// Parámetros por defecto de la comunicación
m_nPuerto = 1; // COM2
m_nBaudios = 6; // 9600
m_nParidad = 0; // Ninguna
m_nBitsCar = 4; // 8
m_nBitsParada = 0; // 1
m_nControlFlujo = 1; // Xon/Xoff
m_nControlFlujo = 0; // Texto/Binario

UpdateData(false); // Refrescar las listas desplegables
}
Ejecute ClassWizard y vincule la función OnPorOmision miembro de la clase
CParamCom al botón Restaurar.
La orden Establecer del menú Conexión tiene como función abrir el puerto de
comunicaciones con los parámetros actualmente seleccionados. Cuando el usuario
ejecute esta orden, como respuesta será invocada la función OnConexionEstable-
cer miembro de la clase CControlComView, que a su vez invocará a la función
EstablecerConexion. Por lo tanto, ejecute ClassWizard, vincule OnConexionEsta-
blecer con la orden Establecer y edítela como se indica a continuación:
void CControlComView::OnConexionEstablecer()
{
if ( EstablecerConexion() )
{
UpdateData(true);
m_botonEnviar.EnableWindow(true);

UpdateData(false);
}
}
Si el dispositivo de comunicaciones se abre satisfactoriamente, la función
OnConexionEstablecer, además, actualiza las variables miembro m_tx y m_rx li-
CAPÍTULO 3: COMUNICACIONES 171

gadas con las cajas de texto de transmisión y de recepción, respectivamente, y
habilita el botón Enviar.
Para no permitir abrir el puerto de comunicaciones cuando ya esté abierto,
ejecute ClassWizard y añada la siguiente función miembro de CControlComView,
controladora del mensaje UPDATE_COMMAND_UI.
void CControlComView::OnUpdateConexionEstablecer(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_ConexionEstablecida);
}
Para cerrar el puerto de comunicaciones, la interfaz de la aplicación propor-
ciona la orden Cortar del menú Conexión. Para hacer operativa esta orden, vincú-
lela con la función OnConexionCortar y edítela así:
void CControlComView::OnConexionCortar()
{
Terminar(); // guardar la configuración
CortarConexion();
m_botonEnviar.EnableWindow(false);
}
Observe que la función OnConexionCortar primero llama a la función Termi-
nar para guardar la configuración actual en el registro de Windows, después invo-
ca a la función CortarConexion y finalmente inhabilita el botón Enviar.
Para no permitir cerrar el puerto de comunicaciones cuando no esté abierto,
ejecute ClassWizard y añada la siguiente función miembro de CControlComView,
controladora del mensaje UPDATE_COMMAND_UI.
void CControlComView::OnUpdateConexionCortar(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_ConexionEstablecida);
}
Si el usuario cierra la aplicación sin haber ejecutado previamente la orden
Cortar, lógicamente estando el puerto abierto, el puerto es cerrado automática-
mente cuando el control es destruido.
ENVIAR Y RECIBIR DATOS
Para enviar datos, el usuario arrancará la aplicación, establecerá las comunicacio-
nes, escribirá el texto a enviar en la caja de Texto a transmitir y pulsará el botón
Enviar. Por lo tanto, añada la función OnEnviar controladora del mensaje
172 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

WM_COMMAND que Windows envía al hacer clic en el botón Enviar y edítela
como se muestra a continuación:
void CControlComView::OnEnviar()
{
int vr, n;

UpdateData(true);
// Enviar los datos que hay en la caja transmisión
if ( n = m_tx.GetLength() )
{
vr = EscribirCarsPuerto( m_tx );
// Eliminar los caracteres transmitidos
if ( vr )
{
m_tx = "";
UpdateData( false );
}
}
}
La función OnEnviar envía la información a la cola de salida invocando a la
función EscribirCarsPuerto y después limpia la caja Texto a transmitir.
Cuando los caracteres enviados desde otra máquina se reciben en la cola de
recepción, el dispositivo de comunicaciones lo notifica por medio del mensaje
OnComm. Como respuesta a este mensaje se ejecuta la función OnComm1, que
en este caso, invoca a la función LeerCaracteresPuerto para obtener los datos del
puerto, y después a la función OnVisualizarCars para visualizarlos en la caja Tex-
to recibido. Por lo tanto, ejecute ClassWizard, añada la función OnVisualizarCars
como miembro de CControlComView y edítela como se indica a continuación:
void CControlComView::OnVisualizarCars(BYTE *pszBytes, int nBytes)
{
m_rx += strRecibida; // añadir los caracteres recibidos a los ya existentes
UpdateData( false ); // visualizarlos
GetDlgItem(IDC_TX)->SetFocus(); // enfocar la caja de transmisión
}
La aplicación está finalizada. Ahora puede compilarla y ejecutarla. Para reali-
zar las pruebas en un solo ordenador, puede unir los hilos numerados dos y tres de
su puerto serie. De esta forma lo que transmita lo recibirá de nuevo. Otra solución,
es conectar un módem. Si envía una orden ATZ más CR, el módem le devolverá
OK.
CAPÍTULO 3: COMUNICACIONES 173

Como ejemplo, puede añadir a la aplicación un menú Utilidades con una or-
den Enviar fichero que permita transmitir ficheros de texto. A continuación se
presenta un código que realiza esta operación:
void CControlComView::OnEnviarFichero()
{
// Caja de diálogo Abrir
CFileDialog DlgAbrir( TRUE, _T("txt"), NULL,
OFN_HIDEREADONLY|OFN_PATHMUSTEXIST,
_T("Ficheros de texto (*.txt)|*.txt|\
Todos (*.*)|*.*||"), this);

if (DlgAbrir.DoModal() != IDOK) return;

CFile FicheroTx( DlgAbrir.GetPathName(), CFile::modeRead );

int nTamBufTx = m_MSComm1.GetOutBufferSize();
int nTamFichTx = FicheroTx.GetLength();
char *pstrBuffer = new char[nTamBufTx+1];

// Leer/transmitir el fichero en bloques del tamaño del buffer Tx
for (int n = 0; n < nTamFichTx/nTamBufTx; n++)
{
FicheroTx.Read( pstrBuffer, nTamBufTx );
pstrBuffer[nTamBufTx] = 0; // carácter nulo de terminación
Transmitir( pstrBuffer, nTamBufTx );
}

// Si el tamaño del fichero no es múltiplo de nTamBufTx,
// enviar los datos restantes
int nResto = nTamFichTx % nTamBufTx;
if ( nResto )
{
FicheroTx.Read( pstrBuffer, nResto );
pstrBuffer[nResto] = 0; // carácter nulo de terminación
Transmitir( pstrBuffer, nResto );
}

delete pstrBuffer;
FicheroTx.Close();
GetDlgItem(IDC_TX)->SetFocus();
}

void CControlComView::OnUpdateUtenviarfichero(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_ConexionEstablecida);
}

void CControlComView::Transmitir(char *pstrBuffer, int nTamBloque)
{
174 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

LPWSTR pwstrDatos = new wchar_t[nTamBloque];

// Convertir el texto ASCII a UNICODE
MultiByteToWideChar(CP_ACP, 0, pstrBuffer, nTamBloque,
pwstrDatos, nTamBloque);
_variant_t var(pstrBuffer);

// Enviar los datos al puerto
m_MSComm1.SetOutput(var);

// Esperar a que el buffer Tx esté vacío
while (m_MSComm1.GetOutBufferCount())
DoEvents();

delete pwstrDatos;
}
La conversión del texto ASCII a UNICODE se ha hecho con fines didácticos.
Si lo prefiere, puede enviar el texto directamente en ASCII. El tipo wchar_t (wide
character - caracteres que utilizan dos bytes) es útil para escribir programas por-
tables a nivel internacional. Este tipo se encuentra definido en stddef.h y stdlib.h.

Faltan páginas...
Faltan páginas...





CAPÍTULO 8
 F.J.Ceballos/RA-MA

BIBLIOTECAS DINÁMICAS
A pesar de la potencia de Visual C++, en algún momento se nos planteará algún
problema que exija extendernos por encima de sus límites. Afortunadamente, Vi-
sual C++ no está limitado a sus capacidades internas. Como ya hemos visto, una
aplicación Visual C++ puede utilizar una amplia variedad de funciones pertene-
cientes a la API de Windows. Si esto no es bastante, aún podemos ir más allá es-
cribiendo bibliotecas dinámicas personalizadas.
¿Qué es una biblioteca dinámica? Una biblioteca dinámica, abreviadamente
DLL (Dynamic Link Library), es un fichero ejecutable de funciones, o simple-
mente de recursos, tal como mapas de bits o definiciones de fuentes, que pueden
ser llamadas por cualquier aplicación Windows. Una DLL personalizada es una
biblioteca dinámica que nosotros mismos escribimos para satisfacer nuestras ne-
cesidades.
Windows está en gran medida formado a partir de DLL, y si no, eche una mi-
rada a su directorio system. Por ejemplo, los ficheros KRNL386.EXE, GDI.EXE y
USER.EXE, así como KEYBOARD.DRV, SYSTEM.DRV y SOUND.DRV, son
todos DLL. Los ficheros de fuentes, esto es, con extensión .FON, también son
DLL. También encontrará cantidad de ficheros con extensión DLL; éstos también
son DLL, muchas de ellas pertenecientes a aplicaciones Windows instaladas, co-
mo Excel o Visual Basic. Según esto, es fácil adivinar que una DLL puede tener
cualquier extensión, aunque .DLL es la más estándar. De todas ellas, sólo las bi-
bliotecas dinámicas con extensión .DLL son cargadas automáticamente por Win-
dows, mientras que las DLL con otras extensiones tienen que ser cargadas
explícitamente.
Como todo, la utilización de las DLL tiene ventajas e inconvenientes. Por
ejemplo, una función en una DLL está disponible para ser llamada por cualquier
aplicación Windows. Las ventajas que esto supone son, por una parte, reducción
460 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

del código de la aplicación, al no tener que escribirla formando parte del código
de la misma, lo que redundará en velocidad de compilación y de carga de la apli-
cación, así como en ahorro de espacio en el disco, ya que solamente existe una
copia de la función. Y por otra parte, como están separadas de la aplicación, se
pueden actualizar sin tener que tocar, y por lo tanto recompilar, las aplicaciones
que las utilizan. Como inconvenientes, caben destacar la necesidad de estar pre-
sentes y el tiempo que se necesita para acceder a ellas cuando se ejecuta una apli-
cación que las utiliza. Sin embargo, cuando se utilizan bibliotecas estáticas, las
funciones que la aplicación necesita se incluyen en la misma durante el proceso de
enlace, por lo que ni se pierde tiempo en leerlas ni la biblioteca tiene que estar
presente.
CREACIÓN DE UNA DLL EN Win32
El punto de entrada y de salida en una DLL en Win32 es una función denominada
DllMain; ésta es una de las diferencias con respecto a Win16. Esta función es op-
cional; esto quiere decir que si no se escribe, el compilador asume una que no ha-
ce nada, simplemente retorna un valor TRUE.
DllMain utiliza el convenio de llamada WINAPI para sus tres parámetros en
lugar de FAR PASCAL que ha quedado obsoleto. El prototipo de esta función es
el siguiente:
BOOL WINAPI DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved);
y el esqueleto de la definición de la función es así:
BOOL WINAPI DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
//...
case DLL_THREAD_ATTACH:
//...
case DLL_THREAD_DETACH:
//...
case DLL_PROCESS_DETACH:
//...
}
return TRUE;
}
La función retorna el valor TRUE para indicar que se ha ejecutado satisfacto-
riamente. Si durante el proceso de iniciación la función retorna FALSE el sistema
cancela el proceso.
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 461

El parámetro dwReason indica la razón por la que ha sido llamada la función
DllMain: iniciación o terminación, por un proceso o por un hilo (thread).
Para recordar las diferencias entre procesos e hilos, repase al capítulo “Hilos”
incluido en esta obra.
La siguiente tabla describe el significado de los posibles valores del paráme-
tro dwReason:
Valor de dwReason Descripción
DLL_PROCESS_ATTACH Un nuevo proceso intenta acceder a la DLL; se
asume un hilo.
DLL_THREAD_ATTACH Un nuevo hilo de un proceso existente intenta ac-
ceder a la DLL; esta llamada se hace a partir del
segundo hilo de un proceso vinculado a la DLL.
DLL_PROCESS_DETACH Un proceso abandona la DLL.
DLL_THREAD_DETACH Uno de los hilos adicionales (no el primer hilo) de
un proceso abandona la DLL.
El parámetro lpReserved se reserva para ser utilizado por el sistema.
El parámetro hModule es el handle a un ejemplar de la DLL.
Cuando necesite acceder a los parámetros emitidos en la línea de órdenes
puede utilizar la función GetCommandLine de la API.
Para crear una DLL, los pasos a seguir son similares a los ejecutados para es-
cribir un programa C/C++. Escribimos los ficheros de cabecera (ficheros .h), los
ficheros con la definición de las funciones (ficheros .c o .cpp), el fichero de defi-
nición de módulos (fichero .def), si es preciso, y los ficheros del proyecto para au-
tomatizar la construcción de la DLL; estos últimos, normalmente serán generados
automáticamente por Visual C++. Dependiendo de la utilidad de la DLL, en oca-
siones puede ser que necesitemos escribir un fichero de recursos (fichero .rc).
Como ejemplo, vamos a desarrollar una DLL denominada strucdll32.dll que
incluya dos funciones denominadas Sumar y Restar. Ambas funciones tendrán
dos parámetros de tipo double y retornarán, respectivamente, un resultado tam-
bién de tipo double que se corresponderá con la suma o la resta de los argumentos
pasados en la llamada.
462 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Fichero de cabecera (.h)
Los ficheros de cabecera contienen declaraciones y definiciones que el preproce-
sador de C incluye en el fichero fuente justo antes de la compilación. Para nuestro
ejemplo, escribiremos un fichero strucdll32.h que contenga las declaraciones de
las funciones Sumar y Restar:
// ----------------------------------------------------
// Nombre del fichero: STCDLL32.H
//
// Este fichero de cabecera contiene las funciones
// prototipo para las funciones exportables por la
// DLL denominada STCDLL32
//
// Copyright (c) Fco. Javier Ceballos
// ----------------------------------------------------

// Variables globales

// Funciones prototipo
#ifdef __cplusplus //si los ficheros fuente son .cpp
extern "C" {
#endif

double WINAPI Sumar( double Param1, double Param2 );
double WINAPI Restar( double Param1, double Param2 );

#ifdef __cplusplus //si el compilador es C++ ...
}
#endif
La macro WINAPI instruye al compilador para que interprete adecuadamente
el convenio de llamada utilizado por la aplicación que invoca a las funciones de la
DLL.
Fichero fuente (.c o .cpp)
Fundamentalmente, el fichero fuente contiene la definición de las funciones. Para
nuestro ejemplo, escribiremos un fichero strucdll32.cpp que contenga los ficheros
de cabecera windows.h y strucdll32.h, la función de entrada y de salida de la
DLL, DllMain, y las funciones que nosotros deseamos incluir en la biblioteca,
Sumar y Restar:

// ----------------------------------------------------
// Nombre del fichero: STCDLL32.CPP
//
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 463

// Éste es el fichero fuente principal de la DLL,
// el punto de entrada y de salida de la DLL
//
// Copyright (c) Fco. Javier Ceballos
// ----------------------------------------------------

#include <windows.h>
#include "stcdll32.h"

// ----------------------------------------------------
// Función DllMain
//
// Éste es el punto de entrada y de salida de la DLL.
// Esta función es llamada por Windows. Usted no tiene
// que llamarla desde su aplicación.
//
// Parámetros:
// hModule - el handle para un ejemplar de la DLL
// dwReason - razón por la que ha sido llamada la DLL
// lpReserved - reservado para uso del sistema.
//
// Valor retornado:
// TRUE - indicando que la DLL se ha iniciado
// satisfactoriamente.
// ----------------------------------------------------

BOOL WINAPI DllMain (HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
// Escriba aquí el código que escribía en LibMain (Win16).
// Quizá tenga que hacer alguna modificación por el hecho de
// que puede ser llamada más de una vez.
// Retorne TRUE para salir de la DLL una vez cargada
// o FALSE si la carga falla.
break;

case DLL_THREAD_ATTACH:
// Escriba aquí el código de iniciación que se tiene
// que ejecutar cada vez que se cree un hilo en un proceso
// que ya tiene cargada esta DLL.
break;

case DLL_THREAD_DETACH:
// Escriba aquí el código de terminación que se tiene
// que ejecutar cada vez que un hilo en un proceso sale
// de la DLL que dicho proceso ya tiene cargada.
break;

case DLL_PROCESS_DETACH:
// Escriba aquí el código que escribía en WEP (Win16).
// Este código quizá no sea necesario porque el
464 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

// sistema operativo ya se encarga de esta labor.
break;
}

return TRUE; // DLL_PROCESS_ATTACH satisfactorio
}

// ----------------------------------------------------
// Función Sumar
//
// Función que suma dos números reales.
//
// Parámetros:
// Param1 - valor real. Primer sumando
// Param2 - valor real. Segundo sumando.
// Valor retornado:
// valor real - Param1 + Param2
// ----------------------------------------------------

double WINAPI Sumar( double Param1, double Param2 )
{
return (Param1 + Param2);
}

// ----------------------------------------------------
// Función Restar
//
// Función que suma dos números reales.
//
// Parámetros:
// Param1 - valor real. Minuendo
// Param2 - valor real. Sustraendo.
// Valor retornado:
// valor real - Param1 - Param2
// ----------------------------------------------------

double WINAPI Restar( double Param1, double Param2 )
{
return (Param1 - Param2);
}
Fichero de definición de módulos (.def)
El fichero de definición de módulos informa al enlazador (linker) sobre cómo
crear el fichero ejecutable. Para nuestra DLL, puede ser el siguiente:
;------------------------------------------------------
; Nombre del fichero: STCDLL32.DEF
;
; Módulo de definición del fichero
;
; Copyright (c) Fco. Javier Ceballos
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 465

;------------------------------------------------------

LIBRARY STCDLL32

DESCRIPTION 'Ejemplo de creación de una DLL'

EXPORTS
Sumar @1
Restar @2
Observe que LIBRARY especifica el nombre de la biblioteca, y que EX-
PORTS define los nombres y los atributos de las funciones que explícitamente
son puestas a disposición de otras aplicaciones y DLL; el valor ordinal a conti-
nuación del nombre de la función define la localización del nombre de la función
en la tabla de nombres de la aplicación. La utilización del número de orden es más
rápida y requiere menos espacio. Este fichero es necesario para que se genere stc-
dll32.lib. Si este fichero no se incluye en el proyecto sólo se generará stcdll32.dll.
Para construir el proyecto que dará lugar a la DLL utilizando Visual C++ (32
bits), ejecute la orden New del menú File y elija la página Project. Después, elija
el tipo de proyecto Win32 Dynamic-Link Library, ponga nombre al proyecto y
pulse el botón OK.
A continuación, si ya tiene editados los ficheros que van a formar parte del
proyecto, añádalos al mismo utilizando la orden Files del submenú Add to Project
del menú Project y compile el proyecto.
LLAMANDO A LAS FUNCIONES DE LA DLL
A continuación, vamos a implementar una aplicación SDI que llame a las funcio-
nes de la DLL. Ejecute AppWizard y cree una aplicación denominada ApDll. De-
rive la clase CApDllView de la clase CFormView.
Abra el editor de recursos y personalice los recursos de la aplicación. Edite la
barra de menús para que sólo aparezcan los menús Fichero con la orden Salir y
Ayuda con la orden Acerca de ApDll... Cree una plantilla de diálogo para que al
ejecutar la aplicación se visualice una ventana como la siguiente:
466 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32


Cuando el usuario haga clic en el botón Sumar, aparecerá en la tercera caja la
suma de las dos primeras, y cuando haga clic en Restar, aparecerá la diferencia.
Para realizar la suma y la resta invocaremos a las funciones Sumar y Restar de la
biblioteca dinámica stcdll32.dll.
A continuación se expone el código correspondiente a esta aplicación.
Lo primero que vamos a hacer es, utilizando ClassWizard, vincular la variable
m_Operando1 con la caja de texto IDC_OPERANDO1, la variable m_Operando2
con la caja de texto IDC_OPERAND02 y la variable m_Resultado con la caja de
texto IDC_RESULTADO, todas de tipo double.
A continuación, añada la función OnInitialUpdate a la clase CApDllView y
edítela para que permita ajustar el tamaño de la ventana marco a la vista.
void CApDllView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
ResizeParentToFit( false );
}
El siguiente paso es dar funcionalidad a los botones Sumar y Restar. Para
ello, edite las funciones que se indican a continuación para que se ejecuten como
respuesta al evento clic sobre cada uno de ellos.
void CApDllView::OnSumar()
{
// Sumar
UpdateData( true ); // actualizar variables miembro
m_Resultado = Sumar( m_Operandol, m_Operando2 );
UpdateData( false ); // actualizar cajas de texto
}
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 467


void CApDllView::OnRestar()
{
// Restar
UpdateData( true ); // actualizar variables miembro
m_Resultado = Restar( m_Operandol, m_Operando2 );
UpdateData( false ); // actualizar cajas de texto
}
Observe que para realizar las operaciones de sumar y restar llamamos a las
funciones de la biblioteca stcdll32.dll que hemos creado anteriormente.
Antes de compilar el programa, hay que hacer todavía dos cosas: especificar
los prototipos de las funciones de Sumar y Restar e indicar al compilador la bi-
blioteca que tiene que utilizar para enlazar estas funciones.
Enlace estático
Incluya en el directorio de la aplicación los ficheros stcdll32.h, stcdll32.dll y
stcdll32.lib. Después, añada al fichero ApDllView.cpp la línea siguiente:
#include "stcdll32.h" // prototipos de funciones
El enlace estático necesita de una biblioteca .lib que le informe acerca de los
puntos de la DLL en los que se encuentran las funciones buscadas. Por eso, en
nuestro proyecto tenemos que incluir la biblioteca stcdll32.lib. Para ello, ejecute
la orden Settings del menú Project, elija la página Link y añada en Object/library
modules el nombre stcdll32.lib.

Ahora guarde la aplicación, ejecútela y observe cómo funciona. Como ejerci-
cio, puede modificar la aplicación y añadir otras funciones para otro tipo de ope-
raciones matemáticas, financieras, estadísticas, etc.
468 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Enlace dinámico
Aunque el enlace estático es el más sencillo de realizar, hay veces que es necesa-
rio realizar un enlace dinámico. Por ejemplo, cuando sólo disponemos del fichero
DLL, cuando la aplicación no conoce el nombre de la DLL hasta la ejecución, etc.
El enlace dinámico consiste en cargar la DLL en memoria durante la ejecu-
ción de la aplicación utilizando la función AfxLoadLibrary (o LoadLibrary) de
la API de Windows y averiguar posteriormente cuál es el punto de entrada a la
función que nos interesa utilizar. Para obtener el punto de entrada al que nos he-
mos referido, utilizaremos la función GetProcAddress. Cuando no utilice la DLL
descárguela utilizando la función AfxFreeLibrary (o FreeLibrary). Las aplica-
ciones basadas en las MFC deberían utilizar las funciones Afx... en lugar de sus
equivalentes puesto que han sido diseñadas para manipular la sincronización de
hilos.
HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName );
BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib );
FARPROC GetProcAddress( HMODULE hModule, // handle al modulo DLL
LPCSTR lpProcName // nombre de la función
);
Para cargar una DLL dinámicamente, la aplicación debe realizar las siguientes
operaciones:
 Llamar a AfxLoadLibrary (o LoadLibrary) para cargar la DLL y obtener un
handle al módulo que la define.
 Llamar a GetProcAddress para obtener un puntero a cada una de las funcio-
nes exportadas que la aplicación necesita llamar.
 Llamar a AfxFreeLibrary (o FreeLibrary) cuando finalice el trabajo con la
DLL.
Por ejemplo:
typedef UINT (CALLBACK* PFNDLLFUNC1)(DWORD,UINT);
// ...
HINSTANCE hDLL; // handle a la DLL
PFNDLLFUNC1 pfnDllFunc1; // puntero a una función
DWORD dwParam1;
UINT uParam2, uValRet;

hDLL = AfxLoadLibrary("MiDLL");
if (hDLL != NULL)
{
pfnDllFunc1 = (PFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 469

if (!pfnDllFunc1)
{
// Manipular el error
AfxFreeLibrary(hDLL);
return CODIGO_DE_ERROR;
}
else
{
// Llamar a la función
uValRet = pfnDllFunc1(dwParam1, uParam2);
}
}
Como ejemplo, reproduzca la misma aplicación anterior, pero ahora alma-
cénela en el directorio ApDll2. Para este ejemplo sólo necesita incluir en el direc-
torio de la aplicación el fichero stcdll32.dll.
Añada a la declaración de la clase CApDllView las siguientes declaraciones y
definiciones:
typedef double (CALLBACK* PFNDLLFUNC1)(double, double);
class CApDllView : public CFormView
{
private:
HINSTANCE m_hDLL; // handle a la DLL
PFNDLLFUNC1 m_pfnDllSumar; // puntero a la función Sumar
PFNDLLFUNC1 m_pfnDllRestar; // puntero a la función Restar
// ...
};
A continuación, modifique la función OnInitialUpdate, OnSumar y OnRes-
tar como se indica a continuación:
void CApDllView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
ResizeParentToFit( false );

// Cargar stcdll32.dll
m_hDLL = AfxLoadLibrary("stcdll32");
if (m_hDLL != NULL)
{
m_pfnDllSumar = (PFNDLLFUNC1)GetProcAddress(m_hDLL, "Sumar");
if (!m_pfnDllSumar)
{
AfxFreeLibrary(m_hDLL);
AfxMessageBox("Error al acceder a la función Sumar");
}
470 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

m_pfnDllRestar = (PFNDLLFUNC1)GetProcAddress(m_hDLL, "Restar");
if (!m_pfnDllRestar)
{
AfxFreeLibrary(m_hDLL);
AfxMessageBox("Error al acceder a la función Restar");
}
}
else
AfxMessageBox("No se puede cargar stcdll32.dll");
}

void CApDllView::OnSumar()
{
// Sumar
UpdateData( true ); // actualizar variables miembro
m_Resultado = (*m_pfnDllSumar)( m_Operando1, m_Operando2 );
UpdateData( false ); // actualizar cajas de texto
}

void CApDllView::OnRestar()
{
// Restar
UpdateData( true ); // actualizar variables miembro
m_Resultado = (*m_pfnDllRestar)( m_Operando1, m_Operando2 );
UpdateData( false ); // actualizar cajas de texto
}
Para decrementar el contador de referencias de la DLL cuando la aplicación
finalice (cuando el contador de referencias sea cero, el sistema descargará la DLL
de memoria), invoque a la función AfxFreeLibrary desde el destructor de la cla-
se CApDllView, así:
CApDllView::~CApDllView()
{
AfxFreeLibrary(m_hDLL);
}
RECURSOS EN UNA DLL
Los recursos que una aplicación necesita pueden ser aportados por la propia apli-
cación (fichero de recursos) o por una biblioteca dinámica. Esto es, además de
funciones, una biblioteca dinámica puede contener también recursos, tal como
mapas de bits o ficheros wav, que pueden ser utilizados por cualquier aplicación
Windows que cargue esa biblioteca.
Por ejemplo, supongamos una aplicación Windows que tiene que visualizar
un mapa de bits adaptado a la resolución y número de colores que tenga el siste-
ma. La solución puede ser crear distintos mapas de bits en función de la resolu-
ción de pantalla y del número de colores y que la aplicación cargue el mapa de
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 471

bits adecuado a nuestro sistema. Estos recursos, junto con otros, pueden ser alma-
cenados en bibliotecas dinámicas para obtener un tiempo de ejecución satisfacto-
rio. Como ejemplo, vamos a crear una DLL denominada recvga.dll con los
recursos para un sistema VGA y recsvga.dll con los mismos recursos pero reali-
zados para un sistema SVGA. Los contenidos de las DLL serán los siguientes:
DLL Recursos Descripción
recvga.dll bm4vga.bmp 640×480 - 16 colores
bm8vga.bmp 640×480 - 256 colores
mikeoldf.wav fichero de sonido
recsvga.dll bm4svga.bmp 800×600 - 16 colores
bm8svga.bmp 800×600 - 256 colores
mikeoldf.wav fichero de sonido
Para construir el proyecto que dará lugar a la DLL recvga.dll utilizando Vi-
sual C++, ejecute la orden New del menú File y elija la página Project. Después,
elija el tipo de proyecto Win32 Dynamic-Link Library, ponga el nombre recvga al
proyecto y pulse el botón OK.
A continuación edite los ficheros que van a formar del proyecto y añádalos al
mismo. En este caso, crearemos los ficheros recvga.h (File - New - Files - C/C++
Header File), recvga.cpp (File - New - Files - C/C++ Source File), recvga.def
(File - New - Files - Text File) y recvga.rc (File - New - Files - Resource Script).
Para nuestros propósitos, el contenido de los ficheros recvga.def, recvga.h y
recvga.cpp se reduce a sus esqueletos básicos:
// ----------------------------------------------------
// Nombre del fichero: RECVGA.H
//
// Este fichero de cabecera contiene las funciones
// prototipo para las funciones exportables por la
// DLL denominada RECVGA
//
// Copyright (c) Fco. Javier Ceballos
// ----------------------------------------------------

// Variables globales
// Funciones prototipo
#ifdef __cplusplus //si los ficheros fuente son .cpp
extern "C" {
#endif

// Declaraciones de la funciones de la DLL

#ifdef __cplusplus //si el compilador es C++ ...
}
472 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

#endif

// ----------------------------------------------------
// Nombre del fichero: REGVGA.CPP
//
// Éste es el fichero fuente principal de la DLL,
// el punto de entrada y de salida de la DLL
//
// Copyright (c) Fco. Javier Ceballos
// ----------------------------------------------------

#include <windows.h>
#include "recvga.h"

// ----------------------------------------------------
// Función DllMain
//
// Éste es el punto de entrada y de salida de la DLL.
// Esta función es llamada por Windows. Usted no tiene
// que llamarla desde su aplicación.
//
// Parámetros:
// hModule - el handle para un ejemplar de la DLL
// dwReason - razón por la que ha sido llamada la DLL
// lpReserved - reservado para uso del sistema.
//
// Valor retornado:
// TRUE - indicando que la DLL se ha iniciado
// satisfactoriamente.
// ----------------------------------------------------

BOOL WINAPI DllMain (HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
// Escriba aquí el código que escribía en LibMain (Win16).
// Quizá tenga que hacer alguna modificación por el hecho de
// que puede ser llamada más de una vez.
// Retorne TRUE para salir de la DLL una vez cargada
// o FALSE si la carga falla.
break;

case DLL_THREAD_ATTACH:
// Escriba aquí el código de iniciación que se tiene
// que ejecutar cada vez que se cree un hilo en un proceso
// que ya tiene cargada esta DLL.
break;

case DLL_THREAD_DETACH:
// Escriba aquí el código de terminación que se tiene
// que ejecutar cada vez que un hilo en un proceso sale
// de la DLL que dicho proceso ya tiene cargada.
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 473

break;

case DLL_PROCESS_DETACH:
// Escriba aquí el código que escribía en WEP (Win16).
// Este código quizá no sea necesario porque el
// sistema operativo ya se encarga de esta labor.
break;
}
return TRUE; // DLL_PROCESS_ATTACH satisfactorio
}

// ----------------------------------------------------
// Definición de las funciones de la DLL
// ----------------------------------------------------

;------------------------------------------------------
; Nombre del fichero: RECVGA.DEF
;
; Módulo de definición. Permite crear RECVGA.LIB
;
; Copyright (c) Fco. Javier Ceballos
;------------------------------------------------------

LIBRARY RECVGA

DESCRIPTION 'DLL con recursos'

EXPORTS
;------------------------------------------------------
; Funciones exportadas explícitamente
;------------------------------------------------------
Antes de editar recvga.rc, cree un directorio recvga\res para almacenar los re-
cursos bm4vga.bmp, bm8vga.bmp y mikeoldf.wav correspondientes a esta DLL.
Para construir los mapas de bits puede utilizar la utilidad Paint de Windows.
Para editar recvga.rc, abra el editor de recursos y añada los recursos anterior-
mente especificados.

474 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Finalmente, compile el proyecto. Como resultado obtendrá el fichero rec-
vga.lib y recvga.dll.
Cuando otras aplicaciones utilicen los recursos proporcionados por recvga.dll,
necesitarán conocer sus identificadores. Por lo tanto, cree un fichero idrecvga.h
con dichos identificadores:
// IDRECVGA.H. Identificadores de los recursos

#define IDR_MIKEOLDF_WAV 101
#define IDB_BM4VGA 102
#define IDB_BM8VGA 103
Siguiendo los mismos pasos, cree otro proyecto recsvga que dé lugar a la
DLL recsvga.dll. Cree también el fichero idrecsvga.h con los identificadores de
los recursos proporcionados por recsvga.dll.
Acceso a los recursos en una DLL
Para explicar cómo utilizar las DLLs que acabamos de construir, vamos a crear
una aplicación, Recursos, que utilice los recursos de una u otra DLL en función de
la resolución de nuestro monitor. Además, el fichero wav se ejecutará cuando se
visualice el diálogo Acerca de. Para empezar, genere una nueva aplicación SDI.
Después copie las bibliotecas anteriormente generadas en el directorio de la apli-
cación. Copie también los ficheros de cabecera idrecvga.h y idrecsvga.h.
A continuación, añada al fichero Recursos.h las dos líneas siguientes:
#include "idrecvga.h"
#include "idrecsvga.h"
Cuando se ejecute la aplicación, lo primero que hay que hacer es obtener un
handle a la biblioteca de recursos que se vaya a utilizar; esto dependerá del núme-
ro de colores y de la resolución de pantalla. Para ello, en primer lugar, añada a la
clase CRecursosApp las variables miembro m_hDll, m_BitsPorPixel, m_resx y
m_resy:
class CRecursosApp : public CWinApp
{
public:
CRecursosApp();
HINSTANCE m_hDll;
int m_BitsPorPixel, m_resx, m_resy;
// ...
};
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 475

Después, añada a la función miembro InitInstance de la clase aplicación el
siguiente código:
BOOL CRecursosApp::InitInstance()
{
AfxEnableControlContainer();
// Standard initialization
// ...
LoadStdProfileSettings();

// Comprobar qué DLL hay que cargar
CString strDll;
CDC dc;
// Obtener información del contexto de dispositivo
dc.CreateIC("DISPLAY", NULL, NULL, NULL);
m_BitsPorPixel = dc.GetDeviceCaps( BITSPIXEL );
m_resx = dc.GetDeviceCaps( HORZRES );
m_resy = dc.GetDeviceCaps( VERTRES );
if ((m_resx >= 800) && (m_resy >= 600)) // SVGA
strDll = ".\\recsvga.dll";
else
strDll = ".\\recvga.dll"; // VGA

// Cargar la biblioteca
if ((m_hDll = AfxLoadLibrary(strDll)) == NULL)
{
char mensaje[80];
wsprintf(mensaje, "Error al cargar la DLL %s", strDll);
AfxMessageBox( mensaje );
return FALSE; // Finalizar. Vuelve al S.O.
}

// ...
return TRUE;
}
Observe que la función InitInstance carga la biblioteca dinámica en función
de la resolución. Si la biblioteca que se intenta cargar no se encuentra en el direc-
torio actual de trabajo, la aplicación presenta mediante una caja de diálogo un
mensaje de error y vuelve al sistema operativo.
Cada vez que una aplicación carga una biblioteca, un contador asociado con
la misma es incrementado en una unidad. Cuando la aplicación que ha cargado la
biblioteca finalice, debe liberar la memoria asignada a la misma llamando a la
función AfxFreeLibrary. Lo que hace esta función en realidad es decrementar en
una unidad el contador asociado con la biblioteca. Cuando este contador alcanza
el valor cero, la biblioteca es descargada de memoria; esto es, la memoria asigna-
da a la biblioteca es liberada.
476 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Según lo expuesto añada a la clase CRecursosApp la función miembro Exit-
Instance para que invoque a la función AfxFreeLibrary.
int CRecursosApp::ExitInstance()
{
if (m_hDll) AfxFreeLibrary( m_hDll );

return CWinApp::ExitInstance();
}
Una vez cargada la biblioteca el siguiente paso es ver cómo se accede a los
recursos de la misma.
Por ejemplo, para acceder al recurso de sonido identificado por la cadena de
caracteres IDR_MIKEOLDF_WAV y ejecutar el sonido cuando se visualice el diá-
logo Acerca de ..., modifique en el fichero Recursos.cpp la función OnAppAbout
así:
void CRecursosApp::OnAppAbout()
{
static bool bError = false;
BOOL bCorrecto = FALSE;
if (!bError)
{
// Obtener el handle a los recursos de la aplicación
HINSTANCE hRecsApp = AfxGetResourceHandle();

// Establecer como recursos de la aplicación los de la
// biblioteca cargada en InitInstance
AfxSetResourceHandle( ((CRecursosApp *)AfxGetApp())->m_hDll );

// Tocar el recurso de sonido
bCorrecto = PlaySound(MAKEINTRESOURCE(IDR_MIKEOLDF_WAV),
((CRecursosApp *)AfxGetApp())->m_hDll,
SND_MEMORY | SND_ASYNC | SND_NODEFAULT | SND_RESOURCE);
if (!bCorrecto)
{
AfxMessageBox("No se puede activar el sonido.\n"
"¿El driver es el adecuado?");
bError = true;
}
// Restablecer los recursos iniciales de la aplicación
AfxSetResourceHandle( hRecsApp );
}

// Visualizar el diálogo Acerca de ...
CAboutDlg aboutDlg;
if ( aboutDlg.DoModal() == IDOK ) // visualizar diálogo
{
if (bCorrecto) PlaySound(NULL, NULL, 0);
}
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 477

}
La función anterior primero invoca a AfxGetResourceHandle para obtener
un handle a los recursos actuales de la aplicación. A continuación, utilizando la
función AfxSetResourceHandle, establece como nuevos recursos los proporcio-
nados por la biblioteca cargada. Una vez hecho esto, utiliza los recursos requeri-
dos de la biblioteca (en nuestro caso el recurso de sonido identificado por
IDR_MIKEOLDF_WAV) y cuando termina de utilizarlos, restablece los recursos
iniciales de la aplicación.
Para reproducir el sonido proporcionado por IDR_MIKEOLDF_WAV la fun-
ción OnAppAbout invoca a la función de la API PlaySound (esta función fue co-
mentada en el capítulo “Multimedia”). Recuerde que para utilizar esta función
tiene que incluir el fichero de cabecera mmsystem.h e indicar al enlazador que uti-
lice la biblioteca winmm.lib.
Para acceder a los mapas de bits IDB_BMxxx de la biblioteca dinámica, pro-
ceda de forma análoga. Por ejemplo, vamos a hacer que cuando se ejecute la apli-
cación, se cargue un mapa de bits y se visualice en la vista. El mapa de bits
cargado será adecuado para la resolución y número colores establecidos en nues-
tro sistema. Para realizar este proceso declare, en primer lugar, las siguientes va-
riables miembro de la clase CRecursosView:
class CRecursosView : public CFormView
{
private:
CDC *m_pMemDCPantalla; // DC del área de trabajo
HBITMAP m_hBitmapAnterior;
int m_nAncho, m_nAlto; // tamaño del mapa de bits
// ...
};
Después, añada a la clase CRecursosView la función miembro OnInitialUp-
date. Esta función obtiene de los recursos almacenados en la biblioteca dinámica
el mapa de bits adecuado a la resolución y número de colores del sistema para ser
seleccionado por un contexto de dispositivo de memoria compatible con la vista;
dicho contexto será utilizado posteriormente por la función OnDraw para pintar
el mapa de bits en la vista.
void CRecursosView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

BOOL bCorrecto = FALSE;

// Crear un DC en memoria compatible con el área de trabajo
CClientDC dc( this );
478 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

m_pMemDCPantalla = new CDC;
m_pMemDCPantalla->CreateCompatibleDC( &dc );

// Obtener el handle a los recursos iniciales de la aplicación
HINSTANCE hRecsApp = AfxGetResourceHandle();
// Establecer como recursos de la aplicación los de la biblioteca
AfxSetResourceHandle( ((CRecursosApp *)AfxGetApp())->m_hDll );

// Cargar el mapa de bits que se va a visualizar
CRecursosApp *pApp = (CRecursosApp *)AfxGetApp();
CBitmap *pBitmapAnterior, *pBitmapActual = new CBitmap;
if ((pApp->m_resx >= 800) && (pApp->m_resy >= 600)) // SVGA
if (pApp->m_BitsPorPixel >= 8) // 256 o más colores
bCorrecto = pBitmapActual->LoadBitmap( IDB_BM8SVGA );
else
bCorrecto = pBitmapActual->LoadBitmap( IDB_BM4SVGA );
else
if (pApp->m_BitsPorPixel >= 8) // 256 o más colores
bCorrecto = pBitmapActual->LoadBitmap( IDB_BM8VGA );
else
bCorrecto = pBitmapActual->LoadBitmap( IDB_BM4VGA );
if (!bCorrecto)
AfxMessageBox("No se puede cargar el mapa de bits");

// Seleccionar el mapa de bits para el DC en memoria
pBitmapAnterior = m_pMemDCPantalla->SelectObject(pBitmapActual);

// Guardar el handle del mapa de bits anterior
m_hBitmapAnterior = (HBITMAP)pBitmapAnterior->GetSafeHandle();

// Restablecer los recursos propios de la aplicación
AfxSetResourceHandle( hRecsApp );

// Dimensiones del mapa de bits fuente
BITMAP bm; // estructura de datos BITMAP
pBitmapActual->GetObject( sizeof(bm), &bm );
CRect rect(0, 0, bm.bmWidth, bm.bmHeight);
dc.DPtoLP( &rect ); // tamaño del mapa de bits en unidades lógicas
m_nAncho = rect.Width();
m_nAlto = rect.Height();
}
Como hemos dicho, la función OnDraw miembro de CRecursosView pintará
el mapa de bits seleccionado en el contexto de dispositivo de memoria m_pMem-
DCPantalla cada vez que la ventana se repinte.
void CRecursosView::OnDraw(CDC* pDC)
{
// Ver si hay un mapa de bits presente
if (m_pMemDCPantalla == NULL) return; // No hay mapa de bits

// Visualizar el mapa de bits
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 479

pDC->BitBlt( // DC destino
0, 0, // origen
m_nAncho, // ancho
m_nAlto, // alto
m_pMemDCPantalla, // DC fuente
0, 0, // origen
SRCCOPY ); // operación
}
Finalmente, utilice el destructor de la clase CRecursosView para liberar los
recursos asignados cuando la vista deje de existir.
CRecursosView::~CRecursosView()
{
// Eliminar el mapa de bits
if ( m_hBitmapAnterior )
{
CBitmap *pbm = CBitmap::FromHandle( m_hBitmapAnterior );
delete m_pMemDCPantalla->SelectObject( pbm );
}
// Eliminar el DC de memoria
delete m_pMemDCPantalla;
}
OBJETOS COM COMO ALTERNATIVA A LAS DLLs
En un capítulo anterior expusimos cómo crear objetos COM utilizando la biblio-
teca ATL. En esa exposición vimos que Visual C++ proporciona un asistente,
ATL COM AppWizard, que permite crear, entre otros, proyectos ATL de tipo
DLL.

480 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

El proyecto creado por ATL COM AppWizard está inicialmente sin objetos
COM. Para añadir un objeto COM utilizaremos la orden New ATL Object de
ClassView. Esta orden abre un asistente que expone los distintos tipos de objetos
que podemos añadir. Por ejemplo, en la categoría objetos hay un objeto COM con
una funcionalidad mínima (Simple object) que puede ser el idóneo para presentar
una alternativa a las DLL que se han expuesto anteriormente.
Como ejemplo, vamos a construir un objeto COM como alternativa a la DLL
stcdll32.dll que construimos al principio de este capítulo. Para ello:
1. Cree un nuevo proyecto de tipo ATL COM Wizard denominado stccom32. Es-
te proyecto dará lugar al fichero stccom32.dll.
2. Seleccione como tipo de servidor, Dynamic Link Library (DLL).
Una vez creado el proyecto, procedemos a añadir un objeto COM simple. Pa-
ra ello, abra ATL Object Wizard desde ClassView o, ejecutando la orden New ATL
Object del menú Insert de Developer Studio, seleccione Objects en el panel iz-
quierdo de ATL Object Wizard y Simple Object en el panel derecho.

Después de pulsar el botón Next en el diálogo anterior, se muestra el diálogo
de propiedades que nos permitirá definir el nombre del objeto, de la clase C++ y
de la clase COM que soportarán el objeto. Seleccione la página Names y escriba
en la caja Short Name el nombre MathCOM. El resto de las cajas se llenarán au-
tomáticamente.
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 481


Esqueleto de la aplicación
El esqueleto de la aplicación que hemos generado a través de los asistentes ATL
COM Wizard y ATL Object Wizard queda resumido en la figura siguiente:

Observamos tres partes bien diferenciadas: las funciones globales, la clase
que encapsula el objeto COM y la interfaz que nos da acceso a la funcionalidad
del objeto COM.
Un objeto COM es parte de una biblioteca dinámica. Por lo tanto, primero ha-
brá que escribir el código que dé lugar a la biblioteca dinámica y después añadi-
remos a la misma objetos con sus interfaces. Precisamente lo que hace ATL COM
Wizard es generar los ficheros necesarios para construir la DLL; en nuestro caso,
estos ficheros son básicamente: stccom32.def, stccom32.cpp, stccom32.idl y stc-
com32.rc.
Stccom32.cpp es el fichero principal de la aplicación y contiene las funciones
globales: entre ellas cabe destacar la función DllMain, que es el punto de entrada
y de salida para la DLL; stccom32.def declara los parámetros del módulo stc-
com32.dll que se construirá finalmente; stccom32.idl describe las interfaces de los
objetos utilizando el lenguaje IDL (Interface Definition Language); y stccom32.rc
aporta los recursos para la biblioteca.
482 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

Cuando compile el proyecto ATL, el compilador MIDL generará un fichero
stccom32.h, el cual define, desde el punto de vista de C++, las interfaces y clases
disponibles en el fichero stccom32.idl.
Cuando añadimos un objeto COM a la biblioteca, se genera una clase que en-
capsula el objeto, así como una interfaz de acceso a la funcionalidad del objeto.
Este trabajo es realizado por ATL Object Wizard. En nuestro caso fue añadida al
proyecto la clase CMathCOM, cuyo código lo podemos localizar en los ficheros
MathCOM.h y MathCOM.cpp. Inicialmente dicho objeto no aporta ninguna fun-
cionalidad. Precisamente el trabajo que tenemos que realizar a continuación es
proveer al objeto de la interfaz requerida para que cumpla el objetivo del diseño.
Concretamente nuestro objetivo requiere los métodos Sumar y Restar.
Añadir métodos
Para añadir un método al objeto COM, seleccione ClassView en la ventana
WorkSpace, apunte con el ratón a la interfaz IMathCOM y haga clic con el botón
derecho del ratón. En el menú contextual que se visualiza, seleccione la orden Add
Method. Aparecerá un diálogo como el que se muestra a continuación que le per-
mitirá introducir el nombre y los parámetros del método:

Observe que la función devuelve un valor de tipo HRESULT que general-
mente se corresponde con un valor distinto de cero si la función se ejecuta con
éxito, o cero en caso contrario. Por este motivo, nuestra función Sumar tiene tres
parámetros, los dos primeros para los operandos y el tercero para el resultado.
Una vez introducidos los datos que se muestran en la figura anterior, pulse el
botón OK. En este instante acaba de añadir la función Sumar como miembro de la
clase CMathCOM. Dicha función es accesible a través de la interfaz IMathCOM.
A continuación, edite la función Sumar así:
STDMETHODIMP CMathCOM::Sumar(double Param1, double Param2, double * Param3)
{
*Param3 = Param1 + Param2;

CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 483

return S_OK;
}
Según expusimos en el capítulo de ATL, es aconsejable realizar las siguientes
modificaciones en el fichero stccom32.idl:
// stccom32.idl
// ...

typedef enum prop_dispid
{
DISPID_SUMAR = 1,
}PROP_DISPID;

// ...

interface IMathCOM : IDispatch
{
[id(DISPID_SUMAR), helpstring("method Sumar")]
HRESULT Sumar(double Param1, double Param2, double *Param3);
};
A continuación, procediendo de forma análoga, añada la función Restar.
Añada también el identificador DISPID_RESTAR.
STDMETHODIMP CMathCOM::Restar(double Param1, double Param2, double * Param3)
{
*Param3 = Param1 - Param2;
return S_OK;
}
Con esto, hemos finalizado la implementación de la biblioteca dinámica.
Compile ahora el proyecto ATL para obtener el fichero stccom32.dll. Cuando Mi-
crosoft Developer Studio finaliza la compilación de la biblioteca dinámica, la re-
gistra en el registro de Windows. Posteriormente, cuando una aplicación utilice
esa biblioteca, Windows recurrirá a su registro para saber en qué directorio se en-
cuentra. Por lo tanto, no sirve cambiar la biblioteca de directorio; ni siquiera al di-
rectorio System.
¿Qué tiene que hacer para registrar la biblioteca en otra posición? Dos cosas:
desregistrarla de la posición actual y volverla a registrar en la nueva posición, uti-
lizando el programa regsvr32 que se encuentra en el directorio System. Por ejem-
plo, eligiendo la orden Ejecutar del menú Inicio, puede emitir las siguientes
órdenes:
 Desregistrar stccom32.dll del directorio actual:
regsvr32 /u "C:\Ejemplos\stccom32\Release\stccom32.dll"
484 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

 Registrar stccom32.dll en el directorio System. Primero copie stccom32.dll en
el directorio System:
regsvr32 "C:\Windows\System\stccom32.dll"
Utilización del servidor COM
Una parte crítica de COM es cómo interactúan los clientes y servidores. Un servi-
dor COM es cualquier objeto que proporciona servicios a los clientes. Estos servi-
cios aparecen en forma de implementaciones de interfaces COM que pueden ser
llamadas por cualquier cliente que puede conseguir un puntero a una de las inter-
faces en el objeto servidor. Hay dos tipos principales de servidores, in-process (se
ejecuta en el espacio de proceso del controlador) y out-of-process (se ejecuta en
su propio espacio de proceso). Los servidores in-process son implementados en
una biblioteca dinámica (DLL), y los servidores out-of-process son implementa-
dos en un archivo EXE. Además, los servidores out-of-process pueden residir en
una máquina local o remota.
Como ejemplo de utilización del servidor stccom32.dll que acabamos de
construir, vamos a reproducir la aplicación ApDll que realizamos al principio de
este capítulo, y que ahora guardaremos en un directorio ApDll3. Incluya en el di-
rectorio de la aplicación los ficheros stccom32.h (definición de la interfaz IMath-
COM) y stccom32_i.c (identificadores COM).
En el capítulo de “Componentes Software” vimos que para que un cliente tu-
viera acceso a un componente software (anteriormente llamado componente OLE
y ahora objeto COM), era necesario que dicho cliente iniciara la biblioteca diná-
mica OLE, para lo cual tenía que invocar a la función AfxOleInit, operación que
realizaremos desde InitInstance así:
BOOL CApDllApp::InitInstance()
{
AfxOleInit(); // necesaria para COM
// ...
}
Como ya sabemos, una aplicación cliente, como es ApDll, puede acceder a un
objeto COM solamente a través de un puntero a una de sus interfaces, el cual, a su
vez, permitirá al cliente llamar a cualquiera de los métodos que componen la in-
terfaz. Nuestro objeto COM tiene una sola interfaz, IMathCOM. Por lo tanto, lo
que tenemos que hacer ahora es obtener un puntero a esta interfaz.
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 485

Obtener un puntero a una interfaz
Realmente, una instancia de la interfaz IMathCOM es un puntero a un array de
punteros a los métodos especificados en dicha interfaz (para más detalles, consulte
el capítulo de “Componentes Software”).
Ya que COM no tiene un modelo de clase estricto, hay varias maneras de ob-
tener un puntero a una interfaz de un objeto. Una de ellas puede ser llamar a una
función de la API de la biblioteca COM que pueda crear un objeto en función de
un identificador de clase (CLSID) y que devuelva un puntero a la interfaz solicita-
da. Por ejemplo:
IMathCOM *m_pIMathCOM;
CoCreateInstance(CLSID_MathCOM,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMathCOM,
(void **)&m_pIMathCOM))
La función CoCreateInstance crea un objeto sin iniciar, de la clase que tiene
el CLSID especificado, en el sistema local (para crear un único objeto en un sis-
tema remoto hay que utilizar CoCreateInstanceEx, y para crear múltiples objetos
con el mismo CLSID dispone de la función CoGetClassObject). El primer pará-
metro es el CLSID asociado con los datos y el código que sería utilizado para
crear el objeto. El segundo parámetro, si es NULL indica que el objeto no se
construye a partir de otro; si no es NULL, entonces es un puntero a la interfaz
IUnknown del objeto agregado. El tercer parámetro indica el contexto en el que
se ejecutará el código que manipula el nuevo objeto. El cuarto parámetro es el
identificador de la interfaz utilizada para comunicar con el objeto. Y el quinto pa-
rámetro es la dirección de la variable puntero que almacenará el puntero a la inter-
faz requerida.
Según esto, añada el siguiente código al fichero ApDllView.h:
// ApDllView.h : interface of the CApDllView class
// ...
// Necesario para utilizar la biblioteca stccom32.dll
#include "stccom32.h" // interfaz IMathCOM

class CApDllView : public CFormView
{
// ...
protected:
IMathCOM *m_pIMathCOM;
// ...
};
486 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

A continuación, añada a la función OnInitialUpdate el código necesario para
obtener el puntero m_pIMathCOM a la interfaz IMathCOM:
void CApDllView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

m_pIMathCOM = 0;
try
{
if (FAILED(CoCreateInstance(CLSID_MathCOM,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMathCOM,
(void **)&m_pIMathCOM)))
throw(_T("¿Tiene registrado el objeto COM?"));
}
catch(_com_error ErrorCom)
{
// Error COM.
throw(ErrorCom.ErrorMessage());
}
catch(TCHAR* pChar)
{
MessageBox( pChar, _T("Error en la aplicación"), MB_ICONERROR);
GetParentFrame()->PostMessage(WM_CLOSE);
}

// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
ResizeParentToFit( false );
}
La función CoCreateInstance es una forma breve de conectar con un objeto
de la clase asociada con el CLSID especificado, creando una instancia no iniciada,
y liberando el objeto de la clase. Así que, encapsula la funcionalidad siguiente:
IClassFactory *pCF;
CoGetClassObject(CLSID_MathCOM, CLSCTX_INPROC_SERVER, NULL,
IID_IClassFactory,
(void **)&pCF);
HRESULT hresult = pCF->CreateInstance(NULL,
IID_IMathCOM,
(void **)&m_pIMathCOM);
pCF->Release(); // decrementa el contador de referencias para
// la interfaz IClassFactory
Para entender el código anterior tiene que saber que cada objeto COM de un
servidor implementa automáticamente una interfaz IClassFactory. Esta clase es
la responsable de crear instancias de la clase de objeto COM que la soporta (es
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 487

análoga al operador new de C++). IClassFactory está derivada de IUnknown y
contiene los métodos: CreateInstance y LockServer. CreateInstance se utiliza
para crear una instancia de una clase COM y LockServer incrementa o decremen-
ta un contador de referencias dentro del servidor COM; cuando este contador es
mayor que cero, el servidor no puede ser descargado de memoria. Según lo ex-
puesto, es posible obtener un puntero a una interfaz del objeto COM a través del
puntero a su interfaz IClassFactory así:
1. Determinar el identificador de la clase del objeto COM del cual se quiere
crear una instancia. En nuestro caso CLSID_MathCOM.
2. Obtener el puntero a la interfaz IClassFactory para el CLSID especificado.
IClassFactory *pCF;
CoGetClassObject(CLSID_MathCOM, CLSCTX_INPROC_SERVER, NULL,
IID_IClassFactory,
(void **)&pCF);
3. Crear una instancia no iniciada de la clase del objeto COM utilizando el mé-
todo CreateInstance de IClassFactory. La interfaz requerida desde el objeto
debe ser aquella que el cliente pide cuando crea una instancia de la clase del
objeto COM. De esta forma se obtiene un puntero a dicha interfaz.
HRESULT hresult = pCF->CreateInstance(NULL,
IID_IMathCOM,
(void **)&m_pIMathCOM);
pCF->Release();
La macro FAILED permite verificar si existe algún fallo. Si CoCreateIns-
tance devuelve un valor negativo es que ha ocurrido un fallo.
Un objeto _com_error es una excepción detectada por los manipuladores de
error en los ficheros de cabecera generados a partir de la biblioteca de tipos o por
alguna de las clases que soportan COM. La clase _com_error está definida en
comdef.h. Por lo tanto, debe incluir este fichero en ApDllView.cpp.
Los identificadores de clase (CLSID) y de interfaz (IID) pasados como argu-
mentos en la llamada a la función CoCreateInstance, están definidos en el fiche-
ro stccom32_i.c creado por el compilador MIDL cuando generamos la biblioteca
dinámica stccom32.dll. Por lo tanto, incluya este fichero en ApDllView.cpp:
// ApDllView.cpp : implementation of the CApDllView class
// ...

#include "ApDllDoc.h"
#include "ApDllView.h"

// Necesario para utilizar la biblioteca stccom32.dll
488 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

#include "stccom32_i.c" // identificadores COM
#include "comdef.h" // necesario para _com_error
Llamando a las funciones de la interfaz
Obtenido el puntero a la interfaz y suponiendo que ya ha reconstruido la aplica-
ción ApDll para que muestre la siguiente interfaz gráfica, el siguiente paso es
asignar funcionalidad a los botones Sumar y Restar.

Análogamente a como procedimos cuando desarrollamos esta aplicación al
principio de este capítulo, edite las siguientes funciones que tienen que ejecutarse
como respuesta al evento clic sobre cada uno de los botones.
void CApDllView::OnSumar()
{
// Sumar
UpdateData( true ); // actualizar variables miembro
m_pIMathCOM->Sumar( m_Operando1, m_Operando2, &m_Resultado );
UpdateData( false ); // actualizar cajas de texto
}

void CApDllView::OnRestar()
{
// Restar
UpdateData( true ); // actualizar variables miembro
m_pIMathCOM->Restar( m_Operando1, m_Operando2, &m_Resultado );
UpdateData( false ); // actualizar cajas de texto
}
Observe que para realizar las operaciones de sumar y restar, llamamos a las
funciones Sumar y Restar de la biblioteca stccom32.dll a través del puntero
m_pIMathCOM que acabamos de obtener. Los prototipos de estas funciones están
declarados en el fichero stccom32.h.
Ahora ya puede compilar la aplicación. En este caso no es necesario indicarle
al enlazador la biblioteca que tiene que utilizar para enlazar estas funciones, por-
que, como dijimos anteriormente, esta información la obtiene el sistema del regis-
tro de Windows por tratarse de un objeto COM registrado.
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 489

Clase _com_ptr_t
Otra alternativa para obtener un puntero a una interfaz de un objeto COM es utili-
zar la funcionalidad de la plantilla de clase _com_ptr_t definida en comdef.h.
Un objeto _com_ptr_t encapsula un puntero a una interfaz COM (smart poin-
ter). La plantilla _com_ptr_t manipula la asignación y liberación de los recursos
necesarios, a través de llamadas a las funciones miembro de la interfaz
IUnknown: QueryInterface, AddRef, y Release, lo cual significa que estamos
liberados de realizar este tipo de llamadas.
Un puntero de este tipo es un objeto de una clase, obtenida a partir de la plan-
tilla _com_ptr_t, particularizada para una determinada interfaz COM. Esta clase
se obtiene a través de la macro:
_COM_SMARTPTR_TYPEDEF(IMiInterfaz, __uuidof(IMiInterfaz));
Esta macro toma como parámetros el nombre de la interfaz y su IID y cons-
truye una clase particularizada para dicha interfaz de nombre, el nombre de la in-
terfaz más el sufijo Ptr. Por ejemplo, la línea anterior daría lugar a la clase
IMiInterfazPtr. A su vez, la macro __uuidof recupera el GUID asociado con el
parámetro especificado.
Como ejemplo, vamos a modificar la aplicación ApDll creada en el ejemplo
anterior para que ahora utilice la plantilla _com_ptr_t para obtener el puntero a la
interfaz IMathCOM. Esta versión la guardaremos en el directorio ApDll4. En este
caso procederemos así:
1. Asegúrese de que InitInstance invoca a AfxOleInit:
BOOL CApDllApp::InitInstance()
{
AfxOleInit(); // necesaria para COM
// ...
}
2. Añada al fichero ApDllView.h el código indicado a continuación:
// ApDllView.h : interface of the CApDllView class
// ...
#include "stccom32.h" // interfaz IMathCOM
#include "comdef.h" // necesario para _com_ptr_t y _com_error

// La siguiente macro define la clase IMathCOMPtr
// a partir de la plantilla _com_ptr_t
_COM_SMARTPTR_TYPEDEF(IMathCOM, __uuidof(IMathCOM));
490 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32


class CApDllView : public CFormView
{
// ...
protected:
// m_pIMathCOM encapsula el puntero a la interfaz IMathCOM
IMathCOMPtr m_pIMathCOM;

// ...
};
3. Añada al fichero ApDllView.cpp el código indicado a continuación:
// ApDllView.cpp : implementation of the CApDllView class
// ...

#include "stccom32_i.c" // identificadores COM de IMathCOM
// ...

void CApDllView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

try
{
if ( FAILED(m_pIMathCOM.CreateInstance(CLSID_MathCOM)))
throw(_T("¿Tiene registrado el objeto COM?"));
}
catch(_com_error ErrorCom)
{
// Error COM.
throw(ErrorCom.ErrorMessage());
}
catch(TCHAR* pChar)
{
MessageBox( pChar, _T("Error en la aplicación"), MB_ICONERROR);
GetParentFrame()->PostMessage(WM_CLOSE);
}

// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
ResizeParentToFit( false );
}
4. Edite las funciones OnSumar y OnRestar de la misma forma que lo hizo ante-
riormente.
La función CreateInstance miembro de _com_ptr_t llama a CoCreateIns-
tance para crear una instancia de un objeto de la clase asociada con el CLSID es-
pecificado y obtener así el puntero a su interfaz IMathCOM, encapsulado en el
objeto m_pIMathCOM de la clase IMathCOMPtr derivada de la plantilla
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 491

_com_ptr_t. También es llamada la función miembro Release para decrementar
el contador de referencias del puntero previamente encapsulado. Esta rutina de-
vuelve un valor HRESULT para indicar el éxito o fallo de la operación.
Observe que las líneas:
IMathCOMPtr m_pIMathCOM;
m_pIMathCOM.CreateInstance(CLSID_MathCOM);
equivalen a:
IMathCOMPtr m_pIMathCOM(CLSID_MathCOM);
Como m_pIMathCOM es un objeto de la clase IMathCOMPtr, una llamada de
la forma:
m_pIMathCOM->Sumar( m_Operando1, m_Operando2, &m_Resultado );
invoca a la función miembro operator-
>
() (sobrecarga al operador -
>
) que de-
vuelve el puntero a la interfaz, encapsulado en dicho objeto.
Directriz #import
Otra alternativa para obtener un puntero a una interfaz de un objeto COM es utili-
zar la directriz #import. Esta directriz permite incorporar información de una bi-
blioteca de tipos. El contenido de la biblioteca de tipos es convertido en clases
C++ que describen las interfaces COM. Su sintaxis es de la forma siguiente:
#import "fichero" [atributos]
#import <fichero> [atributos]
donde fichero es el nombre del fichero que contiene la información de la bibliote-
ca de tipos. El fichero puede ser alguno de los tipos siguientes:
 Una biblioteca de tipos (fichero .TLB o .ODL).
 Un fichero ejecutable (.EXE).
 Una biblioteca dinámica que contenga los recursos de la biblioteca de tipos (tal
como un .OCX o un .DLL).
 Un documento compuesto que posea la biblioteca de tipos.
 Cualquier otro formato de fichero que pueda ser admitido por la función de la
API LoadTypeLib.
Los atributos especificados en #import indican al compilador acciones espe-
ciales que debe tomar. Por ejemplo, los contenidos de la biblioteca de tipos impor-
tada a través de un fichero de cabecera son normalmente definidos en un espacio
492 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

de nombres (namespace) cuyo nombre se especifica en la sentencia library en el
fichero IDL original. Para indicarle al compilador que no genere de nuevo este
espacio de nombres, hay que especificar el atributo no_namespace:
#import "stccom32.tlb" no_namespace
La directriz #import genera dos ficheros de cabecera, con el mismo nombre
de la biblioteca y extensiones .TLH y .TLI, que reconstruyen la biblioteca de tipos
en clases C++. Por ejemplo, la directriz anterior generaría stccom32.tlh y stc-
com32.tli. Estos ficheros pueden ser incluidos en la aplicación cliente, utilizando
una directriz #include, en el lugar donde sean necesarios.
El código del fichero .TLH de forma resumida hace lo siguiente:
 Incluye el fichero de cabecera comdef.h que contiene declaraciones como las
correspondientes a _com_ptr_t y _com_error.
 Define las interfaces y clases de los objetos incluidas en el fichero IDL origi-
nal. Por ejemplo IMathCOM y MathCOM.
 Invoca a la macro _COM_SMARTPTR_TYPEDEF que permite generar una
clase (de la que hemos hablado en el apartado anterior) a partir de la plantilla
_com_ptr_t, particularizada para una determinada interfaz COM. Un objeto
de esta clase envuelve un puntero a dicha interfaz.
 Incluye el fichero .TLI que contiene las definiciones de las funciones miem-
bro de las interfaces.
Ambos ficheros, .TLH y .TLI, una vez generados son leídos y compilados por
el compilador como si se hubiera incluido en el código de la aplicación la directriz
#include para el fichero .TLH. Por ejemplo:
#include "stccom32.tlh"
Como ejemplo, vamos a modificar la aplicación ApDll creada en el ejemplo
anterior para que ahora utilice la directriz #import para obtener el puntero a la in-
terfaz IMathCOM. Esta versión la guardaremos en el directorio ApDll5. En este
caso procederemos así:
1. Copie en el directorio de la aplicación cliente sólo el fichero stccom32.tlb.
2. Asegúrese de que InitInstance invoca a AfxOleInit:
BOOL CApDllApp::InitInstance()
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 493

{
AfxOleInit(); // necesaria para COM
// ...
}
3. Añada al fichero ApDllView.h el código indicado a continuación:
// ApDllView.h : interface of the CApDllView class
// ...
// Importar la biblioteca de tipos. Permite, a través de la
// macro _COM_SMARTPTR_TYPEDEF, definir la clase IMathCOMPtr
// a partir de la plantilla _com_ptr_t

#import "stccom32.tlb" no_namespace

class CApDllView : public CFormView
{
// ...
protected:
// m_pIMathCOM encapsula el puntero a la interfaz IMathCOM
IMathCOMPtr m_pIMathCOM;

// ...
};
4. Añada al fichero ApDllView.cpp las mismas funciones OnInitialUpdate, On-
Sumar y OnRestar que utilizó en el apartado anterior y realice sobre la fun-
ción OnInitialUpdate la siguiente modificación: sustituya CLSID_MathCOM
por la expresión __uuidof(MathCOM). El resultado es el mismo, el CLSID de
la clase pero sin tener que incluir otro fichero de cabecera.
void CApDllView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
try
{
if ( FAILED(m_pIMathCOM.CreateInstance(__uuidof(MathCOM)))
throw(_T("¿Tiene registrado el objeto COM?"));
}
catch(_com_error ErrorCom)
{
// Error COM.
throw(ErrorCom.ErrorMessage());
}
catch(TCHAR* pChar)
{
MessageBox( pChar, _T("Error en la aplicación"), MB_ICONERROR);
GetParentFrame()->PostMessage(WM_CLOSE);
}
// Ajustar el tamaño de la ventana marco a la vista
GetParentFrame()->RecalcLayout();
494 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

ResizeParentToFit( false );
}
Añadir otra interfaz
En algunas ocasiones necesitaremos añadir una nueva interfaz a un objeto COM
existente. En este apartado vamos a exponer los pasos que debe seguir para reali-
zar este proceso.
Vamos a realizar un ejemplo partiendo del objeto COM que creamos ante-
riormente y que almacenamos en la biblioteca stccom32.dll. Cargue, entonces, el
proyecto stccom32 y abra el fichero stccom32.idl (en el disco que acompaña al li-
bro, este proyecto está almacenado en el directorio interfaz2 de este capítulo). A
continuación añada una nueva interfaz IMathExCOM derivada de IUnknown. Es-
to requiere generar un identificador global único para asignárselo al atributo uuid
de la interfaz. Genere el UUID (universally unique identifier) utilizando el pro-
grama guidgen.exe proporcionado por Visual C++. Finalmente, añada a la defini-
ción coclass MathCOM de la sentencia library el nombre de la nueva interfaz. La
sentencia library contiene toda la información que el compilador MIDL necesita
para generar la biblioteca de tipos.
// stccom32.idl : IDL source for stccom32.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (stccom32.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
typedef enum prop_dispid
{
DISPID_SUMAR = 1,
DISPID_RESTAR = 2,
}PROP_DISPID;

[
object,
uuid(934D4B9F-1C0B-11D2-8197-896206EF2C3A),
dual,
helpstring("IMathCOM Interface"),
pointer_default(unique)
]
interface IMathCOM : IDispatch
{
[id(DISPID_SUMAR), helpstring("method Sumar")]
HRESULT Sumar(
double Param1, double Param2, double *Param3);
[id(DISPID_RESTAR), helpstring("method Restar")]
HRESULT Restar(
double Param1, double Param2, double *Param3);
};
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 495


[
object,
uuid(AF9CD1C0-2023-11d2-8197-F60ED797973D),
dual,
helpstring("IMathExCOM Interface"),
pointer_default(unique)
]
interface IMathExCOM : IUnknown
{

};

[
uuid(934D4B92-1C0B-11D2-8197-896206EF2C3A),
version(1.0),
helpstring("stccom32 1.0 Type Library")
]

library STCCOM32Lib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(934D4BA0-1C0B-11D2-8197-896206EF2C3A),
helpstring("MathCOM Class")
]
coclass MathCOM
{
[default] interface IMathCOM;
interface IMathExCOM;
};
};
El siguiente paso es editar el fichero MathCOM.h para especificar que la clase
del objeto CMathCOM se derivará también de IMathExCOM.
A su vez, sabemos que el mapa COM conecta la interfaz IUnknown con to-
das las interfaces soportadas por el objeto. Todos los objetos COM deben imple-
mentar una interfaz IUnknown para que a través de su función miembro
QueryInterface podamos determinar qué otras interfaces soporta el control y ob-
tener, cuando sea preciso, un puntero a ellas. Por lo tanto, debemos añadir tam-
bién a este mapa una entrada que especifique la nueva interfaz.
// MathCOM.h : Declaration of the CMathCOM
#ifndef __MATHCOM_H_
#define __MATHCOM_H_

#include "resource.h" // main symbols

496 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

//////////////////////////////////////////////////////////////////
// CMathCOM
class ATL_NO_VTABLE CMathCOM :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMathCOM, &CLSID_MathCOM>,
public IDispatchImpl<IMathCOM,&IID_IMathCOM,&LIBID_STCCOM32Lib>,
public IMathExCOM
{
public:
CMathCOM()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_MATHCOM)

BEGIN_COM_MAP(CMathCOM)
COM_INTERFACE_ENTRY(IMathCOM)
COM_INTERFACE_ENTRY(IMathExCOM)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IMathExCOM
public:

// IMathCOM
public:
STDMETHOD(Restar)(double Param1, double Param2, double *Param3);
STDMETHOD(Sumar)(double Param1, double Param2, double *Param3);
};
#endif //__MATHCOM_H_
Con esto ha finalizado el proceso de añadir una nueva interfaz. Ahora, desde
ClassView, puede añadir los métodos que crea necesarios, igual que hizo cuando
añadió los métodos Sumar y Restar a la interfaz IMathCom. Como ejemplo, añada
los métodos Multiplicar y Dividir que se muestran a continuación:
typedef enum prop_dispid
{
DISPID_SUMAR = 1,
DISPID_RESTAR = 2,
DISPID_MULTIPLICAR = 3,
DISPID_DIVIDIR = 4,
}PROP_DISPID;

// ...

interface IMathExCOM : IUnknown
{
[id(DISPID_MULTIPLICAR), helpstring("method Multiplicar")]
HRESULT Multiplicar(
double Param1, double Param2, double *Param3);
[id(DISPID_DIVIDIR), helpstring("method Dividir")]
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 497

HRESULT Dividir(
double Param1, double Param2, double *Param3);
};
A continuación, edite las funciones que acaba de añadir:
STDMETHODIMP CMathCOM::Multiplicar(double Param1, double Param2, double * Param3)
{
*Param3 = Param1 * Param2;

return S_OK;
}

STDMETHODIMP CMathCOM::Dividir(double Param1, double Param2, double * Param3)
{
*Param3 = Param1 / Param2;

return S_OK;
}
Después de esto, ha finalizado la implementación de la nueva interfaz. Ahora,
puede compilar el proyecto.
Para probar la nueva interfaz de nuestro objeto COM, vamos a modificar el
proyecto ApDll anterior (en el disco que acompaña al libro, el proyecto resultante
está almacenado en el directorio interfaz2\ApDll6 de este capítulo). La idea es
añadir a la interfaz gráfica dos nuevos botones, Multiplicar y Dividir, y asociarles
con las funciones manipuladoras correspondientes, para que utilizando los méto-
dos Multiplicar y Dividir de la interfaz IMathExCOM realicen las operaciones es-
peradas. Según lo expuesto, siga los siguientes pasos:
 Copie el fichero stccom32.tbl que acaba de generar en el proyecto stccom32,
en el directorio ApDll6 de la aplicación.
 Abra el editor de recursos y añada dos nuevos botones, Multiplicar y Dividir,
a la interfaz gráfica.
 Edite las funciones manipuladoras del evento clic para estos botones:
void CApDllView::OnMultiplicar()
{
// Multiplicar
UpdateData( true ); // actualizar variables miembro
m_pIMathExCOM->Multiplicar( m_Operando1, m_Operando2, &m_Resultado );
UpdateData( false ); // actualizar cajas de texto
}

void CApDllView::OnDividir()
498 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

{
// Dividir
UpdateData( true ); // actualizar variables miembro
m_pIMathExCOM->Dividir( m_Operando1, m_Operando2, &m_Resultado );
UpdateData( false ); // actualizar cajas de texto
}
 Defina, análogamente a como definió el objeto m_pIMathCOM, el objeto
m_pIMathExCOM como miembro de la clase CApDllView, para que a través
de él podamos referenciar la nueva interfaz IMathExCOM del objeto COM.
// Importar la biblioteca de tipos
#import "stccom32.tlb" no_namespace
class CApDllView : public CFormView
{
// ...
protected:
// m_pIMathCOM encapsula el puntero a la interfaz IMathCOM
IMathCOMPtr m_pIMathCOM;
IMathExCOMPtr m_pIMathExCOM; // puntero a la interfaz IMathExCOM
// ...
};
 Inicie el objeto m_pIMathExCOM en la función OnInitialUpdate de la clase
CApDllView para que permita acceder a la interfaz IMathExCOM.
void CApDllView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();

try
{
if ( FAILED(m_pIMathCOM.CreateInstance(__uuidof(MathCOM))))
throw(_T("¿Tiene registrado el objeto COM?"));
m_pIMathExCOM = m_pIMathCOM; // llama a QueryInterface
}
// ...
}
La sentencia m_pIMathExCOM = m_pIMathCOM invoca a la función miem-
bro operator= de la clase de los objetos, que a su vez invoca a la función miem-
bro QueryInterface que permite obtener en su segundo argumento un puntero a
la interfaz identificada por su primer argumento. Para entenderlo mejor, el código
que se muestra a continuación indica cómo utilizar QueryInterface para obtener
un puntero a la interfaz IMathExCOM:
IMathExCOM *p;
m_pIMathCOM->QueryInterface(m_pIMathExCOM.GetIID(), (void **)(&p));
p->Dividir( m_Operando1, m_Operando2, &m_Resultado );
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 499

La función GetIID obtiene el identificador de la interfaz que representa el ob-
jeto m_pIMathExCOM.
Aplicación de tipo consola
Seguramente que en alguna ocasión necesitará utilizar una biblioteca COM en una
aplicación de tipo consola. La forma de proceder en estos casos es similar a la que
acabamos de exponer para una aplicación basada en la biblioteca MFC.
Como ejemplo, vamos a construir un proyecto denominado ApDll7 de tipo
Win32 Console Application que utilice la biblioteca stccom32.dll para realizar las
operaciones que ésta permite con la funcionalidad que expone a través de sus in-
terfaces. Cuando haya construido el proyecto, añada un fichero .cpp denominado
ApDll y edítelo como se indica a continuación:
#include "iostream.h"
void math(void);
int menu(void);

#import "stccom32.tlb" no_namespace

int main()
{
OleInitialize(NULL); // iniciar la biblioteca COM
math();
OleUninitialize(); // cerrar la biblioteca COM
return 0;
}

void math()
{
// pIMathCOM encapsula el puntero a la interfaz IMathCOM
IMathCOMPtr pIMathCOM(__uuidof(MathCOM));
// Obtener un puntero a la interfaz IMathExCOM
IMathExCOMPtr pIMathExCOM = pIMathCOM;

if (pIMathCOM == 0 || pIMathExCOM == 0)
return;

double dato1, dato2, resultado;
int operacion;

while(1)
{
operacion = menu();
if (operacion != 5)
{
cout << "dato 1: ";
cin >> dato1;
cout << "dato 2: ";
500 VISUAL C++. PROGRAMACIÓN AVANZADA EN WIN32

cin >> dato2;
switch (operacion)
{
case 1:
pIMathCOM->Sumar(dato1, dato2, &resultado);
break;
case 2:
pIMathCOM->Restar(dato1, dato2, &resultado);
break;
case 3:
pIMathExCOM->Multiplicar(dato1, dato2, &resultado);
break;
case 4:
pIMathExCOM->Dividir(dato1, dato2, &resultado);
break;
}
cout << "resultado: " << resultado << endl << endl;
}
else
break;
}
}

int menu(void)
{
int op;
do
{
cout << "1. Sumar\n";
cout << "2. Restar\n";
cout << "3. Multiplicar\n";
cout << "4. Dividir\n";
cout << "5. Salir\n\n";
cout << "Seleccione la opción deseada: ";
cin >> op;
}
while (op < 1 || op > 5);
return op;
}
Observe el código sombreado. Comprobará que la forma de proceder no difie-
re casi en nada de la expuesta en el apartado anterior. La directriz #import impor-
ta la biblioteca de tipos stccom32, las funciones OleInitialize y OleUninitialize
hacen el trabajo que hacía AfxOleInit, y los punteros para acceder a las interfaces
de nuestro objeto COM se han obtenido a partir de los objetos pIMathCOM y
pIMathExCOM de las clase IMathCOMPtr e IMathExCOMPtr proporcionadas
por #import.
Compile la aplicación, ejecútela y compruebe que los resultados son los espe-
rados.
CAPÍTULO 8: BIBLIOTECAS DINÁMICAS 501


Faltan páginas...
Faltan páginas...

Del mismo autor


● Curso de programación con
PASCAL
ISBN: 978-84-86381-36-3
224 págs.
● Curso de programación
GW BASIC/BASICA
ISBN: 978-84-86381-87-5
320 págs.
● Manual para TURBO BASIC
Guía del programador
ISBN: 978-84-86381-43-1
444 págs.
● Manual para Quick C 2
Guía del programador
ISBN: 978-84-86381-65-3
540 págs.
● Manual para Quick BASIC 4.5
Guía del programador
ISBN: 978-84-86381-74-5
496 págs.
● Curso de programación
Microsoft COBOL
ISBN: 978-84-7897-001-8
480 págs.
● Enciclopedia del lenguaje
C
ISBN: 978-84-7897-053-7
888 págs.
● Curso de programación
QBASIC y MS-DOS 5
ISBN: 978-84-7897-059-9
384 págs.
● Curso de programación
RM/COBOL-85
ISBN: 978-84-7897-070-4
396 págs.
● El abecé de
MS-DOS 6
ISBN: 978-84-7897-114-5
224 págs.
● Microsoft Visual C ++ (ver. 1.5x de 16 bits)
Aplicaciones para Windows
ISBN: 978-84-7897-180-0
846 págs. + 2 disquetes
● Microsoft Visual C ++
Aplicaciones para Win32 (2ª edición)
ISBN: 978-84-7897-561-7
792 págs. + disquete
● Microsoft Visual C ++
Programación avanzada en Win32
ISBN: 978-84-7897-344-6
888 págs. + CD-ROM
● Visual Basic 6
Curso de programación (2ª edición)
ISBN: 978-84-7897-357-6
528 págs. + disquete
● Enciclopedia de Microsoft
Visual Basic 6
ISBN: 978-84-7897-386-6
1.072 págs. + CD-ROM
● El lenguaje de programación
Java
ISBN: 978-84-7897-485-6
320 págs. + CD-ROM
● El lenguaje de programación
C#
ISBN: 978-84-7897-500-6
320 págs. + CD-ROM

Del mismo autor


● El lenguaje de programación
Visual Basic.NET
ISBN: 978-84-7897-525-9
464 págs. + CD-ROM
● Java 2
Lenguaje y aplicaciones
ISBN: 978-84-7897-745-1
392 págs. + CD-ROM
● Programación orientada a objetos
con C ++ (4ª edición)
ISBN: 978-84-7897-761-1
648 págs. + CD-ROM
● C/C++
Curso de programación (3ª edición)
ISBN: 978-84-7897-762-8
708 págs. + CD-ROM
● Microsoft C#
Lenguaje y aplicaciones (2ª edición)
ISBN: 978-84-7897-813-7
520 págs. + CD-ROM
● Java 2. Interfaces gráficas y aplicaciones para
Internet (3ª edición)
ISBN: 978-84-7897-859-5
718 págs. + CD-ROM
● Aplicaciones .Net multiplataforma
(Proyecto Mono)
ISBN: 978-84-7897-880-9
212 págs. + CD-ROM
● Enciclopedia del lenguaje
C ++ (2ª edición)
ISBN: 978-84-7897-915-8
902 págs. + CD-ROM
● Enciclopedia de Microsoft
Visual C# (3ª edición)
ISBN: 978-84-7897-986-8
1.110 págs. + CD-ROM
● Enciclopedia de Microsoft
Visual Basic (2ª edición)
ISBN: 978-84-7897-987-5
1.090 págs. + CD-ROM
● Microsoft Visual Basic .NET
Lenguaje y aplicaciones (3ª edición)
ISBN: 978-84-9964-020-4
520 págs. + CD-ROM
● Java 2
Curso de programación (4ª edición)
ISBN: 978-84-9964-032-7
820 págs. + CD-ROM
● Microsoft C#
Curso de programación (2ª edición)
ISBN: 978-84-9964-068-6
850 págs. + CD-ROM
● Visual C#. Interfaces gráficas y aplicaciones
para Internet con WPF, WCF y Silverlight
ISBN: 978-84-9964-203-1
956 págs. + CD-ROM
● Visual Basic. Interfaces gráficas y aplicaciones
para Internet con WPF, WCF y Silverlight
ISBN: 978-84-9964-204-8
938 págs. + CD-ROM

Sign up to vote on this title
UsefulNot useful