Call tips between VB5.0 and Windows API

zhaozj2021-02-11  221

The call skills between VB5.0 and the Windows API generally use the Window API's situation, because the VB itself does not provide certain functions, but the program needs to have, for example: read the information within Registry, VB only provides SAVESETTING, GetSetting and other series of instructions, but it can only read the value of a particular area, read, delete, and more than the value of other regions, it will not be used. Again: Take a closer look at the esents of Combo Box, where there is no mousemove, but this is an EVENT we often use, what is it? Yes, it only has through the Winodow API. The VB calls Window API generally do not use the API reviewer, directly in the corresponding API COPY to our program, what skill is it? In fact, because the problem of VB data format, plus VB itself does not have an indicator, in many places need some small skills to solve, and we often have different needs, and then make some modifications after the declaration of the API examiner COPY. Most important, if there is a .dll file, it is not defined in the API reviewer, then only what you want to think. First, integer parameters Windows API32-bit yuan VB ============================================ ============================================================================= Integerbyte, Char Byval Byteeg. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --------------------------------- Windows API Corresponding to the VB declaration of short getKeyState (int NVIRTKey) "User32" (Byval NVIRTKEY As Long) AS Integer --------------------------------------- -------------------------------------- This API can be used to review some Key (such as insert) , Num Lock, Capslock, etc.) is ON / OFF. The program is as follows: This example should be very well seen in the declaration of each integer.

-------------------------------------------------- -------------------------- DIM INSERTMODE AS INTEGERINSERTMODE = GetKeyState (vbkeyInsert) and vbshiftmaskif insertmode = 1 thendebug.print "indicates INSERT MODE" Elsedebug .print "indicates overwrite mode" end if ----------------------------------------- ------------------------------------ Two, pointing to the integer's indicator Windows API 32-bit VB == ========================== ================================================================================================================================================================= == LPINT (ByRef) LongLPUNIT (ByRef) LongLPBOOL (ByRef) LongLPDWORD (ByRef) LongLPHANDLE (such as: PHKEY) (ByRef) LongLPWORD (ByRef) IntegerLPSHORT (ByRef) IntegerLPBYTE (ByRef) ByteVB default is to use the pass-call, so you can ByRef Omitted, that is, func (byref param1 as type) is the same, the way of using the service call is not considered to pass the parameters to the API. However, the service call of the Long type has a considerable amount of weight in VB, because the 32-bit metrics are the type of long, and the string, the StruCTure of the self-customized state is indicator in the Windows API. During delivery, the transmission of the indicator is actually the transmission of the long value, only the last LONG value, the Win API will be used as Address, and the content referred to in the indicator, this concept is behind It will be important.

E.g:------------------------------------------------ ----------------------------- Long Regopenkeyex (HKEY HKEY, // Handle of Open Keylpctstr Lpszsubkey, // address of name of name of subkey to openDWORD dwReserved, // reservedREGSAM samDesired, // security access maskPHKEY phkResult // address of handle of open key); VB corresponding declaration declare Function RegOpenKeyEx Lib "advapi32.dll" Alias ​​"RegOpenKeyExA" _ (ByVal hKey As Long, _Byval LPSUBKEY AS STRING, _BYVAL ULOPTIONS AS Long, _Byval Samdesired As long, _phkresult as long) As long '// The last parameter is the declaration of Byref ------------------- -------------------------------------------------- -------- We often want to use the program to read the information in Registry, for example: how do we know how Win95's Product ID does? There are several ideas here to be clear: First: What is the productID? ProductID under HKEY_LOCAL_MACHINE / SOFTWARE / Microsoft / Windows / CurrentVerson. We want to get the KEY is to HKEY_LOCAL_MACHINESUBKEY as SOFTWARE / Microsoft / Windows / CurrentVersonValueName to ProductId However, to get the value of value ProductId not so direct, you must have at SubKey of KeyHandle acquired KeyHandle is the use of RegQueryKeyEx of the API. The program part is introduced in the introduction of the WIN API string. Third, string parameters All string parameter indicators are passed on the byval parameter name as String. If the second parameter of RegopenKeyex () Byval LPSubKey As String is an example. Perhaps this example is to pass the Subkey value to the Win API. So you use ByVal, nothing big, in fact, when you want WIN API to return the string, you must also use ByVal to announce. This is a different factor in VB5 string format (BSTR) and WIN API Standard String Format (LPSTR).

The LPSTR string format is a string of Null Terminate. If there is a string "Haha! OK!", The format is as follows: ------------------------ -------------------------------------------------- --- Address 0 1 2 3 4 5 6 7 8 9-- - - - - - - Content H A H A! OK! / 0 and BSTR is in the strings There is also a long range of long-won strings, the format is as follows: address 0 .. 3 4 5 6 7 8 9 10 11 12 13 ------ - - - - - - - - - Content 9 h a h A! OK! / 0 --------------------------------- ------------------------------------------, so the string is byval The way is not like referring to the 4th location in BSTR, so, is it possible to be compatible with LPSTR? I think it is because so v-way String can get the return value of the WIN API (even if it is not the case, I want to compare String String to use ByVal). There is another problem now, the string of Window95 API uses ASCII Code but VB is using unicode, Unicode accounts for two bits groups, then can the WinApi string? Fortunately, we can do it first, because VB itself has been converted, that is, VB is transmitted to the API, turn it again, turn back to Unicode, so if we use Byte Array to the signal, you can To go to the code yourself. . However, in the 32-bit VB, the string has a format, one is BSTR, and the other is HLSTR, and if we declare the string is a non-fixed length, it will be BSTR, and it is HLSTR. DIM BSTR5 AS STRING BSTRDIM HLSTR5 AS STRING (255) Win32 API Calls in HLSTRVB5 Please use BSTR, because the result of using HLSTR is that VB has to do HLSTR-> BSTR conversion to call WIN API if there is back String and then Do the work of BSTR-> HLSTR. However, when using BSTR to work, if there is a String parameter with a return value, there is an additional action: 1. First give the primary value of the string, and the length of the string is enough to release the value. 2. Remove the extra valence in the return value. Or, for example: ----------------------------------------------- ----------------------------- Int getWindowText (hwnd hwnd, // handle of window or control with textlptstr lpstring, // address) Of buffer for textint nmaxcount // maximum number of character of character of character of characters to copy; the API gets the text of Window Title Bar, and the return value is the number of Character placed in lpstring.

The declaration of VB is as follows: DECL is Function getWindowText lib "user32" alias "getWindowTexta" _ (byval hwnd as long, _byval lpstring as string, _byval cch as long) AS long sample one ************ *********************************************************** *************** DIM Char Char CHARCNT AS LONGDIM LPSTRING AS STRINGDIM TMPSTR AS STRINGDIM NULLPOS as longform1.caption = "This is a Test" LPSTRING = String (255, 0) 'Setting initial value charCnt = GetWindowText (Me.hwnd, lpString, 256) 'charCnt = 12tmpstr = Left (lpString, charCnt)' to do so there will be some problems Debug.Print Len (tmpstr) 'was 12Label1.Caption = Left (lpString, charCnt) Debug . 8 *************************************** ************************************** When an example is an example, set lpstring = The purpose of String (255, 0) is to set the space for 255 fifths to lpstring (plus the last NULL a total of 256), the value of Charcnt is 12, the eye can see Len ("This is a Test" Will be 8, but Charcnt is 12, so use the Left () function directly to get the subsequent series, this is the relationship between Unicode and ANSI String, so when you see some books Methods Sub strings are not very perfect, so the way of modification is used to be more correct. Example two *********************************************************** ************************************** FORM1.CAPTION = "This is a test" lpstring = String (255, 0) "setting At the initial value charcnt = getWindowText (me.hwnd, lpstring, 256) 'charcnt = 12nullpos = INSTR (1, lpstring, chr (0), vbinarycompare) TMPSTR = Left (lpstring, nullpos - 1) Lable1.caption = tmpstr *** *********************************************************** *************** * Four, Null Value Delivery We will return to the ProDuctID, we know the use of regopenKeyex () to get Subkey The Handle value is followed by regQueryValueex ().

-------------------------------------------------- -------------------------- Long RegqueryValueex (HKEY HKEY, // Handle of Key To Querylptstr Lpszvaluename, // Address of Name of Value To queryLPDWORD lpdwReserved, // reservedLPDWORD lpdwType, // address of buffer for value typeLPBYTE lpbData, // address of data bufferLPDWORD lpcbData // address of data buffer size); VB declaration (the Copy down by a member of the API's view) declare Function RegQueryValueEx LIB "Advapi32.dll" Alias ​​"RegQueryValueexa" _ (Byval HKEY As Long, _Byval LPVALUENAME AS STRING, _BYVAL LPRESERVED AS Long, _LPTYPE AS LONG, _LPDATA AS ANY, _LPCBDATA AS Long AS Long --------- -------------------------------------------------- ------------------ Take a closer look at the third parameter, WIN API is LPDWORD in VB to deliver in the way in ByVal? The reason is that LPRESERVED must pass NULL in, and VB is filled in the position of this parameter when the call (see Example 3). Why do you pass NULL? We can think so, we are in the middle of the program, tell VB to pass out in ByVal, while in the Win API, it can't matter if VB is Byval or byref, the API determines that it is needed, so The third parameter determines that we pass us in the API, and VB passes 0, and if the API will get its content, you will get the contents of Address 0, maybe the NULL value of Window is pointing Address 0! Another method is directly related to the third parameter of the VB declared by the third parameter of the VB by byval lpreserved as long. The fixed vbnullstring is also available. Here is in an idea, that is, VB's announcement of Win API. It is purely to VB himself. Define a parameter of an indicator in the API. The API viewer will declare it into a Byref method (except for strings), but We can do it with the need, one original should be a parameter to Byref, we can change it to byval, as long as we can obtain the address of the parameters, and the location of this state is byval Out, Win API doesn't know what the VB end is used. Anyway, as long as we pass a long value, the Win API will operate as address as this long value as ADDRESS.

The problem has not been resolved, and the fourth parameter of RegQueryValueex () LPTYPE is REG_SZ (= 1) that represents LPDATA is Null Terminate String, if reg_dword (= 4) that represents LPDATA is a long value, because there is no way prior Know the true state of LPDATA, so VB uses Asany's type, it wants to abandon the type of type, you can go in, but there are some problems here, if lptype is reg_dword, then LPDATA does not Question, but if LPTYPE is REG_SZ, String is to declare by byval, so there will be conflicts, and the solution to the solution is to rewrite the announcement of the API reviewer COPY. -------------------------------------------------- -------------------------- Declare Function Regquerylong Lib "Advapi32.dll" Alias ​​"RegQueryValueexa" _ (Byval HKEY As Long, _byval lpvalueename as String, _ByVal lpReserved As Long, _lpType As Long, _lpData As Long, _lpcbData As Long) As LongDeclare Function RegQueryString Lib "advapi32.dll" Alias ​​"RegQueryValueExA" _ (ByVal hKey As Long, _ByVal lpValueName As String, _ByVal lpReserved As Long, _LPTYPE As Long, _Byval Lpdata As String, _lpcbdata as long) AS Long --------------------------------- ------------------------------------------ use two declarations to solve this problem When different LPTYPE calls, when lptype = reg_dword, call regQuerylong, lptype = reg_sz is regQueryString This can also make us understand why the VB API declares why there is Alias ​​existence.

Example three ********************************************************** ************************************** DECLARE FUNCLOSEKEY LIB "Advapi32.dll" (BYVAL HKEY AS Long) _as longdeclare function regopenkeyex lib " advapi32.dll "Alias" RegOpenKeyExA "(ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, _ByVal samDesired As Long, phkResult As Long) As LongDeclare Function RegQueryString Lib" advapi32.dll "Alias ​​_" RegQueryValueExA "(ByVal hKey As Long, _ByVal lpValueName As String, ByVal lpReserved As Long, _lpType As Long, ByVal lpData As String, lpcbData As Long) As LongConst REG_EXPAND_SZ = 2Const HKEY_CLASSES_ROOT = & H80000000Const READ_CONTROL = & H20000Const STANDARD_RIGHTS_READ = (READ_CONTROL) Const KEY_QUERY_VALUE = & H1Const KEY_ENUMERATE_SUB_KEYS = & H8Const KEY_NOTIFY = & H10Const SYNCHRONIZE = & H100000Const KEY_READ = ((STANDARD_RIGHTS_READ Or _KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or _KEY_NOTIFY) And (Not SYNCHRONIZE)) Dim key5 As String, ValueName as String, strBuff as String, Re sultStr as StringDim leng1 As Long, resul As Long, hkey As LongDim tp As Long, i As Longkey5 = "SOFTWARE / Microsoft / Windows / CurrentVerson" resul = RegOpenKeyEx (HKEY_CLASSES_ROOT, key5, 0, KEY_READ, hkey) 'hkey subkey is (^ key5) of KeyHandle, it can first obtain access ValueNameValueName within Subkey = "ProDuctId" tp = REG_SZstrBuff = String (255, 0) leng1 = Len (strBuff) 1resul = RegQueryString (hkey, ValueName, 0, tp, strBuff , Leng1) 'Note, the third parameter transmission 0, Leng1 passed back to COPY to STRBUFF word number (ANCI) Leng1 = INSTR (1, strBuff, chr (0), vbinarycompare)' Re-calculate a number (Unicode) Resultstr = left (strbuff, leng1-1) '

This is the value of ProductID **************************************************************** ******************************************************* Leng1 = len (strbuffer) 1, this line can save, very strange, why is it clearly a return value, but must set it to a StrBuff size? This is because many Win APIs are not smart to find StrBuff's null char, so they need to pass in, and then it is followed by this field to fill the number of strBuff. V. Parameters of Array Parameters We know that the Array Passing of WIN API is the start address of the array, so the only thing to note in the VB is the write method of the starting position.

Take another API where the Window directory is located as an example: ------------------------------------- ---------------------------------------- Uint getWindowsDirectory (lptstr lpbuffer, // address of buffer for Windows directoryUINT uSize // size of directory buffer); // If successful, the number of characters back catalog of VB declaration (API View members) then pass declare Function GetWindowsDirectory Lib "kernel32" Alias ​​_ "GetWindowsDirectoryA" (ByVal lpBuffer As String, byval nsize as long _as long We will change it to Declare Function GetWindowsDirectory LIB "kernel32" Alias ​​_ "getWindowsDirectorya" (LPBuffer as Byte, BYVAL NSIZE AS long) AS long ------------ -------------------------------------------------- --------------- Example four **************************************** **************************************************** DIM N AS Longdim Buff AS BYTEDIM Stra As StringBuff = Space (256) n = getWindowsDirectory (buff (0), 256) BUFF = LEFTB (BUFF, N) STRA = STRCONV (BUFF, VBUNICODE) 'Stra is the directory where Windows is located **** *********************************************************** ******************** In Example 4, getWindowsDirectory () The first parameter buff (0) is the starting Byte of this array, Because VB is announced LPBuffer as byte, the pass is the location of Byref Buff (0), of course, you can also call to n = getWindowsDirectory (buff (1), 256), but the return value is fill in buff (1) to BUFF (N), and buff (0) is still the starting space character (32) because the API transmission value is the number of characters, plus BYTE ARRAY in buff, using Leftb ( Remove more Byte, use StrConv to transfer Byte Array to Unicode's strings. In the case of the example, we can also change byteArray to String. You can make a comparison. Who is better or more smooth, but you can be sure, if you pass the value It is the value of binary, then use Byte Array to do it. If you pass by String, it will be converted into Unicode steps, which will happen in the middle, no one knows.

Sixth, the Callback Function method VB users usually have more doubts about this noun, or called "Cry Dad" Function, and the VB5 manual uses Window Procedure to explain unless there is some understanding of the Window system. Otherwise it may be more unambiguous; I use another example to explain, that is the Keyboard Hook. What is KeyboardHook, Ji Ji is when pressing the keyboard, will automatically perform a function of a paragraph Function, which is like the interception interrupt vector in the DOS era. Let's take a look at the declaration of setting hook. -------------------------------------------------- --------------------------HHOOK SETWINDOWSHOKEX (int IDHOOK, // Type of Hook to installhookproc hkprc, // address of hook procedurehinstance hmod, // handle of application instanceDWORD dwThreadID // identity of thread to install hook for); Declare Function SetWindowsHookEx Lib "user32" Alias ​​SetWindowsHookExA "_ (ByVal idHook As Long, _ByVal lpfn As Long, _ByVal hmod As Long, _ByVal dwThreadId As Long) As long ------------------------------------------------ ---------------------------- Hook has many kinds, such as Keyboard Hook, Mouse Hook, JournalRecord Hook, etc., the first parameter Indicates which hook you want, the second parameter is hook procedure, which is the function in the "Automatically executes a function of a piece of function", which can be given at will, but there is a certain Parameter transfer rules, for example: hnexthookProc = setWindowshookex (wh_keyboard, _addressof mykbhfunc, app.hinstance, 0) When you press any key, the program will automatically perform Mykbhfunc. This hook function is defined by us, But it is automatically called by Window, not by our program, which is called Callback function.

In the above example, this Callback function is defined as follows: ------------------------------------- ---------------------------------------- Public Function Mykbhfunc (Byval icode as long, _byval wParam As Long, ByVal lParam As Long) As LongMyKBHFunc = 0If iCode <0 ThenMyKBHFunc = CallNextHookEx (hnexthookproc, iCode, wParam, lParam) Exit FunctionEnd If 'no detection has to press the key PrintScreen If wParam = vbKeySnapshot ThenMyKBHFunc = 1Debug.Print " Haha "end ifend function --------------------------------------------------------------------------------------------------------------------- ------------------------------- This keyboard hook function is mainly to intercept the key to the print screen This button does not work in the form of KeyDown, KeyPress, Keyup Event, so I have to intercept through the Keyboard Hook. The position of the Callback Function is provided, one is where to be in the same project with the place to call SetWindowsHookex (), in addition, it can only exist in .ba file, it cannot be placed elsewhere. The program of Keyboard Hook is in Fan Wu.

Example five ********************************************************** *********************************** (BYVAL IDHOKEXA) (Byval IDHOKEXA) As Long, ByVal lpfn As Long, _ByVal hmod As Long, ByVal dwThreadId As Long) As LongDeclare Function UnhookWindowsHookEx Lib "user32" _ (ByVal hHook As Long) As LongDeclare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As LongPublic hnexthookproc As LongPublic Const HC_ACTION = 0Public Const WH_KEYBOARD = 2Public Sub UnHookKBD () If hnexthookproc 0 ThenUnhookWindowsHookEx hnexthookprochnexthookproc = 0End IfEnd SubPublic Function EnableKBDHook () If hnexthookproc 0 ThenExit FunctionEnd Ifhnexthookproc = SetWindowsHookEx ( WH_KEYBOARD, AddressOf _MyKBHFunc, App.Hinstance, 0) If hnexthookproc 0 ThenEnableKBDHook = hnexthookprocEnd IfEnd FunctionPublic Function MyKBHFunc (ByVal iCode As Long, _ByVal wParam As Long, ByVal lParam As Long) As Long 'these three parameters The number is fixed and can not move, but as long as the name MyKBHFunc and 'SetWindowsHookex name after the AddressOf () as you can, not necessarily what's MyKBHFunc = 0If iCode <0 ThenMyKBHFunc = CallNextHookEx (hnexthookproc, iCode, wParam, lParam) Exit FunctionEnd IfIf wParam = vbKeySnapshot Then 'detected to have no press PrintScreen key MyKBHFunc = 1Debug.Print "haha" End IfEnd Function' in the following formula FormPrivate Sub Form_Load () Call EnableKBDHookEnd SubPrivate Sub Form_Unload (Cancel As Integer) Call UnHookKBDEnd Sub ** *********************************************************** **************************************************************************** Not explained. Eight, integrated application We will explain the skill of Win API in VB5 with an example.

There is a card called CopyMemory as follows: ---------------------------------------- ------------------------------------- Declare sub copymemory lib "kernel32" Alias ​​"RTLMOVEMEMORY" (_lpvdest As Any, LPVSource As Any, Byval Cbcopy As Long) -------------------------------------- --------------------------------------- This letter can use lpvdest's momory copy to lpvsource Going up, CBCopy represents how many bytes to be Copy. With this one, we can know how much a double value exists in MEMORY. -------------------------------------------------- -------------------------- DIM DBL As Doubledim Bte (0 to 7) as Bytedbl = 168.256CopyMemory DBL, BYT (0), 8 -------------------------------------------------- -------------------------- This check BTE array can know how much this double value is.

Take another JournalRecord Hook as an example: Example six ********************************************* **************************************** 'in the following Hook.basConst WM_MOUSELAST = & H209Const WM_MOUSEFIRST = & H200Public Const WM_KEYLAST = & H108Public Const WM_KEYFIRST = & H100Public Const WH_JOURNALRECORD = 0Type EVENTMSGmessage As LongparamL As LongparamH As Longtime As Longhwnd As LongEnd TypeDeclare Function SetWindowsHookEx Lib "user32" Alias ​​_ "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _ByVal hmod As Long, ByVal dwThreadId As Long) As LongDeclare Function UnhookWindowsHookEx Lib "user32" _ (ByVal hHook As Long) As LongDeclare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _ByVal nCode As Long, ByVal wParam As Long, lParam As Any ) As LongDeclare Sub CopyMemory Lib "KERNEL32" Alias ​​"RtlMoveMemory" _ (lpvDest As Any, ByVal lpvSource As Long, ByVal cbCopy As Long) Public hNxtHook As Long 'handle of Hook ProcedurePublic msg As EVENTMSGSub EnableHook () hNxtHook = SetWindowsHookEx (0, Addressof Hook Proc, App.hInstance, 0) End SubSub FreeHook () Dim ret As Longret = UnhookWindowsHookEx (hNxtHook) End SubFunction HookProc (ByVal code As Long, ByVal wParam As Long, _ByVal lParam As Long) As LongCopyMemory msg, lParam, Lenb (msg ) If (msg.message> = WM_KEYFIRST _And msg.message <= WM_KEYLAST) ThenDebug.Print msg.message, msg.paramHEnd IfHookProc = CallNextHookEx (hNxtHook, code, wParam, lParam) End Function 'in the following formula Form1Private Sub Form_Load () Call enablehookend subprivate subform_unload (cancel As integer) call freehookend sub ********************************************************** *********************************************** Detailed process does not specify,

We only put our focus on hookproc this hook procedure if we check that Journald Hook's hook procedure is defined as follows: -------------------------- -------------------------------------------------- -LRESULT CALLBACK JournalRecordProc (int code, // hook codeWPARAM wParam, // undefinedLPARAM lParam // address for the value of a EVENTMSG Structure); JournalRecordProc this corresponds to our HookProc is Function HookProc (ByVal code as Long, ByVal wParam as Long _BYVAL LPARAM AS Long) As long ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------- Have you noticed the third parameter it is a ByVal's long, Refers to the location where EventMSG is stored, and we mentioned before, the parameters of the custom state will be used to use byref to solve, God! It uses ByVal's way, if it is a C language, that is not a problem, as long as you are: ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------- EventMSG * P ; P = (Eventmsg *) LParam; ----------------------------------------- ------------------------------------ so you can use * p-> message to get content , But VB? Here, you have to use some tips. Imagine that if we can use the location of LParam, a Byte a Byte's copy to an EventMSG variable, can you? So, the copyMomory is used in the form of a game, but the original declaration of CopyMomory is as follows, the front two parameters are byref, but there are currently lParam content (assuming is lparam = 100100) If we use the declaration, go to call - declare one ------------------------------------------------------------------------------------------------- ----------------------------------- Declare sub copymemory lib "kernel32" Alias ​​"RTLMOVEMEMORY" (_lpvdest as Any , lpvsource as any, byval cbcopy as long) CopyMomomory MSG, LPARAM, LENB (MSG) ----------------------------------------------- -------------------------------------------- then WinAPI RTLMoveMemory The second parameter value = 100100 is obtained, but the indicator refers to the address of 100100, then the desired data is not required (due to the location of the data at 25600).

转载请注明原文地址:https://www.9cbs.com/read-4098.html

New Post(0)