From 8d137b82c80ea98770a301d0dc7881d392cdc7da Mon Sep 17 00:00:00 2001 From: Patrick Bruenn Date: Thu, 2 Apr 2026 08:32:28 +0000 Subject: [PATCH 1/6] AdsLib: Add extern "C" to allow shared library exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re seeing increasing demand for a shared library version of the standalone AdsLib. Here we go and mark the external C functions, so we can build a shared library in the next step. Suggested-by: Stefan Lehmann Signed-off-by: Patrick Bruenn --- AdsLib/AdsLib.h | 7 +++++++ AdsLib/standalone/AdsLib.cpp | 6 ++++++ AdsLib/standalone/AdsLib.h | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/AdsLib/AdsLib.h b/AdsLib/AdsLib.h index a9ecb732..225c24b8 100644 --- a/AdsLib/AdsLib.h +++ b/AdsLib/AdsLib.h @@ -13,6 +13,9 @@ #include "Sockets.h" +#ifdef BHF_ADS_EXPORT_C +extern "C" { +#endif /** * Reads data synchronously from an ADS server. * @param[in] port port number of an Ads port that had previously been opened with AdsPortOpenEx(). @@ -188,3 +191,7 @@ long GetRemoteAddress(const std::string &remote, AmsNetId &netId); #define AdsAddRoute bhf::ads::AddLocalRoute #define AdsDelRoute bhf::ads::DelLocalRoute #define AdsSetLocalAddress bhf::ads::SetLocalAddress + +#ifdef BHF_ADS_EXPORT_C +} +#endif diff --git a/AdsLib/standalone/AdsLib.cpp b/AdsLib/standalone/AdsLib.cpp index 6f80f4c9..61ce931b 100644 --- a/AdsLib/standalone/AdsLib.cpp +++ b/AdsLib/standalone/AdsLib.cpp @@ -6,6 +6,9 @@ #include "AdsLib.h" #include "AmsRouter.h" +#ifdef BHF_ADS_EXPORT_C +extern "C" { +#endif static AmsRouter &GetRouter() { static AmsRouter router; @@ -286,3 +289,6 @@ long AdsSyncSetTimeoutEx(long port, uint32_t timeout) ASSERT_PORT(port); return GetRouter().SetTimeout((uint16_t)port, timeout); } +#ifdef BHF_ADS_EXPORT_C +} +#endif diff --git a/AdsLib/standalone/AdsLib.h b/AdsLib/standalone/AdsLib.h index 76e8aa4e..1f05af9a 100644 --- a/AdsLib/standalone/AdsLib.h +++ b/AdsLib/standalone/AdsLib.h @@ -6,6 +6,9 @@ #include "AdsDef.h" +#ifdef BHF_ADS_EXPORT_C +extern "C" { +#endif /** * The connection (communication port) to the message router is * closed. The port to be closed must previously have been opened via @@ -38,3 +41,6 @@ long AdsGetLocalAddressEx(long port, AmsAddr *pAddr); * @return [ADS Return Code](https://infosys.beckhoff.com/content/1031/tcadscommon/html/ads_returncodes.htm?id=1666172286265530469) */ long AdsSyncSetTimeoutEx(long port, uint32_t timeout); +#ifdef BHF_ADS_EXPORT_C +} +#endif From ebffb3ad4e2a4fe3646e324b4108096c842d963b Mon Sep 17 00:00:00 2001 From: Patrick Bruenn Date: Thu, 2 Apr 2026 08:32:34 +0000 Subject: [PATCH 2/6] meson: build a shared library for the standalone version More and more users want a shared library of the standalone version. Here we go and finally add it to our build. Suggested-by: Stefan Lehmann Signed-off-by: Patrick Bruenn --- meson.build | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d3677d84..f5437369 100644 --- a/meson.build +++ b/meson.build @@ -95,7 +95,15 @@ adslib = static_library('AdsLib', install: true, ) -install_libs = [ adslib ] +adslib_so = shared_library('adslib', + [common_files, router_files], + cpp_args: '-DBHF_ADS_EXPORT_C', + include_directories: inc, + install: true, + dependencies: libs, +) + +install_libs = [ adslib, adslib_so ] adslib_dep = declare_dependency( include_directories : inc, From c08e11c9224a45b1d031e6ab4f7d3073193f137d Mon Sep 17 00:00:00 2001 From: Patrick Bruenn Date: Thu, 2 Apr 2026 08:49:28 +0000 Subject: [PATCH 3/6] AdsNotificationHeader: add BHF_ADS_USE_TWINCAT_ORDER In my opinion, the "struct AdsNotificationHeader" in the TwinCAT SDK is broken. It relies on "#pragma pack" and forces unaligned access on 64-bit machines. A long time ago, I decided to fix it by reordering the structure, which is only used on the client side and is not sent "over the wire". However, projects like pyADS[1], which want to dynamically choose whether to use the standalone AdsLib or the TwinCAT router, needed to patch this. Let's give them a compile-time option to do so, and use it ourselves when we build a shared library, which is most likely used "in parallel" with the TwinCAT variant. [1] https://github.com/stlehmann/pyads Signed-off-by: Patrick Bruenn --- AdsLib/standalone/AdsDef.h | 14 +++++++++++++- meson.build | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/AdsLib/standalone/AdsDef.h b/AdsLib/standalone/AdsDef.h index 76507176..3c84b396 100644 --- a/AdsLib/standalone/AdsDef.h +++ b/AdsLib/standalone/AdsDef.h @@ -407,12 +407,24 @@ struct AdsNotificationAttrib { * @brief This structure is also passed to the callback function. */ struct AdsNotificationHeader { +/* Original TwinCAT SDK headers have + * uint32_t | uint64_t | uint32_t + * We use the same order when compiled as a shared library to allow + * consumers to dynamically link against us or the upstream TwinCAT. + */ +#ifdef BHF_ADS_USE_TWINCAT_ORDER + /** Handle for the notification. Is specified when the notification is defined. */ + uint32_t hNotification; + + /** Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC). */ + uint64_t nTimeStamp; +#else /** Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC). */ uint64_t nTimeStamp; /** Handle for the notification. Is specified when the notification is defined. */ uint32_t hNotification; - +#endif /** Number of bytes transferred. */ uint32_t cbSampleSize; }; diff --git a/meson.build b/meson.build index f5437369..70399d8a 100644 --- a/meson.build +++ b/meson.build @@ -98,6 +98,7 @@ adslib = static_library('AdsLib', adslib_so = shared_library('adslib', [common_files, router_files], cpp_args: '-DBHF_ADS_EXPORT_C', + cpp_args: '-DBHF_ADS_USE_TWINCAT_ORDER', include_directories: inc, install: true, dependencies: libs, From 48ac1f2e40822f8b7007b4f19fe13705f48c1a13 Mon Sep 17 00:00:00 2001 From: Patrick Bruenn Date: Sun, 12 Apr 2026 10:33:30 +0000 Subject: [PATCH 4/6] fixup! AdsNotificationHeader: add BHF_ADS_USE_TWINCAT_ORDER --- meson.build | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 70399d8a..e44f1897 100644 --- a/meson.build +++ b/meson.build @@ -97,8 +97,10 @@ adslib = static_library('AdsLib', adslib_so = shared_library('adslib', [common_files, router_files], - cpp_args: '-DBHF_ADS_EXPORT_C', - cpp_args: '-DBHF_ADS_USE_TWINCAT_ORDER', + cpp_args: [ + '-DBHF_ADS_EXPORT_C', + '-DBHF_ADS_USE_TWINCAT_ORDER', + ], include_directories: inc, install: true, dependencies: libs, From 12e9df27b7c504958f5ca902d30e0ecaa3249346 Mon Sep 17 00:00:00 2001 From: Stefan Lehmann Date: Wed, 22 Apr 2026 21:44:38 +0200 Subject: [PATCH 5/6] Replace #define by wrapper functions to avoid name mangling --- AdsLib/AdsLib.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AdsLib/AdsLib.h b/AdsLib/AdsLib.h index 225c24b8..43b1e536 100644 --- a/AdsLib/AdsLib.h +++ b/AdsLib/AdsLib.h @@ -188,9 +188,9 @@ long GetRemoteAddress(const std::string &remote, AmsNetId &netId); } } -#define AdsAddRoute bhf::ads::AddLocalRoute -#define AdsDelRoute bhf::ads::DelLocalRoute -#define AdsSetLocalAddress bhf::ads::SetLocalAddress +long AdsAddRoute(AmsNetId netId, const char* ipAddr); +void AdsDelRoute(AmsNetId netId); +void AdsSetLocalAddress(AmsNetId netId); #ifdef BHF_ADS_EXPORT_C } From 88d3abe992fa36fdb4cf0bb00519065a47113b68 Mon Sep 17 00:00:00 2001 From: Stefan Lehmann Date: Wed, 22 Apr 2026 21:45:00 +0200 Subject: [PATCH 6/6] Define wrapper functions to avoid name mangling --- AdsLib/standalone/AdsLib.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/AdsLib/standalone/AdsLib.cpp b/AdsLib/standalone/AdsLib.cpp index 61ce931b..0bb84d40 100644 --- a/AdsLib/standalone/AdsLib.cpp +++ b/AdsLib/standalone/AdsLib.cpp @@ -289,6 +289,18 @@ long AdsSyncSetTimeoutEx(long port, uint32_t timeout) ASSERT_PORT(port); return GetRouter().SetTimeout((uint16_t)port, timeout); } + +long AdsAddRoute(AmsNetId netId, const char* ipAddr) { + return bhf::ads::AddLocalRoute(netId, ipAddr); +} + +void AdsDelRoute(AmsNetId netId) { + bhf::ads::DelLocalRoute(netId); +} + +void AdsSetLocalAddress(AmsNetId netId) { + bhf::ads::SetLocalAddress(netId); +} #ifdef BHF_ADS_EXPORT_C } #endif