استخدام مكتبة InternetPCools FPC في دلفي

في الواقع ، المقالة أوسع إلى حد ما - تصف طريقة لاستخدام العديد من المكتبات الأخرى بشفافية (وليس فقط من عالم Free Pascal ) ، وتم اختيار InternetTools بسبب ملكيتها الرائعة - هذا هو الحال عندما (من المدهش) مفقود نسخة دلفي مع نفس الميزات وسهولة الاستخدام.

تم تصميم هذه المكتبة لاستخراج المعلومات (التحليل) من مستندات الويب (XML و HTML) ، مما يسمح لك باستخدام لغات الاستعلام عالية المستوى مثل XPath و XQuery للإشارة إلى البيانات المطلوبة ، وكخيار ، توفير الوصول المباشر إلى عناصر شجرة مبنية على المستند.

مقدمة إلى InternetTools


سيتم توضيح المزيد من المواد على أساس مهمة بسيطة إلى حد ما ، مما يعني ضمناً الحصول على تلك العناصر من القوائم ذات التعداد النقطي والمرقمة لهذه المقالة التي تحتوي على روابط ، والتي إذا نظرت إلى الوثائق ، فإن مثل هذا الرمز الصغير يكفي (يتم بناؤه على أساس المثال قبل الأخير مع تغييرات صغيرة غير مبدئية ):

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. 

ومع ذلك ، يمكن الآن كتابة هذا الرمز المضغوط والموجه للكائن فقط في Free Pascal ، ولكننا بحاجة إلى أن نكون قادرين على استخدام كل ما توفره هذه المكتبة في تطبيق دلفي ، ويفضل أن يكون ذلك بأسلوب مماثل ، مع نفس وسائل الراحة من المهم أيضًا ملاحظة أن InternetTools آمن لمؤشر الترابط (الوصول إليه مسموح به من العديد من سلاسل الرسائل في نفس الوقت) ، لذلك يجب أن يوفر خيارنا ذلك.

طرق التنفيذ


إذا اقتربت من المهمة إلى أقصى حد ممكن ، فهناك عدة طرق لاستخدام شيء مكتوب بلغة أخرى - سيشكلون 3 مجموعات كبيرة:

  1. وضع المكتبة في عملية منفصلة ، يتم إنشاء الملف القابل للتنفيذ من قبل القوات ، في هذه الحالة ، FPC . يمكن أيضًا تقسيم هذه الطريقة إلى فئتين لاتصال الشبكة المحتمل:
  2. تغليف مكتبة في مكتبة الارتباط الحيوي ( DLL) (يشار إليها فيما بعد أحيانًا باسم "المكتبة الديناميكية") ، تعمل ، بحكم تعريفها ، في إطار عملية واحدة. على الرغم من أنه يمكن وضع كائنات COM في DLLs ، إلا أن المقالة ستأخذ في الاعتبار طريقة أبسط وأقل استهلاكا للوقت ، والتي توفر ، مع كل هذا ، نفس الراحة عند استدعاء وظائف المكتبة.
  3. ينقل كما هو الحال في الحالات السابقة ، يتم تحديد مدى ملاءمة هذا النهج - إعادة كتابة التعليمات البرمجية إلى لغة أخرى - من خلال التوازن بين إيجابياتها وسلبياتها ، ولكن في حالة استخدام أدوات الإنترنت ، فإن عيوب النقل أكبر بكثير ، أي: نظرًا لكمية رمز المكتبة الكبيرة ، ستحتاج إلى القيام ببعض الأعمال الجادة للغاية (حتى مع مراعاة تشابه لغات البرمجة) ، وكذلك بشكل دوري ، نظرًا لتطوير اللغة المحمولة ، ستظهر مهمة نقل التصحيحات والميزات الجديدة إلى دلفي.

دلل


علاوة على ذلك ، من أجل تزويد القارئ بفرصة الشعور بالفرق ، هناك خياران يتميزان براحة استخدامهما.

تنفيذ "كلاسيكي"


دعونا نحاول البدء في استخدام أدوات الإنترنت بأسلوب إجرائي تمليه طبيعة مكتبة ديناميكية يمكنها تصدير الوظائف والإجراءات فقط ؛ سنجعل طريقة الاتصال بـ DLL مماثلة لـ WinAPI ، عندما يتم طلب مقبض مورد معين أولاً ، وبعد ذلك يتم تنفيذ العمل المفيد ، ثم يتم تدمير المقبض الناتج (مغلق). ليس من الضروري اعتبار هذا الخيار كنموذج يحتذى به في كل شيء - يتم اختياره فقط للتوضيح والمقارنة اللاحقة مع الثاني - وهو نوع من الأقارب الفقراء.

سيبدو تكوين وملكية ملفات الحل المقترح على هذا النحو (تشير الأسهم إلى التبعيات):

تكوين ملفات التنفيذ "الكلاسيكية"


InternetTools.Types Module


نظرًا لأن اللغتين - دلفي و Free Pascal - متشابهان جدًا في هذه الحالة ، فمن المعقول جدًا تخصيص مثل هذه الوحدة الشائعة التي تحتوي على الأنواع المستخدمة في قائمة تصدير DLL ، حتى لا تكرر تعريفها في تطبيق InternetToolsUsage ، والتي تتضمن نماذج أولية للوظائف من مكتبة ديناميكية:

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

في هذا التطبيق ، تم تحديد نوع خجول واحد فقط ، ولكن في المستقبل ، سوف "تنضج" الوحدة وستصبح فائدتها لا يمكن إنكارها.

مكتبة ديناميكية أدوات الإنترنت


يتم تحديد تكوين إجراءات ووظائف مكتبة الارتباط الحيوي (DLL) لتكون في حدها الأدنى ، ولكنها كافية لتنفيذ المهمة المذكورة أعلاه:

 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. 

نظرًا للطبيعة التجريبية للتطبيق الحالي ، لا يتم إعطاء الشفرة الكاملة - والأهم من ذلك هو كيفية استخدام واجهة برمجة التطبيقات الأبسط هذه لاحقًا. هنا لا تحتاج إلى نسيان متطلبات سلامة الخيط ، والتي ، على الرغم من أنها ستتطلب بعض الجهد ، ولكنها لن تكون شيئًا معقدًا.

تطبيق InternetToolsUsage


بفضل الاستعدادات السابقة ، أصبح من الممكن إعادة كتابة مثال القائمة في دلفي:

 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. 

إذا كنت لا تأخذ في الاعتبار النماذج الأولية للوظائف والإجراءات من المكتبة الديناميكية ، فلا يمكنك القول أن الكود أثقل بشكل كارثي مقارنة بالإصدار على Free Pascal ، ولكن إذا قمنا بتعقيد المهمة قليلاً وحاول تصفية بعض العناصر وعرض عناوين الارتباط الموجودة في المتبقي:

 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. 

من الممكن القيام بذلك باستخدام API API الحالي ، ولكن فعل الناتج مرتفع للغاية بالفعل ، مما لا يقلل بشكل كبير من إمكانية قراءة التعليمات البرمجية فحسب ، بل أيضًا (وليس أقل أهمية) ينقلها بعيدًا عن ما سبق:

 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. 

من الواضح - في الحالات الواقعية الأكثر تعقيدًا ، أن حجم الكتابة سينمو بسرعة فقط ، وبالتالي سننتقل إلى حل خالٍ من هذه المشاكل.

تنفيذ الواجهة


إن الأسلوب الإجرائي للعمل مع المكتبة ، كما هو موضح للتو ، ممكن ، ولكن له عيوب كبيرة. نظرًا لأن مكتبة الارتباط الحيوي (DLL) على هذا النحو تدعم استخدام الواجهات (مثل أنواع البيانات المستلمة والمرتجعة) ، يمكنك تنظيم العمل مع InternetTools بنفس الطريقة المناسبة عند استخدامها مع Free Pascal. في هذه الحالة ، من المستحسن تغيير تكوين الملفات بشكل طفيف من أجل توزيع الإعلان وتنفيذ الواجهات في وحدات منفصلة:

تكوين ملفات تنفيذ الواجهة

كما كان من قبل ، سوف نفحص كل ملف على التوالي.

InternetTools.Types Module


تعلن واجهات ليتم تنفيذها في 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. 

توجيهات التجميع الشرطي ضرورية بسبب استخدام الوحدة دون تغيير في كل من مشاريع دلفي و FPC.

واجهة IXQValueEnumerator اختيارية من حيث المبدأ ، ومع ذلك ، من أجل أن تكون قادرًا على استخدام حلقات النموذج " for ... in ... " كمثال ، لا يمكنك الاستغناء عنها ؛ الواجهة الثانية هي الواجهة الرئيسية وهي عبارة عن غلاف تمثيلي عبر IXQValue من InternetTools (تم إنشاؤه خصيصًا بالاسم نفسه لتسهيل ربط رمز دلفي المستقبلي بوثائق المكتبة على Free Pascal). إذا أخذنا في الاعتبار الوحدة من حيث أنماط التصميم ، فإن الواجهات المعلنة فيها هي محولات ، وإن كانت مع ميزة صغيرة - يقع تنفيذها في مكتبة ديناميكية.

تم وصف الحاجة إلى تعيين نوع مكالمة safecall لجميع الطرق جيدًا هنا . لن يتم إثبات الالتزام باستخدام WideString بدلاً من السلاسل "الأصلية" أيضًا ، نظرًا لأن موضوع تبادل هياكل البيانات الديناميكية مع DLLs يتجاوز نطاق هذه المقالة.

InternetTools.Realization Module


الأول من حيث الأهمية والحجم - هو ، كما هو موضح في العنوان ، سيحتوي على تنفيذ الواجهات من الواجهة السابقة: بالنسبة لكل منهما ، يتم TXQValue فقط عن فئة TXQValue ، والطرق التي تكون بسيطة للغاية بحيث تتكون جميعها تقريبًا من سطر واحد من التعليمات البرمجية (هذا تمامًا متوقع ، لأن جميع الوظائف الضرورية موجودة بالفعل في المكتبة - هنا تحتاج فقط للوصول إليها):

 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. 

يجدر التوقف عند طريقة SafeCallException - إن حظرها ، إلى حد كبير ، ليس حيويًا ( TXQValue يتأثر أداء TXQValue بدونها) ، ومع ذلك ، فإن الرمز المعطى هنا يسمح بتمرير نص الاستثناء إلى جانب دلفي الذي سيحدث في طرق الأمان (التفاصيل ، مرة أخرى ، يمكن العثور عليها في مقال حديث).

هذا الحل ، بالإضافة إلى كل شيء آخر ، آمن لمؤشر الترابط - بشرط ألا يتم إرسال IXQValue ، على سبيل المثال ، من خلال OpenURL ، بين IXQValue . ويرجع ذلك إلى حقيقة أن تنفيذ الواجهة يعيد توجيه المكالمات فقط إلى InternetTools الآمنة بالفعل.

مكتبة ديناميكية أدوات الإنترنت


نظرًا للعمل المنجز في الوحدات أعلاه ، يكفي أن تقوم مكتبة الارتباط الديناميكي بتصدير وظيفة واحدة (مقارنة بالخيار حيث تم استخدام النمط الإجرائي):

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

تم تصميم استدعاء الإجراء SetMultiByteConversionCodePage للعمل مع سلاسل Unicode بشكل صحيح.

تطبيق InternetToolsUsage


إذا قمنا الآن بإضفاء الطابع الرسمي على حل دلفي للمثال الأولي على أساس الواجهات المقترحة ، فلن يختلف ذلك عن ذلك على Free Pascal ، مما يعني أنه يمكن اعتبار المهمة المحددة في بداية المقالة مكتملة:

 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. 

لا يتم توصيل الوحدة النمطية System.Win.ComObj طريق الخطأ - بدونها ، سيصبح نص جميع استثناءات safecall "استثناء في طريقة safecall" بدون وجه ، ومعه القيمة الأصلية التي تم إنشاؤها في DLL.

وبالمثل ، فإن المثال المعقد قليلاً يحتوي على اختلافات طفيفة في دلفي:

 ... 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. 

الوظائف المتبقية للمكتبة


إذا نظرت إلى الإمكانات الكاملة لواجهة IXQValue من InternetTools ، فسترى أن الواجهة المقابلة من InternetTools.Types تحدد طريقتين فقط ( Map و ToString ) من المجموعة الغنية بالكامل ؛ إضافة الباقي الذي يعتبره القارئ ضروريًا في حالته الخاصة يتم بنفس الطريقة تمامًا وببساطة: تتم كتابة الطرق الضرورية في InternetTools.Types ، وبعد ذلك يتم إنشاؤها في وحدة InternetTools.Realization مع التعليمات البرمجية (غالبًا كخط واحد).

إذا كنت بحاجة إلى استخدام وظيفة مختلفة قليلاً ، على سبيل المثال ، إدارة ملفات تعريف الارتباط ، فإن تسلسل الخطوات مشابه جدًا:

  1. تم InternetTools.Types واجهة جديدة في InternetTools.Types :

     ... ICookies = interface ['{21D0CC9A-204D-44D2-AF00-98E9E04412CD}'] procedure Add(const URL, Name, Value: WideString); safecall; procedure Clear; safecall; end; ... 
  2. ثم يتم تنفيذه في وحدة 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. بعد ذلك ، يتم إرجاع وظيفة جديدة تم تصديرها إلى مكتبة الارتباط الحيوي (DLL) التي ترجع هذه الواجهة:

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

الإفراج عن الموارد


على الرغم من أن مكتبة InternetTools تستند إلى واجهات تتحكم تلقائيًا في العمر ، إلا أن هناك فارقًا واحدًا غير واضح قد يؤدي إلى تسرب الذاكرة - إذا قمت بتشغيل تطبيق وحدة التحكم التالي (تم إنشاؤه على دلفي ، فلن يتغير شيء في حالة FPC) ، ثم في كل مرة تضغط فيها على مفتاح الإدخال ، ستنمو الذاكرة التي تستهلكها العملية:

 ... 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. 

لا توجد أخطاء في استخدام الواجهات هنا. تكمن المشكلة في أن InternetTools لا تطلق مواردها الداخلية المخصصة أثناء تحليل المستند (في طريقة OpenURL ) - يجب أن يتم ذلك بشكل صريح بعد الانتهاء من العمل معه ؛ لهذه الأغراض ، توفر وحدة مكتبة freeThreadVars إجراء freeThreadVars ، والذي يمكن ضمان استدعائه من تطبيق دلفي بشكل منطقي من خلال توسيع قائمة تصدير DLL:

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

بعد استخدامه ، سيتوقف فقدان الموارد:

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

من المهم أن نفهم ما يلي - يؤدي استدعاء FreeResources إلى حقيقة أن جميع الواجهات التي تم تلقيها سابقًا تصبح بلا معنى وأن أي محاولات لاستخدامها غير مقبولة.

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


All Articles