بسم الله الرحمن الرحيم
طريقة إنجاز
وحدة
DLL
فى لغة الدلفي
مقدمة:
وحدة
DLL
تختلف عن بقية وحدات دلفى، سنرى فى هذا الدرس أوجه
الإختلاف وما هى الأمور التى يجب مراعاتها عند بناء هذه
الوحدة، وأنواع الدوال والإجراءات فى
DLL ،إضافة
إلى موضوع مهم وهو العلاقة بين
DLL وبعض متغيرات النظام
Windows
العامة .
تحليل
وحدة
DLL
:
كأي
وحدة من وحدات Pascal
الأخرى فإن DLL Unit
لها شكلها المستقل ، ولدراسة الشكل العام لهذه الوحدة
لنستخدم المثال التالي :
library TestDLL;
uses
SysUtils,
Classes,
Forms,
Windows;
procedure SayHello(AForm : TForm);
begin
MessageBox(AForm.Handle, `Hello From a DLL!',
`DLL Message Box', MB_OK or MB_ICONEXCLAMATION);
end;
exports
SayHello;
begin
end.
1-الكلمة
المحجوزة
Library : تستخدم فى بداية
الوحدة ، ويليها إسم الوحدة وهو يخضع لاختيارك الشخصي
هنا (TestDLL)
أو أي تسمية أخرى تخضع لشروط التسمية فى
Pascal .
2-الكلمة
المحجوزة
Uses:
تأتي هذه الكلمة متبوعة بأسماء الوحدات المراد تضمينها
فى هذه الوحدة.
3-الدوال
والإجراءات : قائمة بتعريفات
الدوال والإجراءات التى تحتويها وحدة
DLL .
4-الكلمة
المحجوزة
Exports:
وتأتي متبوعة بالدوال والإجراءات المصدرة (من بين الدوال
والإجراءات التى سبق تعريفها) إلى خارج وحدة
DLL .
5-الكلمتان
المحجوزتان
Begin و
End
: يوضع بينهما كتلة الكود الرئيسي لل
DLL ، حيث
تضع هنا أي كود تحتاج لتنفيذه عند لحظة تحميل
DLL ، فى
معظم الحالات أنت لا تحتاج لأي كود استهلالي وتكتفي
بالدوال والإجراءات وبالتالى تبقى هذه الكتلة فارغة .
ملاحظة:
أستخدم كلمة تعريف دالة أو
إجراء للتعبير عن جسم الدالة أو الإجراء كاملاً ،
بينما أستخدم تصريح دالة أو
إجراء للتعبير عن ترويستهما فقط ،كما سأستخدم
التعبير كتلة الكود الرئيسى
للتعبير عن كتلة الكود بين Begin
و End
في ال DLL
.
أساسيات
كتابة
DLL
:
إن
كتابة DLL
ليست عملية صعبة ويمكن اعتبارها كبرمجة
Object Pascal
المعتادة ، لكن هناك عدة أمور هامة يجب عليك إدراكها
،بعدها يمكنك كتابة أول DLL
.
الدوال
والإجراءات فى DLL
: تنقسم الدوال (Functions)
والإجراءات (Procedures)
فى DLL
إلى قسمين :
1-دوال وإجراءات محلية لل DLL
.
2-دوال وإجراءات مصدرة إلى خارج ال
DLL .
أولاً/ الدوال والإجراءات المحلية:
هى
الدوال والإجراءات التى سيتم استدعاؤها داخل ال
DLL نفسه .
هى لا تتطلب معالجة خاصة عند إنجازها ،كل ماعليك فعله هو
تعريف هذه الدوال والإجراءات كأي دالة أو إجراء آخر فى
Object Pascal
.
ويتم
استدعاؤها من قبل دوال وإجراءات أخرى داخل ال
DLL ، لكن لا
يمكن استدعاؤها من خارج ال DLL
، بكلمات أخرى التطبيقات المستدعية لا يمكنها الوصول إلى
هذه الدوال والإجراءات وهى تكون
Private لل
DLL .
ملاحظة:
سيتكرر استخدامي للتعبير
التطبيقات المستدعية (Calling
Applications) وأقصد به تلك
التطبيقات التى تستدعى دالة أو إجراء من دوال وإجراءات
ال DLL
.
تلميح:إضافة
للدوال والإجراءات فإن DLL
يمكن أن يحوى بيانات عامة
Global Data
تستطيع كل الإجراءات والدوال الموجودة فى
DLL الوصول
إليها.
فى
Windows
16_bit فإن البيانات العامة
فى DLL
مشتركة فيما بين كل مكونات DLL،فإذا
قام برنامج ما بتغيير قيمة المتغير العام
X مثلا إلى
100 فإن X
سيمتلك القيمة 100 بالنسبة لكل التطبيقات التى تستخدم
هذا ال DLL
فى
Windows
32_bit
الأمر مختلف حيث أن كل إجراء يصل إلى
DLL يُبنى
له نسخة مستقلة من البيانات العامة .
ثانياً/الدوال والإجراءات المصدَّرة من
DLL :
النوع الآخر من الإجراءات والدوال هو النوع الذي يمكن
استدعاؤه من خارج DLL
،وهى تمثل الإجراءات والدوال المبنية
Public
بواسطة تصديرها من DLL
حيث يمكن أن تستدعى بواسطة الدوال والإجراءات الموجودة
فى DLL
أو بواسطة تطبيقات خارج DLL
أو بواسطة دوال وإجراءات موجودة فى
DLLs الأخرى.
ولتصدير الدوال والإجراءات تستخدم الكلمة المحجوزة
Exports
وهذا هو الفرق بينها وبين الدوال والإجراءات المحلية لل
DLL .
الكلمة
المحجوزة
Exports
:
كما
قلنا سابقا لتصدّر دالة أو إجراء إلى خارج
DLL فإنك
تستخدم الكلمة المحجوزة Exports
، أنظر إلى مثالنا في بداية الدرس ،لاحظ كيف قمنا بتعريف
الدالة SayHello
على أنها مصدّرة ،وبالتالى يمكن استدعاؤها من أي تطبيق
دلفي آخر .
ولتصدير دالة أو إجراء هناك طريقتين :
1-التصدير باستخدام الإسم :
هى الطريقة الإعتيادية لتصدير الدالة أو الإجراء من
DLL
مثلاً :
exports
SayHello,
DoSomething,
DoSomethingReallyCool;
هذه
الدوال صدّرت باستخدام إسم التعريف لها وهو الإسم الوارد
فى تعريف الدالة السابق ،ونلاحظ أن جزء
Exports له
نفس ( Syntax
) قاعدة القائمة Uses
حيث أنه فصل بين المعرفات باستخدام فاصلة عادية ووضعت
فاصلة منقوطة بعد آخر معرّف في القائمة .
2-التصدير
باستخدام قيمة الترتيب :
يمكن أيضاً تصدير الدوال والإجراءات بواسطة قيمة ترتيبها
،وتستخدم لذلك الكلمة المحجوزة
Index مثال :
exports
SayHello index 1,
DoSomething index 2,
DoSomethingReallyCool index 3;
عندما تقوم باستيراد الدالة فى التطبيق المستدعى عليك تحديد قيمة الترتيب
عندما تقوم باستيراد الدالة فى التطبيق المستدعى عليك
تحديد قيمة الترتيب (Ordinal number)
فى
معظم الحالات يتم استخدام الإسم لتصدير الدالة.
ملاحظة:
فى حالة عدم تحديدك لقيمة ترتيب الدوال والإجراءات
الموجودة فى جزء Exports
فإن دلفى تقوم أوتوماتيكياً بإسناد قيمة ترتيب لكل إجراء
أو دالة فى هذا الجزء ،لكن استخدامك
Index يوفر
لك تحكماً فى قيمة Ordinal
Number التى تضعها حسب رغبتك.
تصدير الدالة أو الإجراء هو نصف
القصة ،بقي النصف الآخر وهو طريقة استيراد الدالة أو
الإجراء من قبل التطبيق المستدعى (سنخوض هذا القسم فى
الدرس القادم) وسنعرج هنا على بعض المواضيع التى نحتاج
لفهمها قبل دراسة القسم المذكور.
العلاقة بين
DLLs
ومتغيرات النظام
Windows
:
هذا الجانب
يعد مهماً لكنه معقد بعض الشيء ، للنظام
Windows
عدد من المتغيرات العامة المتعلقة بDLL
والتى يمكن رؤيتها من تطبيقك أو من
DLL
وهي:
1-Islibrary:
يحدد هذا المتغير ما إذا كان كودك ينفذ في
DLL
أو فى التطبيق ،وقيمته دائماً
False
فى التطبيق و
True
فى
DLL
.
2-HInstance:
يحتوى قيمة (Instance
handle)
ممسك
DLL
،الممسك هو أحد أنواع البيانات المعرفة فى الوحدة
Windows
وهى تنفذ كأرقام ،لكنها لا تستعمل كذلك ولكنها إشارة إلى
بنية بيانات داخلية للنظام ،مثلاً عندما تتعامل مع نافذة
أو نموذج (Form)
فإن النظام يعطيك ممسك للنافذة ،النظام يخبرك أن النافذة
التى تتعامل معها هى نافذة رقم 142 مثلاً ، وبالتالي
فالممسك هو توليف داخلي يمكنك استعماله لتشير به إلى
عنصر معين يتم مناولته من قبل النظام ،وهذا الأمر ينطبق
على DLL
حيث أنه كما قلنا المتغير العام
Hinstance
يحتوى ممسك ال
DLL
.
3-DLLProc
: (هذا أكثر المتغيرات
أهمية)، كما لاحظت عند تحليلنا لوحدة
DLL فإنها لا
تحتوي على جزء initialization
أو جزء
finalization
عكس بقية الوحدات الأخرى فى باسكال ،إذاً ماذا يمكنك
أن تفعل لتجعل كود معين ينفذ عند لحظة تحميل
DLL ؟
،أو عند إلغاء تحميله ؟، أو عند تصدير المعالجات أو
استلامها من DLL
محمل فى الذاكرة (هنا حالة استخدام
DLL
من قبل عدة تطبيقات فى ذات الوقت ،أو استخدامه من عدة
مواضع فى ذات التطبيق) ؟
للقيام
بذلك يمكنك حجز ذاكرة فى جزء كتلة الكود الرئيسي لل
DLL
وإجراء المعالجة التى ترغب بها ومن ثم تحرير هذه الذاكرة
، للتحكم بكل ذلك توفر لك
Windows
المتغير العام
DLLProc
.
إن
DLLProc
هو متغير من نوع إجراء (وبتحديد
أكثر مؤشر إلى إجراء).
رغم
أن شرح نوع إجراء خارج موضوعنا ،لكني سأفترض عدم إحاطتك
به ،وبالتالى أنا بحاجة لشرحه لأمكنك من فهم هذا المتغير
DLLProc
،إذا كان النوع إجراء معروفاً بالنسبة لك فبإمكانك تخطى
هذه الجزئية.
ماهو
المتغير من النوع الإجرائي ؟
من
الميزات الفريدة فى Object
Pascal هى وجود الأنواع
الإجرائية Procedure Types
،وهو موضوع برمجي متقدم ( يشبه مفهوم مؤشر الوظيفة
Function Pointer
فى لغة C
) .
تعريف النوع الإجرائي يشير إلى قائمة من المحددات ونوع
المرجوع فى حالة الدوال ،مثلاً يمكنك تعريف نوع إجراء مع
محدد برقم صحيح يتم تمريره بالإشارة مثل :
Type
Intproc = procedure (var num :
integer);
-Intproc
:النوع الجديد الذى تقوم بتعريفه تليه علامة المساواة.
-الكلمة المحجوزة Procedure
فى حالة الإجراء ،أو الكلمة
المحجوزة Function
فى حالة الدالة .
-قائمة المحددات وأنواعها ،يمكنك تمرير المحددات بشكل
اعتيادى كما فى أى إجراء أو دالة فى باسكال.
-فى
حالة الدالة عليك ذكر نوع مرجوعها، مثلاً
Type
sumfunc = function (num1,num2 : integer):integer;
ملاحظة:
يمكنك تعريف متغير من نوع إجراء مباشرة فى جزء
var دون
الحاجة إلى تعريف نوع عنه مثل:
var
F: function (X: Integer): Integer;
بالعودة إلى النوع intproc الذي عرفناه سابقاً نقول أنه
متوافق مع أي إجرائية تملك نفس المحددات ،يمكنك تمرير
المحددات بشكل اعتيادي كما فى أي إجراء أو دالة فى
باسكال ،وهذا مثال لإجرائية متوافقة مع نوع إجراء
السابق:
;(Procedure
DoubleTheValue (var value : integer
Begin
Value:=value*2 ;
End ;
ويمكنك استخدام نوع إجراء السابق كما يلي :
Var
IP: intproc ;
X : integer ;
Begen
IP:= DoubleTheValue ;
X:= 5 ;
IP(X);
لمعرفة المزيد
عن النوع الإجرائي بإمكانك مراجعة "
procedural types
" فى مساعدة دلفى.
والآن
لنعود إلى موضوعنا وهو DLLProc
، قلنا أن DLL
يستلم رسائل من النظام Windows
عند لحظة تحميله إلى الذاكرة، وفى لحظة إلغاء تحميله ،أو
فى لحظة تصدير المعالجات أو استلامها من
DLL المستخدم
من قبل عدة تطبيقات . لالتقاط هذه الرسائل وفحصها ووضع
خدمات بناءً عليها فإنك يجب أن تبني إجراء بشكل خاص
وتسند عنوان هذا الإجراء إلى المتغير العام
DLLProc
،والشكل النموذجي للإجراء الذي يمكن أن يشير إليه
المتغير DLLProc
كالتالي :
procedure MyDLLProc(Reason: Integer);
begin
if Reason = DLL_PROCESS_DETACH then
{ DLL is unloading. Cleanup code here. }
end;
بعد
كتابة الإجراء داخل DLL
كأي إجراء آخر عليك أن تسند عنوان الإجراء إلى المتغير
العام DLLProc
وذلك فى جزء كتلة الكود الرئيسي لل
DLL ،مثلاً :
begin
DLLProc := @MyDLLProc;
{ More initialization code. }
end.
هذا
الكود سيستدعى أوتوماتيكيا فى الحالات الثلاث التى
ذكرتها سابقاً ، الباراميتر
Reason الذي هو المحدد الوحيد
للإجراء( المؤشَّر عليه بالمتغير
DLLProc )
قيمة هذا الباراميتر تحدد أي الحالات الثلاث حدثت ،وكما
لاحظت فى الشكل النموذجى لإجراء
DLLProc فإنه قد تم فحص
الباراميتر Reason
ومقارنته بقيمة معينة ، وإليك الحالات الثلاث لهذا
الباراميتر:
-DLL_PROCESS_DETACH:
وهى الرسالة التى يمكن أن تُستلم مرة واحدة ضمن حياة
DLL
وهى لحظة إلغاء تحميل DLL
من الذاكرة (Unload)،
مثل حالة استدعاء FreeLibrary
(سنراها لاحقاً) .
-كلا من
DLL_THREAD_ATTACH
و DLL_
THREAD _DETACH:
وهما
تستلمان من قبل DLL
عدة مرات خلال حياته (وجوده
بالذاكرة) عندما يكون DLLL
مُستخدماً من قبل عدة معالجات سواءً عدة تطبيقات أو
تطبيق واحد يملك عدة مسالك لاستخدام
DLL (عدة
إجراءات مثلاً)،فالرسالة الأولى تشير إلى أن المعالجة
الحالية قد بنت مسلكاً جديداً ،أما الرسالة الثانية فهى
على عكسها تشير إلى أن المعالجة الحالية قد أنهت
استعمالها لل DLL
.
ملاحظة:
طبقاً لما سبق قد تتساءل لماذا لا يتم فحص حالة بدء
تحميل DLL
وهى الرسالة DLL_PROCESS_ATTACH
؟ هذا فى الحقيقة لا يحدث والسبب هو أن
Windows يعرف
هذه الرسالة لكن Object Pascal
لا تمررها إلى DLLProc
، بدلاً عن ذلك فإن Object
Pascal تستدعى جزء الكود
الرئيسي الموجود فى DLL
عند لحظة استلامها للرسالة
DLL_PROCESS_ATTACH من
النظام Windows
،وهذا يغني عن تمرير هذه الرسالة إلى
DLL ،حيث يتم
وضع أي كود استهلالي فى جزء الكود الرئيسي لل
DLL بين
كلمتي Begin
و End
.
مثال على استخدام
DLLProc :
library TestDLL;
uses
SysUtils,
Classes,
Forms,
Windows;
var
SomeBuffer : Pointer;
procedure MyDLLProc(Reason: Integer);
begin
if Reason = DLL_PROCESS_DETACH then
{ DLL is unloading. Cleanup code here. }
FreeMem(SomeBuffer);
end;
procedure SayHello(AForm : TForm);
begin
MessageBox(AForm.Handle, `Hello From a DLL!',
`DLL Message Box', MB_OK or MB_ICONEXCLAMATION);
end;
{ More DLL code here that uses SomeBuffer. }
exports
SayHello;
begin
{ Assign our DLLProc to the DLLProc global variable. }
DLLProc := @MyDLLProc;
SomeBuffer := AllocMem(1024);
end.
بإلقاء نظرة عامة على المثال السابق نلاحظ تعريف متغير
عام SomeBuffer
من النوع مؤشر واستخدم لحجز ذاكرة فى كتلة الكود الرئيسي
(لا تنسى أن كتلة الكود الرئيسي تنفذ فى لحظة تحميل
DLL)
يمكن لل DLL
استخدام هذه الذاكرة لمعالجاته المختلفة ،والنقطة الهامة
هى لحظة تحرير هذه الذاكرة كما سبق وعرفنا أن هذه
الذاكرة تحرر ضمن الإجراء المؤشر عليه بالمتغير العام
DLLProc
فى مثالنا أسم الإجراء
MyDLLProc الذى يشترط
لتحريرها أن يستلم رسالة تفيد بأن ال
DLL هو الآن
قيد إلغاء التحميل (Unload)التى
يحددها الباراميتر Reason
عبر القيمة DLL_PROCESS_DETACH
.
الخلاصة:
فى
هذا الدرس قمنا بتحليل الشكل العام لل
DLL ورأينا
الفرق بينه وبين باقى وحدات دلفى ، ثم درسنا ما أعتبره
أكثر المواضيع المتعلقة بال DLL
تعقيدا وهو كيفية إدارة العلاقة بين
DLL والتطبيق
المستدعى (هنا تطبيق دلفى) وكيف يقوم النظام
Windows
بإدارة هذه العلاقة باستخدام متغيراته العامة المختلفة،
تلك المتغيرات التى لا نحتاج للإتصال بها فى معظم
التطبيقات تعد هامة هنا ،وأهمها المتغير
DLLProc
،والسبب لكل هذا هو أن DLL
ليس تطبيقا مستقلا يُحمّل ويلغى من الذاكرة بذاته ،وإنما
يضل مرتبطا بالتطبيقات المستدعية مما اضطرنا لاستخدام
متغيرات النظام Windows
لنعرف ما يجرى فى هذه اللحظة وما
يجب أن يكون بناء على تصرفات التطبيقات المستدعية
المختلفة.
المراجع
نفس
المراجع المذكورة فى الدرس الأول لل
DLL .
والله الموفق ... (خ.غ) |