This article illustrates how to create a simple component with a simple COM object. Necessary steps are then presented to manually add and implement the interfaces required for a fully functional pipeline component. The scope of this article covers converting COM servers so that they expose pipeline-compliant interfaces.
Many existing Component Object Model (COM) objects contain business rules that organizations wish to move into the Internet Commerce arena. This migration can be accomplished in several ways. Very often it is advisable to revisit the business rules and design the components with Internet Commerce architecture in mind. This implies re-architecting the solution from the beginning, bearing in mind what it would mean to add Microsoft® Internet Information Services (IIS), Microsoft Transaction Server (MTS), and Microsoft Site Server Commerce to the execution of the component.
However, sometimes one might wish to modify the component to make it a pipeline-compliant component. Several ways to approach this type of modification include—from external pipeline component wrappers, to component aggregation, to implementing the necessary interfaces in the component to make it pipeline-compatible.
In this article, we create a simple component with a simple COM object. Then we follow the steps necessary to manually add and implement the interfaces required for a fully functional pipeline component. We could have also explored writing a pipeline component that simply used the original COM object, or we could have added a COM object to the original component that encapsulated the original object. However, the scope of this article covers converting COM servers so that they expose pipeline-compliant interfaces.
Most of the information in this document comes directly from the Commerce Documentation and the Commerce SDK. For further information, see http://www.microsoft.com/commerce.
To keep things simple, we will create a simple component to calculate a tax amount:
[id(2), helpstring("method CalculateTax")] HRESULT CalculateTax(float Amount, float* TotalTax);
Modify it to have a return value by adding the following:
[id(2), helpstring("method CalculateTax")] HRESULT CalculateTax(float Amount, [out, retval] float* TotalTax);
m_Percent = 0.0;
STDMETHODIMP CCityTax::get_Percent(float *pVal)
{
*pVal = m_Percent;
return S_OK;
}
STDMETHODIMP CCityTax::put_Percent(float newVal)
{
m_Percent = newVal;
return S_OK;
}
STDMETHODIMP CCityTax::CalculateTax(float Amount, float *TotalTax)
{
*TotalTax = Amount * (m_Percent/100);
return S_OK;
}
IPipelineComponent is the only mandatory interface needed for the component to behave as a pipeline component. It includes two methods: EnableDesign and Execute. The Execute method is where all of the processing takes place, and it is where we will be doing the majority of our work. All other interfaces, though needed, are optional.
Commerce.h | Contains the interface definitions for the Site Server 3.0 Commerce objects. |
Comp_ids.h | Contains the class identifiers (CLSIDs) for the pipeline objects and interfaces. |
Mspu_guids.h | Contains the CLSIDs for the order processing pipeline objects and interfaces. |
Pipeline.h | Contains the definitions for the pipeline interfaces. |
Pipecomp.h | Contains the proxy/stub definitions for the pipeline interfaces. |
Pipe_stages.h | Contains the GUIDs for the OPP stages. |
As an option, you could add the path the SDK includes for Microsoft Site Server 3.0 Commerce Edition (SSCE) to your project by adding a line similar to:
/I "C:\Microsoft Site Server\SiteServer\commerce\sdk\commerce\include" in the C/C++ tab.
CPP file: #include "mspu_guids.h"
public IpipelineComponent
#include "computil.h"
#include "pipeline.h"
#include "pipecomp.h"
#include "pipe_stages.h"
// IPipelineComponent
STDMETHOD(Execute)(
IDispatch* pdispOrder,
IDispatch* pdispContext,
LONG lFlags,
LONG* plErrorLevel);
STDMETHOD (EnableDesign) (BOOL fEnable);
// IPipelineComponent Methods
//
STDMETHODIMP CCityTax::Execute (
IDispatch* pdispOrder,
IDispatch* pdispContext,
LONG lFlags,
LONG* plErrorLevel)
{
HRESULT hRes = S_OK;
// TODO: Add code that performs the main operations for this component
return hRes;
}
STDMETHODIMP CCityTax::EnableDesign(BOOL fEnable)
{
return S_OK;
}
COM_INTERFACE_ENTRY(IPipelineComponent)
static HRESULT WINAPI UpdateRegistry(BOOL bRegister)
{
HRESULT hr = _Module.UpdateRegistryClass(GetObjectCLSID(), _T("SimpeTax.CityTax.1"), _T("SimpeTax.CityTax"), IDS_PROJNAME, THREADFLAGS_BOTH, bRegister);
if (SUCCEEDED(hr))
{
// TODO: Add stage affinities here.
hr = RegisterCATID(GetObjectCLSID(), CATID_MSCSPIPELINE_COMPONENT);
hr = RegisterCATID(GetObjectCLSID(), CATID_MSCSPIPELINE_ANYSTAGE);
}
return hr;
};
and comment the following line:
DECLARE_REGISTRY_RESOURCEID(IDR_CITYTAX)
IDictionary *pDictOrder = NULL;
HRESULT hr;
VARIANT var;
VARIANT Price;
float Tax;
OPP_ERRORLEV ErrorLevel = OPPERRORLEV_FAIL;
//initialize variants
VariantInit(&var);
VariantInit(&Price);
if(pdispOrder == NULL)
return E_INVALIDARG;
// Get the OrderForm Dictionary.
if(SUCCEEDED(hr=pdispOrder->QueryInterface(IID_IDictionary, (void**)&pDictOrder)))
{
//Test Code
hr = GetDictValue(pDictOrder, L"Test_Price", &var);
VariantChangeType(&Price,&var,0,VT_R4);
//Call the original method for processing.
m_Percent=.10; //This will be commented out when property persistence is working.
hr = this->CalculateTax(Price.fltVal , &Tax);
var.fltVal = Tax;
var.vt = VT_R4;
hr = PutDictValue(pDictOrder, L"Test_Tax", var);
}
if(SUCCEEDED(hr))
ErrorLevel = OPPERRORLEV_SUCCESS;
if(plErrorLevel) *plErrorLevel = ErrorLevel;
if(pDictOrder)
pDictOrder->Release();
return hr;
The IPipelineComponentDescription is an optional interface that makes it possible for pipeline components to identify the values they read from the pipe context, and the name/value pairs they read or write from or to the OrderForm—for order processing pipeline components—or Dictionary—for Commerce Interchange Pipeline (CIP) components.
public IDispatchImpl<IPipelineComponentDescription, &IID_IPipelineComponentDescription, &LIBID_SIMPLETAXLib>,
IDispatchImpl provides a default implementation for the IDispatch portion of any dual interface on your object.
COM_INTERFACE_ENTRY(IPipelineComponentDescription)
Also, make the following changes in the COM_MAP:
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY2(IDispatch, ICityTax)
These modifications are needed, because our object is now derived from two branches of IDispatch. Using the COM_INTERFACE_ENTRY2 macro allows us to disambiguate the interfaces.
// IPipelineComponentDescription
STDMETHOD(ContextValuesRead)(VARIANT *pVarRead);
STDMETHOD(ValuesRead)(VARIANT *pVarRead);
STDMETHOD(ValuesWritten)(VARIANT *pVarWritten);
//
// IPipelineComponentDescription Methods
//
STDMETHODIMP CCityTax::ContextValuesRead(VARIANT *pVarRead)
{
// TODO: Add your own values to the array
int cEntries = 1;
// allocate the safearray of VARIANTs
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, cEntries);
// Populate the safearray variants
VARIANT* pvarT = (VARIANT*)psa->pvData;
V_BSTR(pvarT) = SysAllocString(L"None");
V_VT(pvarT) = VT_BSTR;
// set up the return value to point to the safearray
V_VT(pVarRead) = VT_ARRAY | VT_VARIANT;
V_ARRAY(pVarRead) = psa;
return S_OK;
}
STDMETHODIMP CCityTax::ValuesRead(VARIANT *pVarRead)
{
// TODO: Add your own values to the array
int cEntries = 1;
// allocate the safearray of VARIANTs
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, cEntries);
// Populate the safearray variants
VARIANT* pvarT = (VARIANT*)psa->pvData;
V_BSTR(pvarT) = SysAllocString(L"Test_Price");
V_VT(pvarT) = VT_BSTR;
// set up the return value to point to the safearray
V_VT(pVarRead) = VT_ARRAY | VT_VARIANT;
V_ARRAY(pVarRead) = psa;
return S_OK;
}
STDMETHODIMP CCityTax::ValuesWritten(VARIANT *pVarWritten)
{
// TODO: Add your own values to the array
int cEntries = 1;
// allocate the safearray of VARIANTs
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, cEntries);
// Populate the safearray variants
VARIANT* pvarT = (VARIANT*)psa->pvData;
V_BSTR(pvarT) = SysAllocString(L"Test_Tax");
V_VT(pvarT) = VT_BSTR;
// set up the return value to point to the safearray
V_VT(pVarWritten) = VT_ARRAY | VT_VARIANT;
V_ARRAY(pVarWritten) = psa;
return S_OK;
}
The ISpecifyPropertyPages interface is a standard Microsoft Win32® OLE interface. Implement it to allow the pipeline administration tool to invoke the component's property page user interface. For more information about ISpecifyPropertyPages, see the OLE Programmer's Reference.
public ISpecifyPropertyPages,
COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
// ISpecifyPropertyPages
STDMETHOD (GetPages)(CAUUID *pPages);
if (NULL == pPages)
return E_INVALIDARG;
// TODO: Uncomment out the line below, and add the CLSID of a custom Property Page Control
/*
pPages->cElems = 1;
pPages->pElems = (GUID*)CoTaskMemAlloc(1*sizeof(GUID));
if(!pPages->pElems){
pPages->cElems = 0; return E_OUTOFMEMORY;
}
memcpy(pPages->pElems, &CLSID_CityTaxPpg, sizeof(GUID));
return S_OK;
*/
return E_NOTIMPL;
STDMETHODIMP CCityTaxPpg::Activate(HWND hWndParent, LPCRECT pRect, BOOL fModal)
{
return S_OK;
}
USES_CONVERSION; //this is needed for BSTR conversion macros
VARIANT Percentage;
VARIANT varbstrPercentage;
float Percent;
HRESULT hRes = IPropertyPageImpl<CCityTaxPpg>::Activate(hWndParent, pRect, fModal);
if(SUCCEEDED(hRes))
{
ATLTRACE(L"Created CityTax Property Page object");
CComQIPtr<ICityTax, &IID_ICityTax>pCityTax(m_ppUnk[0]);
VariantInit(&Percentage);
VariantInit(&varbstrPercentage);
pCityTax->get_Percent(&Percent);
V_R4(&Percentage) = Percent;
V_VT(&Percentage) = VT_R4;
hRes = VariantChangeType(&varbstrPercentage, &Percentage, 0, VT_BSTR);
ATLTRACE(varbstrPercentage.bstrVal );
if(FAILED(hRes))
return hRes;
SetDlgItemText (IDC_PERCENTAGE, W2A(varbstrPercentage.bstrVal));
VariantClear(&varbstrPercentage);
VariantClear(&Percentage);
}
return S_OK;
STDMETHOD(Apply)(void)
{
ATLTRACE(_T("CCityTaxPpg::Apply\n"));
for (UINT i = 0; i < m_nObjects; i++)
{
// Do something interesting here
// ICircCtl* pCirc;
// m_ppUnk[i]->QueryInterface(IID_ICircCtl, (void**)&pCirc);
// pCirc->put_Caption(CComBSTR("something special"));
// pCirc->Release();
}
m_bDirty = FALSE;
return S_OK;
}
Replace this in the .H file with:
STDMETHOD(Apply)(void);
STDMETHODIMP CCityTaxPpg::Apply (void)
{
ATLTRACE(_T("CCityTaxPpg::Apply\n"));
CComQIPtr<ICityTax, &IID_ICityTax>pCityTax(m_ppUnk[0]);
CComBSTR bstrPercentage;
VARIANT varBSTRPercentage, varPercentage;
HRESULT hRes;
VariantInit(&varBSTRPercentage);
VariantInit(&varPercentage);
GetDlgItemText(IDC_PERCENTAGE,bstrPercentage.m_str);
V_BSTR(&varBSTRPercentage) = SysAllocString(bstrPercentage.m_str);
V_VT(&varBSTRPercentage) = VT_BSTR;
hRes = VariantChangeType(&varPercentage,&varBSTRPercentage,0,VT_R4);
if(FAILED(hRes))
return hRes;
pCityTax->put_Percent(varPercentage.fltVal );
VariantClear(&varBSTRPercentage);
VariantClear(&varPercentage);
m_bDirty = FALSE;
return S_OK;
}
The IPersistStreamInit interface is a standard Win32 OLE interface. This interface is typically implemented on any object that needs to support initialized stream-based persistence, regardless of whatever else the object does. In the context of the order processing pipeline (OPP), it is used to load/save the component configuration in the pipeline configuration file (the pipeline will call on this interface, as appropriate). For information about IPersistStreamInit, see the OLE Programmer's Reference.
public IpersistStreamInit,
At this point, your class declaration should like similar to this:
class ATL_NO_VTABLE CCityTax :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CCityTax, &CLSID_CityTax>,
public IPipelineComponent,
public ISpecifyPropertyPages,
public IPersistStreamInit,
public IDispatchImpl<IPipelineComponentDescription, &IID_IPipelineComponentDescription, &LIBID_SIMPLETAXLib>,
public IDispatchImpl<ICityTax, &IID_ICityTax, &LIBID_SIMPLETAXLib>
{
COM_INTERFACE_ENTRY(IPersistStreamInit)
// IPersistStreamInit
const long m_lStreamVersionMajor; // major version number of the stream
const long m_lStreamVersionMinor; // minor version number of the stream
STDMETHOD(GetClassID)(CLSID *pClassID);
STDMETHOD(IsDirty)(void);
STDMETHOD(Load)(IStream *pStm);
STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty);
STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize);
STDMETHOD(InitNew)(void);
STDMETHODIMP CCityTax::IsDirty(void)
{
return S_OK;
}
STDMETHODIMP CCityTax::Load(IStream *pStm)
{
HRESULT hRes = S_OK;
return hRes;
}
STDMETHODIMP CCityTax::Save(IStream *pStm, BOOL fClearDiry)
{
HRESULT hRes = S_OK;
return hRes;
}
STDMETHODIMP CCityTax::GetSizeMax(ULARGE_INTEGER *pcbSize)
{
return S_OK;
}
STDMETHODIMP CCityTax::InitNew(void)
{
return S_OK;
}
STDMETHODIMP CCityTax::GetClassID(CLSID *pClassID)
{
*pClassID = GetObjectCLSID();
return S_OK;
}
CComVariant l_Percent;
l_Percent = m_Percent;
hRes = l_Percent.WriteToStream (pStm);
CComVariant l_Percent;
l_Percent = 0;
l_Percent.ReadFromStream (pStm);
m_Percent = l_Percent.fltVal;
CComVariant l_Percent;
l_Percent = m_Percent;
pcbSize->LowPart = sizeof(l_Percent);
pcbSize->HighPart = 0;
//Call the original method for processing
//m_Percent=.10;
hr = CalculateTax(Price.fltVal , &Tax); //m_Percent * Price;
In addition to exporting the mandatory and optional interfaces that the pipeline component is expected to have, we should support the Microsoft Transaction Server. With the release of SSCE 3.0, the MTSTxPipeline was introduced. This pipeline implementation is transactional. SSCE still supports the original non-transacted pipeline, but it runs inside MTS. Often it is desired, if not necessary, that custom pipeline components participate in distributed transactions. The following instructions cover what is needed to add MTS support to our pipeline component.
#include "mtx.h"
IObjectContext *Context = NULL;
//Get MTS Context
hr=GetObjectContext(&Context);
//Handle MTS respone
switch(hr)
{
case S_OK:
break;
case E_INVALIDARG:
break;
case E_UNEXPECTED:
break;
case CONTEXT_E_NOCONTEXT:
break;
}
if(SUCCEEDED(hr))
{
ErrorLevel = OPPERRORLEV_SUCCESS;
if(Context != NULL){hr=Context->SetComplete ();}
}
else
{
if(Context != NULL){hr=Context->SetAbort ();}
}
//MTS: How to create an object within this transaction context.
hr = Context->CreateInstance(CLSID_MyID, IID_IMyObjInterface, (void**)&pMyObj);
Information in this document, including URL and other Internet web site references, is subject to change without notice. The entire risk of the use or the results of the use of this resource kit remains with the user. This resource kit is not supported and is provided as is without warranty of any kind, either express or implied. The example companies, organizations, products, people and events depicted herein are fictitious. No association with any real company, organization, product, person or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.
Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.
© 1999-2000 Microsoft Corporation. All rights reserved.
Microsoft, Windows, Windows NT, Win32, Visual J++, Visual C++, Visual Basic, Visual FoxPro are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries/regions.
The names of actual companies and products mentioned herein may be the trademarks of their respective owners.