Intro a Win32: tipos, punteros y estructuras

Headers

  • #include <Windows.h> → Core de Win32 (incluye constante, macros y tipos de datos)
  • #include <TlHelp32.h> → Funciones como CreateToolhelp32Snapshot Encabezado Tlhelp32.h - Win32 apps
  • #include <stdio.h> → printf(), scanf(), getchar(), putchar(), gets(), puts().

Type Names and Naming Conventions

Windows usa muchos tipos de nombres que suelen ser aliases de C/C++ pero son mas fáciles de leer.
Ejemplo: unsigned long, void*, wchar_t* → DWORD, PVOID, HANDLE, LPWSTR, SIZE_T
Lista completa Windows Data Types (BaseTsd.h) - Win32 apps
Tabla de los mas comunes:
  • BYTE: Número entero sin signo de 8 bits (0 a 255). Uso: bytes en bruto, buffers, bytes de shellcode, datos binarios
  • WORD: Número entero sin signo de 16 bits (0 a 65.535). Uso: valores numéricos pequeños, flags, campos de la estructura PE
  • DWORD: Número entero sin signo de 32 bits ("Double WORD"). Uso: flags, IDs de proceso (PID), IDs de hilo (TID), códigos de error, tamaños
  • LONG: Número entero con signo de 32 bits (puede ser negativo). Uso: valores con signo, campos de estado, miembros de estructuras
  • ULONG: Número entero sin signo de 32 bits ("Unsigned LONG"). Uso: valores sin signo, contadores, flags
  • BOOL: Booleano guardado como entero (4 bytes). Uso: valor de retorno de funciones Win32: TRUE si funciona, FALSE si falla
  • BOOLEAN: Booleano guardado en un solo byte. Uso: estructuras de bajo nivel de Windows y código estilo NT (ntdll)
  • CHAR: Carácter ANSI de 8 bits. Uso: cadenas ANSI y datos de tamaño byte
  • WCHAR: Carácter "ancho" de 16 bits ("Wide CHAR"). Uso: cadenas Unicode UTF-16, que es lo que usa Windows internamente
  • LPSTR: Puntero a una cadena ANSI modificable. Uso: buffers donde se escribe texto ANSI
  • LPCSTR: Puntero a una cadena ANSI constante (solo lectura). Uso: cadenas ANSI que no se van a modificar
  • LPWSTR: Puntero a una cadena Unicode modificable. Uso: buffers donde se escribe texto Unicode
  • LPCWSTR: Puntero a una cadena Unicode constante (solo lectura). Uso: cadenas Unicode que no se van a modificar
  • PVOID: Puntero genérico, sin tipo concreto (equivale a void*). Uso: direcciones de memoria y buffers genéricos
  • LPVOID: Igual que PVOID (puntero genérico, void*). Uso: direcciones de memoria y buffers genéricos
  • HANDLE: "Manejador": un identificador que Windows te da para referirte a un objeto. Uso: ficheros, procesos, hilos, snapshots, eventos, mutex
  • HMODULE: Handle a un módulo cargado en memoria. Uso: referencia a una DLL o EXE cargado (en la práctica, su dirección base)
  • SIZE_T: Entero sin signo lo bastante grande para representar tamaños de memoria. Uso: tamaños de buffers, tamaños de reservas de memoria, operaciones con memoria
  • DWORD_PTR: Entero sin signo del tamaño de un puntero. Uso: guardar valores que deben "crecer" al pasar de x86 a x64
  • ULONG_PTR: Entero sin signo del tamaño de un puntero. Uso: aritmética de punteros y valores del tamaño de un puntero
  • UINT_PTR: Entero sin signo del tamaño de un puntero. Uso: valores sin signo del tamaño de un puntero

Pointers & Pointer-Sized

Importan porque en 32 bits los punteros ocupan 4 bytes (32 bits), y en 64 bits ocupan 8 bytes
⚠️ MAL en 64
c
DWORD value = (DWORD) pBuffer; // DWORD es 32 bits
✅ BIEN en 64 y 32
c
ULONG_PTR value = (ULONG_PTR)pBuffer;

Prefixes

Ejemplo: dwSize
  • dw → prefijo "Es un DWORD"
  • Size → lo que representa: "un tamaño"
  • b: Booleano o byte (BOOL, BOOLEAN, BYTE). Ej: bEnabled, bInheritHandle. Suele ser verdadero/falso, pero comprueba el tipo real
  • f: Flag o condición booleana (BOOL). Ej: fSuccess, fCreate. Indica una condición sí/no
  • w: WORD. Ej: wVersion, wFlags. Entero sin signo de 16 bits
  • dw: DWORD. Ej: dwSize, dwFlags, dwProcessId. El más común en Win32
  • u: Entero sin signo (UINT). Ej: uExitCode, uMsg
  • ul: Unsigned long (ULONG). Ej: ulSize, ulFlags
  • n: Número o cantidad (int). Ej: nSize, nIndex, nCmdShow. Entero normal
  • i: Índice (int). Ej: i, iItem, iIndex. Contadores de bucles, posiciones
  • cb: Count of Bytes: número de bytes. Ej: cbSize, cbBuffer. Clave en buffers y reservas de memoria
  • cch: Count of Characters: número de caracteres. Ej: cchName, cchPath. No es lo mismo que bytes (ver abajo)
  • ch: Un carácter (CHAR, WCHAR). Ej: chDrive. Un solo carácter
  • sz: String Zero: cadena terminada en \0. Ej: szName, szPath. Normalmente un array de caracteres
  • psz: Puntero a cadena terminada en \0. Ej: pszName, pszPath
  • lpsz: Long pointer a cadena terminada en \0. Ej: lpszFileName, lpszCommandLine. Estilo antiguo, igual que psz
  • p: Puntero. Ej: pBuffer, pInfo. Apunta a un objeto, buffer o estructura
  • pp: Puntero a puntero. Ej: ppBuffer. Cuando una función te "devuelve" un puntero por parámetro
  • pv: Puntero a void (PVOID). Ej: pvBuffer, pvReserved. Puntero genérico
  • ppv: Puntero a puntero a void (void**). Ej: ppvObject. Muy típico en COM
  • lp: Long pointer. Ej: lpBuffer, lpAddress. Herencia de 16 bits; hoy es igual que p
  • h: Handle (HANDLE, HMODULE, HWND, HKEY). Ej: hFile, hProcess, hThread
  • fn: Función o callback. Ej: fnCallback
  • pfn / lpfn: Puntero a función. Ej: pfnCallback, lpfnWndProc. Callbacks, procedimientos de ventana
  • rg: Array (range). Ej: rgHandles, rgData. Estilo antiguo
  • rgsz: Array de cadenas. Ej: rgszNames. Poco común
Los prefijos se combinan ejemplo lpszFileName → lp (puntero) + sz (cadena terminada en cero) + FileName
El detalle importante: cb vs cch
Es la fuente de bugs más típica con cadenas Unicode. Un WCHAR ocupa 2 bytes, así que:
c
WCHAR szPath[260];
DWORD cchPath = 260;              // 260 caracteres
DWORD cbPath  = sizeof(szPath);   // 520 bytes

ANSI and Unicode Windows API

A versiones ANSI y W versiones Unicode
c
CreateFileA(...);
CreateFileW(...);
Versiones modernas se prefiere usar Unicode (W)

Windows String Data Types

  • char / CHAR: Un carácter ANSI de 8 bits (1 byte). Ej: CHAR chValue = 'A';
  • char* / PSTR / LPSTR: Puntero a una cadena ANSI modificable. Ej: LPSTR lpString = Buffer;
  • const char* / PCSTR / LPCSTR: Puntero a una cadena ANSI constante. Ej: LPCSTR lpString = "notepad.exe";
  • wchar_t / WCHAR: Un carácter "ancho". En Windows es una unidad UTF-16 de 16 bits (2 bytes). Ej: WCHAR chValue = L'A';
  • wchar_t* / PWSTR / LPWSTR: Puntero a una cadena Unicode modificable. Ej: LPWSTR lpString = Buffer;
  • const wchar_t* / PCWSTR / LPCWSTR: Puntero a una cadena Unicode constante. Ej: LPCWSTR lpString = L"notepad.exe";
  • TCHAR: Carácter "genérico": se convierte en WCHAR si está definido UNICODE, y en CHAR si no. Ej: TCHAR chValue = TEXT('A');
  • LPTSTR: Puntero a cadena genérica modificable → LPWSTR o LPSTR según UNICODE. Ej: LPTSTR lpString = Buffer;
  • LPCTSTR: Puntero a cadena genérica constante → LPCWSTR o LPCSTR según UNICODE. Ej: LPCTSTR lpString = TEXT("notepad.exe");
Funciones equivalentes, ANSI / Unicode:
  • strlen / wcslen: Longitud en caracteres
  • strcpy / wcscpy: Copiar cadena
  • strcmp / wcscmp: Comparar cadenas
  • printf("%s") / wprintf(L"%ls"): Imprimir

Pointers

Es una variable que almacena el valor de una direccion de memoria
c
int Value = 10;
int* pValue = &Value;
  • Value: Nombre de la variable
  • pValue: Puntero al integer
  • &Value : Valor de la dirección de memoria
  • pValue : Almacena ese valor
Usamos %p para sacar la direccion memoria (0XDEADBEEF)
c
printf("Address of Value: %p\n", (void*)pValue);
Usando * obtenemos el valor de dentro de esa dirección de memoria
c
printf("Value: %d\n", *pValue);
Tambien podemos modificar el valor
c
*pValue = 20;

printf("Value: %d\n", Value);
txt
Value: 20
Son muy usados en Windows API algunos alias que mas usa Windows
PVOID, PBYTE, PDWORD, LPVOID, LPWSTR, and LPCWSTR
c
BYTE  Buffer[16] = { 0 };
PBYTE pBuffer    = Buffer;

pBuffer[0] = 0x41;   // 'A'
pBuffer[1] = 0x42;   // 'B'
pBuffer[2] = 0x43;   // 'C'
pBuffer apunta al primer byte de Buffer 0x41
Punteros genéricos: PVOID
c
PVOID pAddress = Buffer; // Puntero que no dice que (si hay bytes, enteros o una estructura)
No podemos leer ni escribir directamente a través de un PVOID hay que indicar el tipo de dato
c
PVOID pAddress = Buffer;
PBYTE pBytes = (PBYTE)pAddress; // "trata esta dirección como bytes"
printf("First byte: 0x%02X\n", pBytes[0]); // imprime 0x41
Esto es muy común en Windows: funciones como VirtualAlloc te devuelven un PVOID (memoria sin tipo) y tú decides cómo interpretarla.
NULL: "no apunta a nada"
c
int* pValue = NULL;
Es la dirección 0
c
if (pValue != NULL)
{
    printf("%d\n", *pValue);   // solo si apunta a algo
}
Se ve mucho en Windows
c
PBYTE pBuffer = (PBYTE)VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pBuffer == NULL)
{
    return FALSE;   // la reserva falló, no sigas
}
BUENAS COSTUMBRES
c
int* p = NULL;        // 1. Inicializa siempre a NULL
...
free(p);
p = NULL;             // 2. Tras liberar, vuelve a poner NULL

Structures

Es una forma de agrupar varios datos relacionados en un solo paquete
c
typedef struct _READER_INFO {
    int   Id;
    int   Age;
    char  Initial;
} READER_INFO, *PREADER_INFO;
  • _READER_INFO: La "etiqueta" original de la estructura. Analogía: el nombre técnico del molde
  • READER_INFO: Un alias para la estructura. Analogía: el nombre cómodo del molde
  • PREADER_INFO: Un alias para un puntero a la estructura. Analogía: la dirección donde está una ficha
El typedef sirve para poner alias. Sin él tendrías que escribir struct _READER_INFO cada vez;
Estas dos líneas significan exactamente lo mismo:
c
struct _READER_INFO Ficha1;   // forma larga
READER_INFO         Ficha2;   // forma corta (la habitual)
Windows usa este estilo en todos sus headers: PROCESSENTRY32 / PPROCESSENTRY32, STARTUPINFOW / LPSTARTUPINFOW, etc.
Inicializar: { 0 }
c
READER_INFO Ficha = { 0 };     // la ficha, con todos los campos a cero
PREADER_INFO pFicha = NULL;    // un puntero, que aún no apunta a ninguna ficha
Aquí está la regla clave, y es muy sencilla:
  • Si tienes la estructura, usas el punto .
  • Si tienes un puntero a la estructura, usas la flecha ->
c
READER_INFO Ficha = { 0 };
Ficha.Id  = 1470;      // tengo la ficha → punto
Ficha.Age = 34;

PREADER_INFO pFicha = &Ficha;   // pFicha apunta a Ficha
pFicha->Id = 8765;              // tengo un puntero → flecha

printf("%d\n", Ficha.Id);       // imprime 8765, porque pFicha apunta a la misma ficha
Estas dos líneas son idénticas:
c
pFicha->Id = 8765;
(*pFicha).Id = 8765;   // "ve a donde apunta pFicha, y de esa ficha coge Id"