Desenvolvimento - Visual Basic

Você Pode Usar Alguns Ponteiros

Realize o impossível aprendendo a controlar com segurança os ponteiros, variáveis de ponteiro de forma segura.

por David Byres



A documentação do Application Programming Interface (API) do Windows contém ampla referência aos ponteiros e operadores de direcionamento, os quais são usados freqüentemente pelos programadores de C++.

Entretanto, eles são um mistério para muitos desenvolvedores de Visual Basic. O mistério é devido, em grande parte, à estrutura do VB que não permite o tipo de acesso de memória direta que os ponteiros oferecem. Ao contrário, o VB empacota a manipulação do ponteiro na construção de uma linguagem nativa. Conseqüentemente, os programadores VB , sem saber, executam as operações de ponteiro o tempo todo. Quando você declara um objeto, string, ou variáveis de vetores, ou passa um argumento para uma função ou subprocedimento como um parâmetro By Reference (ByRef), você realmente está trabalhando com ponteiros e variáveis de ponteiro.

Explicarei o conceito e as funções de ponteiros e ilustrá-los no contexto VB, onde concluirei mostrando como desenvolver uma especialização da função VarType do VB, que lhe permite determinar exatamente que tipo de vetor é armazenado numa variante. O discernimento entre ponteiros, variáveis de ponteiro e direcionamento faz com que você aproveite a capacidade do Win32 API com segurança, e faça coisas nunca antes realizadas usando só o VB.

Um ponteiro é um termo para um endereço de memória, assim qualquer discussão de ponteiros é, na verdade, sobre as técnicas de acesso direto à memória. Como o VB o protege do acesso de memória direta, as razões de se usar os ponteiros são mais bem compreendidas no contexto de linguagens como C/C++, que oferecem as facilidades nativas à linguagem por trabalharem com ponteiros.

Em geral, os programadores C/C++ usam ponteiros com três objetivos, todos relacionados à melhoria no desempenho dos aplicativos e utilização mais eficiente de memória. Primeiro, os ponteiros são uma alternativa aos mecanismos de linguagem embutidos para manipulação de vetores ou outra estrutura de armazenamento de dados, como listas vinculadas. Segundo, os ponteiros em C/C++ são usados como um mecanismo para passar parâmetros para funções por referência; essa utilização é completamente equivalente ao parâmetro de especificação padrão ByRef, no VB. Terceiro, os programadores C/C++ freqüentemente usam ponteiros para distribuição de memória dinâmica - criando variáveis em run time de um armazenamento de memória não utilizada chamado heap.

Embora falte ao VB algumas facilidades nativas da linguagem para administrar a memória diretamente, você pode desenvolver estas mesmas técnicas em VB usando ponteiros e técnicas de acesso de memória apresentadas neste artigo. Na verdade, para se trabalhar com ponteiros, é necessária uma variável para armazenar endereços de memória. Uma variável que contém um endereço de memória é chamada de variável de ponteiro. Os atuais sistemas operacionais Windows - inclusive Windows 9x, Windows NT e Windows 2000 - possuem sistemas operacionais de 32 bits, o que significa que seus endereços de memória possuem extensão de 32 bits. Um tipo Long Integer VB também possui uma extensão de 32 bits, pode funcionar bem como uma variável de ponteiro.

Para armazenar um endereço de memória ou ponteiro em uma variável de ponteiro, você precisa de algum método por obter endereços de memória de outras variáveis. O VB oferece este mecanismo através de três funções de linguagem não documentada: VarPtr (), StrPTR () e ObjPtr (). Como você poderia esperar, VarPtr () retorna um inteiro longo que contém o ponteiro da variável dada como um argumento. Semelhantemente, StrPtr () retorna o ponteiro de uma variável de string e ObjPtr () retorna o endereço de uma variável de objeto. (Por que as variáveis de string e de objeto exigem funções separadas, é um tópico interessante por si só - para mais informação, consulte o material da seção Fontes de Referência.)

Armazenar um endereço de memória em uma variável de ponteiro é útil para operações de memória direta. Além disso, ganhar acesso ao valor subjacente armazenado no endereço de memória contido na variável de ponteiro permite acesso a muitos dados de estrutura subjacente do VB, como demonstrarei mais tarde. Esta operação é denominada direcionamento (indirection) . Ao contrário do C++, o VB não possui nenhum operador de direcionamento, mas você pode realizar a operação usando a função CopyMemory do Win32 API. Eis a declaração completa para esta função extraída da documentação da MSDN:

Listagem 1: Função CopyMemory

Public|Private Declare Sub CopyMemory _
	Lib "kernel32" Alias _
	"RtlMoveMemory" (Destination As _
	Any, ByVal Source as Any, ByVal _
	Length as Long)

Como sugere o nome, a função CopyMemory transfere os conteúdos de um bloco de bytes Length de memória do endereço de memória especificado no parâmetro Source para o endereço de memória da variável fornecido como o parâmetro Destination.

Transferindo os conteúdos de uma variável no endereço de memória Source para o endereço Destination de uma variável de destino, e examinando o valor da variável de destino, você pode obter o valor subjacente da variável para onde a variável de ponteiro aponta.

Eu mencionei que passar os parâmetros ByRef envolve operações de ponteiro. Observe que o parâmetro Destination da função CopyMemory não é declarado nem ByRef nem ByVal. Em VB, o mecanismo padrão para passar parâmetros é ByRef. Quando você passar uma variável como um argumento para uma função ou subprocedimento ByRef, você está passando o endereço de memória da variável, não o valor subjacente da própria variável. Como resultado, você tem de fornecer uma variável pelo nome, como o parâmetro Destination da função CopyMemory.

O mecanismo ByRef obtém o ponteiro da variável automaticamente e passa-o para a função. Por outro lado, o parâmetro Source como um parâmetro ByVal, espera receber uma variável de ponteiro diretamente - quer dizer, uma variável contendo um endereço de memória.

Adquira o Valor Subjacente

Uma variável de destino e uma variável de ponteiro têm uma relação específica com a memória (veja Figura 1). Por exemplo, na figura, o valor armazenado na variável de ponteiro varPointer é o endereço de memória da variável de destino intVar. Dado a posição de memória, ou ponteiro, da intVar, você usa o direcionamento pela função CopyMemory para chegar ao valor subjacente armazenado em intVar - neste caso, 5. Os endereços de memória são freqüentemente representados em base 16, ou formato hexadecimal, principalmente por ficar mais legível. Em VB, um número hexadecimal é precedido pelos caracteres “&H”. Você pode facilmente converter os números decimais ao seu equivalente hexadecimal, usando a função Hex.

A variável de ponteiro varPointer armazena o endereço de memória da variável de destino, intVar. Em VB, você utiliza o tipo Long Integer como uma variável de ponteiro.

Esquema de funcionamento da variável na memória

Figura 1: Esquema de funcionamento da variável na memória

Eu farei uma demonstração rápida dos conceitos de ponteiro, variável de ponteiro e direcionamento. Para começar, crie um novo projeto EXE padrão em VB. Na seção General_Declarations de um módulo formulário, crie uma função de declaração API:

Listagem 2: Função de declaração da API

Private Declare Sub CopyMemory Lib "kernel32" Alias _
	"RtlMoveMemory" (Destination As _
	Any, ByVal Source As Any, ByVal Length As Long)

Na seção de código do módulo formulário, declare uma variável de inteiros, intVar, e atribua um valor de 5:

Listagem 3: Declaração da variável intVar

Dim intVar as Integer
intVar = 5

Depois, declare uma variável de ponteiro. Use um tipo Long Integer para servir como sua variável de ponteiro. Declare uma variável de destino de Type Integer para armazenar o resultado da operação de direcionamento, usando a função CopyMemory API:

Listagem 4: Declaração de ponteiro

Dim varPointer as Long
Dim intTarget as Integer

Use a função varPtr para recuperar o endereço de memória, ou ponteiro, onde a variável intVar está posicionada, e atribua este valor a sua variável de ponteiro, varPointer:

Listagem 5: Obtenção do endereço na memória da variável intVar

varPointer = varPtr(intVar)

Execute o direcionamento utilizando a função CopyMemory para chegar ao valor subjacente da variável posicionada na varPointer:

Listagem 6: Obtenção do valor subjacente da variável

CopyMemory intTarget, varPointer, 4
Debug.Print Hex$(varPointer); intTarget

A linha Debug.Print transfere o valor hexadecimal de varPointer e o resultado da operação de direcionamento para a janela Immediate. Eis um resultado típico: 12F582 5

&H12F582 é o endereço de memória, ou ponteiro, onde a variável intVar é armazenada. O resultado da operação de direcionamento, o valor 5 retornado, é o valor subjacente da variável posicionada neste endereço de memória.

Agora mãos à obra num aplicativo mais complexo: estenda a função VB VarType de maneira a não fazer só com o VB. A VarType determina o subtipo de uma variável armazenado em uma variante, um tipo de variável especial VB que pode manter os dados contidos em qualquer outro tipo de variável. A função retorna um valor para indicar o subtipo da variável armazenado em uma variante (consulte a Tabela 1). Embora a função VarType possa lhe dizer que um vetor está armazenado em uma variante, não pode indicar o número de dimensões do vetor. Saber que tipo de vetor está armazenado em uma variante torna este tipo de variável mais flexível, como um mecanismo para passar dados entre as partes de um aplicativo.

Tabela 1: A Função VarType Retorna Valores. Quando um vetor n-dimensional está armazenado em uma variante, a função VarType retorna a soma do valor vbArray e a constante, representando o tipo de vetor subjacente. Você pode obter outras informações úteis através do acesso direto à estrutura de memória que descreve o vetor.

Constante Valor Descrição
vbEmpty 0 Vazio
vbNull 1 Nulo
vbInteger 2 Inteiro
vbLong 3 Inteiro Longo
vbSingle 4 Precisão Simples
vbDouble 5 Precisão Dupla
vbCurrency 6 Moeda
vbDate 7 Data
vbString 8 String
vbObject 9 Objeto
vbError 10 Valor de Erro
vbBoolean 11 Valor Booleano
vbVariant 12 Valor de Variante
vbDecimal 14 Valor Decimal
vbByte 17 Valor de Byte
vbArray 8192 Vetor

Considere este exemplo: um módulo de classe implementa uma função que aceita qualquer variante como um parâmetro e retorna o número de dimensões do vetor armazenado como um subtipo variante (veja Listagem 1). Se a variante passou para a função ArrayDims na Listagem 1, que não contém um vetor, a função retorna o valor -1. A função utiliza a manipulação de ponteiro e direcionamento a fim de obter um ponteiro para uma estrutura em memória, que descreve completamente o vetor contido na variável variante passada na função ArrayDims como um parâmetro.

A função ArrayDims utiliza a manipulação de ponteiros e as técnicas de acesso de memória para ler as informações do descritor de vetor diretamente de qualquer tipo de vetor contido numa variável de variante, estendendo a função intrínseca VarType do VB. Você não consegue utilizar essa funcionalidade só com o VB.

Listagem 7: Função ArrayDims

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" _
	Alias "RtlMoveMemory" (Destination As Any, _
	ByVal Source As Any, ByVal Length As Long)

Public Function ArrayDims(vntArray As Variant) As _ 
	Variant
	"certifique-se de que vntArray contém um vetor
	If VarType(vntArray) < 8192 Then
		ArrayDims = -1
		Exit Function
	End If

	"carregue o conteúdo da variante para o
  "de 16 bytes vetor
	Dim bytVntByte(1 To 16) As Byte
	CopyMemory bytVntByte(1), VarPtr(vntArray), 16

	Dim intIndx As Integer
	Dim strHexAddr As String
	Dim strByte As String

	"extraia o ponteiro nos bytes 9-12 e inverta a ordem
	For intIndx = 12 To 9 Step -1
		strByte = Hex$(bytVntByte(intIndx))
		If Len(strByte) = 1 Then strByte = "0" & strByte
		strHexAddr = strHexAddr & strByte
	Next intIndx

"converta o endereço Hex para decimal e posicione em Long 
	Dim strHexVals As String
	strHexVals = "0123456789ABCDEF"
	Dim lngVntPtr As Long

	For intIndx = 1 To 8
		lngVntPtr = lngVntPtr + CLng((InStr(1, _
			strHexVals, Mid$(strHexAddr, intIndx, _
			1)) - 1) * (16 ^ (8 - intIndx)))
	Next intIndx

	"retorne a posição do vetor – armazenado nos 2 
	"primeiros bytes da estrutura de cabeçalho do vetor
	CopyMemory bytVntByte(1), lngVntPtr, 16
	ArrayDims = bytVntByte(1) + bytVntByte(2)

End Function

A função usa primeiro o direcionamento para carregar os conteúdos da variante em um vetor de 16 elementos de byte, para que você possa ler os bytes individuais que compõem a variante em memória. A chave para extrair o ponteiro da variante para a estrutura de cabeçalho de vetor é perceber que o ponteiro é armazenado nos bytes de 9 a 12 da variante, em ordem inversa. Uma vez obtido o ponteiro de vetor, use novamente o direcionamento para ler os dois primeiros bytes da estrutura de cabeçalho de vetor armazenados em memória no ponteiro já obtido. Os dois primeiros bytes da estrutura de cabeçalho de vetor indicam o número de dimensões do vetor para onde está apontado. Este valor é então retornado da função.

Sobre o Autor

David Byres é engenheiro de software na Alitum Inc., em San Diego, Califórnia.

Download do código referente a este artigo

Clique aqui para fazer o download.
David Byres

David Byres