commit ba3729a4a1bd53cd5c090dbad04643297d577845 Author: Adam Date: Wed Sep 30 22:28:57 2026 -0600 First library, its tested and working diff --git a/ExtensionSDK/README b/ExtensionSDK/README new file mode 100644 index 0000000..d0789cb --- /dev/null +++ b/ExtensionSDK/README @@ -0,0 +1,149 @@ +############################################################################## +# Copyright: Copyright CODESYS Development GmbH +# Program: Extension API for the Linux SL products +############################################################################## + +########################## +This is the SDK of the Linux SL products to create an own additional CODESYS runtime component +whose functions that can be called from a CODESYS IEC library. +########################## + +########################## +Prerequisites: +- A CODESYS Installation to create an IEC library on a windows machine +- Some buildtools to create a shared object (linux dynamic library), that can be run with the CODESYS Control SL runtimes + - some kind of GCC (for X64 / ARM / AARCH64, depending on your target system) + - make + - m4 + - sed + - dos2unix +########################## + +########################## +If you want to start a new project, follow these steps: + +On the linux system (where you want to build the component) +1) Install the required tools (see prerequisites), e.g. with + sudo apt-get install build-essential m4 sed dos2unix make + +2) Copy the SDK to a place of your choice, e.g. in your home directory. + Later we call this directory the "", so please remember this location! + Hint: As we are using the tool "make": dont use paths with whitespaces. Make does not like that at all! + +3) Create a folder next to the sdk where you want to start your project, e.g. "CmpFirstTest" + mkdir ~/CmpFirstTest + Later we call this directory the "", so please remember this location! + +4) Now create a new project with the following command, and use the makefile from the : + make -f /makefile newproject + e.g.: + make -f ../sdk/makefile newproject + This creates a makefile within your folder, you might want to take a look at it later ;-) + Your workspace now might look like this: + | + |- SKD + | |- include (dir) + | |- m4 (dir) + | |- src (dir) + | |- makefile + | |- makefile_template + | |- README + | + |- CmpFirstTest + |- makefile + + +5) Now take a look at the generated makefile. + If you are compiling for X64, you dont need to change anything + If you are compiling for another architecture, you need to set a proper cross compiler via the variable CC (and probably more CFLAGS, INCLUDES and LDLIBS) + + +On the Windows system (where CODESYS is installed) +1) Open your CODESYS IDE and create a new library unsing the "Empty library" template (do not use the "External CODESYS library" template). + Set the title in the project information to e.g.: "CmpFirstTest" + and store it under the same filename, e.g. "CmpFirstTest.library". + These two must match, otherwise it wont be possible to generate the shared object! + +2) Add "externally linked" functions to your library as you wish. You can make a Function "externally linked" by right clicking + on the new function ->Properties->Build and select "External implementation". + This function must include "_cext" in its name, otherwise it wont be loaded by the runtime! + E.g.: TestFunction_cext + +3) Now create the runtime system files by selecting Build->"Generate runtime system files". + Choose a suitable path as output directory. + Enable the checkboxes for "M4 interface file" and "C stub file" as you need to generate both: C and M4 files. + Also check that the component name is equal to the name of your library, e.g. "CmpFirstTest" + +4) Save or transfer these generated files (*.c and *Itf.m4 file) to the on your linux system + + +On the linux system (at ) +1) Go to your and try to build your component with + make all + +2) Now a shared object, with the same name as your IEC library should have been generated in the "out" folder + +3) Now you can transfer, copy or symbolically link that to a location on your target system where libraries are normally found (e.g. /usr/lib ) + e.g. if you are on the target system: + sudo cp out/libCmpFirstTest.so /usr/lib + +4) Stop the runtime on your target e.g. with + sudo /etc/init.d/codesyscontrol stop + +5) Open the configuration file CODESYSControl_User.cfg of the runtime, e.g. with nano + and add an entry at the section [ComponentManager], where the components are listed: + [ComponentManager] + Component.1=CmpFirstTest + Component.2=CmpSomeOtherComponent + Component.3=CmpSomeMoreComponent + ... + + hints: + - the number or index in this setting needs to be incrementing, it might not collide with already existing settings/component indices + - if the line starts with ; it is a comment and the setting is not active + +6) Then restart your runtime again, e.g. with + sudo /etc/init.d/codesyscontrol start + +7) and check that your component is properly loaded, e.g. check the logfile (via CODESYS IDE), or under codesyscontrol.log + cat /var/opt/codesys/codesyscontrol.log + you should see a message like this: + ... Dynamic: CmpFirstTest, 0xffff2000 1.0.0.0 + +-> Congratulations! you made your first component and it runs from the CODESYS Control runtime +-> if not: check the FAQs section at the end of this file + + +On the Windows system (where CODESYS is installed) +1) go to the CODESYS project where you want to use your newly created IEC library, and use your library + +2) make a download and check if your code is called (e.g. make a printf there) + +########################## + + + +########################## +FAQs: + 1) My component does not build, what is going wrong? + - this might have very different reasons. Check what compiler (CC), flags (CFLAGS/LDFLAGS), include paths (INCLUDES) and libraries (LDLIBS) you are setting in your makefile + - check if the code you are writing does compile without this SDK stuff + - check if the functions in the component are named with "_cext" + + 2) My component is not loaded from the runtime? + - if you see a message in the log, like: + **** ERROR: libCmpFirstTest.so: cannot open shared object file: No such file or directory + or + **** ERROR: *** Error: Component CmpFirstTest NOT loaded + -> then your shared object is not found. Please double check that you put it somewhere on your linuxsystem, where it can be found (e.g. /usr/lib/) + + 3) Can I output some messages from my code? Where is this output? + - just use the normal printf() function in your code. This will be output at the "debug logger". To enable the debug logger, just change the file /etc/defaults/codesyscontrol: + set these variables: + DEBUGOUTPUT=1 + and + DEBUGLOGFILE=/tmp/codesyscontrol_debug.log + then restart the runtime. Then you can see the output at this logfile + + +########################## \ No newline at end of file diff --git a/ExtensionSDK/include/CmpErrors.h b/ExtensionSDK/include/CmpErrors.h new file mode 100644 index 0000000..93f6a72 --- /dev/null +++ b/ExtensionSDK/include/CmpErrors.h @@ -0,0 +1,16 @@ +/* + * this is a special header for the SL extension API!!! + * + * Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved. + * + */ + + +#ifndef __CMPERRORS_H__ +#define __CMPERRORS_H__ + + +#define ERR_OK 0 +#define ERR_NOTINITIALIZED 0x0003 + +#endif \ No newline at end of file diff --git a/ExtensionSDK/include/CmpItf.h b/ExtensionSDK/include/CmpItf.h new file mode 100644 index 0000000..e54a9c2 --- /dev/null +++ b/ExtensionSDK/include/CmpItf.h @@ -0,0 +1,711 @@ +/* + * this is a special header for the SL extension API!!! + * + * Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved. + * + */ + +#ifndef __CMPITF_H__ +#define __CMPITF_H__ + +#include + +#if defined(WIN32) || defined(_WIN32_WCE) + #ifdef _USRDLL + #define DLL_DECL _declspec(dllexport) + #endif +#endif +#if defined(LINUX) || defined(QNX) + #ifdef _USRDLL + #define DLL_DECL __attribute__ ((visibility ("default"))) + #endif +#endif + +#if !defined(DLL_DECL) + #define DLL_DECL +#endif + +#ifndef CDECL + #define CDECL +#endif + +#ifndef CDECL_EXT + #define CDECL_EXT +#endif + +#ifndef STDCALL + #define STDCALL __stdcall +#endif + +#if defined(__cplusplus) + extern "C" { +#endif + + +typedef RTS_UI32 CMPID; /* Component ID to specify a component. Has the vendor ID as a prefix! */ +typedef RTS_UI32 CLASSID; /* Class ID to specify a specific class (C or C++) */ +typedef RTS_UI32 OBJID; /* Object ID, typically the instance index starting with 0 */ +typedef RTS_UI32 ITFID; /* Interface ID to specify a specific interface (C or C++) */ + +#define RTS_VENDORID_3S 0x0000 +#define RTS_ADDVENDORID(VendorID, ID) (((RTS_UI32)(VendorID) << 16 & UINT32_C(0xFFFF0000)) + (ID)) +#define ADDVENDORID RTS_ADDVENDORID +#define RTS_GETVENDORID(ID) ((RTS_UI32)(ID) >> 16 & UINT32_C(0x0000FFFF)) +#ifdef TRG_64BIT + #define RTSITF_GET_SIGNATURE(sign32bit, sign64bit) sign64bit +#else + #define RTSITF_GET_SIGNATURE(sign32bit, sign64bit) sign32bit +#endif + + +/* Function that can be used for all CAL_ macros to return ERR_NOTIMPLEMENTED */ +RTS_RESULT CDECL FUNCTION_NOTIMPLEMENTED(unsigned long dummy,...); +RTS_RESULT CDECL FUNCTION_NOTIMPLEMENTED2(void *dummy,...); + +/* Structure to handle a mapping of an ID and a name */ +typedef struct RTS_ID_TO_NAME_ +{ + RTS_UI32 id; + char *pszName; +} RTS_ID_TO_NAME; + + +struct tagIBase; + +typedef struct +{ + /*pointer to virtual function table*/ + void* __VFTABLEPOINTER; +} instance_struct; + +typedef struct +{ + instance_struct *pInstance; + RTS_IEC_DWORD dwCount; +} addref_struct; + +typedef struct +{ + instance_struct *pInstance; + RTS_IEC_DWORD dwCount; +} release_struct; + +typedef struct +{ + instance_struct *pInstance; + RTS_IEC_DWORD iid; + RTS_IEC_RESULT *pResult; + instance_struct **ppInterface; +} queryinterface_struct; + +typedef unsigned long (CDECL *PF_ADDREF)(struct tagIBase *pIBase); +typedef unsigned long (CDECL *PF_RELEASE)(struct tagIBase *pIBase); +typedef void *(CDECL *PF_QUERYINTERFACE)(struct tagIBase *pIBase, ITFID iid, RTS_RESULT *pResult); + +typedef void (CDECL *PF_IEC_ADDREF)(void); +typedef void (CDECL *PF_IEC_RELEASE)(void); +typedef void (CDECL *PF_IEC_QUERYINTERFACE)(void); + +/* */ +typedef struct tagIBase +{ + int bIEC; + int iRefCount; + RTS_HANDLE hInstance; + PF_ADDREF AddRef; + PF_RELEASE Release; + PF_QUERYINTERFACE QueryInterface; + CLASSID ClassId; +} IBase_C; + +typedef struct tagIBase_IEC +{ + int bIEC; + int iRefCount; + RTS_HANDLE hInstance; + PF_IEC_ADDREF *AddRef; + PF_IEC_RELEASE *Release; + PF_IEC_QUERYINTERFACE *QueryInterface; +} IBase_IEC; + + +#ifndef STATICITF + #define STATICITF static +#endif + +#ifndef STATICITF_DEF + #define STATICITF_DEF +#endif + +#if defined(CPLUSPLUS) && defined(__cplusplus) + class IBase + { + public: + virtual unsigned long AddRef(IBase *pIBase = NULL) =0; + virtual unsigned long Release(IBase *pIBase = NULL) =0; + virtual void *QueryInterface(IBase *pIBase = NULL, ITFID iid = 0, RTS_RESULT *pResult = NULL) =0; + virtual ~IBase() { }; + }; + + #define DECLARE_ADDREF()\ + virtual unsigned long AddRef(IBase *pIBase = NULL); + + #define IMPLEMENT_ADDREF()\ + virtual unsigned long AddRef(IBase *pIBase = NULL)\ + {\ + iRefCount++;\ + return iRefCount;\ + } + + #define DECLARE_RELEASE()\ + virtual unsigned long Release(IBase *pIBase = NULL); + + #define IMPLEMENT_RELEASE()\ + virtual unsigned long Release(IBase *pIBase = NULL)\ + {\ + iRefCount--;\ + if (iRefCount == 0)\ + {\ + delete this;\ + return 0;\ + }\ + return iRefCount;\ + } + + #define IMPLEMENT_RELEASE_NODELETE()\ + virtual unsigned long Release(IBase *pIBase = NULL)\ + {\ + iRefCount--;\ + if (iRefCount == 0)\ + {\ + return 0;\ + }\ + return iRefCount;\ + } +#else + #define DECLARE_ADDREF\ + static unsigned long CDECL AddRef(struct tagIBase *pIBase); + + #define IMPLEMENT_ADDREF\ + static unsigned long CDECL AddRef(struct tagIBase *pIBase)\ + {\ + return (pIBase ? ++pIBase->iRefCount : 0);\ + } + + #define DECLARE_RELEASE\ + static unsigned long CDECL Release(struct tagIBase *pIBase); + + #define IMPLEMENT_RELEASE\ + static unsigned long CDECL Release(struct tagIBase *pIBase)\ + {\ + return (pIBase ? --pIBase->iRefCount : 0);\ + } + + #define DECLARE_QUERYINTERFACE\ + static void *CDECL QueryInterface(IBase *pBase, ITFID iid, RTS_RESULT *pResult); + + typedef IBase_C IBase; +#endif + +/* Global function pointer declarations */ +typedef int (CDECL *PF_EXPORT_FUNCTIONS)(void); +typedef int (CDECL *PF_IMPORT_FUNCTIONS)(void); +typedef RTS_UI32 (CDECL *PF_GET_VERSION)(void); +typedef RTS_RESULT (CDECL *PF_HOOK_FUNCTION)(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2); +/* typedef RTS_RESULT (CDECL *PF_REGISTER_API)(const char *pszAPIName, RTS_VOID_FCTPTR pfAPIFunction, int bExternalLibrary, RTS_UI32 ulSignatureID); */ +typedef RTS_RESULT (CDECL *PF_REGISTER_API)(const CMP_EXT_FUNCTION_REF *pExpTable, RTS_UINTPTR dummy, int bExternalLibrary, RTS_UI32 cmpId); +typedef RTS_RESULT (CDECL *PF_GET_API)(char *pszAPIName, RTS_VOID_FCTPTR *ppfAPIFunction, RTS_UI32 ulSignatureID); +typedef RTS_RESULT (CDECL *PF_REGISTER_API2)(const char *pszAPIName, RTS_VOID_FCTPTR pfAPIFunction, int bExternalLibrary, RTS_UI32 ulSignatureID, RTS_UI32 ulVersion); +typedef RTS_RESULT (CDECL *PF_GET_API2)(char *pszAPIName, RTS_VOID_FCTPTR *ppfAPIFunction, int bExternalLibrary, RTS_UI32 ulSignatureID, RTS_UI32 ulVersion); +typedef RTS_RESULT (CDECL *PF_CALL_HOOK)(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2, int bReverse); +typedef IBase* (CDECL *PF_CREATEINSTANCE)(CLASSID cid, RTS_RESULT *pResult); +typedef RTS_RESULT (CDECL *PF_DELETEINSTANCE)(IBase *pIBase); +typedef RTS_HANDLE (CDECL *PF_REGISTER_CLASS)(CMPID CmpId, CLASSID ClassId); +typedef RTS_RESULT (CDECL *PF_DELETEINSTANCE2)(CLASSID ClassId, IBase *pIBase); + +typedef struct tagINIT_STRUCT +{ + /* Exported by Component */ + CMPID CmpId; +#ifdef TRG_64BIT + RTS_UI32 aligndummy; +#endif + PF_EXPORT_FUNCTIONS pfExportFunctions; + PF_IMPORT_FUNCTIONS pfImportFunctions; + PF_GET_VERSION pfGetVersion; + PF_CREATEINSTANCE pfCreateInstance; + PF_DELETEINSTANCE pfDeleteInstance; + PF_HOOK_FUNCTION pfHookFunction; + + /* Import from CM */ + PF_REGISTER_API pfCMRegisterAPI; + PF_GET_API pfCMGetAPI; + PF_CALL_HOOK pfCMCallHook; + PF_REGISTER_CLASS pfCMRegisterClass; + PF_CREATEINSTANCE pfCMCreateInstance; + PF_REGISTER_API2 pfCMRegisterAPI2; + PF_GET_API2 pfCMGetAPI2; + PF_DELETEINSTANCE2 pfCMDeleteInstance2; +} INIT_STRUCT; + +#define COMPONENT_ENTRY_NAME "ComponentEntry" + +#if defined(CPLUSPLUS) && defined(__cplusplus) +extern "C" { +#endif + +#if !defined(STATIC_LINK) && !defined(MIXED_LINK) && !defined(DYNAMIC_LINK) && !defined(CPLUSPLUS_STATIC_LINK) + DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct); +#endif +typedef int (CDECL *PF_COMPONENT_ENTRY)(INIT_STRUCT *pInitStruct); + +#if defined(CPLUSPLUS) && defined(__cplusplus) +} +#endif + + +/* Functions exported from main */ +RTS_RESULT CDECL MainLoadComponent(char *pszName, char *pszPath, RTS_HANDLE *phModule, PF_COMPONENT_ENTRY* ppfComponentEntry); +RTS_RESULT CDECL MainUnloadComponent(char *pszName, RTS_HANDLE hModule); + + +/* The following functions are always offered by the component manager */ +int CDECL MainExitLoop(RTS_UINTPTR ulPar); +typedef int (CDECL *PFMAINEXITLOOP)(RTS_UINTPTR ulPar); +#ifdef STATIC_LINK + #define USE_CMExitLoop extern int CDECL MainExitLoop(RTS_UINTPTR ulPar); + #define EXT_CMExitLoop extern int CDECL MainExitLoop(RTS_UINTPTR ulPar); + #define GET_CMExitLoop ERR_OK + #define CAL_CMExitLoop MainExitLoop + #define CHK_CMExitLoop TRUE + #define EXP_CMExitLoop TRUE +#else /* DYNAMIC_LINK */ + #define USE_CMExitLoop PFMAINEXITLOOP pfMainExitLoop; + #define EXT_CMExitLoop extern PFMAINEXITLOOP pfMainExitLoop; + #define GET_CMExitLoop s_pfGetAPI("CMExitLoop", (void **)&pfMainExitLoop, 0) + #define CAL_CMExitLoop pfMainExitLoop + #define CHK_CMExitLoop (pfMainExitLoop != NULL) + #define EXP_CMExitLoop s_pfRegisterAPI("CMExitLoop", MainExitLoop, 1, 0) +#endif + +/** + * Hook definitions to inform all components with a broadcast via HookFunction() ([0..10000] reserved for core components): + *
    + *
  • CH_INIT_XXX Hooks: Used for initialization of all components. + * Future range, not yet defined! = [1..1000]
  • + *
  • CH_EXIT_XXX Hooks: Used for deinitialization of all components. + * Future range, not yet defined! = [2000..3000]
  • + *
  • CH_XXX Hooks: Used for broadcast commands (e.g. cyclic operations). + * Future range, not yet defined! = [4000..5000]
  • + *
+ * IMPLEMENTATION NOTE: + * Future hooks should be defined in the appropriate range above! + *
+ */ + +/* + CH_INIT_XXX Hooks: Used for initialization of all components. + */ +#define CH_INIT_SYSTEM 1 +/* First call at system startup to initialize all system components. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT_SYSTEM2 8 +/* Second call at system startup to initialize all system components. + (used to resolve dependencies between core components) + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT 2 +/* First call at system startup. Compontents should initialize all local variables. + ulParam1: Pointer to int. Parameter name=pbDontCallInitHooksFromCMInit. Content can be set as followed: + 0 [Default]: CMInitEnd() is called at the end of CMInit(). + 1: CMInitEnd (calling hooks from CH_INIT2 to CH_INIT_COMM) can be called asynchronously by your own + component and is not called in the context of CMInit()! + ulParam2: Not used +*/ + +#define CH_POST_INIT 200 +/* Called right after CH_INIT. Use this hook to run initialization between + highly related components (e.g. Register backends or providers to the corresponding components.). + + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_PRE_INIT2 250 +/* Called right before CH_INIT2. Use this hook to finish initialization of a component that is highly + related to an other components. E.g. finish initialization after the providers have registered. + + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT2 3 +/* Called after CH_INIT. All components are initialized. Other components can be used. + + NOTE: + Events must be created of the provider in this init step! + + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT_DONE CH_INIT2 +/* Backward compatibility */ + +#define CH_PRE_INIT3 7 +/* Called after CH_INIT2 and before CH_INIT3. All components are initialized. Other components can be used. + ulParam1: Not used + ulParam2: Not used +*/ +#define CH_INIT201 CH_PRE_INIT3 +/* Backward compatibility */ + +#define CH_INIT3 4 +/* Called after CH_INIT201. All components are initialized. Other components can be used. + + NOTE: + Event callbacks must be registered by the consumer in this init step! Events are created in CH_INIT2. + + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT_SYSTEM_TASKS 500 +/* Called after CH_INIT3. Tasks can be started that are relevant for the IEC applications. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT_TASKS 5 +/* Called after CH_INIT_SYSTEM_TASKS. All components should start their tasks. + + NOTE: + The IEC bootprojects are loaded here! + + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT_COMM 6 +/* Called after CH_INIT_TASKS. Communication can be started at this point, because all + components are ready and running. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_INIT_FINISHED 1000 +/* Called at the end of the complete initialization sequence. This is called after CH_INIT_COMM and right before the runtime system + fall into the cyclic CH_COMM_CYCLE state. + + NOTE: + The IEC bootprojects are started here! + + ulParam1: Not used + ulParam2: Not used +*/ + +/* + CH_EXIT_XXX Hooks: Used for deinitialization of all components. +*/ + +#define CH_EXIT_COMM 10 +/* Called at the beginning of the shutdown sequence to disable all communication channels! + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT_TASKS 11 +/* Called before CH_EXIT_SYSTEM_TASKS to stop and remove all tasks that are not needed by the IEC application. + + NOTE: + IEC applications are unloaded here. + + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT_SYSTEM_TASKS 2200 +/* Called before CH_EXIT3 to stop and remove all running tasks, because at CH_EXIT3 all objects are released and + so no task must run after this point. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT3 12 +/* Called before CH_EXIT2 to store data (e.g. retain data). + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT2 13 +/* Called before CH_EXIT to store data (e.g. retain data). + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_PRE_EXIT CH_EXIT2 +/* Backward compatibility */ + +#define CH_EXIT 14 +/* Here all memory of the components should be released. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT_SYSTEM2 17 +/* Counter part to CH_INIT_SYSTEM2. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT_SYSTEM 15 +/* Called at the end of the shutdown process for the system components to release memory. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_EXIT_GARBAGEMEMORY 16 +/* This is the final call for the garbage collector to check, if heap memory was not released. + ulParam1: Not used + ulParam2: Not used +*/ + +/* + CH_XXX Hooks: Used for broadcast commands (e.g. cyclic operations). + */ +#define CH_COMM_CYCLE 20 +/* Called every main cycle period. Can be used for background executions. + ulParam1: Not used + ulParam2: Not used +*/ + +#define CH_ON_IMPORT 23 +/* Called for components to import new functions from a new loaded component + ulParam1: char *: Pointer to component name + ulParam2: Not used +*/ + +#define CH_ON_IMPORT_RELEASE 24 +/* Called for components to release imported functions from a component that will be unloaded + ulParam1: char *: Pointer to component name + ulParam2: Not used +*/ + +#define CH_ON_LOAD_COMPONENT 25 +/* Called after loading a new component + ulParam1: char *: Pointer to component name + ulParam2: Not used +*/ + +#define CH_ON_UNLOAD_COMPONENT 26 +/* Called before unloading a component + ulParam1: char *: Pointer to component name + ulParam2: Not used +*/ + +#define CH_ON_REGISTER_INSTANCE 27 +/* Called after register a new instance + ulParam1: CLASSID: ClassId of instance + ulParam2: IBase *: Pointer to IBase +*/ + +#define CH_ON_UNREGISTER_INSTANCE 28 +/* Called before unregister a new instance + ulParam1: CLASSID: ClassId of instance + ulParam2: IBase *: Pointer to IBase +*/ + +#define CH_ON_LICENSE_FCTS_REGISTERED 29 +/* + Called after the functionpointers from the license-library + have been registered to the runtime. +*/ + +#define CH_ON_LICENSE_FCTS_REGISTERED2 290 +/* + Called after CH_ON_LICENSE_FCTS_REGISTERED. +*/ + +#define CH_ON_LICENSE_FCTS_LEGACY_REGISTERED 30 +/* + Called after the functionpointers from the license-library + have been registered to the runtime. +*/ + +#define CH_SAFEMODE 4000 +/* + Called when a component fails to resolve its required import functions during startup of the runtime system. In this case, the system is not stable any more. + Every component should check safety. + After entering the safemode, at least the communication should work and so for example a service engineer can investigate the reason for this error. + The logger should work too. + + IMPORTANT NOTE: + It is important, that the IEC-application(s) are not loaded or at least are not started after entering the safemode! This is still implemented in the CmpApp component. + + ulParam1: Not used + ulParam2: Not used +*/ + +/** +* Hook table +* +* This is the table of all hooks with its corresponding name (see CM_HOOK_TABLE). +* +* NOTE: +* The init and exit hooks are defined here in the order they are executed during startup and shutdown! +* +* +*/ +#define CM_INIT_HOOK_TABLE { CH_INIT_SYSTEM, "CH_INIT_SYSTEM" },\ + { CH_INIT_SYSTEM2, "CH_INIT_SYSTEM2" },\ + { CH_INIT, "CH_INIT" },\ + { CH_POST_INIT, "CH_POST_INIT" },\ + { CH_PRE_INIT2, "CH_PRE_INIT2" },\ + { CH_INIT2, "CH_INIT2" },\ + { CH_PRE_INIT3, "CH_PRE_INIT3" },\ + { CH_INIT3, "CH_INIT3" },\ + { CH_INIT_SYSTEM_TASKS, "CH_INIT_SYSTEM_TASKS" },\ + { CH_INIT_TASKS, "CH_INIT_TASKS" },\ + { CH_INIT_COMM, "CH_INIT_COMM" },\ + { CH_INIT_FINISHED, "CH_INIT_FINISHED" } + +#define CM_COMM_CYCLE_HOOK { CH_COMM_CYCLE, "CH_COMM_CYCLE" } + +#define CM_EXIT_HOOK_TABLE { CH_EXIT_COMM, "CH_EXIT_COMM" },\ + { CH_EXIT_TASKS, "CH_EXIT_TASKS" },\ + { CH_EXIT_SYSTEM_TASKS, "CH_EXIT_SYSTEM_TASKS" },\ + { CH_EXIT3, "CH_EXIT3" },\ + { CH_EXIT2, "CH_EXIT2" },\ + { CH_EXIT, "CH_EXIT" },\ + { CH_EXIT_SYSTEM2, "CH_EXIT_SYSTEM2" },\ + { CH_EXIT_SYSTEM, "CH_EXIT_SYSTEM" },\ + { CH_EXIT_GARBAGEMEMORY, "CH_EXIT_GARBAGEMEMORY" } + +#define CM_EXTENDED_HOOK_TABLE { CH_ON_IMPORT, "CH_ON_IMPORT" }, \ + { CH_ON_IMPORT_RELEASE, "CH_ON_IMPORT_RELEASE" }, \ + { CH_ON_LOAD_COMPONENT, "CH_ON_LOAD_COMPONENT" }, \ + { CH_ON_UNLOAD_COMPONENT, "CH_ON_UNLOAD_COMPONENT" }, \ + { CH_ON_REGISTER_INSTANCE, "CH_ON_REGISTER_INSTANCE" }, \ + { CH_ON_UNREGISTER_INSTANCE, "CH_ON_UNREGISTER_INSTANCE" }, \ + { CH_ON_LICENSE_FCTS_REGISTERED, "CH_ON_LICENSE_FCTS_REGISTERED" }, \ + { CH_ON_LICENSE_FCTS_REGISTERED2, "CH_ON_LICENSE_FCTS_REGISTERED2" }, \ + { CH_ON_LICENSE_FCTS_LEGACY_REGISTERED, "CH_ON_LICENSE_FCTS_LEGACY_REGISTERED" }, \ + { CH_SAFEMODE, "CH_SAFEMODE" } \ + +#define CM_HOOK_TABLE {\ + CM_INIT_HOOK_TABLE,\ + CM_COMM_CYCLE_HOOK,\ + CM_EXIT_HOOK_TABLE,\ + CM_EXTENDED_HOOK_TABLE\ + } + +/** + * Single import of interface functions + * + * These macros are used by the System-Components to import single interface functions without modifying + * the global SysXXXDep.m4 files. + * + * ATTENTION: + * With usage of these macros, the RtsConfiguration cannot resolve this dependency anymore!!! + * So use these macros only in Sys-Components and not in standard or customer components! + * + * Usage: + * #include "Itf.h" + * USE_SINGLE_ITF() or USE_SINGLE_ITF(, ) + * USE_SINGLE_FUNCTION() + * + * static int CDECL ImportFunctions(void) + * { + * IMPORT_SINGLE_ITF(); + * IMPORT_SINGLE_FUNCTION(); + * return ERR_OK; + * } + * + */ +#ifdef CPLUSPLUS + #define USE_SINGLE_ITF(Interface_) ITF_##Interface_ + + #define IMPORT_SINGLE_ITF(Interface_) \ + {\ + IBase *pIBase_;\ + RTS_RESULT Result_;\ + if (pI##Interface_ == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase_ = (IBase *)s_pfCMCreateInstance(CLASSID_C##Interface_, &Result_); \ + if (pIBase_ != NULL) \ + {\ + pI##Interface_ = (I##Interface_ *)pIBase_->QueryInterface(pIBase_, ITFID_I##Interface_, &Result_); \ + pIBase_->Release(pIBase_); \ + }\ + } \ + } + + #define EXIT_SINGLE_ITF(Interface_) \ + {\ + IBase *pIBase_;\ + RTS_RESULT exitResult_;\ + if (pI##Interface_ != NULL) \ + { \ + pIBase_ = (IBase *)pI##Interface_->QueryInterface(pI##Interface_, ITFID_IBase, &exitResult_); \ + if (pIBase_ != NULL && exitResult_ == ERR_OK) \ + { \ + pIBase_->Release(pIBase_); \ + if (pIBase_->Release(pIBase_) == 0) /* The object will be deleted here! */ \ + pI##Interface_ = NULL; \ + } \ + } \ + } + + #define IMPORT_SINGLE_ITF2(Class_, Interface_) \ + {\ + IBase *pIBase_;\ + RTS_RESULT Result_;\ + if (pI##Interface_ == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase_ = (IBase *)s_pfCMCreateInstance(CLASSID_C##Class_, &Result_); \ + if (pIBase_ != NULL) \ + {\ + pI##Interface_ = (I##Interface_ *)pIBase_->QueryInterface(pIBase_, ITFID_I##Interface_, &Result_); \ + pIBase_->Release(pIBase_); \ + }\ + } \ + } + + #define EXIT_SINGLE_ITF2(Class_, Interface_) \ + {\ + IBase *pIBase_;\ + RTS_RESULT exitResult_;\ + if (pI##Interface_ != NULL) \ + { \ + pIBase_ = (IBase *)pI##Interface_->QueryInterface(pI##Interface_, ITFID_IBase, &exitResult_); \ + if (pIBase_ != NULL && exitResult_ == ERR_OK) \ + { \ + pIBase_->Release(pIBase_); \ + if (pIBase_->Release(pIBase_) == 0) /* The object will be deleted here! */ \ + pI##Interface_ = NULL; \ + } \ + } \ + } +#else + #define USE_SINGLE_ITF(Interface_) + #define IMPORT_SINGLE_ITF(Interface_) + #define EXIT_SINGLE_ITF(Interface_) + #define IMPORT_SINGLE_ITF2(Class_, Interface_) + #define EXIT_SINGLE_ITF2(Class_, Interface_) +#endif + +#define USE_SINGLE_FUNCTION(Function) USE_##Function +#define IMPORT_SINGLE_FUNCTION(Function) GET_##Function(0) +#define EXIT_SINGLE_FUNCTION(Function) + + +#if defined(__cplusplus) +} +#endif + +#endif /*__CMPITF_H__*/ diff --git a/ExtensionSDK/include/CmpStd.h b/ExtensionSDK/include/CmpStd.h new file mode 100644 index 0000000..fb56572 --- /dev/null +++ b/ExtensionSDK/include/CmpStd.h @@ -0,0 +1,641 @@ +/* + * this is a special header for the SL extension API!!! + * + * Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved. + * + */ + +#ifndef __RTSSTD_H__ +#define __RTSSTD_H__ + + +#if defined(__amd64__) + #define TRG_X64 + #define TRG_64BIT +#elif defined(__arm__) + #define TRG_ARM +#elif defined(__aarch64__) + #define TRG_ARM + #define TRG_64BIT +#elif defined(i386) + #error "32 bit X86 is not supported!" +#else + #error "Unknown architecture!" +#endif + +#define LINUX + +#define CDECL +#define __cdecl CDECL /*necessary for sqlite in CmpCAAStorage*/ +#define STDCALL +#define HUGEPTR + +/****** OS *****/ +#define SYSTARGET_OS_LINUX + +/*** types ***/ +#ifndef STDINT_H_UINTPTR_T_DEFINED + #define STDINT_H_UINTPTR_T_DEFINED +#endif +typedef unsigned short RTS_WCHAR; +#define RTS_WCHAR_DEFINED + +#define INTEL_BYTE_ORDER + + +#include +#include +#include +#include +#include +#include +#include + + + +/** + * set default register width + */ +#ifndef RTS_REGISTER_WIDTH + #if defined(TRG_C16X) || defined(TRG_DSP) + #define RTS_REGISTER_WIDTH 16 + #elif defined(TRG_64BIT) || defined(TRG_IA64) + #define RTS_REGISTER_WIDTH 64 + #else + #define RTS_REGISTER_WIDTH 32 + #endif +#endif + +#if RTS_REGISTER_WIDTH == 64 && !defined (SIZEOF_LONG) + #if defined (SYSTARGET_OS_WINDOWS) || defined (SYSTARGET_OS_WINDOWS_RTE) || defined (SYSTARGET_OS_WINDOWS_CE) + #define SIZEOF_LONG 4 + #else + #define SIZEOF_LONG 8 + #endif +#endif + +#include + +/** + * Redefine UINT32_C(), INT16_C, ..., + * as many compilers are defining this in a bad way. They + * are using type-casts to get the correct data-width. This + * makes it impossible to use for some pedantic C-Compilers. + */ +#ifdef INT64_C + #undef INT64_C +#endif +#ifdef UINT64_C + #undef UINT64_C +#endif +#ifdef INT32_C + #undef INT32_C +#endif +#ifdef UINT32_C + #undef UINT32_C +#endif +#ifdef INT16_C + #undef INT16_C +#endif +#ifdef UINT16_C + #undef UINT16_C +#endif +#ifdef INT8_C + #undef INT8_C +#endif +#ifdef UINT8_C + #undef UINT8_C +#endif + +#define INT64_C(x) (RTS_I64_CONST(x)) +#define UINT64_C(x) (RTS_UI64_CONST(x)) +#define INT32_C(x) (x ## L) +#define UINT32_C(x) (x ## UL) +#define INT16_C(x) (x) +#define UINT16_C(x) (x ## U) +#define INT8_C(x) (x) +#define UINT8_C(x) (x ## U) + +#define RTS_IEC_BOOL_C(x) (x) +#define RTS_IEC_BYTE_C(x) (UINT8_C(x)) +#define RTS_IEC_SINT_C(x) (INT8_C(x)) +#define RTS_IEC_USINT_C(x) (UINT8_C(x)) +#define RTS_IEC_INT_C(x) (INT16_C(x)) +#define RTS_IEC_UINT_C(x) (UINT16_C(x)) +#define RTS_IEC_DINT_C(x) (INT32_C(x)) +#define RTS_IEC_UDINT_C(x) (UINT32_C(x)) +#define RTS_IEC_LINT_C(x) (INT64_C(x)) +#define RTS_IEC_ULINT_C(x) (UINT64_C(x)) +#define RTS_IEC_WORD_C(x) (UINT16_C(x)) +#define RTS_IEC_DWORD_C(x) (UINT32_C(x)) +#define RTS_IEC_LWORD_C(x) (UINT64_C(x)) +#define RTS_IEC_REAL_C(x) (x) +#define RTS_IEC_LREAL_C(x) (x) +#ifdef TRG_64BIT +#define RTS_IEC_XINT_C(x) (INT64_C(x)) +#define RTS_IEC_UXINT_C(x) (UINT64_C(x)) +#define RTS_IEC_XWORD_C(x) (UINT64_C(x)) +#else +#define RTS_IEC_XINT_C(x) (INT32_C(x)) +#define RTS_IEC_UXINT_C(x) (UINT32_C(x)) +#define RTS_IEC_XWORD_C(x) (UINT32_C(x)) +#endif + +/** + * Define some more exotic types, which are missing + * in many versions of stdint.h. + */ +#if !defined(STDINT_H_UINTPTR_T_DEFINED) +/* Note: If your compiler complains about a "redefinition" + * or s.th. similar here, you may set the defines + * _UINTPTR_T, _PTRDIFF_T, ... in your sysdefines.h. + */ + #ifndef _UINTPTR_T + typedef uint32_t uintptr_t; + #define _UINTPTR_T + #endif + #ifndef _PTRDIFF_T + typedef int32_t ptrdiff_t; + #define _PTRDIFF_T + #endif + #ifndef _INTPTR_T + typedef ptrdiff_t intptr_t; + #define _INTPTR_T + #endif + #define STDINT_H_UINTPTR_T_DEFINED +#endif /*!defined(STDINT_H_UINTPTR_T_DEFINED)*/ + +/* Format modifiers for integral types */ +#ifndef ULONG_MAX +# include +# ifndef ULONG_MAX +# error ULONG_MAX does not exist in limits.h! +# endif +#endif + +/* + * Some basic definitions + */ + +#ifndef NULL +# define NULL 0L +#endif + +#ifndef TRUE +# define TRUE 1 +#endif + +#ifndef FALSE +# define FALSE 0 +#endif + +/*lint -e666 -e506 */ +#ifndef MIN +# define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + +#ifndef MAX +# define MAX(x,y) ((x)>(y)?(x):(y)) +#endif + +#ifndef RTS_SETRESULT +# define RTS_SETRESULT(p,r) do { if(p) *(p)=r; } while(0) +#endif + +#ifndef RTS_GETRESULT +# define RTS_GETRESULT(Return,Result) (Result == ERR_OK ? (Return) : ((Return) ? Result : Result)) +#endif + +#ifndef LOWWORD +# define LOWWORD(p) ((uint16_t)((uint32_t)(p) & 0x0000FFFF)) +#endif + +#ifndef HIGHWORD +# define HIGHWORD(p) ((uint16_t)(((uint32_t)(p)>>16) & 0x0000FFFF)) +#endif + + +/* Operations on flags */ +#ifndef RTS_SETFLAGS + #define RTS_SETFLAGS(value, flags) (value |= (flags)) +#endif +#ifndef RTS_SETFLAG + #define RTS_SETFLAG(value, flag) RTS_SETFLAGS(value, flag) +#endif + +#ifndef RTS_RESETFLAGS + #define RTS_RESETFLAGS(value, flags) (value &= ~(flags)) +#endif +#ifndef RTS_RESETFLAG + #define RTS_RESETFLAG(value, flag) RTS_RESETFLAGS(value, flag) +#endif + +#ifndef RTS_HASFLAGS + #define RTS_HASFLAGS(value, flags) ((value & (flags)) == (flags)) +#endif +#ifndef RTS_HASFLAG + #define RTS_HASFLAG(value, flag) (value & (flag)) +#endif + + + +#ifndef ALIGN_MEMORY +# define ALIGN_MEMORY(pMemory) ((void *)(((((uintptr_t)(pMemory) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)))) +#endif + +#ifndef ALIGN_MEMORY_SIZE +# define ALIGN_MEMORY_SIZE(pMemory, iSize) ((iSize) - (((((uintptr_t)(pMemory) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)) - (uintptr_t)(pMemory))) +#endif + +#ifndef ALIGN_MEMORY2 +# define ALIGN_MEMORY2(pMemory, Modulo) ((void *)((((uintptr_t)(pMemory) + (Modulo) - 1) / (uintptr_t)(Modulo)) * (uintptr_t)(Modulo))) +#endif + +#ifndef ALIGN_MEMORY2_BACK +# define ALIGN_MEMORY2_BACK(pMemory, Modulo) ((void *)(((uintptr_t)(pMemory) / (uintptr_t)(Modulo)) * (uintptr_t)(Modulo))) +#endif + +#ifndef ALIGN_MEMORY_SIZE2 +# define ALIGN_MEMORY_SIZE2(pMemory, iSize, Modulo) ((iSize) - (((((uintptr_t)(pMemory) + (Modulo) - 1) / sizeof(void*)) * (Modulo)) - (uintptr_t)(pMemory))) +#endif + +#ifndef ALIGN_SIZE +/* NOTE: it seems like this operation can be made without a comparison: */ +/* #define ALIGN_SIZE(Size, Modulo) (( (Size) + ((Modulo) - 1) ) / (Modulo)) * (Modulo) */ +# define ALIGN_SIZE(Size, Modulo) /*lint -e{506} */(((Size) % (Modulo)) ? ((Size) + ((Modulo) - ((Size) % (Modulo)))) : (Size)) +#endif + +#ifndef RTS_VSNPRINTF + #if defined(RTS_VSNPRINTF_NOT_AVAILABLE) + #define RTS_VSNPRINTF(s,len,format,arglist) vsprintf(s,format,arglist) + #else + #define RTS_VSNPRINTF(s,len,format,arglist) vsnprintf(s,len,format,arglist) + #endif +#endif + +#ifndef va_copy + #define va_copy(destination, source) ((destination) = (source)) +#endif + + +#define CONCAT(x,y) x##y + +/* Some preprocessors do not allow to place such a macro within other macros */ +/* and/or pragmas. */ +#define ____QUOTE____(x) #x +#define QUOTE(x) ____QUOTE____(x) + +#ifdef INTEL_BYTE_ORDER +# define SWAP16u(x) (x) +# define SWAP16s(x) (x) +# define SWAP32u(x) (x) +# define SWAP32s(x) (x) +#elif defined(MOTOROLA_BYTE_ORDER) +# define SWAP16u(x) ( (uint16_t)((((x) & 0xFF)<<8) | (((x) & 0xFF00) >> 8)) ) +# define SWAP16s(x) ( (int16_t)((((x) & 0xFF)<<8) | (((x) & 0xFF00) >> 8)) ) +# define SWAP32u(x) ( (uint32_t)((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) ) +# define SWAP32s(x) ((int32_t)((((uint32_t)(x)) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#else + #error "No byte order defined for the current target" +#endif + + + +#include "CmpErrors.h" + +#define CAL_CMGETAPI(name) ERR_OK +#define CAL_CMEXPAPI(name) ERR_OK + +typedef void HUGEPTR(CDECL *RTS_VOID_FCTPTR)(void); + + +/* + * Runtime system basic typedefinitions. + * Defines some types with special meaning. Systems not using standard type sizes + * must redefine this types in sysdefines.h and set BASETYPES_DEFINED + */ + +#ifndef RTS_I64_CONST + #define RTS_I64_CONST(a) (a##LL) +#endif +#ifndef RTS_UI64_CONST + #define RTS_UI64_CONST(a) (a##ULL) +#endif + +#ifndef BASETYPES_DEFINED +# define BASETYPES_DEFINED + + typedef int8_t RTS_I8; /* Signed 8 bit value */ + typedef int16_t RTS_I16; /* Signed 16 bit value */ +#ifndef RTS_I32_DEFINED + #define RTS_I32_DEFINED + typedef int32_t RTS_I32; /* Signed 32 bit value */ +#endif + + typedef uint8_t RTS_UI8; /* Unsigned 8 bit value */ +# define RTS_UI8_MAX (RTS_UI8)(~((RTS_UI8)0)) + + typedef uint16_t RTS_UI16; /* Unsigned 16 bit value */ +# define RTS_UI16_MAX (RTS_UI16)(~((RTS_UI16)0)) + +#ifndef RTS_UI32_DEFINED + #define RTS_UI32_DEFINED + typedef uint32_t RTS_UI32; /* Unsigned 32 bit value */ +#endif +# define RTS_UI32_MAX (RTS_UI32)(~((RTS_UI32)0)) + +#ifndef IEEE754TYPES_DEFINED +# define IEEE754TYPES_DEFINED + typedef float RTS_REAL32; /* 32 bit floating point value */ + typedef double RTS_REAL64; /* 64 bit floating point value */ + #define RTS_REAL32_MAX 3.4028234e+38 + #define RTS_REAL64_MAX 1.7976931348623157e+308 +#endif + + typedef void* RTS_HANDLE; /* Handle for files, semaphores, ... Usually the native integer type of the platform*/ + #define RTS_INVALID_HANDLE ((RTS_HANDLE)(~((uintptr_t)0))) + + typedef RTS_UI32 RTS_RESULT; /* Result type for most functions. At least 16 bit. Should be the natural integer of a platform. */ + + typedef uintptr_t RTS_UINTPTR; /* Unsigned integral type that can hold a pointer */ + #define RTS_UINTPTR_MAX (RTS_UINTPTR)(~((RTS_UINTPTR)0)) + + typedef intptr_t RTS_INTPTR; /* Signed integral type that can hold a pointer */ + #define RTS_INTPTR_MAX (RTS_INTPTR)(~((RTS_INTPTR)0)) + + typedef ptrdiff_t RTS_PTRDIFF; /* Signed integral type that can hold an array index */ + #define RTS_PTRDIFF_MAX (RTS_PTRDIFF)(~((RTS_PTRDIFF)0)) + +#ifndef RTS_SIZE_DEFINED + #define RTS_SIZE_DEFINED + typedef size_t RTS_SIZE; /* Unsigned integral type that can hold a buffer offset */ +#endif + #define RTS_SIZE_MAX (RTS_SIZE)(~((RTS_SIZE)0)) + +/* Type definition of RTS_SSIZE should be changed to standard datatype ssize_t, if available on any target! */ +#ifndef RTS_SSIZE_DEFINED + #define RTS_SSIZE_DEFINED + typedef intptr_t RTS_SSIZE; /* integral type that can hold a buffer offset */ +#endif + #define RTS_SSIZE_MAX (RTS_SSIZE)(~((RTS_SSIZE)0)) + + typedef RTS_UINTPTR RTS_TIME; /* Coordinated Universal Time (UTC) */ + + typedef int RTS_INT; /* Data type has no constant size, so be careful in sharing shuch datatypes with IEC! */ + typedef int RTS_BOOL; /* Boolean value (TRUE or FALSE). For best performance use platform specific int. + Data type has no constant size, so be carful in sharing shuch datatypes with IEC! */ +#if defined(RTS_DISABLE_64BIT_SUPPORT) && !defined(SYSINTERNAL_DISABLE_64BIT) + #define SYSINTERNAL_DISABLE_64BIT +#endif + +#if !defined(BASE64BITTYPES_DEFINED) && !defined(SYSINTERNAL_DISABLE_64BIT) +# define BASE64BITTYPES_DEFINED + typedef uint64_t RTS_UI64; + typedef int64_t RTS_I64; + #define RTS_UI64_MAX (RTS_UI64)(~((RTS_UI64)0)) + #define RTS_I64_MAX (RTS_I64)(~((RTS_I64)0)) +#endif + + typedef char RTS_CSTRING; + typedef char RTS_UTF8STRING; + +# ifndef RTS_WCHAR_DEFINED +# define RTS_WCHAR_DEFINED + typedef wchar_t RTS_WCHAR; /* wide character value. We expect 2 bytes unicode here! */ +# endif /*RTS_WCHAR_DEFINED*/ + +# ifndef RTS_CWCHAR_DEFINED +# define RTS_CWCHAR_DEFINED +# ifdef RTS_UNICODE + typedef RTS_WCHAR RTS_CWCHAR; +# else + typedef RTS_CSTRING RTS_CWCHAR; +# endif +# endif /*RTS_CWCHAR_DEFINED*/ + +#ifdef RTS_DEFINE_MODE_T + typedef unsigned short mode_t; +#endif +#ifdef RTS_DEFINE_GID_T + typedef short gid_t; +#endif +#ifdef RTS_DEFINE_UID_T + typedef short uid_t; +#endif +#ifdef RTS_DEFINE_ID_T + typedef intptr_t id_t; +#endif +#ifdef RTS_DEFINE_PID_T + typedef uintptr_t pid_t; +#endif +#ifdef RTS_DEFINE_OFF_T + typedef uintptr_t off_t; +#endif +#ifdef RTS_DEFINE_NLINK_T + typedef short nlink_t; +#endif + + + +#endif /*BASETYPES_DEFINED*/ + +typedef enum +{ + RTSTYPE_I8, + RTSTYPE_I16, + RTSTYPE_I32, + RTSTYPE_UI8, + RTSTYPE_UI16, + RTSTYPE_UI32, + RTSTYPE_REAL32, + RTSTYPE_REAL64, + RTSTYPE_HANDLE, + RTSTYPE_RESULT, + RTSTYPE_UINTPTR, + RTSTYPE_INTPTR, + RTSTYPE_SIZE, + RTSTYPE_PTRDIFF, + RTSTYPE_INT, + RTSTYPE_BOOL, + RTSTYPE_UI64, + RTSTYPE_I64, + RTSTYPE_CSTRING, + RTSTYPE_WCHAR, + RTSTYPE_CWCHAR +}RTS_TYPECLASS; + +typedef union +{ + RTS_I8 i8Value; + RTS_I16 i16Value; + RTS_I32 i32Value; + RTS_UI8 ui8Value; + RTS_UI16 ui16Value; + RTS_UI32 ui32Value; + RTS_REAL32 real32Value; + RTS_REAL64 real64Value; + RTS_HANDLE handleValue; + RTS_RESULT resultValue; + RTS_UINTPTR uintptrValue; + RTS_INTPTR intptrValue; + RTS_SIZE sizeValue; + RTS_PTRDIFF ptrdiffValue; + RTS_INT intValue; + RTS_BOOL boolValue; + RTS_UI64 ui64Value; + RTS_I64 i64Value; + RTS_CSTRING *cstringValue; + RTS_WCHAR *wstringValue; + RTS_CWCHAR *cwstringValue; +}RTS_VALUE; + +typedef struct +{ + RTS_TYPECLASS typeClass; + RTS_VALUE value; +}RTS_TYPEDVALUE; + + +/* + * IEC basic typedefinitions. + * Defines all datatypes used in IEC programs. Datatypes must match to CoDeSys compiler. + * Systems that overloads standard types must redefine this types in sysdefines.h and set IECBASETYPES_DEFINED + */ + +typedef void HUGEPTR(CDECL *RTS_VOID_FCTPTR)(void); + +#ifndef IECBASETYPES_DEFINED +#define IECBASETYPES_DEFINED + +# define RTS_IEC_VOID_FCTPTR RTS_VOID_FCTPTR + typedef void* RTS_IEC_VOIDPTR; + + typedef RTS_I8 RTS_IEC_BOOL; + typedef RTS_I8 RTS_IEC_SINT; + typedef RTS_UI8 RTS_IEC_USINT; + typedef RTS_UI8 RTS_IEC_BYTE; + typedef RTS_I16 RTS_IEC_INT; + typedef RTS_UI16 RTS_IEC_UINT; + typedef RTS_UI16 RTS_IEC_WORD; + typedef RTS_I32 RTS_IEC_DINT; + typedef RTS_UI32 RTS_IEC_UDINT; + typedef RTS_UI32 RTS_IEC_DWORD; + + typedef RTS_UI32 RTS_IEC_TIME; + typedef RTS_UI32 RTS_IEC_TIME_OF_DAY; + typedef RTS_UI32 RTS_IEC_TOD; + typedef RTS_UI32 RTS_IEC_DATE_AND_TIME; + typedef RTS_UI32 RTS_IEC_DT; + typedef RTS_UI32 RTS_IEC_DATE; + + typedef RTS_I64 RTS_IEC_LINT; + typedef RTS_UI64 RTS_IEC_ULINT; + typedef RTS_UI64 RTS_IEC_LWORD; + typedef RTS_UI64 RTS_IEC_LTIME; + + typedef RTS_SIZE RTS_IEC_XWORD; + typedef RTS_UINTPTR RTS_IEC_UXINT; + typedef RTS_PTRDIFF RTS_IEC_XINT; + + typedef RTS_REAL32 RTS_IEC_REAL; + typedef RTS_REAL64 RTS_IEC_LREAL; + + typedef RTS_CSTRING RTS_IEC_STRING; +# ifdef RTS_UNICODE + typedef RTS_WCHAR RTS_IEC_WSTRING; +# else + typedef RTS_UI16 RTS_IEC_WSTRING; +# endif + typedef RTS_CWCHAR RTS_IEC_CWCHAR; + + typedef RTS_SIZE RTS_IEC_SIZE; + typedef RTS_HANDLE RTS_IEC_HANDLE; + typedef RTS_IEC_UDINT RTS_IEC_RESULT; +#endif /*IECBASETYPES_DEFINED*/ + + +#ifndef RTS_MEM_REGION_DEFINED +#define RTS_MEM_REGION_DEFINED + +/** + * + * Abstract Memory Region. + * + */ +typedef struct tagRTS_MEM_REGION +{ + /* base address of the region */ + RTS_UINTPTR base; + + /* region size */ + RTS_SIZE size; + +} RTS_MEM_REGION; +#endif + +#ifndef EXT_FUNCT_REF_DEFINED +#define EXT_FUNCT_REF_DEFINED +/** + * Exported function descriptor + */ +typedef struct tagCMP_EXT_FUNCTION_REF +{ + /* pointer to an exported function */ + RTS_VOID_FCTPTR pfExtCall; + + /* name of an exported function */ + const char* pszExtCallName; + + /* signature */ + RTS_UI32 signature; + + /* version */ + RTS_UI32 version; + +} CMP_EXT_FUNCTION_REF; +#endif + +/* The CMGetAPI2/CMGetAPI3 importOptions parameter flag which specifies an import */ +/* as a required function (error if it is unresolved) */ +#define CM_IMPORT_REQUIRED_FUNCTION 0 + +/* The CMGetAPI2/CMGetAPI3 importOptions parameter flag which specifies an import */ +/* as an external libray function */ +#define CM_IMPORT_EXTERNAL_LIB_FUNCTION 1 + +/* The CMGetAPI2/CMGetAPI3 importOptions parameter flag which specifies an import */ +/* as an optional function */ +#define CM_IMPORT_OPTIONAL_FUNCTION 2 + +/* Imported function belongs to an external libray */ +#define ImportIsExternalLibFunction(par) (0 != ((par) & CM_IMPORT_EXTERNAL_LIB_FUNCTION)) + +/* Imported function is optional */ +#define ImportIsOptionalFunction(par) (0 != ((par) & CM_IMPORT_OPTIONAL_FUNCTION)) + +#define RTS_SIZEOF sizeof +#define RTS_GET_WORD_OFFSET(typesize) (typesize) +#define RTS_GET_WORD_SIZE RTS_GET_WORD_OFFSET +#define RTS_GET_BYTE_OFFSET(offset) (offset) +#define RTS_GET_BYTE_SIZE(size) (size) +#define RTS_MEMSET memset +#define RTS_ALIGN_4BYTES 4 +#define RTS_BITS_IN_BYTE 8 + +#define RTS_EMPTY_STRING "" +#define RTS_IS_EMPTY_STRING(s) ((char *)s == NULL || strcmp((char *)s, RTS_EMPTY_STRING) == 0) + +#define RTS_TIMEOUT_INFINITE (~(RTS_UI32)0) +#define RTS_TIMEOUT_DEFAULT (RTS_UI32)(-2) +#define RTS_TIMEOUT_NO_WAIT (0) + +#define RTS_INVALID_FILETRANSFER (RTS_UI32)(-1) + +#include "CmpItf.h" + + +#ifndef COMPONENT_NAME_POSTFIX + #define COMPONENT_NAME_POSTFIX +#endif + +#endif /*__RTSSTD_H__*/ diff --git a/ExtensionSDK/m4/Exports.m4 b/ExtensionSDK/m4/Exports.m4 new file mode 100644 index 0000000..7427e5b --- /dev/null +++ b/ExtensionSDK/m4/Exports.m4 @@ -0,0 +1,1514 @@ +include(`Util.m4') dnl +divert(-1) +% ----------------------------------- +% Copyright: Copyright CODESYS Development GmbH +% ----------------------------------- +changecom(`%') +% ----------------------------- +% Define some diverts that gather information about some special features +% and initialize those diversions +% ----------------------------- +define(DIVERT_INTERFACEDEF_C,3) +define(DIVERT_ENDINTERFACEDEF_C,4) +define(DIVERT_INTERFACEDEF,5) +define(DIVERT_ENDINTERFACEDEF,6) +define(DIVERT_ENTRYFUN,7) +define(DIVERT_ENDFILE,100) + + +% ----------------------------- +% The SET_PLACEHOLDER_NAME macro is to be ignored. It +% is used by the delivery manager only. +% ----------------------------- +define(`SET_PLACEHOLDER_NAME',`dnl') +') + +define(`GEN_INCLUDE', dnl +`ifelse(`-1',regexp(`$1',`\.m4$'), dnl + `error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file + ')',dnl +`#include "patsubst(`$1',`\.m4$',`\.h')"')') + +define(`REF_ITF', `GEN_INCLUDE($1)') + +% Mark safety relevant component, from which the IBase is used +% in a safety critical manner. +define(`INTERFACEDEF', +`divert(DIVERT_INTERFACEDEF) +#ifdef CPLUSPLUS +class `I'$1 : public IBase +{ + public: +divert +divert(DIVERT_INTERFACEDEF_C) + +ifelse($1,`CmpIoDrv',`/* */')dnl +ifelse($1,`CmpIoDrvC',`/* */')dnl +ifelse($1,`CmpIoDrvIec',`/* */') +typedef struct +{ + IBase_C *pBase; +divert') + +define(`ENDINTERFACEDEF', +`divert(DIVERT_ENDINTERFACEDEF)dnl +}; + #ifndef `ITF_'$1 + #define `ITF_'$1 static `I'$1 *`pI'$1 = NULL; + #endif + #define `EXTITF_'$1 +#else /*CPLUSPLUS*/ + typedef `I'$1_C `I'$1; + #ifndef `ITF_'$1 + #define `ITF_'$1 + #endif + #define `EXTITF_'$1 +#endif +divert +divert(DIVERT_ENDINTERFACEDEF_C)dnl +} `I'$1_C; +divert') + +define(`SET_INTERFACE_NAME', + `define(CMP_NAME, $1)' + `define(ITFNOTDEFINED,_UPPERCASE(_TRIM($1))_NOTIMPLEMENTED)' + `define(ITFEXTERNAL,_UPPERCASE(_TRIM($1))_EXTERNAL) +`#ifndef _'_UPPERCASE(_TRIM($1))`ITF_H_' +`#define _'_UPPERCASE(_TRIM($1))`ITF_H_' +` +#include "CmpStd.h"' +define(`_INTERFACE_NAME',`I$1') +INTERFACEDEF($1) +ENDINTERFACEDEF($1) +divert(DIVERT_ENDFILE) +`#ifdef CPLUSPLUS_ONLY' +` #undef CPLUSPLUS_ONLY' +`#endif' + +`#endif /*_'_UPPERCASE(_TRIM($1))`ITF_H_*/' +divert +') + + +% +% Call the DEF_API Makro with the following parameters: +% DEF_API(, , , ) +% +% Example: +% DEF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`REGISTER_API2', `s_pfCMRegisterAPI2( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), ifelse($6,1,_CHKSUM($1, $2, $4),$6), ifelse(eval($# < 7),1,0,$7))' )'dnl +`define(`GET_API', `s_pfCMGetAPI( "$3", (RTS_VOID_FCTPTR *)&pf$3, _CHKSUM($1, $2, $4))' )'dnl +`define(`GET_API2', `s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, ifelse(EXTERNAL,1,1,0), ifelse($6,0,_CHKSUM($1, $2, $4),$6), ifelse(eval($# < 7),1,0,$7))' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6),`1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($5, `1',`typedef $1 ($2 CDECL_EXT* _FUN_PTR_IEC($3)) $4;',`typedef $1 ($2 * _FUN_PTR($3)) $4;'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) ifelse(_TRIM($1),`void',`',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2),CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2)' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3 $3' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ifelse(EXTERNAL,1,ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2),ERR_OK)' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2),CAL_EXPAPI)' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6),`1',`_FUN_PTR($3)',`_FUN_PTR_IEC($3)') pf$3;' + `#define EXT_'$3 `extern ifelse(eval($# < 6),`1',`_FUN_PTR($3)',`_FUN_PTR_IEC($3)') pf$3'; + `#define GET_$3(fl) ifelse(eval($# < 7),1, `s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), ifelse(eval($# < 6),1,0,`ifelse($6,0,_CHKSUM($1, $2, $4),$6)'), ifelse(eval($# < 7),1,0,$7))',`s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl) | CM_IMPORT_EXTERNAL_LIB_FUNCTION, ifelse($6,0,_CHKSUM($1, $2, $4),$6), ifelse(eval($# < 7),1,0,$7))')' + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 ifelse(eval($# < 7),`1',REGISTER_API, REGISTER_API2)' +`#endif' +) + +% +% Call the DEF_STATIC_API Makro with the following parameters: +% DEF_STATIC_API(, , , ) +% +% Example: +% DEF_STATIC_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_STATIC_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6),`1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3 $3' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ifelse(EXTERNAL,1,REGISTER_API,ERR_OK)' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `_FUN_PTR($3) pf$3;' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4),0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +) + +% +% Call the DEF_CREATEITF_API Makro with the following parameters: +% DEF_CREATEITF_API(, , , ) +% +% Example: +% DEF_CREATEITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CREATEITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``((I'CMP_NAME`*)s_pfCMCreateInstance(CLASSID_C'CMP_NAME'`, NULL))->I'$3 + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL && pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``((I'CMP_NAME`*)s_pfCMCreateInstance(CLASSID_C'CMP_NAME'`, NULL))->I'$3 + `#define CHK_$3 (s_pfCMCreateInstance != NULL && pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_DELETEITF_API Makro with the following parameters: +% DEF_DELETEITF_API(, , , ) +% +% Example: +% DEF_DELETEITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_DELETEITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) ifelse(_TRIM($1),`void',`',``(((RTS_HANDLE)p0 == NULL || (RTS_HANDLE)p0 == RTS_INVALID_HANDLE) ? ERR_PARAMETER : ((I'CMP_NAME`*)p0)'->I'$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) ifelse(_TRIM($1),`void',`',``(((RTS_HANDLE)p0 == NULL || (RTS_HANDLE)p0 == RTS_INVALID_HANDLE) ? ERR_PARAMETER : ((I'CMP_NAME`*)p0)'->I'$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_HANDLEITF_API Makro with the following parameters: +% DEF_HANDLEITF_API(, , , ) +% +% Example: +% DEF_HANDLEITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_HANDLEITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 (pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 (pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_HANDLEITF_API2 Makro with the following parameters: +% DEF_HANDLEITF_API2(, , , ) +% +% Example: +% DEF_HANDLEITF_API2(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_HANDLEITF_API2', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)(((IBase*)p0)->QueryInterface((IBase *)p0, ITFID_I`'CMP_NAME, (RTS_RESULT *)1)))->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 (pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)(((IBase*)p0)->QueryInterface((IBase *)p0, ITFID_I`'CMP_NAME, (RTS_RESULT *)1)))->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 (pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_HANDLEITF_API_OWNCPP Makro with the following parameters: +% DEF_HANDLEITF_API_OWNCPP(, , , ) +% +% NOTE: +% Identical to DEF_HANDLEITF_API except, that in the C++ wrapper you can use your own implementation. +% +% Example: +% DEF_HANDLEITF_API_OWNCPP(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_HANDLEITF_API_OWNCPP', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 (pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 (pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + + +% +% Call the DEF_CREATECLASSITF_API Makro with the following parameters: +% DEF_CREATECLASSITF_API(, , , ) +% +% Example: +% DEF_CREATECLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CREATECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_CREATECLASSITF2_API Makro with the following parameters: +% DEF_CREATECLASSITF2_API(, , , ) +% +% Example: +% DEF_CREATECLASSITF2_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CREATECLASSITF2_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == CLASSID_INVALID ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == CLASSID_INVALID ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4'))`\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == CLASSID_INVALID ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +define(`NUMPARAMS_xxx',`$#') +define(`NUMPARAMS',`NUMPARAMS_xxx(_EXTRACTPARAMS($1))') +define(`MINPARAMS',`eval(NUMPARAMS(`$1') >= $2)') + +% +% Call the DEF_PRODUCECLASSITF_API Makro with the following parameters: +% DEF_PRODUCECLASSITF_API(, , , ) +% +% Example: +% DEF_PRODUCECLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_PRODUCECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_PRODUCECLASSITF_API_OWNCPP Makro with the following parameters: +% DEF_PRODUCECLASSITF_API_OWNCPP(, , , ) +% +% Example: +% DEF_PRODUCECLASSITF_API_OWNCPP(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_PRODUCECLASSITF_API_OWNCPP', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_DELETECLASSITF_API Makro with the following parameters: +% DEF_DELETECLASSITF_API(, , , ) +% +% Example: +% DEF_DELETECLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_DELETECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_DELETECLASSITF2_API Makro with the following parameters: +% DEF_DELETECLASSITF2_API(, , , ) +% +% Example: +% DEF_DELETECLASSITF2_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_DELETECLASSITF2_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) != ERR_OK ? \ + ERR_FAILED :\ + s_pfCMDeleteInstance2(((I`'CMP_NAME*)p0)->pBase->ClassId, ((I`'CMP_NAME*)p0)->pBase))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) != ERR_OK ? \ + ERR_FAILED :\ + s_pfCMDeleteInstance2(((I`'CMP_NAME*)p0)->pBase->ClassId, ((I`'CMP_NAME*)p0)->pBase))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) != ERR_OK ? \ + ERR_FAILED :\ + s_pfCMDeleteInstance2(((I`'CMP_NAME*)p0)->pBase->ClassId, ((I`'CMP_NAME*)p0)->pBase))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_CLASSITF_API Makro with the following parameters: +% DEF_CLASSITF_API(, , , ) +% +% Example: +% DEF_CLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_RETURNHANDLECLASSITF_API Makro with the following parameters: +% DEF_RETURNHANDLECLASSITF_API(, , , ) +% +% Example: +% DEF_RETURNHANDLECLASSITF_API(`RTS_HANDLE',`CDECL', `MyApiFunction', `(RTS_HANDLE hObject, int nX, void *pData)') +% ==> ((IMyInterface*)p0)->IMyInterfaceFunction(((IMyInterface*)p0)->pBase->hInstance,p1,p2,p3)) +% +define(`DEF_RETURNHANDLECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : ((('I`'CMP_NAME*)p0)->pBase->hInstance = ((I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : ((('I`'CMP_NAME*)p0)->pBase->hInstance = ((I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : ((('I`'CMP_NAME*)p0)->pBase->hInstance = ((I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_STATICITF_API Makro with the following parameters: +% DEF_STATICITF_API(, , , ) +% +% Example: +% DEF_STATICITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_STATICITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``C'CMP_NAME::I'$3 + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``C'CMP_NAME::I'$3 + `#define CHK_$3 TRUE' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) static $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_API Makro with the following parameters: +% DEF_ITF_API(, , , ) +% +% Example: +% DEF_ITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',ifelse(_TRIM($1),`void *',`($1)(RTS_SIZE)ERR_NOTIMPLEMENTED',`($1)ERR_NOTIMPLEMENTED')))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_API Makro with the following parameters: +% DEF_ITF_API(, , , ) +% +% Example: +% DEF_ITF_API_OWNCPP(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_API_OWNCPP', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',ifelse(_TRIM($1),`void *',`($1)(RTS_SIZE)ERR_NOTIMPLEMENTED',`($1)ERR_NOTIMPLEMENTED')))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_API Makro with the following parameters: +% DEF_ITF_API(, , , ) +% +% Example: +% DEF_ITF_API2(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_API2', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',ifelse(_TRIM($1),`void *',`($1)(RTS_SIZE)ERR_NOTIMPLEMENTED',`($1)ERR_NOTIMPLEMENTED')))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_GLOBAL_API Makro with the following parameters: +% DEF_ITF_GLOBAL_API(, , , ) +% +% Example: +% DEF_ITF_GLOBAL_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_GLOBAL_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`DLL_DECL $1 $2 CDECL_EXT $3$4;',`DLL_DECL $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) +divert \ No newline at end of file diff --git a/ExtensionSDK/m4/Implementation.m4 b/ExtensionSDK/m4/Implementation.m4 new file mode 100644 index 0000000..82c9013 --- /dev/null +++ b/ExtensionSDK/m4/Implementation.m4 @@ -0,0 +1,897 @@ +divert(-1) +% ----------------------------------- +% Copyright: Copyright CODESYS Development GmbH +% ----------------------------------- +# The following lines ("DO NOT MODIFY...") generate a message in the destination file. +# This comment is not aimed at this file. +divert`'dnl +/*********************************************************************** + * DO NOT MODIFY! + * This is a generated file. Do not modify it's contents directly + ***********************************************************************/ +include(`Util.m4') +divert(-1) + +changecom(`%') +% ----------------------------- +% Define some diverts that gather information about some special features +% and initialize those diversions +% ----------------------------- +define(DIVERT_USE,3) +define(DIVERT_USEIMPORT,4) +define(DIVERT_USEEXTERN,5) +define(DIVERT_INIT,6) +define(DIVERT_RENAME_ENTRYFUN,9) +define(DIVERT_BEGINCLASSDEF,10) +define(DIVERT_CLASSDEF,11) +define(DIVERT_CLASSMEMBERS,12) +define(DIVERT_ENDCLASSDEF,13) +define(DIVERT_ENDMETHODDEF,15) +define(DIVERT_URTS_DEFS, 20) +define(DIVERT_ENDFILE,100) + +define(`REF_ITF', `GEN_INCLUDE($1)') + +define(`COMPONENT_VERSION',`#define CMP_VERSION UINT32_C(`$1') +#define CMP_VERSION_STRING "_VERSION_MAJOR(`$1')._VERSION_SUB0(`$1')._VERSION_SUB1(`$1')._VERSION_SUB2(`$1')" +#define CMP_VERSION_RC _VERSION_MAJOR(`$1'),_VERSION_SUB0(`$1'),_VERSION_SUB1(`$1'),_VERSION_SUB2(`$1')') +define(`COMPONENT_VENDORID',`#define CMP_VENDORID $1 + +#ifndef WIN32_RESOURCES') + +divert(DIVERT_USE)dnl +#define USE_STMT \ + /*lint -save --e{528} --e{551} */ \ + static volatile PF_REGISTER_API s_pfCMRegisterAPI; \ + static volatile PF_REGISTER_API2 s_pfCMRegisterAPI2; \ + static volatile PF_GET_API s_pfCMGetAPI; \ + static volatile PF_GET_API2 s_pfCMGetAPI2; \ + static volatile PF_REGISTER_CLASS s_pfCMRegisterClass; \ + static volatile PF_CREATEINSTANCE s_pfCMCreateInstance; \ + static volatile PF_CALL_HOOK s_pfCMCallHook; \ + static const CMP_EXT_FUNCTION_REF s_ExternalsTable[] =\ + {\ + EXPORT_EXTREF_STMT\ + EXPORT_EXTREF2_STMT\ + { ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }\ + };\ + static const CMP_EXT_FUNCTION_REF s_ItfTable[] = EXPORT_CMPITF_STMT; \ + /*lint -restore */ \ + static int CDECL ExportFunctions(void); \ + static int CDECL ImportFunctions(void); \ + static IBase* CDECL CreateInstance(CLASSID cid, RTS_RESULT *pResult); \ + static RTS_RESULT CDECL DeleteInstance(IBase *pIBase); \ + static RTS_UI32 CDECL CmpGetVersion(void); \ + static RTS_RESULT CDECL HookFunction(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2); \ + dnl +divert(-1) + + +divert(DIVERT_USEIMPORT) +#define USEIMPORT_STMT \ + /*lint -save --e{551} */ \ + static volatile PF_REGISTER_API s_pfCMRegisterAPI; \ + static volatile PF_REGISTER_API2 s_pfCMRegisterAPI2; \ + static volatile PF_GET_API s_pfCMGetAPI; \ + static volatile PF_GET_API2 s_pfCMGetAPI2; \ + static volatile PF_REGISTER_CLASS s_pfCMRegisterClass; \ + static volatile PF_CREATEINSTANCE s_pfCMCreateInstance; \ + static volatile PF_CALL_HOOK s_pfCMCallHook; \ + /*lint -restore */ \ + dnl +divert(-1) + + + +divert(DIVERT_USEEXTERN) +#define USEEXTERN_STMT \ + dnl +divert(-1) + +divert(DIVERT_RENAME_ENTRYFUN) +#ifndef COMPONENT_NAME + #error COMPONENT_NAME is not defined. This prevents the component from being linked statically. Use `SET_COMPONENT_NAME'() to set the name of the component in your .m4 component description. +#endif +divert(-1) + +divert(DIVERT_BEGINCLASSDEF) +#ifdef CPLUSPLUS +divert(-1) + +divert(DIVERT_ENDMETHODDEF) +#endif /*CPLUSPLUS*/ +divert(-1) + +divert(DIVERT_ENDFILE) +`#endif /*WIN32_RESOURCES*/' +`#endif /*_DEP_H_*/' +divert(-1)') + + +% --------------------------- +% The SET_COMPONENT_NAME directive should reside at the top of each "Itf" file. +% --------------------------- + +define(`SET_COMPONENT_NAME', +`define(_COMPONENT_NAME,`$1')dnl +`#ifndef _'_UPPERCASE(_COMPONENT_NAME)`DEP_H_' +`#define _'_UPPERCASE(_COMPONENT_NAME)`DEP_H_' + +#define COMPONENT_NAME "$1" COMPONENT_NAME_POSTFIX +#define COMPONENT_ID ADDVENDORID(CMP_VENDORID, CMPID_$1) +#define COMPONENT_NAME_UNQUOTED $1 + +divert(DIVERT_RENAME_ENTRYFUN) + +divert(DIVERT_RENAME_ENTRYFUN) + +#if defined(STATIC_LINK) || defined(MIXED_LINK) || defined(DYNAMIC_LINK) || defined(CPLUSPLUS_STATIC_LINK) + #define ComponentEntry $1`'__Entry +#endif + +divert + +divert(DIVERT_URTS_DEFS)dnl + +divert +') + +% ----------------------------- +% The COMPONENT_SOURCES and COMPONENT_SYSSOURCES macros are to be ignored. They +% are used by the configuration tool only. +% ----------------------------- +define(`COMPONENT_SOURCES',`dnl') +define(`EXTERNAL_SOURCES',`dnl') +define(`EXTERNAL_INCLUDES',`dnl') +define(`COMPONENT_INCLUDES',`dnl') +define(`COMPONENT_SYSSOURCES',`dnl') +define(`CONTRIB_SOURCES',`dnl') +define(`CONTRIB_INCLUDES',`dnl') +') + +% ----------------------------- +% The CATEGORY macro is to be ignored. It +% is used by the configuration tool only. +% ----------------------------- +define(`CATEGORY',`dnl') +') + +% ----------------------------- +% After all input is read, some macros have to be created +% ----------------------------- +define(`ADD_IMPORTS',`ifdef(`__IMPORTS',`__IMPORTS popdef(`__IMPORTS') ADD_IMPORTS')') +define(`CREATE_IMPORT_STMT', dnl +`ifdef(`__IMPORTS',`#if defined(STATIC_LINK) + #define IMPORT_STMT +#else + #define IMPORT_STMT \ + {\ + RTS_RESULT importResult = ERR_OK;\ + RTS_RESULT TempResult = ERR_OK;\ + INIT_STMT \ + TempResult = GET_LogAdd(CM_IMPORT_OPTIONAL_FUNCTION); \ + TempResult = GET_CMUtlMemCpy(CM_IMPORT_OPTIONAL_FUNCTION); \ + ADD_IMPORTS \ + /* To make LINT happy */\ + TempResult = TempResult;\ + if (ERR_OK != importResult) return importResult;\ + } +#endif +',`#define IMPORT_STMT INIT_STMT') +') + +% ----------------------------- +% This function emits all export statements inside the macro EXPORT_STMT (one after another) +% ----------------------------- +define(`ADD_EXPORTS',`ifdef(`__EXPORTS',`__EXPORTS popdef(`__EXPORTS') ADD_EXPORTS')') +define(`ADD_EXTREF_EXPORTS',`ifdef(`__EXPORTS_EXTREF',`__EXPORTS_EXTREF popdef(`__EXPORTS_EXTREF') ADD_EXTREF_EXPORTS')') +define(`ADD_EXTREF_EXPORTS2',`ifdef(`__EXPORTS_EXTREF2',`__EXPORTS_EXTREF2 popdef(`__EXPORTS_EXTREF2') ADD_EXTREF_EXPORTS2')') +define(`ADD_EXPORTS_CPLUSPLUS_ONLY',`ifdef(`__EXPORTS_CPLUSPLUS_ONLY',`__EXPORTS_CPLUSPLUS_ONLY popdef(`__EXPORTS_CPLUSPLUS_ONLY') ADD_EXPORTS_CPLUSPLUS_ONLY')') + +% ----------------------------- +% Define the EXPORT_STMT macro. the content is filled out by ADD_EXPORTS. +% ----------------------------- +define(`CREATE_EXPORT_STMT',dnl +`ifdef(`__EXPORTS_EXTREF',dnl +`#ifndef _UPPERCASE(_COMPONENT_NAME)_DISABLE_EXTREF +#define EXPORT_EXTREF_STMT \ + ADD_EXTREF_EXPORTS +#else +#define EXPORT_EXTREF_STMT +#endif',`#define EXPORT_EXTREF_STMT') +'dnl +`ifdef(`__EXPORTS_EXTREF2',dnl +`#ifndef _UPPERCASE(_COMPONENT_NAME)_DISABLE_EXTREF2 +#define EXPORT_EXTREF2_STMT \ + ADD_EXTREF_EXPORTS2 +#else +#define EXPORT_EXTREF2_STMT +#endif',`#define EXPORT_EXTREF2_STMT') +'dnl +`ifdef(`__EXPORTS',dnl +`#if !defined(_UPPERCASE(_COMPONENT_NAME)_DISABLE_CMPITF) && !defined(STATIC_LINK) && !defined(CPLUSPLUS) && !defined(CPLUSPLUS_ONLY) +#define EXPORT_CMPITF_STMT \ + {\ + ADD_EXPORTS\ + { ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }\ + } +#else +#define EXPORT_CMPITF_STMT \ + {\ + { ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }\ + } +#endif',`#define EXPORT_CMPITF_STMT {{ ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }}') +'dnl +`ifdef(`__EXPORTS_CPLUSPLUS_ONLY', ` +#define EXPORT_CPP_STMT \ + {\ + ADD_EXPORTS_CPLUSPLUS_ONLY \ + } +',`#define EXPORT_CPP_STMT') +'dnl +) + +% ----------------------------- +% This function emits all init statements inside the macro INIT_STMT (one after another) +% ----------------------------- +define(`ADD_INITS',`ifdef(`__INITS',`__INITS popdef(`__INITS') ADD_INITS')') +define(`ADD_INIT_LOCALS',`ifdef(`__INIT_LOCALS',`__INIT_LOCALS popdef(`__INIT_LOCALS') ADD_INIT_LOCALS')') +define(`ADD_EXITS',`ifdef(`__EXITS',`__EXITS popdef(`__EXITS') ADD_EXITS')') + +% ----------------------------- +% Define the INIT_STMT macro. +% ----------------------------- +define(`CREATE_INIT_STMT', dnl +`ifdef(`__INITS',`` +#ifdef CPLUSPLUS + #define INIT_STMT' \ + {\ + IBase *pIBase;\ + RTS_RESULT initResult;\ + if (pICmpLog == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCmpLog, &initResult); \ + if (pIBase != NULL) \ + { \ + pICmpLog = (ICmpLog *)pIBase->QueryInterface(pIBase, ITFID_ICmpLog, &initResult); \ + pIBase->Release(pIBase); \ + } \ + } \ + if (pICMUtils == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCMUtils, &initResult); \ + if (pIBase != NULL) \ + { \ + pICMUtils = (ICMUtils *)pIBase->QueryInterface(pIBase, ITFID_ICMUtils, &initResult); \ + pIBase->Release(pIBase); \ + } \ + } \ + ADD_INITS \ + } + #define INIT_LOCALS_STMT \ + {\ + pICmpLog = NULL; \ + pICMUtils = NULL; \ + ADD_INIT_LOCALS \ + } + #define EXIT_STMT \ + {\ + IBase *pIBase;\ + RTS_RESULT exitResult;\ + if (pICmpLog != NULL) \ + { \ + pIBase = (IBase *)pICmpLog->QueryInterface(pICmpLog, ITFID_IBase, &exitResult); \ + if (pIBase != NULL) \ + { \ + pIBase->Release(pIBase); \ + if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \ + pICmpLog = NULL; \ + } \ + } \ + if (pICMUtils != NULL) \ + { \ + pIBase = (IBase *)pICMUtils->QueryInterface(pICMUtils, ITFID_IBase, &exitResult); \ + if (pIBase != NULL) \ + { \ + pIBase->Release(pIBase); \ + if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \ + pICMUtils = NULL; \ + } \ + } \ + ADD_EXITS \ + } +#else + #define INIT_STMT + #define INIT_LOCALS_STMT + #define EXIT_STMT +#endif +',`#ifdef CPLUSPLUS + #define INIT_STMT \ + {\ + IBase *pIBase;\ + RTS_RESULT initResult;\ + if (pICmpLog == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCmpLog, &initResult); \ + if (pIBase != NULL) \ + { \ + pICmpLog = (ICmpLog *)pIBase->QueryInterface(pIBase, ITFID_ICmpLog, &initResult); \ + pIBase->Release(pIBase); \ + } \ + } \ + if (pICMUtils == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCMUtils, &initResult); \ + if (pIBase != NULL) \ + { \ + pICMUtils = (ICMUtils *)pIBase->QueryInterface(pIBase, ITFID_ICMUtils, &initResult); \ + pIBase->Release(pIBase); \ + } \ + } \ + } + #define INIT_LOCALS_STMT \ + {\ + pICmpLog = NULL; \ + pICMUtils = NULL; \ + ADD_INIT_LOCALS \ + } + #define EXIT_STMT \ + {\ + IBase *pIBase;\ + RTS_RESULT exitResult;\ + if (pICmpLog != NULL) \ + { \ + pIBase = (IBase *)pICmpLog->QueryInterface(pICmpLog, ITFID_IBase, &exitResult); \ + if (pIBase != NULL) \ + { \ + pIBase->Release(pIBase); \ + if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \ + pICmpLog = NULL; \ + } \ + } \ + if (pICMUtils != NULL) \ + { \ + pIBase = (IBase *)pICMUtils->QueryInterface(pICMUtils, ITFID_IBase, &exitResult); \ + if (pIBase != NULL) \ + { \ + pIBase->Release(pIBase); \ + if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \ + pICMUtils = NULL; \ + } \ + } \ + } +#else + #define INIT_STMT + #define INIT_LOCALS_STMT + #define EXIT_STMT +#endif') +') + + +% ----------------------------- +% The next lines causes the CREATE_IMPORT_STMT, CREATE_EXPORT_STMT +% and CREATE_INIT_STMT function to be called after all input has been read +% ----------------------------- +m4wrap(` +CREATE_EXPORT_STMT + +#if defined(STATIC_LINK) + #define EXPORT_STMT\ + {\ + RTS_RESULT ExpResult;\ + if (NULL == s_pfCMRegisterAPI)\ + return ERR_NOTINITIALIZED;\ + ExpResult = s_pfCMRegisterAPI(s_ExternalsTable, 0, 1, COMPONENT_ID);\ + if (ERR_OK != ExpResult)\ + return ExpResult;\ + } +#else + #define EXPORT_STMT\ + {\ + RTS_RESULT ExpResult;\ + if (NULL == s_pfCMRegisterAPI)\ + return ERR_NOTINITIALIZED;\ + ExpResult = s_pfCMRegisterAPI(s_ExternalsTable, 0, 1, COMPONENT_ID);\ + if (ERR_OK != ExpResult)\ + return ExpResult;\ + ExpResult = s_pfCMRegisterAPI(s_ItfTable, 0, 0, COMPONENT_ID);\ + if (ERR_OK != ExpResult)\ + return ExpResult;\ + } +#endif + +') +m4wrap(` +CREATE_IMPORT_STMT +') +m4wrap(` +CREATE_INIT_STMT +') + + +% ----------------------------- +% Generate an "#include " directive, +% replacing the extension ".m4" with ".h" +% ----------------------------- +define(`GET_INCLUDE_FILE', `ifelse(`-1',regexp(`$1',`\.m4$'),`error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file')',dnl +`/** + * \file 'patsubst(`$1',`\.m4$',`\.h')` + */')') + +define(`GEN_INCLUDE', dnl +`ifelse(`-1',regexp(`$1',`\.m4$'), dnl + `error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file + ')',dnl +$1,`CmpLogItf.m4', `/*Obsolete include: CmpLogItf.m4*/',dnl +$1,`CMUtilsItf.m4', `/*Obsolete include: CMUtilsItf.m4*/',dnl +`#include "patsubst(`$1',`\.m4$',`\.h')"')') + +define(`GEN_INIT_STMT', `pushdef(`__INITS',ifelse($1,`CMUtils',`/*Obsolete include CMUtils*/ \ + ', + $1,`CmpLog',`/*Obsolete include CmpLog*/ \ + ', +`if (pI$1 == NULL && s_pfCMCreateInstance != NULL) \ + { \ + pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_C$1, &initResult); \ + if (pIBase != NULL) \ + { \ + pI$1 = (I$1 *)pIBase->QueryInterface(pIBase, ITFID_I$1, &initResult); \ + pIBase->Release(pIBase); \ + } \ + } \ + '))') + +define(`GEN_INIT_LOCALS_STMT', `pushdef(`__INIT_LOCALS',`ifelse($1,`CMUtils',`/*Obsolete include CMUtils*/ \ + ', +$1,`CmpLog',`/*Obsolete include CmpLog*/ \ + ', +`pI$1 = NULL; \ + ')')') + +define(`GEN_EXIT_STMT', `pushdef(`__EXITS',`ifelse($1,`CMUtils',`/*Obsolete include CMUtils*/ \ + ', +$1,`CmpLog',`/*Obsolete include CmpLog*/ \ + ', +`if (pI$1 != NULL) \ + { \ + pIBase = (IBase *)pI$1->QueryInterface(pI$1, ITFID_IBase, &exitResult); \ + if (pIBase != NULL) \ + { \ + pIBase->Release(pIBase); \ + if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \ + pI$1 = NULL; \ + } \ + } \ + ')')') + +define(`GEN_USED_INTERFACES', dnl +`ifelse(`-1',regexp(`$1',`\.m4$'), dnl + `error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file + ')',dnl +`GEN_INCLUDE($1)'dnl +`GEN_INIT_STMT(`'patsubst(`$1',`\Itf.m4$',`'))'dnl +`GEN_INIT_LOCALS_STMT(`'patsubst(`$1',`\Itf.m4$',`'))'dnl +`GEN_EXIT_STMT(`'patsubst(`$1',`\Itf.m4$',`'))')dnl +') + +% ----------------------------- +% All variations of DEF_*_API +% Depending on the Variable DEF_CPP, +% we are adding them to __EXPORTS or __EXPORTS_CPP. +% ----------------------------- +define(`PUSH_ITF', dnl +`ifdef(`DEF_CPP', dnl +`pushdef(`__EXPORTS_CPLUSPLUS_ONLY',` \ + if (ERR_OK != EXP_`'_COMPONENT_NAME`'$3) \ + {\ + if (CHK_LogAdd)\ + (void)CAL_LogAdd(STD_LOGGER, COMPONENT_ID, LOG_ERROR, ERR_FAILED, 0, "Export of ApiFunction [$3] failed\n"); \ + return ERR_FAILED;\ + }')' +, dnl +`pushdef(`__EXPORTS_EXTREF',`ifelse(`$5',1,`ifelse(`$8',1,`', `{ (RTS_VOID_FCTPTR)$3, "$3", ifelse(eval($#>5),1,$6,0), ifelse(eval($#>6),1,$7,0) },\ + ')',`')')' dnl +`pushdef(`__EXPORTS_EXTREF2',`ifelse(`$8',1,`{ (RTS_VOID_FCTPTR)$3, "$3", $6, $7 }, \ + ',`')')' dnl +`pushdef(`__EXPORTS',`ifelse(`$5',1,`',`{ (RTS_VOID_FCTPTR)$3, "$3", 0, 0 },\ + ')')' dnl +)' dnl +) + +define(`PUSH_CLASS_ITF', dnl +`ifdef(`DEF_CPP', dnl +`pushdef(`__EXPORTS_CPLUSPLUS_ONLY',` \ + if (ERR_OK != EXP_`'_COMPONENT_NAME`'$3) \ + {\ + if (CHK_LogAdd)\ + (void)CAL_LogAdd(STD_LOGGER, COMPONENT_ID, LOG_ERROR, ERR_FAILED, 0, "Export of ApiFunction [$3] failed\n"); \ + return ERR_FAILED;\ + }')' +, dnl +`pushdef(`__EXPORTS_EXTREF',`ifelse(`$5',1,`ifelse(`$8',1,`',`{ (RTS_VOID_FCTPTR)$3, "$3",ifelse(eval($#>5),1,$6,0),ifelse(eval($#>6),1,$7,0)},\ + ')',`')')' dnl +`pushdef(`__EXPORTS_EXTREF2',`ifelse(`$8',1,`{ (RTS_VOID_FCTPTR)$3, "$3", $6, $7 }, \ + ',`')')' dnl +`pushdef(`__EXPORTS',`')' dnl +)' dnl +) + + + +define(`DEF_API', dnl +`PUSH_ITF($@)' dnl +) + +define(`DEF_STATIC_API', +`PUSH_ITF($@)' dnl +) + +define(`DEF_CREATEITF_API', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_DELETEITF_API', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_HANDLEITF_API', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_HANDLEITF_API2', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_HANDLEITF_API_OWNCPP', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_CREATECLASSITF_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_CREATECLASSITF2_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_PRODUCECLASSITF_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_PRODUCECLASSITF_API_OWNCPP', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_DELETECLASSITF_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_DELETECLASSITF2_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_CLASSITF_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_RETURNHANDLECLASSITF_API', +`PUSH_CLASS_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_STATICITF_API', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) static $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_ITF_API', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_ITF_API_OWNCPP', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_ITF_API2', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + +define(`DEF_ITF_GLOBAL_API', +`PUSH_ITF($@)' dnl +`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert(-1)' +) + + + +define(`SET_INTERFACE_NAME',` + ifdef(`_IN_IMPLEMENT',` + pushdef(`_INTERFACE_LIST', `$1') + pushdef(`_INTERFACECAST_LIST',` + else if (iid == ITFID_`I'$1) + pItf = dynamic_cast<`I'$1 *>(this);')', + `ifelse($1,`CMUtils',`divert(DIVERT_USE)\ + /*obsolete entry ITF_$1*/ dnl divert(-1)', + $1,`CmpLog',`divert(DIVERT_USE)\ + /*obsolete entry ITF_$1*/ dnl divert(-1)', + `divert(DIVERT_USE)\ + `ITF_'`$1'dnl') + ifelse($1,`CMUtils',`divert(DIVERT_USEIMPORT)\ + /*obsolete entry ITF_$1*/ dnl divert(-1)', + $1,`CmpLog',`divert(DIVERT_USEIMPORT)\ + /*obsolete entry ITF_$1*/ dnl divert(-1)', + `divert(DIVERT_USEIMPORT)\ + `ITF_'`$1'dnl') + ifelse($1,`CMUtils',`divert(DIVERT_USEEXTERN)\ + /*obsolete entry EXTITF_$1*/ dnl divert(-1)', + $1,`CmpLog',`divert(DIVERT_USEEXTERN)\ + /*obsolete entry EXTITF_$1*/ dnl divert(-1)', + `divert(DIVERT_USEEXTERN)\ + `EXTITF_'`$1'dnl') + divert(-1)') +') + +define(`WRITE_INTERFACE_LIST2',`ifdef(`_INTERFACE_LIST',`, public `I'_INTERFACE_LIST popdef(`_INTERFACE_LIST')WRITE_INTERFACE_LIST2')') +define(`WRITE_INTERFACE_LIST',`ifdef(`_INTERFACE_LIST',`define(`_FIRST_INTERFACE_NAME',_INTERFACE_LIST)public `I'_INTERFACE_LIST popdef(`_INTERFACE_LIST')WRITE_INTERFACE_LIST2')') + +define(`WRITE_INTERFACE_CASTS',`ifdef(`_INTERFACECAST_LIST',`_INTERFACECAST_LIST popdef(`_INTERFACECAST_LIST')WRITE_INTERFACE_CASTS')') + + +define(`TASK',`#ifndef RTS_TASKNAME_`'_UPPERCASE($1) + #define RTS_TASKNAME_`'_UPPERCASE($1) "$1" +#endif +#ifndef RTS_TASKPRIO_`'_UPPERCASE($1) + #define RTS_TASKPRIO_`'_UPPERCASE($1) $2 +#endif +ifelse($3,`',`', `#ifndef RTS_TASKGROUP_`'_UPPERCASE($1) + #define RTS_TASKGROUP_`'_UPPERCASE($1) $3 +#endif') +') + +define(`TASKPREFIX',`#ifndef RTS_TASKPREFIX_`'_UPPERCASE($1) + #define RTS_TASKPREFIX_`'_UPPERCASE($1) "$1" +#endif +#ifndef RTS_TASKPRIO_`'_UPPERCASE($1) + #define RTS_TASKPRIO_`'_UPPERCASE($1) $2 +#endif +ifelse($3,`',`', `#ifndef RTS_TASKGROUP_`'_UPPERCASE($1) + #define RTS_TASKGROUP_`'_UPPERCASE($1) $3 +#endif') +') + +define(`TASKPLACEHOLDER',`#ifndef RTS_TASKPLACEHOLDER_`'_UPPERCASE($1) + #define RTS_TASKPLACEHOLDER_`'_UPPERCASE($1) "" +#endif +#ifndef RTS_TASKPRIO_`'_UPPERCASE($1) + #define RTS_TASKPRIO_`'_UPPERCASE($1) $2 +#endif +ifelse($3,`',`', `#ifndef RTS_TASKGROUP_`'_UPPERCASE($1) + #define RTS_TASKGROUP_`'_UPPERCASE($1) $3 +#endif') +') + + +% ----------------------------- +% Get all interfaces, which are implemented by this component. +% Differentiate between C++ and C Interfaces. +% ----------------------------- +define(`DO_IMPLEMENT_ITF',` +GET_INCLUDE_FILE($1) +GEN_INCLUDE($1) +ifelse(`$#',1,`',`DO_IMPLEMENT_ITF(shift($@))') +divert(-1) +include($1) +divert +') + + +define(`IMPLEMENT_QUERY_INTERFACE',`define(`QUERY_INTERFACE', `$1')') + +define(`IMPLEMENT_ITF',dnl +`undefine(`DEF_CPP')'dnl +`IMPLEMENT_ITF_AS(``C'_COMPONENT_NAME', $@)' +) +define(`IMPLEMENT_CPP_ITF', +`define(`DEF_CPP')' +`undefine(`_INTERFACE_LIST')undefine(`_INTERFACECAST_LIST')undefine(`_FIRST_INTERFACE_NAME')define(`_IN_IMPLEMENT')define(`_INTERFACE_LIST_INIT')' +`DO_IMPLEMENT_ITF($@)' +) +define(`IMPLEMENT_ITF_AS', +`ifdef(`_INTERFACE_LIST_INIT', `', +`undefine(`_INTERFACE_LIST')undefine(`_INTERFACECAST_LIST')undefine(`_FIRST_INTERFACE_NAME')' +)' +`define(`_IN_IMPLEMENT')define(`_INTERFACE_LIST_INIT')' +` +DO_IMPLEMENT_ITF(shift($@)) +divert(DIVERT_CLASSDEF) +class $1 : WRITE_INTERFACE_LIST +{ + public: + $1`() : h'_COMPONENT_NAME`'(RTS_INVALID_HANDLE), iRefCount(0) + { + } + virtual ~$1`()' + { + } + virtual unsigned long AddRef(IBase *pIBase = NULL) + { + iRefCount++; + return iRefCount; + } + virtual unsigned long Release(IBase *pIBase = NULL) + { + iRefCount--; + if (iRefCount == 0) + { + delete this; + return 0; + } + return iRefCount; + } + + ifdef(`QUERY_INTERFACE', `QUERY_INTERFACE', ` + virtual void* QueryInterface(IBase *pIBase, ITFID iid, RTS_RESULT *pResult) + { + void *pItf; + if (iid == ITFID_IBase) + pItf = dynamic_cast((`I'_FIRST_INTERFACE_NAME *)this);dnl + WRITE_INTERFACE_CASTS + else + { + if (pResult != NULL) + *pResult = ERR_NOTIMPLEMENTED; + return NULL; + } + if (pResult != (RTS_RESULT *)1) + (reinterpret_cast(pItf))->AddRef(); + if (pResult != NULL && pResult != (RTS_RESULT *)1) + *pResult = ERR_OK; + return pItf; + }') +divert(DIVERT_ENDCLASSDEF) + public: + RTS_HANDLE h`'_COMPONENT_NAME; + int iRefCount; +}; +divert(-1) +define(`DIVERT_CLASSDEF',eval(DIVERT_CLASSDEF+3)) +define(`DIVERT_CLASSMEMBERS',eval(DIVERT_CLASSMEMBERS+3)) +define(`DIVERT_ENDCLASSDEF',eval(DIVERT_ENDCLASSDEF+3)) +undefine(`_IN_IMPLEMENT') +divert +') + +%define(`IMPLEMENT_ITF', ` +%divert(-1) +%include($1) +%divert') + +define(`GENERATE_ITF_MACRO', `divert(DIVERT_USE)\ + `ITF_'`$1' dnl +divert(-1)') + +define(`USE_ITF', `GEN_USED_INTERFACES($1) +divert(-1) +pushdef(`DEF_API',`') +pushdef(`DEF_STATIC_API',`') +pushdef(`DEF_CREATEITF_API',`') +pushdef(`DEF_DELETEITF_API',`') +pushdef(`DEF_HANDLEITF_API',`') +pushdef(`DEF_HANDLEITF_API2',`') +pushdef(`DEF_HANDLEITF_API_OWNCPP',`') +pushdef(`DEF_CREATECLASSITF_API',`') +pushdef(`DEF_CREATECLASSITF2_API',`') +pushdef(`DEF_PRODUCECLASSITF_API',`') +pushdef(`DEF_PRODUCECLASSITF_API_OWNCPP',`') +pushdef(`DEF_DELETECLASSITF_API',`') +pushdef(`DEF_DELETECLASSITF2_API',`') +pushdef(`DEF_CLASSITF_API',`') +pushdef(`DEF_RETURNHANDLECLASSITF_API',`') +pushdef(`DEF_STATICITF_API',`') +pushdef(`DEF_ITF_API',`') +pushdef(`DEF_ITF_API_OWNCPP',`') +pushdef(`DEF_ITF_API2',`') +pushdef(`DEF_ITF_GLOBAL_API',`') +include($1) +popdef(`DEF_API') +popdef(`DEF_STATIC_API') +popdef(`DEF_CREATEITF_API') +popdef(`DEF_DELETEITF_API') +popdef(`DEF_HANDLEITF_API') +popdef(`DEF_HANDLEITF_API_OWNCPP') +popdef(`DEF_HANDLEITF_API2') +popdef(`DEF_CREATECLASSITF_API') +popdef(`DEF_CREATECLASSITF2_API') +popdef(`DEF_PRODUCECLASSITF_API') +popdef(`DEF_PRODUCECLASSITF_API_OWNCPP') +popdef(`DEF_DELETECLASSITF_API') +popdef(`DEF_DELETECLASSITF2_API') +popdef(`DEF_RETURNHANDLECLASSITF_API') +popdef(`DEF_CLASSITF_API') +popdef(`DEF_STATICITF_API') +popdef(`DEF_ITF_API2') +popdef(`DEF_ITF_API') +popdef(`DEF_ITF_API_OWNCPP') +popdef(`DEF_ITF_GLOBAL_API') +divert +') + +define(`DOUSE', `divert(DIVERT_USE) \ + ifelse($1,`CMUtlMemCpy',`/*obsolete USE_$1*/', + $1,`LogAdd',`/*obsolete USE_$1*/', + USE_$1) dnl + divert(DIVERT_USEIMPORT) \ + ifelse($1,`CMUtlMemCpy',`/*obsolete USE_$1*/', + $1,`LogAdd',`/*obsolete USE_$1*/', + USE_$1) dnl + divert(DIVERT_USEEXTERN) \ + ifelse($1,`CMUtlMemCpy',`/*obsolete USE_$1*/', + $1,`LogAdd',`/*obsolete EXT_$1*/', + EXT_$1) dnl +divert') + + +define(`DOIMPORT_REQ', `pushdef(`__IMPORTS',`ifelse($1,`LogAdd', `/*Obsolete required include LogAdd*/ \ + ',dnl +$1,`CMUtlMemCpy', `/*Obsolete required include CMUtlMemCpy*/ \ + ', +`if (ERR_OK == importResult ) importResult = GET_$1(0);\ + ')')') + + +define(`DOIMPORT_OPT', `pushdef(`__IMPORTS',`ifelse($1,`LogAdd', `/*Obsolete optional include LogAdd*/ \ + ',dnl +$1,`CMUtlMemCpy', `/*Obsolete optional include CMUtlMemCpy*/ \ + ', +`if (ERR_OK == importResult ) TempResult = GET_$1(CM_IMPORT_OPTIONAL_FUNCTION);\ + ')')') + + + +define(`REQUIRED_IMPORTS', dnl + `ifelse($1,`', `', dnl + `_FOREACH(`DOIMPORT_REQ', $@)dnl + _FOREACH(`DOUSE', $@)dnl + ') +') + +define(`OPTIONAL_IMPORTS', dnl + `ifelse($1,`', `', dnl + `_FOREACH(`DOIMPORT_OPT', $@) dnl + _FOREACH(`DOUSE', $@) dnl + ') +') +changecom(`/*', `*/') +divert \ No newline at end of file diff --git a/ExtensionSDK/m4/Util.m4 b/ExtensionSDK/m4/Util.m4 new file mode 100644 index 0000000..e1d3d28 --- /dev/null +++ b/ExtensionSDK/m4/Util.m4 @@ -0,0 +1,113 @@ +divert(-1) +changecom(`%') +% +% Copyright: Copyright CODESYS Development GmbH +% + +define(`_CONCAT',`ifelse($#,0,`',$#,1,`$1',$#,2,`$1$2',`_CONCAT(`$1$2',`_CONCAT(shift(shift($@)))')')') +define(`_UPPERCASE', `translit($1, `a-z', `A-Z')') +define(`_TRIM',`regexp(`$1',`^[ ]*\(.*[^ ]\)[ ]*$',`\1')') +define(`_TRIMCMPPREFIX',`regexp(`$1',`...\(.*\)$',`\1')') +define(`_TRIMPOINTERPARAM', `ifelse(regexp(`$1',`[^,]\*',`RTS_HANDLE'),-1,0,1)') +define(`_TRIMVARPARAMLIST', `ifelse(regexp(`$1',`\.\.\.'),-1,0,1)') + +define(`_FUN_PTR', _FUN_PTR1(`_UPPERCASE(`$1')')) +define(`_FUN_PTR_IEC', _FUN_PTR1(`_UPPERCASE(`$1')'_IEC)) +define(`_FUN_PTR1', `PF'$1) + +define( _CHKSUM, 0) + +define(`_EXTRACTFIRSTPARAMTYPE',`_EXTRACTTYPE(_EXTRACTPARAMS(`$1'))') +define(`_EXTRACTTYPE',`regexp(`$1',`\([\w_]*[^ ]*\)',`\1')') +define(`_EXTRACTFIRSTPARAMTYPEPOINTER',`_EXTRACTTYPEPOINTER(_EXTRACTPARAMS(`$1'))') +define(`_EXTRACTTYPEPOINTER',`regexp(`$1',`\([\*]\)',`\1')') +define(`_EXTRACTFIRSTPARAMTYPERTS',`_EXTRACTTYPERTS(_EXTRACTPARAMS(`$1'))') +define(`_EXTRACTTYPERTS',`regexp(`$1',`\(RTS_\)',`\1')') + +define(`_EXTRACTPARAMS',`regexp(`$1',`^[ ]*(\(.*\))[ ]*$',`\1')') +define(`_EXTRACTPARAMNAME',`regexp(`$1',`\([a-zA-Z0-9_]+\)$',`\1')') +define(`_HAS_VARIABLEPARAMETER',`regexp(`$1',`\.\.\.')') +define(`_EXTRACT_VARIABLEPARAMETER_FORMAT',`regexp(`$1',` \*?\([a-zA-Z0-9_]+\), \.\.\.',`\1')') + +% +% Extract the parameter out of a parameter list. +% +% _EXTRACT_PARAM_LIST(`(int nCount, char *pBuf)') => `nCount,pBuf' +% _EXTRACT_PARAM_LIST(`(void)') => `' +% +define(_EXTRACT_PARAM_LIST_RECURSIVE,`ifelse($#,1,`_EXTRACTPARAMNAME(`$1')',`_EXTRACTPARAMNAME(`$1')`',_EXTRACT_PARAM_LIST_RECURSIVE(shift($@))')') +define(_EXTRACT_PARAM_LIST_,`ifelse(_TRIM(`$1'),`',`',_TRIM(`$1'),`void',`',`_EXTRACT_PARAM_LIST_RECURSIVE($@)')') +define(_EXTRACT_PARAM_LIST,`_EXTRACT_PARAM_LIST_(_EXTRACTPARAMS(`$1'))') + +define(_EXTRACT_PARAM_LIST_HANDLE_RECURSIVE,`ifelse($#,1,`,_EXTRACTPARAMNAME(`$1')',`,_EXTRACTPARAMNAME(`$1')`'_EXTRACT_PARAM_LIST_HANDLE_RECURSIVE(shift($@))')') +define(_EXTRACT_PARAM_LIST_HANDLE_,`ifelse(_TRIM(`$1'),`',`',_TRIM(`$1'),`void',`',`_EXTRACT_PARAM_LIST_HANDLE_RECURSIVE($@)')') +define(_EXTRACT_PARAM_LIST_HANDLE,`_EXTRACT_PARAM_LIST_HANDLE_(shift(_EXTRACTPARAMS(`$1')))') + +% +% Creates a parameter list for a macro call "(p1, p2, p3)", with one parameter for each parameter +% in the c parameterlist provided as parameter. If the provided parameterlist contains only `void' +% then it is treated as if it was empty. +% +% _MAKE_DUMMYPARAMLIST(`(int nCount, char *pBuf)') => `p0,p1' +% _MAKE_DUMMYPARAMLIST(`(void)') => `' +% +define(_MAKE_DUMMYPARAMLIST1,`ifelse($#,1,`',$#,2,`p$1',`p$1,_MAKE_DUMMYPARAMLIST1(incr($1),shift(shift($@)))')') +define(_MAKE_DUMMYPARAMLIST_,`ifelse(_TRIM(`$1'),`',`',_TRIM(`$1'),`void',`',`_MAKE_DUMMYPARAMLIST1(0,$@)')') +define(_MAKE_DUMMYPARAMLIST_WITHVOID_,`ifelse(_TRIM(`$1'),`',`',`_MAKE_DUMMYPARAMLIST1(0,$@)')') +define(_MAKE_DUMMYPARAMLIST,`_MAKE_DUMMYPARAMLIST_(_EXTRACTPARAMS(`$1'))') + +% +% Creates a parameter list for a macro call "(p1, p2, p3)", with one parameter for each parameter +% in the c parameterlist provided as parameter. If the provided parameterlist contains only `void' +% then it is treated as if it had exactly one parameter. +% +% _MAKE_DUMMYPARAMLIST(`(int nCount, char *pBuf)') => `p0,p1' +% _MAKE_DUMMYPARAMLIST(`(void)') => `p0' +% +define(_MAKE_DUMMYPARAMLIST_WITHVOID,`_MAKE_DUMMYPARAMLIST_WITHVOID_(_EXTRACTPARAMS(`$1'))') + +% +% Same as _MAKE_DUMMYPARAMLIST, but drops the first parameter +% +% _MAKE_HANDLE_DUMMYPARAMLIST(`(RTS_HANDLE, hHandle, int nCount, char *pBuf)') => `p1,p2' +% +define(_MAKE_HANDLE_DUMMYPARAMLIST,`shift(_MAKE_DUMMYPARAMLIST(`$1'))') + +% +% Same as _MAKE_DUMMYPARAMLIST, but drops the first n parameter +% +% _MAKE_HANDLE_DUMMYPARAMLIST2(`(RTS_HANDLE hHandle, int nCount, char *pBuf, int nValue)', 2) => `p2,p3' +% +define(nshift, `ifelse($2, 0, `$1', `nshift(`shift($1)', decr($2))')') +define(_MAKE_HANDLE_DUMMYPARAMLIST2,`nshift(`_MAKE_DUMMYPARAMLIST(`$1')', $2)') + + +define(_MAKE_PARAMLIST2,`ifelse(_TRIM($@),`',`',`, $1`'_MAKE_PARAMLIST2(shift($@))')') +define(_MAKE_PARAMLIST1,`ifelse(_TRIM($@),`',`',`$1`'_MAKE_PARAMLIST2(shift($@))')') +define(_MAKE_HANDLEITF_PARAMLIST,`ifelse(_TRIM(shift($@)),`',`void',_TRIM(shift($@)),`void',`void',`_MAKE_PARAMLIST1(shift($@))')') +define(_MAKE_ITF_PARAMLIST, `ifelse(_TRIM($@),`',`void',_TRIM($@),`void',`void',`_MAKE_PARAMLIST1($@)')') +define(_MAKE_CREATEITF_PARAMLIST,`ifelse(_TRIM($@),`',`void',_TRIM($@),`void',`void',`_MAKE_PARAMLIST1($@)')') + +% +% Ieration over some string elements +% +define(`_FOREACH', `ifelse(1,$#,` ',`$1($2) +ifelse(2,$#, ,`_FOREACH(`$1',shift(shift($@)))')')') + +% +% Split component version number +% +% Example: +% $1 = "0x03020104" +% _VERSION_MAJOR => 3 +% _VERSION_SUB0 => 2 +% _VERSION_SUB1 => 1 +% _VERSION_SUB2 => 4 +% +define(`_TRIMLEFT_ZERO', `regexp(`$1', `^0*\(.+\)', `\1')') +define(`_VERSION_MAJOR',`_TRIMLEFT_ZERO(regexp(`$1',`^0x\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))') +define(`_VERSION_SUB0',`_TRIMLEFT_ZERO(regexp(`$1',`^0x[0-9a-fA-F][0-9a-fA-F]\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))') +define(`_VERSION_SUB1',`_TRIMLEFT_ZERO(regexp(`$1',`^0x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))') +define(`_VERSION_SUB2',`_TRIMLEFT_ZERO(regexp(`$1',`^0x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))') + +divert`'dnl diff --git a/ExtensionSDK/makefile b/ExtensionSDK/makefile new file mode 100644 index 0000000..9edd426 --- /dev/null +++ b/ExtensionSDK/makefile @@ -0,0 +1,183 @@ +############################################################################## +# Copyright: Copyright CODESYS Development GmbH +# Program: Extension API for the Linux SL products +############################################################################## + +ifeq ($(dir $(lastword $(MAKEFILE_LIST))), ./) +# +# MAKEFILE is used wrong! Never call it from this folder directly +# +.PHONY:help +help: + @echo "**********************" + @echo "Dont work inside this folder! Please go to your library folder and call this makefile e.g.:" + @echo "make -f ../sdk/makefile newproject" + @echo + @echo "**********************" + +.PHONY:all +all: help + +.PHONY:newproject +newproject: help + +.PHONY:makefile +makefile: help + +else +# +# NORMAL RULES +# + +# get some info from generated files +ITFM4 = $(wildcard *Itf.m4) +CSRC = $(wildcard *.c) +CMPBASENAME=$(CSRC:.c=) + +# set some variables +OUTDIR=./out +ITF = $(basename ${ITFM4}) +TEMPLATEDEP = $(SDKDIR)/m4/TemplateDep.m4 +DEP = ${CMPBASENAME}Dep + + +# set name of shared object +SONAME= lib${CMPBASENAME}.so + +# set sources of component +SRC += ${CSRC} CmpFrame.c + +# objects: all sources +OBJS = $(addprefix ${OUTDIR}/,$(SRC:.c=.o)) + +# set default includes +INCLUDES += -I${SDKDIR}/include/ -I${OUTDIR}/ -I. +CFLAGS += -fPIC -g -O0 -Wall -Wextra -Wno-unused +CFLAGS += -DDEP_H=\"${DEP}.h\" + +# set a vendorID +CMPVENDORID=0xFFFF + +vpath %.m4 ${SDKDIR}/cmpstub/ +vpath %.c ${SDKDIR}/src/ + +.PHONY:help +help: + @echo "**********************" + @echo "help / usage / available make targets:" + @echo "-all : just do everything needed" + @echo "-clean : cleanup all generated files" + @echo "-newproject : create a new project" + @echo "-showenv : show intern variables (for debugging)" + @echo "**********************" + + +.PHONY:newproject +newproject: makefile + +.PHONY:all +all: ${OUTDIR}/$(SONAME) + +SDK_BASE=$(dir $(lastword $(MAKEFILE_LIST))) + +.PHONY:showenv +showenv: + @echo "**********************" + @echo "ITFM4: ${ITFM4}" + @echo "CSRC: ${CSRC}" + @echo "SONAME: ${SONAME}" + @echo "ITF: ${ITF}" + @echo "DEP: ${DEP}" + @echo "OBJS: ${OBJS}" + @echo "CC: $(CC)" + @echo "CFLAGS: ${CFLAGS}" + @echo "SDKDIR: ${SDKDIR}" + @echo "CMPBASENAME: ${CMPBASENAME}" + @echo "MAKEFILE_LIST: $(MAKEFILE_LIST)" + @echo "**********************" + @echo "" + +# check if there are whitespaces in the SDK BASE DIR +# this causes horrible things in make, so we detect/avoid it directly at creating a new project +ifeq (1,$(words $(MAKEFILE_LIST))) +makefile: + @echo "**********************" + @echo "creating makefile for your project " + cp ${SDK_BASE}/makefile_template $@ + @echo "# include of the SDK makefiles" >> $@ + @echo "SDKDIR="$(abspath ${SDK_BASE}) >> $@ + @echo 'include $${SDKDIR}/makefile' >> $@ + @echo "" >> $@ + @echo "now check your makefile here" + @echo "**********************" + @echo "" + +else +makefile: + @echo "**********************" + @echo "creating makefile for your project FAILED:" + @echo "there are whitespaces in the SDK path" + @echo "this does not work with 'make'!" + @echo "Please put your SDK to a path without whitespaces" + @echo "**********************" + @echo "" + +endif + +${OUTDIR}/%.o: %.c + @echo "**********************" + @echo "compile: $@ from $<" + @mkdir -p ${OUTDIR} + $(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $< + @echo "**********************" + @echo "" + +${OUTDIR}/${SONAME}: ${OUTDIR}/$(DEP).h ${OUTDIR}/$(ITF).h $(OBJS) + @echo "**********************" + @echo "link: $@" + @mkdir -p ${OUTDIR} + $(CC) $(LDFLAGS) -shared $(OBJS) -Wl,-soname,$(SONAME) -o $@ $(LDLIBS) + @echo "**********************" + @echo "" + +${OUTDIR}/$(DEP).m4: + @echo "**********************" + @echo "create $@ from template" + @mkdir -p ${OUTDIR} + echo 'SET_COMPONENT_NAME(`'${CMPBASENAME}"')" > $@ + echo 'COMPONENT_VERSION(`'${CMPVERSION}"')" >> $@ + echo 'COMPONENT_VENDORID(`'${CMPVENDORID}"')" >> $@ + echo "#define CMPID_${CMPBASENAME} ${CMPID}" >> $@ + echo "IMPLEMENT_ITF("'`'"${CMPBASENAME}Itf.m4')" >> $@ + @echo "**********************" + @echo "" + +${OUTDIR}/$(DEP).h: ${OUTDIR}/$(DEP).m4 $(ITF).m4 + @echo "**********************" + @echo "build dependency header: $@ from $<" + @mkdir -p ${OUTDIR} + $(DOS2UNIX) $< + $(M4) -I$(SDKDIR)/m4 -I. Implementation.m4 - < $< > $@ + @echo "**********************" + @echo "" + +${OUTDIR}/$(ITF).h: $(ITF).m4 + @echo "**********************" + @echo "build interface header: $@ from $<" + @sed -n '/DEF_API/ !d; /_cext/ ! { s/^/ERROR: This functionname does not contain _cext (this is mandatory, see help for details):\n/p;q 1}' $< + @mkdir -p ${OUTDIR} + $(DOS2UNIX) $< + $(M4) -I$(SDKDIR)/m4 Exports.m4 - < $< > $@ + @echo "**********************" + @echo "" + +.PHONY:clean +clean: + @echo "" + @echo "**********************" + @echo "clean up" + rm -rf ${OUTDIR}; + @echo "**********************" + @echo "" + +endif diff --git a/ExtensionSDK/makefile_template b/ExtensionSDK/makefile_template new file mode 100644 index 0000000..14e4984 --- /dev/null +++ b/ExtensionSDK/makefile_template @@ -0,0 +1,48 @@ +############################################################################## +# Copyright: Copyright CODESYS Development GmbH +# Program: Extension API for the Linux SL products +############################################################################## + +# +# with this makefile you can influence the build of your component +# +# +# Hints: +# - the name of the *Itf.m4 file in this folder will be used as name of the shared object (and component name) +# - all C files in this folder will be compiled by default +# + +# set versions of your shared object +# must be single digit, decimal +MAJORVER=0 +MINORVER=1 + +# set a component version (must match your IEC library version) +# must be 4 bytes, hexadecimal +CMPVERSION=0x01000000 + +# set a component ID +# must be 2 bytes >= 0x2000 and hexadecimal and can be used to differentiate different components +CMPID=0x2000 + +# set your tools +DOS2UNIX = dos2unix +M4 = m4 + +# set a compiler +CC ?= gcc + +# add some compiler flags (with += ) +#CFLAGS += -g + +# add some include paths (with +=) +#INCLUDES += -I. + +# add some linker flags (with += ) +#LDFLAGS += + +# add some libraries that you might need (with += ) +#LDLIBS += -lc + + + diff --git a/ExtensionSDK/src/CmpFrame.c b/ExtensionSDK/src/CmpFrame.c new file mode 100644 index 0000000..6cfba62 --- /dev/null +++ b/ExtensionSDK/src/CmpFrame.c @@ -0,0 +1,78 @@ +/***************************************************************************** +* Copyright: Copyright CODESYS Development GmbH +* Program: Extension API for the Linux SL products +******************************************************************************/ + +#include "CmpStd.h" + +#include DEP_H + +USE_STMT + +DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct) +/* Used to exchange function pointers between component manager and components. + Called at startup for each component. + pInitStruct: IN Pointer to structure with: + pfExportFunctions OUT Pointer to function that exports component functions + pfImportFunctions OUT Pointer to function that imports functions from other components + pfGetVersion OUT Pointer to function to get component version + pfRegisterAPI IN Pointer to component manager function to register a API function + pfGetAPI IN Pointer to component manager function to get a API function + pfCallHook IN Pointer to component manager function to call a hook function + Return ERR_OK if library could be initialized, else error code +*/ +{ + pInitStruct->CmpId = COMPONENT_ID; + pInitStruct->pfExportFunctions = ExportFunctions; + pInitStruct->pfImportFunctions = ImportFunctions; + pInitStruct->pfGetVersion = CmpGetVersion; + pInitStruct->pfHookFunction = HookFunction; + pInitStruct->pfCreateInstance = NULL; + pInitStruct->pfDeleteInstance = NULL; + + s_pfCMRegisterAPI = pInitStruct->pfCMRegisterAPI; + s_pfCMRegisterAPI2 = pInitStruct->pfCMRegisterAPI2; + s_pfCMGetAPI = pInitStruct->pfCMGetAPI; + s_pfCMGetAPI2 = pInitStruct->pfCMGetAPI2; + s_pfCMCallHook = pInitStruct->pfCMCallHook; + s_pfCMRegisterClass = pInitStruct->pfCMRegisterClass; + s_pfCMCreateInstance = pInitStruct->pfCMCreateInstance; + return ERR_OK; +} + +/** + * Publish exported functions to the component manager, to make them available for other components. + * Use generated macro EXPORT_STMT. + * ERR_OK or error code in case of error + */ +static int CDECL ExportFunctions(void) +{ + /* Macro to export functions */ + EXPORT_STMT; + return ERR_OK; +} + +/** + * Import function pointers from other components. Use generated macro IMPORT_STMT. + * ERR_OK or error code if function name could not be resolved + */ +static int CDECL ImportFunctions(void) +{ + /* Macro to import functions */ + IMPORT_STMT; + return ERR_OK; +} + +/** + * Return the version of our component. + * Version as defined in dep file + */ +static RTS_UI32 CDECL CmpGetVersion(void) +{ + return CMP_VERSION; +} + +static RTS_RESULT CDECL HookFunction(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2) +{ + return ERR_OK; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d2fce1 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# atMb — documented working library + +Start with `library/atMb-Documented.export`: comments-only documentation of your +working CmpAtomModbus export, retaining its embedded C library object. It uses +`ClientTCP`, matching the uploaded working interface. + +Read `docs/USER-GUIDE.md` for setup and examples and `docs/API-REFERENCE.md` for +all 95 code objects, parameters, outputs, methods and properties. +`docs/DOCUMENTATION-VALIDATION.md` records code/payload preservation checks. +`reference/Working-CmpAtomModbus.export` is the exact uploaded original. + +The earlier development export and generator are retained for source history. +`library/build_native_export.py` generates the earlier migration export; it does +not generate or supersede the newly documented working export. Do not overwrite +working IDE changes by regenerating that migration export. The documentation +adds no C changes; the source-to-uploaded-binary match remains unverified. + +The rest of the source/build kit remains below. + +# atMb — native Modbus for CODESYS + +Git-ready source snapshot, revision 0.9.1. Public namespace `atMb`. +C implementation supports TCP client, RTU client and TCP slave; IEC code has +no ModbusFB dependency. Online-change support remains future work. + +## Files + +- `component-source/`: C engine, CODESYS component frame/interface and host tests. +- `ExtensionSDK/`: supplied SDK used by the component build. +- `library/atMb-Native.export`: corrected IEC import; includes public status declarations. +- `library/atMb-Native-Review.st`: readable generated IEC source. +- `library/build_native_export.py`: reproducible export generator. +- `reference/CmpAtomModbus-AllInOne.export`: required generator baseline, not the final import. +- `artifacts/arm64/`: your uploaded shared object and precompile cache, preserved unchanged. +- `docs/BUILD-AND-USAGE.md`: import, deployment, API, diagnostics and limitations. +- `SHA256SUMS.txt`: snapshot file checksums. + +## Build and test on the Pi + +```sh +sudo apt install build-essential m4 dos2unix python3 +make -C component-source/component SDKDIR="$(realpath ExtensionSDK)" clean +make -C component-source/component SDKDIR="$(realpath ExtensionSDK)" all +make -C component-source test +``` + +Build natively for each Linux target architecture; the uploaded ARM64 `.so` +cannot run on ARM32 or x86-64. Runtime/SDK compatibility must also be checked +for each CODESYS target. The host tests exercise protocol code independently +of the CODESYS runtime. + +## IEC export + +```sh +python3 library/build_native_export.py +``` + +Import `library/atMb-Native.export` into a fresh CODESYS library project, +namespace `atMb`, and embed the newly built `.so` in a C Implemented Library +object. See the full instructions in `docs/BUILD-AND-USAGE.md`. +The export is source, not a compiled `.library`. The supplied precompile cache +is an opaque reference artifact and is not a substitute for the library project. + +Your uploaded `.so` is ARM64 and contains RTU/server symbols. Its exact match +to this source snapshot and its operation in the runtime have not been verified; +rebuild from source before making a release. The older development instructions +say no prebuilt binary is included: that applies to the earlier kit; this repo +snapshot adds your uploaded binary under `artifacts/arm64/`. + +## Start a repository + +```sh +cd atMb-repo +git init +git add . +git commit -m "Initial atMb native Modbus source" +``` + +No Git history or remote is included. The SDK and baseline came from supplied +files; review their redistribution terms before publishing a public repository. +No new license is assigned by this snapshot. diff --git a/SHA256SUMS.txt b/SHA256SUMS.txt new file mode 100644 index 0000000..977275c --- /dev/null +++ b/SHA256SUMS.txt @@ -0,0 +1,48 @@ +d1192772047955638e8bb171080ed9b37e10683de935e4e0503c93b6d26f03fc .gitattributes +675d431f63e2c212360b692b18de50a344aed5664d17b3683f0b687b96479f24 .gitignore +0ac6e4fe82a82d1bd27ff20dec2f18820c0e0a5057cd0cb1a8020f6dc506603f ExtensionSDK/README +1cc355e875e572de3829d28bff9c100a4eecbfd741d938654be97881023c84d5 ExtensionSDK/include/CmpErrors.h +2010310fbeb8b4734e638610d2f6dc01b600fe42b8b104155806ef0784e95f2d ExtensionSDK/include/CmpItf.h +a8de018bfde8d10d3f281bb45de03c4b4f3cd28466eb0fa0981086bc4033a69c ExtensionSDK/include/CmpStd.h +212fcd438a0ad38712d575521c34ed875ac86353e543c737aec3770386503b14 ExtensionSDK/m4/Exports.m4 +06b55c74620a6247bf4ddcf69f7373255f4677b2c2b7ef1ed0ac02ba0407f94e ExtensionSDK/m4/Implementation.m4 +8fb9f4e77e2f2c4df494b6199bfd16ab2ed4257d76c735e46460f837d6e22163 ExtensionSDK/m4/Util.m4 +814d06ca83d775f3bcb93f80859602b6c1debe9511b3d42ef337948bd2e2b5d0 ExtensionSDK/makefile +4fc8013238169e924ee2eb5dd6c2b39c9d274f4c7c2219dc0b9a530687097519 ExtensionSDK/makefile_template +aa7ccad11508ccbf4f9b24aa8db8f22392f9d723ae3af5cbfa67e38271cfbac3 ExtensionSDK/src/CmpFrame.c +67076180ec906d9f27f5b63406d8a2701faa58771f76e0f4487ae750660ce7e4 README.md +f0279c023b82eeda388cdc1b9c9f358b7794869360a06684df670cd34d5cab56 artifacts/arm64/CmpAtomModbus_library.precompilecache +07527262a8efcf9add5554cfcaed22cf927af63b985e431ab5341078a6864667 artifacts/arm64/libCmpAtomModbus.so +14db5bb96e65494eed63da0139b37734dfc08e312a3cb0924ad10b7d9476d442 component-source/Makefile +d91cffa9751bca79f13ae80552d654519333536c894bb13b41a1182c179c53b7 component-source/component/CmpAtomModbus.c +30a8d7d5dfd372d171d4acdafd0b77cedca684e0b6e661ae1ab67e2627d35708 component-source/component/CmpAtomModbusItf.m4 +513586eed183bcc53cdf008c483da03feff6b5cb0f3ed3eff17de1462def12c3 component-source/component/CmpFrame.c +0972948a5cc88892421433fb01875f4f51609cda3b0df57ff58f05c61390e1c2 component-source/component/makefile +c362c04636cb2cfc00eec8800f0aa9631709cd9eba76b66332f61a988823a055 component-source/generated/CmpAtomModbus.c +fa9b00a24ff9f24b63c9a08b8eba4d585a23f4fd6e19f990ec36dcc70f745427 component-source/generated/CmpAtomModbusItf.m4 +78240595fa115d3706cbc7316bdcd4e0101a3269e5faacce98826bc32143e9d9 component-source/src/atom_bridge.c +4a3eaca066d0c06ac7fa95d802ff78b270a1ccdc3fc67c6a072b7e09efba08c0 component-source/src/atom_bridge.h +4f772a0dab94053bfa2f073f6877ec63a8c6b7999d8e3415160705db441f6068 component-source/src/atom_mb.c +f8140b5076d5cfb0feda5c9b20d0bc494d7b77edf528f10d96ed071fc4dd0461 component-source/src/atom_mb.h +e5f8e76efdbc2c4875ceee375f1ea3bd93e1cb4e2075373bb8a0eb4b31ea5e4a component-source/src/atom_rtu.c +565a5122de679b2e60990b1252f356e69316b51979485c3cf0101c6e32ac5a4a component-source/src/atom_rtu.h +771f9671ea8f0644596633970dae1d8651275251d6cdb0a44373a00d064ad35c component-source/src/atom_server.c +51f3b4a5779347e58b9993b3c2d8f180a73e0a70cb2a8b81bf9350aca8b42bad component-source/src/atom_server.h +d770393632f9d3d1acddf229f562148648792398a39d90851aa529d2923f8382 component-source/tests/bridge_driver.c +50bb7854c3a966c46a583fba951c1d3a605bf9d1739ac83685322d9a7b740c4d component-source/tests/driver.c +3af1a5f71d69bae2abda3de9dba31740ad254d7157d702847fb921fc2b64df3d component-source/tests/test_bridge.c +2a4b582bed0404f865f4fa2c905dd251c3dccfc4961f20467723c7349793a7f2 component-source/tests/test_native.py +0cf8f8fefda6376cacf0c56729d24aaa76241e807b0dee0b36001b3ad3cea509 component-source/tests/test_tcp.py +c1f09f7498453182f7ed88c1cb343b637ca6c6507e728ac71591a826588c0d3a docs/API-REFERENCE.md +a3cd41084413b1f7c2045e6a7a647b793c8df1c9fa338e146439031b269a1dcb docs/BUILD-AND-USAGE.md +17d84b44d288b46012d9e52d7c8a247592ad28f3d5c3032fffbab701a8478418 docs/DOCUMENTATION-VALIDATION.md +3a2d04af7a1fc7f2e39fea1401d12653fda16864cf4cb7d8edfe2d9988091453 docs/USER-GUIDE.md +fe7df15839ff4bcf3272cbec71cd67a026e514c79df4bd0be829c8a373391f4d docs/VALIDATION.md +8aa5a11a22263ea04902ce883b14fd31c3de270c711deca13f57889a05b689a0 docs/document_export.py +87e2f904f08d7ee8c37ba8362989d2453b2860e91336f21623d0ab91eb8a178b library/atMb-Documented-Review.st +19a979f94b437fc6c523fdfbd24fd8f6038860cb107b45dea2466020e2a319fe library/atMb-Documented.export +0efb6d1760ea5104331cd0fb13b7324fd468c5ab2ec415f0dd9e38db74961e70 library/atMb-Native-Review.st +e2223cf6cef8cecb71cd41093c67f095559764eaa52e22f0f31347e3faec7238 library/atMb-Native.export +1d66a3b8cefa95e39815b12d1e802192b83d416ad281aaca5b8efa8c90ed4f77 library/build_native_export.py +c3be3f6ee2e065633f8e8e04482c95fe5c30f61e34cf6223d6454500a756dbb2 reference/CmpAtomModbus-AllInOne.export +5a07da11853e955633a977bdd59a7d1f8f292c594c2116bc220bd63c8edc9329 reference/Working-CmpAtomModbus.export diff --git a/artifacts/arm64/CmpAtomModbus_library.precompilecache b/artifacts/arm64/CmpAtomModbus_library.precompilecache new file mode 100644 index 0000000..06722cd Binary files /dev/null and b/artifacts/arm64/CmpAtomModbus_library.precompilecache differ diff --git a/artifacts/arm64/libCmpAtomModbus.so b/artifacts/arm64/libCmpAtomModbus.so new file mode 100644 index 0000000..cba5e2c Binary files /dev/null and b/artifacts/arm64/libCmpAtomModbus.so differ diff --git a/component-source/Makefile b/component-source/Makefile new file mode 100644 index 0000000..70d8b2c --- /dev/null +++ b/component-source/Makefile @@ -0,0 +1,16 @@ +CC ?= cc +CFLAGS = -std=c11 -Wall -Wextra -Werror -O2 +.PHONY: test clean +test: tests/driver tests/test_bridge tests/bridge_driver + ./tests/test_bridge + python3 tests/test_tcp.py + python3 tests/test_tcp.py bridge_driver + python3 tests/test_native.py +tests/bridge_driver: tests/bridge_driver.c src/atom_bridge.c src/atom_bridge.h src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h + $(CC) $(CFLAGS) -Isrc tests/bridge_driver.c src/atom_bridge.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@ +tests/test_bridge: tests/test_bridge.c src/atom_bridge.c src/atom_bridge.h src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h + $(CC) $(CFLAGS) -Isrc tests/test_bridge.c src/atom_bridge.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@ +tests/driver: tests/driver.c src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h + $(CC) $(CFLAGS) -Isrc tests/driver.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@ +clean: + rm -f tests/driver tests/test_bridge tests/bridge_driver diff --git a/component-source/component/CmpAtomModbus.c b/component-source/component/CmpAtomModbus.c new file mode 100644 index 0000000..ff44353 --- /dev/null +++ b/component-source/component/CmpAtomModbus.c @@ -0,0 +1,33 @@ +#define _DEFAULT_SOURCE +#include "../src/atom_bridge.h" +#include "CmpStd.h" +#include "CmpErrors.h" +#include "CmpItf.h" +#include "CmpAtomModbusDep.h" + +void CDECL CDECL_EXT atmbcall_cext(atmbcall_cext_struct *p) +{ + p->atMbCall_cext = ab_call( + p->command, + p->handle, + p->ipAddress, + p->port, + p->unitId, + p->functionCode, + p->startAddress, + p->quantity, + p->pData, + p->dataCapacity, + p->pStatus, + p->statusCapacity, + p->nowMs, + p->connectTimeoutMs, + p->responseTimeoutMs, + p->retryDelayMs); +} + + +#include "../src/atom_mb.c" +#include "../src/atom_rtu.c" +#include "../src/atom_server.c" +#include "../src/atom_bridge.c" diff --git a/component-source/component/CmpAtomModbusItf.m4 b/component-source/component/CmpAtomModbusItf.m4 new file mode 100644 index 0000000..0f01230 --- /dev/null +++ b/component-source/component/CmpAtomModbusItf.m4 @@ -0,0 +1,70 @@ +/** + * CmpAtomModbus + * + * + * + */ + +SET_INTERFACE_NAME(`CmpAtomModbus') + +/** EXTERN LIB SECTION BEGIN **/ +/* Comments are ignored for m4 compiler so restructured text can be used. changecom(`/*', `*/') */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * + * Create FUNCTION atMbCall_cext returning UDINT, paste this declaration. + * Properties > Build > External implementation must be selected. + * Library filename and Project Information title: CmpAtomModbus. + * This declaration is the SINGLE external entry point for the C engine. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * +*/ +typedef struct tagatmbcall_cext_struct +{ + RTS_IEC_UDINT command; + RTS_IEC_UDINT handle; + RTS_IEC_DWORD ipAddress; + RTS_IEC_UINT port; + RTS_IEC_BYTE unitId; + RTS_IEC_BYTE functionCode; + RTS_IEC_UINT startAddress; + RTS_IEC_UINT quantity; + RTS_IEC_WORD *pData; + RTS_IEC_UDINT dataCapacity; + RTS_IEC_UDINT *pStatus; + RTS_IEC_UDINT statusCapacity; + RTS_IEC_ULINT nowMs; + RTS_IEC_UDINT connectTimeoutMs; + RTS_IEC_UDINT responseTimeoutMs; + RTS_IEC_UDINT retryDelayMs; + RTS_IEC_UDINT atMbCall_cext; +} atmbcall_cext_struct; + +DEF_API(`void',`CDECL',`atmbcall_cext',`(atmbcall_cext_struct *p)',1,0x709EE65B,0x00020000) + +#ifdef __cplusplus +} +#endif + +/** EXTERN LIB SECTION END **/ + diff --git a/component-source/component/CmpFrame.c b/component-source/component/CmpFrame.c new file mode 100644 index 0000000..7c1f10f --- /dev/null +++ b/component-source/component/CmpFrame.c @@ -0,0 +1,81 @@ +#include "../src/atom_bridge.h" +/***************************************************************************** +* Copyright: Copyright CODESYS Development GmbH +* Program: Extension API for the Linux SL products +******************************************************************************/ + +#include "CmpStd.h" + +#include DEP_H + +USE_STMT + +DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct) +/* Used to exchange function pointers between component manager and components. + Called at startup for each component. + pInitStruct: IN Pointer to structure with: + pfExportFunctions OUT Pointer to function that exports component functions + pfImportFunctions OUT Pointer to function that imports functions from other components + pfGetVersion OUT Pointer to function to get component version + pfRegisterAPI IN Pointer to component manager function to register a API function + pfGetAPI IN Pointer to component manager function to get a API function + pfCallHook IN Pointer to component manager function to call a hook function + Return ERR_OK if library could be initialized, else error code +*/ +{ + pInitStruct->CmpId = COMPONENT_ID; + pInitStruct->pfExportFunctions = ExportFunctions; + pInitStruct->pfImportFunctions = ImportFunctions; + pInitStruct->pfGetVersion = CmpGetVersion; + pInitStruct->pfHookFunction = HookFunction; + pInitStruct->pfCreateInstance = NULL; + pInitStruct->pfDeleteInstance = NULL; + + s_pfCMRegisterAPI = pInitStruct->pfCMRegisterAPI; + s_pfCMRegisterAPI2 = pInitStruct->pfCMRegisterAPI2; + s_pfCMGetAPI = pInitStruct->pfCMGetAPI; + s_pfCMGetAPI2 = pInitStruct->pfCMGetAPI2; + s_pfCMCallHook = pInitStruct->pfCMCallHook; + s_pfCMRegisterClass = pInitStruct->pfCMRegisterClass; + s_pfCMCreateInstance = pInitStruct->pfCMCreateInstance; + return ERR_OK; +} + +/** + * Publish exported functions to the component manager, to make them available for other components. + * Use generated macro EXPORT_STMT. + * ERR_OK or error code in case of error + */ +static int CDECL ExportFunctions(void) +{ + /* Macro to export functions */ + EXPORT_STMT; + return ERR_OK; +} + +/** + * Import function pointers from other components. Use generated macro IMPORT_STMT. + * ERR_OK or error code if function name could not be resolved + */ +static int CDECL ImportFunctions(void) +{ + /* Macro to import functions */ + IMPORT_STMT; + return ERR_OK; +} + +/** + * Return the version of our component. + * Version as defined in dep file + */ +static RTS_UI32 CDECL CmpGetVersion(void) +{ + return CMP_VERSION; +} + +static RTS_RESULT CDECL HookFunction(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2) +{ + if (ulHook == CH_EXIT) ab_shutdown(); + + return ERR_OK; +} diff --git a/component-source/component/makefile b/component-source/component/makefile new file mode 100644 index 0000000..65a1337 --- /dev/null +++ b/component-source/component/makefile @@ -0,0 +1,13 @@ +# Generated from supplied ExtensionSDK; native build on ARM64 Pi. +SDKDIR ?= ../../ExtensionSDK +MAJORVER=0 +MINORVER=3 +CMPVERSION=0x00030000 +# Local development ID; check no other component uses this ID. +CMPID=0x2001 +DOS2UNIX=dos2unix +M4=m4 +CC ?= gcc +# The SDK otherwise includes CmpFrame in its component identity wildcard. +override CSRC := CmpAtomModbus.c +include $(SDKDIR)/makefile diff --git a/component-source/generated/CmpAtomModbus.c b/component-source/generated/CmpAtomModbus.c new file mode 100644 index 0000000..be3373b --- /dev/null +++ b/component-source/generated/CmpAtomModbus.c @@ -0,0 +1,10 @@ +#include "CmpStd.h" +#include "CmpErrors.h" +#include "CmpItf.h" +#include "CmpAtomModbusDep.h" + +void CDECL CDECL_EXT atmbcall_cext(atmbcall_cext_struct *p) +{ + +} + diff --git a/component-source/generated/CmpAtomModbusItf.m4 b/component-source/generated/CmpAtomModbusItf.m4 new file mode 100644 index 0000000..0f01230 --- /dev/null +++ b/component-source/generated/CmpAtomModbusItf.m4 @@ -0,0 +1,70 @@ +/** + * CmpAtomModbus + * + * + * + */ + +SET_INTERFACE_NAME(`CmpAtomModbus') + +/** EXTERN LIB SECTION BEGIN **/ +/* Comments are ignored for m4 compiler so restructured text can be used. changecom(`/*', `*/') */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * + * Create FUNCTION atMbCall_cext returning UDINT, paste this declaration. + * Properties > Build > External implementation must be selected. + * Library filename and Project Information title: CmpAtomModbus. + * This declaration is the SINGLE external entry point for the C engine. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * +*/ +typedef struct tagatmbcall_cext_struct +{ + RTS_IEC_UDINT command; + RTS_IEC_UDINT handle; + RTS_IEC_DWORD ipAddress; + RTS_IEC_UINT port; + RTS_IEC_BYTE unitId; + RTS_IEC_BYTE functionCode; + RTS_IEC_UINT startAddress; + RTS_IEC_UINT quantity; + RTS_IEC_WORD *pData; + RTS_IEC_UDINT dataCapacity; + RTS_IEC_UDINT *pStatus; + RTS_IEC_UDINT statusCapacity; + RTS_IEC_ULINT nowMs; + RTS_IEC_UDINT connectTimeoutMs; + RTS_IEC_UDINT responseTimeoutMs; + RTS_IEC_UDINT retryDelayMs; + RTS_IEC_UDINT atMbCall_cext; +} atmbcall_cext_struct; + +DEF_API(`void',`CDECL',`atmbcall_cext',`(atmbcall_cext_struct *p)',1,0x709EE65B,0x00020000) + +#ifdef __cplusplus +} +#endif + +/** EXTERN LIB SECTION END **/ + diff --git a/component-source/src/atom_bridge.c b/component-source/src/atom_bridge.c new file mode 100644 index 0000000..2f64364 --- /dev/null +++ b/component-source/src/atom_bridge.c @@ -0,0 +1,96 @@ +#include "atom_bridge.h" +#include "atom_mb.h" +#include "atom_rtu.h" +#include "atom_server.h" +#include +#include +typedef struct { am_client c; ar_transport rtu; int serial; uint32_t generation; int used, retired; } slot; +static slot slots[AM_POOL_SIZE]; +static uint32_t token(unsigned i) { return (slots[i].generation<<8)|(i+1u); } +static slot *lookup(uint32_t h) { + unsigned n=h&255u; + if(!n || n>AM_POOL_SIZE) return NULL; + slot *s=&slots[n-1]; + return s->used && s->generation==(h>>8)?s:NULL; +} +static void snapshot(const am_client *c,uint32_t h,uint32_t *s) { + s[0]=h; s[1]=(uint32_t)c->state; s[2]=(uint32_t)c->busy; + s[3]=(uint32_t)c->done; s[4]=(uint32_t)c->error; s[5]=(uint32_t)c->result; + s[6]=(uint32_t)c->last_error; s[7]=(uint32_t)c->socket_error; + s[8]=c->exception; s[9]=(uint32_t)c->write_uncertain; + s[10]=c->connect_attempts; s[11]=c->reconnects; s[12]=c->completed; s[13]=c->failed; + s[14]=(uint32_t)c->has_success; s[15]=(uint32_t)c->last_success_ms; + s[16]=(uint32_t)(c->last_success_ms>>32); s[17]=c->quantity; + s[18]=c->function; s[19]=c->unit; s[20]=(uint32_t)c->enabled; + s[21]=c->connect_timeout_ms; s[22]=c->response_timeout_ms; s[23]=c->retry_delay_ms; +} +uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port, + uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap, + uint32_t *status,uint32_t sc,uint64_t now,uint32_t ct,uint32_t rt,uint32_t retry) { + slot *s; unsigned i; uint32_t result=0; + if(!status || sc=AS_OPEN && cmd<=AS_PULL)return as_call(cmd,h,ip,port,f,addr,q,data,cap,status,now,retry); + /* Caller must provide distinct data/status buffers with declared capacities. */ + if(cmd==AB_OPEN || cmd==AB_OPEN_RTU) { + char text[128]; + memset(status,0,AM_STATUS_WORDS*sizeof(*status)); + if(cmd==AB_OPEN && !port) return AM_ARGUMENT; + for(i=0;i>24),(unsigned)((ip>>16)&255u), + (unsigned)((ip>>8)&255u),(unsigned)(ip&255u)); + s->serial=(cmd==AB_OPEN_RTU); + if(s->serial) { + if(!data || !q || q>=sizeof(text) || cap127)return AM_ARGUMENT;text[j]=(char)data[j]; } + text[q]=0; + if(!ar_init(&s->c,&s->rtu,text,ip,unit,f,(uint8_t)addr,ct))return AM_ARGUMENT; + } else if(!am_init(&s->c,text,port)) return AM_ARGUMENT; + s->generation++; s->used=1; h=token(i); + snapshot(&s->c,h,status); return 0; + } + s=lookup(h); + if(!s) { memset(status,0,AM_STATUS_WORDS*sizeof(*status)); return AB_BAD_HANDLE; } + switch(cmd) { + case AB_CLOSE: + if(s->serial)ar_close(&s->c);else am_close(&s->c); s->used=0; + /* Never recycle a generation after wrap: stale handles stay invalid. */ + if(s->generation==0xffffffu) s->retired=1; + snapshot(&s->c,0,status); return 0; + case AB_ENABLE: am_enable(&s->c,1,now); break; + case AB_DISABLE: if(s->serial)ar_close(&s->c);else am_enable(&s->c,0,now); break; + case AB_TICK: if(s->serial)ar_tick(&s->c,&s->rtu,now);else am_tick(&s->c,now); break; + case AB_RECONNECT: if(s->serial)ar_reconnect(&s->c,&s->rtu,now);else am_reconnect(&s->c,now); break; + case AB_CONFIGURE: + if(s->c.enabled) result=AM_BUSY; + else if(!ct || !rt || !retry) result=AM_ARGUMENT; + else { s->c.connect_timeout_ms=ct; s->c.response_timeout_ms=rt; s->c.retry_delay_ms=retry; } + break; + case AB_SUBMIT: + if((f==5 || f==6 || f==15 || f==16) && (!data || capserial?ar_request(&s->c,&s->rtu,unit,f,addr,q,data,now):am_request(&s->c,unit,f,addr,q,data,now)); + break; + case AB_COPY_READ: + if(!s->c.done || s->c.function>4 || s->c.function<1) result=AM_ARGUMENT; + else if(!data || capc.quantity) result=AB_BAD_CAPACITY; + else memcpy(data,s->c.data,s->c.quantity*sizeof(*data)); + break; + case AB_ACK: s->c.done=0; s->c.error=0; break; + case AB_REQUEST_TIMEOUT: + if(s->c.busy) result=AM_BUSY; + else if(!rt) result=AM_ARGUMENT; + else s->c.response_timeout_ms=rt; + break; + default: result=AB_BAD_COMMAND; break; + } + snapshot(&s->c,h,status); return result; +} +void ab_shutdown(void) { + as_shutdown(); + unsigned i; + for(i=0;i +/* Fixed pool, one IEC task owns ALL calls. Do not share across tasks. + * Only synchronous pointer access; generation-checked handles, no heap. */ +#define AM_STATUS_WORDS 24 +#define AM_POOL_SIZE 16 +enum { AB_OPEN=1, AB_CLOSE, AB_ENABLE, AB_DISABLE, AB_TICK, AB_SUBMIT, + AB_COPY_READ, AB_RECONNECT, AB_CONFIGURE, AB_ACK, AB_REQUEST_TIMEOUT }; +enum { AB_OPEN_RTU=20 }; +enum { AB_BAD_HANDLE=256, AB_POOL_FULL, AB_BAD_CAPACITY, AB_BAD_COMMAND }; +uint32_t ab_call(uint32_t command,uint32_t handle,uint32_t ip,uint16_t port, + uint8_t unit,uint8_t function,uint16_t address,uint16_t quantity, + uint16_t *data,uint32_t capacity,uint32_t *status,uint32_t status_capacity, + uint64_t now,uint32_t connect_ms,uint32_t response_ms,uint32_t retry_ms); +/* Call on component unload; invalidate handles and close all sockets. */ +void ab_shutdown(void); +#endif diff --git a/component-source/src/atom_mb.c b/component-source/src/atom_mb.c new file mode 100644 index 0000000..453136c --- /dev/null +++ b/component-source/src/atom_mb.c @@ -0,0 +1,147 @@ +#include "atom_mb.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif +static uint16_t get16(const uint8_t *p) { return (uint16_t)((p[0]<<8)|p[1]); } +static void put16(uint8_t *p,uint16_t v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; } +static int writing(uint8_t f) { return f==5 || f==6 || f==15 || f==16; } +static void drop(am_client *c) { if(c->fd>=0) { if(c->serial_port)ioctl(c->fd,TIOCNXCL);close(c->fd); } c->fd=-1; } +static void finish(am_client *c,am_error e) { + c->busy=0; c->done=e==AM_OK; c->error=e!=AM_OK; c->result=e; + if(e==AM_OK) c->completed++; else { c->failed++; c->last_error=e; } +} +static void fault(am_client *c,am_error e,uint64_t now) { + if(c->busy) { c->write_uncertain=writing(c->function) && c->tx_pos>0; finish(c,e); } + c->last_error=e; drop(c); c->state=c->enabled?AM_RETRY:AM_DISABLED; + c->deadline_ms=now+c->retry_delay_ms; +} +int am_init(am_client *c,const char *ip,uint16_t port) { + struct in_addr a; + if(!c || !ip || strlen(ip)>=16 || !port || inet_pton(AF_INET,ip,&a)!=1) return 0; + memset(c,0,sizeof(*c)); c->fd=-1; strcpy(c->ip,ip); c->port=port; + c->connect_timeout_ms=2000; c->response_timeout_ms=1000; c->retry_delay_ms=1000; + return 1; +} +void am_close(am_client *c) { c->enabled=0; fault(c,AM_CANCELLED,0); } +void am_enable(am_client *c,int enabled,uint64_t now) { + if(!enabled) { if(c->enabled) am_close(c); return; } + if(!c->enabled) { c->enabled=1; c->state=AM_RETRY; c->deadline_ms=now; } +} +void am_reconnect(am_client *c,uint64_t now) { fault(c,AM_CANCELLED,now); c->deadline_ms=now; } +am_error am_request(am_client *c,uint8_t unit,uint8_t f,uint16_t addr,uint16_t q, + const uint16_t *v,uint64_t now) { + size_t n=12, bytes=0; unsigned i; + if(c->busy) return AM_BUSY; + if(c->state!=AM_READY) return AM_OFFLINE; + if(!q || (uint32_t)addr+q>65536u) return AM_ARGUMENT; + switch(f) { + case 1: case 2: if(q>2000) return AM_ARGUMENT; break; + case 3: case 4: if(q>125) return AM_ARGUMENT; break; + case 5: case 6: if(q!=1 || !v || (f==5 && v[0]>1)) return AM_ARGUMENT; break; + case 15: if(q>1968 || !v) return AM_ARGUMENT; + for(i=0;i1) return AM_ARGUMENT; + bytes=(q+7u)/8u; n=13+bytes; break; + case 16: if(q>123 || !v) return AM_ARGUMENT; bytes=2u*q; n=13+bytes; break; + default: return AM_ARGUMENT; + } + memset(c->tx,0,sizeof(c->tx)); c->transaction++; + put16(c->tx,c->transaction); put16(c->tx+4,(uint16_t)(n-6)); + c->tx[6]=unit; c->tx[7]=f; put16(c->tx+8,addr); put16(c->tx+10,q); + if(f==5 || f==6) put16(c->tx+10,f==5?(v[0]?0xff00:0):v[0]); + if(f==15 || f==16) { + c->tx[12]=(uint8_t)bytes; + for(i=0;itx+13+2*i,v[i]); + else if(v[i]) c->tx[13+i/8]|=(uint8_t)(1u<<(i%8)); + } + } + c->unit=unit; c->function=f; c->address=addr; c->quantity=q; + c->tx_len=n; c->tx_pos=0; c->rx_len=0; c->rx_need=7; + c->busy=1; c->done=0; c->error=0; c->exception=0; c->write_uncertain=0; + c->result=AM_OK; c->state=AM_SENDING; c->deadline_ms=now+c->response_timeout_ms; + return AM_OK; +} +void am_accept_response(am_client *c,uint64_t now) { + unsigned i; size_t bytes; + if(c->rx[7]==(uint8_t)(c->function|0x80)) { + if(c->rx_need!=9 || !c->rx[8]) { fault(c,AM_PROTOCOL,now); return; } + c->exception=c->rx[8]; finish(c,AM_EXCEPTION); c->state=AM_READY; return; + } + if(c->rx[7]!=c->function) { fault(c,AM_PROTOCOL,now); return; } + if(writing(c->function)) { + if(c->rx_need!=12 || memcmp(c->rx+8,c->tx+8,4)) { fault(c,AM_PROTOCOL,now); return; } + } else { + bytes=c->function<=2?(c->quantity+7u)/8u:2u*c->quantity; + if(c->rx_need!=9+bytes || c->rx[8]!=bytes) { fault(c,AM_PROTOCOL,now); return; } + for(i=0;iquantity;i++) c->data[i]=c->function<=2? + (uint16_t)((c->rx[9+i/8]>>(i%8))&1):get16(c->rx+9+2*i); + } + c->last_success_ms=now; c->has_success=1; finish(c,AM_OK); c->state=AM_READY; +} +void am_tick(am_client *c,uint64_t now) { + struct sockaddr_in a; struct pollfd p; int e,r; socklen_t len; ssize_t n; + if(!c->enabled) return; + if(c->state==AM_RETRY) { + if(nowdeadline_ms) return; + c->connect_attempts++; if(c->connect_attempts>1) c->reconnects++; + c->socket_error=0; c->fd=socket(AF_INET,SOCK_STREAM,0); + if(c->fd<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; } + r=fcntl(c->fd,F_GETFL,0); + if(r<0 || fcntl(c->fd,F_SETFL,r|O_NONBLOCK)<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; } + memset(&a,0,sizeof(a)); a.sin_family=AF_INET; a.sin_port=htons(c->port); + if(inet_pton(AF_INET,c->ip,&a.sin_addr)!=1) { fault(c,AM_ARGUMENT,now); return; } + r=connect(c->fd,(struct sockaddr*)&a,sizeof(a)); + if(r==0) c->state=AM_READY; + else if(errno==EINPROGRESS) { c->state=AM_CONNECTING; c->deadline_ms=now+c->connect_timeout_ms; } + else { c->socket_error=errno; fault(c,AM_SOCKET,now); } + return; + } + if(c->state==AM_CONNECTING) { + if(now>=c->deadline_ms) { fault(c,AM_CONNECT_TIMEOUT,now); return; } + p.fd=c->fd; p.events=POLLOUT; p.revents=0; r=poll(&p,1,0); + if(r<0) { if(errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } return; } + if(!r) return; + len=sizeof(e); e=0; + if(getsockopt(c->fd,SOL_SOCKET,SO_ERROR,&e,&len)<0) e=errno; + if(e) { c->socket_error=e; fault(c,AM_SOCKET,now); } else c->state=AM_READY; + return; + } + if(c->state==AM_READY) { + /* Detect idle peer closure; unsolicited bytes cannot belong to a request. */ + uint8_t b; n=recv(c->fd,&b,1,MSG_PEEK); + if(n==0) fault(c,AM_PEER_CLOSED,now); + else if(n>0) fault(c,AM_PROTOCOL,now); + else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } + return; + } + if(now>=c->deadline_ms) { fault(c,AM_RESPONSE_TIMEOUT,now); return; } + if(c->state==AM_SENDING) { + n=send(c->fd,c->tx+c->tx_pos,c->tx_len-c->tx_pos,MSG_NOSIGNAL); + if(n>0) { c->tx_pos+=(size_t)n; if(c->tx_pos==c->tx_len) c->state=AM_RECEIVING; } + else if(n==0) fault(c,AM_PEER_CLOSED,now); + else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } + return; + } + if(c->state!=AM_RECEIVING) return; + n=recv(c->fd,c->rx+c->rx_len,c->rx_need-c->rx_len,0); + if(n==0) { fault(c,AM_PEER_CLOSED,now); return; } + if(n<0) { if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } return; } + c->rx_len+=(size_t)n; + if(c->rx_len!=c->rx_need) return; + if(c->rx_need==7) { + uint16_t l=get16(c->rx+4); + if(get16(c->rx)!=c->transaction || get16(c->rx+2)!=0 || c->rx[6]!=c->unit || l<3 || l>254) { + fault(c,AM_PROTOCOL,now); return; + } + c->rx_need=6u+l; return; + } + am_accept_response(c,now); +} diff --git a/component-source/src/atom_mb.h b/component-source/src/atom_mb.h new file mode 100644 index 0000000..dab8cc2 --- /dev/null +++ b/component-source/src/atom_mb.h @@ -0,0 +1,40 @@ +#ifndef ATOM_MB_H +#define ATOM_MB_H +#include +#include +/* Single owner/task per context. Init once, close before disposal. No heap allocation. + * tick() time must be monotonic milliseconds. POSIX IPv4 transport, numeric IP only. */ +typedef enum { AM_DISABLED, AM_RETRY, AM_CONNECTING, AM_READY, AM_SENDING, AM_RECEIVING } am_state; +typedef enum { AM_OK, AM_ARGUMENT, AM_BUSY, AM_OFFLINE, AM_SOCKET, AM_CONNECT_TIMEOUT, + AM_RESPONSE_TIMEOUT, AM_PEER_CLOSED, AM_PROTOCOL, AM_EXCEPTION, AM_CANCELLED } am_error; +typedef struct { + int fd, enabled, serial_port; + am_state state; + am_error last_error, result; + int socket_error; + uint8_t exception, unit, function; + uint16_t transaction, address, quantity; + uint32_t connect_timeout_ms, response_timeout_ms, retry_delay_ms; + uint64_t deadline_ms, last_success_ms; + uint32_t connect_attempts, reconnects, completed, failed; + int busy, done, error, write_uncertain, has_success; + char ip[16]; uint16_t port; + uint8_t tx[260], rx[260]; + size_t tx_len, tx_pos, rx_len, rx_need; + /* Reads: coils/discretes unpacked as 0/1; registers as host-order uint16_t. + * Result is copied only after the entire response validates. */ + uint16_t data[2000]; +} am_client; +int am_init(am_client *c, const char *ip, uint16_t port); +void am_enable(am_client *c, int enabled, uint64_t now_ms); +void am_reconnect(am_client *c, uint64_t now_ms); +void am_close(am_client *c); +void am_tick(am_client *c, uint64_t now_ms); +/* Supported functions: 1,2,3,4,5,6,15,16. Zero-based protocol addresses. + * Writes copy input immediately. No automatic replay of a failed request. + * Completion flags persist until next accepted request; result is per-request. */ +am_error am_request(am_client *c, uint8_t unit, uint8_t function, + uint16_t address, uint16_t quantity, const uint16_t *values, uint64_t now_ms); +/* Shared validated transport response parser. */ +void am_accept_response(am_client *, uint64_t); +#endif diff --git a/component-source/src/atom_rtu.c b/component-source/src/atom_rtu.c new file mode 100644 index 0000000..0cfbfab --- /dev/null +++ b/component-source/src/atom_rtu.c @@ -0,0 +1,101 @@ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#include "atom_rtu.h" +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#endif +uint16_t ar_crc(const uint8_t *p,size_t n) { + uint16_t crc=0xffff; + while(n--) { crc^=*p++; for(unsigned i=0;i<8;i++) crc=(uint16_t)((crc>>1)^((crc&1)?0xa001:0)); } + return crc; +} +static speed_t ar_speed(uint32_t baud) { + switch(baud) { case 1200:return B1200;case 2400:return B2400;case 4800:return B4800; + case 9600:return B9600;case 19200:return B19200;case 38400:return B38400; + case 57600:return B57600;case 115200:return B115200;default:return 0; } +} +int ar_init(am_client *c,ar_transport *r,const char *path,uint32_t baud,uint8_t parity,uint8_t bits,uint8_t stops,uint32_t rs485) { + if(!path || !*path || strlen(path)>=sizeof(r->device) || !ar_speed(baud) || parity>2 || bits!=8 || (stops!=1 && stops!=2) || rs485>1) return 0; + am_init(c,"127.0.0.1",1);c->serial_port=1;memset(r,0,sizeof(*r));strcpy(r->device,path); + r->baud=baud;r->parity=parity;r->bits=bits;r->stops=stops;r->rs485=rs485; + r->gap_ms=baud>19200?2:(uint32_t)((3500u*(1u+bits+(parity!=0)+stops)+baud-1u)/baud); + return 1; +} +static void ar_fault(am_client *c,ar_transport *r,am_error error,uint64_t now) { + if(c->busy) { c->write_uncertain=(c->function>=5 && r->tx_pos>0);c->busy=0;c->done=0;c->error=1;c->result=error;c->failed++; } + c->last_error=error;if(c->fd>=0){ioctl(c->fd,TIOCNXCL);close(c->fd);}c->fd=-1;c->state=c->enabled?AM_RETRY:AM_DISABLED; + c->deadline_ms=now+c->retry_delay_ms;r->quiet_until=c->deadline_ms;r->rx_len=0; +} +am_error ar_request(am_client *c,ar_transport *r,uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,const uint16_t *v,uint64_t now) { + /* Broadcast writes have no response; reject explicitly rather than fabricate success. */ + if(unit<1 || unit>247)return AM_ARGUMENT; + am_error error=am_request(c,unit,f,addr,q,v,now);if(error!=AM_OK)return error; + r->tx_len=c->tx_len-6;memcpy(r->tx,c->tx+6,r->tx_len); + uint16_t crc=ar_crc(r->tx,r->tx_len);r->tx[r->tx_len++]=(uint8_t)crc;r->tx[r->tx_len++]=(uint8_t)(crc>>8); + r->tx_pos=0;r->rx_len=0;r->rx_need=0;return AM_OK; +} +void ar_tick(am_client *c,ar_transport *r,uint64_t now) { + struct termios options;ssize_t n; + if(!c->enabled)return; + if(c->state==AM_RETRY) { + if(nowdeadline_ms)return; + c->connect_attempts++;if(c->connect_attempts>1)c->reconnects++; + c->socket_error=0;c->fd=open(r->device,O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC); + if(c->fd<0) { c->socket_error=errno;ar_fault(c,r,AM_SOCKET,now);return; } + if(ioctl(c->fd,TIOCEXCL)<0 || tcgetattr(c->fd,&options)<0) goto serial_error; + cfmakeraw(&options);options.c_cflag&=~(CSIZE|PARENB|PARODD|CSTOPB|CRTSCTS); + options.c_cflag|=CLOCAL|CREAD|(r->bits==7?CS7:CS8); + if(r->parity)options.c_cflag|=PARENB|(r->parity==1?PARODD:0); + if(r->stops==2)options.c_cflag|=CSTOPB; + options.c_cc[VMIN]=0;options.c_cc[VTIME]=0; + if(cfsetispeed(&options,ar_speed(r->baud))<0 || cfsetospeed(&options,ar_speed(r->baud))<0 || tcsetattr(c->fd,TCSANOW,&options)<0 || tcflush(c->fd,TCIOFLUSH)<0) goto serial_error; +#ifdef __linux__ + if(r->rs485) { struct serial_rs485 mode;memset(&mode,0,sizeof(mode));mode.flags=SER_RS485_ENABLED|SER_RS485_RTS_ON_SEND; + if(ioctl(c->fd,TIOCSRS485,&mode)<0)goto serial_error; } +#endif + c->state=AM_READY;r->quiet_until=now+r->gap_ms;return; + } + if(c->state==AM_READY)return; + if(now>=c->deadline_ms) { ar_fault(c,r,AM_RESPONSE_TIMEOUT,now);return; } + if(c->state==AM_SENDING) { + if(nowquiet_until)return; + if(r->tx_pos==0 && tcflush(c->fd,TCIFLUSH)<0)goto serial_error; + n=write(c->fd,r->tx+r->tx_pos,r->tx_len-r->tx_pos); + if(n>0) { r->tx_pos+=(size_t)n;c->tx_pos=r->tx_pos;if(r->tx_pos==r->tx_len)c->state=AM_RECEIVING; } + else if(n<0 && errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR)goto serial_error; + return; + } + if(c->state==AM_RECEIVING) { + n=read(c->fd,r->rx+r->rx_len,sizeof(r->rx)-r->rx_len); + if(n<0) { if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR)goto serial_error;return; } + if(n==0)return; + r->rx_len+=(size_t)n;r->quiet_until=now+r->gap_ms; + if(r->rx_len>=2 && (r->rx[0]!=c->unit || (r->rx[1]!=c->function && r->rx[1]!=(uint8_t)(c->function|0x80)))) { ar_fault(c,r,AM_PROTOCOL,now);return; } + if(r->rx_len>=2) { + if(r->rx[1]&0x80)r->rx_need=5; + else if(c->function>=5)r->rx_need=8; + else if(r->rx_len>=3)r->rx_need=5u+r->rx[2]; + } + if(r->rx_need>sizeof(r->rx) || (r->rx_need && r->rx_len>r->rx_need)) { ar_fault(c,r,AM_PROTOCOL,now);return; } + if(!r->rx_need || r->rx_lenrx_need)return; + if(ar_crc(r->rx,r->rx_need)!=0) { ar_fault(c,r,AM_PROTOCOL,now);return; } + /* Convert validated RTU ADU to the shared response validator's MBAP representation. */ + size_t payload=r->rx_need-2;memcpy(c->rx+6,r->rx,payload);c->rx_need=6+payload; + am_accept_response(c,now); + if(c->state==AM_RETRY)r->quiet_until=c->deadline_ms; + return; + } + return; +serial_error: + c->socket_error=errno;ar_fault(c,r,AM_SOCKET,now); +} + +void ar_close(am_client *c){if(c->fd>=0)ioctl(c->fd,TIOCNXCL);am_close(c);} +void ar_reconnect(am_client *c,ar_transport *r,uint64_t now){ar_fault(c,r,AM_CANCELLED,now);c->deadline_ms=now;} diff --git a/component-source/src/atom_rtu.h b/component-source/src/atom_rtu.h new file mode 100644 index 0000000..54154e3 --- /dev/null +++ b/component-source/src/atom_rtu.h @@ -0,0 +1,16 @@ +#ifndef ATOM_RTU_H +#define ATOM_RTU_H +#include "atom_mb.h" +typedef struct { + char device[128]; uint32_t baud, rs485; + uint8_t parity, bits, stops; + uint8_t tx[256], rx[256]; size_t tx_len, tx_pos, rx_len, rx_need; + uint64_t quiet_until; uint32_t gap_ms; +} ar_transport; +int ar_init(am_client *, ar_transport *,const char *,uint32_t,uint8_t,uint8_t,uint8_t,uint32_t); +am_error ar_request(am_client *,ar_transport *,uint8_t,uint8_t,uint16_t,uint16_t,const uint16_t *,uint64_t); +void ar_tick(am_client *,ar_transport *,uint64_t); +uint16_t ar_crc(const uint8_t *,size_t); +void ar_close(am_client *); +void ar_reconnect(am_client *, ar_transport *, uint64_t); +#endif diff --git a/component-source/src/atom_server.c b/component-source/src/atom_server.c new file mode 100644 index 0000000..34a8b71 --- /dev/null +++ b/component-source/src/atom_server.c @@ -0,0 +1,131 @@ +#include "atom_server.h" +#include "atom_mb.h" +#include +#include +#include +#include +#include +#include +#define AS_POOL 4 +#define AS_PEERS 8 +#define AS_ITEMS 2000 +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif +typedef struct { int fd;uint8_t rx[260],tx[260];size_t received,needed,sent,length;uint64_t deadline; } as_peer; +typedef struct { + int used,enabled,listener,running,error,os_error;uint32_t generation,received,replied,exceptions,retry; + uint32_t ip;uint16_t port,base[4],count[4],values[4][AS_ITEMS];uint64_t retry_at; + as_peer peers[AS_PEERS]; +} as_server; +static as_server as_slots[AS_POOL]; +static uint16_t as_get16(const uint8_t *p){return (uint16_t)((p[0]<<8)|p[1]);} +static void as_put16(uint8_t *p,uint16_t v){p[0]=(uint8_t)(v>>8);p[1]=(uint8_t)v;} +static void as_peer_close(as_peer *p){if(p->fd>=0)close(p->fd);p->fd=-1;p->received=0;p->needed=7;p->sent=0;p->length=0;} +static void as_stop(as_server *s){if(s->listener>=0)close(s->listener);s->listener=-1;s->running=0;for(unsigned i=0;ipeers[i]);} +static void as_snapshot(as_server *s,uint32_t h,uint32_t *o){ + memset(o,0,24*sizeof(*o));o[0]=h;o[1]=(uint32_t)s->running;o[2]=(uint32_t)s->error;o[3]=s->error?AM_SOCKET:AM_OK;o[4]=(uint32_t)s->os_error; + for(unsigned i=0;ipeers[i].fd>=0)o[5]++; + o[6]=s->received;o[7]=s->replied;o[8]=s->exceptions; +} +static int as_nonblock(int fd){int flags=fcntl(fd,F_GETFL,0);return flags>=0 && fcntl(fd,F_SETFL,flags|O_NONBLOCK)==0;} +static int as_range(as_server *s,unsigned table,uint16_t addr,uint16_t count){return count && addr>=s->base[table] && (uint32_t)addr+count<=(uint32_t)s->base[table]+s->count[table];} +static void as_reply(as_server *s,as_peer *p){ + uint8_t f=p->rx[7],exception=0;uint16_t address=0,q=0;unsigned table=0,index=0;size_t n=9; + if(p->needed<12){exception=(f==1 || f==2 || f==3 || f==4 || f==5 || f==6 || f==15 || f==16)?3:1;goto finish;} + address=as_get16(p->rx+8);q=as_get16(p->rx+10); + switch(f){ + case 1:case 2:case 3:case 4: + table=f==1?0:f==2?1:f==3?2:3; + if(p->needed!=12 || !q || q>(f<=2?2000:125)){exception=3;break;} + if(!as_range(s,table,address,q)){exception=2;break;} + index=address-s->base[table];p->tx[8]=(uint8_t)(f<=2?(q+7u)/8u:2u*q);n=9+p->tx[8];memset(p->tx+9,0,n-9); + for(unsigned i=0;ivalues[table][index+i])p->tx[9+i/8]|=(uint8_t)(1u<<(i%8));}else as_put16(p->tx+9+2*i,s->values[table][index+i]);} + break; + case 5:case 6: + table=f==5?0:2; + if(p->needed!=12 || (f==5 && q!=0 && q!=0xff00)){exception=3;break;} + if(!as_range(s,table,address,1)){exception=2;break;} + s->values[table][address-s->base[table]]=f==5?(q!=0):q;memcpy(p->tx+8,p->rx+8,4);n=12;break; + case 15:case 16: + table=f==15?0:2; + if(!q || q>(f==15?1968:123) || p->needed<13 || p->rx[12]!=(f==15?(q+7u)/8u:2u*q) || p->needed!=(size_t)(13+p->rx[12])){exception=3;break;} + if(!as_range(s,table,address,q)){exception=2;break;} + index=address-s->base[table]; + for(unsigned i=0;ivalues[table][index+i]=f==15?(uint16_t)((p->rx[13+i/8]>>(i%8))&1):as_get16(p->rx+13+2*i); + memcpy(p->tx+8,p->rx+8,4);n=12;break; + default:exception=1;break; + } +finish: + s->received++;memcpy(p->tx,p->rx,4);p->tx[6]=p->rx[6];p->tx[7]=f; + if(exception){p->tx[7]=(uint8_t)(f|0x80);p->tx[8]=exception;n=9;s->exceptions++;} + as_put16(p->tx+4,(uint16_t)(n-6));p->length=n;p->sent=0; +} +static void as_tick(as_server *s,uint64_t now){ + if(!s->enabled)return; + if(s->listener<0){ + if(nowretry_at)return; + struct sockaddr_in a;int reuse=1;s->listener=socket(AF_INET,SOCK_STREAM,0); + if(s->listener<0)goto listener_error; + setsockopt(s->listener,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)); + if(!as_nonblock(s->listener))goto listener_error; + memset(&a,0,sizeof(a));a.sin_family=AF_INET;a.sin_port=htons(s->port);a.sin_addr.s_addr=htonl(s->ip); + if(bind(s->listener,(struct sockaddr *)&a,sizeof(a))<0 || listen(s->listener,AS_PEERS)<0)goto listener_error; + s->running=1;s->error=0;s->os_error=0; + } + /* One accept and one I/O action per peer per scan; fixed work/storage bounds. */ + int fd=accept(s->listener,NULL,NULL); + if(fd>=0){unsigned i;for(i=0;ipeers[i].fd<0)break; + if(i==AS_PEERS || !as_nonblock(fd))close(fd);else{s->peers[i].fd=fd;s->peers[i].deadline=now+60000;}} + else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR)goto listener_error; + for(unsigned i=0;ipeers[i];if(p->fd<0)continue; + if(now>=p->deadline){as_peer_close(p);continue;} + if(p->length){ + ssize_t n=send(p->fd,p->tx+p->sent,p->length-p->sent,MSG_NOSIGNAL); + if(n>0){p->sent+=(size_t)n;if(p->sent==p->length){s->replied++;p->length=0;p->sent=0;p->received=0;p->needed=7;p->deadline=now+60000;}} + else if(n==0 || (errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR))as_peer_close(p); + }else{ + ssize_t n=recv(p->fd,p->rx+p->received,p->needed-p->received,0); + if(n>0){if(p->received==0)p->deadline=now+5000;p->received+=(size_t)n; + if(p->received==7){uint16_t length=as_get16(p->rx+4);if(as_get16(p->rx+2)!=0 || length<2 || length>254){as_peer_close(p);continue;}p->needed=6u+length;} + if(p->received==p->needed){as_reply(s,p);p->deadline=now+5000;} + }else if(n==0 || (errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR))as_peer_close(p); + } + } + return; +listener_error: + s->os_error=errno;s->error=1;as_stop(s);s->retry_at=now+s->retry; +} +uint32_t as_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t table,uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap,uint32_t *out,uint64_t now,uint32_t retry){ + as_server *s;unsigned i;uint32_t result=0; + if(cmd==AS_OPEN){ + memset(out,0,24*sizeof(*out));if(!port || !retry)return AM_ARGUMENT; + for(i=0;igeneration+1;memset(s,0,sizeof(*s));s->generation=generation;s->used=1;s->listener=-1;s->ip=ip;s->port=port;s->retry=retry; + for(unsigned j=0;jpeers[j].fd=-1;as_peer_close(&s->peers[j]);} + h=(generation<<8)|(i+65u);as_snapshot(s,h,out);return 0; + } + i=(h&255u);if(i<65 || i>=65+AS_POOL){memset(out,0,24*sizeof(*out));return 256;}i-=64; + if( !as_slots[i-1].used || as_slots[i-1].generation!=(h>>8)){memset(out,0,24*sizeof(*out));return 256;} + s=&as_slots[i-1]; + switch(cmd){ + case AS_CLOSE:as_stop(s);s->used=0;as_snapshot(s,0,out);return 0; + case AS_ENABLE:s->enabled=1;break; + case AS_DISABLE:s->enabled=0;as_stop(s);break; + case AS_TICK:as_tick(s,now);break; + case AS_PUSH:case AS_PULL: + if(table>3 || !q || q>AS_ITEMS || (uint32_t)addr+q>65536u)result=AM_ARGUMENT; + else if(!data || cap1){result=AM_ARGUMENT;break;}} + if(!result){s->base[table]=addr;s->count[table]=q;memcpy(s->values[table],data,q*sizeof(*data));} + }else if(s->base[table]!=addr || s->count[table]!=q)result=AM_ARGUMENT; + else memcpy(data,s->values[table],q*sizeof(*data)); + break; + default:result=259;break; + } + as_snapshot(s,h,out);return result; +} +void as_shutdown(void){for(unsigned i=0;i +enum { AS_OPEN=30,AS_CLOSE,AS_ENABLE,AS_DISABLE,AS_TICK,AS_PUSH,AS_PULL }; +uint32_t as_call(uint32_t,uint32_t,uint32_t,uint16_t,uint8_t,uint16_t,uint16_t,uint16_t *,uint32_t,uint32_t *,uint64_t,uint32_t); +void as_shutdown(void); +#endif diff --git a/component-source/tests/bridge_driver.c b/component-source/tests/bridge_driver.c new file mode 100644 index 0000000..da2349f --- /dev/null +++ b/component-source/tests/bridge_driver.c @@ -0,0 +1,29 @@ +#define _POSIX_C_SOURCE 200809L +#include "atom_bridge.h" +#include +#include +#include +static uint64_t now(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return (uint64_t)t.tv_sec*1000+(uint64_t)t.tv_nsec/1000000; } +int main(int argc,char **argv) { + uint32_t s[24],h=0; uint16_t data[2]={42,43}; unsigned ok=0,err=0; + int uncertain=0,last=0; uint64_t start=now(); struct timespec pause={0,1000000}; + if(argc!=4) return 2; + uint16_t port=(uint16_t)atoi(argv[1]); uint8_t f=(uint8_t)atoi(argv[2]); +#define CALL(cmd) ab_call(cmd,h,0x7f000001u,port,1,f,0,2,data,2,s,24,now(),100,80,20) + if(CALL(AB_OPEN)) return 2; + h=s[0]; if(CALL(AB_CONFIGURE) || CALL(AB_ENABLE)) return 2; + while(now()-start<1500 && ok<(unsigned)atoi(argv[3])) { + if(CALL(AB_TICK)) return 2; + if(s[3]) { + if(f<=4 && CALL(AB_COPY_READ)) return 2; + ok++; if(CALL(AB_ACK)) return 2; + } + if(s[4]) { err++; last=(int)s[5]; uncertain|=(int)s[9]; if(CALL(AB_ACK)) return 2; } + if(s[1]==3 && ok<(unsigned)atoi(argv[3])) { + if(CALL(AB_REQUEST_TIMEOUT) || CALL(AB_SUBMIT)) return 2; + } + nanosleep(&pause,0); + } + printf("%u %u %u %d %d %u %u\n",ok,err,s[11],uncertain,last,data[0],data[1]); + CALL(AB_CLOSE); return ok==(unsigned)atoi(argv[3])?0:1; +} diff --git a/component-source/tests/driver.c b/component-source/tests/driver.c new file mode 100644 index 0000000..92f55d7 --- /dev/null +++ b/component-source/tests/driver.c @@ -0,0 +1,24 @@ +#define _POSIX_C_SOURCE 200809L +#include "atom_mb.h" +#include +#include +#include +static uint64_t now(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return (uint64_t)t.tv_sec*1000+(uint64_t)t.tv_nsec/1000000; } +int main(int argc,char **argv) { + am_client c; uint64_t start=now(); unsigned successes=0,errors=0; int uncertain=0,last=0; + struct timespec pause={0,1000000}; uint16_t values[2]={42,43}; + if(argc!=4 || !am_init(&c,"127.0.0.1",(uint16_t)atoi(argv[1]))) return 2; + c.retry_delay_ms=20; c.response_timeout_ms=80; c.connect_timeout_ms=100; + am_enable(&c,1,start); + while(now()-start<1500 && successes<(unsigned)atoi(argv[3])) { + uint64_t t=now(); am_tick(&c,t); + if(c.done) { successes++; c.done=0; } + if(c.error) { errors++; last=c.result; uncertain|=c.write_uncertain; c.error=0; } + if(c.state==AM_READY && successes<(unsigned)atoi(argv[3])) { + int f=atoi(argv[2]); am_request(&c,1,(uint8_t)f,0,2,values,t); + } + nanosleep(&pause,0); + } + printf("%u %u %u %d %d %u %u\n",successes,errors,c.reconnects,uncertain,last,c.data[0],c.data[1]); + am_close(&c); return successes==(unsigned)atoi(argv[3])?0:1; +} diff --git a/component-source/tests/test_bridge.c b/component-source/tests/test_bridge.c new file mode 100644 index 0000000..45cc4aa --- /dev/null +++ b/component-source/tests/test_bridge.c @@ -0,0 +1,19 @@ +#include "atom_bridge.h" +#include +#include +#define CALL(cmd,h) ab_call(cmd,h,0x7f000001u,502,1,3,0,2,NULL,0,status,24,100,2000,1000,1000) +int main(void) { + uint32_t status[24],handles[16],old,newh; unsigned i; + assert(ab_call(AB_OPEN,0,0,502,0,0,0,0,NULL,0,status,23,0,0,0,0)==AB_BAD_CAPACITY); + for(i=0;i<16;i++) { assert(CALL(AB_OPEN,0)==0); handles[i]=status[0]; assert(handles[i]); } + assert(CALL(AB_OPEN,0)==AB_POOL_FULL); + old=handles[0]; assert(CALL(AB_CLOSE,old)==0); assert(CALL(AB_TICK,old)==AB_BAD_HANDLE); + assert(CALL(AB_OPEN,0)==0); newh=status[0]; assert(newh!=old); + assert(CALL(AB_CONFIGURE,newh)==0); assert(CALL(AB_ENABLE,newh)==0); + assert(CALL(AB_REQUEST_TIMEOUT,newh)==0); assert(status[22]==1000); + assert(CALL(AB_CONFIGURE,newh)==2); assert(CALL(AB_SUBMIT,newh)==3); + assert(CALL(AB_DISABLE,newh)==0); assert(CALL(AB_COPY_READ,newh)==1); + ab_shutdown(); assert(CALL(AB_TICK,newh)==AB_BAD_HANDLE); + puts("PASS bridge: capacity, pool limit, stale handles, configure, offline submission, shutdown"); + return 0; +} diff --git a/component-source/tests/test_native.py b/component-source/tests/test_native.py new file mode 100644 index 0000000..bc388c0 --- /dev/null +++ b/component-source/tests/test_native.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Real PTY/socket integration tests for the native RTU and TCP server bridge.""" +import ctypes as C,os,pty,select,socket,struct,subprocess,time,tempfile +from pathlib import Path +root=Path(__file__).resolve().parents[1] +with tempfile.TemporaryDirectory() as tmp: + so=Path(tmp)/'test-native.so' + subprocess.run(['cc','-shared','-fPIC','-std=c11','-Wall','-Wextra','-Werror','-O2','-I'+str(root/'src'),*[str(root/'src'/f) for f in ['atom_mb.c','atom_rtu.c','atom_server.c','atom_bridge.c']],'-o',str(so)],check=True) + lib=C.CDLL(str(so));fn=lib.ab_call + U=C.c_uint32;W=C.c_uint16;B=C.c_uint8 + fn.argtypes=[U,U,U,W,B,B,W,W,C.POINTER(W),U,C.POINTER(U),U,C.c_uint64,U,U,U];fn.restype=U + data=(W*2000)();status=(U*24)() + def now():return int(time.monotonic()*1000) + def call(cmd,h=0,ip=0,port=0,unit=1,f=3,addr=0,q=1,ct=100,rt=100,retry=10,when=None): + return fn(cmd,h,ip,port,unit,f,addr,q,data,2000,status,24,now() if when is None else when,ct,rt,retry) + def crc(frame): + v=0xffff + for byte in frame: + v^=byte + for _ in range(8):v=(v>>1)^(0xa001 if v&1 else 0) + return v + def adu(payload):return payload+struct.pack('=7 and f>=15 else 8 + if len(received)>=need and not sent: + assert crc(received)==0;sent=True + payload=(bytes([1,f])+received[2:6]) if f>=15 else bytes([1,f,4,0,42,0,43]) + if mode=='exception':payload=bytes([1,f|0x80,2]) + if mode=='echo' and attempt==0:payload=payload[:3]+bytes([payload[3]^1])+payload[4:] + response=adu(payload) + if mode=='crc' and attempt==0:response=response[:-1]+bytes([response[-1]^1]) + if mode!='timeout' or attempt>0: + fragments=[response[:2],response[2:4],response[4:]] if mode=='fragment' else [response] + if fragments:os.write(master,fragments.pop(0)) + if status[3] or status[4]:break + time.sleep(.002) + assert sent and (status[3] or status[4]),list(status) + if mode in ['crc','echo'] and attempt==0:assert status[5]==8 + elif mode=='timeout' and attempt==0:assert status[5]==6 + elif mode=='exception':assert status[5]==9 and status[8]==2 and status[1]==3 + else: + assert status[3]==1,list(status) + if f<=4:assert call(7,h)==0 and list(data[:2])==[42,43] + assert call(10,h)==0 + if attempt==0 and mode in ['crc','timeout','echo']:ready(h) + assert call(6,h,unit=0,f=3,addr=0,q=1)==1 # Broadcast unsupported, no fake completion. + assert call(2,h)==0;assert call(5,h)==256;os.close(master) + for mode in ['normal','fragment','crc','timeout','exception']:rtu_scenario(mode);print('PASS RTU',mode) + rtu_scenario('normal',16);print('PASS RTU register write') + rtu_scenario('echo',16);print('PASS RTU invalid echo and reopen recovery') + rtu_scenario('normal',15);print('PASS RTU coil write') + # Native TCP server, four independent tables mapped at nonzero Modbus address. + probe=socket.socket();probe.bind(('127.0.0.1',0));port=probe.getsockname()[1];probe.close() + assert call(30,ip=0x7f000001,port=port)==0;h=status[0] + for table in range(4): + for i in range(16):data[i]=i%2 if table<2 else 100+i + assert call(35,h,f=table,addr=10,q=16)==0 + assert call(32,h)==0;assert call(34,h)==0 and status[1]==1 + peer=socket.create_connection(('127.0.0.1',port));peer.setblocking(False) + tid=0 + def exchange(pdu): + global tid + tid+=1;packet=struct.pack('>HHHB',tid,0,len(pdu)+1,1)+pdu + peer.send(packet[:3]);assert call(34,h)==0;peer.send(packet[3:]) + reply=b'';end=time.monotonic()+1 + while time.monotonic()=7 and len(reply)==6+struct.unpack('>H',reply[4:6])[0]:return reply[7:] + time.sleep(.001) + raise AssertionError(('server timeout',pdu,reply)) + for f in [1,2,3,4]: + rep=exchange(struct.pack('>BHH',f,10,2));assert rep==(bytes([f,1,2]) if f<=2 else bytes([f,4,0,100,0,101]));print('PASS slave read FC',f) + for f in [5,6]: + value=0xff00 if f==5 else 456;pdu=struct.pack('>BHH',f,10,value);assert exchange(pdu)==pdu + assert call(36,h,f=0 if f==5 else 2,addr=10,q=16)==0 and data[0]==(1 if f==5 else 456);print('PASS slave write FC',f) + for f in [15,16]: + payload=b'\x01' if f==15 else struct.pack('>HH',321,654) + pdu=struct.pack('>BHHB',f,10,2,len(payload))+payload + assert exchange(pdu)==struct.pack('>BHH',f,10,2) + assert call(36,h,f=0 if f==15 else 2,addr=10,q=16)==0 and list(data[:2])==([1,0] if f==15 else [321,654]);print('PASS slave write FC',f) + assert exchange(struct.pack('>BHH',3,0,1))==bytes([0x83,2]) + assert exchange(struct.pack('>BHH',16,10,0))==bytes([0x90,3]) + assert exchange(struct.pack('>BHH',99,10,1))==bytes([0xe3,1]) + print('PASS slave exceptions and nonzero table bases') + assert call(31,h)==0;assert call(34,h)==256;peer.close() + # Listener bind error recovers after the conflicting listener disappears. + blocker=socket.socket();blocker.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);blocker.bind(('127.0.0.1',port));blocker.listen() + assert call(30,ip=0x7f000001,port=port)==0;h=status[0];call(32,h);call(34,h);assert status[2]==1 + blocker.close();call(34,h,when=now()+100);assert status[1]==1 and status[2]==0 + call(31,h);print('PASS slave listener recovery and stale handles') + lib.ab_shutdown() diff --git a/component-source/tests/test_tcp.py b/component-source/tests/test_tcp.py new file mode 100644 index 0000000..bcdcd36 --- /dev/null +++ b/component-source/tests/test_tcp.py @@ -0,0 +1,59 @@ +import socket, struct, subprocess, threading, time, sys +from pathlib import Path +ROOT=Path(__file__).resolve().parents[1] +DRIVER=ROOT/'tests'/(sys.argv[1] if len(sys.argv)>1 else 'driver') +def exact(s,n): + b=b'' + while len(b)H',h[4:6])[0]-1) + except (EOFError,ConnectionError,socket.timeout): break + if first and mode in ('close','timeout','bad_tid','bad_length','write_drop'): + first=False + if mode=='timeout': time.sleep(.12) + elif mode=='bad_tid': s.sendall(b'\xff\xff'+h[2:4]+b'\x00\x07'+h[6:7]+b'\x03\x04\x00\x2a\x00\x2b') + elif mode=='bad_length': s.sendall(h[:4]+b'\xff\xff'+h[6:7]) + break + if first and mode=='exception': reply=bytes([p[0]|128,2]); first=False + elif function==16: reply=p[:5] + elif function==1: reply=b'\x01\x01\x03' + else: reply=b'\x03\x04\x00\x2a\x00\x2b' + packet=h[:4]+struct.pack('>H',len(reply)+1)+h[6:7]+reply + if mode=='fragment': + for b in packet: s.sendall(bytes([b])); time.sleep(.001) + else: s.sendall(packet) + except Exception as e: issues.append(e) + worker=threading.Thread(target=server); worker.start() + try: + p=subprocess.run([str(DRIVER),str(listener.getsockname()[1]),str(function),'2'],capture_output=True,text=True,timeout=4) + assert p.returncode==0,(mode,p.stdout,p.stderr) + ok,err,reconnect,uncertain,last,a,b=map(int,p.stdout.split()) + assert ok==2 + if function==3: assert (a,b)==(42,43) + if function==1: assert (a,b)==(1,1) + if mode in ('close','timeout','bad_tid','bad_length','write_drop'): assert err>=1 and reconnect>=1 + if mode=='exception': assert err==1 and reconnect==0 and last==9 + if mode=='write_drop': assert uncertain==1 + print('PASS',mode,p.stdout.strip()) + finally: + listener.close(); worker.join(3) + assert not issues,issues +for mode in ('normal','fragment','close','timeout','bad_tid','bad_length','exception'): run(mode) +run('write_drop',16) +run('coils',1) diff --git a/docs/API-REFERENCE.md b/docs/API-REFERENCE.md new file mode 100644 index 0000000..34ee4c8 --- /dev/null +++ b/docs/API-REFERENCE.md @@ -0,0 +1,1590 @@ +# atMb API reference + +Generated from your working `CmpAtomModbus(1).export`. Documents every code object, including internal methods and property accessors. Runtime implementations, identifiers, declarations and embedded native payloads are preserved in the documented export; additions are comments only. + +## atMbCall_cext + +External C ABI entry point used by the wrappers. Application code should normally call the function blocks. Do not change parameter order/types or external implementation settings. Pointers are accessed synchronously by C; provide distinct valid data/status buffers and truthful capacities. Client status requires 24 UDINT elements. All calls must be owned by one IEC task. + +```iecst +(* Create FUNCTION atMbCall_cext returning UDINT, paste this declaration. + Properties > Build > External implementation must be selected. + Library filename and Project Information title: CmpAtomModbus. + This declaration is the SINGLE external entry point for the C engine. *) +FUNCTION atMbCall_cext : UDINT +VAR_INPUT + command : UDINT; + handle : UDINT; + ipAddress : DWORD; + port : UINT; + unitId : BYTE; + functionCode : BYTE; + startAddress : UINT; + quantity : UINT; + pData : POINTER TO WORD; + dataCapacity : UDINT; + pStatus : POINTER TO UDINT; + statusCapacity : UDINT; + nowMs : ULINT; + connectTimeoutMs : UDINT; + responseTimeoutMs : UDINT; + retryDelayMs : UDINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `command` | VAR_INPUT | `UDINT` | Native command selector. Use wrappers in application code. | +| `handle` | VAR_INPUT | `UDINT` | Generation-checked native context handle; 0 for open. | +| `ipAddress` | VAR_INPUT | `DWORD` | Packed IPv4 for TCP commands; command 20 overloads this field with RTU baud rate. | +| `port` | VAR_INPUT | `UINT` | Slave listening port, default 502, must be nonzero. | +| `unitId` | VAR_INPUT | `BYTE` | Transaction unit ID; command 20 overloads it with serial parity. | +| `functionCode` | VAR_INPUT | `BYTE` | Modbus FC; command 20 uses data bits, server push/pull use table ID. | +| `startAddress` | VAR_INPUT | `UINT` | Protocol start; command 20 uses stop bits. | +| `quantity` | VAR_INPUT | `UINT` | Item count; command 20 uses serial path byte count. | +| `pData` | VAR_INPUT | `POINTER TO WORD` | WORD buffer; client reads/writes, serial ASCII path, or server table staging. | +| `dataCapacity` | VAR_INPUT | `UDINT` | Available WORD elements, not bytes. | +| `pStatus` | VAR_INPUT | `POINTER TO UDINT` | UDINT status buffer; requires at least 24 elements. | +| `statusCapacity` | VAR_INPUT | `UDINT` | Available UDINT elements, not bytes. | +| `nowMs` | VAR_INPUT | `ULINT` | Monotonic time in milliseconds; wrappers use SysTimeGetUs / 1000. | +| `connectTimeoutMs` | VAR_INPUT | `UDINT` | TCP connection timeout; command 20 uses kernel RS485 flag. | +| `responseTimeoutMs` | VAR_INPUT | `UDINT` | Reply deadline in milliseconds. | +| `retryDelayMs` | VAR_INPUT | `UDINT` | Native recovery wait in milliseconds. | + +## ModbusMaster.ClientStatus + +Aggregate request status used by the RTU monitoring property: noneOk=0, someOk=1, allOk=2. This describes request online flags, not the physical connection. The current toString getter compares onlineCnt with all registered requests and reports allOk for an empty registry. + +```iecst +{attribute 'qualified_only'} +{attribute 'strict'} +TYPE ClientStatus : +( + noneOk := 0, + someOk := 1, + allOk := 2 +); +END_TYPE +``` + +## ModbusMaster.Error + +IEC error classification. TCP-named socket/timeout members are also used for RTU failures. For the precise native reason inspect out_nativeError/out_lastNativeError; for an OS failure inspect out_socketError. + +```iecst +{attribute 'qualified_only'} +TYPE Error : ( + OK := 0, RequestParameterError := 1, TcpSocketError := 4, + TcpConnectTimeout := 5, ReplyTimeout := 6, InvalidReply := 8, + RequestException := 9, RequestCancelled := 10, InternalError := 255 +); +END_TYPE +``` + +## ModbusMaster.ExceptionCodes + +Modbus exception response values plus local TIMEOUT=-1. A valid Modbus exception proves communication, but the requested operation failed. In current statistics, non-exception errors can also be classified as TIMEOUT; use native diagnostics to distinguish their actual cause. + +```iecst +{attribute 'qualified_only'} +TYPE ExceptionCodes : +( + // Local / transport-level (non-Modbus) result + TIMEOUT := -1, // Communication timeout / no response + + // Modbus exception codes (returned in exception response PDU) + RESPONSE_SUCCESS := 16#00, // Everything OK (no exception) + + ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave + ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped) + ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.) + SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action + + ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout) + SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later + + MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21) + + GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond +); +END_TYPE +``` + +## ModbusMaster.ClientBase + +Abstract shared client scheduler and native transport owner. Use ClientTCP or ClientRTU. Holds up to 100 registered requests and runs one transaction at a time. Call the concrete client once per scan, from the same task as its requests. + +```iecst +{attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT ClientBase +VAR_INPUT + {attribute 'symbol' := 'none'} + in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate. + {attribute 'symbol' := 'none'} + in_resetStats: BOOL; // Set this bool to true to reset statistics. +END_VAR +VAR_OUTPUT + {attribute 'symbol' := 'none'} + msgLastSecondCnt: UDINT; //Messages in the last second + {attribute 'symbol' := 'none'} + msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms + {attribute 'symbol' := 'none'} + numMsgSent: UDINT; + {attribute 'symbol' := 'none'} + numMsgReply: UDINT; + {attribute 'symbol' := 'none'} + allRequestsOnline: BOOL; //All requests are working + reqStats: ARRAY[0..99] OF POINTER TO RequestStats; // For debugging modbus requests +END_VAR +VAR + dataRate : DINT; + requests : ARRAY[0..99] OF POINTER TO RequestBase; + requestCnt : UINT; + allRequestsOffline : BOOL; +END_VAR +VAR +nativeMode : BOOL; +nativeHandle : UDINT; +nativeIP : DWORD; +nativePort : UINT; +nativeNowMicroseconds : SYSTIME; +nativeNowMilliseconds : ULINT; +nativeStatus : ARRAY[0..23] OF UDINT; +nativeActiveIndex : DINT := -1; +nativeSelected : POINTER TO RequestBase; +nativeResultReady : BOOL; +nativeFinishedIndex : DINT := -1; +nativeWindowStartMilliseconds : ULINT; +nativeWasConnected : BOOL; +nativeEnable : BOOL := TRUE; +nativeConnectTimeoutMilliseconds : UDINT := 2000; +nativeRetryDelayMilliseconds : UDINT := 100; +nativeHoldUncertainWrites : BOOL := FALSE; +END_VAR +VAR_INPUT +{attribute 'symbol' := 'none'} +nativeIO : ARRAY[0..1999] OF WORD; +{attribute 'symbol' := 'none'} +nativeActiveRequest : POINTER TO RequestBase; +{attribute 'symbol' := 'none'} +nativeArgUnit : BYTE; +{attribute 'symbol' := 'none'} +nativeArgFunction : BYTE; +{attribute 'symbol' := 'none'} +nativeArgAddress : UINT; +{attribute 'symbol' := 'none'} +nativeArgQuantity : UINT; +{attribute 'symbol' := 'none'} +nativeArgTimeout : UDINT := 1000; +{attribute 'symbol' := 'none'} +nativeSent : UDINT; +{attribute 'symbol' := 'none'} +nativeReplied : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowCount : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowTime : UDINT; +END_VAR + +VAR_OUTPUT +out_connected : BOOL; +out_connectionState : UDINT; +out_lastNativeError : UDINT; +out_socketError : UDINT; +out_reconnectCount : UDINT; +out_registrationFull : BOOL; +END_VAR + +VAR CONSTANT + NATIVE_CMD_OPEN : UDINT := 1; + NATIVE_CMD_CLOSE : UDINT := 2; + NATIVE_CMD_ENABLE : UDINT := 3; + NATIVE_CMD_TICK : UDINT := 5; + NATIVE_CMD_SUBMIT : UDINT := 6; + NATIVE_CMD_COPY_READ : UDINT := 7; + NATIVE_CMD_CONFIGURE : UDINT := 9; + NATIVE_CMD_ACKNOWLEDGE : UDINT := 10; + NATIVE_CMD_REQUEST_TIMEOUT : UDINT := 11; + NATIVE_STATE_DISABLED : UDINT := 0; + NATIVE_STATE_READY : UDINT := 3; + NATIVE_STATE_RECEIVING : UDINT := 5; + NATIVE_ERROR_INVALID_HANDLE : UDINT := 256; + NATIVE_ERROR_CANCELLED : UDINT := 10; + NATIVE_STATUS_HANDLE : UDINT := 0; + NATIVE_STATUS_STATE : UDINT := 1; + NATIVE_STATUS_DONE : UDINT := 3; + NATIVE_STATUS_ERROR : UDINT := 4; + NATIVE_STATUS_RESULT : UDINT := 5; + NATIVE_STATUS_LAST_ERROR : UDINT := 6; + NATIVE_STATUS_SOCKET_ERROR : UDINT := 7; + NATIVE_STATUS_EXCEPTION : UDINT := 8; + NATIVE_STATUS_WRITE_UNCERTAIN : UDINT := 9; + NATIVE_STATUS_RECONNECTS : UDINT := 11; + NATIVE_STATUS_FUNCTION : UDINT := 18; +END_VAR + + + +VAR + nativeOpenCommand : UDINT := 1; + nativeDevice : STRING(127); + nativeSerialBaud : UDINT; + nativeSerialParity : BYTE; + nativeSerialBits : BYTE; + nativeSerialStops : BYTE; + nativeSerialRs485 : UDINT; +END_VAR + +VAR_INPUT + in_enable : BOOL := TRUE; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `in_timeout` | VAR_INPUT | `TIME := T#0MS` | Reply timeout override. ClientBase: TIME; RequestBase: SYSTIME numeric milliseconds (not microseconds). Zero selects inherited/calculated timeout. | +| `in_resetStats` | VAR_INPUT | `BOOL` | Reset request consumed and cleared by nativeCycle. Supplying TRUE on every call resets statistics every scan; pulse once. | +| `msgLastSecondCnt` | VAR_OUTPUT | `UDINT` | Submitted request count in the last completed window of at least 1000 ms, not necessarily an exact sliding second. | +| `msgLastSecondTime` | VAR_OUTPUT | `UDINT` | Sum of successful transaction durations in that completed window (ms); excludes failed durations. | +| `numMsgSent` | VAR_OUTPUT | `UDINT` | IEC accepted submission count since resetStats; not a count of physical packets. | +| `numMsgReply` | VAR_OUTPUT | `UDINT` | IEC successful or Modbus-exception reply count since resetStats. | +| `allRequestsOnline` | VAR_OUTPUT | `BOOL` | TRUE when at least one active request exists and onlineCnt equals activeReqCnt. Online can include exception replies. | +| `reqStats` | VAR_OUTPUT | `ARRAY[0..99] OF POINTER TO RequestStats` | Pointers to registered request statistics by registry index, populated on client scans. Null entries are unused; validate before dereference. | +| `nativeIO` | VAR_INPUT | `ARRAY[0..1999] OF WORD` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeActiveRequest` | VAR_INPUT | `POINTER TO RequestBase` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeArgUnit` | VAR_INPUT | `BYTE` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeArgFunction` | VAR_INPUT | `BYTE` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeArgAddress` | VAR_INPUT | `UINT` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeArgQuantity` | VAR_INPUT | `UINT` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeArgTimeout` | VAR_INPUT | `UDINT := 1000` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeSent` | VAR_INPUT | `UDINT` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeReplied` | VAR_INPUT | `UDINT` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeWindowCount` | VAR_INPUT | `UDINT` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `nativeWindowTime` | VAR_INPUT | `UDINT` | Internal wrapper state exposed for compatibility; normally leave under library control. | +| `out_connected` | VAR_OUTPUT | `BOOL` | Transport is in ready/sending/receiving state. Does not prove a request succeeded or data is fresh. | +| `out_connectionState` | VAR_OUTPUT | `UDINT` | 0 disabled, 1 retry wait, 2 TCP connecting, 3 ready, 4 sending, 5 receiving. | +| `out_lastNativeError` | VAR_OUTPUT | `UDINT` | Native last transport error; may persist after recovery. Check current connection and request diagnostics too. | +| `out_socketError` | VAR_OUTPUT | `UDINT` | Native Linux errno snapshot as UDINT; may be historical. Used for both serial and sockets. | +| `out_reconnectCount` | VAR_OUTPUT | `UDINT` | Native successful reconnections after the first connection. Context recreation resets its native lifetime counters. | +| `out_registrationFull` | VAR_OUTPUT | `BOOL` | Sticky request registry overflow indicator; max 100 requests/client. Client reinitialization clears it. | +| `in_enable` | VAR_INPUT | `BOOL := TRUE` | Enable processing; TRUE by default. FALSE releases transport. Continue scanning to refresh state. | + +## ModbusMaster.ClientRTU + +Modbus RTU client using a Linux serial device. Call every scan. Supports 8 data bits, parity 0 none/1 odd/2 even, and 1 or 2 stop bits. Uses unit IDs 1..247; broadcast 0 is unsupported. Serial-setting changes recreate the context. Automatic recovery proceeds while enabled and scanned. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ClientRTU EXTENDS ClientBase +VAR_INPUT + in_device : STRING(127) := '/dev/ttyUSB0'; + in_baudRate : UDINT := 9600; + in_parity : BYTE := 0; // 0 none, 1 odd, 2 even + in_dataBits : BYTE := 8; + in_stopBits : BYTE := 1; // 1 or 2 + in_rs485 : BOOL; // Request Linux kernel RS485 direction control. + + in_reconnectDelay : TIME := T#100MS; + in_holdUncertainWrites : BOOL := FALSE; +END_VAR +VAR + lastDevice : STRING(127); + lastBaud : UDINT; + lastParity : BYTE; + lastBits : BYTE; + lastStops : BYTE; + lastRs485 : BOOL; + lastRetry : TIME; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `in_device` | VAR_INPUT | `STRING(127) := '/dev/ttyUSB0'` | Linux serial device path, default /dev/ttyUSB0, at most 127 ASCII characters. Runtime account needs access. | +| `in_baudRate` | VAR_INPUT | `UDINT := 9600` | Serial baud rate, default 9600. Supplied C source accepts 1200,2400,4800,9600,19200,38400,57600,115200. | +| `in_parity` | VAR_INPUT | `BYTE := 0` | 0 none, 1 odd, 2 even. Must match the slave. | +| `in_dataBits` | VAR_INPUT | `BYTE := 8` | Only 8 supported by supplied native implementation. | +| `in_stopBits` | VAR_INPUT | `BYTE := 1` | 1 or 2; must match the slave. | +| `in_rs485` | VAR_INPUT | `BOOL` | Request Linux kernel RS485 direction control. Leave FALSE for USB adapters handling direction automatically. Requires driver support. | +| `in_reconnectDelay` | VAR_INPUT | `TIME := T#100MS` | Delay before native recovery attempt, default 100 ms; clamped to at least 1 ms. Effective timing also depends on scan interval and failure detection. | +| `in_holdUncertainWrites` | VAR_INPUT | `BOOL := FALSE` | FALSE allows normal retries after uncertain writes. TRUE holds affected write requests until writeNow(); read requests can continue. | + +## ModbusMaster.ClientTCP + +Modbus TCP client with automatic retry after connection or transaction failure. Call every scan. Endpoint or timeout/retry configuration changes close the current context and reopen it on subsequent scans. Requests share this connection and execute sequentially. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ClientTCP EXTENDS ClientBase +VAR_INPUT + {attribute 'symbol' := 'none'} + in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address + {attribute 'symbol' := 'none'} + in_port: UINT := 502; // TCP port + {attribute 'symbol' := 'none'} + in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout. +END_VAR +VAR_INPUT + +in_connectTimeout : TIME := T#2S; +in_reconnectDelay : TIME := T#100MS; // Delay after a failed TCP connection/transaction. +in_holdUncertainWrites : BOOL := FALSE; +END_VAR + +VAR +nativeEndpointIP : DWORD; +nativeEndpointPort : UINT; +nativeConfigConnect : UDINT; +nativeConfigRetry : UDINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `in_ipAddress` | VAR_INPUT | `ARRAY[0..3] OF BYTE` | ClientTCP: four IPv4 bytes. Slave: packed numeric DWORD, 0 binds all interfaces; e.g. DWORD#16#C0A8321F for 192.168.50.31. | +| `in_port` | VAR_INPUT | `UINT := 502` | Remote TCP port, default 502. | +| `in_baudrate` | VAR_INPUT | `UINT := 0` | TCP gateway serial baud-rate hint for calculated reply timeout; 0 uses internal dataRate 105200. Does not configure a serial port. | +| `in_connectTimeout` | VAR_INPUT | `TIME := T#2S` | TCP connection-attempt deadline, default 2 s; clamped to at least 1 ms. | +| `in_reconnectDelay` | VAR_INPUT | `TIME := T#100MS` | Delay before native recovery attempt, default 100 ms; clamped to at least 1 ms. Effective timing also depends on scan interval and failure detection. | +| `in_holdUncertainWrites` | VAR_INPUT | `BOOL := FALSE` | FALSE allows normal retries after uncertain writes. TRUE holds affected write requests until writeNow(); read requests can continue. | + +## ModbusMaster.ReqReadCoil + +Modbus FC 1: read BOOL coils. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is 2000. Usable length is clamped to array capacity. Only a successful validated reply updates the destination slice. Old values remain after failures; check out_validData and out_stale. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ReqReadCoil EXTENDS RequestBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `data` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice. | + +## ModbusMaster.ReqReadDiscrete + +Modbus FC 2: read BOOL discrete inputs. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is 2000. Usable length is clamped to array capacity. Only a successful validated reply updates the destination slice. Old values remain after failures; check out_validData and out_stale. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ReqReadDiscrete EXTENDS RequestBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `data` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice. | + +## ModbusMaster.ReqReadHolding + +Modbus FC 3: read UINT holding registers. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is 125. Usable length is clamped to array capacity. Only a successful validated reply updates the destination slice. Old values remain after failures; check out_validData and out_stale. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ReqReadHolding EXTENDS RequestBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `data` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice. | + +## ModbusMaster.ReqReadInput + +Modbus FC 4: read UINT input registers. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is 125. Usable length is clamped to array capacity. Only a successful validated reply updates the destination slice. Old values remain after failures; check out_validData and out_stale. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ReqReadInput EXTENDS RequestBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `data` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice. | + +## ModbusMaster.RequestBase + +Abstract request binding, scheduling and diagnostics. Derived requests register automatically when called with a valid initialized client. Call each instance every scan with stable array storage. Quantity zero disables this request. Actual length is MIN(in_quantity, remaining array capacity). + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT RequestBase +VAR_INPUT + {attribute 'symbol' := 'none'} + client: REFERENCE TO ClientBase; // Instance of atMBClient + {attribute 'symbol' := 'none'} + in_startAddr: UINT; + {attribute 'symbol' := 'none'} + in_quantity: UINT; + {attribute 'symbol' := 'none'} + in_dataOffset: UINT; // Offset position for data into array. + {attribute 'symbol' := 'none'} + in_unitId: BYTE := 0; + {attribute 'symbol' := 'none'} + in_timeout: SYSTIME := 0; // Reply timeout in ms; zero uses client/default calculation. + {attribute 'symbol' := 'none'} + in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority. + {attribute 'symbol' := 'none'} + in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority. + {attribute 'symbol' := 'none'} + in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero. + {attribute 'symbol' := 'none'} + stats: RequestStats; //Request stats + +END_VAR +VAR + {attribute 'symbol' := 'none'} + dataCapacity: UINT; +END_VAR + +VAR +nativeFunction : BYTE; +nativeTransactionQuantity : UINT; +nativeTransactionDataIndex : UINT; +nativeInFlight : BOOL; +nativeReadComplete : BOOL; +nativeCopyAllowed : BOOL; +nativeWriteHold : BOOL; +nativeRequestStartMilliseconds : ULINT; +nativeDataIndex : DINT; +nativeCallResult : UDINT; +END_VAR + +VAR_OUTPUT +out_nativeError : UDINT; +out_socketError : UDINT; +out_writeUncertain : BOOL; +out_validData : BOOL; +out_dataAgeMs : ULINT; +out_stale : BOOL := TRUE; +out_parameterError : BOOL; +END_VAR + +VAR_INPUT +in_staleAfter : TIME := T#2S; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `client` | VAR_INPUT | `REFERENCE TO ClientBase` | Reference to the owning ClientBase concrete instance. Initialize/call client before first registration; retain a stable instance. | +| `in_startAddr` | VAR_INPUT | `UINT` | Zero-based Modbus protocol address, not a 40001/30001 display reference. Must fit the transaction into address space. | +| `in_quantity` | VAR_INPUT | `UINT` | Requested item count; actual stats.reqLength is MIN(this, available destination/source capacity). Zero makes the request inactive. | +| `in_dataOffset` | VAR_INPUT | `UINT` | Actual first index in the application array. For ARRAY[1..10], use 1 to start at the first element; not a relative offset. | +| `in_unitId` | VAR_INPUT | `BYTE := 0` | Modbus unit/slave ID, default 0. RTU requires explicit 1..247; TCP/gateway values depend on the target. | +| `in_timeout` | VAR_INPUT | `SYSTIME := 0` | Reply timeout override. ClientBase: TIME; RequestBase: SYSTIME numeric milliseconds (not microseconds). Zero selects inherited/calculated timeout. | +| `in_minDelay` | VAR_INPUT | `TIME := T#10MS` | Minimum start-to-start time before scheduler eligibility, default 10 ms. Not a fixed polling period. | +| `in_prioritySetpoint` | VAR_INPUT | `INT := 50` | Priority restored on success, default 50. Lower values win. Error backoff can replace current priority. | +| `in_priorityCurrent` | VAR_INPUT | `INT` | Mutable scheduler priority. Lower values win, equal values use age. WriteNow sets 0. Application need not maintain it each scan. | +| `stats` | VAR_INPUT | `RequestStats` | Per-request RequestStats instance maintained by request processing; inspect rather than overwrite. | +| `out_nativeError` | VAR_OUTPUT | `UDINT` | Result of the last completed/failed request; 0 success. Precise native code, distinct from IEC Error enum. | +| `out_socketError` | VAR_OUTPUT | `UDINT` | Native Linux errno snapshot as UDINT; may be historical. Used for both serial and sockets. | +| `out_writeUncertain` | VAR_OUTPUT | `BOOL` | Write may have reached target without a confirmed response. It is not proof of failure or success. Relevant for write requests. | +| `out_validData` | VAR_OUTPUT | `BOOL` | TRUE after a successful read; subsequent read failures do not necessarily clear it. Client release or invalidateNativeData clears it. | +| `out_dataAgeMs` | VAR_OUTPUT | `ULINT` | Age since last successful read in milliseconds. Updated by normal processing; completion early returns can defer refresh by one scan. | +| `out_stale` | VAR_OUTPUT | `BOOL := TRUE` | TRUE before valid read data or when age exceeds in_staleAfter. Array contents are retained. | +| `out_parameterError` | VAR_OUTPUT | `BOOL` | Array-offset validation for requests; full transaction validation can instead appear in out_nativeError/stats.lastErrorId. Slave: invalid port/interfaceName/table bounds or capacity. | +| `in_staleAfter` | VAR_INPUT | `TIME := T#2S` | Read data age threshold, default 2 s. out_stale becomes TRUE when age is strictly greater; does not stop polling. | + +## ModbusMaster.RequestStats + +Request counters, completion properties, online/offline tracking and 100 ms indicator lights. Normally maintained by RequestBase. Online means a valid response was received, including a Modbus exception; it does not guarantee successful data. Several fields are declared VAR_INPUT for compatibility but are maintained internally. + +```iecst +{attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK RequestStats +VAR_INPUT + lastErrorId: Error; //Last error id + lastExceptionCode: ExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions. + lastValidDuration: UDINT; // How long last transaction took (ms) + longestValidDuration: UDINT; //Longest valid duration (ms) + interval: UDINT; // Start-to-start interval (ms) + offlineCnt: UINT; //Count of how many times this request has gone offline + doneCnt: UDINT; // Count of successful request + errorCnt: UDINT; // Count of failed requests + doneCntRst: UDINT; // Count of succesful requests, resets on error result + errorCntRst: UDINT; // Count of failed requests, resets on succesful request + sinceLastExec: UDINT; // Time since the last start (ms) + timeoutCnt: UDINT; + reqLength: UINT; //Number of registers requested in request, used to calc active. +END_VAR +VAR_OUTPUT + executeLight: BOOL; //Execute that stays on momentarily + reqDoneLight: BOOL; //Request done that stays on momentarily + reqErrorLight: BOOL; //Request error that stays on momentarily + reqBusyLight: BOOL; //Request busy that stays on momentarily + online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok. + offline: BOOL; //Opposite of online, but starts out as false on program start. + active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request. + executeMbFb: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + doneIndicatorDelay: TOF; + {attribute 'symbol' := 'none'} + errorIndicatorDelay: TOF; + {attribute 'symbol' := 'none'} + busyIndicatorDelay: TOF; + {attribute 'symbol' := 'none'} + executeIndicatorDelay: TOF; + toString: STRING(20); + {attribute 'symbol' := 'none'} + executeTrig: R_TRIG; //Execute trigger + {attribute 'symbol' := 'none'} + offlineTrig: R_TRIG; //Offline trigger + {attribute 'symbol' := 'none'} + lastRequestStartMicroseconds: SYSTIME; //time of start of last request + {attribute 'symbol' := 'none'} + currentTimeMicroseconds: SYSTIME; + _execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete + _reqDone: BOOL; //Request done + _reqBusy: BOOL; //Request busy + _reqError: BOOL; //Request error +END_VAR + +VAR CONSTANT + {attribute 'symbol' := 'none'} + INDICATOR_HOLD_TIME: TIME:= T#100MS; //Light on time for debug indicators +END_VAR +VAR_INPUT +nativeResponseSeen : BOOL; +nativeLastSuccessMs : ULINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `lastErrorId` | VAR_INPUT | `Error` | Last mapped IEC error; successful completion resets to OK. | +| `lastExceptionCode` | VAR_INPUT | `ExceptionCodes` | Last Modbus exception, success=0; stats may substitute TIMEOUT=-1 for non-exception failures. | +| `lastValidDuration` | VAR_INPUT | `UDINT` | Last successful request duration (ms). | +| `longestValidDuration` | VAR_INPUT | `UDINT` | Maximum successful duration recorded; resetStats does not clear it. | +| `interval` | VAR_INPUT | `UDINT` | Start-to-start execution interval (ms). | +| `offlineCnt` | VAR_INPUT | `UINT` | Count of NOT online rising edges; can include initial not-online state, not only a physical disconnect. | +| `doneCnt` | VAR_INPUT | `UDINT` | Total successful completions; resetStats clears. | +| `errorCnt` | VAR_INPUT | `UDINT` | Total failed completions; resetStats clears. | +| `doneCntRst` | VAR_INPUT | `UDINT` | Consecutive successes; reset on error/offline/resetStats. | +| `errorCntRst` | VAR_INPUT | `UDINT` | Consecutive errors; reset on successful completion/resetStats. | +| `sinceLastExec` | VAR_INPUT | `UDINT` | Age since last execution start (ms), capped around 60000 by the statistics clock logic. | +| `timeoutCnt` | VAR_INPUT | `UDINT` | Statistics non-exception error count; can include other native failures. Not exclusively reply timeouts; resetStats does not clear it. | +| `reqLength` | VAR_INPUT | `UINT` | Effective transaction length; zero means inactive. | +| `executeLight` | VAR_OUTPUT | `BOOL` | Execution indicator with 100 ms off-delay. | +| `reqDoneLight` | VAR_OUTPUT | `BOOL` | Completion indicator with 100 ms off-delay. | +| `reqErrorLight` | VAR_OUTPUT | `BOOL` | Error indicator with 100 ms off-delay. | +| `reqBusyLight` | VAR_OUTPUT | `BOOL` | Busy indicator with 100 ms off-delay. | +| `online` | VAR_OUTPUT | `BOOL` | Communication status; valid normal/exception response sets evidence. Distinct from valid data. | +| `offline` | VAR_OUTPUT | `BOOL` | Offline status; initialized FALSE, explicitly set on client-offline or repeated failures. | +| `active` | VAR_OUTPUT | `BOOL` | TRUE when reqLength is nonzero. | +| `executeMbFb` | VAR_OUTPUT | `BOOL` | Legacy retained output; no assignment in current native implementation. Do not use as a trigger. | +| `nativeResponseSeen` | VAR_INPUT | `BOOL` | Internal communication-response evidence, maintained by nativeFinish and offline handling. | +| `nativeLastSuccessMs` | VAR_INPUT | `ULINT` | Internal successful-read timestamp in monotonic milliseconds. | + +## ModbusMaster.ReqWriteCoil + +Modbus FC 15: write BOOL coils. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is 1968. Usable length is clamped to array capacity. Values are copied into native storage at submission. This FB uses the multiple-write FC even for one element. Use writeNow() to release a held uncertain write. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ReqWriteCoil EXTENDS RequestBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `data` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice. | + +## ModbusMaster.ReqWriteHolding + +Modbus FC 16: write UINT holding registers. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is 123. Usable length is clamped to array capacity. Values are copied into native storage at submission. This FB uses the multiple-write FC even for one element. Use writeNow() to release a held uncertain write. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ReqWriteHolding EXTENDS RequestBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `data` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice. | + +## ModbusMaster.To_IPAddress + +Returns the four supplied bytes as ARRAY[0..3] OF BYTE. Example: atMb.To_IPAddress(192,168,50,10). This is the ClientTCP address format; the slave bind address instead uses a packed DWORD. + +```iecst +FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE +VAR_INPUT + byte1: BYTE; + byte2: BYTE; + byte3: BYTE; + byte4: BYTE; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `byte1` | VAR_INPUT | `BYTE` | First IPv4 octet. | +| `byte2` | VAR_INPUT | `BYTE` | Second IPv4 octet. | +| `byte3` | VAR_INPUT | `BYTE` | Third IPv4 octet. | +| `byte4` | VAR_INPUT | `BYTE` | Fourth IPv4 octet. | + +## ModbusSlave.ModbusSlave + +Native Modbus TCP server. Same implementation and interface as SlaveTCP. Call once per scan with all four memory arrays. Array bounds are protocol addresses; each table is limited to 2000 elements and addresses 0..65535. Pushes application memory, performs bounded network processing, then copies native memory back. This is TCP only, not an RTU slave. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK ModbusSlave +VAR_IN_OUT + aDiscreteInputsMemory : ARRAY[*] OF BOOL; + aCoilsMemory : ARRAY[*] OF BOOL; + aInputRegistersMemory : ARRAY[*] OF UINT; + aHoldingRegistersMemory : ARRAY[*] OF UINT; +END_VAR +VAR_INPUT + in_enable : BOOL := TRUE; + in_reset : BOOL; + in_reconnectDelay : TIME := T#100MS; + interfaceName : WSTRING; // Legacy field; nonempty rejected. Use in_ipAddress. + in_ipAddress : DWORD; // Numeric bind IP, 0 = all interfaces. + port : UINT := 502; +END_VAR +VAR_OUTPUT + out_running : BOOL; + out_error : BOOL; + out_errorId : Error; + out_parameterError : BOOL; + out_socketError : UDINT; + out_connectedClients : UINT; + out_requestsReceived : UDINT; + out_repliesSent : UDINT; + out_exceptionReplies : UDINT; +END_VAR +VAR + serverHandle : UDINT; + serverStatus : ARRAY[0..23] OF UDINT; + serverIO : ARRAY[0..1999] OF WORD; + nowUs : SYSTIME; + nowMs : ULINT; + lastIP : DWORD; + lastPort : UINT; + lastRetry : TIME; + callResult : UDINT; + tableIndex : BYTE; + itemIndex : DINT; + tableStart : UINT; + tableCount : UINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `aDiscreteInputsMemory` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Read-only from network BOOL input table; FC2 reads. Application updates it. | +| `aCoilsMemory` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Read/write BOOL coil table. Array bounds are protocol addresses; FC1 reads, FC5/15 write. | +| `aInputRegistersMemory` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Read-only from network UINT input table; FC4 reads. Application updates it. | +| `aHoldingRegistersMemory` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Read/write UINT holding table; FC3 reads, FC6/16 write. | +| `in_enable` | VAR_INPUT | `BOOL := TRUE` | Enable processing; TRUE by default. FALSE releases transport. Continue scanning to refresh state. | +| `in_reset` | VAR_INPUT | `BOOL` | Slave one-shot recreation input. FB clears it internally; supplying TRUE every scan recreates every scan. | +| `in_reconnectDelay` | VAR_INPUT | `TIME := T#100MS` | Delay before native recovery attempt, default 100 ms; clamped to at least 1 ms. Effective timing also depends on scan interval and failure detection. | +| `interfaceName` | VAR_INPUT | `WSTRING` | Legacy slave field. Must be empty; nonempty is rejected. Use packed in_ipAddress instead. | +| `in_ipAddress` | VAR_INPUT | `DWORD` | ClientTCP: four IPv4 bytes. Slave: packed numeric DWORD, 0 binds all interfaces; e.g. DWORD#16#C0A8321F for 192.168.50.31. | +| `port` | VAR_INPUT | `UINT := 502` | Slave listening port, default 502, must be nonzero. | +| `out_running` | VAR_OUTPUT | `BOOL` | Native listener is bound and enabled. Does not mean a peer is connected. | +| `out_error` | VAR_OUTPUT | `BOOL` | Current server error flag when outputs are refreshed; some early returns leave other diagnostics unchanged. | +| `out_errorId` | VAR_OUTPUT | `Error` | IEC server error classification: parameter/internal/native socket class. Consult errno for OS detail. | +| `out_parameterError` | VAR_OUTPUT | `BOOL` | Array-offset validation for requests; full transaction validation can instead appear in out_nativeError/stats.lastErrorId. Slave: invalid port/interfaceName/table bounds or capacity. | +| `out_socketError` | VAR_OUTPUT | `UDINT` | Native Linux errno snapshot as UDINT; may be historical. Used for both serial and sockets. | +| `out_connectedClients` | VAR_OUTPUT | `UINT` | Number of native TCP peers connected (max 8 per server in supplied C source). | +| `out_requestsReceived` | VAR_OUTPUT | `UDINT` | Native request count for current server context. | +| `out_repliesSent` | VAR_OUTPUT | `UDINT` | Native fully transmitted reply count for current context, including exception replies. | +| `out_exceptionReplies` | VAR_OUTPUT | `UDINT` | Number of Modbus exception replies for current server context. | + +## ModbusSlave.SlaveTCP + +Native Modbus TCP server with four separate memory tables. Same implementation and interface as ModbusSlave. FC 1/2/3/4 read the tables; FC 5/6/15/16 write coils/holding registers. Array lower bounds define Modbus addresses. Call every scan and retain array storage. This is TCP only. + +```iecst +{attribute 'no_assign'} +FUNCTION_BLOCK SlaveTCP +VAR_IN_OUT + aDiscreteInputsMemory : ARRAY[*] OF BOOL; + aCoilsMemory : ARRAY[*] OF BOOL; + aInputRegistersMemory : ARRAY[*] OF UINT; + aHoldingRegistersMemory : ARRAY[*] OF UINT; +END_VAR +VAR_INPUT + in_enable : BOOL := TRUE; + in_reset : BOOL; + in_reconnectDelay : TIME := T#100MS; + interfaceName : WSTRING; // Legacy field; nonempty rejected. Use in_ipAddress. + in_ipAddress : DWORD; // Numeric bind IP, 0 = all interfaces. + port : UINT := 502; +END_VAR +VAR_OUTPUT + out_running : BOOL; + out_error : BOOL; + out_errorId : Error; + out_parameterError : BOOL; + out_socketError : UDINT; + out_connectedClients : UINT; + out_requestsReceived : UDINT; + out_repliesSent : UDINT; + out_exceptionReplies : UDINT; +END_VAR +VAR + serverHandle : UDINT; + serverStatus : ARRAY[0..23] OF UDINT; + serverIO : ARRAY[0..1999] OF WORD; + nowUs : SYSTIME; + nowMs : ULINT; + lastIP : DWORD; + lastPort : UINT; + lastRetry : TIME; + callResult : UDINT; + tableIndex : BYTE; + itemIndex : DINT; + tableStart : UINT; + tableCount : UINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `aDiscreteInputsMemory` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Read-only from network BOOL input table; FC2 reads. Application updates it. | +| `aCoilsMemory` | VAR_IN_OUT | `ARRAY[*] OF BOOL` | Read/write BOOL coil table. Array bounds are protocol addresses; FC1 reads, FC5/15 write. | +| `aInputRegistersMemory` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Read-only from network UINT input table; FC4 reads. Application updates it. | +| `aHoldingRegistersMemory` | VAR_IN_OUT | `ARRAY[*] OF UINT` | Read/write UINT holding table; FC3 reads, FC6/16 write. | +| `in_enable` | VAR_INPUT | `BOOL := TRUE` | Enable processing; TRUE by default. FALSE releases transport. Continue scanning to refresh state. | +| `in_reset` | VAR_INPUT | `BOOL` | Slave one-shot recreation input. FB clears it internally; supplying TRUE every scan recreates every scan. | +| `in_reconnectDelay` | VAR_INPUT | `TIME := T#100MS` | Delay before native recovery attempt, default 100 ms; clamped to at least 1 ms. Effective timing also depends on scan interval and failure detection. | +| `interfaceName` | VAR_INPUT | `WSTRING` | Legacy slave field. Must be empty; nonempty is rejected. Use packed in_ipAddress instead. | +| `in_ipAddress` | VAR_INPUT | `DWORD` | ClientTCP: four IPv4 bytes. Slave: packed numeric DWORD, 0 binds all interfaces; e.g. DWORD#16#C0A8321F for 192.168.50.31. | +| `port` | VAR_INPUT | `UINT := 502` | Slave listening port, default 502, must be nonzero. | +| `out_running` | VAR_OUTPUT | `BOOL` | Native listener is bound and enabled. Does not mean a peer is connected. | +| `out_error` | VAR_OUTPUT | `BOOL` | Current server error flag when outputs are refreshed; some early returns leave other diagnostics unchanged. | +| `out_errorId` | VAR_OUTPUT | `Error` | IEC server error classification: parameter/internal/native socket class. Consult errno for OS detail. | +| `out_parameterError` | VAR_OUTPUT | `BOOL` | Array-offset validation for requests; full transaction validation can instead appear in out_nativeError/stats.lastErrorId. Slave: invalid port/interfaceName/table bounds or capacity. | +| `out_socketError` | VAR_OUTPUT | `UDINT` | Native Linux errno snapshot as UDINT; may be historical. Used for both serial and sockets. | +| `out_connectedClients` | VAR_OUTPUT | `UINT` | Number of native TCP peers connected (max 8 per server in supplied C source). | +| `out_requestsReceived` | VAR_OUTPUT | `UDINT` | Native request count for current server context. | +| `out_repliesSent` | VAR_OUTPUT | `UDINT` | Native fully transmitted reply count for current context, including exception replies. | +| `out_exceptionReplies` | VAR_OUTPUT | `UDINT` | Number of Modbus exception replies for current server context. | + +## ClientBase.addRequest + +Register a non-null RequestBase pointer once. Duplicate pointers are ignored. Capacity is 100 requests per client; overflow sets out_registrationFull. No removal API is provided. Normally called automatically by the request FB. + +```iecst +//Adds the specified request to the global list +METHOD PUBLIC addRequest +VAR_INPUT + request: POINTER TO RequestBase; +END_VAR +VAR + requestIndex : DINT := 0; + +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `request` | VAR_INPUT | `POINTER TO RequestBase` | Internal wrapper state exposed for compatibility; normally leave under library control. | + +## ClientBase.FB_exit + +CODESYS lifecycle hook releasing owned native resources. Applications should use release/recreateClient rather than call lifecycle hooks manually. Server hooks close the server context; the client hook calls nativeRelease. + +```iecst +METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `bInCopyCode` | VAR_INPUT | `BOOL` | Runtime lifecycle copy-code indicator; current implementation does not branch on it. | + +## ClientBase.FB_reinit + +CODESYS lifecycle hook clearing native handles and in-flight state. ClientBase also clears the request registry for re-registration; RequestBase clears execution/completion flags; servers clear their handle. These hooks alone do not establish qualified online-change support. + +```iecst +METHOD FB_reinit : BOOL +VAR + requestIndex : DINT; +END_VAR +``` + +## ClientBase.nativeCall + +Internal adapter to atMbCall_cext. Command 20 packs the ASCII serial path into WORD storage and passes serial settings in ABI fields; other commands use normal client arguments and status buffers. Return is the native command result, 0 for success. + +```iecst +METHOD nativeCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR + +VAR + deviceBytes : POINTER TO BYTE; + deviceLength : UINT; + deviceIndex : DINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `command` | VAR_INPUT | `UDINT` | Native command selector. Use wrappers in application code. | + +## ClientBase.nativeCycle + +Internal per-scan engine: acquire context, tick nonblocking I/O, update diagnostics, copy completed read data, process registered requests, acknowledge completion, select the next request, and publish statistics. Invalid native handles are discarded and reopened on a later scan. + +```iecst +METHOD nativeCycle +VAR + callResult : UDINT; +END_VAR +``` + +## ClientBase.nativeEnsureContext + +Acquire/configure/enable a native context if absent. Returns TRUE when a handle exists or initialization succeeds. A handle does not mean connected; check out_connected. Failure records a native error and closes partially initialized contexts. + +```iecst +METHOD PROTECTED nativeEnsureContext : BOOL +VAR + callResult : UDINT; +END_VAR +``` + +## ClientBase.nativeRelease + +Close the native context, complete any active request as cancelled, clear selection, set registered requests offline and invalidate cached read data. Returns TRUE. It does not set in_enable FALSE; an enabled scanned client can reopen. + +```iecst +METHOD nativeRelease : BOOL +VAR + requestIndex : DINT; +END_VAR +``` + +## ClientBase.nativeSelectNextRequest + +Select an active, unheld request that meets in_minDelay and was not just completed this scan. Lowest in_priorityCurrent wins; oldest sinceLastExec breaks ties. Positive priorities are decremented when the best selected priority is positive. The selected request is submitted immediately; zero priority is not a prerequisite for execution. + +```iecst +METHOD PROTECTED nativeSelectNextRequest +VAR + requestIndex : DINT; + selectedRequestIndex : DINT; + selectedPriority : INT; + oldestRequestAgeMilliseconds : UDINT; +END_VAR +``` + +## ClientBase.nativeUpdateDiagnostics + +Publish native connection state, last error, errno and reconnect count. States 3..5 count as connected. A connected-to-disconnected transition marks registered requests offline. + +```iecst +METHOD PROTECTED nativeUpdateDiagnostics +``` + +## ClientBase.nativeUpdateStatistics + +Publish IEC send/reply counters, registered statistics pointers and active-request aggregate online flags. Every elapsed window of at least 1000 ms publishes submitted-request count and successful transaction-duration sum, then restarts the window. + +```iecst +METHOD PROTECTED nativeUpdateStatistics +VAR + requestIndex : DINT; +END_VAR +``` + +## ClientBase.processAllRequests + +Internal loop over registered non-null requests, dispatching processRequest. Returns immediately for an empty or invalid registry. Usually invoked by nativeCycle rather than application code. + +```iecst +METHOD processAllRequests +VAR_INPUT +END_VAR +VAR + requestIndex: DINT; // Request registry index. +END_VAR +``` + +## ClientBase.recreateClient + +Close/release the current context so the next enabled scan creates a fresh one with current settings. Cancels an active transaction and invalidates cached read data. Abstract on ClientBase; implemented by both concrete clients. + +```iecst +METHOD ABSTRACT recreateClient +VAR_INPUT +END_VAR +``` + +## ClientBase.release + +Stop this client by setting in_enable FALSE, then release its native context. Returns the release result. To resume, set in_enable TRUE and keep calling the client. A call that supplies in_enable := TRUE every scan will re-enable it. + +```iecst +METHOD release : BOOL +``` + +## ClientBase.resetStats + +Reset IEC send/reply/window counters and call each registered stats.resetStats. Does not clear native errno/reconnect counters or all per-request history. No transport recreation. + +```iecst +METHOD resetStats +VAR + requestIndex : DINT; +END_VAR +``` + +## ClientBase.setAllRequestsOffline + +Internal helper marking every registered request offline via its statistics FB. Returns TRUE after processing a nonempty valid registry; early return does not explicitly assign the return value. + +```iecst +METHOD PROTECTED setAllRequestsOffline : BOOL +VAR_INPUT +END_VAR +VAR + requestIndex: DINT; +END_VAR +``` + +## ClientBase.setupClient + +Returns whether nativeHandle is nonzero. It does not create a context or guarantee transport connectivity. Normal FB scanning performs initialization. + +```iecst +METHOD setupClient : BOOL +VAR_INPUT +END_VAR +``` + +## ClientBase.activeIndex + +Get the current active request registry index, or -1 when idle. Setting a valid index sets that request stats.execute TRUE; it does not bypass native scheduler selection or directly submit a transaction. + +```iecst +PROPERTY activeIndex : DINT +``` + +## ClientBase.activeReqCnt + +Private count of registered requests whose stats.active is TRUE. Zero quantity/usable length requests are inactive. + +```iecst +PROPERTY PRIVATE activeReqCnt : UINT +``` + +## ClientBase.allRequestsComplete + +Private legacy completion check. TRUE only if none of the registered requests has execute, reqDone, reqBusy or reqError set. Empty registry returns FALSE. This is not a network completion pulse. + +```iecst +{attribute 'symbol' := 'none'} +PROPERTY PRIVATE allRequestsComplete : BOOL +``` + +## ClientBase.onlineCnt + +Count registered requests with stats.online TRUE. Includes any registered request with that flag; used for aggregate reporting. + +```iecst +{attribute 'symbol' := 'none'} +PROPERTY onlineCnt : DINT//Count of requests that are online +``` + +## ClientBase.requestExecuting + +Private property returning activeIndex <> -1. Represents selected/active request state maintained by the scheduler. + +```iecst +{attribute 'symbol' := 'none'} +PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing +``` + +## ClientRTU.recreateClient + +Close/release the current context so the next enabled scan creates a fresh one with current settings. Cancels an active transaction and invalidates cached read data. Abstract on ClientBase; implemented by both concrete clients. + +```iecst +METHOD recreateClient +``` + +## ClientRTU.toString + +ClientRTU monitoring property returning ClientStatus based on onlineCnt versus requestCnt. Reports allOk for an empty registry. RequestStats also has an internal STRING field of this name; these are different values. + +```iecst +{attribute 'symbol' := 'none'} +PROPERTY toString : ClientStatus //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working +``` + +## ClientTCP.recreateClient + +Close/release the current context so the next enabled scan creates a fresh one with current settings. Cancels an active transaction and invalidates cached read data. Abstract on ClientBase; implemented by both concrete clients. + +```iecst +METHOD recreateClient +``` + +## ReqReadCoil.processRequest + +Derived request dispatch: performs ReqReadCoil transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data. + +```iecst +METHOD processRequest +``` + +## ReqReadCoil.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD recreateRequest : BOOL +``` + +## ReqReadDiscrete.processRequest + +Derived request dispatch: performs ReqReadDiscrete transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data. + +```iecst +METHOD processRequest +``` + +## ReqReadDiscrete.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD recreateRequest : BOOL +``` + +## ReqReadHolding.processRequest + +Derived request dispatch: performs ReqReadHolding transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data. + +```iecst +METHOD processRequest +``` + +## ReqReadHolding.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD recreateRequest : BOOL +``` + +## ReqReadInput.processRequest + +Derived request dispatch: performs ReqReadInput transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data. + +```iecst +METHOD processRequest +``` + +## ReqReadInput.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD recreateRequest : BOOL +``` + +## RequestBase.busy + +Assign stats.reqBusy from input in. Internal request helper. Although declared BOOL, its implementation does not assign a return value; use the side effect rather than the return. + +```iecst +METHOD busy : BOOL +VAR_INPUT + in: BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `in` | VAR_INPUT | `BOOL` | New state to assign. Normally used internally by completion helpers. | + +## RequestBase.done + +Set stats.reqDone. When in is TRUE, restore in_priorityCurrent to in_prioritySetpoint for the next scheduler cycle. Successful completion counters are maintained by the stats property setter. + +```iecst +METHOD done +VAR_INPUT + // Done state + in: BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `in` | VAR_INPUT | `BOOL` | New state to assign. Normally used internally by completion helpers. | + +## RequestBase.err + +Set stats.reqError and, on TRUE, record errorId/exceptionCode. Set next priority to 10 * MIN(consecutive errors,3)^2. Lower numeric priority wins, so this backs off repeated failures relative to lower-priority-number requests. + +```iecst +METHOD err +VAR_INPUT + // Error state + in: BOOL; + errorId: Error; + exceptionCode: ExceptionCodes; +END_VAR +VAR + priorityMultiplier: INT; //Raise priority when not response +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `in` | VAR_INPUT | `BOOL` | New state to assign. Normally used internally by completion helpers. | +| `errorId` | VAR_INPUT | `Error` | Mapped IEC error supplied to the error helper. | +| `exceptionCode` | VAR_INPUT | `ExceptionCodes` | Modbus exception classification supplied to the error helper. | + +## RequestBase.FB_reinit + +CODESYS lifecycle hook clearing native handles and in-flight state. ClientBase also clears the request registry for re-registration; RequestBase clears execution/completion flags; servers clear their handle. These hooks alone do not establish qualified online-change support. + +```iecst +METHOD FB_reinit : BOOL +``` + +## RequestBase.invalidateNativeData + +Mark cached data invalid and stale. Does not erase the application array. Called during client release; applications should gate use of cached values with validity and age flags. + +```iecst +METHOD PUBLIC invalidateNativeData +``` + +## RequestBase.nativeFinish + +Complete a request from native result. Map errors, copy errno/write uncertainty, clear busy, update response/counters and successful-read freshness. Modbus exceptions count as replies and communication-online evidence. If enabled, uncertain writes are held pending writeNow(). + +```iecst +METHOD nativeFinish +VAR_INPUT + result : UDINT; +END_VAR +VAR + mapped : Error; + ex : ExceptionCodes; + duration : UDINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `result` | VAR_INPUT | `UDINT` | Native completion result; 0 success. | + +## RequestBase.nativeProcess + +Shared request processing called by derived processRequest. Completes native results or submits a selected request, sets its timeout and records busy/send counters. Updates data age and stale flags during ordinary processing. Completion branches can return before age refresh, so allow a subsequent scan for freshness diagnostics. + +```iecst +METHOD nativeProcess +VAR_INPUT + functionCode : BYTE; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `functionCode` | VAR_INPUT | `BYTE` | Modbus FC; command 20 uses data bits, server push/pull use table ID. | + +## RequestBase.processRequest + +Abstract internal request dispatch implemented by each concrete read/write block. + +```iecst +METHOD ABSTRACT processRequest +VAR_INPUT +END_VAR +``` + +## RequestBase.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD ABSTRACT recreateRequest : BOOL +``` + +## RequestBase.replyTimeout + +Protected timeout property expressed internally in microseconds. Request in_timeout is milliseconds and takes precedence, then client TIME in_timeout, then a dataRate formula, then 1 second fallback. Submission rounds up to milliseconds and clamps to at least 1 ms. + +```iecst +//Calculates the reply timout setting +PROPERTY PROTECTED replyTimeout : UDINT +``` + +## RequestStats.resetStats + +Reset only doneCnt, doneCntRst, errorCnt and errorCntRst. Retains durations, offlineCnt, timeoutCnt and timestamps. + +```iecst +METHOD resetStats +VAR_INPUT +END_VAR +``` + +## RequestStats.setClientOffline + +Clear nativeResponseSeen and consecutive-success count, force online FALSE/offline TRUE. Does not clear data arrays. Declared BOOL but does not assign a return value. + +```iecst +METHOD setClientOffline : BOOL +VAR_INPUT +END_VAR +``` + +## RequestStats.setToString + +Private statistics helper formatting OK:/Er: into STRING(20). Declared BOOL but does not assign a return value. + +```iecst +METHOD PRIVATE setToString : BOOL +VAR_INPUT +END_VAR +``` + +## RequestStats.execute + +Statistics execution property. Setting TRUE starts the execution state only when reqDone and reqError are FALSE; otherwise the setter clears execution. Usually controlled by the scheduler. + +```iecst +PROPERTY execute : BOOL +``` + +## RequestStats.reqBusy + +Get/set the underlying request-busy flag. Normally managed by the native wrapper. The scan-held reqBusyLight provides a more visible monitoring indicator. + +```iecst +PROPERTY reqBusy : BOOL +``` + +## RequestStats.reqDone + +Get/set completion flag. When TRUE during execution, record duration, increment total/consecutive successes, clear consecutive errors and last error, clear execution and update the monitor string. Do not repeatedly drive this manually. + +```iecst +PROPERTY reqDone : BOOL +``` + +## RequestStats.reqError + +Get/set error flag. When TRUE during execution, increment total/consecutive errors, clear consecutive successes and execution. If lastExceptionCode is success, increment timeoutCnt and set TIMEOUT even for some non-timeout native errors. + +```iecst +PROPERTY reqError : BOOL +``` + +## ReqWriteCoil.processRequest + +Derived request dispatch: performs ReqWriteCoil transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data. + +```iecst +METHOD processRequest +``` + +## ReqWriteCoil.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD recreateRequest : BOOL +``` + +## ReqWriteCoil.writeNow + +Clear uncertain-write hold and out_writeUncertain, then set in_priorityCurrent to 0. Returns TRUE. Requests a prompt opportunity at the next scheduler selection; does not guarantee transmission in this call and does not bypass connection/minimum-delay requirements. + +```iecst +METHOD writeNow : BOOL +VAR_INPUT +END_VAR +``` + +## ReqWriteHolding.processRequest + +Derived request dispatch: performs ReqWriteHolding transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data. + +```iecst +METHOD processRequest +``` + +## ReqWriteHolding.recreateRequest + +Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport. + +```iecst +METHOD recreateRequest : BOOL +``` + +## ReqWriteHolding.writeNow + +Clear uncertain-write hold and out_writeUncertain, then set in_priorityCurrent to 0. Returns TRUE. Requests a prompt opportunity at the next scheduler selection; does not guarantee transmission in this call and does not bypass connection/minimum-delay requirements. + +```iecst +METHOD writeNow : BOOL +VAR_INPUT +END_VAR +``` + +## ModbusSlave.FB_exit + +CODESYS lifecycle hook releasing owned native resources. Applications should use release/recreateClient rather than call lifecycle hooks manually. Server hooks close the server context; the client hook calls nativeRelease. + +```iecst +METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `bInCopyCode` | VAR_INPUT | `BOOL` | Runtime lifecycle copy-code indicator; current implementation does not branch on it. | + +## ModbusSlave.FB_reinit + +CODESYS lifecycle hook clearing native handles and in-flight state. ClientBase also clears the request registry for re-registration; RequestBase clears execution/completion flags; servers clear their handle. These hooks alone do not establish qualified online-change support. + +```iecst +METHOD FB_reinit : BOOL +``` + +## ModbusSlave.serverCall + +Private adapter for native server commands. Uses separate server status and WORD staging buffers. Returns the command result; 0 means command accepted, not necessarily that the listener has bound. + +```iecst +METHOD PRIVATE serverCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `command` | VAR_INPUT | `UDINT` | Native command selector. Use wrappers in application code. | + +## SlaveTCP.FB_exit + +CODESYS lifecycle hook releasing owned native resources. Applications should use release/recreateClient rather than call lifecycle hooks manually. Server hooks close the server context; the client hook calls nativeRelease. + +```iecst +METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `bInCopyCode` | VAR_INPUT | `BOOL` | Runtime lifecycle copy-code indicator; current implementation does not branch on it. | + +## SlaveTCP.FB_reinit + +CODESYS lifecycle hook clearing native handles and in-flight state. ClientBase also clears the request registry for re-registration; RequestBase clears execution/completion flags; servers clear their handle. These hooks alone do not establish qualified online-change support. + +```iecst +METHOD FB_reinit : BOOL +``` + +## SlaveTCP.serverCall + +Private adapter for native server commands. Uses separate server status and WORD staging buffers. Returns the command result; 0 means command accepted, not necessarily that the listener has bound. + +```iecst +METHOD PRIVATE serverCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR +``` + +| Member | Direction | Declaration | Meaning | +|---|---|---|---| +| `command` | VAR_INPUT | `UDINT` | Native command selector. Use wrappers in application code. | + +## activeIndex.Get + +Get accessor for activeIndex; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst +VAR + requestIndex: DINT; //Loop variable +END_VAR +``` + +## activeIndex.Set + +Set accessor for activeIndex; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## activeReqCnt.Get + +Get accessor for activeReqCnt; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst +VAR + requestIndex : DINT; //Loop variable + cnt: UINT; +END_VAR +``` + +## allRequestsComplete.Get + +Get accessor for allRequestsComplete; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst +VAR + requestIndex : DINT; +END_VAR +``` + +## onlineCnt.Get + +Get accessor for onlineCnt; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst +VAR + requestIndex : DINT; //Loop variable + cnt: UINT; +END_VAR +``` + +## requestExecuting.Get + +Get accessor for requestExecuting; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## toString.Get + +Get accessor for toString; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## replyTimeout.Get + +Get accessor for replyTimeout; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## execute.Get + +Get accessor for execute; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## execute.Set + +Set accessor for execute; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## reqBusy.Get + +Get accessor for reqBusy; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## reqBusy.Set + +Set accessor for reqBusy; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## reqDone.Get + +Get accessor for reqDone; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## reqDone.Set + +Set accessor for reqDone; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## reqError.Get + +Get accessor for reqError; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + +## reqError.Set + +Set accessor for reqError; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor. + +```iecst + +``` + diff --git a/docs/BUILD-AND-USAGE.md b/docs/BUILD-AND-USAGE.md new file mode 100644 index 0000000..43929eb --- /dev/null +++ b/docs/BUILD-AND-USAGE.md @@ -0,0 +1,203 @@ +# atMb native TCP + RTU + TCP slave — development revision 0.9 + +The IEC export and C implementation have no ModbusFB dependency. All socket/ +serial/frame processing runs in the native component. Public namespace: atMb. +Standard and SysTime are still required for IEC timers and time measurement. + +This is a tested host development build, not a compiled CODESYS library or a +hardware-qualified Pi release. Full CODESYS compilation and physical RTU testing +remain required. Online-change ownership/pointer relocation is future work. + +## Rebuild on the Pi (required) + +Extract this bundle on the Pi, preserving component-source and ExtensionSDK +as siblings. Install build-essential, m4, dos2unix, make and python3 if absent. + +```sh +cd component-source +make -C component SDKDIR="$(realpath ../ExtensionSDK)" clean +make -C component SDKDIR="$(realpath ../ExtensionSDK)" all +file component/out/libCmpAtomModbus.so +make test +``` + +The component registration version is now 0.3.0.0. The external atMbCall_cext +function ABI, signature 0x709EE65B and generated API version 0.2.0.0 are unchanged. +No regeneration of runtime interface files is required for these extra commands. +Keep the supplied generated interface; do not regenerate blindly from a changed +library title/version. No prebuilt Pi binary is included. A strict host compile +of the component function used the supplied SDK headers and original real +parameter struct; the full SDK/M4/component frame build must run on your Pi. + +## Build the IEC library without legacy objects + +1. Create a fresh library project saved as CmpAtomModbus.library (or a backup copy + with the old Modbus master/slave folders removed). Import atMb-Native.export. + A fresh project is preferred because import does not delete omitted old methods. +2. Keep Project Information title CmpAtomModbus and default namespace atMb, as + supplied in the export. This is one combined library, not a wrapper referencing + a second CmpAtomModbus/atMb library. Remove those self/wrapper references. +3. Add only Standard and SysTime references needed by this IEC code. Remove the + ModbusFB reference. Resolve their ordinary dependencies if the IDE requests it. +4. Add a C Implemented Library object and embed your NEWLY BUILT ARM64 + component/out/libCmpAtomModbus.so for the exact Raspberry Pi 64 SL target. + Use CmpAtomModbus for the component name. The old TCP-only embedded object was + deliberately omitted from this export; do not reuse its binary. +5. Compile, save/install and update references in the Atom library/application. +6. Perform a full download. Verify runtime loads the new component version. + +For automatic deployment, stop using the old manually preloaded component +before testing the embedded one: remove only its ComponentManager entry, then +restart the runtime during maintenance. Alternatively, for a manual bench test, +stop the runtime and replace /usr/lib/libCmpAtomModbus.so with the new binary. +Inspect /var/log/codesyscontrol.log. No production install is performed by this kit. + +## TCP client + +Existing atMb.Client and ReqReadHolding/ReqReadInput/ReqReadCoil/ReqReadDiscrete/ +ReqWriteHolding/ReqWriteCoil calls remain. Supported C function codes are +1,2,3,4,5,6,15,16. Automatic retry continues while enabled; normal Modbus +exception replies do not force reconnects. The default retry delay is 100 ms. + +## Native RTU + +```iecst +VAR + bus : atMb.ClientRTU; + read : atMb.ReqReadHolding; + registers : ARRAY[0..9] OF UINT; +END_VAR +``` + +```iecst +bus( + in_device := '/dev/ttyUSB0', + in_baudRate := 9600, + in_parity := 0, + in_dataBits := 8, + in_stopBits := 1, + in_timeout := T#500MS, + in_reconnectDelay := T#100MS +); +read(client := bus, in_unitId := 1, in_startAddr := 0, + in_quantity := 10, in_dataOffset := 0, in_minDelay := T#100MS, + data := registers); +``` + +Call client/requests every scan in ONE task. One RTU client owns each serial bus; +register different unit IDs on that same client, rather than opening multiple +clients on the same tty. The native open uses exclusive serial access. + +Use /dev/serial/by-id/... when it fits the 127-byte path limit, or a stable +short udev symlink. The runtime OS user must have access to the device; check the +actual service user and device group on your Pi. Stop other serial consumers. +The old numeric in_port selection was removed; a numeric index is not a portable +Linux device mapping. in_baudRate is now UDINT so 115200 fits. + +Supported baud rates: 1200,2400,4800,9600,19200,38400,57600,115200. +RTU uses 8 data bits; parity 0 none/1 odd/2 even; stop bits 1 or 2. +Unit IDs 1..247 are accepted. Broadcast unit 0 is explicitly rejected because +broadcast completion cannot be reported as an acknowledged write. + +For automatic-direction USB RS485 adapters leave in_rs485 FALSE. TRUE requests +Linux TIOCSRS485 with RTS during transmit; unsupported drivers report an OS error. +No software GPIO direction control or adapter-specific configuration is included. + +CRC, unit/function, length and write echo are validated before accepting replies. +Faults close/reopen the tty after the configured retry delay. No raw write packet +is replayed by the engine. The existing scheduler can issue fresh cyclic writes; +use in_holdUncertainWrites TRUE for commands requiring an application decision. +Delayed serial replies have no transaction ID: use suitable timeout/retry settings +for slow devices. Serial silence is conservatively scheduled in milliseconds; +physical adapter/baud/parity/line-turnaround tests are still necessary. PTY tests +cannot certify electrical timing, parity generation or RS485 direction behavior. + +out_connected on RTU means the tty is open/ready, not proof that a remote unit +responds. Use per-request online/stale/error status. out_socketError carries the +OS errno for tty failures too. All existing client status counters are shared. + +## Native TCP slave + +atMb.ModbusSlave and atMb.SlaveTCP both run the C server (no ModbusFB server). +Map four BOOL/UINT arrays using aDiscreteInputsMemory/aCoilsMemory/ +aInputRegistersMemory/aHoldingRegistersMemory. Their actual LOWER_BOUND is their +zero-based protocol starting address; each table is limited to 2000 items and +must fit within addresses 0..65535. A full 65536-item table is not supported. + +Use in_ipAddress DWORD = 0 to bind all addresses, or numeric IPv4 in human octet +order (16#7F000001 for 127.0.0.1). port defaults to 502. The legacy interfaceName +field is retained but must be empty; numeric bind IP replaces interface-name +binding. Nonempty names are reported as parameter errors rather than ignored. + +Example: `slave : atMb.ModbusSlave;` with all four arrays passed every scan: + +```iecst +slave(in_enable := TRUE, in_ipAddress := DWORD#0, port := 502, + aDiscreteInputsMemory := discreteInputs, + aCoilsMemory := coils, + aInputRegistersMemory := inputRegisters, + aHoldingRegistersMemory := holdingRegisters); +``` + +FC1/2/3/4/5/6/15/16 are implemented, with exception replies for unsupported +functions, invalid quantities or unmapped addresses. Four server contexts, +eight simultaneous peers per server. Requests are bounded and processed in +order per connection. A server tick accepts at most one peer and performs at +most one nonblocking I/O action per peer per IEC call. Incomplete frames/send +stall timeout after 5 seconds; idle peers close after 60 seconds. + +The wrapper pushes PLC table snapshots, ticks the engine, then pulls completed +remote writes in the same scan. C retains no PLC pointers between calls. Application +writes between calls become the next snapshot. Arrange application order accordingly. +The fixed server limits and scan-bounded throughput differ from the old ModbusFB +server; load test your actual client count/polling rates. No RTU slave is provided. + +in_enable controls listening, in_reset recreates the context, endpoint changes +recreate it, and listener failures retry automatically after in_reconnectDelay. +Outputs: out_running/out_error/out_errorId/out_parameterError/out_socketError, +out_connectedClients/out_requestsReceived/out_repliesSent/out_exceptionReplies. + +## Native lifecycle and API + +All bridge/server contexts are fixed-pool, generation-checked and single-task. +No RETAIN/PERSISTENT handles. release() disables client enable and closes its +context. STOP alone does not guarantee FB_exit: disable and scan before STOP. +New full download required; online-change safety is not claimed in this revision. + +Own Error and ExceptionCodes DUTs replace the ModbusFB enums. Error retains the +named compatibility labels used by request statistics. Legacy dynamically +allocated request methods are removed or become native recreateRequest BOOL. +Old allocation-alarm outputs/legacy request reset fields were removed because +these contexts use fixed native pools. A pool-full result is native error 257. + +Command 20: open RTU. data contains one device-path ASCII byte per WORD, quantity +is path length; ipAddress=baud; unitId=parity; functionCode=bits; +startAddress=stop bits; connectTimeoutMs=kernel RS485 flag (0/1). +The existing commands 2..11 configure/tick/submit/close this client normally. +Commands 30..36: server open/close/enable/disable/tick/push/pull. Push/pull use +functionCode as table 0 coils/1 discrete/2 holding/3 input, startAddress as base +and quantity as length. Server handles use distinct low-byte tags from clients. +Server status indices: 0 handle,1 running,2 error,3 result,4 OS errno,5 peers, +6 requests,7 replies,8 exception replies. Client status layout is unchanged. + +## Tests and remaining checks + +Run make test on Linux. Includes existing nine TCP scenarios directly and through +the bridge, pool/handle tests, PTY RTU read/fragment/CRC/timeout/recovery/exception/ +register-write/coil-write/invalid-write-echo recovery tests and native slave FC1/2/3/4/5/6/15/16, nonzero mapping, +exception/listener-recovery/stale-handle tests. All passed on the development host. +The CODESYS XML has no ModbusFB identifiers and passes structural checks. It has +not been compiled in the IDE; hardware Pi/serial and embedded deployment tests +remain. Do not interpret host tests as target or online-change qualification. + + +v0.9.1 export correction +------------------------ +Restores ClientBase public diagnostics: msgLastSecondCnt, msgLastSecondTime, +numMsgSent, numMsgReply, allRequestsOnline and reqStats. The generator now +checks these declarations and balanced VAR sections. Replace matching objects +when importing the corrected export. C sources are unchanged from v0.9; this +correction alone does not require rebuilding an already installed v0.9 component. +The full native migration still requires the C build and embedding described above. +Validated XML and declaration checks locally; CODESYS compilation must be checked +in the IDE. diff --git a/docs/DOCUMENTATION-VALIDATION.md b/docs/DOCUMENTATION-VALIDATION.md new file mode 100644 index 0000000..8a636a7 --- /dev/null +++ b/docs/DOCUMENTATION-VALIDATION.md @@ -0,0 +1,11 @@ +# Documentation validation + +Documented 95 code objects. + +- All interface executable tokens unchanged after removing comments/whitespace. +- All XML content outside Interface text identical after parse/serialization. +- Implementations, GUIDs, native payloads, project information and references unchanged. +- Documented XML parses successfully. +- No CODESYS IDE compile performed; this is a comments-only change. + +Input SHA256: `5a07da11853e955633a977bdd59a7d1f8f292c594c2116bc220bd63c8edc9329` diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md new file mode 100644 index 0000000..806fdb0 --- /dev/null +++ b/docs/USER-GUIDE.md @@ -0,0 +1,216 @@ +# atMb usage and behavior + +This guide describes the supplied working export, including its renamed `ClientTCP`. The documented export changes comments only and retains the embedded C Implemented Library object. Import with replacement of matching objects into your existing library project. No C rebuild is needed for these documentation changes. + +## Choosing the block + +| Application | Block | Data | +|---|---|---| +| TCP master/client | `atMb.ClientTCP` | IPv4 byte array and TCP port | +| RTU master/client | `atMb.ClientRTU` | Linux serial device and serial settings | +| Read coils, FC1 | `atMb.ReqReadCoil` | BOOL array | +| Read discrete inputs, FC2 | `atMb.ReqReadDiscrete` | BOOL array | +| Read holding registers, FC3 | `atMb.ReqReadHolding` | UINT array | +| Read input registers, FC4 | `atMb.ReqReadInput` | UINT array | +| Write coils, FC15 | `atMb.ReqWriteCoil` | BOOL array | +| Write holding registers, FC16 | `atMb.ReqWriteHolding` | UINT array | +| TCP slave/server | `atMb.SlaveTCP` or `atMb.ModbusSlave` | Four memory arrays | + +The two slave blocks have identical code/interfaces. They do not provide RTU slave operation. `ClientBase` and `RequestBase` are abstract and are not instantiated directly. + +## TCP read example + +Declarations: + +```iecst +VAR + mbClient : atMb.ClientTCP; + readRegisters : atMb.ReqReadHolding; + registers : ARRAY[0..9] OF UINT; + dataUsable : BOOL; +END_VAR +``` + +Cyclic body: + +```iecst +mbClient( + in_ipAddress := atMb.To_IPAddress(192,168,50,10), + in_port := 502, + in_enable := TRUE, + in_connectTimeout := T#2S, + in_reconnectDelay := T#100MS, + in_timeout := T#500MS); + +readRegisters( + client := mbClient, + in_unitId := 1, + in_startAddr := 0, + in_quantity := 10, + in_dataOffset := 0, + in_minDelay := T#100MS, + in_staleAfter := T#2S, + data := registers); + +dataUsable := readRegisters.out_validData AND NOT readRegisters.out_stale; +``` + +Call the client and each request every scan from one IEC task. On the first scan the client initializes its native mode, and the request registers afterwards; polling can begin on a subsequent scan. The request body binds the current array and usable count; the client dispatches the request method. No separate execute trigger is required for cyclic polling. + +Keep instances and arrays stable. Avoid temporarily allocated request objects or sharing native contexts across tasks. Multiple requests can share a client; they are serialized. The supplied C implementation has 16 client contexts shared between TCP/RTU, 100 registrations per IEC client, and one in-flight transaction per client. + +## Addresses and lengths + +Protocol addresses are zero-based: a manual describing its first holding register as 40001 often maps to `in_startAddr := 0`. Confirm the device's mapping; do not pass 40001 automatically. + +`in_dataOffset` is the actual array index. With `ARRAY[1..10]`, use 1 to access the first element. `in_quantity` is clamped to the remaining array capacity rather than always rejected. Check `stats.reqLength` for the effective count. + +Limits per transaction: FC1/2 2000 bits, FC3/4 125 registers, FC15 1968 coils, FC16 123 registers. A one-element write still uses FC15/16 in the public write blocks. Register values are unsigned 16-bit words; converting floats, signed values or multi-register values and selecting word order are application responsibilities. + +## RTU example + +Use the same requests with this client: + +```iecst +VAR + serialClient : atMb.ClientRTU; + readRegisters : atMb.ReqReadHolding; + registers : ARRAY[0..9] OF UINT; +END_VAR +``` + +```iecst +serialClient( + in_device := '/dev/ttyUSB0', + in_baudRate := 9600, + in_parity := 0, + in_dataBits := 8, + in_stopBits := 1, + in_rs485 := FALSE, + in_enable := TRUE, + in_timeout := T#500MS, + in_reconnectDelay := T#100MS); + +readRegisters(client := serialClient, in_unitId := 1, + in_startAddr := 0, in_quantity := 10, in_dataOffset := 0, + data := registers); +``` + +Set RTU unit ID explicitly to 1..247; the inherited request default is 0 and broadcast is unsupported. The Linux runtime account needs serial device permissions. Use a stable device path where available. `in_rs485` requests kernel-managed direction control and requires driver support; USB adapters that control direction automatically generally use FALSE. Serial-port configuration must match the device. + +The supplied native source supports 1200/2400/4800/9600/19200/38400/57600/115200 baud, 8 bits, parity none/odd/even and one/two stop bits. Timing uses nonblocking I/O and scan-driven millisecond deadlines; physical adapter timing must be verified on the target hardware. + +## Polling and priority + +Requests become active when their effective length is nonzero. They register automatically and poll while the client is enabled. `in_quantity := 0` makes a registered request inactive; it does not remove it from the registry. + +`in_minDelay` controls eligibility by age since the previous start. It is not a guaranteed polling period. Lowest `in_priorityCurrent` wins, and age breaks equal-priority ties. On success, current priority is restored to `in_prioritySetpoint`. On error it becomes `10 * MIN(consecutive errors,3)^2`: 10, 40, then 90. The current scheduler does not require priority to reach zero before submitting. Avoid overwriting current priority every scan unless that is intended. + +## Writes and uncertainty + +Write arrays are copied at submission, so changes made afterwards affect a later transaction. An active write request is cyclic, just like a read; `writeNow()` is a priority/hold-release operation, not a single-shot write API. + +When a connection fails after a write may have been transmitted, `out_writeUncertain` means the target could already have applied it. With `in_holdUncertainWrites := FALSE`, normal scheduling can retry. With TRUE, that write request is held until its `writeNow()` is called. Other eligible requests can continue. + +```iecst +// On an application-controlled event, after preparing the source array: +writeRegisters.writeNow(); +``` + +This clears the hold/uncertain output and sets priority 0. The client must still be enabled/connected, the request active and its minimum-delay condition satisfied. Confirm target behavior before retrying commands whose effects should occur only once. + +## Timeouts and reconnects + +Reply timeout precedence: + +1. Request `in_timeout`: SYSTIME numeric milliseconds, when nonzero. +2. Client `in_timeout`: TIME, when nonzero. +3. Calculated timeout using client dataRate. +4. A one-second fallback if dataRate is zero. + +The internal property calculates microseconds; submission rounds up to milliseconds with a minimum of 1 ms. TCP defaults its dataRate hint to 105200; the calculated timeout is approximately 38 ms. RTU uses its baud rate; at 9600 the formula gives approximately 411 ms. Explicit client/request timeouts make behavior easier to control. + +`in_connectTimeout` is the TCP connection deadline, separate from reply timeout. `in_reconnectDelay` defaults to 100 ms. It is the recovery wait after detecting failure, not a guarantee of recovery within 100 ms: scan time, connection timeout, reply timeout, OS behavior and target readiness also matter. + +Endpoint/configuration changes release and recreate the native context. `recreateClient()` forces this process; it does not permanently disable the client. `release()` also sets `in_enable := FALSE`. To restart, set TRUE and continue scanning. Supplying TRUE as a call parameter every scan will override that disabled input on the next call. + +## Data quality and statistics + +| Diagnostic | Meaning | +|---|---| +| Client `out_connected` | Native transport is ready/sending/receiving | +| Request `stats.online` | Valid response evidence, including Modbus exceptions | +| Request `out_validData` | A successful read has populated data since invalidation | +| Request `out_stale` | No valid data, or last successful read is older than threshold | +| Request `out_dataAgeMs` | Age of successful read data | +| Request `out_nativeError` | Last native request result | +| Request `stats.lastErrorId` | Mapped IEC error classification | +| Request `stats.lastExceptionCode` | Modbus exception or local statistics classification | +| `out_socketError` | OS errno snapshot for socket/serial failure | + +These represent different conditions. Exception replies can make communication online while the read fails. Read failures retain old values and may retain `out_validData`; use freshness as well. Releasing the client marks cached data invalid but does not erase arrays. Some completion paths return before updating age, so freshness outputs can refresh on the following scan. + +`reqDone`/`reqError` are internal completion flags; the light outputs use a 100 ms off-delay for monitoring. `timeoutCnt` currently counts non-exception errors that statistics classify as TIMEOUT, not only native reply deadlines. Prefer native result 6 for a precise reply timeout. + +Client `resetStats()` resets IEC send/reply/window counters and each request's four success/error counters. Request `stats.resetStats()` resets only `doneCnt`, `doneCntRst`, `errorCnt`, `errorCntRst`. Durations, timestamps, offline count, timeout count and native reconnect/errno history are retained. `in_resetStats` is consumed and cleared by the client; providing TRUE every scan keeps resetting it. + +## TCP slave example + +```iecst +VAR + server : atMb.SlaveTCP; + coils : ARRAY[0..15] OF BOOL; + discreteInputs : ARRAY[0..15] OF BOOL; + holding : ARRAY[0..31] OF UINT; + inputs : ARRAY[0..31] OF UINT; +END_VAR +``` + +```iecst +// Publish application inputs before the server call. +inputs[0] := 123; +server(in_enable := TRUE, in_ipAddress := DWORD#0, port := 1502, + aCoilsMemory := coils, + aDiscreteInputsMemory := discreteInputs, + aHoldingRegistersMemory := holding, + aInputRegistersMemory := inputs); +// Consume network-written coils/holding values after this call. +``` + +Port 1502 is an example; select the application's port. Keep `interfaceName` empty. `in_ipAddress := 0` binds all interfaces. A specific bind IPv4 is packed into DWORD, e.g. `DWORD#16#C0A8321F` for 192.168.50.31. + +Each table has independent addresses. `ARRAY[100..109] OF UINT` maps addresses 100..109, not 0..9. All tables must use nonnegative bounds within 0..65535 and at most 2000 elements. The current API requires four arrays; a small placeholder can represent an otherwise unused table. + +The wrapper snapshots all four application tables into native memory, processes bounded network work, then copies the tables back. Network writes affect coils and holding registers. Avoid overwriting externally controlled values unconditionally in application code each scan unless that override is intentional. The supplied C engine supports four server contexts and eight peers per server. Server contexts are separate from the 16-client pool. + +`out_running` means the listener is bound; `out_connectedClients` indicates peers. `in_reset` is consumed and cleared; a constant TRUE recreates the server every scan. Listener bind failures retry while enabled. Counters reset with server context recreation. Some early-return paths leave prior outputs unchanged; interpret diagnostic outputs together. + +## Native result codes and client states + +| Result | Meaning | +|---|---| +| 0 | Success | +| 1 | Invalid argument | +| 2 | Native client busy | +| 3 | Client offline/not ready | +| 4 | Socket/serial OS error | +| 5 | TCP connection deadline | +| 6 | Reply deadline | +| 7 | Peer closed | +| 8 | Invalid protocol/frame/reply | +| 9 | Modbus exception response | +| 10 | Cancelled | +| 256 | Invalid/stale native handle | +| 257 | Native pool full | +| 258 | Insufficient/invalid buffer capacity | +| 259 | Unknown command | + +These are distinct from the sparse IEC `Error` enum. IEC mapping combines 4/7 into `TcpSocketError`, 1/258 into `RequestParameterError`; other unmapped results become `InternalError`. TCP names also classify RTU failures. + +States: 0 disabled, 1 retry wait, 2 connecting, 3 ready, 4 sending, 5 receiving. + +## Maintenance and scope + +The documented export preserves the working implementations, metadata and embedded payloads. Internal native fields are sometimes exposed as VAR_INPUT for compatibility; leave them under library control. Several legacy BOOL methods do not assign a return value; the API reference identifies them so callers rely on their side effects. + +Lifecycle hooks exist, but reliable online-change ownership and pointer relocation remain future work. Documentation adds no behavior changes or online-change guarantee. Use the API reference for every method/property, including internal accessors. diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md new file mode 100644 index 0000000..322a86b --- /dev/null +++ b/docs/VALIDATION.md @@ -0,0 +1,14 @@ +# Snapshot validation + +- Export generator completed: 99 objects, no ModbusFB references. +- Public diagnostics and balanced VAR declaration checks passed. +- Strict host C compilation passed. +- Bridge pool/stale handle/configuration/shutdown tests passed. +- Nine TCP scenarios passed directly and through the native bridge. +- RTU pseudo-terminal tests passed, including CRC/timeout recovery, partial frames, + writes, exceptions and invalid write echo recovery. +- TCP slave FC 1/2/3/4/5/6/15/16, exception, nonzero base, listener recovery + and stale handle tests passed. + +No CODESYS IDE compile, full ARM64 SDK build, physical serial hardware test, +or online-change qualification was performed for this repository package. diff --git a/docs/document_export.py b/docs/document_export.py new file mode 100644 index 0000000..92b6be8 --- /dev/null +++ b/docs/document_export.py @@ -0,0 +1,194 @@ +from pathlib import Path +import xml.etree.ElementTree as E +import re,hashlib,json,zipfile +B=Path(__file__).resolve().parent +src=B.parent/'reference/Working-CmpAtomModbus.export' +r=E.parse(src).getroot();entries=list(r.find('.//List2[@Name="EntryList"]')) +def meta(e,n):return e.find('./Single[@Name="MetaObject"]/Single[@Name="'+n+'"]') +names={meta(e,'Guid').text:meta(e,'Name').text for e in entries} +def path(e): + n=meta(e,'Name').text;p=e.find('./Single[@Name="ParentSVNodeGuid"]').text + return names.get(p,''),n +def blob(e,p):return e.find('./Single[@Name="Object"]/Single[@Name="'+p+'"]//Single[@Name="TextBlobForSerialisation"]') +summary={ +'ClientBase':'Abstract shared client scheduler and native transport owner. Use ClientTCP or ClientRTU. Holds up to 100 registered requests and runs one transaction at a time. Call the concrete client once per scan, from the same task as its requests.', +'ClientTCP':'Modbus TCP client with automatic retry after connection or transaction failure. Call every scan. Endpoint or timeout/retry configuration changes close the current context and reopen it on subsequent scans. Requests share this connection and execute sequentially.', +'ClientRTU':'Modbus RTU client using a Linux serial device. Call every scan. Supports 8 data bits, parity 0 none/1 odd/2 even, and 1 or 2 stop bits. Uses unit IDs 1..247; broadcast 0 is unsupported. Serial-setting changes recreate the context. Automatic recovery proceeds while enabled and scanned.', +'RequestBase':'Abstract request binding, scheduling and diagnostics. Derived requests register automatically when called with a valid initialized client. Call each instance every scan with stable array storage. Quantity zero disables this request. Actual length is MIN(in_quantity, remaining array capacity).', +'RequestStats':'Request counters, completion properties, online/offline tracking and 100 ms indicator lights. Normally maintained by RequestBase. Online means a valid response was received, including a Modbus exception; it does not guarantee successful data. Several fields are declared VAR_INPUT for compatibility but are maintained internally.', +'To_IPAddress':'Returns the four supplied bytes as ARRAY[0..3] OF BYTE. Example: atMb.To_IPAddress(192,168,50,10). This is the ClientTCP address format; the slave bind address instead uses a packed DWORD.', +'ModbusSlave':'Native Modbus TCP server. Same implementation and interface as SlaveTCP. Call once per scan with all four memory arrays. Array bounds are protocol addresses; each table is limited to 2000 elements and addresses 0..65535. Pushes application memory, performs bounded network processing, then copies native memory back. This is TCP only, not an RTU slave.', +'SlaveTCP':'Native Modbus TCP server with four separate memory tables. Same implementation and interface as ModbusSlave. FC 1/2/3/4 read the tables; FC 5/6/15/16 write coils/holding registers. Array lower bounds define Modbus addresses. Call every scan and retain array storage. This is TCP only.', +'ClientStatus':'Aggregate request status used by the RTU monitoring property: noneOk=0, someOk=1, allOk=2. This describes request online flags, not the physical connection. The current toString getter compares onlineCnt with all registered requests and reports allOk for an empty registry.', +'Error':'IEC error classification. TCP-named socket/timeout members are also used for RTU failures. For the precise native reason inspect out_nativeError/out_lastNativeError; for an OS failure inspect out_socketError.', +'ExceptionCodes':'Modbus exception response values plus local TIMEOUT=-1. A valid Modbus exception proves communication, but the requested operation failed. In current statistics, non-exception errors can also be classified as TIMEOUT; use native diagnostics to distinguish their actual cause.', +'atMbCall_cext':'External C ABI entry point used by the wrappers. Application code should normally call the function blocks. Do not change parameter order/types or external implementation settings. Pointers are accessed synchronously by C; provide distinct valid data/status buffers and truthful capacities. Client status requires 24 UDINT elements. All calls must be owned by one IEC task.', +'addRequest':'Register a non-null RequestBase pointer once. Duplicate pointers are ignored. Capacity is 100 requests per client; overflow sets out_registrationFull. No removal API is provided. Normally called automatically by the request FB.', +'nativeCall':'Internal adapter to atMbCall_cext. Command 20 packs the ASCII serial path into WORD storage and passes serial settings in ABI fields; other commands use normal client arguments and status buffers. Return is the native command result, 0 for success.', +'nativeCycle':'Internal per-scan engine: acquire context, tick nonblocking I/O, update diagnostics, copy completed read data, process registered requests, acknowledge completion, select the next request, and publish statistics. Invalid native handles are discarded and reopened on a later scan.', +'nativeEnsureContext':'Acquire/configure/enable a native context if absent. Returns TRUE when a handle exists or initialization succeeds. A handle does not mean connected; check out_connected. Failure records a native error and closes partially initialized contexts.', +'nativeRelease':'Close the native context, complete any active request as cancelled, clear selection, set registered requests offline and invalidate cached read data. Returns TRUE. It does not set in_enable FALSE; an enabled scanned client can reopen.', +'nativeSelectNextRequest':'Select an active, unheld request that meets in_minDelay and was not just completed this scan. Lowest in_priorityCurrent wins; oldest sinceLastExec breaks ties. Positive priorities are decremented when the best selected priority is positive. The selected request is submitted immediately; zero priority is not a prerequisite for execution.', +'nativeUpdateDiagnostics':'Publish native connection state, last error, errno and reconnect count. States 3..5 count as connected. A connected-to-disconnected transition marks registered requests offline.', +'nativeUpdateStatistics':'Publish IEC send/reply counters, registered statistics pointers and active-request aggregate online flags. Every elapsed window of at least 1000 ms publishes submitted-request count and successful transaction-duration sum, then restarts the window.', +'processAllRequests':'Internal loop over registered non-null requests, dispatching processRequest. Returns immediately for an empty or invalid registry. Usually invoked by nativeCycle rather than application code.', +'recreateClient':'Close/release the current context so the next enabled scan creates a fresh one with current settings. Cancels an active transaction and invalidates cached read data. Abstract on ClientBase; implemented by both concrete clients.', +'release':'Stop this client by setting in_enable FALSE, then release its native context. Returns the release result. To resume, set in_enable TRUE and keep calling the client. A call that supplies in_enable := TRUE every scan will re-enable it.', +'setAllRequestsOffline':'Internal helper marking every registered request offline via its statistics FB. Returns TRUE after processing a nonempty valid registry; early return does not explicitly assign the return value.', +'setupClient':'Returns whether nativeHandle is nonzero. It does not create a context or guarantee transport connectivity. Normal FB scanning performs initialization.', +'activeIndex':'Get the current active request registry index, or -1 when idle. Setting a valid index sets that request stats.execute TRUE; it does not bypass native scheduler selection or directly submit a transaction.', +'activeReqCnt':'Private count of registered requests whose stats.active is TRUE. Zero quantity/usable length requests are inactive.', +'allRequestsComplete':'Private legacy completion check. TRUE only if none of the registered requests has execute, reqDone, reqBusy or reqError set. Empty registry returns FALSE. This is not a network completion pulse.', +'onlineCnt':'Count registered requests with stats.online TRUE. Includes any registered request with that flag; used for aggregate reporting.', +'requestExecuting':'Private property returning activeIndex <> -1. Represents selected/active request state maintained by the scheduler.', +'toString':'ClientRTU monitoring property returning ClientStatus based on onlineCnt versus requestCnt. Reports allOk for an empty registry. RequestStats also has an internal STRING field of this name; these are different values.', +'busy':'Assign stats.reqBusy from input in. Internal request helper. Although declared BOOL, its implementation does not assign a return value; use the side effect rather than the return.', +'done':'Set stats.reqDone. When in is TRUE, restore in_priorityCurrent to in_prioritySetpoint for the next scheduler cycle. Successful completion counters are maintained by the stats property setter.', +'err':'Set stats.reqError and, on TRUE, record errorId/exceptionCode. Set next priority to 10 * MIN(consecutive errors,3)^2. Lower numeric priority wins, so this backs off repeated failures relative to lower-priority-number requests.', +'invalidateNativeData':'Mark cached data invalid and stale. Does not erase the application array. Called during client release; applications should gate use of cached values with validity and age flags.', +'nativeFinish':'Complete a request from native result. Map errors, copy errno/write uncertainty, clear busy, update response/counters and successful-read freshness. Modbus exceptions count as replies and communication-online evidence. If enabled, uncertain writes are held pending writeNow().', +'nativeProcess':'Shared request processing called by derived processRequest. Completes native results or submits a selected request, sets its timeout and records busy/send counters. Updates data age and stale flags during ordinary processing. Completion branches can return before age refresh, so allow a subsequent scan for freshness diagnostics.', +'replyTimeout':'Protected timeout property expressed internally in microseconds. Request in_timeout is milliseconds and takes precedence, then client TIME in_timeout, then a dataRate formula, then 1 second fallback. Submission rounds up to milliseconds and clamps to at least 1 ms.', +'setClientOffline':'Clear nativeResponseSeen and consecutive-success count, force online FALSE/offline TRUE. Does not clear data arrays. Declared BOOL but does not assign a return value.', +'setToString':'Private statistics helper formatting OK:/Er: into STRING(20). Declared BOOL but does not assign a return value.', +'execute':'Statistics execution property. Setting TRUE starts the execution state only when reqDone and reqError are FALSE; otherwise the setter clears execution. Usually controlled by the scheduler.', +'reqBusy':'Get/set the underlying request-busy flag. Normally managed by the native wrapper. The scan-held reqBusyLight provides a more visible monitoring indicator.', +'reqDone':'Get/set completion flag. When TRUE during execution, record duration, increment total/consecutive successes, clear consecutive errors and last error, clear execution and update the monitor string. Do not repeatedly drive this manually.', +'reqError':'Get/set error flag. When TRUE during execution, increment total/consecutive errors, clear consecutive successes and execution. If lastExceptionCode is success, increment timeoutCnt and set TIMEOUT even for some non-timeout native errors.', +'recreateRequest':'Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport.', +'writeNow':'Clear uncertain-write hold and out_writeUncertain, then set in_priorityCurrent to 0. Returns TRUE. Requests a prompt opportunity at the next scheduler selection; does not guarantee transmission in this call and does not bypass connection/minimum-delay requirements.', +'serverCall':'Private adapter for native server commands. Uses separate server status and WORD staging buffers. Returns the command result; 0 means command accepted, not necessarily that the listener has bound.', +'FB_exit':'CODESYS lifecycle hook releasing owned native resources. Applications should use release/recreateClient rather than call lifecycle hooks manually. Server hooks close the server context; the client hook calls nativeRelease.', +'FB_reinit':'CODESYS lifecycle hook clearing native handles and in-flight state. ClientBase also clears the request registry for re-registration; RequestBase clears execution/completion flags; servers clear their handle. These hooks alone do not establish qualified online-change support.'} +for name,fc,kind,limit in [('ReqReadCoil',1,'BOOL coils',2000),('ReqReadDiscrete',2,'BOOL discrete inputs',2000),('ReqReadHolding',3,'UINT holding registers',125),('ReqReadInput',4,'UINT input registers',125),('ReqWriteCoil',15,'BOOL coils',1968),('ReqWriteHolding',16,'UINT holding registers',123)]: + summary[name]=f'Modbus FC {fc}: '+('write ' if fc>=15 else 'read ')+f'{kind}. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is {limit}. Usable length is clamped to array capacity. '+('Values are copied into native storage at submission. This FB uses the multiple-write FC even for one element. Use writeNow() to release a held uncertain write.' if fc>=15 else 'Only a successful validated reply updates the destination slice. Old values remain after failures; check out_validData and out_stale.') +fields={ +'in_enable':'Enable processing; TRUE by default. FALSE releases transport. Continue scanning to refresh state.', +'in_timeout':'Reply timeout override. ClientBase: TIME; RequestBase: SYSTIME numeric milliseconds (not microseconds). Zero selects inherited/calculated timeout.', +'in_resetStats':'Reset request consumed and cleared by nativeCycle. Supplying TRUE on every call resets statistics every scan; pulse once.', +'in_ipAddress':'ClientTCP: four IPv4 bytes. Slave: packed numeric DWORD, 0 binds all interfaces; e.g. DWORD#16#C0A8321F for 192.168.50.31.', +'in_port':'Remote TCP port, default 502.', 'in_baudrate':'TCP gateway serial baud-rate hint for calculated reply timeout; 0 uses internal dataRate 105200. Does not configure a serial port.', +'in_connectTimeout':'TCP connection-attempt deadline, default 2 s; clamped to at least 1 ms.', +'in_reconnectDelay':'Delay before native recovery attempt, default 100 ms; clamped to at least 1 ms. Effective timing also depends on scan interval and failure detection.', +'in_holdUncertainWrites':'FALSE allows normal retries after uncertain writes. TRUE holds affected write requests until writeNow(); read requests can continue.', +'in_device':'Linux serial device path, default /dev/ttyUSB0, at most 127 ASCII characters. Runtime account needs access.', +'in_baudRate':'Serial baud rate, default 9600. Supplied C source accepts 1200,2400,4800,9600,19200,38400,57600,115200.', +'in_parity':'0 none, 1 odd, 2 even. Must match the slave.', 'in_dataBits':'Only 8 supported by supplied native implementation.', 'in_stopBits':'1 or 2; must match the slave.', +'in_rs485':'Request Linux kernel RS485 direction control. Leave FALSE for USB adapters handling direction automatically. Requires driver support.', +'client':'Reference to the owning ClientBase concrete instance. Initialize/call client before first registration; retain a stable instance.', +'in_startAddr':'Zero-based Modbus protocol address, not a 40001/30001 display reference. Must fit the transaction into address space.', +'in_quantity':'Requested item count; actual stats.reqLength is MIN(this, available destination/source capacity). Zero makes the request inactive.', +'in_dataOffset':'Actual first index in the application array. For ARRAY[1..10], use 1 to start at the first element; not a relative offset.', +'in_unitId':'Modbus unit/slave ID, default 0. RTU requires explicit 1..247; TCP/gateway values depend on the target.', +'in_minDelay':'Minimum start-to-start time before scheduler eligibility, default 10 ms. Not a fixed polling period.', +'in_prioritySetpoint':'Priority restored on success, default 50. Lower values win. Error backoff can replace current priority.', +'in_priorityCurrent':'Mutable scheduler priority. Lower values win, equal values use age. WriteNow sets 0. Application need not maintain it each scan.', +'in_staleAfter':'Read data age threshold, default 2 s. out_stale becomes TRUE when age is strictly greater; does not stop polling.', +'data':'Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice.', +'stats':'Per-request RequestStats instance maintained by request processing; inspect rather than overwrite.', +'out_connected':'Transport is in ready/sending/receiving state. Does not prove a request succeeded or data is fresh.', +'out_connectionState':'0 disabled, 1 retry wait, 2 TCP connecting, 3 ready, 4 sending, 5 receiving.', +'out_lastNativeError':'Native last transport error; may persist after recovery. Check current connection and request diagnostics too.', +'out_nativeError':'Result of the last completed/failed request; 0 success. Precise native code, distinct from IEC Error enum.', +'out_socketError':'Native Linux errno snapshot as UDINT; may be historical. Used for both serial and sockets.', +'out_reconnectCount':'Native successful reconnections after the first connection. Context recreation resets its native lifetime counters.', +'out_registrationFull':'Sticky request registry overflow indicator; max 100 requests/client. Client reinitialization clears it.', +'out_writeUncertain':'Write may have reached target without a confirmed response. It is not proof of failure or success. Relevant for write requests.', +'out_validData':'TRUE after a successful read; subsequent read failures do not necessarily clear it. Client release or invalidateNativeData clears it.', +'out_dataAgeMs':'Age since last successful read in milliseconds. Updated by normal processing; completion early returns can defer refresh by one scan.', +'out_stale':'TRUE before valid read data or when age exceeds in_staleAfter. Array contents are retained.', +'out_parameterError':'Array-offset validation for requests; full transaction validation can instead appear in out_nativeError/stats.lastErrorId. Slave: invalid port/interfaceName/table bounds or capacity.', +'msgLastSecondCnt':'Submitted request count in the last completed window of at least 1000 ms, not necessarily an exact sliding second.', +'msgLastSecondTime':'Sum of successful transaction durations in that completed window (ms); excludes failed durations.', +'numMsgSent':'IEC accepted submission count since resetStats; not a count of physical packets.', +'numMsgReply':'IEC successful or Modbus-exception reply count since resetStats.', +'allRequestsOnline':'TRUE when at least one active request exists and onlineCnt equals activeReqCnt. Online can include exception replies.', +'reqStats':'Pointers to registered request statistics by registry index, populated on client scans. Null entries are unused; validate before dereference.', +'in_reset':'Slave one-shot recreation input. FB clears it internally; supplying TRUE every scan recreates every scan.', +'interfaceName':'Legacy slave field. Must be empty; nonempty is rejected. Use packed in_ipAddress instead.', +'port':'Slave listening port, default 502, must be nonzero.', +'aCoilsMemory':'Read/write BOOL coil table. Array bounds are protocol addresses; FC1 reads, FC5/15 write.', +'aDiscreteInputsMemory':'Read-only from network BOOL input table; FC2 reads. Application updates it.', +'aHoldingRegistersMemory':'Read/write UINT holding table; FC3 reads, FC6/16 write.', +'aInputRegistersMemory':'Read-only from network UINT input table; FC4 reads. Application updates it.', +'out_running':'Native listener is bound and enabled. Does not mean a peer is connected.', +'out_error':'Current server error flag when outputs are refreshed; some early returns leave other diagnostics unchanged.', +'out_errorId':'IEC server error classification: parameter/internal/native socket class. Consult errno for OS detail.', +'out_connectedClients':'Number of native TCP peers connected (max 8 per server in supplied C source).', +'out_requestsReceived':'Native request count for current server context.', +'out_repliesSent':'Native fully transmitted reply count for current context, including exception replies.', +'out_exceptionReplies':'Number of Modbus exception replies for current server context.', +'lastErrorId':'Last mapped IEC error; successful completion resets to OK.', 'lastExceptionCode':'Last Modbus exception, success=0; stats may substitute TIMEOUT=-1 for non-exception failures.', +'lastValidDuration':'Last successful request duration (ms).','longestValidDuration':'Maximum successful duration recorded; resetStats does not clear it.', +'interval':'Start-to-start execution interval (ms).','offlineCnt':'Count of NOT online rising edges; can include initial not-online state, not only a physical disconnect.', +'doneCnt':'Total successful completions; resetStats clears.','errorCnt':'Total failed completions; resetStats clears.', +'doneCntRst':'Consecutive successes; reset on error/offline/resetStats.','errorCntRst':'Consecutive errors; reset on successful completion/resetStats.', +'sinceLastExec':'Age since last execution start (ms), capped around 60000 by the statistics clock logic.', + 'timeoutCnt':'Statistics non-exception error count; can include other native failures. Not exclusively reply timeouts; resetStats does not clear it.', +'reqLength':'Effective transaction length; zero means inactive.', 'online':'Communication status; valid normal/exception response sets evidence. Distinct from valid data.', +'offline':'Offline status; initialized FALSE, explicitly set on client-offline or repeated failures.', 'active':'TRUE when reqLength is nonzero.', +'executeLight':'Execution indicator with 100 ms off-delay.','reqDoneLight':'Completion indicator with 100 ms off-delay.','reqErrorLight':'Error indicator with 100 ms off-delay.','reqBusyLight':'Busy indicator with 100 ms off-delay.', +'executeMbFb':'Legacy retained output; no assignment in current native implementation. Do not use as a trigger.', +'nativeResponseSeen':'Internal communication-response evidence, maintained by nativeFinish and offline handling.', 'nativeLastSuccessMs':'Internal successful-read timestamp in monotonic milliseconds.', +'command':'Native command selector. Use wrappers in application code.', 'handle':'Generation-checked native context handle; 0 for open.', +'ipAddress':'Packed IPv4 for TCP commands; command 20 overloads this field with RTU baud rate.', +'unitId':'Transaction unit ID; command 20 overloads it with serial parity.', 'functionCode':'Modbus FC; command 20 uses data bits, server push/pull use table ID.', +'startAddress':'Protocol start; command 20 uses stop bits.', 'quantity':'Item count; command 20 uses serial path byte count.', +'pData':'WORD buffer; client reads/writes, serial ASCII path, or server table staging.', 'dataCapacity':'Available WORD elements, not bytes.', +'pStatus':'UDINT status buffer; requires at least 24 elements.', 'statusCapacity':'Available UDINT elements, not bytes.', +'nowMs':'Monotonic time in milliseconds; wrappers use SysTimeGetUs / 1000.', 'connectTimeoutMs':'TCP connection timeout; command 20 uses kernel RS485 flag.', +'responseTimeoutMs':'Reply deadline in milliseconds.', 'retryDelayMs':'Native recovery wait in milliseconds.', +'byte1':'First IPv4 octet.','byte2':'Second IPv4 octet.','byte3':'Third IPv4 octet.','byte4':'Fourth IPv4 octet.', +'bInCopyCode':'Runtime lifecycle copy-code indicator; current implementation does not branch on it.', +'in':'New state to assign. Normally used internally by completion helpers.', 'errorId':'Mapped IEC error supplied to the error helper.', 'exceptionCode':'Modbus exception classification supplied to the error helper.', 'result':'Native completion result; 0 success.'} +original={} +sections=[];count=0 +for e in entries: + parent,name=path(e);t=blob(e,'Interface');impl=blob(e,'Implementation') + if t is None:continue + old=t.text or '';original[meta(e,'Guid').text]=old + if name=='resetStats': + desc=('Reset IEC send/reply/window counters and call each registered stats.resetStats. Does not clear native errno/reconnect counters or all per-request history. No transport recreation.' if parent=='ClientBase' else 'Reset only doneCnt, doneCntRst, errorCnt and errorCntRst. Retains durations, offlineCnt, timeoutCnt and timestamps.') + elif name=='processRequest':desc=('Derived request dispatch: performs '+parent+' transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data.' if parent!='RequestBase' else 'Abstract internal request dispatch implemented by each concrete read/write block.') + elif name in ['Get','Set']:desc=f'{name} accessor for {parent}; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor.' + else:desc=summary.get(name) + assert desc,(parent,name) + header=f'(* atMb API reference: {parent+"." if parent else ""}{name}\n{desc}\n*)\n' + # Add comments only to declaration lines, never change code or types. + lines=[];scope='' + for line in old.splitlines(keepends=True): + sm=re.match(r'\s*(VAR(?:_INPUT|_OUTPUT|_IN_OUT|_TEMP)?)(?:\s+CONSTANT)?\s*$',line) + if sm:scope=sm.group(1) + if re.match(r'\s*END_VAR',line):scope='' + m=re.match(r'\s*(\w+)\s*:',line) + if m and m.group(1) in fields: + lines.append(' // '+fields[m.group(1)]+'\n') + lines.append(line) + t.text=header+''.join(lines);count+=1 + sections.append('## '+(parent+'.' if parent else '')+name+'\n\n'+desc+'\n\n```iecst\n'+old.strip()+'\n```\n\n') + # Parameter table scoped to input/output/open-array sections. + rows=[];scope='' + for line in old.splitlines(): + sm=re.match(r'\s*(VAR(?:_INPUT|_OUTPUT|_IN_OUT)?)(?:\s+CONSTANT)?\s*$',line) + if sm:scope=sm.group(1) + if re.match(r'\s*END_VAR',line):scope='' + m=re.match(r'\s*(\w+)\s*:\s*(.*?);',line) + if m and scope in ['VAR_INPUT','VAR_OUTPUT','VAR_IN_OUT']: + fn=m.group(1); explanation=fields.get(fn,'Internal wrapper state exposed for compatibility; normally leave under library control.') + rows.append('| `'+fn+'` | '+scope+' | `'+m.group(2).replace('|','\\|')+'` | '+explanation+' |') + if rows:sections.append('| Member | Direction | Declaration | Meaning |\n|---|---|---|---|\n'+'\n'.join(rows)+'\n\n') +# Verify executable tokens and all non-Interface XML content unchanged. +def without_comments(s):return re.sub(r'\s+','',re.sub(r'//[^\n]*','',re.sub(r'\(\*.*?\*\)','',s,flags=re.S))) +for e in entries: + guid=meta(e,'Guid').text + if guid in original:assert without_comments(original[guid])==without_comments(blob(e,'Interface').text),path(e) +output=B/'atMb-Documented.export';E.ElementTree(r).write(output,encoding='utf-8',xml_declaration=True) +check=E.parse(output).getroot();orig=E.parse(src).getroot() +for root in [check,orig]: + for e in root.find('.//List2[@Name="EntryList"]'): + t=blob(e,'Interface') + if t is not None:t.text='DOCUMENTATION_ONLY' +assert E.tostring(check)==E.tostring(orig),'Non-documentation content changed' +(B/'API-REFERENCE.md').write_text('# atMb API reference\n\nGenerated from your working `CmpAtomModbus(1).export`. Documents every code object, including internal methods and property accessors. Runtime implementations, identifiers, declarations and embedded native payloads are preserved in the documented export; additions are comments only.\n\n'+''.join(sections)) +(B/'VALIDATION.md').write_text(f'# Documentation validation\n\nDocumented {count} code objects.\n\n- All interface executable tokens unchanged after removing comments/whitespace.\n- All XML content outside Interface text identical after parse/serialization.\n- Implementations, GUIDs, native payloads, project information and references unchanged.\n- Documented XML parses successfully.\n- No CODESYS IDE compile performed; this is a comments-only change.\n\nInput SHA256: `{hashlib.sha256(src.read_bytes()).hexdigest()}`\n') +print('Documented',count,'objects; comment-only and native-payload preservation checks passed') diff --git a/library/CmpAtomModbus.library b/library/CmpAtomModbus.library new file mode 100644 index 0000000..5e3a574 Binary files /dev/null and b/library/CmpAtomModbus.library differ