雖然這裡一片的.net氣氛,到處充斥着像MVC、WPF、WorkFlow、LINQ等各種niubility的術語。但我們使用的Windows還是由COM技術主宰着;我們在選擇日常使用的軟體時,也會避免使用.net開發的軟體。即便是.net的桌面程式,也會經常使用ActiveX控件。這篇文章就讓我們用最原始的方式來使用ActiveX,不使用任何MFC,ATL等架構,也不使用編譯器提供的#import之類的指令,也不使用任何ide提供的向導。
像OLE、ActiveX等COM的術語,即便是微軟也說不清它們的關系,是以下面說的我也這樣模棱兩可下去,隻要明白意思即可。
首先,要了解一下的是ActiveX技術是為了做“嵌入”這樣的功能而誕生的,比如:在Word中插入一張Bitmap圖檔,輕按兩下此圖檔,Word會調用畫筆程式的功能來編輯圖檔,整個Word的菜單欄也會變成畫筆程式的菜單欄。是以,ActiveX控件相當的複雜,有着幾十個相關的接口。簡單的說來,ActiveX控件的父視窗被稱為“容器”,是以作為ActiveX控件的使用者來說,要實作的接口基本上都是IOleXXXXContainer或IOleXXXXSite之類的;而ActiveX則實作了IOleXXXXObject等接口。在這些接口中,大多有“InPlace”這個術語,指的是“InPlace edit”,也就是Word通過輕按兩下圖檔調用畫筆編輯圖檔就稱為“InPlace edit”(僅僅了解一下,和這篇文章說的使用AcitveX控件無關)。
在這樣一篇文章中,我并不想講很多COM或者AcitveX的知識,隻是講使用ActiveX所必須涉及的接口,然後你就可以去查MSDN中的其他一些可選的接口來一步步對這個ActiveX加強控制。
作為一個最簡單的程式,我們需要實作的接口有:
IOleClientSite 和 IOleInPlaceSite 。使用到的AcitveX提供的接口有: IOleObject IOleInPlaceObject 。建立ActiveX控件的步驟:- 建立一個類,實作 。
- 使用CoCreateInstance建立相應ActiveX控件的執行個體,并擷取它的 接口指針。
- 調用 IOleObject::SetClientSite 傳入第一步中的類的指針。
- IOleObject::DoVerb 完成ActiveX控件的建立。
- 之後,可以調用ActiveX控件的 IOleInPlaceObject::SetObjectRects 調整控件的大小和位置。
根據以上步驟,建立如下函數:
HRESULT CreateAxControl(HWND hWnd,const wchar_t * ProgId,IUnknown ** ppControlUnknown,IUnknown ** ppContainerUnknown);
第一個參數hWnd是父視窗句柄。ProgId是ActiveX控件的ProgId,因為我們不使用編譯器的#import,一般不知道所要建立控件的CLSID。ppControlUnknown是用來傳回ActiveX控件的IUnknown指針。ppContainerUnknown是用來傳回用來代表父視窗的IUnknown指針。
代碼如下(這裡的代碼去除了出錯的處理):

HRESULT CreateAxControl(HWND hWnd,const wchar_t * ProgId,IUnknown ** ppControlUnknown,IUnknown ** ppContainerUnknown)
{
HRESULT hr;
CLSID cls;
IOleObject * pObject = NULL;
CControlContainer * pContainer = NULL;
//通過ProgId得到CLSID
CLSIDFromProgID(ProgId,&cls);
//建立ActiveX控件的對象,順便得到IOleObject指針
CoCreateInstance(cls,NULL,CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER|CLSCTX_LOCAL_SERVER,
IID_IOleObject,(void**)&pObject);
//CControlContainer是實作了IOleClientSite和IOleInPlaceSite接口的類
pContainer = new CControlContainer(hWnd);
//調用IOleObject::SetClientSite,傳入容器指針
pObject->SetClientSite(pContainer);
//調用IOleObject::DoVerb,顯示控件
pObject->DoVerb(OLEIVERB_SHOW,0,pContainer,0,hWnd,0);
//一些傳回的參數
pObject->QueryInterface(IID_IUnknown,(void**)ppControlUnknown);
pContainer->QueryInterface(IID_IUnknown,(void**)ppContainerUnknown);
if (pObject) pObject->Release();
if (pContainer) pContainer->Release();
return S_OK;
}

CControlContainer類的實作非常簡單,基本上IOleClientSite和IOleInPlaceSite接口大部分的方法都隻要簡單的傳回S_OK或E_NOTIMPLE即可。唯一需要實作的是IUnknown的方法,還有IOleWindow接口(IOleInPlaceSite繼承于IOleWindow)的GetWindow(傳回父視窗的句柄)。代碼如下:

class CControlContainer:public IOleClientSite,public IOleInPlaceSite
{
HWND m_hWnd;
ULONG m_refCnt;
public:
CControlContainer(HWND hWnd)
{
m_hWnd = hWnd;
m_refCnt = 1;
}
~CControlContainer()
{
}
.... IUnknown的實作
//IOleControlSite
STDMETHOD(SaveObject())
{
return E_NOTIMPL;
}
STDMETHOD(GetMoniker(DWORD,DWORD,IMoniker**))
{
return E_NOTIMPL;
}
STDMETHOD(GetContainer(IOleContainer **ppContainer))
{
return E_NOINTERFACE;
}
STDMETHOD(ShowObject())
{
return S_OK;
}
STDMETHOD(OnShowWindow(BOOL bShow))
{
return S_OK;
}
STDMETHOD(RequestNewObjectLayout())
{
return E_NOTIMPL;
}
//IOleWindow
STDMETHOD(GetWindow(HWND * pHwnd))
{
*pHwnd = m_hWnd;
return S_OK;
}
STDMETHOD(ContextSensitiveHelp(BOOL bEnterMode))
{
return S_OK;
}
//IOleInPlaceSite
STDMETHOD(CanInPlaceActivate())
{
return S_OK;
}
STDMETHOD(OnInPlaceActivate())
{
return S_OK;
}
STDMETHOD(OnUIActivate())
{
return S_OK;
}
STDMETHOD(GetWindowContext(/* [out] */ IOleInPlaceFrame **ppFrame,
/* [out] */ IOleInPlaceUIWindow **ppDoc,
/* [out] */ LPRECT lprcPosRect,
/* [out] */ LPRECT lprcClipRect,
/* [out][in] */ LPOLEINPLACEFRAMEINFO lpFrameInfo))
{
return E_NOTIMPL;
}
STDMETHOD(Scroll(SIZE scrollSize))
{
return S_OK;
}
STDMETHOD(OnUIDeactivate(BOOL bUndoable))
{
return S_OK;
}
STDMETHOD(OnInPlaceDeactivate())
{
return S_OK;
}
STDMETHOD(DiscardUndoState())
{
return S_OK;
}
STDMETHOD(DeactivateAndUndo())
{
return S_OK;
}
STDMETHOD(OnPosRectChange(LPCRECT lprcPosRect))
{
return S_OK;
}
};

接下來,在父視窗的視窗過程中,調用上面實作的函數,建立一個Flash控件:

IUnknown * g_pControl = NULL;//控件的指針
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
....
switch(message)
{
case WM_CREATE:
//Flash控件
hr = CreateAxControl(hWnd,L"ShockwaveFlash.ShockwaveFlash",&pControl,&pContainer);
if (SUCCESSED(hr))
{
VARIANT src;
src.vt = VT_BSTR;
src.bstrVal = SysAllocString(L"http://www.google.com/intl/en_ALL/images/logo.gif");
DispSetProperty(pControl,L"movie",&src);//這個函數的實作,請下載下傳源代碼
}
break;
case WM_SIZE:
{
//調整控件的大小
RECT rcClient;
GetClientRect(hWnd,&rcClient);
IOleInPlaceObject * pInPlaceObject;
if (g_pControl &&
SUCCEEDED(g_pControl->QueryInterface(IID_IOleInPlaceObject,(void**)&pInPlaceObject)))
{
pInPlaceObject->SetObjectRects(&rcClient,&rcClient);
pInPlaceObject->Release();
}
}
break;
}
....
}

在下一篇,可能會寫如何響應ActiveX的Dispatch事件。