Bahkan, artikelnya agak lebih luas - itu menggambarkan cara untuk menggunakan banyak perpustakaan lain secara
transparan (dan tidak hanya dari dunia
Free Pascal ), dan
InternetTools dipilih karena propertinya yang luar biasa - inilah kasus ketika (secara mengejutkan) tidak ada Versi delphi dengan fitur dan kegunaan yang sama.
Perpustakaan ini dirancang untuk mengekstraksi informasi (parsing) dari dokumen web (XML dan HTML), memungkinkan Anda untuk menggunakan
bahasa permintaan tingkat tinggi seperti
XPath dan
XQuery untuk menunjukkan data yang diperlukan, dan, sebagai opsi, menyediakan
akses langsung ke elemen pohon yang dibangun di atas dokumen.
Pengantar InternetTools
Materi lebih lanjut akan diilustrasikan berdasarkan tugas yang agak sederhana, yang menyiratkan mendapatkan elemen-elemen dari daftar berpoin dan bernomor dari artikel ini yang berisi tautan, yang, jika Anda melihat pada
dokumentasi , kode sekecil itu sudah cukup (dibangun atas dasar contoh kedua dari belakang dengan perubahan kecil tanpa prinsip) ):
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.
Namun, sekarang kode yang ringkas dan berorientasi objek ini hanya dapat ditulis dalam Free Pascal, tetapi kita harus dapat menggunakan segala yang disediakan oleh perpustakaan ini dalam aplikasi Delphi, dan lebih disukai dalam gaya yang sama, dengan fasilitas yang sama; Penting juga untuk dicatat bahwa InternetTools
aman dari utas (mengaksesnya diizinkan dari banyak utas pada saat yang bersamaan), jadi opsi kami harus menyediakan ini.
Metode implementasi
Jika Anda mendekati tugas sejauh mungkin, maka ada beberapa cara untuk menggunakan sesuatu yang ditulis dalam
bahasa lain - mereka akan membentuk 3 kelompok besar:
- Menempatkan pustaka dalam proses terpisah yang file yang dapat dieksekusi dibuat secara paksa, dalam hal ini, FPC . Metode ini juga dapat dibagi menjadi dua kategori untuk kemungkinan komunikasi jaringan:
- Mengenkapsulasi pustaka dalam DLL (selanjutnya disebut sebagai "pustaka dinamis"), bekerja, menurut definisi, dalam kerangka proses tunggal. Meskipun objek COM dapat ditempatkan dalam DLL, artikel ini akan mempertimbangkan metode yang lebih sederhana dan tidak terlalu memakan waktu, yang memberikan, dengan semua ini, kenyamanan yang sama ketika memanggil fungsi perpustakaan.
- Porting Seperti dalam kasus-kasus sebelumnya, kesesuaian pendekatan ini - menulis ulang kode ke dalam bahasa lain - ditentukan oleh keseimbangan antara pro dan kontra, tetapi dalam situasi dengan InternetTools, kerugian porting jauh lebih besar, yaitu: karena banyaknya kode perpustakaan, Anda perlu melakukan pekerjaan yang sangat serius (bahkan dengan mempertimbangkan kesamaan bahasa pemrograman), dan juga secara berkala, karena pengembangan yang portabel , tugas mentransfer koreksi dan fitur baru ke Delphi akan muncul.
Dll
Lebih lanjut, untuk memberi pembaca kesempatan merasakan perbedaan, ada 2 opsi yang dibedakan berdasarkan kemudahan penggunaannya.
Implementasi "Klasik"
Mari kita mulai menggunakan InternetTools dengan gaya prosedural yang ditentukan oleh sifat perpustakaan dinamis yang hanya dapat mengekspor fungsi dan prosedur; kami akan membuat cara berkomunikasi dengan DLL mirip dengan WinAPI, ketika pegangan sumber daya tertentu pertama kali diminta, setelah itu pekerjaan yang bermanfaat dilakukan, dan kemudian pegangan yang dihasilkan dihancurkan (ditutup). Tidak perlu mempertimbangkan opsi ini sebagai panutan dalam segala hal - ia hanya dipilih untuk demonstrasi dan perbandingan selanjutnya dengan yang kedua - semacam kerabat yang miskin.
Komposisi dan kepemilikan file dari solusi yang diusulkan akan terlihat seperti ini (panah menunjukkan dependensi):
Modul InternetTools.Types
Karena kedua bahasa - Delphi dan Free Pascal - sangat mirip dalam kasus ini, sangat masuk akal untuk memilih modul umum yang berisi jenis yang digunakan dalam daftar ekspor DLL, agar tidak menggandakan definisi mereka dalam aplikasi
InternetToolsUsage
, yang mencakup prototipe fungsi dari perpustakaan dinamis:
unit InternetTools.Types; interface type TXQHandle = Integer; implementation end.
Dalam implementasi ini, hanya satu tipe bashful didefinisikan, tetapi di masa depan, modul akan "matang" dan kegunaannya akan menjadi tak terbantahkan.
Perpustakaan Dinamis InternetTools
Komposisi prosedur dan fungsi DLL dipilih minimal, tetapi cukup untuk implementasi
tugas di atas:
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.
Karena sifat demo dari implementasi saat ini, kode lengkap tidak diberikan - jauh lebih penting adalah bagaimana API paling sederhana ini akan digunakan nanti. Di sini Anda tidak perlu melupakan persyaratan keamanan benang, yang, meskipun akan membutuhkan sedikit usaha, tetapi itu tidak akan menjadi sesuatu yang rumit.
Aplikasi InternetToolsUsage
Berkat persiapan sebelumnya, menjadi mungkin untuk menulis ulang
contoh daftar di 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.
Jika Anda tidak mempertimbangkan prototipe fungsi dan prosedur dari pustaka dinamis, maka Anda tidak dapat mengatakan bahwa kodenya lebih berat dibandingkan dengan versi pada Free Pascal, tetapi jika kami sedikit menyulitkan tugas dan mencoba memfilter beberapa elemen dan menampilkan alamat tautan yang terkandung dalam tersisa:
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.
Dimungkinkan untuk melakukan ini dengan API DLL saat ini, tetapi verbositas dari hasilnya sudah sangat tinggi, yang tidak hanya sangat mengurangi keterbacaan kode, tetapi juga (dan yang tidak kalah penting) memindahkannya dari yang di atas:
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.
Jelas - dalam kasus nyata dan lebih kompleks, volume penulisan hanya akan tumbuh dengan cepat, dan oleh karena itu kami akan beralih ke solusi yang bebas dari masalah seperti itu.
Implementasi antarmuka
Gaya prosedural bekerja dengan perpustakaan, seperti yang baru saja ditunjukkan, adalah mungkin, tetapi memiliki kelemahan signifikan. Karena fakta bahwa DLL mendukung penggunaan antarmuka (seperti tipe data yang diterima dan dikembalikan), Anda dapat mengatur kerja dengan InternetTools dengan cara yang sama nyamannya seperti ketika menggunakannya dengan Free Pascal. Dalam hal ini, diinginkan untuk sedikit mengubah komposisi file untuk mendistribusikan deklarasi dan implementasi antarmuka menjadi modul terpisah:
Seperti sebelumnya, kita akan memeriksa setiap file secara berurutan.
Modul InternetTools.Types
Mendeklarasikan antarmuka yang akan diterapkan di 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.
Arahan kompilasi bersyarat diperlukan karena penggunaan modul tidak berubah di kedua proyek Delphi- dan FPC.
Antarmuka
IXQValueEnumerator
pada prinsipnya opsional, namun, untuk dapat menggunakan loop dari bentuk "
for ... in ...
" sebagai
contoh , Anda tidak dapat melakukannya tanpa itu; antarmuka kedua adalah yang utama dan merupakan pembungkus analog lebih dari
IXQValue
dari InternetTools (itu dibuat khusus dengan nama yang sama untuk membuatnya lebih mudah untuk mengkorelasikan kode Delphi masa depan dengan dokumentasi perpustakaan pada Free Pascal). Jika kita mempertimbangkan modul dalam hal pola desain, maka antarmuka yang dideklarasikan di dalamnya adalah
adaptor , meskipun dengan fitur kecil - implementasinya terletak di perpustakaan yang dinamis.
Kebutuhan untuk mengatur jenis panggilan panggilan masuk untuk semua metode dijelaskan dengan baik di
sini . Kewajiban untuk menggunakan
WideString
sebagai ganti string "asli" juga tidak akan dibuktikan, karena topik pertukaran struktur data dinamis dengan DLL berada di luar ruang lingkup artikel ini.
Modul InternetTools.Realization
Yang pertama dalam hal kepentingan dan volume - dialah yang, sebagaimana tercermin dalam judul, akan berisi implementasi antarmuka dari yang sebelumnya: untuk keduanya, satu-satunya kelas
TXQValue
, metode yang sangat sederhana sehingga hampir semua terdiri dari satu baris kode (ini cukup diharapkan, karena semua fungsi yang diperlukan sudah ada di perpustakaan - di sini Anda hanya perlu mengaksesnya):
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.
Layak berhenti di metode
SafeCallException
- pemblokirannya, pada umumnya, tidak penting (kinerja
TXQValue
tidak
TXQValue
menderita tanpanya), namun, kode yang diberikan di sini memungkinkan Anda untuk mengirim teks pengecualian ke sisi Delphi yang akan muncul dalam metode safecall (detail, lagi, dapat ditemukan di
artikel terbaru).
Solusi ini, selain yang lainnya, aman untuk thread - asalkan
IXQValue
diterima, misalnya, melalui
OpenURL
, tidak ditransmisikan antar thread. Ini disebabkan oleh kenyataan bahwa implementasi antarmuka hanya mengalihkan panggilan ke InternetTools yang sudah aman.
Perpustakaan Dinamis InternetTools
Karena pekerjaan yang dilakukan dalam modul di atas, cukup bagi DLL untuk mengekspor fungsi tunggal (bandingkan dengan
opsi di mana gaya prosedural digunakan):
library InternetTools; uses InternetTools.Types, InternetTools.Realization; function GetXQValue: IXQValue; stdcall; begin Result := TXQValue.Create; end; exports GetXQValue; begin SetMultiByteConversionCodePage(CP_UTF8); end.
Panggilan ke prosedur
SetMultiByteConversionCodePage
dirancang untuk bekerja dengan string Unicode dengan benar.
Aplikasi InternetToolsUsage
Jika kita sekarang memformalkan solusi Delphi dari
contoh awal berdasarkan antarmuka yang diusulkan, maka itu akan sangat berbeda dari yang ada pada Free Pascal, yang berarti tugas yang diatur pada awal artikel dapat dianggap selesai:
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.
Modul
System.Win.ComObj
tidak terkoneksi secara tidak sengaja - tanpanya, teks dari semua pengecualian safecall akan menjadi “Exception in safecall method” tanpa wajah, dan dengannya nilai asli yang dihasilkan dalam DLL.
Contoh yang sedikit
rumit juga memiliki perbedaan minimal pada 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.
Fungsi perpustakaan yang tersisa
Jika Anda melihat kemampuan penuh antarmuka
IXQValue dari InternetTools, Anda akan melihat bahwa
antarmuka yang sesuai dari
InternetTools.Types
hanya mendefinisikan 2 metode (
Map
dan
ToString
) dari seluruh rangkaian kaya; menambahkan sisanya yang dianggap perlu oleh pembaca dalam kasus khususnya dilakukan dengan cara yang persis sama dan sederhana: metode yang diperlukan ditulis dalam
InternetTools.Types
, setelah itu mereka dibangun di
InternetTools.Realization
modul dengan kode (paling sering sebagai satu baris).
Jika Anda perlu menggunakan fungsionalitas yang sedikit berbeda, misalnya, manajemen cookie, urutan langkah-langkahnya sangat mirip:
- Antarmuka baru
InternetTools.Types
di InternetTools.Types
:
... ICookies = interface ['{21D0CC9A-204D-44D2-AF00-98E9E04412CD}'] procedure Add(const URL, Name, Value: WideString); safecall; procedure Clear; safecall; end; ...
- Kemudian diimplementasikan dalam modul
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; ...
- Setelah itu, fungsi yang diekspor baru dikembalikan ke DLL yang mengembalikan antarmuka ini:
... function GetCookies: ICookies; stdcall; begin Result := TCookies.Create; end; exports ..., GetCookies; ...
Pelepasan sumber daya
Meskipun pustaka InternetTools didasarkan pada antarmuka yang secara otomatis mengontrol masa pakai, ada satu nuansa yang tidak jelas yang kelihatannya menyebabkan kebocoran memori - jika Anda menjalankan aplikasi konsol berikutnya (dibuat pada Delphi, tidak ada yang akan berubah dalam kasus FPC), maka setiap kali Anda menekan tombol enter, memori yang dikonsumsi oleh proses akan tumbuh:
... 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.
Tidak ada kesalahan dengan penggunaan antarmuka di sini. Masalahnya adalah bahwa InternetTools tidak merilis sumber daya internalnya yang dialokasikan selama analisis dokumen (dalam metode
OpenURL
) - ini harus dilakukan
secara eksplisit setelah pekerjaan dengan itu selesai; untuk tujuan ini, modul perpustakaan
xquery
menyediakan prosedur
freeThreadVars
, panggilan yang dari aplikasi Delphi logis untuk memastikan dengan memperluas daftar ekspor DLL:
... procedure FreeResources; stdcall; begin freeThreadVars; end; exports ..., FreeResources; ...
Setelah penggunaannya, hilangnya sumber daya akan berhenti:
for I := 1 to 100 do begin Writeln( GetXQValue.OpenURL(ArticleURL).Map(TitleXPath).ToString ); FreeResources; Readln; end;
Penting untuk memahami yang berikut - panggilan ke
FreeResources
mengarah pada kenyataan bahwa semua antarmuka yang sebelumnya diterima menjadi tidak berarti dan segala upaya untuk menggunakannya tidak dapat diterima.