|
Usuários |
|
156 Usuários Online
|
|
[Artigos]
DLL, string e PChar |
Publicado por cdelphi : Sexta, Janeiro 09, 2004 - 02:36 GMT-3 (34178 leituras)
1 Comentário Enviar para um amigo Versão para impressão
|
Você saberia se virar sem a unidade Sharemem ao criar suas DLLs?
Este artigo descreve um pouco do funcionamento da unidade Sharemem e do gerenciador de memória do Delphi e apresenta duas formas criar DLLs sem ela. Funcionamento de Sharemem e BORLANDMM.DLL
Este "capítulo" descreve um pouco do funcionamento do sistema de alocação de strings e de gerenciamento de memória com e sem a unidade Sharemem. Se não estiver interessado nisso, pode ignorá-lo e ir direto ao próximo.
O problema em usar strings e outras variáveis com processamento especial de alocação e desalocação com DLLs é o gerenciamento de memória.
Primeiro, um pouco sobre long strings.
Como você deve saber, uma string longa não é muito mais que um ponteiro para um local na memória. Este local tem algumas informações importantes a respeito da string, além, é claro, dos caracteres armazenados no momento.
Além dos caracteres, há um inteiro de 32 bits indicando o comprimento da string. Ao contrário de PChar, o Delphi pode ler o tamanho da string sem precisar percorrê-la e procurar pelo caractere #0. Além disso, uma string longa pode conter caracteres #0 válidos, ou seja, que não indiquem o final da string, mas algo que você possa interpretar como quiser.
Por fim, existe o campo reference count, outro inteiro de 32 bits indicando o número de variáveis que estão apontando no momento para aquele local na memória. Isso permite ao Delphi fazer algumas otimizações na atribuição de variáveis string.
Quando você atribui um valor a uma variável string, o Delphi normalmente aloca o espaço em memória necessário ao armazenamento dos caracteres, preenche os campos que indicam o comprimento e reference count e copia os caracteres para esse local na memória.
Quando a execução sair do escopo da variável (por exemplo sair da função onde existe uma variável local do tipo string), o Delphi trata de zerar as variáveis string e verificar se precisa desalocar o espaço onde os caracteres se encontram. Se precisar, utiliza a função de desalocação do gerenciador de memória.
O gerenciador de memória é uma biblioteca (localizada na unidade System) que trata de alocação, desalocação e realocação de memória no Delphi. Veja GetMem, FreeMem e ReallocMem para saber mais sobre essas operações.
O problema é que um executável host e as DLLs ligadas à ele têm seus próprios gerenciadores de memória. Uma porção de memória alocada em um deles não pode ser liberado em outro. Veja o diagrama abaixo.
Apesar dos gerenciadores serem idênticos (ambos implementados na unidade System), a "instância" é diferente. Todas as variáveis e áreas de controle, etc, estão localizadas em locais diferentes.
Quando a unidade Sharemem é incluída em um executável e suas DLLs, ela trata de substituir o gerenciador de memória padrão para um localizado em BORLANDMM.DLL. O Delphi permite essa alteração do gerenciador de memória através da procedure SetMemoryManager.
Assim, qualquer porção de memória (incluindo strings) alocada em um pode ser desalocada em outro.
Desta forma, o gerenciador padrão para o executável e suas DLLs são ignorados, dando lugar a um gerenciador compartilhado. Ele é compartilhado também por qualquer outro aplicativo que utilizar Sharemem.
Método 1: Procedure de liberação de PChar
Esse método se baseia na implementação, na DLL, de uma procedure de liberação de PChars retornados por ela. É uma procedure bastante simples, parecida com StrDispose do Delphi:
procedure DllStrDispose(Str: PChar);
Vantagens
Simplifica o processo de preparação para as chamadas. Não é necessário pré-alocar um PChar e nem chamar a função antes para retornar o tamanho necessário. Por só precisar chamar a função uma vez, evita processamento repetido.
Esse método pode ser usado para a criação de DLLs para uso com qualquer linguagem (Delphi, C/C++, VB, etc).
Desvantagens
Sempre que a DLL retornar uma PChar, o programa deve se encarregar de liberá-la ao final do uso. Isso não chega a ser uma desvantagem pois isso é uma consequência do uso de PChar.
Não é prático se você utiliza várias DLLs. É interessante para aplicativos com só uma DLL.
Como mencionado, cada DLL deve exportar sua função de desalocação de PChar, o que pode tornar o programa confuso se você precisar usar várias. Cada uma teria que ter um nome diferente e isso poderia criar algumas confusões como tentar desalocar uma PChar criada por uma DLL com a procedure de liberação de outra. Isso, claro, iria gerar uma exceção.
Vamos ao exemplo de uma DLL criada com este método:
library StrDLL2;
uses
SysUtils,
Classes;
{ Aloca um PChar e copia os caracteres da string AString para ele }
function DllStrGet(AString: string): PChar;
var
Size: Integer;
begin
Size := Length(AString) + 1;
Result := StrAlloc(Size); { Aloca usando o gerenciador de memória da DLL }
Move(Pointer(AString)^, Result^, Size);
end;
{ Função de liberação das PChars retornadas pela DLL }
procedure DllStrFree(Str: PChar); stdcall;
begin
StrDispose(Str); { Libera usando o gerenciador de memória da DLL }
end;
{ Função de demonstração }
function GetAutoexec: PChar; stdcall;
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.LoadFromFile('c:\autoexec.bat');
Result := DllStrGet(SL.Text);
finally
SL.Free;
end;
end;
exports
DllStrFree, GetAutoexec;
begin
end.
A função de alocação e cópia da string (DllStrGet) é usada só internamente, sempre que você for retornar uma string. Se quiser, você pode tranquilamente alocar a PChar usando diretamente StrAlloc ou outras funções do Delphi.
Para usar as funções da DLL em seu aplicativo, você deve primeiro declarar as funções:
function GetAutoexec: PChar; stdcall;; stdcall; external 'StrDLL2.dll';
procedure DllStrFree(Str: PChar); stdcall; external 'StrDLL2.dll';
Para chamar as funções, faça como abaixo:
procedure ShowAutoexec;
var
P: PChar;
begin
P := GetAutoexec;
try
ShowMessage(P);
finally
DllStrFree(P);
end;
end;
O bloco try..finally foi usado para garantir a desalocação da variável. Se ocorresse exceção entre a chamada de GetAutoexec e DllStrFree, esta não seria chamada, fazendo com que o PChar retornado não fosse desalocado.
Método 2: Interface IString
Este segundo método é muito mais interessante que o primeiro. Ele tem todas as vantagens dele, além de:
Nenhuma função de desalocação precisa ser chamada. A desalocação é feita automaticamente pelo Delphi, mesmo utilizando PChar.
Pode ser usada em várias DLLs sem causar confusão.
A única desvantagem é com relação à performance. O processo de transferência de strings é um pouco mais lento do que com strings longas.
A solução baseia-se nas facilidades que o Delphi apresenta com relação a interfaces COM. Iremos utilizar duas unidades novas. Uma delas será usada tanto no aplicativo host quanto nas DLLs e a outra, só nas DLLs.
A primeira é a declaração de uma interface que chamaremos de IString. Em sua declaração, existe só um método, o Str, que deve retornar um PChar:
unit StrInt;
interface
type
IString = interface(IUnknown)
['{6ADF6275-82C0-4290-8C8D-DFDAA2AA17CC}']
function Str: PChar;
end;
implementation
end.
A segunda é a impementação de um objeto (TString) que implementa uma interface IString:
unit StrIntImp;
interface
uses SysUtils, StrInt;
type
TString = class(TInterfacedObject, IString)
private
FStr: PChar;
{ IString }
function Str: PChar;
public
constructor Create(AString: string); overload;
destructor Destroy; override;
end;
implementation
{ TString }
constructor TString.Create(AString: string);
var
Size: Integer;
begin
inherited Create;
Size := Length(AString) + 1;
FStr := StrAlloc(Size);
Move(Pointer(AString)^, FStr^, Size);
end;
destructor TString.Destroy;
begin
StrDispose(FStr);
inherited;
end;
function TString.Str: PChar;
begin
Result := FStr;
end;
end.
Ao implementar a DLL, sempre que quiser retornar uma string, você deve fazê-lo através de IString, e não mais PChar. Exemplo:
library strdll;
uses
SysUtils,
Classes,
StrIntImp,
StrInt;
{$R *.RES}
function GetAutoexec: IString; stdcall;
var
S: TStringList;
begin
S := TStringList.Create;
try
S.LoadFromFile('c:\autoexec.bat');
Result := TString.Create(S.Text);
finally
S.Free;
end;
end;
exports
GetAutoexec;
begin
end.
Perceba, então, que onde se retornaria um PChar, retornamos um IString criado através de TString.Create, que tem como parâmetro a string longa original que queremos retornar.
Para usar uma DLL criada com este método em um aplicativo host, você deve incluir na cláusula uses de todas as unidades que usarem a DLL a unidade StrInt e usar o valor retornado de forma parecida com o seguinte:
procedure ShowAutoexec;
var
I: IString;
begin
I := GetAutoexec;
ShowMessage(I.Str);
end;
Note que podemos usar também a seguinte forma para fazer a mesma coisa:
procedure ShowAutoexec;
begin
ShowMessage(GetAutoexec.Str);
end;
Perceba que a utilização é muito mais simples que quando com o primeiro método (o que usa uma procedure para liberar o PChar).
Para entender o funcionamento desse método, você deve ter um pouco de conhecimento de interfaces COM. Se quiser, pode pular os parágrafos seguintes.
O método baseia-se na chamada automática do método Release das interfaces quando a execução sai do escopo de uma variável do tipo interface COM. Ao sair do escopo de uma dessas variáveis o Delphi zera ela e chama o método Release automaticamente, de forma semelhante à liberação de strings.
Aproveitamos isso, então, para criarmos um objeto que implementa uma interface especial (IString). O objeto (TString) mantém uma PChar apontada para a string. Quando o objeto é destruído, ele automaticamente libera a variável PChar.
Vamos analisar o que ocorre no exemplo acima.
Quando o aplicativo chama a função GetAutoexec, esta (que está na DLL), cria uma string e a usa como parâmetro para a criação de um objeto TString. O construtor desse objeto, então, aloca o espaço necessário e copia os caracteres da string para lá. GetAutoexec retorna a interface IString associada ao objeto (o Delphi faz automaticamente a "conversão" entre TString e IString baseado no tipo que deve ser retornado por GetAutoexec).
No aplicativo host, usamos o método Str da interface como parâmetro para ShowMessage. Ao final da procedure ShowAutoexec, o Delphi executa automaticamente o método Release da interface IString.
Perceba que a classe TString é derivada de TInterfacedObject. Ela implementa os métodos de IUnknown de forma que o último Release destrua o objeto. Assim, quando o método Release é chamado ao final de ShowAutoexec, o objeto verifica se não existe mais nenhuma outra variável apontada para ele e executa o destrutor do objeto que, por suz vez, libera a PChar.
Claro que você não precisa entender isso para usar o método. Basta seguir os exemplos.
Importante
Só utilize estes métodos se realmente não quiser incluir a unidade Sharemem e a DLL BORLANDMM.DLL em seu projeto. A inclusão desses componentes facilita bastante a comunicação entre seu aplicativo e as DLLs criadas para ele, visto que as operações com strings longas em Delphi são bastante facilitadas pela linguagem.
|
|
Comentários | |
| | Comentários pertencem aos seus respectivos autores. Não somos responsáveis pelo seus conteúdos. |
por: cristianok : Ago 16, 2006 - 08:56 (Informações sobre o membro | Enviar uma mensagem)
|
Seria muito interessante se indicassem o autor do artigo:
http://www.cristianok.hpg.ig.com.br/art2/index.html
Dá para ver as imagens, inclusive. Peço que o webmaster inclua o link no cabeçalho da página.
Obrigado.
|
|
|
Edição 112 |
|
|
50 Programas Fontes |
|
|
Produtos |
|
|