C++ Module -> OnlineChange

These instructions describe how to subsequently make a module OnlineChange-capable in a versioned TwinCAT C++ project.

Versioned C++ project with C++ module, which is not yet Online Change capable. For this sample, a module "Module1" is assumed. In addition, an empty, new project can be created with an Online Change-capable module, from which the changes can be more easily adopted.
1. Open the project and the TMC Editor.
2. Set the Auto generate on save option for the module so that the ClassID is changed automatically.
C++ Module -> OnlineChange 1:
3. Under Implemented Interfaces, delete the interfaces ITcADI and ITcWatchsource and add ITComOnlineChange.
C++ Module -> OnlineChange 2:
4. Delete the CyclicCaller under Interface Pointer.
C++ Module -> OnlineChange 3:
5. Under Parameters some predefined parameters have to be added.
Press + under Parameters and select Predefined to select the predefined ParameterIDs:
C++ Module -> OnlineChange 4:
You create the following parameters with the given names, in each case without code generation:
"PID_LibraryID" with the name "LibraryID"
"PID_ModuleClsId" with the name "ModuleClsId"
"PID_Ctx_TaskSortOrders with the name "SortOrders"
"PID_Ctx_TaskOids" with the name "Contexts"
"IOFFS_TcIoDataAreaSize" with the name "DataAreas"
The result in the overview:
C++ Module -> OnlineChange 5:
6. Start the code generation. C++ Module -> OnlineChange 6:
7. Some changes must be made in the header of the module "Module1.h". First, delete the declarations of the interfaces that are no longer required and the corresponding maps in two places.
class CModule1 
: public ITComObject
, publicITcADI
, publicITcWatchSource
///<AutoGeneratedContent id="InheritanceList">
, public ITcCyclic
///</AutoGeneratedContent>
{
public:
DECLARE_IUNKNOWN()
DECLARE_IPERSIST(CID_Untitled1CModule1)
DECLARE_ITCOMOBJECT_LOCKOP()
DECLARE_ITCADI()
DECLARE_ITCWATCHSOURCE()
DECLARE_OBJPARAWATCH_MAP()
DECLARE_OBJDATAAREA_MAP()
8. Create a new member variable in the header:
///</AutoGeneratedContent>
ITcADIPtr m_spADI;
// TODO: Custom variable
9. Some changes need to be made to the source code of the module "Module1.cpp". First, delete the implementations of the interfaces that are no longer required in two places.
BEGIN_INTERFACE_MAP(CModule1)
INTERFACE_ENTRY_ITCOMOBJECT()
INTERFACE_ENTRY(IID_ITcADI, ITcADI)
INTERFACE_ENTRY(IID_ITcWatchSource, ITcWatchSource)

///<AutoGeneratedContent id="InterfaceMap">
INTERFACE_ENTRY(IID_ITcCyclic, ITcCyclic)
///</AutoGeneratedContent>
END_INTERFACE_MAP()
IMPLEMENT_ITCOMOBJECT(CModule1)
IMPLEMENT_ITCOMOBJECT_SETSTATE_LOCKOP2(CModule1)
IMPLEMENT_ITCADI(CModule1)
IMPLEMENT_ITCWATCHSOURCE(CModule1)
10. Delete the implementation of the maps belonging to the deleted interfaces:
BEGIN_SETOBJPARA_MAP(CModule1)
SETOBJPARA_DATAAREA_MAP()
///<AutoGeneratedContent id="SetObjectParameterMap">
SETOBJPARA_VALUE(PID_TcTraceLevel, m_TraceLevelMax)
SETOBJPARA_VALUE(PID_Module1Parameter, m_Parameter)
SETOBJPARA_ITFPTR(PID_Ctx_TaskOid, m_spCyclicCaller)
///</AutoGeneratedContent>
END_SETOBJPARA_MAP()
///////////////////////////////////////////////////////////////////////////////
// Get parameters of CModule1
BEGIN_GETOBJPARA_MAP(CModule1)
GETOBJPARA_DATAAREA_MAP()
///<AutoGeneratedContent id="GetObjectParameterMap">
GETOBJPARA_VALUE(PID_TcTraceLevel, m_TraceLevelMax)
GETOBJPARA_VALUE(PID_Module1Parameter, m_Parameter)
GETOBJPARA_ITFPTR(PID_Ctx_TaskOid, m_spCyclicCaller)
///</AutoGeneratedContent>
END_GETOBJPARA_MAP()
///////////////////////////////////////////////////////////////////////////////
// Get watch entries of CModule1
BEGIN_OBJPARAWATCH_MAP(CModule1)
OBJPARAWATCH_DATAAREA_MAP()
///<AutoGeneratedContent id="ObjectParameterWatchMap">
///</AutoGeneratedContent>
END_OBJPARAWATCH_MAP()
///////////////////////////////////////////////////////////////////////////////
// Get data area members of CModule1
BEGIN_OBJDATAAREA_MAP(CModule1)
///<AutoGeneratedContent id="ObjectDataAreaMap">
OBJDATAAREA_VALUE(ADI_Module1Inputs, m_Inputs)
OBJDATAAREA_VALUE(ADI_Module1Outputs, m_Outputs)
///</AutoGeneratedContent>
END_OBJDATAAREA_MAP()
11. The m_spAPI pointer must be obtained in the transition P->S in the state machine:
HRESULT CModule1::SetObjStatePS(PTComInitDataHdr pInitData)
{
m_Trace.Log(tlVerbose, FENTERA);
HRESULT hr = S_OK;
IMPLEMENT_ITCOMOBJECT_EVALUATE_INITDATA(pInitData);
// query TcCOM object server for ITcADI interface with own object id,
// which retrieves a reference to the TMC module instance handler
m_spADI.SetOID(m_objId);
hr = FAILED(hr) ? hr : m_spSrv->TcQuerySmartObjectInterface(m_spADI);

// TODO: Add initialization code
12. Add the following in the state machine in the transition S->O, the calls to AddModuleToCaller or RemoveModuleFromCaller are omitted.
HRESULT hr = S_OK;
// Retrieve pointer to data areas via ITcADI interface from TMC module handler
///<AutoGeneratedContent id="DataAreaPointerInitialization">
///</AutoGeneratedContent>
// TODO: Add any additional initialization
// Cleanup if transition failed at some stage
if ( FAILED(hr) )
{
   SetObjStateOS();
}
13. Add the following in the state machine in the transition O->S, the call RemoveModuleFromCaller is omitted:
HRESULT hr = S_OK;
// Release pointer to data areas via ITcADI interface from TMC module handler
///<AutoGeneratedContent id="DataAreaPointerRelease">
///</AutoGeneratedContent>
// TODO: Add any additional deinitialization
14. The AddModuleToCaller and RemoveModuleFromCaller methods are not required and can be deleted.
15. Change the accesses to the DataAreas:
HRESULT CModule1::CycleUpdate(ITcTask* ipTask, ITcUnknown* ipCaller, ULONG_PTR context)
{
  HRESULT hr = S_OK;
  // TODO: Replace the sample with your cyclic code
  m_Counter+=m_pInputs->Value;
  m_pOutputs->Value=m_Counter;
  return hr;
}
16. Implement the ITcOnlineChange. The functions are created by the previous code generation, but must not return NOTIMPL.
///<AutoGeneratedContent id="ImplementationOf_ITComOnlineChange">
///////////////////////////////////////////////////////////////////////////////
// PrepareOnlineChange is called after this instance has been set to PREOP in non RT context.
// Parameter ipOldObj refers to the currently active instance which is still in OP.
// Retrieve parameter values that are not changed during OP via ipOldObj here.
//
// Parameter pOldInfo refers to instance data which includes the libraryId and
// the module class id. This information can be used to implement switch from one
// specific version to another.
HRESULT CModule1::PrepareOnlineChange(ITComObject* ipOldObj, TmcInstData* pOldInfo)
{
HRESULT hr = S_OK;

ULONG nData = sizeof(m_Parameter);
PVOID pData = &m_Parameter;
ipOldObj->TcGetObjPara(PID_Module1Parameter, nData, pData);
return hr;
}
///////////////////////////////////////////////////////////////////////////////
// PerformOnlineChange is called after this instance has been set to SAFEOP in RT context.
// Parameter ipOldObj refers to old instance which is now in SAFEOP.
// Allows to retrieve data after the last cyclic update of the old instance and
// before the first cyclic update of this instance.
HRESULT CModule1::PerformOnlineChange(ITComObject* ipOldObj, TmcInstData* pOldInfo)
{
HRESULT hr = S_OK;

ULONG nData = sizeof(m_Counter);
PVOID pData = &m_Counter;
ipOldObj->TcGetObjPara(PID_Module1Counter, nData, pData);
return hr;
}
///</AutoGeneratedContent>
17. Start the TMC Code Generator again to generate the code for the initialization of the data area pointers.