diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index f1eb93a8e..8e934cf8c 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -18,7 +18,13 @@ __This version of EPICS has not been released yet.__ __Add new items below here__ ------ +======= +### epicsExport simplifications + +`epicsExportAddress()`, `epicsExportRegistrar()` and `epicsRegisterFunction()` +no loger require to be wrapped in `extern "C" { }` in C++ code. + +### Build system `$(PYTHON)` default changed ## EPICS Release 7.0.9 diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index 377e747e0..c49050c49 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -227,6 +227,29 @@ TARGET_SRCS += dbHeaderTest.cpp TARGETS += dbHeaderTestxx$(OBJ) TARGET_SRCS += dbHeaderTestxx.cpp +TARGETS += $(COMMON_DIR)/epicsExportTestIoc.dbd +DBDDEPENDS_FILES += epicsExportTestIoc.dbd$(DEP) +epicsExportTestIoc_DBD += base.dbd +epicsExportTestIoc_DBD += epicsExportTest.dbd +TESTLIBRARY += epicsExportTestLib +epicsExportTestLib_SRCS += epicsExportTest.c +epicsExportTestLib_LIBS += dbCore Com +TESTPROD_HOST += epicsExportTest +TESTS += epicsExportTest +TARGETS += epicsExportTest$(OBJ) +epicsExportTest_SRCS += epicsExportTestMain.c +epicsExportTest_SRCS += epicsExportTestIoc_registerRecordDeviceDriver.cpp +epicsExportTest_LIBS = epicsExportTestLib +TESTLIBRARY += epicsExportTestxxLib +epicsExportTestxxLib_SRCS += epicsExportTestxx.cpp +epicsExportTestxxLib_LIBS += dbCore Com +TESTPROD_HOST += epicsExportTestxx +TESTS += epicsExportTestxx +TARGETS += epicsExportTestxx$(OBJ) +epicsExportTestxx_SRCS += epicsExportTestMain.c +epicsExportTestxx_SRCS += epicsExportTestIoc_registerRecordDeviceDriver.cpp +epicsExportTestxx_LIBS = epicsExportTestxxLib + ifeq ($(T_A),$(EPICS_HOST_ARCH)) # Host-only tests of softIoc/softIocPVA, caget and pvget (if present) # Unfortunately hangs too often on CI systems: diff --git a/modules/database/test/std/rec/epicsExportTest.c b/modules/database/test/std/rec/epicsExportTest.c new file mode 100644 index 000000000..0dda3c0b4 --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTest.c @@ -0,0 +1,140 @@ +/*************************************************************************\ +* Copyright (c) 2025 Dirk Zimoch +* SPDX-License-Identifier: EPICS +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Compile and link test for epicsExport.h +* +* Test if macros eppicsExportAddr, epicsExportRegistrar and epicsRegisterFunction +* expand to valid C and C++ code, in the latter case regardless if +* wrapped with extern "C" {} or not. +* Also test that those macros have the intended effect in both cases. +* +* This file is compiled directly as C +* and included from epicsExportTestxx.cpp as C++ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int i1, i2; + +static long myReadLongin(struct dbCommon* prec) +{ + struct longinRecord* pli = +#ifdef __cplusplus + reinterpret_cast(prec); +#else + (struct longinRecord*)prec; +#endif + pli->val=5; + return 0; +} + +/* Also test cast from user-specific mydset to dset */ +struct mydset { + long number; + long (*report)(int); + long (*init)(int); + long (*init_record)(struct dbCommon*); + long (*get_ioint_info)(int, struct dbCommon*, IOSCANPVT*); + long (*process)(struct dbCommon*); + long (*something_else)(struct dbCommon*); +} dset1 = { + 6, + NULL, + NULL, + NULL, + NULL, + myReadLongin, + NULL +}, dset2 = { + 6, + NULL, + NULL, + NULL, + NULL, + myReadLongin, + NULL +}; + +static void registrar1() { + i1++; + testPass("registrar1 executed"); +} + +static void registrar2() { + i2++; + testPass("registrar2 executed"); +} + +/* Also test cast from int(*)() to REGISTRAR */ +static int registrar3() { + i1++; + testPass("registrar3 executed"); + return 0; +} + +static int registrar4() { + i2++; + testPass("registrar4 executed"); + return 0; +} + +/* Test both, native (potentially C++) and extern "C" functions */ +static long aSubInit1(aSubRecord* prec) { + *(epicsInt32*)prec->a = 1; + return 0; +} + +static long aSubProc1(aSubRecord* prec) { + *(epicsInt32*)prec->b = 2; + return 0; +} + +#ifdef __cplusplus +extern "C" { +#endif +static long aSubInit2(aSubRecord* prec) { + *(epicsInt32*)prec->a = 3; + return 0; +} +static long aSubProc2(aSubRecord* prec) { + *(epicsInt32*)prec->b = 4; + return 0; +} +#ifdef __cplusplus +} +#endif + +/* Test without wrapping */ +epicsExportAddress(int, i1); +epicsExportAddress(dset, dset1); +epicsExportRegistrar(registrar1); +epicsExportRegistrar(registrar3); +epicsRegisterFunction(aSubInit1); +epicsRegisterFunction(aSubProc1); + +/* In C++ test wrapped in extern "C" {} */ +#ifdef __cplusplus +extern "C" { +#endif +epicsExportAddress(int, i2); +epicsExportAddress(dset, dset2); +epicsExportRegistrar(registrar2); +epicsExportRegistrar(registrar4); +epicsRegisterFunction(aSubInit2); +epicsRegisterFunction(aSubProc2); + +#ifdef __cplusplus +} +#endif diff --git a/modules/database/test/std/rec/epicsExportTest.db b/modules/database/test/std/rec/epicsExportTest.db new file mode 100644 index 000000000..8aa297b55 --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTest.db @@ -0,0 +1,24 @@ +record(longin, "li1") { + field(DTYP, "dset1") + field(VAL, "-1") +} +record(longin, "li2") { + field(DTYP, "dset2") + field(VAL, "-2") +} +record(aSub, "asub1") { + field(FTA, "LONG") + field(FTB, "LONG") + field(INPA, "-1") + field(INPB, "-2") + field(INAM, "aSubInit1") + field(SNAM, "aSubProc1") +} +record(aSub, "asub2") { + field(FTA, "LONG") + field(FTB, "LONG") + field(INPA, "-3") + field(INPB, "-4") + field(INAM, "aSubInit2") + field(SNAM, "aSubProc2") +} diff --git a/modules/database/test/std/rec/epicsExportTest.dbd b/modules/database/test/std/rec/epicsExportTest.dbd new file mode 100644 index 000000000..6f547c5e4 --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTest.dbd @@ -0,0 +1,12 @@ +variable(i1,int) +variable(i2,int) +device(longin, CONSTANT, dset1, "dset1") +device(longin, CONSTANT, dset2, "dset2") +registrar(registrar1) +registrar(registrar2) +registrar(registrar3) +registrar(registrar4) +function(aSubInit1) +function(aSubProc1) +function(aSubInit2) +function(aSubProc2) diff --git a/modules/database/test/std/rec/epicsExportTestMain.c b/modules/database/test/std/rec/epicsExportTestMain.c new file mode 100644 index 000000000..32b22cb40 --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTestMain.c @@ -0,0 +1,74 @@ +/*************************************************************************\ +* Copyright (c) 2025 Dirk Zimoch +* SPDX-License-Identifier: EPICS +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Compile and link test for epicsExport.h +* +*/ + +#include +#include +#include +#include +#include +#include + +void epicsExportTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(epicsExportTest) +{ + const iocshVarDef *var_i1, *var_i2; + + testPlan(26); + testdbPrepare(); + testdbReadDatabase("epicsExportTestIoc.dbd", 0, 0); + epicsExportTestIoc_registerRecordDeviceDriver(pdbbase); + + testDiag("Testing if dsets and functions are found"); + testdbReadDatabase("epicsExportTest.db", 0, 0); + testIocInitOk(); + + testDiag("Testing if dsets work correctly"); + testdbGetFieldEqual("li1", DBF_LONG, -1); + testdbGetFieldEqual("li2", DBF_LONG, -2); + testdbPutFieldOk("li1.PROC", DBF_LONG, 1); + testdbPutFieldOk("li2.PROC", DBF_LONG, 1); + testdbGetFieldEqual("li1", DBF_LONG, 5); + testdbGetFieldEqual("li2", DBF_LONG, 5); + + testDiag("Testing if functions work correctly"); + testdbGetFieldEqual("asub1.A", DBF_LONG, 1); + testdbGetFieldEqual("asub1.B", DBF_LONG, -2); + testdbGetFieldEqual("asub2.A", DBF_LONG, 3); + testdbGetFieldEqual("asub2.B", DBF_LONG, -4); + testdbPutFieldOk("asub1.PROC", DBF_LONG, 1); + testdbPutFieldOk("asub2.PROC", DBF_LONG, 1); + testdbGetFieldEqual("asub1.A", DBF_LONG, 1); + testdbGetFieldEqual("asub1.B", DBF_LONG, 2); + testdbGetFieldEqual("asub2.A", DBF_LONG, 3); + testdbGetFieldEqual("asub2.B", DBF_LONG, 4); + + testDiag("Testing if variable access works"); + var_i1 = iocshFindVariable("i1"); + var_i2 = iocshFindVariable("i2"); + if (var_i1 && var_i2 && var_i1->type == iocshArgInt && var_i2->type == iocshArgInt) { + int *pi1 = (int*)(var_i1->pval); + int *pi2 = (int*)(var_i2->pval); + + testOk(*pi1==2, "Variable i1 counted registrars: %d == 2", *pi1); + testOk(*pi2==2, "Variable i2 counted registrars: %d == 2", *pi2); + testDiag("Testing if variables are accessible from iocsh"); + testOk(iocshCmd("var i1,4") == 0, "Setting i1 = 4 in iocsh"); + testOk(iocshCmd("var i2,5") == 0, "Setting i2 = 5 in iocsh"); + testOk(*pi1==4, "Variable i1: %d == 4", *pi1); + testOk(*pi2==5, "Variable i2: %d == 5", *pi2); + } else { + testFail("Cannot access variables i1, i2"); + } + testIocShutdownOk(); + testdbCleanup(); + return testDone(); +} diff --git a/modules/database/test/std/rec/epicsExportTestxx.cpp b/modules/database/test/std/rec/epicsExportTestxx.cpp new file mode 100644 index 000000000..3bb1628ce --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTestxx.cpp @@ -0,0 +1,2 @@ +// just compile as c++ +#include "epicsExportTest.c" diff --git a/modules/libcom/src/misc/epicsExport.h b/modules/libcom/src/misc/epicsExport.h index 7c084a602..c3aba1376 100644 --- a/modules/libcom/src/misc/epicsExport.h +++ b/modules/libcom/src/misc/epicsExport.h @@ -75,12 +75,16 @@ typedef void (*REGISTRAR)(void); * * \param typ Object's data type. * \param obj Object's name. - * - * \note C++ code needs to wrap with @code extern "C" { } @endcode */ +#ifdef __cplusplus +#define epicsExportAddress(typ, obj) \ + extern "C" { epicsShareExtern typ *EPICS_EXPORT_POBJ(typ,obj); } \ + epicsShareDef typ *EPICS_EXPORT_POBJ(typ, obj) = reinterpret_cast(&obj) +#else #define epicsExportAddress(typ, obj) \ epicsShareExtern typ *EPICS_EXPORT_POBJ(typ,obj); \ epicsShareDef typ *EPICS_EXPORT_POBJ(typ, obj) = (typ *) (char *) &obj +#endif /** \brief Declare a registrar function for exporting. * @@ -94,11 +98,15 @@ typedef void (*REGISTRAR)(void); \endcode * * \param fun Registrar function's name. - * - * \note C++ code needs to wrap with @code extern "C" { } @endcode */ +#ifdef __cplusplus +#define epicsExportRegistrar(fun) \ + extern "C" { extern epicsShareFunc REGISTRAR EPICS_EXPORT_PFUNC(fun); } \ + REGISTRAR EPICS_EXPORT_PFUNC(fun) = reinterpret_cast(&fun) +#else #define epicsExportRegistrar(fun) \ epicsShareFunc REGISTRAR EPICS_EXPORT_PFUNC(fun) = (REGISTRAR) &fun +#endif /** \brief Declare and register a function for exporting. * @@ -111,15 +119,22 @@ typedef void (*REGISTRAR)(void); \endcode * * \param fun Function's name - * - * \note C++ code needs to wrap with @code extern "C" { } @endcode */ +#ifdef __cplusplus +#define epicsRegisterFunction(fun) \ + static void register_func_ ## fun(void) \ + { \ + registryFunctionAdd(#fun, reinterpret_cast(fun)); \ + } \ + epicsExportRegistrar(register_func_ ## fun) +#else #define epicsRegisterFunction(fun) \ static void register_func_ ## fun(void) \ { \ registryFunctionAdd(#fun, (REGISTRYFUNCTION) fun); \ } \ epicsExportRegistrar(register_func_ ## fun) +#endif #ifdef __cplusplus }