Archivo

Posts Tagged ‘MFC’

Aplicación que se ejecuta como consola e interfaz gráfica


Siguiendo una petición en la galería de código de MSDN, he publicado este ejemplo: aplicación que se ejecuta como consola e interfaz gráfica. ¡Échale un ojo, descarga y vota si te gusta! A continuación transcribo el extracto del artículo.

Introducción

En ciertas ocasiones nuestra aplicación Windows necesita ejecutarse en modo de consola, o al menos, proveer esta interfaz bajo ciertas condiciones. Es decir, una aplicación que se comporte como aplicación Windows y aplicación consola.

Construyendo el ejemplo

El código asociado es una solución de Visual Studio 2010, y se requiere tener Visual C++ instalado (aunque no debe ser difícil portar hacia versiones anteriores). El proyecto de Visual C++ es  una aplicación MFC basada en ventana de diálogos. Para compilar sólo se necesita MFC y, dado que se hacen llamadas al API de Windows, algún SDK reciente.

Descripción

Al iniciar su ejecución, la aplicación determina si ha recibido un parámetro llamado /use-console. De ser así, crea una consola y la adjunta al proceso actual, para después crear un búfer y asociarlo a la consola. Posteriormente se escribe y lee información de la misma. En caso de no recibir dicho parámetro, la aplicación simplemente muestra una ventana de diálogo modal, con algunos controles en ella.

El bloque de código interesante lo presento a continuación:

if (objCmdLine.UseConsole()) 
{ 
    ::AllocConsole(); 
    HANDLE hBuffer = ::CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, 
        CONSOLE_TEXTMODE_BUFFER, 
        NULL); 
    ::SetConsoleActiveScreenBuffer(hBuffer); 
         
    HANDLE hInputHandle = GetStdHandle(STD_INPUT_HANDLE); 
 
    CString strMsg;         
    strMsg.LoadString(IDS_INFO); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
    strMsg.LoadString(IDS_GREETINGS); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
         
    CONSOLE_READCONSOLE_CONTROL objCtrl; 
    ZeroMemory(&objCtrl, sizeof(CONSOLE_READCONSOLE_CONTROL)); 
    objCtrl.nLength = sizeof(CONSOLE_READCONSOLE_CONTROL);         
 
    const int nInputSize = 256; 
    TCHAR szInput[nInputSize]; 
    ZeroMemory(szInput, sizeof(TCHAR) * nInputSize); 
    DWORD dwCharsRead; 
    ::ReadConsole(hInputHandle, szInput, nInputSize, &dwCharsRead, &objCtrl); 
 
    CString strInput(szInput); 
    strInput = strInput.Mid(0, dwCharsRead - 2); 
     
    strMsg.Format(IDS_HELLOFELLOW, strInput); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
    strMsg.LoadString(IDS_PRESSKEYTOEXIT); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
         
    ::ReadConsole(hInputHandle, szInput, 1, NULL, NULL); 
 
    ::FreeConsole(); 
} 
else  
{ 
    CSampleDlg dlg; 
    m_pMainWnd = &dlg; 
    dlg.DoModal(); 
}

Del bloque anterior, hay que notar las siguientes llamadas a funciones del API.

  • AllocConsole se encarga de crear una consola y asociarla al proceso actual.
  • CreateConsoleScreenBuffer y SetConsoleActiveScreenBuffer crea un búfer y lo asocia a la consola previamente creada.
  • GetStdHandle nos proporciona un manejador hacia la salida de la consola (i.e. para poder leer y escribir desde y hacia ella).
  • ReadConsole y WriteConsole se encargan de leer datos desde y escribirlos hacia la consola, respectivamente.
  • Por último, FreeConsole desasocia la consola creada.

Código fuente

  • AboutDlg (h y cpp) – el cuadro de diálogo de "acerca de".
  • ConsoleCommandLineInfo (h y cpp) – implementa la clase que analiza los parámetros de la aplicación.
  • Resource.h – el encabezado donde se guardan los valores de recursos.
  • SampleApp (h y cpp) – la aplicación; en particular, contiene el método InitInstance donde se decide si mostrar la aplicación Windows o la aplicación por consola.
  • SampleDlg (h y cpp) – la ventana de diálogo principal de la aplicación.

Más información

Para mayor información, revisa esta sección: Sobre Consolas en MSDN.

Categorías:C++, Código y ejemplos, WIN-32 Etiquetas: , ,

Cómo crear una aplicación MFC sin usar el wizard de Visual C++


Buenas noches entusiastas de la programación de C++. Hacía rato que no blogueaba de noche, así que heme aquí, retomando las buenas costumbres.

Afrontémoslo: C++ es el único lenguaje que obtiene todos los beneficios que ofrece Windows desde un inicio. Incluso C# y .NET tardan en acoplarse a las nuevas realidades. Para muestra, un botón: el famoso Ribbon del Fluent UI que se estrenó con Microsoft Office 2007, y que ha sido incorporado de forma nativa a Windows 7. Este elemento que hace vistosas nuestras interfaces gráficas para aplicaciones de escritorio llegó a nosotros a partir del Enhancement Pack de Visual Studio 2008 y viene incorporado de forma nativa en Visual C++ 2010. Sin embargo, no hay versiones oficiales, aunque hay muy buenos componentes por parte de terceros algunos, gratuitos como la versión del compatriota Menéndez Poo para Windows Forms o la versión que se distribuye con el WPF Toolkit en CodePlex para Windows Presentation Foundation. Sin embargo, aunque buenos, tienen el estigma de no ser oficiales de Microsoft.

Dicho esto, también hay que reconocer que la única forma para desarrollar aplicaciones con estas bondades en tiempos y costos razonables es necesario utilizar MFC. Ésta ha sido una gran herramienta, aunque su diseño, que data de 1991 las primeras versiones, es algo anticuado. Sin embargo, Qt aunque lejos todavía no ofrece todo lo que MFC en términos de componentes y otras librerías como wxWidgets o el impresionante Visual Component Framework del buen Jim Crafton les falta soporte general todavía, a mi parecer. Así que seguimos utilizando MFC, pese a todo.

Visual C++ es una gran ayuda para desarrollar con MFC, y está lleno de herramientas y asistentes para hacer el trabajo menos pesado. Pero seamos ciertos: a veces los asistentes generan mucho código basura. Esto es cierto en especial con Visual C++. ¿Recuerdan Visual C++ 6.0, que salió allá por 1998? El Class Wizard generaba tanta basura que era un dolor de cabeza terrible el mirar el código. Y es cierto, esto ha ido mejorando con cada nueva versión del IDE. En particular, he de decir que me siento mucho más cómo con la versión 2010 que con ningún otro entorno de desarrollo. Pero aún así los asistentes no son perfectos.

Tomemos como ejemplo el asistente de proyecto. Tantas opciones que hay: usar MDI, SDI, o tabs; soportar documento/vista, estilos visuales de Windows, MFC estándar, Internet Explorer, Visual Studio Office; usar barra de herramientas estándar, de navegación, Ribbon… en fin. Esto puede ser de gran ayuda, pero a veces el asistente genera tanta cosa que luego hay que pasarse horas limpiando todo para contar con un cascarón decente. Por ejemplo, el simple hecho de utilizar MFC genera infinidad de cadenas de texto en la tabla de cadenas del archivo de recursos, y usar el Ribbon hará que se generen decenas de imágenes, que luego uno tiene que editar y quitar.

WizardMFC

Así pues, a pesar de la ayuda que presta el asistente, a veces es mejor comenzar desde cero. Pero ¡oh sorpresa! A diferencia de los proyectos Win 32, cuyo asistente cuenta con la opción de “Proyecto vacío”, MFC no tiene nada de eso. Entonces, ¿cómo hacer para emular esto?

La solución radica en crear un proyecto nuevo para Win 32. En el asistente de proyecto nuevo, seleccionamos Win 32Project y dejamos las opciones por defecto.

NewProject

Cuando iniciemos, contamos con algunos archivos típicos: BlogTest.h (así se llama la aplicación que cree de ejemplo) no tendrá nada, Resource.h lo usa internamente el IDE, stdafx.h contiene algunos encabezados básicos y targetver tiene una llamada #include al archivo SDKDKVer.h. Por otra parte, stdafx.cpp tendrá un #include “stdafx.h” y será nuestro archivo precompilado, y BlogTest.cpp contendrá la función WinMain. Bien, pues ahora sí, manos a la obra. Lo primero a hacer es eliminar el fastidioso ReadMe.txt.

Acto seguido, necesitamos indicarle al compilador que vamos a trabajar con MFC, así que nos vamos al menú Project y seleccionamos la opción Propiedades de proyecto. En la ventana que nos aparece, expandimos el árbol de “Propiedades de configuración” y nos vamos al nodo “General”. En las propiedades, buscamos la que dice “Uso de MFC” y establecemos el valor a “Usar MFC en librería compartida”. Le damos clic al botón “Aplicar” y repetimos los pasos ahora para la configuración de “Release”.

ProjectProperties

Ya que hicimos estos pasos, estamos listos para utilizar MFC. Necesitaremos a continuación comenzar a escribir código a manita, y mi recomendación sería por comenzar a limpiar los archivos con los que contamos. Comencemos por stdafx.h. En primer lugar, nos deshacemos de los comentarios, dejamos el #include hacia targetver.h y reemplazamos los #includes hacia los archivos de Windows con los de MFC. Al final, deberías tener algo como esto:


#pragma once

#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN            
#endif

#include "targetver.h"

#include <afxwin.h>
#include <afxext.h>
#include <afxdtctl.h>
#include <afxcmn.h>  
#include <afxcontrolbars.h>
#include <afxwinappex.h>
#include <afxdialogex.h>

Luego nos vamos a targetver.h. Este archivo se usa para definir las versiones de Windows que planeamos soportar. En Visual C++ 2008 el asistente generaba algunos símbolos, que ahora han sido reemplazados por un archivo que viene dentro del SDK de Windows, llamado sdkdkver.h. Lo único que tenemos que hacer, pues, es definir el símbolo _WIN32_WINNT con la versión mínima de Windows que queremos soportar y luego dejar el include a sdkdkver. Si no añadimos nada, por defecto asumirá soporte para Windows 7. En mi caso, me gustaría que el soporte mínimo fuera para Windows Vista, por lo que mi targetver.h luce así:


#pragma once

#define _WIN32_WINNT	0x0600

#include <sdkddkver.h>

Perfecto. Resource.h se usa de forma interna, por lo que de momento lo dejamos de lado y pasamos a BlogTest.h. Dado que comenzamos con un proyecto Win32, no habrá nada, pero como sabes para iniciar una aplicación MFC necesitamos crear una clase derivada de CWinApp ó CWinAppEx e instanciarla en algún objeto global. Bien, pues manos a la obra. Creamos dicha clase que debe incluir por lo menos la sobrecarga de InitInstance y ExitInstance. Alternativamente podemos sobreescribir OnAppAbout y de requerirse, PreLoadState, LoadCustomState y SaveCustomState. En mi ejemplo mantendré éstas tres últimas, aunque no hagan nada.

#pragma once

class CBlogTestApp : public CWinAppEx
{
    DECLARE_MESSAGE_MAP();

    public:
        CBlogTestApp();
        virtual ~CBlogTestApp();

        virtual BOOL InitInstance();
        virtual int ExitInstance();

        virtual void PreLoadState();
        virtual void LoadCustomState();
        virtual void SaveCustomState();

        virtual void OnAppAbout();
    
};

extern CBlogTestApp g_objApp;

Bien, ya terminamos nuestros encabezados, vamos ahora con los archivos de implementación. Primero, limpiamos stdafx.cpp de los comentarios y dejamos solo el #include “stdafx.h”. Pasamos ahora al BlogTest.cpp y borramos todo lo que haya. Y a escribir se ha dicho.

  1. Primero, incluimos los archivos de cabecera stdafx.h, resource.h y blogtest.h. Creamos el constructor (donde establecemos si queremos utilizar el Restart Manager que viene con MFC 10, y el ID de nuestra aplicación), el destructor y el mapa de mensajes, que dejaremos con las opciones tradicionales para el AppAbout, Archivo nuevo, Abrir archivo e Imprimir. Luego, instanciamos nuestro objeto global.
  2. Segundo, escribimos el método InitInstance. Tendremos que inicializar los controles comunes, llamar a la versión base de CWinAppEx, inicializar OLE, inicializar cuestiones avanzadas de la interfaz gráfica y establecer las llaves del registro de Windows donde se guardarán nuestras configuraciones. Nota que como de momento no usamos una ventana  o un diálogo ni nada, no incluimos las llamadas al parseo de la línea de comandos o del shell, ni el registro del documento, la vista y el marco hijo (arquitectura documento/vista) ni nada, pero eventualmente necesitaremos añadir el soporte, según veamos necesario.
  3. Finalmente, escribimos el OnAppAbout, el ExitInstance y demás métodos que tengamos.

He aquí el código.


#include "stdafx.h"
#include "Resource.h"
#include "BlogTest.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CBlogTestApp::CBlogTestApp()
{
	m_dwRestartManagerSupportFlags = 
        AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
	SetAppID(_T("Fermasmas.BlogTest.BlogTest.1"));
}

CBlogTestApp::~CBlogTestApp()
{
}

BEGIN_MESSAGE_MAP(CBlogTestApp, CWinAppEx)
	ON_COMMAND(ID_APP_ABOUT, &CBlogTestApp::OnAppAbout)
	ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)
	ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen)
	ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup)
END_MESSAGE_MAP()

CBlogTestApp g_objApp;

BOOL CBlogTestApp::InitInstance()
{
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

	CWinAppEx::InitInstance();

	if (!AfxOleInit())
	{
		AfxMessageBox(_T("Init failed"));
		return FALSE;
	}

	AfxEnableControlContainer();
	EnableTaskbarInteraction();

	SetRegistryKey(_T("Fermasmas Blog Test"));
	LoadStdProfileSettings(5);  

	InitContextMenuManager();
	InitShellManager();
	InitKeyboardManager();

	InitTooltipManager();
	CMFCToolTipInfo ttParams;
	ttParams.m_bVislManagerTheme = TRUE;
	g_objApp.GetTooltipManager()->SetTooltipParams(
        AFX_TOOLTIP_TYPE_ALL, 
        RUNTIME_CLASS(CMFCToolTipCtrl), 
        &ttParams);

	return TRUE;
}

int CBlogTestApp::ExitInstance()
{
	AfxOleTerm(FALSE);

	return CWinAppEx::ExitInstance();
}

void CBlogTestApp::OnAppAbout()
{
	AfxMessageBox(_T("Fermasmas BlogTest 1.0"));
}

void CBlogTestApp::PreLoadState()
{
}

void CBlogTestApp::LoadCustomState()
{
}

void CBlogTestApp::SaveCustomState()
{
}


Ahora ya está todo listo, y aunque nuestra aplicación no hace nada ya tenemos una aplicación funcional. O casi. Si compilamos, seguramente tendremos una advertencia de compilación que nos dice que IDC_STATIC ya ha sido redefinido. Esto pasa porque el archivo de recursos que el asistente de Win32 nos genera está pensado para proyectos Win32 y no MFC. Necesitamos editar este archivo. Así que primero, abrimos Resource.h. Necesitamos eliminar la línea con el símbolo de que no se soporta MFC: _APS_NO_MFC. Y luego, eliminar la definición de IDC_STATIC. Al hacerlo, quedaría así:

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by BlogTest.rc
//

#define IDS_APP_TITLE            103

#define IDR_MAINFRAME            128
#define IDD_BLOGTEST_DIALOG    102
#define IDD_ABOUTBOX            103
#define IDM_ABOUT                104
#define IDM_EXIT                105
#define IDI_BLOGTEST            107
#define IDI_SMALL                108
#define IDC_BLOGTEST            109
#define IDC_MYICON                2
//#ifndef IDC_STATIC
//#define IDC_STATIC                -1
//#endif
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

//#define _APS_NO_MFC                    130
#define _APS_NEXT_RESOURCE_VALUE    129
#define _APS_NEXT_COMMAND_VALUE        32771
#define _APS_NEXT_CONTROL_VALUE        1000
#define _APS_NEXT_SYMED_VALUE        110
#endif
#endif

Y ahora sí, abrimos el archivo de recursos desde el editor de código. Lo primero que notamos es que en las líneas 13-15 se hace referencia a los archivos de Windows. Mala cosa, necesitamos cambiarla por referencias a MFC. Además, necesitamos añadir la referencia a los recursos de MFC estándares y para el Ribbon y demás cosas. Sin embargo, tenemos el pequeño problema de que si sobreescribimos este archivo, Visual C++ se encargará amablemente de regenerarlo como estaba antes. Patatús.

Si nos vamos a las líneas 102-130, veremos algo como lo que sigue.

#ifdef APSTUDIO_INVOKED
///////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#ifndef APSTUDIO_INVOKED\r\n"
    "#include ""targetver.h""\r\n"
    "#endif\r\n"
    "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
    "#include ""windows.h""\r\n"
    "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

Esto, señoras y señores, es el texto con lo que Visual C++ generará en automático. Como ves, aquí tenemos los #define’s e #include’s a cosas de windows.h, por lo que vamos a reemplazarlo por los #include’s a los archivos de MFC. Algo así debería quedar ese bloque.

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#ifndef APSTUDIO_INVOKED\r\n"
    "#include ""targetver.h""\r\n"
    "#endif\r\n"
    "#include ""afxres.h""\r\n"
    "#include ""verrsrc.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "#define _AFX_NO_OLE_RESOURCES\r\n"
    "#define _AFX_NO_TRACKER_RESOURCES\r\n"
    "#define _AFX_NO_PROPERTY_RESOURCES\r\n"
    "\r\n"
    "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n"
    "LANGUAGE 9, 1\r\n"
    "#include ""afxres.rc""      // Standard components\r\n"
    "#include ""afxprint.rc""    // printing/print preview resources\r\n"
    "#if !defined(_AFXDLL)\r\n"
    "#include ""afxribbon.rc""  // MFC ribbon and control bar resources\r\n"
    "#endif\r\n"
    "#endif\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

El primer bloque queda como está. En el segundo, cambiamos la referencia a windows.h  por la de afxres.h y añadimos versrc.h. El tercer bloque contiene #include’s al archivo de recursos estándar (afxres.rc), a uno para soporte para impresión (afxprint.rc) y otro más para el Ribbon (afxribbon.rc). Una vez que hayamos hecho este cambio, guardamos y cerramos los archivos de recursos, y lo abrimos nuevamente desde la vista de recursos en el IDE. En cuanto hagamos desde ahí algún cambio y guardemos el archivo, Visual C++ lo regenerará con la nueva información.

NOTA: es posible que al intentar abrir la vista de recursos te mande un error, diciendo que IDC_STATIC no está definido. Sucede que seguramente en el archivo de recursos tienes la definición para una ventana de diálogo que contiene el “Acerca de”. Y como eliminamos la definición de IDC_STATIC y el archivo de recurso todavía no se ha generado con los nuevos #include’s pues el IDE protestará. No problemo. Editamos el archivo de recurso y comenta todas las referencias a IDC_STATIC –ya luego las agregarás de nuevo.

Bueno pues, ahora sí tenemos nuestra solución lista para ser desarrollada. Si vas a hacer alguna aplicación basada en diálogos, crea tus clases derivadas de CDialog o CDialogEx y haz el DoModal desde el InitInstance de tu clase derivada de CWinAppEx. Si vas a utilizar una ventana con soporte SDI/MDI/TDI, ya sabes: a derivar de CFrameWndEx / CMDIFrameWndEx, CChildFrameEx / CMDIChildFrameEx, etc., así como de CDocument y CView y hacer todo lo que uno hace.

Conclusiones a las dos de la mañana:

  1. C++ sigue siendo la única forma de acceder a las últimas características que ofrece Windows de forma natural, y MFC la única forma comercialmente viable de desarrollar con C++ para Windows.
  2. Visual C++ cuenta con asistentes para facilitar el desarrollo en MFC, pero a veces puede ser un Pain In The Ass® por todo el código y archivos adicionales que le mete, de tal suerte que podemos pasar mucho tiempo limpiando nuestro código.
  3. Existe una forma de solucionar lo anterior, aunque implica escribir algo de código a manita, lo cual sigue siendo mejor que limpiar lo generado por el asistente. Esta forma consiste en crear un proyecto para Win32, agregarle soporte para MFC en las propiedades del proyecto, cambiar el WinMain por una clase derivada de CWinApEx y ajustar los archivos de recursos.
  4. En adelante, lo que queda es derivar las clases que vamos a usar y en general construir nuestra aplicación.

Bueno jóvenes, eso ha sido todo por hoy. Espero que le encuentre utilidad. Ciao bambini!

 

Post Scriptum. Desde hace mucho tiempo he tenido la intención de escribir un tutorial de MFC. El problema es que hacer algo así es algo monumental que lleva mucho tiempo, por lo que no me he decidido a dar el paso. Pues bien, este artículo quizás sirva de base para comenzar. Quién sabe, ya veremos. Pero antes necesito saber: ¿creen que sería bueno hacer el tutorial, o con los que ya existen tenemos? Espero sus comentarios. 

Categorías:C++, Cómo hacer, WIN-32 Etiquetas: ,