線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:4221
推到 Plurk!
推到 Facebook!

LPT Status Register Interrupt Trigger

尚未結案
ray24
中階會員


發表:18
回覆:88
積分:56
註冊:2002-07-24

發送簡訊給我
#1 引用回覆 回覆 發表時間:2005-05-31 12:24:46 IP:220.135.xxx.xxx 未訂閱
找了一下之前的文章 WINDOWS 程式大部分由 LPT 取得資料的方式都採迴圈的方式詢問 不知道如何由Interrupt的方式來Trigger 接收的機制(DOS好像有< >) 就像是訊息觸發某些函式的 >) 請前輩提示我一二........卡關了糟糕< >
------
台上一分鐘,台下十年功
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#2 引用回覆 回覆 發表時間:2005-06-01 00:54:29 IP:220.143.xxx.xxx 未訂閱
http://delphi.ktop.com.tw/topic.php?TOPIC_ID=70017    http://www.vcomsoft.com/infopage/LPTPORT.html     
 CONTENTS 
I. INTRODUCTION 
II. Using The LPTPORT.DLL in C     
III. Using the LPTPORT.DLL in Visual Basic 5-6 
IV. BIOS Memory Mapped I/O regions 
                  INTRODUCTION 
LPTPORT.DLL is a 32 bit, user configurable dynamic link library created to afford the developer power and control over low level LPT and COM port hardware . This DLL was written in C   and gives the Visual Basic, C   Developer an application Interface to Read and Write LPT and COM Port Registers.  The LPTPORT library provides full R/W access to LPT and COM Port Byte, Status and Control registers. LPTPORT can be used to automatically detect and open default LPT, COM Ports as well as BIOS memory mapped I/O devices which will greatly reduce development time. LPTPORT 1.3.1 library is available with the purchase of a  single user development license for only  $9.95 and comes with C   and Visual Basic source code examples.     If you are currently developing an C   or Visual Basic application and want to resign from writing complicated I/O port drivers LPTPORT can help. I have built in many more functions in version 1.3.1.1 that will even simplify accessing I/O ports on any WIN9x system even more. You no longer need to know how memory mapped I/O works or even how access to the BIOS memory map  is achieved. LPTPORT still supports BIOS memory map access but only if you want to use those features. Two new functions GetDefaultLPT and GetDefaultCOM can be used to automatically determine the default LPT or COM I/O port base address.     In addition to Default LPT and COM port address determination I have provided a function called GetAddress that will allow you to read a Word from BIOS memory at a given segment and offset address.     Data reading and writing is very easy using LPTPORT. After you have the default LPT or COM port base address. A simple call to the  ReadData function will return the data read from an port address. Data can also be written to a port address using the WriteData function.      Seeking an NT/2000 or XP solution for I/O port access ? We now have an NT/2000/XP version of LPTPORT .     For NT/2000/XP version information : Visual Enterprises DDK           II. Using The LPTPORT.DLL in C     
I have written LPTPORT in Visual C   and compiled it into a resuable DLL . The DLL can be used in either Visual C   or Borland C   development environments. The prototypes listed below are callable by loading the DLL into your applications memory space. The easiest way to accomplish this is to use the LoadLibrary API call. Once the library is loaded you can then define pointers to each function within the process memory space. Here is a simple lptport.h header file you can use to call the library functions in C  .     Includes:     #include      #include      #include      #include     Special Datatypes:     typedef unsigned char BYTE; /* The Type of BYTE */     Function Prototypes:      typedef short(WINAPI* LPT1ADDR)(short segm, short offs); /* Pointer to GetLPT1Addr Func */     typedef short(WINAPI* GETADDR)(short segm, short offs); /* Pointer to Get Address Function */     typedef short(WINAPI* DEFLPT)(void); /* Pointer to default LPT function */     typedef short(WINAPI* DEFCOM)(void); /* Pointer to default COM function */     typedef BYTE(WINAPI* WRDATA)(short addr,  BYTE dbyte); /* Pointer to WriteData Func */     typedef BYTE(WINAPI* RDATA)(short addr); /* Pointer to ReadData Func */     /* Make pointers Look like each DLL Function so they can be called. */     LPT1ADDR lib_GetLPT1Addr;     GETADDR lib_GetAddress;     DEFLPT lib_GetDefaultLPT;     DEFCOM lib_GetDefaultCOM;     WRDATA lib_WriteData;     RDATA lib_ReadData;     /* Get the Address Location of the DLL Functions in Process Memory and create pointers to library functions */     lib_GetLPT1Addr = (LPT1ADDR)GetProcAddress((HMODULE)hLib,"GetLPT1Addr");     lib_GetAddress = (GETADDR)GetProcAddress((HMODULE)hLib,"GetAddress");     lib_GetDefaultLPT = (DEFLPT)GetProcAddress((HMODULE)hLib,"GetDefaultLPT");      lib_GetDefaultCOM = (DEFCOM)GetProcAddress((HMODULE)hLib,"GetDefaultCOM");      lib_WriteData = (WRDATA)GetProcAddress((HMODULE)hLib,"WriteData");      lib_ReadData = (RDATA)GetProcAddress((HMODULE)hLib,"ReadData");       Loading LPTPORT Library into memory space:     char WINSYSDLL[30]; /* Location of LPTPORT.DLL file C:\windows\system\lptport.dll */     /* Get Windows System Directory c:\windows\system */     if(!GetSystemDirectory((LPTSTR)WINSYSDLL,sizeof(WINSYSDLL)))     { cout << "Could not load system directory !" <<"\n\r"; }     /* Append the LPTPORT.DLL to the Windows System Directory Location C:\windows\system\lptport.dll */     strcat(WINSYSDLL,\\lptport.dll);     /* Load the Library into process memory and create an instance */     HINSTANCE hLib = LoadLibrary(WINSYSDLL);     /* Make sure the library loaded */     if(hLib==NULL)     { cout << WINSYSDLL <<" Load library failed !" << "\n\r"; cout << "Check to make sure library is installed." << "\n\r"; return(0); }     Calling LPTPORT Library functions:     short LPT1Address=lib_GetLPT1Addr(short segm, short offs); /* Get LPT Address located at BIOS memory Segment, Offset, returns address */     short Address=lib_GetAddress(short segm, short offs); /* Get  Address located at BIOS memory Segment, Offset, returns address */     short LPTAddress= lib_GetDefaultLPT(void); /* Get Default LPT Address from BIOS memory map, returns address */     short COMAddress=lib_GetDefaultCOM(void); /* Get Default COM Address from BIOS memory map, returns address */     BYTE DataByte=lib_WriteData(short addr, BYTE dbyte); /* Write an Data Byte to port address, returns Data Byte */     BYTE DataByte=lib_ReadData(short addr); /* Read data byte from specified port address, returns Data Byte */      Unloading LPTPORT Library:     FreeLibrary((HMODULE)hLib); /* Clean up library and free memory */            III. Using the LPTPORT.DLL in Visual Basic 5-6 
LPTPORT was also built with Visual Basic and Delphi programmers in mind. Loading library functions within VB is more simplified as compared to C  . Within VB a few simple declares are needed to call each function independantly. Here are the declares and I would suggest using since these have been tested with the DLL.      General Declarations:   
Option Explicit     Private Declare Function GetLPT1Addr Lib "LPTPORT.dll" (ByVal Segm As Integer,  ByVal Offs As Integer) As Integer     Private Declare Function GetAddress Lib "LPTPORT.dll" (ByVal Segm As Integer,  ByVal Offs As Integer) As Integer     Private Declare Function GetDefaultLPT Lib "LPTPORT.dll" () As Integer     Private Declare Function GetDefaultCOM Lib "LPTPORT.dll" () As Integer     Private Declare Function WriteData Lib "LPTPORT.dll" (ByVal address As Integer,  ByVal databyte As Byte) As Byte     Private Declare Function ReadData Lib "LPTPORT.dll"  (ByVal address As Integer) As Byte     Function Description:     GetLPT1Addr(Segm As Integer, Offs As Integer) As Integer - Returns the address located at BIOS memory segment:offset address.     GetAddress Lib "LPTPORT.dll" (ByVal Segm As Integer,  ByVal Offs As Integer) As Integer - Same as GetLPT1Addr.     GetDefaultLPT Lib "LPTPORT.dll" () As Integer - Returns the default LPT1 Address from BIOS.     GetDefaultCOM Lib "LPTPORT.dll" () As Integer - Returns the default COM1 Address from BIOS.     WriteData Lib "LPTPORT.dll" (ByVal address As Integer,  ByVal databyte As Byte) As Byte  - Writes a Data BYTE to port address, returns BYTE written.     ReadData Lib "LPTPORT.dll"  (ByVal address As Integer) As Byte  - Returns , reads a BYTE from port address.           IV. BIOS Memory Mapped I/O regions 
BIOS MEMORY MAPPED I/O ADDRESSES     ADDRESS SEGM:OFFS DESCRIPTION     0x400      0040:0000  COM1 BASE ADDRESS     0x402      0040:0002  COM2 BASE ADDRESS     0x404      0040:0004  COM3 BASE ADDRESS     0x406      0040:0006  COM4 BASE ADDRESS     0x408      0040:0008   LPT1 BASE ADDRESS     0x40A     0040:000A   LPT2 BASE ADDRESS     0x40C     0040:000C   LPT3 BASE ADDRESS 
ray24
中階會員


發表:18
回覆:88
積分:56
註冊:2002-07-24

發送簡訊給我
#3 引用回覆 回覆 發表時間:2005-06-07 13:58:16 IP:220.135.xxx.xxx 未訂閱
先感謝conundrum的回覆, 但可能誤會我的問題了 那我舉個例子好了    就用您提供的FUNCTION來說明    lib_ReadData這各FUNCTION 是來讀LPT PORT 的某一個位置的值 但是我使用時必須不斷的執行這個FUNCTION 來取值 並且判斷這各值中的某一個BIT 是否為TRUE 這樣做有一點耗程式的資源    所以我希望是當這個BIT 由 FALSE 轉成 TRUE時主動的通知應用程式 就像OS會送MESSAGE給應用程式那樣的呼叫 CALLBACK FUNCTION    不知道這樣的需求要如何實作 我看了一些類似 > ---------------------- 台上一分鐘,台下十年功
------
台上一分鐘,台下十年功
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#4 引用回覆 回覆 發表時間:2005-06-08 01:58:54 IP:218.175.xxx.xxx 未訂閱
基本上庵不知道你說啥 不過這 也許可以去看看 http://www.vincenzov.net/eng/design/vviodll.htm http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62686
ray24
中階會員


發表:18
回覆:88
積分:56
註冊:2002-07-24

發送簡訊給我
#5 引用回覆 回覆 發表時間:2005-06-08 15:05:16 IP:220.135.xxx.xxx 未訂閱
恩...寫的連我自己都快看不懂了 用程式來說明ㄅ 我使用的是 class="code"> class TLPTRead : public TThread { protected: void __fastcall Execute(); public: __fastcall TLPTRead(bool CreateSuspended=true); }; void __fastcall TLPTRead::Execute() { while(!Terminated) { r.type= IOACCESS_READ_BYTE; r.m_dx= 0x37A; WinIO(); data= r.m_al; if(data & 0x01) //檢查其中一支腳位是否為 1 { //是的1做下列的事情 } } } 以上這種方式我就稱他為"輪詢的方式"進行資料的取得 但這種方式會比較消耗CPU資源 甚至OS太多程式在執行時 若外部訊號給的太快就會 miss 掉 所以在以前DOS時代或現在的單晶片中都有Interrupt的機制 Interrupt 簡單來說就像是 OS中的 CALLBACK 當使用者按下滑鼠的右鍵 OS會主動的告知相關的應用程式通知她們滑鼠被按下右鍵的訊息 這時應用程式就會去執行相關的程式碼(就像是 VCL 中的 eventㄧ樣) 目前小弟想做的就是接收 LPT status register 其中的一個 Bit 作為觸發訊號(此觸發訊號是經由PC外部提供的) 當訊號觸發時我寫的應用程式會被告知 然後執行相關的程式碼 這樣處理的速度會加快 也不會消耗太多的CPU資源 但我不知道要如何達到上述的結果 因為我看了大部分的作法都是使用輪詢的方式 所以我嚴重的懷疑我的想法不知道有沒有可能實現 希望有做過相關題目的前輩能給我一個方向 一個在程式路途上迷失方向的小綿羊留 ---------------------- 台上一分鐘,台下十年功
------
台上一分鐘,台下十年功
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#6 引用回覆 回覆 發表時間:2005-06-08 21:17:15 IP:218.175.xxx.xxx 未訂閱
基本上 庵可能是無法解答的 以下凡KTOP轉貼的 請自行至原網站看看資料是否有可思考的 如由 LPT to socket 或 虛擬RS232 to RS232 HOOK的訊息接收 等等 http://www.beyondlogic.org/spp/parallel.htm http://www.luberth.com/cstep/parallel.htm http://support.gateway.com/s/Servers/COMPO/Cases/3501128/manual/05740a2.htm http://bioscentral.com/misc/bda.htm http://www-user.tu-chemnitz.de/~mad/BiosKompendium/isicmos.htm http://www.franksteinberg.de/progss.htm http://www.groovyweb.uklinux.net/?page_name=operating systems tutorial http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62691 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62687 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62268 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=56540 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=53955 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=53767 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=51359 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=51747 http://jiurl.nease.net/document.htm 8. 内存篇 内存共享(一) ProtoPTE 9. 内存篇 内存共享(二) CopyOnWrite http://delphi.ktop.com.tw/topic.php?TOPIC_ID=50996 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=44873 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=71607 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62690 Caller ID Developer Tools http://delphi.ktop.com.tw/topic.php?TOPIC_ID=61453 http://www.ainslie.org.uk/callerid/dev_soft.htm http://delphi.ktop.com.tw/topic.php?TOPIC_ID=51356 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=51006 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=51007 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=50997 若外部訊號給的太快就會 miss 掉 //不能由硬體轉換為別的協定嗎 如 RS232 to tcp to plc等 目前小弟想做的就是接收 LPT status register 其中的一個 Bit 作為觸發訊號(此觸發訊號是經由PC外部提供的) //很像UPS的 RS232訊號給PC說 快斷電了 ㄟ OS 快準備關機 可找 axsoft 版主的討論文章 庵幫不上了 哈哈 哈哈哈
derrenbol1
中階會員


發表:5
回覆:113
積分:93
註冊:2004-12-09

發送簡訊給我
#7 引用回覆 回覆 發表時間:2005-06-08 21:40:27 IP:210.202.xxx.xxx 未訂閱
To ray24:      看了有些亂, 雖然不太知道你們對話的詳細, 但我知道你的訴求是應用程式要直接使用中斷是吧? 我從事的是Embedded System建置及Firmware撰寫. 若依照我對"OS"的觀念來說的話, 那作業系統是不應該給使用者直接使用中斷的權利; 作業系統不光只為了貴單位的程式而運作, 它得保持系統穩定, 在正常使用情況下, 考慮一個情況若當作業系統允許使用者操作中斷, 但萬一使用者忘了將系統原本預設的中斷服務常式還原的話, 那後果就嘿嘿嘿了.   我想你應該知道"中斷"在作業系統中算是一個資源, 而作業系統應該全部都掌控在核心內, 提供給使用者的是另一層的介面, 一個Call Back的介面以讓使用者與核心作連接, 或是一個事件, 來通知使用者, 這樣才能保證當上述情況發生時作業系統不導致當機, 不然Window 98不應該是98天才當一次, 應該是趨近9.8天才對.   以各位應用程式撰寫人員不漠生的Thread為例, 它是利用系統計時器的功能做到的, 該計時器是作業系統所控制用來切換行程, 而Thread基本上也算是一種行程, 而且比較不會佔記憶體; 那那天你心血來潮想拿計時器來用用的話, 大概該時間內只剩下你的程式在執行, "Multitask"恐怕就不存在了.   DOS ? 這種東西在任一本作業系統的書籍中僅能被歸類成"監控程式"而已; 同時間只有一個程式能被執行, 中斷給你又有何不可, 當機頂多重開而已; 雖然在我的觀念中是不允許這種行為的, 但我還是很想知道有沒有人在 > 請參考
derrenbol1
中階會員


發表:5
回覆:113
積分:93
註冊:2004-12-09

發送簡訊給我
#8 引用回覆 回覆 發表時間:2005-06-08 21:45:42 IP:210.202.xxx.xxx 未訂閱
To ray24 : 看了有些亂, 雖然不太知道你們對話的詳細, 但我知道你的訴求是應用程式 要直接使用中斷是吧? 我從事的是Embedded System建置及Firmware撰寫. 若依 照我對"OS"的觀念來說的話, 那作業系統是不應該給使用者直接使用中斷的權 利; 作業系統不光只為了貴單位的程式而運作, 它得保持系統穩定, 在正常使 用情況下, 考慮一個情況若當作業系統允許使用者操作中斷, 但萬一使用者忘 了將系統原本預設的中斷服務常式還原的話, 那後果就嘿嘿嘿了. 我想你應該知道"中斷"在作業系統中算是一個資源, 而作業系統應該全部都 掌控在核心內, 提供給使用者的是另一層的介面, 一個Call Back的介面以讓使 用者與核心作連接, 或是一個事件, 來通知使用者, 這樣才能保證當上述情況 發生時作業系統不導致當機, 不然Window 98不應該是98天才當一次, 應該是趨 近9.8天才對. 以各位應用程式撰寫人員不漠生的Thread為例, 它是利用系統計時器的功能 做到的, 該計時器是作業系統所控制用來切換行程, 而Thread基本上也算是一 種行程, 而且比較不會佔記憶體; 那那天你心血來潮想拿計時器來用用的話, 大概該時間內只剩下你的程式在執行, "Multitask"恐怕就不存在了. DOS ? 這種東西在任一本作業系統的書籍中僅能被歸類成"監控程式"而已; 同時間只有一個程式能被執行, 中斷給你又有何不可, 當機頂多重開而已; 雖 然在我的觀念中是不允許這種行為的, 但我還是很想知道有沒有人在Windows中 搞這招, 對於80x86的CPU而言, 應該必須切到Supervisor Mode才有資格去更改 IDT才對, 用Window API的話, 比爾蓋子不知道同不同意. 再Port一次, 好像忘了Enter了, 上一回答怎麼那麼長?
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#9 引用回覆 回覆 發表時間:2005-06-08 23:33:24 IP:218.175.xxx.xxx 未訂閱
但我還是很想知道有沒有人在Windows中 搞這招 庵是知道一位 林君學 這老版主 自己有寫一支 保護/真實 模式 切換於 2000/xp 話說那 林sir 版主 不知有無看到 看到是否會出來一下 哈哈 令一個就是 大名頂頂的 除錯魔神 SoftICE lu 網友 thread 高手的問答 預防死結 http://delphi.ktop.com.tw/winnertopics.asp?M_ID=16913&M_NAME=lu http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62819 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=62520 http://www.delphi3d.net/articles/viewarticle.php?article=threads.htm http://delphi.ktop.com.tw/topic.php?topic_id=69191 【問題】有關WinIO的使用 http://delphi.ktop.com.tw/topic.php?topic_id=66444 純討論 題外話 "OS"的觀念來說的話, 那作業系統是不應該給使用者直接使用中斷的權 利; 作業系統不光只為了貴單位的程式而運作, 它得保持系統穩定, // 理論上 可能要先看 OS系統排程的特性 一般程式只能在ring 3 執行 提高 執行權限 一般的Window API當然ms是常常藏招的 例如 網路封包 與 資料複製 2者比較 在2000/xp等OS下 它們是認定 那一個為優先 答案 如果都在背景執行 網路封包是高於 資料複製 原因 網路封包有時間限制的困擾 當初OS就有考慮 時差與重要性 所以內定 封包傳遞 優先於 資料複製 重點 : 網路封包資料較小 無法重覆(甚至不回應接收) 拒絕遺失 傳遞時間有限 所以卡高 再說下去 系統分析的書 先來先出 後來先出 可能都看不完了 庵扯太遠了 哈哈
ray24
中階會員


發表:18
回覆:88
積分:56
註冊:2002-07-24

發送簡訊給我
#10 引用回覆 回覆 發表時間:2005-06-08 23:41:35 IP:220.135.xxx.xxx 未訂閱
conundrum 大哥別這麼說...你能提供這些資料我已經非常感激了 畢竟這種冷門的題目...甚至有人願意多看一眼衝衝人氣就非常難能可貴了    derrenbol1兄... 我啦哩拉扎的寫了一堆...亂沒重點的 倒是您說到了一個重點     而作業系統應該全部都 掌控在核心內, 提供給使用者的是另一層的介面, 一個Call Back的介面以讓使 用者與核心作連接, 或是一個事件, 來通知使用者 其實寫了這麼多要的就是這個< > 就是因為 > 若是可能的話又該如何實做呢? 臭比爾蓋子 < > 我恨妳....>_< ---------------------- 台上一分鐘,台下十年功
------
台上一分鐘,台下十年功
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#11 引用回覆 回覆 發表時間:2005-06-08 23:56:58 IP:218.175.xxx.xxx 未訂閱
【BCB】【問題】I/O Port By NT/2000/XP http://delphi.ktop.com.tw/topic.php?TOPIC_ID=35368
derrenbol1
中階會員


發表:5
回覆:113
積分:93
註冊:2004-12-09

發送簡訊給我
#12 引用回覆 回覆 發表時間:2005-06-09 00:14:34 IP:210.202.xxx.xxx 未訂閱
To ray24:      或許有, 或許沒有, 這我也不知道, 因為畢竟我的工作主要不是在為Windows的平台開發程式; 我只是看到你想在Windows中使用實際中斷線路, 才寫上次的回答; 你的要求我覺得算滿特殊的, 想要利用Data Bit去當中斷輸入源, 這應該不會存在於作業系統層的設計者所會去考慮的, 而在全球的程式設計師才有可能會有像你這種須求, 所以看開一點, 保庇有貴人相助, 或者該寫Driver時就摸摸鼻子硬做了. 還有若就這個議題上, 別怪比爾了, 因為
ray24
中階會員


發表:18
回覆:88
積分:56
註冊:2002-07-24

發送簡訊給我
#13 引用回覆 回覆 發表時間:2005-06-12 14:49:51 IP:220.135.xxx.xxx 未訂閱
感謝兩位前輩的說明以及提供了這麼多的參考資料 雖然誘拐不出 林君學以及除錯魔神 SoftICE  這兩位高手高手高高手 但看了些許他們的文章也感覺受益良多    看來是該硬著頭皮的時候到了< > 希望不要光看 > 哈哈哈 來去拼了...< > ---------------------- 台上一分鐘,台下十年功
------
台上一分鐘,台下十年功
系統時間:2024-04-20 11:53:17
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!