Cempi实战攻略(六)——如何截获到达的短消息
Cempi實戰(zhàn)攻略(六)——如何截獲到達的短消息
By 吳春雷
QQ:819543772
EMAIL:wuchunlei@163.com
1.????? MapiRule是什么?我從哪里能夠得到它?
MapiRule是微軟提供的用于演示短信攔截技術的DEMO程序,程序展示了使用COM技術為tmail.exe注冊服務,實現客戶端短信攔截的基本方法。您可以再SDK的安裝目錄中找到它,如果您使用的是PPC2003的SDK,MapiRule程序可以在下面的目錄找到:
?
C:\Program Files\Windows CE Tools\wce420\POCKET PC 2003\Samples\Win32\mapiRule
????????????? 如果您的開發(fā)平臺是WM5.0則,您可以再下面目錄中找到改程序:
C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Mapirule
????????????? 不知道出于什么原因,WM6.0的SDk中并沒有提供MapiRule程序,因此,如果您的開發(fā)平臺是WM6.0,那么就只能到網上下載該程序了。
?
2.????? 我想先部署到WM6.0平臺上看看效果,我該怎么做?
a)??????? 編譯MapiRule
首先,肯定是要搞到MapiRule的源程序,源程序有兩個不同的版本,分別再PPC2003 SDK和WM5.0 SDk中提供,兩個版本的源程序是再不同的開發(fā)環(huán)境中編寫,但內容是完全相同的,只是需要使用不同的編譯器進行編譯。PPC2003的版本要再EVC4.0下編譯,而WM5.0的版本要再VS2003/2005/2008下編譯。成功編譯后可以得到mapirule.dll文件,將該文件拷貝到WM仿真器或設備上(一般放在Windows目錄下),就可以進行后面的操作了。
?????? 如果您的目的僅僅是部署到設備上試一下,又不想或者沒有安裝開發(fā)環(huán)境無法編譯,那么您可以在下面目錄下找到編譯好的Cab包(不一定會有這個cab包,PPC2003的版本沒有該cab包),然后拷貝到設備上直接安裝即可。
C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Mapirule\Setupmapirule
b)??????? 如何注冊該Com組件
MapiRule的工作機制是基于COM技術的,但限于文章的主題,本文不會對COM技術進行詳細討論,推薦潘愛民的COM技術教材,有興趣的朋友可以找來研究。眾所周知,要想讓系統(tǒng)知道COM組件的存在,需要首先再系統(tǒng)注冊表中對COM組件進行注冊,應用程序會通過注冊表加載對應的COM組件。對于MapiRule,可以有三種方式進行注冊,分別是手動修改注冊表,修改安裝包中的inf文件,以及調用源程序中提供的DllRegisterServer的函數。下面分別進行介紹
????????????????????????????????????? i.????????????? 第一種方式:手動向注冊表中輸入注冊信息
WM設備的注冊表再OS中是看不到,因此要想手動修改系統(tǒng)注冊表,需要使用專用的注冊表瀏覽工具,VS2005中提供了瀏覽手機注冊表的工具,非常好用。依次選擇“開始”——“Microsoft Visual Studio 2005”——“Visual Studio Remote Tools”,運行“遠程注冊表編輯器”,就可以打開該工具對注冊表進行操作。如果您的編譯環(huán)境中沒有該工具,可以從網上下載相應的工具。
打開遠程注冊表編輯器,建立與設備的連接,再系統(tǒng)注冊表找到如下目錄:
?[HKEY_CLASSES_ROOT\CLSID\]
再CLSID下添加一個Key目錄(注意要有大括號),目錄名為COM組件的CLSID,默認為:
{3AB4C10E-673C-494c-98A2-CC2E91A48115}
??????????????????????????? ?????? 然后在該目錄下建立一個名為InProcServer32的目錄
最后再InProcServer32目錄下添加一個字符串型的Key,名稱為{3AB4C10E-673C-494c-98A2-CC2E91A48115}
值為mapirule.dll
如果mapirule.dll文件被放置再windows目錄下,則key值只需要填寫mapirule.dll即可,否則這里需要填寫絕對路徑。
然后再找到目錄:
[HKEY_LOCAL_MACHINE\Software\Microsoft\Inbox\Svc\SMS\Rules]
再Rules下添加一個DWORD型key,名稱為(COM組件的CLSID):
{3AB4C10E-673C-494c-98A2-CC2E91A48115}
值為1
?
然后刷新一下注冊表編輯器,查看系統(tǒng)注冊表中是否有如下目錄和鍵值。
目錄:
[HKEY_CLASSES_ROOT\CLSID\{3AB4C10E-673C-494c-98A2-CC2E91A48115}\InProcServer32]
鍵類型:字符串
鍵名稱:{3AB4C10E-673C-494c-98A2-CC2E91A48115}
值:"mapirule.dll"
(如果mapirule.dll不再windows目錄下,則填寫據對路徑)
目錄:
[HKEY_LOCAL_MACHINE\Software\Microsoft\Inbox\Svc\SMS\Rules]
鍵名稱:DWORD
鍵名稱:{3AB4C10E-673C-494c-98A2-CC2E91A48115}"
鍵值:1
??????????????????????????????????? ii.????????????? 第二種方式:cab安裝包中配置inf文件
WM5.0的MapiRule源程序提供了一個cab工程Setupmapirule,這個工程將幫助你建立一個cab包,用于自動安裝和部署mapirule組件,再部署組件的同時,cab包會通過inf文件中的設置自動注冊COM組件。如果不使用程序默認的CLSID,可以通過修改inf文件實現更改注冊到注冊表中的鍵值(源程序中對應CLSID也要修改,并且需要重新編譯源程序)。
????????????????????????????????? iii.????????????? 第三種方式:調用MapiRule中提供的DllRegisterServer函數
如果想在自己的程序中注冊組件,可以通過掉調用序中提供DllRegisterServer函數來實現,該函數的定義如下:
STDAPI DllRegisterServer()
??????????????????????????? STDAPI是一個宏定義,進行宏替換以后,函數定義為:
extern “C” HRESULT __stdcall DllRegisterServer()
函數如果成功過調用則會返回S_OK,否則返回E_FAIL。成功調用該函數后,可以通過注冊表編輯器查看上面提到的注冊表目錄中是否成功添加兩個鍵值。
這里特別提一句,在程序中調用該函數前,不要忘了先從MapiRule.dll中導出該函數,導出方法是再MapiRule工程中的MapiRule.def文件中添加如下語句:
DllRegisterServer PRIVATE
再我們的程序中要通過LoadLibrary和GetProcAddress函數獲取該函數的EntryPoint指針。方法如下:
???
LPFDLLREGISTERSERVER lpfDllRegisterServer;?
typedef HRESULT (*LPFDLLREGISTERSERVER)(void);?????? //定義函數指針類型
//初始化操作
???? HINSTANCE hInst;
???? hInst=LoadLibrary(_T("\\windows\\mapirule.dll"));?????????? //獲取mapirule.dll
???? if(NULL==hInst)????? //沒有找到DLL
???? {
???????? //異常處理
???? }
//導出函數lpfDllRegisterServer=(LPFDLLREGISTERSERVER)GetProcAddress(hInst,_T("DllRegisterServer"));
//調用函數
HRESULT hr=lpfDllRegisterServer();
if(FAILED(hr))
{
??? //異常處理
}
c)??????? 卸載該組件
????????????????????????????????????? i.????????????? 手動刪除注冊表信息
利用遠程注冊表編輯器,刪除下面的Key目錄和鍵值
目錄:
[HKEY_CLASSES_ROOT\CLSID\{3AB4C10E-673C-494c-98A2-CC2E91A48115}\InProcServer32]
鍵類型:字符串
鍵名稱:{3AB4C10E-673C-494c-98A2-CC2E91A48115}
?
目錄:
[HKEY_LOCAL_MACHINE\Software\Microsoft\Inbox\Svc\SMS\Rules]
鍵名稱:DWORD
鍵名稱:
{3AB4C10E-673C-494c-98A2-CC2E91A48115}"
?
重啟設備后,在使用遠程注冊表編輯器,確認上面鍵值是否被刪除。
?
??????????????????????????????????? ii.????????????? 調用MapiRule中提供的DllUnregisterServer函數
MapiRule程序同樣提供了用于取消注冊的函數DllUnregisterServer,函數的聲明如下(宏替換后):
extern “C” HRESULT __stdcall DllUnregisterServer()
同樣,返回S_OK表示運行成功,返回E_FAIL則失敗。調用源程序如下:
typedef HRESULT (*LPFDLLUNREGISTERSERVER)(void);?????? //定義函數指針類型
//初始化操作
?????? HINSTANCE hInst;
LPFUNDLLREGISTERSERVER lpfUnDllRegisterServer;
?
?????? hInst=LoadLibrary(_T("\\windows\\mapirule.dll"));?????????? //獲取mapirule.dll
?????? if(NULL==hInst)????? //沒有找到DLL
?????? {
?????????? //異常處理
?????? }
//導出函數lpfDllUnRegisterServer=(LPFDLUNLREGISTERSERVER)GetProcAddress(hInst,_T("DllUnregisterServer"));
//調用函數
HRESULT hr=lpfDllUnRegisterServer ();
if(FAILED(hr))
{
??? //異常處理
}
?
d)??????? 如何確定MapiRule.dll已經被tmail進程加載?
編譯MapiRule程序,拷貝MapiRule.dll文件到Windows目錄,并成功進行注冊表注冊后,重新啟動WM設備,啟動tmail.exe程序(直接運行設備的“短信”功能即可)。我們還需要查看tmail.exe是否已經成功加載了該MapiRule組件。依次選擇“開始”——“Microsoft Visual Studio 2005”——“Visual Studio Remote Tools”,運行“遠程進程查看器”。建立與設備的連接以后,可以再process列表中找到tmail.exe進程,選中該進程,在最下面的module列表中如果看到mapirule.dll項,則COM組件加載成功。這里要注意,如果沒有找到該項,也不意味著COM組件一定沒有被加載。如果module中沒有該項,也可以通過一個簡單操作來確定mapirule.dll是否被加載,保證tmail.exe進程正在運行,使用ActiveSync同步設備,再Windows目錄下找到mapirule.dll文件,嘗試刪除該文件,如果可以被刪除則加載不成功,否則加載成功。
e)??????? 測試該組件是否能夠正常工作
確保mapiRule.dll已經被tmail.exe進程成功加載后,tmail.exe將獲得截獲短信的能力,如果沒有做修改的話,MapiRule.dll默認會對所有接受到的包含”zzz”的短信進行攔截,并彈出MessageBox提示截獲短信的內容,被截獲的的短信將不會到達收件箱。而其他短信將不會被攔截。
???? 打開Cellular Emulator工具,向仿真器發(fā)送一條包含”zzz”的短信,或者利用WM模擬器的短信功能發(fā)送一條包含”zzz”的信息到14250010001。這時會彈出一個對話框,顯示被截獲的短信信息。
f)???????? 為什么我已經注冊成功了,它卻還不工作?
我在工作過程中遇到過,雖然注冊已經成功,且組件已經嵌入到了tmail.exe進程中,但是該組件卻無法工作的情況。下面三種情況是導致該問題的最常見原因。
????????????????????????????????????? i.????????????? 查看GUID是否與別的組件沖突
前面提到每個COM組件都需要指派一個唯一的ID,也就是GUID,這個ID是由系統(tǒng)開發(fā)人員來指定的,因此不可避免的會出現沖突。利用遠程注冊表編輯器可以查看注冊表中是否有沖突的GUID。啟動編輯器,連接設備,查看“HKEY_CLASSES_ROOT\CLSID”目錄下對應的GUID目錄鍵值是否為mailrule.dll,如果不是那么可能存在其它COM組件使用了該GUID。解決方法是單擊VS編譯器的“工具”——“創(chuàng)建GUID”來生成一個新的GUID,并且將MapiRuile的源程序中所有GUID的位置設置成新的GUID,然后重新編譯源程序。
?
??????????????????????????????????? ii.????????????? 證書
再真實的WM設備中運行應用往往程序需要證書來確保應用程序的安全性,如果沒有提供該證書,可能會導致MapiRule無法編譯或不能正常執(zhí)行。Mcriosoft提供了一個開發(fā)專用的證書,這個證書可以在下面的位置上找到。
D:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Tools\SDKSamplePrivDeveloper.pfx
雙擊運行將證書導入系統(tǒng)后,為MapiRule指派該證書,重新編譯,部署該組件。
????????????????????????????????? iii.????????????? 查看注冊表中是否有其它程序已經為tmail.exe進程注冊過了實現相同接口的組件
這種情況下,GUID雖然沒有被占用,但是程序中有兩個實現短信截獲的組件,我們的MapiRule雖然被正確注冊和加載,但是當短信到達的時候,首先被另一個組件所截獲,從而導致我們的程序無法正常運行。這時可以通過遠程注冊表編輯器,記錄并刪除以下目錄中除我們的GUID以外的所有鍵值。
[HKEY_LOCAL_MACHINE\Software\Microsoft\Inbox\Svc\SMS\Rules]
然后再[HKEY_CLASSES_ROOT\CLSID]這里找到并刪除剛才記錄下的鍵值所對應的目錄。最后,重新啟動設備或仿真器。
?
3.????? 這么多代碼,我不知道該從那看起?(別急,我們只需要了解其中幾個關鍵的地方就好)
a)??????? 什么是GUID?如何產生新的GUID?
關于GUID的詳細解釋,請查看COM技術的相關書籍,這里我們只需知道GUID是使tmail.exe找到并加載我們的組件的唯一標志。單擊VS編譯器的“工具”——“創(chuàng)建GUID”可以生成一個新的GUID,生成的GUID不能保證唯一,這一點要特別注意。生成GUID以后,別忘了替換源程序中的對應位置。
b)??????? 攔截短信的原理是什么?
短信攔截實際上是利用COM技術對tmail.exe功能進行擴展。像所有com程序一樣,系統(tǒng)提供了一個接口(協(xié)議)來讓外部程序實現,外部程序通過實現該接口來為tmail.exe增加新的功能。CMapiClient是一組接口,當短信到達本地設備的時候,系統(tǒng)會調用一系列的函數獲取并加載COM組件,然后調用ProcessMessage方法來處理到達的短消息,這個過程由系統(tǒng)實現,除了ProcessMessage方法以外不需要我們來寫任何程序(其實獲取獲取ObjectId和QueryInterface等方法也需要程序員去寫,但是基本都是固定的形式,因此直接復制就OK了,MapiRule源程序中提供了這些方法)。我們可以再ProcessMessage中對收到的短信進行我們希望的操作。該方法的定義如下:
HRESULT CMailRuleClient::ProcessMessage(IMsgStore *pMsgStore, ULONG cbMsg, LPENTRYID lpMsg, ULONG cbDestFolder, LPENTRYID lpDestFolder, ULONG *pulEventType, MRCHANDLED *pHandled)
這里我們主要關心前三個參數即可,第一個參數代表指向短信(郵件)倉庫的指針,后兩個參數表示被截獲短信的ENTRYID。參數cbDestFolder和lpDestFolder指定了該條短信將被發(fā)送到的目的信箱(Folder)的ENTRYID,這里指向的一定是收件箱,從這里我們可以隱約猜到,截獲短信只是ProcessMessage方法的其中一個功能,其它功能有興趣的朋友可以挖掘一下。倒數第二個參數pulEventType用于設置短信變化的事件,通過為它賦值,可以實現當該短信被刪除等操作時我們可以通過上一篇文章提供的方法獲取到對應的通知(Notify)。
MapiRule中提供的實現中有一句代碼需要注意。
hr = pMsgStore->OpenEntry(cbMsg, lpMsg, NULL, 0, NULL, (LPUNKNOWN *) &pMsg);
從上面的代碼中可以看出,被攔截的消息是從短信(郵件)倉庫中直接獲取的,而不是具體信箱。這跟我們前面使用IMAPIFolder對象獲取短信的方法有所不同,這也間接的提示了我們ProcessMessage被調用的時機。即被攔截的短信并已經到達了短信郵件倉庫,但并未被分配到具體的短信信箱(Folder)中時被調用。
c)??????? 被攔截到的短信跑哪去了?
細心的朋友應該發(fā)現,被攔截的短信沒有在系統(tǒng)收件箱中出現,通過瀏覽ProcessMessage中的代碼,我們可以找到如下代碼。
hr = DeleteMessage(pMsgStore, pMsg, cbMsg, lpMsg, cbDestFolder, lpDestFolder, pulEventType, pHandled);
這行代碼幫助我們刪除掉了被截獲的短消息,其參數與ProcessMessage相同,這里不再贅述。
4.????? 我想再我自己的程序中獲取被攔截到的短消息,如何實現呢?
a)??????? 外部程序獲取截獲消息信息的思路
短信被mapirule截獲后,我們希望在自己的程序中獲取被截獲的短信信息,但是由于mapirule.dll和我們的程序不在同一個進程中,因此無法直接將獲取的信息反饋給我們的程序。進程間通訊的方法有很多種,比如PostMessage,xml等流文件,管道,內存映射文件等。在微軟提供的ReceivingSMS中給出的方法是采用內存映射文件的方式,因此我們也使用內存映射文件的方式來實現進程間數據的傳遞。簡單的思路是,首先創(chuàng)建內存映射文件,然后再ProcessMessage方法被調用時(短信被截獲時)像映射文件中寫入截獲的內容,然后通過指針傳遞給我們的程序,在程序不再需要內存映射文件的時候,刪除該文件。
b)??????? 微軟已經給我提供實現這個思路的部分源程序,可以直接從ReceivingSMS這個Demo中獲得。
微軟在ReceivingSMS中給出了實現上述思路的方法,該方法由如下3個函數組成。
typedef struct {
????? WCHAR g_szMessageBody[255];
????? WCHAR g_szPhoneNr[255];
} SMS_BUFFER;
?
typedef SMS_BUFFER *PSMS_BUFFER;
?
HANDLE g_hMMObj = NULL;
PSMS_BUFFER g_pSmsBuffer = NULL;
?
HANDLE g_hSmsAvailableEvent = NULL;
HANDLE g_hMutex = NULL;
?
EXTERN_C void CaptureSMSMessages (void)
{
????? g_hClientEvent = CreateEvent(NULL, FALSE, FALSE, _T("SMSAvailableEvent"));
????? assert(g_hClientEvent != NULL);
????? g_hClientMutex = CreateMutex(NULL, FALSE, _T("SMSDataMutex"));
????? assert(g_hClientMutex != NULL);
?
????? g_hClientMMObj = CreateFileMapping((HANDLE)-1, NULL, PAGE_READWRITE, 0, MMBUFSIZE, TEXT("SmsBuffer"));
????? assert(g_hClientMMObj != NULL);
?
???? g_pClientSmsBuffer = (PSMS_BUFFER)MapViewOfFile(g_hClientMMObj, FILE_MAP_WRITE, 0, 0, 0);
????? if (g_pClientSmsBuffer == NULL) {
???????????? CloseHandle(g_hClientMMObj);
????? }
????? assert(g_pClientSmsBuffer != NULL);
?
}
?
?
EXTERN_C BOOL SMSMessageAvailable (wchar_t *lpDestination, wchar_t *lpPhoneNr)
{
????? WaitForSingleObject(g_hClientEvent, INFINITE);
?
????? if (g_pClientSmsBuffer != NULL) {
???????????? WaitForSingleObject(g_hClientMutex, INFINITE);
???????????? lstrcpy(lpPhoneNr, g_pClientSmsBuffer->g_szPhoneNr);
???????????? lstrcpy(lpDestination, g_pClientSmsBuffer->g_szMessageBody);
???????????? ReleaseMutex(g_hClientMutex);
????? } else {
???????????? *lpPhoneNr = '\0';
???????????? *lpDestination = '\0';
????? }
????? return *lpPhoneNr != '\0';
}
?
?
EXTERN_C void TerminateSMSMessagePassing (void)
{
????? memset(g_pClientSmsBuffer, 0, sizeof(SMS_BUFFER));
?
????? SetEvent(g_hClientEvent);???
????? CloseHandle(g_hClientEvent);
????? CloseHandle(g_hClientMutex);
?
????? if (g_pClientSmsBuffer) {
???????????? UnmapViewOfFile(g_pClientSmsBuffer);
???????????? g_pClientSmsBuffer = NULL;
????? }
????? if (g_hClientMMObj) {
???????????? CloseHandle(g_hClientMMObj);
???????????? g_hClientMMObj = NULL;
????? }
}
CaptureSMSMessages函數創(chuàng)建了內存映射文件、互斥資源g_hMutex、以及阻塞事件hSmsAvailableEvent。SMSMessageAvailable函數用于為外部程序返回我們所截獲的短信內容,注意WaitForSingleObject這行代碼,當程序運行這里會等待事件被設置(事件會在ProcessMessage截獲到短信后使用SetEvent函數來設置),否則會阻塞等待,這也提示了我們,在外部程序中需要使用工作線程來調用該函數。 TerminateSMSMessagePassing用于刪除內存映射文件以及事件和互斥資源。
在ProcessMessage截獲短信后和刪除短信前,增加了如下代碼:
WaitForSingleObject(g_hMutex, INFINITE);
???????????? lstrcpy(g_pSmsBuffer->g_szPhoneNr, pspvEmail->Value.lpszW);
lstrcpy(g_pSmsBuffer->g_szMessageBody, pspvSubject->Value.lpszW);
???????????? ReleaseMutex(g_hMutex);
????????????? ?????? SetEvent(g_hSmsAvailableEvent);
?????? 這段代碼將截獲到的短信信息拷貝到內存映射文件中,并設置事件,取消SMSMessageAvailable函數的阻塞等待狀態(tài)。
c)??????? 如果你是CSharp的開發(fā)者,那么恭喜你ReceivingSms就是一個CSharp調用MapiRule攔截短消息的Demo,你可以直接使用了。如果你是C++的開發(fā)者,那么我們還需要做些工作。
將前面的三個函數添加到源程序中(別忘了再def文件中聲明導出),重新編譯并部署以后,就可以在我們的程序中調用了。使用LoadLibrary和GetProcAddress函數導出上面的三個函數,然后調用CaptureSMSMessages函數初始化內存映射文件和資源,創(chuàng)建一個線程,在線程中調用
SMSMessageAvailable函數。當短信到達時,短信內容會由該函數返回。當使用完畢后不要忘了調用TerminateSMSMessagePassing函數刪除資源。這部分的源代碼如下:
?
//初始化操作
HINSTANCE hInst;
HANDLE hThread;
hInst=LoadLibrary(_T("\\windows\\mapirule.dll"));?????????? //獲取mapirule.dll
if(NULL==hInst)????? //沒有找到DLL
{
??? return CMsgResult(_T("加載MapiRule.dll失敗!沒有找到DLL"),_T("CMsgControl->BeginMonitor"),ERR_LOAD_DLL);
}
//導出函數
lpfCaptureSmsMessages=(LPFCAPTURESMSMESSAGES)GetProcAddress(hInst,_T("CaptureSMSMessages"));
lpfSmsMessageAvailable=(LPFSMSMESSAGEAVAILABLE)GetProcAddress(hInst,_T("SMSMessageAvailable"));
lpfTermainateSmsMessagePassing=(LPFTERMINATESMSMESSAGEPASSING)GetProcAddress(hInst,_T("TerminateSMSMessagePassing"));
hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,(LPVOID)&stThreadParameter,NULL,&dwThreadId);
?
線程處理函數ThreadProc定義如下:
ThreadProc(LPVOID lpParam)???????? //進程函數
{
??? wchar_t pswBody[360]={0};
??? wchar_t pswPhone[20]={0};
bool bRet=lpfSmsMessageAvailable(pswBody,pswPhone);???? //阻塞等待
??? return 0;
}
?
轉載于:https://www.cnblogs.com/wude/archive/2009/03/29/1941604.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Cempi实战攻略(六)——如何截获到达的短消息的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: equals和==
- 下一篇: Sql 中取小数点后面两位小数.