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 <atlctl.h>

class ATL_NO_VTABLE CSample :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IDispatchImpl<ISample, &IID_ISample, 
                                  &LIBID_SAMPLEACTIVEXCONTROLLib>,
    public CComControl<CSample>,
    public IPersistStreamInitImpl<CSample>,
    public IOleControlImpl<CSample>,
    public IOleObjectImpl<CSample>,
    public IOleInPlaceActiveObjectImpl<CSample>,
    public IViewObjectExImpl<CSample>,
    public IOleInPlaceObjectWindowlessImpl<CSample>,
    public ISupportErrorInfo,
    public IConnectionPointContainerImpl<CSample>,
    public IPersistStorageImpl<CSample>,
    public ISpecifyPropertyPagesImpl<CSample>,
    public IQuickActivateImpl<CSample>,
    public IDataObjectImpl<CSample>,
    public IProvideClassInfo2Impl<&CLSID_Sample, &DIID__ISampleEvents,
                &LIBID_SAMPLEACTIVEXCONTROLLib>,
    public IPropertyNotifySinkCP<CSample>,
    public CComCoClass<CSample, &CLSID_Sample>,

    // Added to support property bag persistence:
    public IPersistPropertyBagImpl<CSample>
{
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<CSample>)
    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<CSample>::
                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<CSample>::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<CSample>::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<CSample>::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

