فصل ۲۰ — فراخوانیهای API
با اینکه Access Object Model و VBA برای بسیاری از عملیات Database بسیار جامعاند، برخی Functionهای مربوط به خود Windows مستقیماً در VBA وجود ندارند. حتی Visual Basic نیز برای بعضی از این کارها Command مستقیم ارائه نمیکند. برای مثال با VBA عادی نمیتوانید فضای آزاد یک Disk Device را مستقیماً بخوانید، Keyboard را مستقل از Focus یک Form/Control بررسی کنید یا Position فعلی Mouse را بهطور مستقیم به دست آورید.
Windows خودش این اطلاعات را دارد؛ Windows Explorer فضای Disk را نشان میدهد و OS میداند چه Keyای فشار داده شده است. VBA برای چنین کارهایی اجازهٔ دسترسی به WIN32 API یا Application Programming Interface را میدهد؛ در نتیجه میتوان مجموعهٔ بزرگی از Functionalityهای Windows را از Code فراخوانی کرد.
API Call موضوعی پیشرفته است و قابلیت بسیار زیادی به برنامه میافزاید. هدف این فصل پوشش کامل APIها نیست، بلکه نشاندادن چند نمونهٔ عملی است.
API Call چیست؟
API امکان دسترسی به Programming Functionهای داخلی DLL و EXEها، بهخصوص فایلهایی که Windows را پشتیبانی میکنند، فراهم میکند. Applicationهای Third-party نیز ممکن است Function Libraryهای خود را در DLL ارائه دهند و اگر Documentation مناسب موجود باشد میتوان آنها را نیز از VBA فراخوانی کرد.
API Callها غالباً Functionهایی هستند که Value برمیگردانند و در عین حال Action نیز انجام میدهند؛ بعضی نیز Subroutine هستند و فقط Action دارند. پیش از استفاده، باید Function/Subroutine موردنظر را با Statementِ Declare تعریف کنید. این دشوارترین بخش کار است، زیرا Declaration مشخص میکند DLL کدام است، Function چه نامی دارد و Parameterها چگونه و با چه Typeهایی پاس داده شوند.
Declaration باید دقیق باشد. اشتباه در Name، Type یا Parameter میتواند Call را Fail کند و حتی System را Crash دهد. کتاب توصیه میکند پیش از API Call فایل خود را Save کنید، زیرا در صورت Crash ممکن است مجبور به Reboot شوید و Data ذخیرهنشده از دست برود. APIهای معیوب همیشه با CTRL+BREAK متوقف نمیشوند و حتی ارسال Value با Type اشتباه میتواند Crash ایجاد کند. با این حال، اگر درست استفاده شوند Functionalityهایی را به VBA میآورند که معمولاً در Access موجود نیست.
بهدستآوردن فضای Disk
ابتدا Declaration را در Declarations Section بالای Module قرار دهید:
Private Declare Function GetDiskFreeSpaceEx Lib "kernel32" _
Alias "GetDiskFreeSpaceExA" (ByVal lpDirectoryName As _
String, lpFreeBytesAvailableToCaller As Currency, _
lpTotalNumberOfBytes As Currency, lpTotalNumberOfFreeBytes _
As Currency) As Long
این Statement Reference به kernel32.dll ایجاد و نحوهٔ Parameterها و Return Type را مشخص میکند. متن کتاب اشاره میکند Declarationهای API در منابع Visual Basic مانند API32.TXT و Documentationهای توسعهدهنده قابل یافتناند؛ Declaration باید عیناً و دقیق باشد.
سپس Function را فراخوانی کنید:
Sub Test_Api()
Dim x as Double
Dim FreeBytesAvailableToCaller As Currency, TotalNumberOfBytes As _
Currency, TotalNumberOfFreeBytes As Currency
x = GetDiskFreeSpaceEx("c:\", FreeBytesAvailableToCaller, _
TotalNumberOfBytes, TotalNumberOfFreeBytes)
MsgBox "Total Space " & Format(TotalNumberOfBytes * 10000, "#,##0")
MsgBox "Free Space " & Format(TotalNumberOfFreeBytes * 10000, "#,##0")
End Sub
Variableها Valueهای بازگشتی API را نگه میدارند. Value برگشتی اصلی در x وضعیت Error/Success را مشخص میکند و مقدار 1 نشانهٔ Success است. Root Directory برابر C:\ به Function داده میشود. طبق توضیح کتاب، Valueهای Byte به دلیل شیوهٔ Return شدن توسط API در 10,000 ضرب و برای نمایش با Comma Format میشوند.
شکل ۲۰-۱ — نمایش Total Space و Free Space با GetDiskFreeSpaceEx
میتوانید Result را با Properties درایو C در Windows Explorer مقایسه کنید؛ Total Space و Free Space باید با Output Code مطابقت داشته باشند.
خواندن و نوشتن INI File
API Callها برای Read/Write فایلهای INI نیز قابل استفادهاند. INIها تا حد زیادی با Windows Registry جایگزین شدهاند، ولی هنوز روش سادهای برای نگهداری Program Settingها هستند. Registry نیز API دارد، اما تغییر آن بدون شناخت کافی خطرناک است و اشتباه میتواند Application یا حتی Windows را خراب کند.
INI File میتواند Settingهای «Sticky» مانند Path انتخابی User را نگه دارد. Variable و Propertyهای عادی VBA پس از بستهشدن برنامه از بین میروند؛ بنابراین اگر User Path خاصی را در TextBox وارد کند، بدون Persistence بار بعد به Default برمیگردد. INI این مقدار را روی Disk حفظ میکند.
دو Declaration مورد استفاده در مثال:
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias _
"GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal _
lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As _
String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Private Declare Function WritePrivateProfileString Lib "kernel32" Alias _
"WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal _
lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As _
Long
اینها نیز در Declarations Section قرار میگیرند و به kernel32.dll اشاره میکنند. استفاده:
Sub Test_INI()
x = WritePrivateProfileString("Parameters", "Path", "C:\temp\", "myini.ini")
s$ = Space$(256)
x = GetPrivateProfileString("Parameters", "Path", "", s$, 256, "myini.ini")
MsgBox s$
MsgBox x
End Sub
خط نخست در INI مینویسد و اگر File وجود نداشته باشد ساخته میشود. Location پیشفرض در مثال Windows Directory است، مگر Filename شامل Path شود. Parameter اول Section، دومی Key Name، سومی Value و چهارمی Filename است. اگر Key Name در Write برابر Null باشد Keyهای Section حذف میشوند؛ اگر Value برابر Null باشد String موجود برای همان Key حذف میشود.
Suffix فایل الزاماً .ini نیست. کتاب حتی اشاره میکند میتوان Extension دیگری برای پنهانماندن Setting File در نگاه سطحی انتخاب کرد. File ایجادشده در مثال چنین محتوایی دارد:
[Parameters]
Path=C:\temp\
برای Read، ابتدا Space$(256) Stringی با 256 Space ایجاد میکند تا Buffer مقصد فراهم شود. Variable باید String باشد چون Return Data به شکل String است. GetPrivateProfileString Section و Key را میگیرد، سپس Default Value در صورت نبود Entry، Buffer مقصد، Maximum Character Count و Filename را دریافت میکند.
x تعداد Characterهای برگشتی و s$ String نتیجه را که با Null Character خاتمه دارد نگه میدارد. اگر KeyName برابر Null باشد، همهٔ Entryهای Section با Null Separator بازگردانده میشوند. این APIها نسبتاً Forgiving هستند: Write در نبود File یا Key آن را میسازد؛ Read در نبود File Value خالی/Null برمیگرداند. از این روش برای بازیابی Settingهای شخصی User در اجرای بعدی برنامه استفاده میشود. متن کتاب یادآوری میکند Windows جدید بهطور اصلی Registry را برای چنین Settingهایی به کار میبرد.
خواندن فعالیت Keyboard
Eventهای Form فقط زمانی Keyboard را میبینند که Form یا Control مربوط Focus داشته باشد. اگر Macro فرایند طولانی با هزاران Iteration اجرا کند، ممکن است بخواهید User Key خاصی برای توقف اضطراری داشته باشد؛ در چنین حالتی میتوان از GetKeyState استفاده کرد.
Declaration:
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) _
As Integer
نمونه:
Sub Test_Key()
x = 0
Do Until x = 1
If GetKeyState(&H9) < 0 Then x = 1
DoEvents
Loop
MsgBox "You pressed the TAB key"
End Sub
Loop تا زمانی که x برابر 1 شود ادامه دارد. Virtual Key Code برابر Hex 09 مربوط به TAB است. وقتی TAB فشرده شود Return Value کمتر از صفر میشود، x به 1 تغییر میکند و Loop پایان مییابد.
DoEvents در این مثال مهم است؛ به Operating System فرصت میدهد Messageهای Pending را Process کند. بدون آن ممکن است Keyboard Message پیش از Iteration بعدی پردازش نشود و Keypress از دست برود.
Table 20-1 در کتاب Virtual Key Codeهای اصلی را فهرست میکند:
| Symbolic Constant | Hex | Mouse/Keyboard |
| VK_LBUTTON | 01 | Left mouse button |
| VK_RBUTTON | 02 | Right mouse button |
| VK_CANCEL | 03 | CTRL+BREAK |
| VK_MBUTTON | 04 | Middle mouse button |
| — | 05–07 | Undefined |
| VK_BACK | 08 | BACKSPACE |
| VK_TAB | 09 | TAB |
| — | 0A–0B | Undefined |
| VK_CLEAR | 0C | CLEAR |
| VK_RETURN | 0D | ENTER |
| — | 0E–0F | Undefined |
| VK_SHIFT | 10 | SHIFT |
| VK_CONTROL | 11 | CTRL |
| VK_MENU | 12 | ALT |
| VK_PAUSE | 13 | PAUSE |
| VK_CAPITAL | 14 | CAPS LOCK |
| — | 15–19 | Reserved for Kanji systems |
| — | 1A | Undefined |
| VK_ESCAPE | 1B | ESC |
| — | 1C–1F | Reserved for Kanji systems |
| VK_SPACE | 20 | SPACEBAR |
| VK_PRIOR | 21 | PAGE UP |
| VK_NEXT | 22 | PAGE DOWN |
| VK_END | 23 | END |
| VK_HOME | 24 | HOME |
| VK_LEFT | 25 | LEFT ARROW |
| VK_UP | 26 | UP ARROW |
| VK_RIGHT | 27 | RIGHT ARROW |
| VK_DOWN | 28 | DOWN ARROW |
| VK_SELECT | 29 | SELECT |
| — | 2A | OEM-specific |
| VK_EXECUTE | 2B | EXECUTE |
| VK_SNAPSHOT | 2C | PRINT SCREEN |
| VK_INSERT | 2D | INS |
| VK_DELETE | 2E | DEL |
| VK_HELP | 2F | HELP |
| VK_0 … VK_9 | 30–39 | 0 … 9 |
| — | 3A–40 | Undefined |
| VK_A … VK_Z | 41–5A | A … Z |
| VK_LWIN | 5B | LEFT WINDOWS |
| VK_RWIN | 5C | RIGHT WINDOWS |
| VK_APPS | 5D | APPLICATIONS |
| — | 5E–5F | Undefined |
| VK_NUMPAD0 … VK_NUMPAD9 | 60–69 | Numeric keypad 0 … 9 |
| VK_MULTIPLY | 6A | MULTIPLY |
| VK_ADD | 6B | ADD |
| VK_SEPARATOR | 6C | SEPARATOR |
| VK_SUBTRACT | 6D | SUBTRACT |
| VK_DECIMAL | 6E | DECIMAL |
| VK_DIVIDE | 6F | DIVIDE |
| VK_F1 … VK_F12 | 70–7B | F1 … F12 |
| VK_F13 … VK_F24 | 7C–87 | F13 … F24 |
| — | 88–8F | Unassigned |
| VK_NUMLOCK | 90 | NUM LOCK |
| VK_SCROLL | 91 | SCROLL LOCK |
| VK_LSHIFT | A0 | LEFT SHIFT |
| VK_RSHIFT | A1 | RIGHT SHIFT |
| VK_LCONTROL | A2 | LEFT CTRL |
| VK_RCONTROL | A3 | RIGHT CTRL |
| VK_LMENU | A4 | LEFT MENU |
| VK_RMENU | A5 | RIGHT MENU |
| — | E7–E8 | Unassigned |
| — | E9–F5 | OEM-specific |
| VK_ATTN | F6 | ATTN |
| VK_CRSEL | F7 | CRSEL |
| VK_EXSEL | F8 | EXSEL |
| VK_EREOF | F9 | ERASE EOF |
| VK_PLAY | FA | PLAY |
| VK_ZOOM | FB | ZOOM |
| VK_NONAME | FC | Reserved for future use |
| VK_PA1 | FD | PA1 |
| VK_OEM_CLEAR | FE | CLEAR |
پخش صدای Multimedia
با API میتوان WAV File پخش کرد. زبان Macro قدیمی Commandی برای این کار داشت، اما در VBA و حتی Visual Basic کلاسیک Command مستقیم مشابهی وجود ندارد. Declaration:
Public Declare Function PlaySound Lib "winmm.dll" Alias "PlaySoundA" _
(ByVal lpszName As String, ByVal hModule As Long, ByVal dwFlags As _
Long) As Long
نمونه:
Sub test_sound()
x = PlaySound("c:\windows\media\windows exclamation.wav", 0, 0)
x = PlaySound("c:\windows\media\tada.wav", 0, 0)
End Sub
دو Sound استاندارد Windows پخش میشوند. Flags برابر 0 یعنی Sound به شکل Synchronous پخش شود و Control تا پایان Sound به VBA برنگردد. اگر Microphone دارید میتوانید WAV اختصاصی شامل Sound Effect یا Voice ضبط و از همین روش پخش کنید.
این مثالها تنها نمونهای از قدرت API Callها هستند؛ APIهای بسیار بیشتری برای Functionalityهای گوناگون Windows وجود دارند.
صفحهٔ پایانی فصل در نسخهٔ اصلی عمداً خالی است.