با سلام به همه
امروز قصد دارم مبحث مهمی رو بیان کنم که باید قبل از مقاله های قبلی راجع به اینجکت کد در پروسه های دیگه بیان میکردم
- استفاده از حلقه ها در ویژوال بیسیک و انواع آن و کاربرد آنها
همونطور که میدونید ویندوز بر پایه دریافت و ارسال پیغام هاست یعنی اینکه هر عملی که شما در حین کار کردن با سیستم انجام میدید باعث بوجود آمدن یک یا چند پیغام میشه و بهمون طریق پیغامها هم باعث انجام شدن اون عمل میشن این پیغام ها از بین اشیاء عبور میکنن و به جایی که مقصد اونهاست فرستاده میشن همچنین این پیغام ها حاوی اطلاعات خاصی از جمله آدرس فرستنده اونها هستند که مشخص کننده منبع و یا برنامه ای که اون پیغام رو فرستاده هستند اما......
اما این آموزش رو فقط نمیخوام به حلقه هایی اختصاص بدم که با پیغام ها سروکار دارند به مثال های زیر با دقت توجه کنید
کد:
PrevProc = SetWindowLong(F.hwnd, GWL_WNDPROC, AddressOf WindowProc(
کد:
hHook = SetWindowsHookEx(WH_KEYBOARD, AddressOf KeyboardProc, App.hInstance, App.ThreadID)
کد:
EnumChildWindows GetDesktopWindow, AddressOf EnumChildProc, ByVal 0&
کد:
EnumWindows AddressOf EnumWindowsProc, ByVal 0&
کد:
EnumFonts Me.hDC, vbNullString, AddressOf EnumFontProc, 0
کد:
SetTimer Me.hwnd, 0, 1, AddressOf TimerProc
خوب اگه با دقت به همه اونا دقت کنین
یک تشابه مشترک در همه اونها میبینید و اون استفاده از Keywordی هست با نام AddressOf
حالا در پایین ببینیم که این تیکه کدها در هر یک از خطهای بالا چه معنیی میده
ما با
کیورد AddressOf در واقع یک آدرس دهی میکنیم بدین صورت که تمامی این
APIها طوری طراحی شدن که به ما این امکان رو میدن که بتونیم یک سری رخداد رو در یک تابع که خودمون طراحی کردیم کنترول کنیم براتون یک مثال از یکی از موارد بالا میزنم
به مثال زیر توجه کنید که از سایت
www.all-api.net گرفتم
این رو در قسمت کد نویسی فرممون قرار میدیم
کد:
Private Sub Form_Load()
SetTimer Me.hwnd, 0, 1000, AddressOf TimerProc
End Sub
Private Sub Form_Unload(Cancel As Integer)
KillTimer Me.hwnd, 0
End Sub
و این قسمت رو هم در یک ماژول قرار میدیم
کد:
Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Public Sub TimerProc(ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long)
Debug.Print "salam"
End Sub
درست به دو قسمت کد بالا دقت کنید چون میخوام اطلاعات پایه رو حول این مثال بهتون بدم
در قسمت Form_Load ما یک حلقه با استفاده از تابع SetTimer رو ایجاد کردیم
نکته: SetTimer
تابعی هست که مثل یک تایمر در ویژوال بیسیک برای ما یک رویداد را در زمانهای مشخص اجر میکنه
ما تابع رو بصورت زیر فراخونی کردیم
کد:
SetTimer Me.hwnd, 0, 1000, AddressOf TimerProc
در کد بالا ما یک تایمر رو ایجاد کردیم که
هر 1000 میلی ثانیه یا 1ثانیه اجرا میشه
حالا چجوری باید از اون استفاده کنیم؟
نکته: طریقه ی فراخوانی هر یک از این توابع در حالت کلی با یک دیگر متفاوت است. پس نمی توان قاعده ی خاصی را برای تمامی این حلقه ها در نظر گرفت. با توجه به این گفته باید ساختار تک تک این گونه حلقه ها را در ذهن به خاطر سپرد.
اگر دقت کنید ما باز هم از
کیورد AddressOf استفاده کردیم خوب این قسمت محلی رو که رویداد قراره در فواصل زمانی مشخص اجرا میکنه یعنی ما باید
Procedureی داشته باشیم با نام
TimerProc که این تابع هر یک ثانیه اجرا بشه پس با
کیورد AddressOf تابعی
(TimerProc) که خودمان طراحی کردیم را آدرس دهی میکنیم
حالا این دوتا کد رو با هم مقایسه کنید:
کد:
Private Sub Timer1_Timer()
Debug.Print "salam"
End Sub
کد:
Public Sub TimerProc(ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long)
Debug.Print "salam"
End Sub
تابع اول مربوط به یک شیئ
Timer هست که مثلاَ هر یک ثانیه یک بار اجرا میشه و همه ی شما کما بیش با اون آشنایی دارید.
هر دوی اینها یک کار رو انجام میدن فقط اگر دقت کنید
تابع TimerProc کمی فرق داره
(منظور از فرق همون پارامترهایی است که تابع به عنوان ورودی میگیره که در تابع Timer شیئ تایمر این پارامترها وجود نداره)
خوب این فرق رو میتونیم با کاربردهایی که برنامه نویس این API برای تابع
TimerProc لازم دونسته توجیح کنیم یعنی در تابع
TimerProc ما مقادیری که میتونیم بدست بیاریم یکی هندل شیئی هست که این تایمر رو ایجاد کرده و آدرس حافظه ای که با هر بار خوندن تابع میتونیم اونو بدست بیاریم که البته اینها اصلا مهم نیستند
چیزی که مهمه اینه که این تابع هر یک ثانیه یک بار اجرا میشه و در واقع ما حلقه ای رو ایجاد کردیم که کاربردش اینه که کدهای درون تابع
TimerProc رو اجرا کنه.
در پایین میخوام براتون ساختار تک تک حلقه هایی که هر یک از توابع زیر با اونا سروکار دارن رو بهتون نشون بدم:
برای تابع
EnumChildWindows به این صورت است:
کد:
Public Function EnumChildProc(ByVal hwnd As Long, ByVal lParam As Long) As Long
EnumChildProc = True
End Function
برای تابع
EnumWindows به این صورت است:
کد:
Public Function EnumWindowsProc(ByVal hwnd As Long, ByVal lParam As Long) As Boolean
EnumWindowsProc = True
End Function
برای تابع
EnumFonts به این صورت است:
کد:
Function EnumFontProc(ByVal lplf As Long, ByVal lptm As Long, ByVal dwType As Long, ByVal lpData As Long) As Long
EnumFontProc = 1
End Function
برای تابع
SetTimer که توضیحاتش رو قبلا بهتون گفته بودم به این صورت است:
کد:
Sub TimerProc(ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long)
End Sub
چند نکته:
- تمامی توابعی که به آنها آدرس دهی کردیم که در واقع قرار است رویدادها در آنجا اتفاق بیفتد یعنی توابعی (حلقه هایی) که در بالا معرفی کردیم باید در یک ماژول تعریف شوند و این امر به دلیلی است که ساختار AddressOf ایجاب میکند.
- در حلقه هایی که کار آنها پایان ناپذیر است ما برای خارج شدن از حلقه ناگزیر به استفاده از تابعی برای این کار هستیم. برای مثال (حلقه ی TimerProc)
و در حلقه هایی که کار آنها پایان پذیر است برای خارج شدن از حلقه به تابع خاصی نیاز نداریم و به جای آن میتوانیم نام حلقه را برای ادامه ی کار برابر (1 یا True) و برای پایان کار برابر(0 یا False) قرار دهیم.
خوب دقت کنید که حلقه هایی رو که ساختارشون رو توضیح دادم حلقه هایی با کاربرد ویژه اند درواقع دو تابع معروف
SetWindowLong و
SetWindowsHookEx که توضیحی راجع به اونا نشنیدید زیر بنای اصلی حلقه ها را تشکیل میدن و به عبارتی دیگر فقط این دو تابع میتوانند حلقه هایی ایجاد کنند که پیغامهای ارسالی به پنجره ها را کنترل کنند.
تابع معروف SetWindowLong :
این تابع به کنترول گر پیام معروف هست یعنی کارایی که داره فقط در گرفتن پیام هست وهمچنین میتونه باعث جلوگیری یا عوض شدن یک پیغام قبل از رسیدن به پنجره مذکور باشه
طریقه فراخونی این تابع برای ایجاد حلقه کمی با بقیه تفاوت داره
این فرم کلی خود تابع هست
کد:
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
کار کلی این تابع در واقع ایجاد یک حالت جدید برای یک پنجره است و همچنین میتونه یک حلقه 32 بیتی در آدرس حافضه اون پنجره آدرس دهی بکنه
حالا یکی یکی به توضیح پارامترها میپردازیم:
پارامتر اول hwnd:
که مشخص کننده هندل پنجره ای هست که قراره روی اون حلقه ی آدرس دهی بشه یا اینکه حالت(Style) جدیدی براش در نظر گرفته بشه.
پارامتر دوم nIndex:
که مشخص کننده این هست که چه اتفاقی برای اون پنجره بیفته یعنی این پارامتر میتونه مقدارهای زیر رو به خودش اختصاص بده
GWL_EXSTYLE
GWL_STYLE
GWL_WNDPROC
GWL_HINSTANCE
GWL_ID
GWL_USERDATA
......
که ما در اینجا فقط با
GWL_WNDPROC کار داریم و قرار دادن
GWL_WNDPROC در
nIndex بدین معنی هست که ما قصد ایجاد یک حلقه 32 بیتی رو در آدرس حافضه پنجره داریم
پارامتر سوم dwNewLong:
و اما این پارامتر همونطور هم که از اسمش پیداست باید آدرس دهی جدید در اون صورت بگیره اما به یک نکته کاملا دقت کنید که در حلقه هایی که ما با این تابع ایجاد میکنیم حلقه صرفا نقش یک رابط را دارد پس در واقع مسیر آدرس دهی بطور مجازی عوض میشود
نکته هایی که قبلا به اونها اشاره کردم رو به خاطر بیاریید در یکی از اونها گفته شد
نقل قول: - در حلقه هایی که کار آنها پایان ناپذیر است ما برای خارج شدن از حلقه ناگزیر به استفاده از تابعی برای این کار هستیم. برای مثال (حلقه ی TimerProc)
و در حلقه هایی که کار آنها پایان پذیر است برای خارج شدن از حلقه به تابع خاصی نیاز نداریم و به جای آن میتوانیم نام حلقه را برای ادامه ی کار برابر (1 یا True) و برای پایان کار برابر(0 یا False) قرار دهیم.
با توجه به نکته بالا و کاربردی که این حلقه دارد یعنی دریافت پیغام های ارسالی به پنجره مذکور
در واقع این کار
(دریافت پیغامها) تا آخر ادامه دارد پس برای اینکه از حلقه خارج شویم یا بخواهیم برنامه ای را که حلقه در آن قرار دارد ببندیم باید از یک تابع کمکی برای خارج شدن از حلقه استفاده کنیم
یه مثال از کاربرد این تابع میزنم و حول همین مثال هم جلو میرم
فرض کنید یک حلقه روی یک
TextBox که در فرم اصلی مون قرار داره ایجاد کنیم که وقتی ما از
چرخک بالا و پایین موس(Wheel Down-Wheel UP) که
در ویژوال بیسیک به طور مستقیم قابل دسترس نیست استفاده کنیم این حلقه آن را تشخیص دهد و به صورتی خاص آن را به ما نمایش دهد
البته این مثال حول قسمت کدنویسی راحتتر قابل درک خواهد بود
خوب بازم مثل موارد قبل ما شروع به ایجاد حلقه مون میکنیم
با استفاده از خط زیر:
کد:
OldProc = SetWindowLong(Text1.hWnd, GWL_WNDPROC, AddressOf TextBoxProc)
در خط بالا ما مسیری رو که پیغام ها از اون به
TextBox می رسن رو ردیابی کردیم
(البته به صورت مجازی) و میتونیم بفهمیم که چه پیغام هایی به حلقه ما فرستاده میشه
خوب بازم یک تابع برای حلقه خودمون در نظر گرفتیم که در اینجا
TextBoxProc نام داره که پیغام ها به اونجا هدایت میشن
اما اگر دقت کنید تابعی که در خط بالا فراخونی شده یک مقدار بازگشتی هم داره که اونو در متغیر
OldProc ذخیره کردیم در واقع این مقدار آدرس پیش فرض قبلی برای درگاه پیغام هاست که زمانی بدرد میخوره که ما بخواییم از حلقه خودمون خارج بشیم و با دادن آدرس قبلی مسیر عبور پیغامها رو به روال خودش برمیگردونیم بزارید به زبان خیلی ساده اینجوری توضیح بدم که ما با ایجاد این حلقه یک درگاه در مسیر پیغام ایجاد میکنیم که پیغام رو مجبور به رد شدن از اون مسیری میکنیم که خودمون خواستیم حالا اگر بخواییم مسییر رو به حالت قبلی برگردونیم باید از آدرسی که در متغیر
OldProc ذخیره شده استفاده کنیم
اما در قسمت حلقه مون ببینیم که چه چیزایی داریم:
کد:
Public Function TextBoxProc(ByVal wHwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If wMsg = WM_MOUSEWHEEL Then
Form1.Text1 = IIf(wParam < 0, "Wheel Down", "Wheel UP")
End If
TextBoxProc = CallWindowProc(OldProc, wHwnd, wMsg, wParam, lParam)
End Function
در خطی که ما از
شرط If استفاده کردیم منظور اینه که اگه از
Mouse Wheel استفاده شد یک سری عملیات رو انجام بده
اون عملیات اینه که ما از
Wheel Down استفاده میکنیم یا از
Wheel UP که اونم بسیار روشنه که چگونه تشخیص داده شده
در خط یکی مانده به آخر ما از یک تابع استفاده کردیم با نام
CallWindowProc که بسیار نقش مهمی رو در این حلقه بازی میکنه در واقع وظیفه این تابع اینه که پیغامی رو که توسط این درگاه دریافت شده به مقصد بفرسته که اگه این تابع موجود نباشه پیغام مذکور
هیچ گاه به مقصد نمیرسه و البته یک استثناهایی هست مثلا میشود با دستور
End Function در حلقه مان از رسیدن یک پیغام خاص به پنجره حلقه گذاری شده جلوگیری کنیم اما این کار زمانی امکان پذیر است که تعداد پیغام هایی که فیلتر میشوند زیاد نباشد و در صورت زیاد شدن یا کلی شدن فلتر پیغام ها برنامه بسرعت قفل میکنه و در اکثر موارد بسته هم میشه
خوب آخرین مرحله ای که باقی مونده اینه که بتونیم از حلقه خارج بشیم
همون طور که گفتم برای رهایی از حلقه ایجاد شده و برگردوندن مسیر قبلی که مسیر طی میکرد باید با استفاده از یک تابع مسیر رو عوض کرد و مطمئنا بعد از این کار حلقه ای هم وجود نخواهد داشت و اصولا از بین رفته محسوب میشود برای این کار ما از همان تابع قبلی استفاده میکنیم به این صورت:
کد:
SetWindowLong Text1.hWnd, GWL_WNDPROC, OldProc
همون جور که میبینید ما در قسمتی که آدرس دهی انجام میدادیم آدرس قبلی رو وارد کردیم با این کار حلقه خود به خود از بین میرود
البته آموزش ایجاد حلقه ها با استفاده از
SetWindowsHookEx هم موند برای سری بعد
برای تمامی حلقه ها یک پروژه آماده کردم که میتونید آموزش ها رو با توجه به اونها طی کنید
در ضمن برای تابع SetWindowLong هم دو مثال گذاشتم که مثال (SetWindowLong(Hook TextBox همونی هست که راجع بهش توضیح دادم اما مثال دیگه ای که برای این تابع آوردم یک برنامه دریافت کننده و یک برنامه فرستنده پیام هست که ابتدا هر دو رو کمپایل کنید و بعد اونها رو اجرا و استفاده کنید
(البته این مثال رو قبلا زده بودم که فک کنم پاک شد)
..............................................
EnumChildWindows
EnumFonts
EnumWindows
SetTimer
SetWindowLong(Hook TextBox)
SetWindowLong(Sender & Reciever)
..............................................