不注冊COM在Richedit中使OLE支持復制粘貼


正常情況下在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對象就完成了。

 

寫這么多,希望看到的人能夠有所啟發。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM