NovaMonoFix
Errores PHP
X
Usuario
Password
0 FPS

Tutorial WinAPI C++ 3.11 (Terminando el Ensamblador)

06 de Noviembre del 2010 por Josep Antoni Bover, 22 visitas, 0 comentarios, 0 votos
Categorías : Windows, Programación, C y C++.
La web está en segundo plano, animación en pausa.
Cargando animación...
Tutorial WinAPI C++ 3.11 (Terminando el Ensamblador)

Esta es la ultima parte del tercer tutorial, donde juntaremos todos los controles estandar que hemos visto anteriormente y formaremos una aplicacion encargada de hacer una lista de archivos.

Con esa lista de archivos la aplicacion podra comprimirlos todos para luego insertarlos en el ejecutable del Instalador.

Y tambien le indicaremos la ruta predeterminada de la instalación.

Ahora que tenemos todos los controles necesarios, además del ejecutable de la instalación solo nos queda hacer el programa para ensamblar la instalación con los archivos.

La idea es hacer un programa que nos permita seleccionar los archivos que queremos meter dentro del instalador, y nos pida datos, como el directorio por defecto y el archivo que se usara como instalador.

Además le añadiremos posibilidad de cargar y guardar proyectos de instalador.

Por último en vez de utilizar una ventana como siempre utilizaremos un dialogo y veremos en que se diferencian.

Vamos al lio, lo primero será crear un ObjetoDialogo para contener diálogos de windows :

Aunque parece prácticamente igual que un ObjetoVentana hay que destacar que el _GestorMensajes retorna BOOL mientras que el el ObjetoVentana retorna LRESULT. Esto no es porque ami me guste mas… y a decir verdad no se la razón de ello, simplemente si vamos a usar un dialogo necesitamos utilizar WindowProcedures que retornen BOOL. Y aquí está la razón de porque utilizamos la clase plantilla PlantillaEventos a la hora de basar todas nuestras ventanas / diálogos / controles.

Por último la función CrearDialogo solo recibe un parámetro, que es la ID que tiene el dialogo en los recursos. Para especificar los estilos del dialogo hay que hacerlo mediante el editor de recursos.

Veamos la declaración de un dialogo pequeño que necesitaremos para poner nombre al nuevo proyecto DialogoNuevoProyecto :

Archivo : DialogoNuevoProyecto.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Clase que hereda ObjetoControlEstandar y se centra en las funciones del listbox
class DialogoNuevoProyecto : protected ObjetoDialogo {
public : //////////////////// Miembros publicos
// -Constructor
DialogoNuevoProyecto(void);
// -Destructor
~DialogoNuevoProyecto(void);
// -Función para crear el dialogo para introducir el nombre del nuevo proyecto
// Devuelve el nombre del proyecto o NULL si se ha cancelado.
const TCHAR *Mostrar(HWND hWndParent);
// -Re-emplazamos el evento cerrar y añadimos la API EndDialog
BOOL Evento_Cerrar(void) {
EndDialog(_hWnd, 0);
return ObjetoDialogo::Evento_Cerrar();
};
// -Función que se llama al crear el dialogo
BOOL Evento_IniciarDialogo(HWND hWndNuevoFoco);
// -Re-emplazamos el evento commando para obtener que button se ha pulsado
BOOL Evento_Comando(const UINT nID, const UINT nNotificacion, HWND hWndControl);
protected :
ObjetoEditBox Edit_Nombre;
ObjetoButton Boton_Ok;
ObjetoButton Boton_Cancelar;
TCHAR *_NombreProyecto;
};

En esencia es muy parecido a una ventana, pero con la diferencia de que todos los eventos devuelven BOOL, y que en el Evento_Cerrar para cerrar el dialogo utilizamos la API EndDialog en vez de la API DestroyWindow.

Veamos la función Mostrar :

Archivo : DialogoNuevoProyecto.cpp
1
2
3
4
5
6
7
// -Función para crear el dialogo MODAL para introducir el nombre del nuevo proyecto
const TCHAR *DialogoNuevoProyecto::Mostrar(HWND hWndParent) {
if (_NombreProyecto != NULL) delete [] _NombreProyecto;
_NombreProyecto = NULL;
DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_NUEVO_PROYECTO), hWndParent, (DLGPROC)_GestorMensajes, (LPARAM)this);
return _NombreProyecto;
}

Para crear un dialogo y enlazarlo a nuestro ObjetoDialogo utilizamos la API DialogBoxParam pasándole la ID del dialogo, y el _GestorMensajes que debe usar. El _GestorMensajes que se ha usado es el de ObjetoDialogo::_GestorMensajes, y este cuando recibe el mensaje WM_INITDIALOG enlaza la clase con el dialogo utilizando la API SetWindowLongPtr con la constante GWLP_USERDATA en el que le especificamos un puntero a nuestro ObjetoDialogo.

Una vez hecho esto el gestor de mensajes estático puede pasar los mensajes a las funciones virtuales de los eventos.

Ahora veamos la declaración del dialogo principal de la aplicación DialogoEnsamblador :

Archivo : DialogoEnsamblador.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Objeto final : ObjetoHWND -> PlantillaEventos -> ObjetoDialogo -> DialogoEnsamblador
class DialogoEnsamblador : public ObjetoDialogo {
public : //////////////////// Miembros publicos
// Constructor
DialogoEnsamblador(void);
// Función que recibe el mensjae WM_CLOSE
BOOL Evento_Cerrar(void);
// Función que recibe cuando se pulsa un Button de windows
BOOL Evento_Comando(const UINT nID, const UINT nNotificacion, HWND hWndControl);
// Función que crea el dialogo para el ensamblador
void Crear(void);
// Función para mostrar un dialogo nuevo poryecto
void NuevoProyecto(void);
// Función para mostrar un dialogo abrir proyecto
void CargarProyecto(void);
// Función para mostrar un dialogo guardar proyecto
void GuardarProyecto(void);
// Función para mostrar un dialogo de seleccion de archivos
void AgregarArchivos(void);
// Función para mostrar un dialogo de seleccion de directorios
void AgregarDirectorio(void);
// Función que borra los archivos seleccionados de la lista
void BorrarSeleccionado(void);
// Función que muestra un dialogo para buscar el Instalar.exe
void BuscarEXE(void);
// Función que crea el instalar.exe con los archivos de la lista
void CrearInstalador(void);
protected : ///////////////// Miembros protegidos
// Función que comprueba si la ruta del path relativa tiene caracteres invalidos
// Los caracteres / : * ? " < > | no son válidos para crear archivos y directorios
BOOL _PathValido(const TCHAR *nText);
// Función para activar / desactivar los controles del dialogo
void _ActivarControles(const bool nActivar);
// Función que nos dice si el path esta ya en la lista
BOOL _BuscarPathAbsoluto(const TCHAR *nPath);
// Función para agregar un directorio de forma recursiva
UINT _AgregarArchivosRecursivo(const TCHAR *nPath, const UINT TamPathInicial = 0);
// Función que recorre todos los nodos para construir la jerarquia de directorios
UINT _ArbolRecursivo( ObjetoArchivo &InstalarExe, HTREEITEM nNodo = NULL,
TCHAR *PathActual = TEXT(""), const bool Root = true );
// Función para mostrar el ultimo error con FormatMessage añadiendo un texto personalizado
void _MostrarUltimoError(const TCHAR *Mensaje = NULL);
// Función para imprimir texto en la ventana Resultados
void _ImprimirDebug(const TCHAR *Txt, ...);
// Botones
ObjetoButton Boton_NuevoProyecto;
ObjetoButton Boton_AbrirProyecto;
ObjetoButton Boton_GuardarProyecto;
ObjetoButton Boton_AgregarArchivos;
ObjetoButton Boton_AgregarDirectorios;
ObjetoButton Boton_BorrarSeleccionado;
ObjetoButton Boton_CrearInstalador;
ObjetoButton Boton_Buscar;
// Barra de progreso
ObjetoProgressBar Barra_Total;
// Lista de archivos
ObjetoListView Lista;
// Arbol de directorios a crear
ObjetoTreeView Arbol;
// EditBox que indican los paths
ObjetoEditBox Path_Destino;
ObjetoEditBox Path_Instalar;
// ComboBox que indica el path por defecto
ObjetoComboBox Path_Defecto;
ProyectoIPRJ IPRJ;
};

La mayoría de funciones de este objeto ya se han tratado en los tutoriales anteriores, lo que mas nos interesa ver de este objeto son las siguientes funciones : CrearInstalador, _AgregarArchivosRecursivo, y _ArbolRecursivo.

Empezaremos por _AgregarArchivosRecursivo, esta función tiene como objetivo agregar todos los archivos que se encuentren en el directorio especificado al ObjetoListView :

Archivo : DialogoEnsamblador.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Función para agregar un directorio de forma recursiva
UINT DialogoEnsamblador::_AgregarArchivosRecursivo(const TCHAR *nPath, const UINT TamPathInicial) {
WIN32_FIND_DATA FindInfoPoint;
HANDLE hFind = NULL;
bool Valido = false;
TCHAR PathFinal[MAX_PATH +1];
TCHAR PathTmp[MAX_PATH +1];
TCHAR PathRelativoTmp[MAX_PATH +1];
UINT TamInicial = 0;
UINT Tam = wcslen(nPath);
wcscpy_s(PathFinal, MAX_PATH +1, nPath);
// Agrego una antibarra al final, si no la lleva
if (PathFinal[Tam -1] != TEXT('\\')) {
Tam ++;
wcscat_s(PathFinal, TEXT("\\"));
}
wcscat_s(PathFinal, TEXT("*.*"));
// Especifico el tamaño inicial del path
if (TamPathInicial == 0) TamInicial = Tam;
else TamInicial = TamPathInicial;
// Busco archivos recursivamente
hFind = FindFirstFile(PathFinal, &FindInfoPoint);
if (hFind == INVALID_HANDLE_VALUE) return 0;
do {
wcscpy_s(PathTmp, MAX_PATH +1, PathFinal);
PathTmp[Tam] = 0;
Valido = true;
// Comprobamos la validez del archivo (basicamente por si es el directorio "." o el directorio "..")
if (FindInfoPoint.cFileName[0] == TEXT('.')) {
if (FindInfoPoint.cFileName[1] == 0) Valido = false;
else if (FindInfoPoint.cFileName[1] == TEXT('.') && FindInfoPoint.cFileName[2] == 0) Valido = false;
}
if (Valido == true) {
wcscat_s(PathTmp, MAX_PATH +1, FindInfoPoint.cFileName);
if (FindInfoPoint.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// Si es un directorio realizo una nueva busqueda recursiva dentro de el
_AgregarArchivosRecursivo(PathTmp, TamInicial);
}
else {
// Si es un archivo lo añado a la lista, y analizo sus directorios
wcscpy_s(PathRelativoTmp, MAX_PATH +1, TEXT("\\"));
wcscat_s(PathRelativoTmp, MAX_PATH +1, &PathTmp[TamInicial]);
if (_BuscarPathAbsoluto(PathTmp) == FALSE) {
IPRJ.AnalizarRuta(PathRelativoTmp);
Lista.AgregarItem(0, PathRelativoTmp, PathTmp);
}
}
}
} while (FindNextFile(hFind, &FindInfoPoint) != 0);
return 0;
}

Lo primero que hace esta función es construir un path que al final tenga “*.*” para pasárselo a la API FindFirstFile. A partir de que le pasamos los parámetros de búsqueda a la API FindFirstFile, debemos ir llamando a la API FindNextFile hasta que retorne 0.

Cada vez que llamamos a FindFirstFile o FindNextFile el miembro de la estructura WIN32_FIND_DATA cFileName contendrá el nombre de un archivo, lo que hacemos es mirar si es un archivo valido, ya que nos puede devolver “.” Y “..”, y luego comprobamos si es un directorio o un archivo.
En el caso de ser un directorio hacemos otra llamada a _AgregarArchivosRecursivo para inspeccionarlo. Y en el caso de ser un archivo lo agregamos al ObjetoListView.

Con esto conseguimos enumerar todos los archivos del directorio especificado recursivamente.

Ahora veamos la función CrearInstalador :

Archivo : DialogoEnsamblador.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Función que crea el instalar.exe con los archivos de la lista
void DialogoEnsamblador::CrearInstalador(void) {
TCHAR PathDestino[MAX_PATH +1];
TCHAR Tmp2[512];
TCHAR TmpRelativo[512];
TCHAR PathInstalarExe[MAX_PATH +1];
TCHAR Mensaje[512];
// 1 - Comprobación de datos
// 1.1 No hay archivos para la instalación
if (Lista.TotalItems() == 0) {
MessageBox(_hWnd, TEXT("No se han añadido archivos al instalador"), TEXT("Error!"), MB_OK);
return;
}
// 1.2 La ruta destino de la instalación no es valida
Path_Destino.ObtenerTexto(PathDestino, MAX_PATH + 1);
if (_PathValido(PathDestino) == FALSE) {
MessageBox( _hWnd,
TEXT("La ruta especificada no es válida. \nLa ruta no puede tener los siguientes caracteres / : * ? \" < > |"),
TEXT("Error"), MB_OK );
Path_Destino.AsignarFoco();
return;
}
// 1.3 El archivo Instalar.exe no existe
Path_Instalar.ObtenerTexto(PathInstalarExe, MAX_PATH + 1);
HANDLE HInstalar = CreateFile(PathInstalarExe, FILE_READ_DATA, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
DWORD TamInstalar = GetFileSize(HInstalar, NULL);
if (HInstalar == INVALID_HANDLE_VALUE) {
// Si estas ejecutando el ejemplo de www.devildrey33.es y te sale este error, compila el ejemplo 3.10 Terminando Instalador
swprintf_s( Tmp2, 512, TEXT("No se encuentra el archivo : %s \nSin este archivo no se puede crear el instalador."),
PathInstalarExe);
MessageBox(_hWnd, Tmp2, TEXT("Error!"), MB_OK);
Path_Instalar.AsignarFoco();
return;
}
CloseHandle(HInstalar);
HInstalar = NULL;
// 2 - Creación del instalador
// 2.1 Abrimos el archivo destino
ObjetoArchivo InstalarExe;
ObjetoArchivo ArchivoL; // NOTA hay que hacer que se valide el nombre del proyecto o podria incluir caracteres no validos
swprintf_s(Tmp2, 512, TEXT("..\\Instalar %s.exe"), IPRJ.Nombre());
if (InstalarExe.AbrirArchivoEscritura(Tmp2, true) == FALSE) {
swprintf_s(Mensaje, 512, TEXT("Error creando el archivo %s."), Tmp2);
_MostrarUltimoError(Mensaje);
return;
}
// 2.2 Leemos el contenido del Instalar.exe y lo volcamos al Instalador final.
ArchivoL.AbrirArchivoLectura(PathInstalarExe);
char *Buffer = new char[TamInstalar];
ArchivoL.Leer(Buffer, TamInstalar * sizeof(char));
InstalarExe.Guardar(Buffer, TamInstalar * sizeof(char));
delete [] Buffer;
// 2.3 Guardamos el directorio destino
InstalarExe.GuardarUINT(Path_Defecto.Seleccion());
InstalarExe.GuardarString(PathDestino);
// 2.4 Parseamos el arbol para crear la estructura de directorios y la guardamos
UINT TotalDirectorios = Arbol.TotalNodos() - 1; // Restamos 1 porque el root ya se creara por otro lado
InstalarExe.GuardarUINT(TotalDirectorios); // Guardamos el total de directorios
_ArbolRecursivo(InstalarExe);
// 2.5 Comprimimos y guardamos los archivos
UINT TotalArchivos = Lista.TotalItems();
ObjetoZLIB Compresor;
ContenedorBinario ArchivoComprimido;
InstalarExe.GuardarUINT(TotalArchivos); // Guardamos el total de archivos
Barra_Total.AsignarRango(0, TotalArchivos);
for (UINT i = 0; i < TotalArchivos; i++) {
Lista.ObtenerTexto(i, 1, Tmp2, 512); // Obtenemos la ruta absoluta del archivo
Lista.ObtenerTexto(i, 0, TmpRelativo, 512); // Obtenemos la ruta relativa del archivo
InstalarExe.GuardarString(TmpRelativo); // Guardamos la ruta relativa dentro del Instalar.exe
Compresor.Comprimir(Tmp2, ArchivoComprimido); // Comprimimos el archivo en memoria
InstalarExe.GuardarUINT(ArchivoComprimido.Longitud()); // Guardamos el tamaño de los datos comprimidos
InstalarExe.Guardar(ArchivoComprimido(), ArchivoComprimido.Longitud()); // Guardamos los datos comprimidos en el Instalar.exe
Barra_Total.Valor(i);
// esperar eventos
}
// 2.6 Guardamos el tamaño original del Instalar.exe al final del archivo
InstalarExe.GuardarDWORD(TamInstalar);
// 2.7 Guardamos la cabecera al final de todo para saber si el instalar.exe esta vacio o lleno.
TCHAR Cabecera[32] = TEXT("Instalador 1.0 devildrey33 ");
InstalarExe.Guardar(Cabecera, sizeof(TCHAR) * 32);
MessageBox( _hWnd, TEXT("El instalador se ha creado correctamente en el directorio '3.11 Tutorial terminando Ensamblador'."),
TEXT("Instalador creado"), MB_OK);
}
Tabla que nos muestra un instalador por dentro :
Instalar.exe (BINARO) : Esta parte contiene el ejecutable que descomprimirá los datos.
Directorio destino : UINT con el directorio por defecto, String con el nombre del directorio.
Subdirectorios a crear : UINT con el total de directorios, String * total directorios.
Archivos comprimidos : UINT con el total de archivos, String con el path, UINT con el tamaño de los datos binarios, char * tamaño.
Cabecera : DWORD con la posición de inicio del instalador, TCHAR * 32 con este texto “Instalador 1.0 devildrey33    “
Un String se compone de : UINT con el tamaño en caracteres, TCHAR * tamaño en caracteres.

Y por ultimo veamos la función _ArbolRecursivo, que enumera todos los nodos dentro del TREEVIEW (que representan directorios) y los guarda dentro del Instalar.exe

Archivo : DialogoEnsamblador.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Función que recorre todos los nodos para construir la jerarquia de directorios
UINT DialogoEnsamblador::_ArbolRecursivo(ObjetoArchivo &InstalarExe, HTREEITEM nNodo, TCHAR *PathActual, const bool Root) {
TCHAR nPathActual[512];
TCHAR Tmp[512];
HTREEITEM NodoTmp = NULL;
HTREEITEM Hijo = NULL;
UINT TotalDirectorios = 0;
// Si el nNodo es NULL asignamos el nodo ROOT
if (nNodo == NULL) NodoTmp = Arbol.NodoRoot();
else NodoTmp = nNodo;
// Mientras NodoTmp no sea NULL
while (NodoTmp != NULL) {
// Obtenemos el primer hijo de NodoTmp o NULL si no hay ninguno
Hijo = Arbol.NodoHijo(NodoTmp);
// Si no es el nodo ROOT
if (Root == false) {
Arbol.ObtenerTexto(NodoTmp, Tmp, 512);
// Añadimos PathActual al principio del nPathActual, y al final agregamos el nombre del nodo actual
swprintf_s(nPathActual, 512, TEXT("%s\\%s"), PathActual, Tmp);
_ImprimirDebug(TEXT("%s\n"), nPathActual);
// Guardamos la ruta completa del directorio
InstalarExe.GuardarString(nPathActual);
TotalDirectorios ++;
}
else {
// Si es el nodo ROOT dejamos PathActual tal y como esta
wcscpy_s(nPathActual, 512, PathActual);
}
// Si el Hijo no es NULL lo escaneamos recursivamente
if (Hijo != NULL) TotalDirectorios += _ArbolRecursivo(InstalarExe, Hijo, nPathActual, false);
// Asignamos como nodo actual el siguiente nodo
NodoTmp = Arbol.NodoSiguiente(NodoTmp);
}
// Devolvemos el total de directorios a crear
return TotalDirectorios;
}

El objetivo de esta función es enumerar todos los nodos partiendo del nodo ROOT, e ir montando el path en memoria para volcarlo al archivo instalar.exe

Hay que pensar que los nodos simplemente son strings, y que tenemos que concaternar todos los padres con sus hijos para obtener el path correctamente.

En esencia escaneamos todos los nodos partiendo del nodo ROOT utilizando la función ObjetoTreeView::NodoSiguiente, si vemos que algún nodo tiene uno o mas hijos con la función ObjetoTreeView::NodoHijo, volvemos a llamar a la función _ArbolRecursivo para añadir sus hijos. Por último agregamos las cadenas concatenadas al Instalar.exe

Y aqui termina la tercera serie de tutoriales, espero que sean de ayuda para aquellos que os iniciais en la programación con el API de windows.

Actualmente creo que los tutoriales en cuanto a ventanas y graficos tienen un nivel bastante aceptable (quizas se revise o extienda algúno pero por lo general abarcan la mayoria de cosas básicas a mi entender), por lo que los proximos tutoriales intentare enfocarlos a otros temas de interes como pueden ser : Creación de archivos dmp (para depurar aplicaciones), Creación y manejo de componentes COM, Interacción con redes/internet (por ejemplo mandar un e-mail mediante Winsock, SMTP y MIME, o hacer un chat cliente/servidor simple), etc...