Maggio 2017

Volume 32 Numero 5

Il presente articolo è stato tradotto automaticamente.

C++ - Usa C++ moderno per accedere al Registro di sistema di Windows

Da Giovanni Dicanio

Il sistema operativo Windows espone una serie di interfaccia C delle API per consentire agli sviluppatori di accedere al Registro di sistema. Alcune di queste API sono piuttosto basso livello ed è necessario prestare attenzione a numerosi dettagli. A partire da Windows Vista, un tipo di API di livello superiore è stato aggiunto alla combinazione: la funzione RegGetValue (bit.ly/2jXtfpJ). Prima dell'introduzione di questa API, per leggere un valore dal Registro di sistema, innanzitutto era necessario aprire la chiave del Registro di sistema desiderata contenente il valore chiamata RegOpenKeyEx. Quindi, era necessario chiamare l'API RegQueryValueEx, gestiscono molti dettagli complessi. Ad esempio, se si legge un valore stringa con RegQueryValueEx, la stringa restituita è necessariamente NUL-interrotto correttamente, che può causare una serie di errori di protezione pericoloso nel codice. Per evitare questo inconveniente, è necessario prestare attenzione per verificare se vi è un carattere di terminazione null nella stringa restituita. Se ne esiste uno, è necessario aggiungerlo. Inoltre, è necessario assicurarsi di correttamente chiudere chiave aperta, la chiamata RegCloseKey.

Naturalmente, aprire la chiave del Registro di sistema potrebbe non riuscire, pertanto è necessario aggiungere codice per gestire, nonché. L'API RegGetValue semplifica questo flusso di lavoro come venga aperta automaticamente la chiave del Registro di sistema desiderato, si chiude dopo l'utilizzo e viene correttamente stringhe termina NUL prima di restituirle al chiamante. Nonostante questa semplificazione, la funzione RegGetValue è ancora una funzione di interfaccia C a basso livello. Inoltre, il fatto che può gestire diversi tipi di valori del Registro di sistema (da DWORD in stringhe di dati binari), rende la relativa interfaccia complicato per programmare.

Fortunatamente, è possibile utilizzare C++ moderno per compilare correttamente le astrazioni di livello superiore per l'API Win32 RegGetValue, che offre un'interfaccia semplificata per leggere i valori di tipi diversi dal Registro di sistema.

Che rappresenta gli errori di utilizzo di eccezioni

L'API RegGetValue è un'interfaccia C delle API e di conseguenza segnala le condizioni di errore al chiamante mediante i codici restituiti. In particolare, questa funzione restituisce un valore di tipo LONG: ERROR_SUCCESS (vale a dire zero) nel caso di esito positivo e un valore diverso nel caso di errori. Ad esempio, se il buffer di output fornito dal chiamante non è sufficientemente grande per l'API per la scrittura dei dati, la funzione restituisce ERROR_MORE_DATA. Per la creazione di un'interfaccia C++ di livello superiore per questa API C, è possibile definire una classe di eccezioni di C++ per rappresentare gli errori. Questa classe può essere derivata dalla classe std::runtime_error standard ed è possibile importare il codice di errore lungo restituito da RegGetValue all'interno:

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

Inoltre, possono essere incorporati altre parti di informazioni dell'oggetto eccezione. ad esempio, HKEY e il nome della chiave secondaria. Questa è un'implementazione di base.

È possibile aggiungere un costruttore per creare un'istanza di questa classe di eccezione utilizzando un messaggio di errore e il codice restituito dalla chiamata RegGetValue non riuscita:

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

E il codice di errore può essere esposto ai client utilizzando un accesso di sola lettura (GET):

LONG ErrorCode() const noexcept
{
  return m_errorCode;
}

Ora che è stata creata questa classe di eccezioni, è possibile procedere per eseguire il wrapping dell'API C RegGetValue in un'interfaccia C++ di livello superiore che è più facile da utilizzare e meno soggetto a bug.

Lettura di un valore DWORD nel Registro di sistema

Per iniziare un'operazione semplice: uso dell'API RegGetValue per leggere un valore DWORD del Registro di sistema. In questo caso il modello di utilizzo è abbastanza semplice. Prima di tutto, esaminiamo il tipo di interfaccia può essere definito in C++ per la gestione di questo caso.

Ecco il prototipo 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
);

Come può notare, questa funzione di interfaccia C accetta dati altamente generici, ad esempio un buffer di output void * (pvData) e un parametro di dimensione del buffer di input/output (pcbData). Inoltre, esistono stringhe di tipo C (lpSubKey e lpValue) che identificano la chiave del Registro di sistema e il nome del valore specifico in tale chiave. È possibile convertono questo prototipo di funzione C, rendendo più semplice per i chiamanti di C++.

In primo luogo, prevede di segnalare condizioni di errore la generazione di eccezioni di C++, il valore DWORD di lettura dal Registro di sistema può semplicemente verrà restituito dal wrapper C++ come valore restituito. Automaticamente questo Elimina la necessità di raw void * parametro di output del buffer (pvData) e il parametro delle dimensioni associati (pcbData).

Inoltre, come si utilizza C++, è preferibile rappresentano stringhe Unicode (UTF-16) utilizzando la classe di std:: wstring anziché puntatori non elaborati di tipo C. Pertanto, è possibile definire questa funzione C++ molto più semplice per leggere un valore DWORD del Registro di sistema:

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

Come può notare, non sono presenti parametri PVOID e LPDWORD; le stringhe di input vengono passate tramite const riferimenti agli oggetti std:: wstring e il valore letto dal Registro di sistema viene restituito come un valore DWORD da questa funzione di C++. Questo è un'interfaccia molto semplice e livello superiore.

Ora esaminiamo l'implementazione. Come accennato, il modello di invoke per RegGetValue in questo caso è abbastanza semplice. È necessario dichiarare una variabile DWORD che verrà archiviato il valore di lettura dal Registro di sistema:

DWORD data{};

È necessario quindi un'altra variabile DWORD che rappresenta le dimensioni (in byte) del buffer di output scritti da RegGetValue. Si noti che il buffer di output in questo caso è sufficiente la variabile "dati" precedente, e la dimensione è costantemente le dimensioni di un valore DWORD:

DWORD dataSize = sizeof(data);

Tuttavia, tenere presente che non è possibile contrassegnare dataSize come "const" perché è sia un parametro di input e output per RegGetValue.

È quindi possibile richiamare l'API RegGetValue:

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

Gli oggetti di input wstring vengono convertiti in puntatori di stringa di tipo C non elaborati utilizzando il metodo wstring::c_str. Il flag RRF_RT_REG_DWORD limita il tipo del valore del Registro di sistema DWORD. Se si sta tentando di leggere il valore di registro di sistema è di tipo diverso, la chiamata di funzione RegGetValue in modo sicuro.

Gli ultimi due parametri rappresentano l'indirizzo del buffer di output (in questo caso, l'indirizzo della variabile di dati) e l'indirizzo di una variabile che archivia le dimensioni del buffer di output. Infatti, la restituzione, RegGetValue indica le dimensioni dei dati scritti nel buffer di output. In questo caso di lettura di un valore DWORD semplice, le dimensioni dei dati sono sempre 4 byte, vale a dire sizeof(DWORD). Tuttavia, questo parametro di dimensione è più importante per i valori di dimensioni variabili, ad esempio stringhe; Che illustrerò più avanti in questo articolo.

Dopo la chiamata alla funzione RegGetValue, è possibile controllare il codice restituito e generare un'eccezione in caso di errore:

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

Si noti che il codice di errore (retCode) restituito da RegGetValue è incorporato nell'oggetto di eccezione e può essere recuperato in un secondo momento tramite il codice che elaborerà l'eccezione.

In alternativa, esito positivo, la variabile di dati DWORD solo può essere restituita al chiamante:

return data;

È tutto per l'implementazione della funzione.

Il chiamante può semplicemente richiamare questa funzione wrapper C++ con codice simile al seguente:

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

Si noti come semplice questo codice è rispetto alla chiamata API C RegGetValue originale. È sufficiente passare un handle per un registro di sistema aprire la chiave (in questo esempio, la chiave predefinita HKEY_CURRENT_USER), una stringa contenente la chiave secondaria e il nome del valore. Esito positivo, il valore DWORD viene restituito al chiamante. D'altra parte, in caso di errore, personalizzata di tipo RegistryError viene generata un'eccezione. Questo tipo di codice è di livello superiore e molto più semplice rispetto al richiamo RegGetValue. Infatti, la complessità di RegGetValue è stata nascosta all'interno di questa funzione wrapper C++ RegGetDword personalizzata.

È possibile utilizzare lo stesso modello per leggere un valore QWORD (dati a 64 bit) dal Registro di sistema. In questo caso, è sufficiente sostituire il tipo DWORD per il valore del Registro di sistema con il ULONGLONG a 64 bit.

Lettura di un valore stringa dal Registro di sistema

Lettura di un valore DWORD valore dal Registro di sistema è abbastanza semplice: è sufficiente una sola chiamata all'API Win32 RegGetValue. Principalmente perché è un valore DWORD di dimensioni fisse, ovvero quattro byte, ovvero le dimensioni di un valore DWORD. D'altra parte, la lettura di stringhe dal Registro di sistema introduce un ulteriore livello di complessità poiché le stringhe sono dati di dimensioni variabili. In questo caso, l'idea è per chiamare l'API RegGetValue due volte: Nella prima chiamata, richiesta questa API per restituire le dimensioni desiderate per il buffer di stringa di output. Successivamente, si alloca dinamicamente un buffer di dimensione appropriata. È infine possibile creare una seconda chiamata a RegGetValue per scrivere i dati della stringa nel buffer allocato in precedenza. (Questo modello è stato illustrato in dettaglio nel mio articolo precedente, "Utilizzando STL stringhe in Win32 API limiti," in msdn.com/magazine/mt238407).

In primo luogo, esaminiamo il prototipo della funzione wrapper di alto livello C++:

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

Come nel caso DWORD, molto semplificata per quanto riguarda il prototipo API C RegGetValue complesso originale. Il valore della stringa viene restituito dalla funzione come un'istanza di std:: wstring. Al contrario, in caso di errori, viene generata un'eccezione. Il nome della chiave secondaria e il nome del valore vengono passati come input wstring const parametri di riferimento, nonché.

Tratterò ora il codice di implementazione.

Come ho scritto, l'idea consiste nella prima chiamata l'API RegGetValue per ottenere la dimensione del buffer di output per archiviare il valore di stringa:

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

È possibile visualizzare una sintassi simile al caso precedente valore DWORD. Gli oggetti wstring vengono convertiti in puntatori di stringa di tipo C richiamando il metodo wstring::c_str. In questo caso, il flag RRF_RT_REG_SZ limita il tipo di registro di sistema valido al tipo di stringa (REG_SZ). Esito positivo, l'API RegGetValue scriverà nella variabile dataSize la dimensione del buffer di output desiderato (espressa in byte).

In caso di errore, è necessario generare un'eccezione della classe RegistryError personalizzata:

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

Dopo aver appreso le dimensioni del buffer di output desiderato, è possibile allocare un oggetto wstring della dimensione richiesta per la stringa di output:

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

Si noti che il valore di dataSize restituito da RegGetValue è espresso in byte, ma il metodo wstring::resize prevede una dimensione, espressa in numero wchar_t. Pertanto, è necessario scala da byte a wchar_t, dividendo il valore delle dimensioni del primo byte per sizeof(wchar_t).

Ora che si dispone di una stringa con sufficiente spazio allocato, è possibile passare un puntatore al buffer interno per l'API RegGetValue, che questa volta i dati della stringa effettiva verranno scritti nel buffer specificato:

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

I & dati [0] sono l'indirizzo del buffer interno wstring che verranno scritti dall'API RegGetValue.

Come al solito, è importante verificare il risultato della chiamata API e genererà un'eccezione in caso di errore:

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

Si noti che in caso di riuscita, RegGetValue scrive le dimensioni della stringa di risultato effettivo (in byte) nella variabile dataSize. È necessario ridimensionare l'oggetto wstring in base a questa dimensione. Come dataSize è espresso in byte, è preferibile convertire al conteggio wchar_t corrispondente quando si gestiscono wstrings:

DWORD stringLengthInWchars = dataSize / sizeof(wchar_t);

Inoltre, dataSize contiene il carattere di terminazione null per la stringa di output. Tuttavia, wstring gli oggetti sono già con terminazione null, pertanto è necessario prestare attenzione per evitare una terminazione doppia-NUL fittizio e non corretti per la stringa di lettura. È necessario disattivare il terminatore null scritto da RegGetValue di ritaglio:

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

Si noti che l'API RegGetValue garantisce una stringa con terminazione null esito positivo, anche se la stringa originale memorizzata nel Registro di sistema non è con terminazione null. Si tratta di un comportamento molto più sicuro rispetto alla precedente API RegQueryValueEx, che non garantiscono la terminazione null per le stringhe restituite. Pertanto, il chiamante non dispone di scrivere codice aggiuntivo per eseguire correttamente tale caso conto, aumentando la complessivo di bug e complessità area del codice.

Ora che la variabile di dati contiene il valore di stringa di lettura dal Registro di sistema, è possibile restituire al chiamante in uscita dalla funzione:

return data;

Dopo aver creato questo wrapper C++ RegGetString pratico per l'API C di basso livello RegGetValue, è possibile richiamarla simile al seguente:

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

Come nel caso DWORD, è stato generato il livello di astrazione dell'API Win32 RegGetValue, fornendo una funzione wrapper C++ rigido abuso e facile da utilizzare per leggere un valore stringa dal Registro di sistema. Tutti i dettagli e complessità di gestione con l'API RegGetValue in modo sicuro sono nascosti all'interno del corpo della funzione C++ RegGetString personalizzato.

Lettura di valori multistringa dal Registro di sistema

Un altro tipo di valore del Registro di sistema è la cosiddetta "multi-stringa": In sostanza, si tratta di un set di stringhe che terminano con doppia-NUL compressi in un singolo valore. Questa struttura di dati stringa con terminazione null doppia è costituito da una serie di stringhe con terminazione null di tipo C che occupano posizioni di memoria adiacenti. Fine della sequenza è contrassegnata da un terminatore NUL aggiuntivo, pertanto l'intera struttura viene terminata da due NULs. Per ulteriori informazioni su questa struttura di dati, vedere il post di blog, "Qual è il formato della stringa con terminazione Null doppia con alcuna?" (bit.ly/2jCqg2u).

In questo caso il modello di utilizzo dell'API Win32 RegGetValue è molto simile al caso precedente di un'unica stringa. Vale a dire prima l'API RegGetValue viene richiamato per ottenere la dimensione del buffer di destinazione intero che contiene i dati desiderati (in questo caso, l'intera sequenza di stringhe adiacenti terminato con un valore double-NUL). Quindi, viene allocato dinamicamente un buffer di tale dimensione. E, infine, la funzione RegGetValue viene chiamata per la seconda volta, passa l'indirizzo del buffer allocato in precedenza, pertanto l'API è possibile scrivere i dati effettivi multistringa nel buffer.

In questo caso, è necessario prestare attenzione alla struttura di dati di archiviazione della stringa di terminazione NUL doppia. In effetti, mentre un std:: wstring correttamente possono contenere NULs incorporato, potrebbe essere utilizzato per archiviare una stringa con terminazione null doppia struttura, ma è preferibile aumentare il livello di astrazione e analizzare la stringa di terminazione NUL doppia in un vettore di livello superiore e più comodo < wstring >.

Pertanto, il prototipo della funzione wrapper C++ leggere valori multistringa dal Registro di sistema è simile al seguente:

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

Esito positivo, restituirà la multi-stringa al chiamante come vettore interessante < wstring >. D'altra parte, in caso di errore, nella forma più comune RegistryError verrà generata l'eccezione.

All'interno del corpo della funzione wrapper C++, prima richiamare l'API RegGetValue per ottenere la dimensione del buffer di output desiderato per archiviare la stringa più:

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

Si noti l'utilizzo del flag RRF_RT_REG_MULTI_SZ questa volta per specificare il tipo di valore multistringa del Registro di sistema.

Come al solito, in caso di errore, viene generata un'eccezione, incorporare il codice di errore nell'oggetto RegistryError:

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

Esito positivo, si alloca un buffer di dimensioni appropriate, per archiviare l'intera stringa multipla:

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

Considerare un vettore < wchar_t > molto più semplice rispetto a un wstring per rappresentare il buffer non elaborato a più stringhe. Si noti inoltre che il valore restituito dall'API RegGetValue delle dimensioni è espresso in byte, in modo che deve essere convertito correttamente in un conteggio wchar_t prima di passarlo al metodo Vector:: Resize.

Quindi, è possibile richiamare l'API RegGetValue per la seconda volta, per scrivere i dati effettivi multistringa nel buffer allocato in precedenza:

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

I & argomento di dati [0] punta all'inizio del buffer di output.

Anche in questo caso, è necessario controllare il codice restituito API e segnalare gli errori che genera un'eccezione C++:

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

È inoltre utile ridimensionare correttamente il buffer dei dati con il valore di dataSize restituito come parametro di output dall'API RegGetValue:

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

A questo punto, la variabile di dati, ovvero un vettore < wchar_t >, viene memorizzata la sequenza di stringa con terminazione null doppia. L'ultimo passaggio consiste di analizzare la struttura di dati e di convertirlo in un vettore di livello superiore e più comodo < wstring >:

// 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;
}

Infine, l'oggetto vector < wstring > risultato può essere restituito al chiamante:

return result;

Questo wrapper C++ RegGetMultiString può semplicemente essere richiamato, simile al seguente:

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

Anche in questo caso, tutte le complessità dell'API Win32 RegGetValue è stata nascosta dietro una comoda interfaccia C++ ad alto livello.

Enumerazione dei valori in una chiave del Registro di sistema

Un'altra operazione comune di registro di sistema Windows è l'enumerazione dei valori in una chiave del Registro di sistema specificato. Windows fornisce l'API RegEnumValue (bit.ly/2jB4kaV) per questo scopo. In questo caso, illustrerò come utilizzare questa API per ottenere un elenco dei nomi e i tipi di valori in una chiave del Registro di sistema specificata, il processo di enumerazione il wrapping in una comoda funzione C++ di livello superiore. La funzione di C++ personalizzata può accettare come input un valido HKEY gestire associata alla chiave che si desidera enumerare. Esito positivo, questa funzione wrapper C++ personalizzata restituirà un vettore di coppie: Il primo elemento nella coppia sarà un wstring contenente il nome del valore e il secondo elemento un valore DWORD che rappresenta il tipo di valore. Pertanto, il prototipo della funzione wrapper C++ avrà un aspetto simile al seguente:

std:: Vector < std:: Pair < std:: wstring, DWORD >> RegEnumValues (HKEY hKey)

Ora tratterò i dettagli del processo di enumerazione. Lo scopo consiste nel chiamare innanzitutto il RegQueryInfoKey (bit.ly/2jraw2H) API per ottenere utili informazioni pre-enumerazione, ad esempio il numero totale di valore e la lunghezza massima dei nomi dei valori nella chiave del Registro di sistema specificata, come illustrato nella figura 1.

Figura 1 richiamo RegQueryInfoKey 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
);

Si noti che è stato passato nullptr per le parti di informazioni in cui non sono interessato. Naturalmente, è necessario controllare il valore restituito e generare un'eccezione se si è verificato un errore quando si chiama l'API menzionate in precedenza:

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

In base alla tabella di funzione di Windows Dev Center RegQueryInfoKey (bit.ly/2lctUDt), le dimensioni restituite per la lunghezza massima dei nomi di valore (archiviato nella variabile maxValueNameLen nel codice precedente) non include la terminazione NUL; pertanto, possibile modificare questo valore, aggiungendo uno per tener conto di terminazione null quando si alloca un buffer per leggere i nomi dei valori:

maxValueNameLen++;

Quindi è possibile allocare un buffer di dimensioni appropriate per leggere i nomi dei valori in ogni fase di enumerazione; a tale scopo, è possibile utilizzare un sovraccarico ridotto efficiente. std:: unique_ptr < wchar_t [] >:

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

Il risultato dell'enumerazione, sotto forma di coppie di nome valore e tipo di valore, può essere memorizzato in un std:: vector:

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

Si aggiungeranno progressivamente il contenuto di questo vettore durante il processo di enumerazione e "valori" verranno restituiti al chiamante quando l'enumerazione è stata completata.

È quindi possibile utilizzare un ciclo for, la chiamata dell'API RegEnumValue ripetutamente e l'enumerazione di un nuovo valore a ogni passaggio dell'iterazione:

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

Si noti che hai valueCount dalla chiamata RegQueryInfoKey pre-enumerazione iniziale.

All'interno del corpo del ciclo for, l'API RegEnumValue può essere chiamato per ottenere le informazioni desiderate per il valore corrente. In questo contesto, si è interessati nel nome del valore e tipo di valore. Nome del valore verrà letti nel nameBuffer precedentemente allocato; il tipo di valore verrà archiviato in un semplice DWORD. In tal caso, all'interno del corpo del ciclo for, è possibile scrivere codice simile al seguente:

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

Come al solito, è buona norma controllare il valore restituito di API e generare un'eccezione in caso di errore:

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

Esito positivo, l'API RegEnumValue scriverà nome del valore nameBuffer specificato e il tipo del valore nella variabile di valueType. Pertanto, è possibile creare una coppia < wstring, DWORD > con questi due tipi di informazioni e aggiungere questa coppia di informazioni per il vettore di risultato dell'enumerazione:

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

Dopo il ciclo for, il vettore di "valori" risultato può essere restituito al chiamante:

return values;

Il chiamante può quindi enumerare tutti i valori in una chiave del Registro di sistema solo chiamando la funzione wrapper C++ simile al seguente:

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

Un modello di codifica simile può essere utilizzato per enumerare le sottochiavi di una chiave del Registro di sistema specificato. In questo caso, il RegEnumKeyEx Win32 (bit.ly/2k3VEX8) API deve essere utilizzata invece RegEnumValue illustrato in precedenza. Il codice di questo tipo di funzione enumerazione la sottochiave viene fornito nel download associato all'articolo.

Gestione delle risorse Safe per gli handle non elaborato HKEY

Le chiavi del Registro di sistema rappresentate dal tipo di handle Win32 HKEY non elaborato possono essere incluso in modo sicuro e semplice in una classe di gestione risorse di C++. Il distruttore della classe verrà correttamente chiamata l'API RegCloseKey dell'handle non elaborato incluso chiudere automaticamente l'handle. Inoltre, spostare operazioni semantica come un costruttore di spostamento e un operatore di assegnazione di spostamento possono essere definite per trasferire la proprietà dell'handle sottoposta a wrapping in modo efficiente tra istanze diverse della classe di gestione risorse di C++. Per migliorare l'efficienza, tutti i metodi della classe che non generano eccezioni sono contrassegnati come noexcept, tramite il compilatore C++ di generare più codice ottimizzato. Questa classe di manager C++ pratico risorse chiave denominata chiave di registro, viene implementata nel file Registry.hpp che accompagna questo articolo. In questo file di sola intestazione riutilizzabile, sono inoltre disponibili le implementazioni di una coppia di funzioni di supporto: RegOpenKey e RegCreateKey, che incapsulano rispettivamente il RegOpenKeyEx API Win32 e RegCreateKeyEx, restituendo un HKEY gestiscono in modo sicuro racchiuso la classe di gestione risorse C++ citato in precedenza. In caso di errori, le funzioni C++ generano un'eccezione RegistryError, il codice di errore restituito per le API di Win32 non elaborati di interfaccia C esegue il wrapping.

Conclusioni

L'API Win32 RegGetValue fornisce un'interfaccia relativamente alto livello per la lettura dei valori dal Registro di sistema rispetto alle API di livello inferiore, ad esempio RegQueryValueEx. RegGetValue offre inoltre un'interfaccia più sicura che, ad esempio, garantisce che le stringhe restituite sono NUL terminata correttamente. Ciononostante, RegGetValue è ancora un'API di basso livello di interfaccia C che richiede al programmatore di fare attenzione a più di programmazione su di esso e dettagli può causare il codice è soggetto a bug complesso. In questo articolo è stato illustrato come è possibile compilare un'interfaccia C++ moderna pratico, semplice da utilizzare e disco rigido abuso per nascondere la complessità dell'API RegGetValue, semplificando l'accesso al Registro di sistema Windows. Inoltre, l'API RegEnumValue è stato eseguito il wrapping in una comoda funzione di C++ di livello superiore per enumerare tutti i valori in una chiave del Registro di sistema specificato. Il codice sorgente che contiene l'implementazione di funzioni e le classi descritte in questo articolo è reperibile in riutilizzabile sola intestazione forma (nel file Registry.hpp) nel download dell'articolo.


Giovanni Dicanioè un programmatore di computer specializzato in C++ e il sistema operativo Windows, un autore di Pluralsight (bit.ly/GioDPS) e un MVP di Visual C++. Oltre alla programmazione e la creazione di corsi, ama aiutare altri utenti nella community dedicate a C++ e forum. È possibile contattarlo tramite posta elettronica all'indirizzo giovanni.dicanio@gmail.com. Il suo blog è anche in msmvps.com/gdicanio.

Grazie a seguenti esperti tecnici per la revisione dell'articolo: David Cravey e Marc Gregoire
David Cravey è un architetto a GlobalSCAPE, lead diversi gruppi di utenti di C++ ed è stato un MVP di Visual C++ quattro volte.

Marc Gregoire è un software engineer senior dal Belgio, fondatore di belga gruppo di utenti di C++, autore di "Professional C++" (Wiley), coautore di "C++ Standard Library Quick Reference" (Apress), redattore tecnico su numerosi libri e, poiché 2007, ha ricevuto il riconoscimento MVP annuo per la sua esperienza VC + +. È possibile contattare Marc marc.gregoire@nuonsoft.com.