Usando a biblioteca FPC do InternetPCools no Delphi

De fato, o artigo é um pouco mais amplo - descreve uma maneira de usar de maneira transparente muitas outras bibliotecas (e não apenas do mundo Free Pascal ), e o InternetTools foi escolhido por causa de sua propriedade notável - é o caso quando (surpreendentemente) está faltando Versão Delphi com os mesmos recursos e usabilidade.

Essa biblioteca foi projetada para extrair informações (análise) de documentos da Web (XML e HTML), permitindo o uso de linguagens de consulta de alto nível , como XPath e XQuery, para indicar os dados necessários e, como opção, fornecendo acesso direto a elementos de uma árvore construída no documento.

Introdução ao InternetTools


O material adicional será ilustrado com base em uma tarefa bastante simples, que implica obter os elementos de listas numeradas e com marcadores deste artigo que contêm links, para os quais, se você olhar para a documentação , um código tão pequeno é suficiente (ele é construído com base no penúltimo exemplo com pequenas alterações sem princípios) ):

uses xquery; const ArticleURL = 'https://habr.com/post/415617'; ListXPath = '//div[@class="post__body post__body_full"]//li[a]'; var ListValue: IXQValue; begin for ListValue in xqvalue(ArticleURL).retrieve.map(ListXPath) do Writeln(ListValue.toString); end. 

No entanto, agora esse código compacto e orientado a objetos só pode ser escrito no Free Pascal, mas precisamos poder usar tudo o que essa biblioteca fornece em um aplicativo Delphi, e de preferência em um estilo semelhante, com as mesmas comodidades; Também é importante observar que o InternetTools é seguro para threads (acessá-lo é permitido a partir de vários threads ao mesmo tempo); portanto, nossa opção deve fornecer isso.

Métodos de implementação


Se você abordar a tarefa o mais longe possível, existem várias maneiras de usar algo escrito em outro idioma - elas formarão 3 grandes grupos:

  1. Colocar a biblioteca em um processo separado , cujo arquivo executável é criado por forças, nesse caso, FPC . Este método também pode ser dividido em duas categorias para possível comunicação em rede:
  2. Encapsular uma biblioteca em uma DLL (a seguir denominada "biblioteca dinâmica"), trabalhando, por definição, na estrutura de um único processo. Embora objetos COM possam ser colocados em DLLs, o artigo considerará um método mais simples e demorado, que oferece, com tudo isso, o mesmo conforto ao chamar a funcionalidade da biblioteca.
  3. Porting Como nos casos anteriores, a adequação dessa abordagem - reescrever código em outro idioma - é determinada pelo equilíbrio entre seus prós e contras, mas na situação com o InternetTools as desvantagens de portar são muito maiores, a saber: devido à quantidade considerável de código de biblioteca, você precisará realizar um trabalho muito sério (mesmo levando em consideração a semelhança das linguagens de programação) e também periodicamente, devido ao desenvolvimento da portátil , a tarefa de transferir correções e novos recursos para o Delphi aparecerá.

Dll


Além disso, para oferecer ao leitor a oportunidade de sentir a diferença, existem 2 opções que se distinguem pela conveniência de seu uso.

Implementação "clássica"


Vamos tentar começar a usar o InternetTools em um estilo processual ditado pela própria natureza de uma biblioteca dinâmica que pode exportar apenas funções e procedimentos; definiremos o modo de nos comunicar com a DLL semelhante ao WinAPI, quando o identificador de um determinado recurso for solicitado pela primeira vez, após o qual um trabalho útil seja executado e, em seguida, o identificador resultante será destruído (fechado). Não é necessário considerar essa opção como um modelo em tudo - ela é escolhida apenas para demonstração e comparação subsequente com a segunda - uma espécie de parente pobre.

A composição e a propriedade dos arquivos da solução proposta terão a seguinte aparência (as setas mostram as dependências):

A composição dos arquivos da implementação "clássica"


Módulo InternetTools.Types


Como os dois idiomas - Delphi e Free Pascal - são muito semelhantes nesse caso, é muito razoável selecionar um módulo comum contendo os tipos usados ​​na lista de exportação da DLL, para não duplicar sua definição no aplicativo InternetToolsUsage , que inclui protótipos de funcionalidade de uma biblioteca dinâmica:

 unit InternetTools.Types; interface type TXQHandle = Integer; implementation end. 

Nesta implementação, apenas um tipo tímido é definido, mas, no futuro, o módulo "amadurecerá" e sua utilidade se tornará inegável.

Biblioteca dinâmica do InternetTools


A composição dos procedimentos e funções da DLL é selecionada para ser mínima, mas suficiente para a implementação da tarefa acima:

 library InternetTools; uses InternetTools.Types; function OpenDocument(const URL: WideString): TXQHandle; stdcall; begin ... end; procedure CloseHandle(const Handle: TXQHandle); stdcall; begin ... end; function Map(const Handle: TXQHandle; const XQuery: WideString): TXQHandle; stdcall; begin ... end; function Count(const Handle: TXQHandle): Integer; stdcall; begin ... end; function ValueByIndex(const Handle: TXQHandle; const Index: Integer): WideString; stdcall; begin ... end; exports OpenDocument, CloseHandle, Map, Count, ValueByIndex; begin end. 

Devido à natureza de demonstração da implementação atual, o código completo não é fornecido - muito mais importante é como essa API mais simples será usada posteriormente. Aqui, você simplesmente não precisa esquecer o requisito de segurança de encadeamento, que, embora exija algum esforço, mas não será algo complicado.

Aplicativo InternetToolsUsage


Graças aos preparativos anteriores, foi possível reescrever o exemplo da lista em Delphi:

 program InternetToolsUsage; ... uses InternetTools.Types; const DLLName = 'InternetTools.dll'; function OpenDocument(const URL: WideString): TXQHandle; stdcall; external DLLName; procedure CloseHandle(const Handle: TXQHandle); stdcall; external DLLName; function Map(const Handle: TXQHandle; const XQuery: WideString): TXQHandle; stdcall; external DLLName; function Count(const Handle: TXQHandle): Integer; stdcall; external DLLName; function ValueByIndex(const Handle: TXQHandle; const Index: Integer): WideString; stdcall; external DLLName; const ArticleURL = 'https://habr.com/post/415617'; ListXPath = '//div[@class="post__body post__body_full"]//li[a]'; var RootHandle, ListHandle: TXQHandle; I: Integer; begin RootHandle := OpenDocument(ArticleURL); try ListHandle := Map(RootHandle, ListXPath); try for I := 0 to Count(ListHandle) - 1 do Writeln( ValueByIndex(ListHandle, I) ); finally CloseHandle(ListHandle); end; finally CloseHandle(RootHandle); end; ReadLn; end. 

Se você não levar em conta os protótipos de funções e procedimentos da biblioteca dinâmica, não poderá dizer que o código se tornou catastroficamente mais pesado em comparação com a versão no Free Pascal, mas que se complicarmos um pouco a tarefa e tentarmos filtrar alguns elementos e exibir os endereços de link contidos em restantes:

 uses xquery; const ArticleURL = 'https://habr.com/post/415617'; ListXPath = '//div[@class="post__body post__body_full"]//li[a]'; HrefXPath = './a/@href'; var ListValue, HrefValue: IXQValue; begin for ListValue in xqvalue(ArticleURL).retrieve.map(ListXPath) do if {   } then for HrefValue in ListValue.map(HrefXPath) do Writeln(HrefValue.toString); end. 

É possível fazer isso com a DLL da API atual, mas a verbosidade do resultado já é muito alta, o que não apenas reduz bastante a legibilidade do código, mas também (e não menos importante) o afasta do exposto acima:

 program InternetToolsUsage; ... const ArticleURL = 'https://habr.com/post/415617'; ListXPath = '//div[@class="post__body post__body_full"]//li[a]'; HrefXPath = './a/@href'; var RootHandle, ListHandle, HrefHandle: TXQHandle; I, J: Integer; begin RootHandle := OpenDocument(ArticleURL); try ListHandle := Map(RootHandle, ListXPath); try for I := 0 to Count(ListHandle) - 1 do if {   } then begin HrefHandle := Map(ListHandle, HrefXPath); try for J := 0 to Count(HrefHandle) - 1 do Writeln( ValueByIndex(HrefHandle, J) ); finally CloseHandle(HrefHandle); end; end; finally CloseHandle(ListHandle); end; finally CloseHandle(RootHandle); end; ReadLn; end. 

Obviamente - em casos reais e mais complexos, o volume de escrita só aumentará rapidamente e, portanto, passaremos para uma solução livre de tais problemas.

Implementação de interface


O estilo procedural de trabalhar com a biblioteca, como acabamos de mostrar, é possível, mas possui desvantagens significativas. Devido ao fato de a DLL oferecer suporte ao uso de interfaces (como tipos de dados recebidos e retornados), você pode organizar o trabalho com o InternetTools da mesma maneira conveniente que ao usá-lo com o Free Pascal. Nesse caso, é desejável alterar ligeiramente a composição dos arquivos para distribuir a declaração e a implementação das interfaces em módulos separados:

A composição dos arquivos de implementação da interface

Como antes, examinaremos cada um dos arquivos em sequência.

Módulo InternetTools.Types


Declara as interfaces a serem implementadas na DLL:

 unit InternetTools.Types; {$IFDEF FPC} {$MODE Delphi} {$ENDIF} interface type IXQValue = interface; IXQValueEnumerator = interface ['{781B23DC-E8E8-4490-97EE-2332B3736466}'] function MoveNext: Boolean; safecall; function GetCurrent: IXQValue; safecall; property Current: IXQValue read GetCurrent; end; IXQValue = interface ['{DCE33144-A75F-4C53-8D25-6D9BD78B91E4}'] function GetEnumerator: IXQValueEnumerator; safecall; function OpenURL(const URL: WideString): IXQValue; safecall; function Map(const XQuery: WideString): IXQValue; safecall; function ToString: WideString; safecall; end; implementation end. 

As diretivas de compilação condicional são necessárias devido ao uso do módulo inalterado nos projetos Delphi- e FPC.

A interface IXQValueEnumerator em princípio, opcional, no entanto, para poder usar loops do formato " for ... in ... " como exemplo , você não pode ficar sem ele; a segunda interface é a principal e é um invólucro analógico sobre o IXQValue do InternetTools (foi criado especialmente com o mesmo nome para facilitar a correlação do futuro código Delphi com a documentação da biblioteca no Free Pascal). Se considerarmos o módulo em termos de padrões de design, as interfaces declaradas nele são adaptadoras , embora com um pequeno recurso - sua implementação está localizada em uma biblioteca dinâmica.

A necessidade de definir o tipo de chamada de chamada safecall para todos os métodos está bem descrita aqui . A obrigação de usar WideString vez de seqüências "nativas" também não será comprovada, pois o tópico de troca de estruturas de dados dinâmicas com DLLs está além do escopo deste artigo.

Módulo InternetTools.Realization


O primeiro em termos de importância e volume - é ele quem, como refletido no título, conterá a implementação das interfaces da anterior: para ambas, a única classe TXQValue , cujos métodos são tão simples que quase todos consistem em uma linha de código (isso é bastante esperado, porque toda a funcionalidade necessária já está contida na biblioteca - aqui você só precisa acessá-la):

 unit InternetTools.Realization; {$MODE Delphi} interface uses xquery, InternetTools.Types; type IOriginalXQValue = xquery.IXQValue; TXQValue = class(TInterfacedObject, IXQValue, IXQValueEnumerator) private FOriginalXQValue: IOriginalXQValue; FEnumerator: TXQValueEnumerator; function MoveNext: Boolean; safecall; function GetCurrent: IXQValue; safecall; function GetEnumerator: IXQValueEnumerator; safecall; function OpenURL(const URL: WideString): IXQValue; safecall; function Map(const XQuery: WideString): IXQValue; safecall; function ToString: WideString; safecall; reintroduce; public constructor Create(const OriginalXQValue: IOriginalXQValue); overload; function SafeCallException(ExceptObject: TObject; ExceptAddr: CodePointer): HResult; override; end; implementation uses sysutils, comobj, w32internetaccess; function TXQValue.MoveNext: Boolean; begin Result := FEnumerator.MoveNext; end; function TXQValue.GetCurrent: IXQValue; begin Result := TXQValue.Create(FEnumerator.Current); end; function TXQValue.GetEnumerator: IXQValueEnumerator; begin FEnumerator := FOriginalXQValue.GetEnumerator; Result := Self; end; function TXQValue.OpenURL(const URL: WideString): IXQValue; begin FOriginalXQValue := xqvalue(URL).retrieve; Result := Self; end; function TXQValue.Map(const XQuery: WideString): IXQValue; begin Result := TXQValue.Create( FOriginalXQValue.map(XQuery) ); end; function TXQValue.ToString: WideString; begin Result := FOriginalXQValue.toJoinedString(LineEnding); end; constructor TXQValue.Create(const OriginalXQValue: IOriginalXQValue); begin FOriginalXQValue := OriginalXQValue; end; function TXQValue.SafeCallException(ExceptObject: TObject; ExceptAddr: CodePointer): HResult; begin Result := HandleSafeCallException(ExceptObject, ExceptAddr, GUID_NULL, ExceptObject.ClassName, ''); end; end. 

Vale a pena parar no método SafeCallException - seu bloqueio, em geral, não é vital ( TXQValue desempenho TXQValue não TXQValue afetado sem ele); no entanto, o código fornecido aqui permite que você transmita o texto da exceção para o lado do Delphi que ocorrerá nos métodos de chamada segura (detalhes, novamente, pode ser encontrado em um artigo recente).

Essa solução, além de tudo o mais, é segura para threads - desde que IXQValue recebido, por exemplo, através do OpenURL , não seja transmitido entre threads. Isso se deve ao fato de que a implementação da interface redireciona apenas as chamadas para o InternetTools já seguro para threads.

Biblioteca dinâmica do InternetTools


Devido ao trabalho realizado nos módulos acima, é suficiente para a DLL exportar uma única função (compare com a opção em que o estilo procedural foi usado):

 library InternetTools; uses InternetTools.Types, InternetTools.Realization; function GetXQValue: IXQValue; stdcall; begin Result := TXQValue.Create; end; exports GetXQValue; begin SetMultiByteConversionCodePage(CP_UTF8); end. 

A chamada para o procedimento SetMultiByteConversionCodePage projetada para funcionar corretamente com seqüências de caracteres Unicode.

Aplicativo InternetToolsUsage


Se formalizarmos agora a solução Delphi do exemplo inicial com base nas interfaces propostas, dificilmente será diferente da do Free Pascal, o que significa que a tarefa definida no início do artigo pode ser considerada concluída:

 program InternetToolsUsage; ... uses System.Win.ComObj, InternetTools.Types; const DLLName = 'InternetTools.dll'; function GetXQValue: IXQValue; stdcall; external DLLName; const ArticleURL = 'https://habr.com/post/415617'; ListXPath = '//div[@class="post__body post__body_full"]//li[a]'; var ListValue: IXQValue; begin for ListValue in GetXQValue.OpenURL(ArticleURL).Map(ListXPath) do Writeln(ListValue.ToString); ReadLn; end. 

O módulo System.Win.ComObj não está conectado acidentalmente - sem ele, o texto de todas as exceções de safecall se tornará uma "exceção no método de segurança de chamada" sem rosto e, com ele, o valor original gerado na DLL.

Um exemplo um pouco complicado também tem diferenças mínimas no Delphi:

 ... const ArticleURL = 'https://habr.com/post/415617'; ListXPath = '//div[@class="post__body post__body_full"]//li[a]'; HrefXPath = './a/@href'; var ListValue, HrefValue: IXQValue; begin for ListValue in GetXQValue.OpenURL(ArticleURL).Map(ListXPath) do if {   } then for HrefValue in ListValue.Map(HrefXPath) do Writeln(HrefValue.ToString); ReadLn; end. 

A funcionalidade restante da biblioteca


Se você observar todos os recursos da interface IXQValue do InternetTools, verá que a interface correspondente do InternetTools.Types define apenas dois métodos ( Map e ToString ) de todo o conjunto completo; adicionar o restante que o leitor considerar necessário em seu caso específico é realizado exatamente da mesma maneira e de maneira simples: os métodos necessários são escritos no InternetTools.Types , após o qual são criados no módulo InternetTools.Realization com código (geralmente como uma única linha).

Se você precisar usar uma funcionalidade ligeiramente diferente, por exemplo, gerenciamento de cookies, a sequência de etapas será muito semelhante:

  1. Uma nova interface é InternetTools.Types no InternetTools.Types :

     ... ICookies = interface ['{21D0CC9A-204D-44D2-AF00-98E9E04412CD}'] procedure Add(const URL, Name, Value: WideString); safecall; procedure Clear; safecall; end; ... 
  2. Em seguida, é implementado no módulo InternetTools.Realization :

     ... type TCookies = class(TInterfacedObject, ICookies) private procedure Add(const URL, Name, Value: WideString); safecall; procedure Clear; safecall; public function SafeCallException(ExceptObject: TObject; ExceptAddr: CodePointer): HResult; override; end; ... implementation uses ..., internetaccess; ... procedure TCookies.Add(const URL, Name, Value: WideString); begin defaultInternet.cookies.setCookie( decodeURL(URL).host, decodeURL(URL).path, Name, Value, [] ); end; procedure TCookies.Clear; begin defaultInternet.cookies.clear; end; ... 
  3. Depois disso, uma nova função exportada é retornada para a DLL que retorna essa interface:

     ... function GetCookies: ICookies; stdcall; begin Result := TCookies.Create; end; exports ..., GetCookies; ... 

Liberação de recursos


Embora a biblioteca do InternetTools seja baseada em interfaces que controlam automaticamente a vida útil, há uma nuance não óbvia que parece levar a vazamentos de memória - se você executar o próximo aplicativo de console (criado no Delphi, nada mudará no caso do FPC), toda vez que você pressionar a tecla Enter, a memória consumida pelo processo aumentará:

 ... const ArticleURL = 'https://habr.com/post/415617'; TitleXPath = '//head/title'; var I: Integer; begin for I := 1 to 100 do begin Writeln( GetXQValue.OpenURL(ArticleURL).Map(TitleXPath).ToString ); Readln; end; end. 

Não há erros com o uso de interfaces aqui. O problema é que o InternetTools não libera seus recursos internos alocados durante a análise do documento (no método OpenURL ) - isso deve ser feito explicitamente , após o término do trabalho; para esses propósitos, o módulo da biblioteca xquery fornece o procedimento freeThreadVars , cuja chamada de um aplicativo Delphi pode ser logicamente garantida, expandindo a lista de exportação da DLL:

 ... procedure FreeResources; stdcall; begin freeThreadVars; end; exports ..., FreeResources; ... 

Após seu uso, a perda de recursos será interrompida:

 for I := 1 to 100 do begin Writeln( GetXQValue.OpenURL(ArticleURL).Map(TitleXPath).ToString ); FreeResources; Readln; end; 

É importante entender o seguinte: uma chamada para o FreeResources leva ao fato de que todas as interfaces recebidas anteriormente ficam sem sentido e qualquer tentativa de usá-las é inaceitável.

Source: https://habr.com/ru/post/pt415617/


All Articles