正常情況下在Richedit中使用OLE,如果需要OLE支持復制粘貼,那么這個OLE對象必須是已經注冊的COM對象。
注冊COM很簡單,關鍵問題在於注冊時需要管理員權限,這樣一來,如果希望APP做成綠色版本就不好使了。
為什么需要注冊成COM?因為在粘貼時Richedit需要能夠從COM對象的GUID實例化出你的OLE對象。
從一個COM的GUID創建一個COM對象,必然需要通過CoCreateInstance(Ex)這個系統API。那么我們是不是只要Hook到這個API就可以不需要注冊了呢?
通過Hook CoCreateInstance,我們發現創建已經注冊的COM確實會到CoCreateInstance這個API里來。然而在試圖粘貼未注冊的COM對象時,確並沒有走到自己Hook的CoCreateInstance,粘貼並沒有成功。
為什么呢?
既然是粘貼,我們可以先看一下剪貼板里有些什么東西,隨便找一個剪貼板查看的工具,看一下里面的RTF格式里有些什么東西。當復制的是注冊的COM時,RTF里有這個COM的字符串ID,而當復制沒有注冊的COM時,剪貼板里沒有這些信息。
問題出在哪呢?
有Richedit的原代碼就好了。
正好有一份Wince里的Richedit的源代碼,通過分析Richedit的復制的代碼,可以發現在復制OLE對象時,先要調用ProgIDFromCLSID來查詢這個對象的ProgID。
如果一個OLE對象沒有注冊,那么ProgIDFromCLSID會返回失敗,從而導致復制階段就失敗了。
知道了這個流程就好辦了,繼續HOOK,把ProgIDFromCLSID加到HOOK表就好了。
HOOK了這個函數后發現粘貼時能夠執行CoCreateInstance了,我們也可以自己實例化這個等待粘貼的對象了,但事實是還是粘貼失敗了,因為在執行這個對象的Load方法前還沒有設置ClientSite對象,而我的這個表情對象需要從這個ClintSite來QueryInterface出一個自己定義的接口,沒有這個接口對象就沒有辦法初始化。
為什么呢?為什么呢?
如果有Windows的原代碼查一下就好了。
對了,可以看看Wine里OleLoad是怎么做的(通過調用棧可以知道Richedit里直接調用的是OleLoad)。Wine是一個在Linux上運行Windows程序的開源框架,里面有各種Windows API的實現,雖然和Windows還是不一樣,但大體流程差不多了。
https://source.winehq.org/ 這個網站不錯,想看哪個API的實現搜索一下就出來。
/****************************************************************************** * OleLoad [OLE32.@] */ HRESULT WINAPI OleLoad( LPSTORAGE pStg, REFIID riid, LPOLECLIENTSITE pClientSite, LPVOID* ppvObj) { IPersistStorage* persistStorage = NULL; IUnknown* pUnk; IOleObject* pOleObject = NULL; STATSTG storageInfo; HRESULT hres; TRACE("(%p, %s, %p, %p)\n", pStg, debugstr_guid(riid), pClientSite, ppvObj); *ppvObj = NULL; /* * TODO, Conversion ... OleDoAutoConvert */ /* * Get the class ID for the object. */ hres = IStorage_Stat(pStg, &storageInfo, STATFLAG_NONAME); if (FAILED(hres)) return hres; /* * Now, try and create the handler for the object */ hres = CoCreateInstance(&storageInfo.clsid, NULL, CLSCTX_INPROC_HANDLER|CLSCTX_INPROC_SERVER, riid, (void**)&pUnk); /* * If that fails, as it will most times, load the default * OLE handler. */ if (FAILED(hres)) { hres = OleCreateDefaultHandler(&storageInfo.clsid, NULL, riid, (void**)&pUnk); } /* * If we couldn't find a handler... this is bad. Abort the whole thing. */ if (FAILED(hres)) return hres; if (pClientSite) { hres = IUnknown_QueryInterface(pUnk, &IID_IOleObject, (void **)&pOleObject); if (SUCCEEDED(hres)) { DWORD dwStatus; hres = IOleObject_GetMiscStatus(pOleObject, DVASPECT_CONTENT, &dwStatus); } } /* * Initialize the object with its IPersistStorage interface. */ hres = IUnknown_QueryInterface(pUnk, &IID_IPersistStorage, (void**)&persistStorage); if (SUCCEEDED(hres)) { hres = IPersistStorage_Load(persistStorage, pStg); IPersistStorage_Release(persistStorage); persistStorage = NULL; } if (SUCCEEDED(hres) && pClientSite) /* * Inform the new object of its client site. */ hres = IOleObject_SetClientSite(pOleObject, pClientSite); /* * Cleanup interfaces used internally */ if (pOleObject) IOleObject_Release(pOleObject); if (SUCCEEDED(hres)) { IOleLink *pOleLink; HRESULT hres1; hres1 = IUnknown_QueryInterface(pUnk, &IID_IOleLink, (void **)&pOleLink); if (SUCCEEDED(hres1)) { FIXME("handle OLE link\n"); IOleLink_Release(pOleLink); } } if (FAILED(hres)) { IUnknown_Release(pUnk); pUnk = NULL; } *ppvObj = pUnk; return hres; }
上面是Wine 1.9.4里OleLoad的源代碼。
可以看到在執行IPersistStorage_Load前會先調用IOleObject_GetMiscStatus這個方法,然而從代碼來看它並沒有什么用啊?
哦,我差點忘記了,這是Wine,並不是真正的Windows的代碼。趕緊查一下IOleObject_GetMiscStatus應該返回什么。
不看不知道,一看嚇一跳,原來這里有一個OLEMISC_SETCLIENTSITEFIRST這個標志,看名字就知道有這個標志時,會先調用SetClientSite再調用Load。
好了,改寫OLE的這個方法,不去查注冊表,直接返回這個標志就好了。
到這里,一個不需要注冊的OLE對象就完成了。
寫這么多,希望看到的人能夠有所啟發。