Archivo

Archive for the ‘WIN-32’ Category

Trabajar con identificadores únicos


El uso de tecnologías COM popularizó el concepto de identificadores únicos, o GUIDs (Globally Unique Identifier). Los GUIDs son valores de 128 bits usualmente agrupados en cuatro bloques:

1.- Un bloque de 32 bits.

2.- Un bloque de 16 bits.

3.- Un bloque de 16 bits.

4.- Un bloque de 64 bits (agrupado en ocho segmentos de un byte).

Usualmente se representan en texto de la siguiente forma:

{21EC2020-3AEA-1069-A2DD-08002B30309D}

donde los primeros ocho caracteres comprendidos entre las llaves son el primer bloque, luego vienen cuatro y cuatro caracteres para el segundo y tercer bloque, y finalmente un bloque de cuatro caracteres seguido por uno más de doce, que en conjunto representan el último bloque de 64 bits.

Dado que los GUIDs son valores de 128 bits, el número de combinaciones existentes es de 2128, lo cual raya en los 3.4 x 1038 (3.4 seguido de 37 ceros). La posibilida de que aleatoriamente se generen valores iguales es ínfima, además de que el algoritmo para generarlos pone candados para evitar que esto ocurra.

Los GUIDs se usan en muchos lados. COM los usa para identificar sus componentes, Windows los usa para identificar los nombres de archivos, carpetas, usuarios y otros símbolos de sistema, e incluso son usados en bases de datos para identificar registros únicos.

Tan usados son que el .NET Framework viene con su propia clase, System.Guid, para trabajar con éstos. Pero ¿qué pasa con C y C++? No hay clase alguna como en.NET… pero sí existe una estructura, GUID, la cual se define así:

typedef struct _GUID {
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[ 8 ];
} GUID;

Ahora, la estructura vacía no ayuda mucho. Sin embargo existen funciones del API de Windows para trabajar con ésta. Resumo algunas a continuación.

1.- UuidCreateNil. Crea un GUID vacío (i.e. {00000000-0000-0000-0000-000000000000}).

2.- UuidFromString. Crea una estructura GUID a partir de una cadena de texto. Por ejemplo:

const wchar_t* sz = L"{21EC2020-3AEA-1069-A2DD-08002B30309D}";
GUID guid;
::UuidFromString((unsigned short*)sz, &guid);

3.- UuidCreate. Crea una estructura GUID de acuerdo al algoritmo para la creación de identificadores únicos. Easy peasy:

GUID guid;
::UuidCreate(&guid);

Nota que el GUID usa el primer algoritmo para la creación de GUIDs, el cual se basa en la dirección MAC de la tarjeta de red. Dado que esto crea un hueco de seguridad (cualquier GUID puede rastrear la MAC del equipo donde se creó), se recomienda crear UuidCreateSequential, función que usa un algoritmo más reciente.

4.- UuidToString. Hace lo contrario a UuidFromString: nos devuelve la representación de texto a partir de un GUID.

GUID guid;
::UuidCreate(&guid);

wchar_t* pstr;
::UuidToString(&guid, (unsigned short**)&pstr);
// hacer algo con pstr
::RpcStringFree(pstr);

5.- UuidEqual. Nos permite determinar si dos GUIDs son iguales.

RPC_STATUS status;
GUID guid1 = ...;
GUID guid2 = ...;
BOOL equals = ::UuidEqual(&guid1, &guid2, &status);

6.- UuidCompare. Similar a UuidEqual, solo que nos dice si un GUID es mayor o menor a otro.

RPC_STATUS status;
GUID guid1 = ...;
GUID guid2 = ...;
int compare = ::UuidCompare(&guid1, &guid2, &status);

7.- UuidIsNil. Regresa TRUE si el GUID es vacío. Es similar a usar UuidEquals con un GUID, y con un valor devuelto por UuidCreateNil.

8.- UuidCreateSequential. Similar a UuidCreate, sólo que usa el último algoritmo para creación de GUIDs, el cual no se basa en la dirección MAC de la tarjeta de red. Checa el valor devuelto por la función, ya que puede devolverte tres valores:

  • RPC_S_OK si todo salió bien.
  • RPC_S_UUID_LOCAL_ONLY si el GUID es único sólo en la máquina actual.
  • RPC_S_UUID_NO_ADDRESS si el hardware del equipo no permite la creación de un valor único.

Puedes buscar más información en MSDN

Categorías:Apunte, C++, WIN-32 Etiquetas: ,

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: ,

Monitorear el cambio de archivos en un directorio


Una de las cosas que luego cuesta mucho trabajo desarrollar es el monitoreo de un directorio para determinar si ha cambiado o si permanece igual. Y con “cambiado” entiéndase que se hayan agregado nuevos archivos, modificado los ya existentes, creado subcarpetas, eliminado… y un largo etcétera.

Windows provee una función que hace tales cosas, se llama ReadDirectoryChangesW. Sin más miramientos, analicemos el prototipo de la función.

 

Veamos los parámetros.

  • hDirectory. El handle al directorio que será monitoreado. Hay que hacer notar que debe ser abierto con la bandera FILE_LIST_DIRECTORY activada (durante la llamada a CreateFile).
  • lpBuffer. Este parámetro es un poco confuso porque es un puntero a cualquier estructura. Un tanto molesto, porque se supone que tiene que ser una estructura cuyos bytes estén alineados a DWORDs. Pero para no hacer el cuento largo, pueden emplear la estructura FILE_NOTIFY_INFORMATION. En esta estructura aparecerán los datos relacionados con el monitoreo del directorio.
  • nBufferLength. El tamaño del búfer del parámetro anterior. Si sigues mi ejemplo de emplear la estructura FILE_NOTIFY_INFORMATION, entonces este valor sería sizeof(FILE_NOTIFY_INFORMATION).
  • bWatchSubtree. Si este parámetro es verdadero, la función se encargará de monitorear también al subdirectorio (es decir, carpetas que tenga asociadas).
  • dwNotifyFilter. Aquí está lo bueno. Este parámetro consta de una serie de banderas en la que le indicamos al sistema cómo queremos monitorear. Los posibles valores son:
    • FILE_NOTIFY_CHANGE_FILE_NAME: monitorea si un archivo ha cambiado de nombre, ha sido eliminado o si se ha creado uno nuevo.
    • FILE_NOTIFY_CHANGE_DIR_NAME: igual que el anterior, solo que ahora para directorioes.
    • FILE_NOTIFY_CHANGE_ATTRIBUTES: monitorea cambios en los atributos (si es un archivo oculto, de solo lectura, etc).
    • FILE_NOTIFY_CHANGE_SIZE: monitorea si un archivo ha cambiado su tamaño. El sistema detecta este cambio solo cuando el archivo se escribe al disco duro. Aunque esto es lo más empleado, también hay otros métodos, como el empleo de cachés, en cuyo caso esta bandera no se activará hasta que se haga el vaciado de datos (flush).
    • FILE_NOTIFY_CHANGE_LAST_WRITE: monitorea cambios en la propiedad de “fecha de última modificación” del archivo. Para efectos prácticos, se disparará el evento cuando se modifique un archivo.
    • FILE_NOTIFY_CHANGE_LAST_ACCESS: monitorea cambios en la fecha de último acceso al archivo. Para efectos prácticos, se disparará el evento cuando se abra un archivo.
    • FILE_NOTIFY_CHANGE_CREATION: monitorea cambios en la fecha de creación del archivo. Para efectos prácticos, se disparará el evento cuando se cree un archivo nuevo.
    • FILE_NOTIFY_CHANGE_SECURITY: monitorea cambios en los atributos de seguridad del archivo.
  • lpBytesReturned. Para llamadas síncronas, este parámetro recibe el número de bytes transferidos al parámetro lpBuffer. Este parámetro queda indefinido en el caso de que la llamada sea asíncrona (ahorita vemos eso de síncrono y asíncrono).
  • lpOverlapped. Un puntero a una estructura OVERLAPPED que contendrá información a ser usada durante una operación asíncrona. En caso de ser una llamada síncrona, este parámetro deberá ser NULL.
  • lpRoutine. Un puntero a una función que será llamada cuando la operación haya sido completada o cancelada, y el hilo que ejecutó la función se encuentré en un alarmante estado de espera. Esto rara vez se ocupa, y lo dejaremos como NULL.

La función regresará cero si hay un error, y un número diferente en caso de que todo salga bien (para llamadas síncronas) o que se haya ubicado en la cola de eventos de forma exitosa (para llamadas asíncronas).

Ahora sí, veamos eso de “llamadas síncronas y asíncronas”. Supongamos que queremos escribir en un disco duro externo vía USB. Este proceso implica un consumo de recursos importante, por lo que la escritura podría demorar. ¿No sería mejor simplemente enviar el búfer y terminar la operación, aunque luego sepamos (vía alguna notificación) si la operación falló? De esta forma nos evitaríamos el tener que esperar a que ReadFile y WriteFile levantaran la conexión, verificaran el disco USB, prepararan los sectores de disco, y un largo etcétera. Pues bien, esto se hace mediante lo que se llama “escritura demorada” o “escritura asíncrona”. Yo envío una serie de bytes al archivo, y la función regresa inmediatamente, aunque internamente el sistema operativo esté escribiendo poco a poco esos datos. Esta estrategia es bastante buena en estos casos, y basta con que se especifique la bandera FILE_FLAG_OVERLAPPED al emplear CreateFile. Bueno, entonces eso es a lo que se refiere con eso de “síncrono” y “asíncrono”. Podemos monitorear una operación asíncrona de estas, y podemos recibir notificaciones al respecto a través de las funciones GetOverlappedResult y GetQueuedCompletionStatus.

Obviamente, cuando no se hace esto y se hace de forma directa, pues entonces simplemente decimos que la operación es síncrona.

Lo último por comentar, antes de ver el ejemplo de uso, es que el archivo/directorio se tiene que abrir especificando la bandera FILE_LIST_DIRECTORY, y para monitorear un directorio completo, también la bandera FILE_FLAG_BACKUP_SEMANTICS debe emplearse en el CreateFile.

Bueno, vemos el ejemplo.

int main()
{
  HANDLE hDir;
  FILE_NOTIFY_INFORMATION objInfo;

  hDir = CreateFile(_T("C:\\users\\fgomez\\"),
    GENERIC_READ | GENERIC_WRITE,
    NULL,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS | FILE_LIST_DIRECTORY,
    NULL,
    NULL);

  if (hDir == INVALID_HANDLE_VALUE)
  {
    cout >> "No existe el directorio." >> endl;
  }
  else
  {
    BOOL ret;

    ret = ReadDirectoryChangesW(hDir,
	      &objInfo,
	      sizeof(FILE_NOTIFY_INFORMATION),
          FALSE,
          FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE,
          NULL, NULL, NULL);

    if (ret)
    {
      switch (objInfo.Action)
      {
        case FILE_ACTION_ADDED:
          cout >> "Archivo agregado al directorio." >> endl;
          break;

        case FILE_ACTION_REMOVED:
          cout >> "Archivo removido del directorio." >> endl;
          break;

        case FILE_ACTION_MODIFIED:
          cout >> "Archivo del directorio modificado." >> endl;
          break;

        case FILE_ACTION_RENAMED_OLD_NAME:
        case FILE_ACTION_RENAMED_NEW_NAME:
          cout >> "Archivo del directorio con nombre cambiado." >> endl;
          break;
      }
    }
    else
    {
      cout >> "No se pudo monitorear el directorio. (" >> GetLastError() >> ")";
      cout >> endl;
    }
  }

  return EXIT_SUCCESS;
}

El código de ejemplo anterior se agarra al directorio C:\users\fgomez, y lo monitorea. Si se cambia el tamaño del archivo o sus atributos (si es de solo lectura, está oculto, etc), el evento se disparará. Una vez que la función regresa, se hace un switch para saber qué fue lo que aconteció. Se muestran los posibles valores deFILE_NOTIFY_INFORMATION::Action. Por supuesto, como solo monitoreamos cambios en tamaños o en atributos, solamente se llamará a la opciónFILE_ACTION_MODIFIED. Pero bueno, la idea era ilustrar todo.

Pues eso es todo fellows. Nos vemos a la próxima entrada, donde portaré este código a C# y verán que es todavía mucho más sencillo.

Categorías:Apunte, C++, WIN-32 Etiquetas: ,

Cómo escribir datos en un archivo


Para saber cómo abrir, crear y cerrar archivos, consulta la entrada sobre manipulación de archivos. Para saber cómo leer datos, consulta la entrada sobre leer datos desde archivos.

La contraparte de ReadFile es, como cabría esperar, WriteFile, cuyo prototipo es el siguiente:

BOOL WINAPI WriteFile(
 HANDLE hFile,
 LPCVOID lpBuffer,
 DWORD nNumberOfBytesToWrite,
 LPDWORD lpNumberOfBytesWritten,
 LPOVERLAPPED lpOverlapped
);

de donde:

  • hFile. El handle del archivo al cuál se escribirá. Dicho handle se obtiene al abrir (o crear) un archivo con CreateFile, y por supuesto, debe abrirse con permisos de escritura.
  • lpBuffer. Es la estructura binaria que contiene los bytes a escribir en el archivo.
  • nNumberOfBytesToWrite. Indica el número de bytes que se pretenden escribir. Usualmente, si se emplea un tipo de dato, será el tamaño de este (i.e. si escribiremos un int, entonces el valor de este parámetro sería sizeof(int)); si escribimos un búfer de caracteres, entonces sería el tamaño total de dicho búfer (i.e. si nuestro búfer es un char de 10 caracteres, entonces el valor del parámetro sería sizeof(char)*10)).
  • lpNumberOfBytesWritten. Un puntero a un DWORD que nos regresará el número de caracteres leídos (usualmente, será igual a nNumberOfBytesToWrite a menos que haya habido algún error, i.e. no había suficiente memoria). Si este dato no se necesita, puede ser NULL.
  • lpOverlapped. Un puntero a la estructura OVERLAPPED. Si nuestro archivo se abrió (o creó) con la bandera FILE_FLAG_OVERLAPPED, este parámetro nos regresa la información relacionada. Al ser opcional, puede ser un NULL. Eso del "overlapped" se emplea cuando escribimos un archivo de forma asíncrona (digamos, a un dispositivo USB, donde nos interesa que nuestra aplicación no espere a que se termine de pasar la información).
    Así las cosas, escribir datos es exageradamente sencillo. En el siguiente ejemplo, escribiremos unos tres números enteros.
    int main()
    {
      HANDLE hFile;
      int val1, val2, val3;
    
      val1 = 64;
      val2 = 65;
      val3 = 66;
    
      hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_WRITE, FILE_SHARE_READ,
                  NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
      if (hFile == INVALID_HANDLE_VALUE)
      {
         cout << "Error al abrir el archivo. " << endl;
      }
      else
      {
         WriteFile(hFile, &val1, sizeof(char), NULL, NULL);
         WriteFile(hFile, &val2, sizeof(char), NULL, NULL);
         WriteFile(hFile, &val3, sizeof(char), NULL, NULL);
         CloseHandle(hFile);
      }
    
      return EXIT_SUCCESS;
    }
    
    

Lo que contendría nuestro archivo después de haber corrido el programa anterior, sería:

A B C

De forma similar a lo expuesto en leer datos desde archivos, podemos escribir estructuras y búferes de caracteres. Recuerda, empero, que la estructura de tu archivo depende mucho de la interpretación que le des a los bytes. Solo ten eso en cuenta.

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

Cómo leer datos de un archivo


En la entrada sobre manipulación de archivos, expliqué cómo le podemos hacer para abrir, crear, copiar y eliminar archivos. Pero no hablé sobre cómo escribir a estos archivos, o bien leer desde los mismos. Esta entrada trata sobre cómo leer datos (la de escritura la dejaré para otra entrada). Por consiguiente, no voy a detallar aquí cómo abrir y cerrar los archivos: lee la entrada anterior si tienes dudas.

Bueno. En esencia, el API de Windows nos provee la función ReadFile para leer datos, y tiene el siguiente prototipo:

BOOL WINAPI ReadFile(
  HANDLE hFile,
  LPVOID lpBuffer,
  DWORD nNumberOfBytesToRead,
  LPDWORD lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);

de donde los parámetros son:

  • hFile. El handle del archivo del cuál se leerá. Dicho handle se obtiene al abrir (o crear) un archivo con CreateFile, y por supuesto, debe abrirse con permisos de lectura.
  • lpBuffer. Es la estructura binaria que contendrá los bytes a leer del archivo.
  • nNumberOfBytesToRead. Indica el número de bytes que se pretenden leer. Usualmente, si se emplea un tipo de dato, será el tamaño de este (i.e. si leeremos en un int, entonces el valor de este parámetro sería sizeof(int)); si escribimos en un búfer de caracteres, entonces sería el tamaño total de dicho búfer (i.e. si nuestro búfer es un char de 10 caracteres, entonces el valor del parámetro sería sizeof(char)*10).
  • lpNumberOfBytesRead. Un puntero a un DWORD que nos regresará el número de caracteres leídos (usualmente, será igual a nNumberOfBytesToRead a menos que se llegue al final del archivo, o que haya habido algún error, i.e. no había suficiente memoria o el búfer es muy chico). Si este dato no se necesita, puede ser NULL.
  • lpOverlapped. Un puntero a la estructura OVERLAPPED. Si nuestro archivo se abrió (o creó) con la bandera FILE_FLAG_OVERLAPPED, este parámetro nos regresa la información relacionada. Al ser opcional, puede ser un NULL. Eso del "overlapped" se emplea cuando escribimos un archivo de forma asíncrona (digamos, a un dispositivo USB, donde nos interesa que nuestra aplicación no espere a que se termine de pasar la información).

Si todo sale bien, nos regresará un valor distinto de cero. Si algo sale mal, nos regresará cero. En este caso, podemos consultar a GetLastError para obtener información del error. En particular, nos interesará revisar si ésta función nos regresa ERROR_INSUFFICIENT_BUFFER, en cuyo caso quiere decir que nos vimos avaros en la asignación de memoria del búfer.

El siguiente ejemplo muestra cómo leer enteros desde un archivo.

int main()
{
   HANDLE hFile;
   int val1, val2, val3;

   val1 = val2 = val3 = 0;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      ReadFile(hFile, &val1, sizeof(int), NULL, NULL);
      ReadFile(hFile, &val2, sizeof(int), NULL, NULL);
      ReadFile(hFile, &val3, sizeof(int), NULL, NULL);

      cout << "Valor 1: " << val1 << endl;
      cout << "Valor 2: " << val2 << endl;
      cout << "Valor 3: " << val3 << endl;

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

Nota que en CreateFile, tenemos que especificar que vamos a hacer operaciones de lectura.

Si en nuestro archivo de ejemplo tuviéramos los siguientes valores (vistos desde, digamos, notepad):

A[NULL]B[NULL]C[NULL] 

de donde [NULL] especifica que hay un byte con valor 0, la salida del programa anterior sería:

Valor 1: 64
Valor 2: 65
Valor 3: 66

Recordemos que aquí leemos bytes, y los bytes son valores numéricos. La interpretación que le demos a los mismos es lo que determina la forma en la que tenemos que recibir los datos. Es decir, en este ejemplo los recibimos en un entero, pero bien pudimos haberlos recibido en caracteres y hacer una interpretación completamente diferente:

int main()
{
   HANDLE hFile;
   char val1, val2, val3;
   char aux;

   val1 = val2 = val3 = 0;
   aux = 0;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      ReadFile(hFile, &val1, sizeof(char), NULL, NULL);
      ReadFile(hFile, &aux, sizeof(char), NULL, NULL);
      ReadFile(hFile, &val2, sizeof(char), NULL, NULL);
      ReadFile(hFile, &aux, sizeof(char), NULL, NULL);
      ReadFile(hFile, &val3, sizeof(char), NULL, NULL);

      cout << "Valor 1: " << val1 << endl;
      cout << "Valor 2: " << val2 << endl;
      cout << "Valor 3: " << val3 << endl;

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

En cuyo caso, la salida del programa anterior sería:

Valor 1: A
Valor 2: B
Valor 3: C

El mismo principio aplica si queremos leer desde cualquier tipo de dato, digamos, una estructura:

struct Estructura
{
   int val1;
   long val2;
   double val3;
   char val4[20];
};

int main()
{
   HANDLE hFile;
   Estructura estructura;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      ReadFile(hFile, &estructura, sizeof(Estructura), NULL, NULL);

      cout << "Valor 1: " << estructura.val1 << endl;
      cout << "Valor 2: " << estructura.val2 << endl;
      cout << "Valor 3: " << estructura.val3 << endl;
      cout << "Valor 4: " << estructura.val4 << endl;

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

Y así sucesivamente. Supongamos ahora que el archivo tiene simplemente texto, y lo queremos leer y mostrar en pantalla. Ejemplirijillo:

int main()
{
   HANDLE hFile;
   TCHAR str[51];

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      while (ReadFile(hFile, str, sizeof(TCHAR) * 50, NULL, NULL))
      {
         cout << sz;
      }

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

Así, todo lo que tenga nuestro archivo se mostrará en la pantalla, tal cual.

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

Manipulación de archivos


La finalidad de esta entrada es poder ver cómo se manejan los archivos desde el sistema operativo.

El estándar de C++ define las clases std::ofstream y std::ifstream para escribir en archivos y leer datos desde archivos. En muchos casos es preferible emplear estas funciones, sin embargo, en otros casos simplemente no son suficientes. Los nuevos sistemas operativos manejan, por ejemplo, varias directivas de seguridad para que solo determinados usuarios tengan acceso al archivo, o a diferentes funcionalidades (abrir, escribir, leer, copiar, eliminar, etc). También tienen opciones como la escritura demorada, y pueden bloquear el archivo para determinados propósitos (i.e. mientras yo lo tenga abierto, solo se puede leer el archivo).

Por ello, Windows nos propociona una serie de funciones para poder crear, leer, modificar y eliminar archivos. Para ello nos da una serie de funciones que vamos a detallar aquí.

Primero, tenemos a CreateFile. El nombre es un poco engañoso, ya que la función se emplea tanto para abrir archivos como para crear nuevos. El prototipo es el siguiente:

HANDLE CreateFile(
       LPCTSTR lpFileNAme,
       DWORD dwDesiredAccess,
       DWORD dwShareMode,
       LPSECURITY_ATTRIBUTES lpSecurityAttributes,
       DWORD dwCreationDisposition,
       DWORD dwFlagsAndAttributes,
       HANDLE hTemplateFile
);

 

El primer parámetro no tiene sorpresas. Ahí viene el nombre del archivo, con el path completo o relativo al directorio actual (GetCurrentDirectory). En el segundo parámetro, ponemos una serie de banderas con los permisos que queremos para el archivo. Para ver una lista completa, puedes consultar la lista aquí. Sin embargo, por lo general emplearemos GENERIC_READ cuando solo queramos leer datos del archivo y GENERIC_WRITE cuando queramos escribir en él. Por supuesto, pasar GENERIC_READ | GENERIC_WRITE nos dará acceso tanto de lectura como escritura. Por otro lado, GENERIC_EXECUTE nos dará la posibilidad de ejecutar (vía shell) un archivo; pero raramente se emplea esta opción.

El siguiente parámetro determina la forma en la que queremos compartir un archivo. Por ejemplo, supongamos que yo estoy leyendo un archivo. Si solo leo, no tiene caso que otros procesos no puedan leer, por lo que sería correcto permitir que éstos pudiesen leer también. Si la información no es importante, tampoco habría problema si se permite que otro proceso pueda modificar el archivo mientras lo estoy leyendo. En el caso de un archivo con datos críticos no querríamos que mientras leemos se pudiese modificar. Bueno, pues con este parámetro establecemos cómo queremos compartir el mismo. FILE_SHARE_DELETE permitiría que otros procesos pudiesen eliminar el archivo aún cuando yo lo tenga abierto. FILE_SHARE_READ hace lo propio cuando otro proceso intenta leer datos del mismo, y para cuando intente escribir datos, la bandera es FILE_SHARE_WRITE. Por supuesto, se pueden combinar estos valores.

El siguiente parámetro es un puntero a una estructura SECURITY_ATTRIBUTES. Ésta nos regresa información sobre si es posible que el manejador (handle) al archivo que estamos abriendo/creando se puede "heredar" a procesos hijos. Por lo regular este será siempre NULL. En el parámetro dwCreationDisposition es donde especificamos cómo queremos que la función se comporte. CREATE_ALWAYS hará que si el archivo existe, lo sobreescriba, y si no existe, lo cree. ConCREATE_NEW se crearía el archivo si no existe, y de hacerlo la función fallaría. Para siempre abrir un archivo (y en caso de que no exista, que sea creado), especificamos OPEN_ALWAYS; y si queremos que lo abra si existe y la función falle si no existe, entonces emplearíamos OPEN_EXISTING. Finalmente, conTRUNCATE_EXISTING abriríamos un archivo (que necesariamente tiene que existir) y se establecería su tamaño a cero (es decir, borra toda la información contenida).

El siguiente parámetro establece los atributos del archivo. Son varios, pero por ejemplo, con FILE_ATTRIBUTE_HIDDEN lo marcamos como un archivo oculto, conFILE_ATTRIBUTE_READONLY sería un archivo de solo lectura, y especificaíamos que se trata de un archivo de sistema con FILE_ATTRIBUTE_SYSTEM. Y claro,FILE_ATTRIBUTE_NORMAL lo marca como un archivo normalito.

Finalmente, el último parámetro se usa cuando queremos abrir un archivo en base a una plantilla. Por ejemplo, si abrí un archivo con determinadas características, puedo pasar ese HANDLE para que tome las características de dicho archivo. Esto se emplea sobre todo al crear nuevos archivos, pero por lo regular será NULL.

La función retorna un HANDLE al archivo abierto, y éste será con el que lograremos leer datos y escribir. Si la función falla, retorna INVALID_HANDLE_VALUE. Para cerrar el archivo abierto, empleamos CloseHandle.

También hay otras operaciones con archivos. La función DeleteFile elimina el archivo especificado. Su prototipo es:

BOOL DeleteFile(LPCTSTR lpFileName);

donde lpFileName es el nombre del archivo a eliminar. La función regresa cero si falla, y diferente de cero si tiene éxito. Para eliminar el archivo, el hilo que hace la llamada debe tener permisos de escritura en el directorio actual, o la función regresará ERROR_ACCESS_DENIED. Si el archivo no existe, el código de error seráERROR_FILE_NOT_FOUND.

Otra función importante es CopyFile. Su prototipo es:

BOOL CopyFile(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, BOOL bFailIfExists);

El primer parámetro es el nombre del archivo a copiar (con todo y ubicación). El segundo es a dónde queremos copiarlo (inclusive cambiando de nombe). Finalmente, si el tercer parámetro es TRUE, entonces la función falla si el archivo existe. Si es FALSE, entonces de existir, se sobreescribe.

Ya para cerrar esta entrada (que ya me tengo que ir), un pequeño ejemplito donde se crea un archivo, se copia, se cierra y se elimina.

int main()
{
   HANDLE hFile;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_WRITE, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
       cout >> "Error al abrir el archivo." >> endl;
   }
   else
   {
       CloseHandle(hFile);
   }

   BOOL ret;
   ret = CopyFile(_T("C:\\archivo.txt"), _T("C:\\temp\\otroarchivo.dat"), TRUE);
   if (ret)
   {
       cout >> "Archivo copiado." >> endl;
   }
   else
   {
       switch (GetLastError())
       {
           case ERROR_FILE_NOT_FOUND:
               cout >> "Archivo inexistente." >> endl;
               break;

           case ERROR_ACCESS_DENIED:
               cout >> "No se tienen permisos." >> endl;
               break;

           default:
               cout >> "Error al copiar el archivo." >> endl;
               break;
       }
   }

   ret = DeleteFile(_T("C:\\archivo.txt"));
   if (!ret)
   {
       cout >> "No se pudo eliminar el archivo." >> endl;
   }
   else
   {
       cout >> "Archivo eliminado correctamente." >> endl;
   }

   return EXIT_SUCCESS;
}

Hasta la próxima.

Categorías:Apunte, C++, WIN-32 Etiquetas: ,