C++ Modul -> OnlineChange

Diese Anleitung beschreibt, wie Sie in einem versionierten TwinCAT C++ Projekt ein Modul nachträglich OnlineChange-fähig machen.

Versioniertes C++ Projekt mit C++ Modul, welches noch nicht Online-Change fähig ist. Für dieses Beispiel wird von einem Modul „Module1“ ausgegangen. Zusätzlich kann ein leeres, neues Projekt mit einem Online-Change fähigen Modul erzeugt werden, aus welchem die Änderungen einfacher übernommen werden können.
1. Öffnen Sie das Projekt und den TMC Editor.
2. Setzen Sie die Auto generate on save-Option für das Modul, sodass die ClassID automatisch geändert wird.
C++ Modul -> OnlineChange 1:
3. Löschen Sie unter Implemented Interfaces die Interfaces ITcADI und ITcWatchsource und fügen Sie ITComOnlineChange hinzu.
C++ Modul -> OnlineChange 2:
4. Löschen Sie unter Interface Pointer den CyclicCaller.
C++ Modul -> OnlineChange 3:
5. Unter Parameters müssen einige vordefinierte Parameter hinzufügt werden.
Drücken Sie hierfür unter Parameters auf + und wählen Sie jeweils Predefined aus, wodurch die vordefinierten ParameterIDs ausgewählt werden können:
C++ Modul -> OnlineChange 4:
Legen Sie folgende Parameter mit den gegebenen Namen an, jeweils ohne CodeGenerierung:
„PID_LibraryID“ mit Namen „LibraryID“
„PID_ModuleClsId“ mit Namen „ModuleClsId“
„PID_Ctx_TaskSortOrders“ mit Namen „SortOrders“
„PID_Ctx_TaskOids“ mit Namen „Contexts“
„IOFFS_TcIoDataAreaSize“ mit Namen „DataAreas“
Das Ergebnis in der Übersicht:
C++ Modul -> OnlineChange 5:
6. Starten Sie die Code Generierung. C++ Modul -> OnlineChange 6:
7. In dem Header des Modules „Module1.h“ müssen einige Änderungen vorgenommen werden. Als erstes löschen Sie die Deklarationen der nicht mehr benötigten Interfaces sowie der zugehörigen Maps an zwei Stellen.
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. Legen Sie im Header eine neue Member-Variable an:
///</AutoGeneratedContent>
ITcADIPtr m_spADI;
// TODO: Custom variable
9. Im Quellcode des Moduls „Module1.cpp“ müssen einige Änderungen vorgenommen werden. Als erstes löschen Sie die Implementierungen der nicht mehr benötigten Interfaces an zwei Stellen.
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. Löschen Sie die Implementierung der zu den gelöschten Interfaces gehörigen Maps:
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. In der Statemachine muss in der Transition P->S der m_spAPI Pointer bezogen werden:
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. Ergänzen Sie in der Statemachine in der Transition S->O folgendes, die Aufrufe zu AddModuleToCaller bzw. RemoveModuleFromCaller fallen weg.
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. Ergänzen Sie in der Statemachine in der Transition O->S folgendes, der Aufruf RemoveModuleFromCaller fällt weg:
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. Die Methoden AddModuleToCaller sowie RemoveModuleFromCaller werden nicht benötigt und können gelöscht werden.
15. Ändern Sie die Zugriffe auf die 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. Implementieren Sie das ITcOnlineChange. Durch die vorherige CodeGenerierung sind die Funktionen angelegt, dürfen aber nicht NOTIMPL zurückgeben.
///<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. Starten Sie den TMC Code Generator nochmals, um den Code für die Initialisierung der Data-Area-Pointer zu erzeugen.