diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 387c4191b..0435a1cc6 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -18,6 +18,11 @@ __This version of EPICS has not been released yet.__ __Add new items below here__ +### epicsExport simplifications + +`epicsExportAddress()`, `epicsExportRegistrar()` and `epicsRegisterFunction()` +no longer require to be wrapped in `extern "C" { }` in C++ code. + ### New `dbServerStats()` API for iocStats A new routine provides the ability to request channel and client counts from diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index 377e747e0..d6aaf44d3 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -221,6 +221,34 @@ testHarness_SRCS += linkFilterTest.c TESTFILES += ../linkFilterTest.db TESTS += linkFilterTest +TARGETS += $(COMMON_DIR)/epicsExportTestIoc.dbd +DBDDEPENDS_FILES += epicsExportTestIoc.dbd$(DEP) +epicsExportTestIoc_DBD += base.dbd +epicsExportTestIoc_DBD += epicsExportTest.dbd +TESTFILES += $(COMMON_DIR)/epicsExportTestIoc.dbd ../epicsExportTest.db + +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 + # These are compile-time tests, no need to link or run TARGETS += dbHeaderTest$(OBJ) TARGET_SRCS += dbHeaderTest.cpp diff --git a/modules/database/test/std/rec/epicsExportTest.c b/modules/database/test/std/rec/epicsExportTest.c new file mode 100644 index 000000000..aaa3f2215 --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTest.c @@ -0,0 +1,146 @@ +/*************************************************************************\ +* 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; +volatile int v1=10, v2=20; +const int c1=100, c2=200; + +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 const mydset to dset */ +const 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(int, v1); +epicsExportAddress(int, c1); +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(int, v2); +epicsExportAddress(int, c2); +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..fc9ee2f6d --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTest.dbd @@ -0,0 +1,16 @@ +variable(i1,int) +variable(i2,int) +variable(v1,int) +variable(v2,int) +variable(c1,int) +variable(c2,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..2f6d11f1e --- /dev/null +++ b/modules/database/test/std/rec/epicsExportTestMain.c @@ -0,0 +1,95 @@ +/*************************************************************************\ +* 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 + +#ifdef __cplusplus +extern "C" +#endif +int epicsExportTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static int* testVarEquals(const char* name, int expected) +{ + const iocshVarDef *var; + int *p; + + var = iocshFindVariable(name); + if (!var) { + testFail("Cannot access variable %s", name); + return NULL; + } + if (var->type != iocshArgInt) { + testFail("Variable %s has wrong type", name); + return NULL; + } + p = (int*)(var->pval); + testOk(*p == expected, "Variable %s == %d", name, expected); + return p; +} + +MAIN(epicsExportTest) +{ + int *p1, *p2; + + testPlan(31); + testdbPrepare(); + testdbReadDatabase("epicsExportTestIoc.dbd", 0, 0); + testOk(epicsExportTestIoc_registerRecordDeviceDriver(pdbbase)==0, "registerRecordDeviceDriver"); + + 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"); + p1 = testVarEquals("i1", 2); + p2 = testVarEquals("i2", 2); + testVarEquals("v1", 10); + testVarEquals("v2", 20); + testVarEquals("c1", 100); + testVarEquals("c2", 200); + + if (p1 && p2) { + 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(*p1==4, "Variable i1 == 4"); + testOk(*p2==5, "Variable i2 == 5"); + } + + 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..356d62b39 100644 --- a/modules/libcom/src/misc/epicsExport.h +++ b/modules/libcom/src/misc/epicsExport.h @@ -41,6 +41,10 @@ extern "C" { typedef void (*REGISTRAR)(void); +#ifdef __cplusplus +} +#endif + #define EPICS_EXPORT_POBJ(typ, obj) pvar_ ## typ ## _ ## obj #define EPICS_EXPORT_PFUNC(fun) EPICS_EXPORT_POBJ(func, fun) @@ -75,12 +79,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) = (typ *) (char *) &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 +102,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,18 +123,21 @@ 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) - -#ifdef __cplusplus -} #endif #endif /* INC_epicsExport_H */