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