Mayo de 2017

Volumen 32, número 5

C++: usar el lenguaje C++ moderno para acceder al Registro de Windows

Por Giovanni Dicanio

El SO Windows expone una serie de API de la interfaz C, destinada a proporcionar a los desarrolladores acceso al Registro. Algunas de estas API son de bastante bajo nivel y exigen que los programadores presten atención a muchos detalles. A partir de Windows Vista, se incorporó una especie de API de mayor nivel a la combinación: la función RegGetValue (bit.ly/2jXtfpJ). Antes de la introducción de esta API, para leer un valor del Registro, se tenía que abrir primero la clave del Registro deseada que contenía el valor mediante una llamada a RegOpenKeyEx. A continuación, se tenía que llamar a RegQueryValueEx API, lo que suponía tratar con un gran número de complejos detalles. Por ejemplo, la lectura de un valor de cadena con RegQueryValueEx no garantiza que la cadena devuelta terminase en NUL correctamente, lo que causa peligrosos errores de seguridad en el código. Para evitar que eso suceda, se debe prestar atención y comprobar si existe un terminador NUL en la cadena devuelta; de lo contrario, debe agregarlo. Además, debe asegurarse de cerrar correctamente la clave abierta mediante una llamada a RegCloseKey.

Obviamente, la apertura de la clave del Registro puede fallar, por lo que también debe agregar código para resolverlo. RegGetValue API simplifica este flujo de trabajo, ya que abre automáticamente la clave del Registro deseada, la cierra tras su uso y termina las cadenas en NUL correctamente antes de devolverlas al llamador. A pesar de esta simplificación, la función RegGetValue sigue siendo una función de la interfaz C de bajo nivel; además, el hecho de que pueda controlar distintos tipos de valores del Registro (desde valores DWORD hasta cadenas y datos binarios) hace que su interfaz resulte difícil de programar.

Afortunadamente, puede usar el lenguaje C++ moderno para compilar correctamente abstracciones de mayor nivel en torno a RegGetValue API de Win32, lo que ofrece una interfaz simplificada para leer distintos tipos de valores del Registro.

Representación de errores mediante excepciones

RegGetValue API es una API de la interfaz C y, como tal, usa códigos de error para indicar estados de error al llamador. En particular, esta función devuelve un valor de tipo LONG: ERROR_SUCCESS (es decir, cero) en caso de éxito y un valor distinto en caso de errores. Por ejemplo, si el búfer de salida que proporciona el llamador no es lo bastante grande para que la API escriba sus datos, la función devuelve ERROR_MORE_DATA. Para compilar una interfaz C++ de mayor nivel en torno a la API de C, puede definir una clase de excepción de C++ para representar errores. Esta clase puede derivar de la clase std::runtime_error estándar, en la cual se puede insertar el código de error LONG devuelto por RegGetValue:

class RegistryError
  : public std::runtime_error
{
public:
  ...
private:
  LONG m_errorCode;
};

Además, se pueden insertar otros fragmentos de información en el objeto de excepción; por ejemplo, HKEY y el nombre de la subclave. Esta es una implementación básica.

Se puede agregar un constructor para crear una instancia de esta clase de excepción mediante un mensaje de error y el código de retorno de la llamada con error a RegGetValue:

RegistryError(const char* message, LONG errorCode)
  : std::runtime_error{message}
  , m_errorCode{errorCode}
{}

El código de error se puede exponer en los clientes mediante un descriptor de acceso de solo lectura (getter):

LONG ErrorCode() const noexcept
{
  return m_errorCode;
}

Ahora que tiene compilada esta clase de excepciones, puede pasar a encapsular RegGetValue API de C en una interfaz C++ de nivel superior que sea más fácil de usar y menos propensa a errores.

Lectura de un valor DWORD del Registro

Empecemos con una operación sencilla: usar RegGetValue API para leer un valor DWORD del Registro. En este caso, el patrón de uso es bastante simple. Veamos primero qué tipo de interfaz se puede definir en C++ para administrar este caso.

Este es el prototipo de RegGetValue API:

LONG WINAPI RegGetValue(
  _In_        HKEY    hkey,
  _In_opt_    LPCTSTR lpSubKey,
  _In_opt_    LPCTSTR lpValue,
  _In_opt_    DWORD   dwFlags,
  _Out_opt_   LPDWORD pdwType,
  _Out_opt_   PVOID   pvData,
  _Inout_opt_ LPDWORD pcbData
);

Como puede ver, esta función de la interfaz C toma datos altamente genéricos, como un búfer de salida void* (pvData) y un parámetro de tamaño de búfer de entrada/salida (pcbData). Además, existen cadenas de estilo C (lpSubKey e lpValue) que identifican la clave del Registro y el nombre del valor especifico bajo esa clave. Este prototipo de función C se puede trabajar un poco a fin de simplificarlo para los llamadores de C++.

En primer lugar, ya que va a indicar estados de error mediante la generación de excepciones de C++, el contenedor de C++ puede devolver el valor DWORD leído del Registro como un valor devuelto. Esta acción elimina automáticamente la necesidad del parámetro del búfer de salida void* sin procesar (pvData) y el parámetro de tamaño asociado (pcbData).

Además, mientras usa C++, es mejor representar cadenas Unicode (UTF-16) mediante la clase std::wstring en lugar de punteros de estilo C sin formato. De este modo, puede definir esta función de C++ mucho más simple para leer un valor DWORD del Registro:

DWORD RegGetDword(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

Como puede ver, no existen parámetros PVOID ni LPDWORD; las cadenas de entrada se pasan a través de referencias const a objetos std::wstring, y esta función de C++ devuelve el valor leído del Registro como un valor DWORD. Esta es una interfaz indudablemente mucho más simple y de mayor nivel.

Profundicemos ahora en la implementación. Como ya mencionamos, el patrón de invocación de RegGetValue en este caso es bastante simple. Solo tiene que declarar una variable DWORD que almacenará el valor leído del Registro:

DWORD data{};

Luego, necesita otra variable DWORD que represente el tamaño (en bytes) del búfer de salida escrito por RegGetValue. Observe que el búfer de salida en este caso simple es la variable “data” anterior y su tamaño es constantemente el tamaño de un valor DWORD:

DWORD dataSize = sizeof(data);

No obstante, tenga en cuenta que no puede marcar dataSize como “const”, porque es al mismo tiempo un parámetro de entrada y de salida de RegGetValue.

Luego, puede invocar RegGetValue API:

LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_DWORD,
  nullptr,
  &data,
  &dataSize
);

Los objetos wstring de entrada se convierten en punteros de cadena de estilo C sin formato a través del método wstring::c_str. La marca RRF_RT_REG_DWORD restringe el tipo del valor del Registro a DWORD. Si el valor del Registro que intenta leer es de otro tipo, la llamada de función RegGetValue fallará sin duda.

Los últimos dos parámetros representan la dirección del búfer de salida (en este caso, la dirección de la variable de datos) y la dirección de una variable que almacena el tamaño del búfer de salida. De hecho, cuando se devuelve, la función RegGetValue indica el tamaño de los datos escritos en el búfer de salida. En este caso de lectura de un valor DWORD simple, el tamaño de los datos siempre es de 4 bytes, es decir, sizeof(DWORD). No obstante, este parámetro de tamaño es más importante para los valores de tamaño variable, tales como las cadenas; lo explicaré más adelante en este artículo.

Después de invocar la función RegGetValue, puede comprobar el código de retorno y generar una excepción en caso de error:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read DWORD from registry.", retCode};
}

Observe que el código de error (retCode) que devuelve RegGetValue está insertado en el objeto de excepción y que, más tarde, puede recuperarlo el código que procesará la excepción.

O bien, en caso de éxito, la variable de datos DWORD se puede devolver simplemente al llamador:

return data;

Eso es todo lo necesario para la implementación de la función.

El llamador puede invocar simplemente esta función de contenedor de C++ con código como el siguiente:

DWORD data = RegGetDword(HKEY_CURRENT_USER, subkey, L"MyDwordValue");

Observe la simplicidad de este código en comparación con la llamada API RegGetValue de C original. Solo se tiene que pasar un identificador a una clave del Registro abierta (en este ejemplo, la clave predefinida HKEY_CURRENT_USER), una cadena que contiene la subclave y el nombre del valor. En caso de éxito, el valor DWORD se devuelve al llamador. Por otro lado, en caso de error, se genera una excepción personalizada de tipo RegistryError. Este tipo de código es de mayor nivel y mucho más simple que invocar RegGetValue. De hecho, la complejidad de RegGetValue está oculta dentro de esta función de contenedor RegGetDword personalizada de C++.

Puede usar el mismo patrón para leer un valor QWORD (datos de 64 bits) del Registro; en este caso, solo tiene que sustituir el tipo DWORD del valor del Registro por el valor ULONGLONG de 64 bits.

Lectura de un valor de cadena del Registro

La lectura de un valor DWORD del Registro es bastante simple: una sola llamada a RegGetValue API de Win32 API es suficiente. Esto se debe principalmente a que un valor DWORD es de tamaño fijo, 4 bytes, el tamaño de un valor DWORD. Por otro lado, la lectura de cadenas del Registro presenta un nuevo nivel de complejidad, ya que las cadenas son datos de tamaño variable. La idea en este caso es llamar dos veces a RegGetValue API: En la primera llamada, se solicita a la API que devuelva el tamaño deseado del búfer de la cadena de salida. A continuación, se asigna de forma dinámica un búfer del tamaño adecuado. Finalmente, se realiza una segunda llamada a RegGetValue para escribir realmente los datos de la cadena en el búfer asignado anteriormente. (Este patrón se trata de manera más detallada en mi artículo anterior, "Using STL Strings at Win32 API Boundaries" (Uso de cadenas STL en límites de API Win32), disponible en msdn.com/magazine/mt238407).

En primer lugar, eche un vistazo al prototipo de la función de contenedor de nivel superior de C++:

std::wstring RegGetString(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

Como en el caso de DWORD, se simplifica enormemente en relación con el complejo prototipo de RegGetValue API de C original. El valor de cadena se devuelve desde la función como una instancia std::wstring. En su lugar, en caso de errores, se genera una excepción. El nombre de subclave y el nombre de valor se pasan también como parámetros de referencia const del objeto wstring de entrada.

Ahora explicaré el código de la implementación.

Como escribí, la idea es llamar primero a RegGetValue API para obtener el tamaño del búfer de salida donde se almacenará el valor de cadena:

DWORD dataSize{};
LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_SZ,
  nullptr,
  nullptr,
  &dataSize
);

Se puede observar una sintaxis de llamada similar a la del caso del valor DWORD anterior. Los objetos wstring se convierten en punteros de cadena de estilo C al invocar el método wstring::c_str. La marca RRF_RT_REG_SZ de este caso restringe el tipo de Registro válido al tipo de cadena (REG_SZ). En caso de éxito, RegGetValue API escribirá el tamaño del búfer de salida deseado (expresado en bytes) en la variable dataSize.

En caso de error, deberá generar una excepción de la clase personalizada RegistryError:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read string from registry", retCode};
}

Ahora que conoce el tamaño del búfer de salida deseado, puede asignar un objeto wstring del tamaño necesario para la cadena de salida:

std::wstring data;
data.resize(dataSize / sizeof(wchar_t));

Observe que el valor de dataSize devuelto por RegGetValue se expresa en bytes, pero el método wstring::resize espera un tamaño expresado en número de wchar_t. Por tanto, debe aplicar la escala de bytes a wchar_t y dividir el valor de tamaño de bytes anterior por sizeof(wchar_t).

Ahora que tiene una cadena con suficiente espacio asignado, puede pasar a RegGetValue API un puntero a su búfer interno, que esta vez escribirá los datos de la cadena real en el búfer proporcionado:

retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_SZ,
  nullptr,
  &data[0],
  &dataSize
);

&data[0] es la dirección del búfer interno de wstring que escribirá RegGetValue API.

Como es habitual, es importante comprobar el resultado de la llamada API y generar una excepción en caso de error:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read string from registry", retCode};
}

Observe que, en caso de éxito, RegGetValue escribe el tamaño de la cadena de resultado real (en bytes) en la variable dataSize. Debe cambiar el tamaño del objeto wstring de acuerdo con este tamaño. Dado que dataSize se expresa en bytes, es mejor convertirlo al número de wchar_t correspondiente al tratar con objetos wstring:

DWORD stringLengthInWchars = dataSize / sizeof(wchar_t);

Además, dataSize incluye el carácter NUL de terminación de la cadena de salida. No obstante, los objetos wstring ya terminan con NUL, por lo que debe prestar atención para evitar una terminación NUL doble falsa para la cadena de lectura. Tiene que cortar el terminador NUL escrito por RegGetValue:

stringLengthInWchars--; // Exclude the NUL written by the Win32 API
data.resize(stringLengthInWchars);

Observe que RegGetValue API garantiza una cadena terminada en NUL en caso de éxito, aunque la cadena original almacenada en el Registro no terminase en NUL. Este comportamiento es mucho más seguro que la antigua API RegQueryValueEx API, que no garantizaba la terminación NUL para las cadenas devueltas. Por tanto, el llamador tenía que escribir código adicional para tener en cuenta el caso correctamente, lo que aumentaba la complejidad general del código y el área expuesta de los errores.

Ahora que la variable de datos contiene el valor de cadena leído del Registro, puede devolverla al llamador al salir la función:

return data;

Cuando tenga este práctico contenedor RegGetString de C++ en torno a RegGetValue API de bajo nivel de C, puede invocarlo de la siguiente manera:

wstring s = RegGetString(HKEY_CURRENT_USER, subkey, L"MyStringValue");

Como en el caso de DWORD, aumentó el nivel de abstracción de RegGetValue API de Win32, lo que proporciona una función de contenedor de C++ fácil de usar y difícil de usar incorrectamente para leer un valor de cadena del Registro. Todos los detalles y la complejidad de tratar con RegGetValue API están ocultos con seguridad en el cuerpo de esta función RegGetString personalizada de C++.

Lectura de valores de cadena múltiple del Registro

Otro tipo de valor del Registro es el que denominamos "de cadena múltiple": Básicamente, se trata de un conjunto de cadenas terminadas en NUL doble empaquetadas en un solo valor del Registro. Esta estructura de datos de las cadenas terminadas en NUL doble consta de una serie de cadenas terminadas en NUL de estilo C que residen en ubicaciones de memoria adyacentes. El final de la secuencia está marcado con un terminador NUL adicional, de modo que la estructura completa termina con dos NUL. Para obtener más detalles sobre esta estructura de datos, consulte la publicación en el blog "What Is the Format of a Double-Null-Terminated String with No Strings?" (¿Cuál es el formato de una cadena terminada en doble Null sin cadenas?) en bit.ly/2jCqg2u.

El patrón de uso de RegGetValue API de Win32 de este caso es muy parecido al del caso anterior de cadenas únicas. Es decir, primero se invoca RegGetValue API para obtener el tamaño del búfer de destino completo que contiene los datos deseados (en este caso, la secuencia completa de cadenas adyacentes que terminan con NUL doble). A continuación, se asigna de forma dinámica un búfer de ese tamaño. Finalmente, se llama a la función RegGetValue por segunda vez y se pasa la dirección del búfer asignado anteriormente para que la API pueda escribir en este los datos de cadena múltiple reales.

En este caso, debe prestar atención a la estructura de datos donde se almacena la cadena terminada en doble NUL. De hecho, mientras que un objeto std::wstring puede contener correctamente NUL insertados, se podría usar para almacenar una estructura de cadenas terminada en doble NUL, pero prefiero aumentar el nivel de abstracción y analizar la cadena terminada en doble NUL en un objeto vector<wstring> más práctico y de nivel superior.

Así, el prototipo de la función de contenedor de C++ para la lectura de valores de cadena múltiple del Registro puede tener el aspecto siguiente:

std::vector<std::wstring> RegGetMultiString(
  HKEY hKey,
  const std::wstring& subKey,
  const std::wstring& value
)

En caso de éxito, la cadena múltiple se devolverá al llamador como un objeto vector<wstring> preciso. En cambio, en caso de error, se generará una excepción con el formato RegistryError habitual.

Dentro del cuerpo de la función de contenedor de C++, debe invocar primero RegGetValue API para obtener el tamaño del búfer de salida deseado donde se almacenará la cadena múltiple:

DWORD dataSize{};
LONG retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_MULTI_SZ,
  nullptr,
  nullptr,
  &dataSize
);

Observe el uso de la marca RRF_RT_REG_MULTI_SZ en esta ocasión para especificar el tipo de valor de cadena múltiple del Registro.

Como es habitual, en caso de error, se genera una excepción y se inserta el código de error en el objeto RegistryError:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read multi-string from registry", retCode};
}

En caso de éxito, asigna un búfer del tamaño adecuado para almacenar el valor de cadena múltiple completo:

std::vector<wchar_t> data;
data.resize(dataSize / sizeof(wchar_t));

Considero que el objeto vector<wchar_t> es mucho más claro que el objeto wstring para representar el búfer de cadena múltiple sin procesar. Tenga en cuenta también que el valor de tamaño que devuelve RegGetValue API se expresa en bytes, por lo que debe convertirse adecuadamente en un número de wchar_t antes de pasarlo al método vector::resize.

A continuación, se puede invocar por segunda vez RegGetValue API para escribir los datos de cadena múltiple reales en el búfer asignado anteriormente:

retCode = ::RegGetValue(
  hKey,
  subKey.c_str(),
  value.c_str(),
  RRF_RT_REG_MULTI_SZ,
  nullptr,
  &data[0],
  &dataSize
);

El argumento &data[0] señala el principio del búfer de salida.

De nuevo, tiene que comprobar el código de retorno de la API y generar una excepción de C++ para señalar los errores:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot read multi-string"
    from registry", retCode};
}

También se recomienda cambiar el tamaño del búfer de datos correctamente con el valor de dataSize devuelto por RegGetValue API como un parámetro de salida:

data.resize( dataSize / sizeof(wchar_t) );

En este punto, la variable de datos (que es un elemento vector<wchar_t>) almacena la secuencia de cadenas terminadas en NUL doble. El último paso consiste en analizar esta estructura de datos y convertirla en un objeto vector<wstring> más práctico y de nivel superior:

// Parse the double-NUL-terminated string into a vector<wstring>
std::vector<std::wstring> result;
const wchar_t* currStringPtr = &data[0];
while (*currStringPtr != L'\0')
{
  // Current string is NUL-terminated, so get its length with wcslen
  const size_t currStringLength = wcslen(currStringPtr);
  // Add current string to result vector
  result.push_back(std::wstring{ currStringPtr, currStringLength });
  // Move to the next string
  currStringPtr += currStringLength + 1;
}

Por último, el objeto vector<wstring> de resultado se puede devolver al llamador:

return result;

Este contenedor RegGetMultiString de C++ se puede invocar simplemente de la siguiente manera:

vector<wstring> multiString = RegGetMultiString(
  HKEY_CURRENT_USER,
  subkey,
  L"MyMultiSz"
);

De nuevo, toda la complejidad de RegGetValue API de Win32 está oculta tras una práctica interfaz de C++ de alto nivel.

Enumeración de valores en una clave del Registro

Otra operación común del Registro de Windows es la enumeración de los valores en una clave del Registro determinada. Windows proporciona RegEnumValue API (bit.ly/2jB4kaV) para este fin. Aquí, mostraré cómo usar esta API para obtener una lista de los nombres y los tipos de los valores ubicados en una determinada clave del Registro y encapsularé el proceso de enumeración en una práctica función de C++ de nivel superior. Su función de C++ personalizada puede tomar como entrada un identificador HKEY válido asociado a la clave que quiere enumerar. En caso de éxito, esta función de contenedor de C++ personalizada devolverá un vector de pares: El primer elemento del par será un objeto wstring que contiene el nombre del valor y el segundo elemento será un valor DWORD que representa el tipo de valor. Así, el prototipo de esta función de contenedor de C++ tendrá la apariencia siguiente:

std::vector<std::pair<std::wstring, DWORD>> RegEnumValues(HKEY hKey)

Ahora, explicaré los detalles del proceso de enumeración. La idea es llamar primero a RegQueryInfoKey API (bit.ly/2jraw2H) para obtener información útil antes de la enumeración, como el recuento de valores total y la longitud máxima de los nombres de valor en la clave del Registro determinada, como se muestra en la Figura 1.

Figura 1 Invocación de RegQuery­InfoKey API

DWORD valueCount{};
DWORD maxValueNameLen{};
LONG retCode = ::RegQueryInfoKey(
  hKey,
  nullptr,    // No user-defined class
  nullptr,    // No user-defined class size
  nullptr,    // Reserved
  nullptr,    // No subkey count
  nullptr,    // No subkey max length
  nullptr,    // No subkey class length
  &valueCount,
  &maxValueNameLen,
  nullptr,    // No max value length
  nullptr,    // No security descriptor
  nullptr     // No last write time
);

Observe que he pasado nullptr para los fragmentos de información que no me interesan. Obviamente, debe comprobar el valor devuelto y generar una excepción si se produce algún error al llamar a la API mencionada anteriormente:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot query key info from"
    the registry", retCode};
}

De acuerdo con la página de la función RegQueryInfoKey del Centro de desarrollo de Windows (bit.ly/2lctUDt), el tamaño devuelto para la longitud máxima de los nombres de valor (almacenado en la variable maxValueNameLen en el código anterior) no incluye el valor NUL de terminación; por tanto, vamos a ajustar este valor y agregaremos uno para tener en cuenta el valor NUL de terminación al asignar un búfer para la lectura de nombres de valor:

maxValueNameLen++;

A continuación, puede asignar un búfer del tamaño correcto para leer los nombres de valor en cada paso de la enumeración; se puede usar un objeto std::unique_ptr<wchar_t[]> eficiente con poca sobrecarga para este fin:

auto nameBuffer = std::make_unique<wchar_t[]>(maxValueNameLen);

El resultado de la enumeración, en forma de pares de nombre de valor y tipo de valor, se puede almacenar en un contenedor std::vector:

std::vector<std::pair<std::wstring, DWORD>> values;

Agregará contenido a este vector progresivamente durante el proceso de enumeración y, cuando se complete la enumeración, se devolverán "valores" al llamador.

A continuación, puede usar un bucle for, llamar a RegEnumValue API repetidamente y enumerar un nuevo valor en cada paso de la iteración:

for (DWORD index = 0; index < valueCount; index++)
{
  // Call RegEnumValue to get data of current value ...
}

Observe que obtuvo valueCount de la llamada a RegQueryInfoKey anterior a la enumeración inicial.

Dentro del cuerpo del bucle for, se puede llamar a RegEnumValue API para obtener la información deseada del valor actual. En este contexto, le interesan el nombre del valor y el tipo del valor. El nombre del valor se leerá en el elemento nameBuffer asignado anteriormente; el tipo de valor se almacenará en un valor DWORD simple. Por tanto, dentro del cuerpo del bucle for, puede escribir código como el siguiente:

DWORD valueNameLen = maxValueNameLen;
DWORD valueType{};
retCode = ::RegEnumValue(
  hKey,
  index,
  nameBuffer.get(),
  &valueNameLen,
  nullptr,    // Reserved
  &valueType,
  nullptr,    // Not interested in data
  nullptr     // Not interested in data size

Como es habitual, se recomienda comprobar el valor devuelto de la API y generar una excepción en caso de error:

if (retCode != ERROR_SUCCESS)
{
  throw RegistryError{"Cannot get value info from the registry", retCode};
}

En caso de éxito, RegEnumValue API escribirá el nombre del valor en el elemento nameBuffer proporcionado y el tipo de valor en la variable valueType. De este modo, puede compilar un objeto pair<wstring, DWORD> con estos dos datos y agregar el par de datos al vector de resultado de la enumeración:

values.push_back(std::make_pair(
  std::wstring{ nameBuffer.get(), valueNameLen },
  valueType
));

Después del bucle for, el vector de "values" de resultado se puede devolver al llamador:

return values;

A continuación, el llamador puede enumerar todos los valores bajo una clave del Registro mediante una simple llamada a la función de contenedor de C++ como se indica a continuación:

auto values = RegEnumValues(hKey);
// For each value
for (const auto& v : values)
{
  // Process v.first (value's name) and v.second (value's type)
  // ...
}

Se puede usar un patrón de codificación similar para enumerar las subclaves en una clave del Registro determinada; en este caso, se debe usar RegEnumKeyEx API de Win32 (bit.ly/2k3VEX8) en lugar del RegEnumValue descrito anteriormente. El código de esta función de enumeración de subclaves se incluye en la descarga asociada a este artículo.

Un administrador de recursos seguro para los indicadores HKEY sin procesar

Las claves del Registro representadas por el tipo de identificador HKEY sin procesar de Win32 se pueden encapsular de manera segura y práctica en una clase de administrador de recursos de C++. El destructor de clase llamará correctamente a RegCloseKey API en el identificador sin procesar encapsulado para cerrarlo automáticamente. Además, se pueden definir operaciones de semántica de transferencia de recursos, como un constructor de movimiento y un operador de asignación de movimiento, para transferir de manera eficiente la propiedad del identificador encapsulado entre las distintas instancias de la clase de administrador de recursos de C++. Por motivo de eficiencia, todos los métodos de clase que no generan excepciones se marcan como noexcept, lo que permite al compilador de C++ emitir código más optimizado. Esta práctica clase de administrador de recursos de claves de C++, denominada RegKey, se implementa en el archivo Registry.hpp que acompaña a este artículo. En este archivo reutilizable de solo encabezado, también encontrará las implementaciones de un par de funciones auxiliares: RegOpenKey y RegCreateKey, que encapsulan RegOpenKeyEx API y RegCreateKeyEx API de Win32, respectivamente, y devuelven un identificador HKEY encapsulado de forma segura en la clase de administrador de recursos de C++ mencionada anteriormente. En caso de errores, esas funciones de C++ generan una excepción RegistryError y encapsulan el código de error devuelto por las API de Win32 de la interfaz C.

Resumen

RegGetValue API de Win32 proporciona una interfaz de nivel relativamente superior para la lectura de valores del Registro de Windows, en comparación con otras API de nivel inferior, como RegQueryValueEx. RegGetValue también ofrece una interfaz más segura que, por ejemplo, garantiza que las cadenas devueltas terminen en NUL correctamente. Sin embargo, RegGetValue sigue siendo una API de bajo nivel de la interfaz C, que requiere que el programador preste atención a muchos detalles. La programación en esta interfaz puede dar como resultado código complejo y propenso a errores. En este artículo, se mostró cómo compilar una moderna interfaz de C++ fácil de usar y difícil de usar incorrectamente, para ocultar las complejidades de RegGetValue API y simplificar el acceso al Registro de Windows. Además, RegEnumValue API se encapsuló en una función de C++ práctica y de nivel superior para enumerar todos los valores en una clave del Registro determinada. El código fuente que contiene la implementación de las funciones y las clases tratadas en este artículo se puede encontrar en un formulario reutilizable de solo encabezado (en el archivo Registry.hpp) en la descarga del artículo.


Giovanni Dicanio es un programador informático especializado en C++ y en el sistema operativo Windows, autor de Pluralsight (bit.ly/GioDPS) y MVP de Visual C++. Además de la programación y la creación de cursos, disfruta ayudando a los demás en foros y comunidades dedicadas a C++. Puede ponerse en contacto con él por correo electrónico en la dirección giovanni.dicanio@gmail.com. También escribe en el blog en msmvps.com/gdicanio.

Gracias a los siguientes expertos técnicos por revisar este artículo: David Cravey y Marc Gregoire
David Cravey trabaja como arquitecto empresarial en GlobalSCAPE, dirige varios grupos de usuarios de C++ y ha sido cuatro veces MVP de Visual C++.

Marc Gregoire es un ingeniero de software senior de Bélgica, fundador del grupo belga de usuarios de C++, autor de "Professional C++" (Wiley), coautor de "C++ Standard Library Quick Reference" (Apress), editor técnico en numerosos libros y, desde 2007, ha recibido el premio anual MVP por sus conocimientos en VC++. Puede ponerse en contacto con Marc en marc.gregoire@nuonsoft.com.