ActiveX Controls for Embedded Visual Basic by Steve Lardieri Example 1: (a) #define _WIN32_WINNT 0x0400 #define _ATL_FREE_THREADED #if defined(_WIN32_WCE) #undef _WIN32_WINNT #endif (b) #if defined(_WIN32_WCE) #define _ATL_FREE_THREADED #else // desktop build #define _WIN32_WINNT 0x0400 #define _ATL_APARTMENT_THREADED #endif Example 2: (a) val ThreadingModel = s 'Free' (b) val ThreadingModel = s 'Both' Example 3: Dim X(3) X(1) = 20 X(2) = 3.14 X(3) = "Hello world" Listing One // Sample.h : Definition of CSample #include "resource.h" // main symbols #include class ATL_NO_VTABLE CSample : public CComObjectRootEx, public IDispatchImpl, public CComControl, public IPersistStreamInitImpl, public IOleControlImpl, public IOleObjectImpl, public IOleInPlaceActiveObjectImpl, public IViewObjectExImpl, public IOleInPlaceObjectWindowlessImpl, public ISupportErrorInfo, public IConnectionPointContainerImpl, public IPersistStorageImpl, public ISpecifyPropertyPagesImpl, public IQuickActivateImpl, public IDataObjectImpl, public IProvideClassInfo2Impl<&CLSID_Sample, &DIID__ISampleEvents, &LIBID_SAMPLEACTIVEXCONTROLLib>, public IPropertyNotifySinkCP, public CComCoClass, // Added to support property bag persistence: public IPersistPropertyBagImpl { public: CSample() { m_bWindowOnly = TRUE; // Added to demonstrate using plain BSTR (as opposed to CComBSTR) // for string-valued property: #if USE_PLAIN_BSTR bstrSampleString = NULL; #endif // Added to demonstrate custom property persistence: plSampleArray = new long[1]; plSampleArray[0] = 12345; countSampleArray = 1; // Added to demonstrate ambient properties: bAutomaticSIP = FALSE; } ~CSample() { // If we use CComBSTR, the destructor does this automatically... #if USE_PLAIN_BSTR if (bstrSampleString) #endif // Free up our array-valued property. if (plSampleArray) delete [] plSampleArray; } DECLARE_REGISTRY_RESOURCEID(IDR_SAMPLE) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CSample) COM_INTERFACE_ENTRY(ISample) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IViewObjectEx) COM_INTERFACE_ENTRY(IViewObject2) COM_INTERFACE_ENTRY(IViewObject) COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY(IOleInPlaceObject) COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY(IOleInPlaceActiveObject) COM_INTERFACE_ENTRY(IOleControl) COM_INTERFACE_ENTRY(IOleObject) COM_INTERFACE_ENTRY(IPersistStreamInit) // added to support property bag persistence: COM_INTERFACE_ENTRY(IPersistPropertyBag) // changed to support property bag persistence: COM_INTERFACE_ENTRY2(IPersist, IPersistPropertyBag) COM_INTERFACE_ENTRY(ISupportErrorInfo) COM_INTERFACE_ENTRY(IConnectionPointContainer) COM_INTERFACE_ENTRY(ISpecifyPropertyPages) COM_INTERFACE_ENTRY(IQuickActivate) COM_INTERFACE_ENTRY(IPersistStorage) COM_INTERFACE_ENTRY(IDataObject) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2) END_COM_MAP() BEGIN_PROP_MAP(CSample) PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4) PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4) // added to make IPersistPropertyBagImpl<> persist this property: PROP_ENTRY("SampleString", // property name 1, // dispid CLSID_NULL) // no custom property page END_PROP_MAP() BEGIN_CONNECTION_POINT_MAP(CSample) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) END_CONNECTION_POINT_MAP() BEGIN_MSG_MAP(CSample) CHAIN_MSG_MAP(CComControl) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() // ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) { if (InlineIsEqualGUID(IID_ISample, riid)) return S_OK; else return S_FALSE; } // IViewObjectEx DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE) // Drawing: public: HRESULT OnDraw(ATL_DRAWINFO& di); // Added to demonstrate string-valued properties: public: STDMETHOD(get_SampleString)(BSTR *pVal); STDMETHOD(put_SampleString)(BSTR newVal); private: #if USE_PLAIN_BSTR BSTR bstrSampleString; #else CComBSTR bstrSampleString; #endif // Added to demonstrate array-valued properties: public: STDMETHOD(get_SampleArray)(VARIANT *pVal); STDMETHOD(put_SampleArray)(VARIANT newVal); private: long * plSampleArray; long countSampleArray; // Added to demonstrate custom property bag persistence: public: STDMETHOD(Save)(LPPROPERTYBAG pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties); STDMETHOD(Load)(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog); // Added to demonstrate raising custom errors: public: STDMETHOD(RaiseError)(long number, BSTR message); // Added to demonstrate using ambient properties: public: STDMETHOD(OnAmbientPropertyChange)(DISPID dispid); STDMETHOD(SetClientSite)(IOleClientSite *pClientSite); private: BOOL bAutomaticSIP; CComBSTR bstrDisplayName; }; Listing Two // Sample.cpp : Implementation of CSample #include "stdafx.h" #include "Sample ActiveX Control.h" #include "Sample.h" // Implementation of string-valued property, SampleString: #if USE_PLAIN_BSTR // bstrSampleString is a plain BSTR, not a CComBSTR. STDMETHODIMP CSample::get_SampleString(BSTR *pVal) { if (IsBadWritePtr(pVal, sizeof(*pVal))) return E_POINTER; *pVal = ::SysAllocString(bstrSampleString); return S_OK; } STDMETHODIMP CSample::put_SampleString(BSTR newVal) { if (bstrSampleString) ::SysFreeString(bstrSampleString); bstrSampleString = ::SysAllocString(newVal); return S_OK; } #else // bstrSampleString is a CComBSTR smart string. STDMETHODIMP CSample::get_SampleString(BSTR *pVal) { if (IsBadWritePtr(pVal, sizeof(*pVal))) return E_POINTER; *pVal = bstrSampleString.Copy(); return S_OK; } STDMETHODIMP CSample::put_SampleString(BSTR newVal) { bstrSampleString = newVal; // See CComBSTR::operator= return S_OK; } #endif // Drawing. Notice conversion of bstrDisplayName for Windows 98. HRESULT CSample::OnDraw(ATL_DRAWINFO& di) { RECT& rc = *(RECT*)di.prcBounds; HBRUSH hBrush, hOldBrush; hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH); hOldBrush = (HBRUSH)SelectObject(di.hdcDraw, hBrush); Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom); SelectObject(di.hdcDraw, hOldBrush); USES_CONVERSION; LPCTSTR pszText = OLE2T(bstrDisplayName); DrawText(di.hdcDraw, pszText, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); return S_OK; } // Implementation of array-valued property, SampleArray: STDMETHODIMP CSample::get_SampleArray(VARIANT *pVal) { // Make sure pVal points to a valid variant. if (IsBadWritePtr(pVal, sizeof(*pVal))) return E_POINTER; // Make sure we actually have an array to return. if (plSampleArray == NULL) return E_FAIL; // Allocate a SafeArray of the same size as our C++ array. SAFEARRAY * safeArray; SAFEARRAYBOUND arrayBounds; arrayBounds.lLbound = 0; // lower bound arrayBounds.cElements = countSampleArray; // element count safeArray = SafeArrayCreate(VT_VARIANT, 1, &arrayBounds); if (safeArray == NULL) return E_OUTOFMEMORY; // Copy elements from C++ array to SafeArray. for (long i = 0; i < countSampleArray; i++) { CComVariant var(plSampleArray[i]); SafeArrayPutElement(safeArray, &i, &var); } // Package the SafeArray into the variant. pVal->vt = VT_VARIANT | VT_ARRAY; pVal->parray = safeArray; return S_OK; } STDMETHODIMP CSample::put_SampleArray(VARIANT newVal) { SAFEARRAY * safeArray; // Our single variant parameter points to a SafeArray of variants. if (newVal.vt == (VT_ARRAY | VT_VARIANT | VT_BYREF)) safeArray = *(newVal.pparray); else return E_UNEXPECTED; // Find out how many elements the SafeArray contains. long lowerBound, upperBound; SafeArrayGetLBound(safeArray, 1, &lowerBound); SafeArrayGetUBound(safeArray, 1, &upperBound); countSampleArray = upperBound - lowerBound + 1; // Allocate a C++ array of the same size as the SafeArray. if (plSampleArray) delete [] plSampleArray; plSampleArray = new long[countSampleArray]; if (plSampleArray == NULL) return E_OUTOFMEMORY; // Copy elements from the SafeArray to the C++ array. for (long i = lowerBound; i <= upperBound ; i++) { CComVariant var; // smart wrapper class for VARIANT SafeArrayGetElement(safeArray, &i, &var); if (FAILED(var.ChangeType(VT_I4))) // VT_I4 is "long" return DISP_E_TYPEMISMATCH; plSampleArray[i - lowerBound] = var.lVal; } return S_OK; } // This will persist our array-valued property to a property bag. STDMETHODIMP CSample::Save(LPPROPERTYBAG pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties) { if (countSampleArray > 0) { // Allocate a SafeArray that is one larger than our C++ array. SAFEARRAYBOUND arrayBounds; arrayBounds.lLbound = 0; // lower bound arrayBounds.cElements = countSampleArray + 1; // element count safeArray = SafeArrayCreate(VT_I4, 1, &arrayBounds); if (safeArray == NULL) return E_OUTOFMEMORY; // Store the size of our C++ array, in bytes, in the first element // of the SafeArray. long i = 0; long bytes = countSampleArray * sizeof(long); SafeArrayPutElement(safeArray, &i, &bytes); // Copy the array elements. Notice plSampleArray[0] goes into // SafeArray element 1, and so on. for (i = 1; i <= countSampleArray; i++) SafeArrayPutElement(safeArray, &i, plSampleArray + i - 1); // Package the array into the variant. VARIANT var; var.vt = VT_BLOB; // Get a pointer to the data inside the SafeArray. // This data is in the format of a "blob." SafeArrayAccessData(safeArray, &var.byref); // Try writing the blob to the property bag. HRESULT hr = pPropBag->Write(OLESTR("SampleArray"), &var); // Release the pointer. SafeArrayUnaccessData(safeArray); // If the blob didn't work, try writing the SafeArray instead. if (E_INVALIDARG == hr) { var.vt = VT_ARRAY | VT_I4; var.parray = safeArray; pPropBag->Write(OLESTR("SampleArray"), &var); } // Release the SafeArray. SafeArrayDestroy(safeArray); } // Let ATL handle the other properties. return IPersistPropertyBagImpl:: Save(pPropBag, fClearDirty, fSaveAllProperties); } // This will retrieve our array-valued property from a property bag. STDMETHODIMP CSample::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog) { CComVariant var; long i; SAFEARRAY * safeArray; HRESULT hr = pPropBag->Read(OLESTR("SampleArray"), &var, pErrorLog); if (SUCCEEDED(hr)) { // The variant will either be a SafeArray of longs or a blob. switch (var.vt) { case (VT_ARRAY | VT_I4): safeArray = var.parray; SafeArrayGetUBound(safeArray, 1, &countSampleArray); // Allocate our own array of longs. if (plSampleArray) delete [] plSampleArray; plSampleArray = new long[countSampleArray]; if (plSampleArray == NULL) return E_OUTOFMEMORY; // Copy the array. for (i = 1; i <= countSampleArray; i++) SafeArrayGetElement(safeArray, &i, plSampleArray + i - 1); break; case VT_BLOB: countSampleArray = var.plVal[0] / sizeof(long); // Allocate our own array of longs. if (plSampleArray) delete [] plSampleArray; plSampleArray = new long[countSampleArray]; if (plSampleArray == NULL) return E_OUTOFMEMORY; // Copy the array. for (i = 0; i < countSampleArray; i++) plSampleArray[i] = var.plVal[i + 1]; break; default: return DISP_E_TYPEMISMATCH; } } // Let ATL handle the other properties. return IPersistPropertyBagImpl::Load(pPropBag, pErrorLog); } // This will raise an arbitrary eVB error. // Err.Number will be set to number and Err.Description will be set to message. STDMETHODIMP CSample::RaiseError(long number, BSTR message) { HRESULT hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_CONTROL, number); return Error(message, IID_ISample, hr); } // This lets us know when we can start requesting ambient properties. STDMETHODIMP CSample::SetClientSite(IOleClientSite *pClientSite) { // Call base class method that we are overriding. // This sets up m_spAmbientDispatch. HRESULT hr = IOleObjectImpl::SetClientSite(pClientSite); if (SUCCEEDED(hr) && pClientSite) // pClientSite may be NULL if we are shutting down. { // If we are running in design mode, get our name BOOL bRuntime = TRUE; GetAmbientUserMode(bRuntime); if (!bRuntime) { GetAmbientDisplayName(bstrDisplayName.m_str); // If we are running on a Pocket PC, test for automatic SIP behavior. CComVariant var; if (SUCCEEDED( // will only succeed on a Pocket PC m_spAmbientDispatch.GetPropertyByName(L"SIPBehavior", & var))) { var.ChangeType(VT_I4); if (1 /* vbSIPAutomatic */ == var.lVal) bAutomaticSIP = TRUE; } } return hr; } // This lets us know when ambient properties have changed. STDMETHODIMP CSample::OnAmbientPropertyChange(DISPID dispid) { // Call base class method that we are overriding. HRESULT hr = IOleControlImpl::OnAmbientPropertyChange(dispid); // If we are changing our name, then request a redraw. if (DISPID_AMBIENT_DISPLAYNAME == dispid || DISPID_UNKNOWN == dispid) { GetAmbientDisplayName(bstrDisplayName.m_str); FireViewChange(); } return hr; } 8