Utilizando o Registry (Parte 2)
23 de June de 2008 - Fernando RobertoPois é, como eu estava dizendo na primeira parte deste post. Isso é muito código. Todo esse negócio parece trabalhoso, não? Montar uma UNICODE_STRING para o caminho da chave que se deseja acessar, montar um OBJECT_ATTRIBUTES com ela, abrir handle, determinar o tamanho dos valores a serem lidos, alocar um buffer grande suficiente, ler o valor que se deseja, desalocar qualquer buffer temporário utilizado no processo, e por fim, fechar o handle para a chave do registro. Você pode conferir quanto código utilizamos em nosso exemplo do post anterior para se ler dois valores no registro. Hoje veremos um grupo de rotinas de manipulação de registro que facilita esse processo.
Rotinas RTL de Registro
São apenas cinco as rotinas que compõem esse grupinho. Na referência você encontra uma descrição de cada uma delas. Esse grupo de rotinas tem como característica comum o fato de não termos que utilizar handles para as chaves. Os handles são abertos e fechados para cada operação. Outra característica comum é que você não precisa compor o nome completo da chave na qual estão os valores que você quer ler ou escrever. Essas rotinas trabalham com um esquema de nome de chave relativo a algum lugar pré-definido. Confuso? Vamos dar uma olhada numas das rotinas para que tenhamos um exemplo mais prático. Tentando seguir a mesma idéia do exemplo dado no post anterior, precisamos inicialmente verificar se a chave, onde os valores estão armazenados, existe de fato. Tem uma funçãozinha justamente para isso.
NTSTATUS
RtlCheckRegistryKey(
IN ULONG RelativeTo,
IN PWSTR Path
);
Notem que o primeiro parâmetro dessa rotina é uma constante que indica qual será a referência a ser aplicada ao segundo parâmetro. Observe a tabela abaixo que foi retirada da referência.
Um exemplo seria o seguinte: Se usarmos RTL_REGISTRY_ABSOLUTE como primeiro parâmetro, o segundo deve ser o caminho completo da chave de registro. Afinal, temos que passar o caminho absoluto da chave. Por outro lado, se utilizarmos RTL_REGISTRY_SERVICES, o segundo parâmetro deveria ser apenas o complemento do caminho referente a chave Services. Foi esta a opção que utilizei no exemplo deste post. Observe como ficou nossa rotina de criação dos parâmetros no registro.
/****
*** CreateParams
**
** Esta rotina é chamada quando não for detectada a chave
** que contém os parâmetros. Ela cria a chave e os parâmetros
** com valores pré-definidos.
*/
NTSTATUS
CreateParams(VOID)
{
NTSTATUS nts = STATUS_SUCCESS;
WCHAR wzParam[] = L"DriverEntry.com.br";
ULONG ulParam = 0x12345678;
__try
{
//-f--> Cria a chave onde os parâmetros serão armazenados.
// Simples assim.
nts = RtlCreateRegistryKey(RTL_REGISTRY_SERVICES,
L"KernelReg2\\Parameters");
if (!NT_SUCCESS(nts))
ExRaiseStatus(nts);
//-f--> Aqui escrevemos o valor numérico
nts = RtlWriteRegistryValue(RTL_REGISTRY_SERVICES,
L"KernelReg2\\Parameters",
L"DoubleWord",
REG_DWORD,
&ulParam,
sizeof(ulParam));
if (!NT_SUCCESS(nts))
ExRaiseStatus(nts);
//-f--> E aqui a String. Notem que não foi necessário
// criar um UNICODE_STRING para isso.
nts = RtlWriteRegistryValue(RTL_REGISTRY_SERVICES,
L"KernelReg2\\Parameters",
L"String",
REG_SZ,
wzParam,
sizeof(wzParam));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//-f--> Ops!
nts = GetExceptionCode();
ASSERT(FALSE);
}
return nts;
}
Mais uma vêz, o código demonstrado aqui está disponível para download ao final deste post.
O fácil que complica
Pelo que pudemos ver até aqui, esse grupo de rotinas já facilitou um pouco nossa vida. Mas é na rotina de leitura que podemos ver a economia de código acontecer de verdade. O custo disso fica por conta de entender como a rotina de leitura funciona.
NTSTATUS
RtlQueryRegistryValues(
IN ULONG RelativeTo,
IN PCWSTR Path,
IN PRTL_QUERY_REGISTRY_TABLE QueryTable,
IN PVOID Context,
IN PVOID Environment OPTIONAL
);
`
Olhando assim nem parece que ela morde, mas se você der uma olhada na referência, verá que ela pode ser bem flexivel. Flexibilidade as vezes é traduzido por “Difícil de acertar a maneira certa de utilizar”. Os dois primeiros parâmetros são os já conhecidos de antes. A coisa começa a mudar com o terceiro parâmetro, que é uma tabela que contém os detalhes dos valores a serem lidos do registro. Não vou entrar nos detalhes de cada parâmetro desta rotina neste post. Vou apenas fazer o código equivalente ao exemplo do post anterior.
/****
*** LoadParams
**
** Esta rotina carrega as variaveis globais com os
** valores recuperados do registro.
*/
NTSTATUS
LoadParams(VOID)
{
NTSTATUS nts = STATUS_SUCCESS;
RTL_QUERY_REGISTRY_TABLE QueryTable[] =
{
{ NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED,
L"DoubleWord", &g_ulParam, REG_NONE, NULL, 0 },
{ NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED,
L"String", &g_usParam, REG_NONE, NULL, 0 },
{ NULL, 0, NULL, NULL, REG_NONE, 0, NULL }
};
__try
{
//-f--> Caso esta rotina seja chamada mais de uma vez, vamos liberar
// o buffer deste parâmetro.
if (g_usParam.Buffer)
RtlFreeUnicodeString(&g_usParam);
//-f--> Aqui todos os valores da tabela são lidos
nts = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
L"KernelReg2\\Parameters",
QueryTable,
NULL,
NULL);
if (!NT_SUCCESS(nts))
ExRaiseStatus(nts);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//-f--> Ops!
nts = GetExceptionCode();
ASSERT(FALSE);
}
return nts;
}
Não, não está faltando nada. É só isso mesmo. Vamos olhar mais de perto o que fizemos aqui. A tabela que preenchemos tem os dados das leituras a serem realizadas e é definida como mostra abaixo.
typedef struct _RTL_QUERY_REGISTRY_TABLE {
PRTL_QUERY_REGISTRY_ROUTINE QueryRoutine;
ULONG Flags;
PWSTR Name;
PVOID EntryContext;
ULONG DefaultType;
PVOID DefaultData;
ULONG DefaultLength;
} RTL_QUERY_REGISTRY_TABLE, *PRTL_QUERY_REGISTRY_TABLE;
Olhando cada membro dela temos:
- QueryRoutine: Aqui você tem um ponteiro de função que será chamado quando o este ítem da tabela for lido no registro. Não usamos este método em nosso exemplo. Existem algumas coisas interessantes sobre essa rotina de callback. Ela é chamada para cada ítem desta tabela, mas se o valor a ser lido for do tipo REG_MULTI_SZ, a rotina de callback é chamada uma vez para cada string contida neste valor. Consulte a referência para uma completa descrição destes detalhes.
- Flags: O flag que utilizamos aqui foi o RTL_REGISTRY_DIRECT que sinaliza que não usaremos o método do callback. Outro flag é o RTL_QUERY_REGISTRY_REQUIRED que indica que o valor descrito nesta linha da tabela é obrigatório. Caso o valor não seja encontrado, RtlQueryRegistryValues retorna erro e os demais valores não serão lidos.
- Name: Aqui vem o nome do valor a ser lido. Note que não é um UNICODE_STRING, mas um array de WCHAR terminado em zero.
- EntryContext: Em nosso exemplo, este parâmetro é o buffer onde o valor lido será armazenado, mas nos casos onde recebemos a chama pela a rotina de callback, este valor é passado como um dos parâmetros.
Os parâmetros DefaultType, DefaultData e DefaultLengh seriam utilizados caso o valor não existisse e não fosse um valor obrigatório.
Cada valor a ser lido é representado por uma linha nesta tabela. A rotina identifica o fim da tabela quando encontrar os membros QueryRotuine e Name forem NULL.
Calma aí Fernando! Muito bem, nada nessa manga, nada nessa e os valores são lidos? Espertinho você hein? Pensou que ia enganar todo mundo com essa conversinha? Muito bem! Onde é que eu indico o tamanho do buffer oferecido para a leitura?
Muito boa a sua pergunta! Acho até que eu mesmo não teria feito uma pergunta tão boa. O tamanho o buffer é indicado pelo próprio buffer. No caso da string, passamos um ponteiro para um UNICODE_STRING, o tamanho do buffer da string já é descrito por essa estrutura, mas se o buffer desta estrutura for NULL, a rotina aloca o buffer do tamanho necessário. Temos que desalocar esse buffer quando não for mais utilizá-lo. Para leituras onde o buffer resultante é menor ou igual ao sizeof(ULONG) então o valor resultante é armazenado diretamente no lugar apontado por EntryContext. Para demais detalhes consulte a referência.
Esta função é bem flexível e não vou conseguir colocar tudo que essa rotina oferece em um único post, mas sintam-se a vontade para mandar suas dúvidas em relação a ela.
Have fun!