summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorMnikolenko Productengine <mnikolenko@productengine.com>2022-06-29 21:02:57 +0300
committerMnikolenko Productengine <mnikolenko@productengine.com>2022-06-29 21:03:15 +0300
commitb0e98406488f712220175141f2b0f45a95082f12 (patch)
tree4a965843d24f984caf12722c7809a84797ddc212 /indra/llcommon
parent76c8a7ff0b57e7856613960391be7c400384183b (diff)
parent1e4f2ec07e32a142f35817d3186a124df3f8cd25 (diff)
Merge branch 'master' into DRTVWR-539
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt14
-rw-r--r--indra/llcommon/classic_callback.cpp16
-rw-r--r--indra/llcommon/classic_callback.h292
-rw-r--r--indra/llcommon/llsdserialize.cpp2
-rw-r--r--indra/llcommon/llsys.cpp28
-rw-r--r--indra/llcommon/llsys.h4
-rw-r--r--indra/llcommon/tests/classic_callback_test.cpp144
7 files changed, 492 insertions, 8 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index ca8b5e946f..59aa731af2 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -10,7 +10,7 @@ include(Boost)
include(LLSharedLibs)
include(JsonCpp)
include(Copy3rdPartyLibs)
-include(ZLIB)
+include(ZLIBNG)
include(URIPARSER)
include(Tracy)
@@ -18,7 +18,7 @@ include_directories(
${EXPAT_INCLUDE_DIRS}
${LLCOMMON_INCLUDE_DIRS}
${JSONCPP_INCLUDE_DIR}
- ${ZLIB_INCLUDE_DIRS}
+ ${ZLIBNG_INCLUDE_DIRS}
${URIPARSER_INCLUDE_DIRS}
${TRACY_INCLUDE_DIR}
)
@@ -129,6 +129,7 @@ set(llcommon_HEADER_FILES
CMakeLists.txt
chrono.h
+ classic_callback.h
ctype_workaround.h
fix_macros.h
indra_constants.h
@@ -300,7 +301,7 @@ target_link_libraries(
${APR_LIBRARIES}
${EXPAT_LIBRARIES}
${JSONCPP_LIBRARIES}
- ${ZLIB_LIBRARIES}
+ ${ZLIBNG_LIBRARIES}
${WINDOWS_LIBRARIES}
${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
@@ -336,16 +337,17 @@ if (LL_TESTS)
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY})
- LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lleventfilter "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
@@ -363,8 +365,8 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(threadsafeschedule "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(tuple "" "${test_libs}")
diff --git a/indra/llcommon/classic_callback.cpp b/indra/llcommon/classic_callback.cpp
new file mode 100644
index 0000000000..5674e0a44d
--- /dev/null
+++ b/indra/llcommon/classic_callback.cpp
@@ -0,0 +1,16 @@
+/**
+ * @file classic_callback.cpp
+ * @author Nat Goodspeed
+ * @date 2021-09-23
+ * @brief Implementation for classic_callback.
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Copyright (c) 2021, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+namespace {
+
+const char dummy[] = "cpp file required to build test program";
+
+} // anonymous namespace
diff --git a/indra/llcommon/classic_callback.h b/indra/llcommon/classic_callback.h
new file mode 100644
index 0000000000..1ad6dbc58f
--- /dev/null
+++ b/indra/llcommon/classic_callback.h
@@ -0,0 +1,292 @@
+/**
+ * @file classic_callback.h
+ * @author Nat Goodspeed
+ * @date 2016-06-21
+ * @brief ClassicCallback and HeapClassicCallback
+ *
+ * This header file addresses the problem of passing a method on a C++ object
+ * to an API that requires a classic-C function pointer. Typically such a
+ * callback API accepts a void* pointer along with the function pointer, and
+ * the function pointer signature accepts a void* parameter. The API passes
+ * the caller's pointer value into the callback function so it can find its
+ * data. In C++, there are a few ways to deal with this case:
+ *
+ * - Use a static method with correct signature. If you don't need access to a
+ * specific instance, that works fine.
+ * - Store the object statically (or store a static pointer to a non-static
+ * instance). As long as you only care about one instance, that works, but
+ * starts to get a little icky. As soon as there's more than one pertinent
+ * instance, fight valiantly against the temptation to stuff the instance
+ * pointer into a static pointer variable "just for a moment."
+ * - Code a static trampoline callback function that accepts the void* user
+ * data pointer, casts it to the appropriate class type and calls the actual
+ * method on that class.
+ *
+ * ClassicCallback encapsulates the last. You need only construct a
+ * ClassicCallback instance somewhere that will survive until the callback is
+ * called, binding the target C++ callable. You then call its get_callback()
+ * and get_userdata() methods to pass an appropriate classic-C function
+ * pointer and void* user data pointer, respectively, to the old-style
+ * callback API. get_callback() synthesizes a static trampoline function
+ * that casts the user data pointer and calls the bound C++ callable.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_CLASSIC_CALLBACK_H)
+#define LL_CLASSIC_CALLBACK_H
+
+#include <tuple>
+#include <type_traits> // std::is_same
+
+/*****************************************************************************
+* Helpers
+*****************************************************************************/
+
+// find a type in a parameter pack: http://stackoverflow.com/q/17844867/5533635
+// usage: index_of<0, sought_t, PackName...>::value
+template <int idx, typename sought, typename candidate, typename ...rest>
+struct index_of
+{
+ static constexpr int const value =
+ std::is_same<sought, candidate>::value ?
+ idx : index_of<idx + 1, sought, rest...>::value;
+};
+
+// recursion tail
+template <int idx, typename sought, typename candidate>
+struct index_of<idx, sought, candidate>
+{
+ static constexpr int const value =
+ std::is_same<sought, candidate>::value ? idx : -1;
+};
+
+/*****************************************************************************
+* ClassicCallback
+*****************************************************************************/
+/**
+ * Instantiate ClassicCallback in whatever storage will persist long enough
+ * for the callback to be called. It holds a modern C++ callable, providing a
+ * static function pointer and a USERDATA (default void*) capable of being
+ * passed through a classic-C callback API. When the static function is called
+ * with that USERDATA pointer, ClassicCallback forwards the call to the bound
+ * C++ callable.
+ *
+ * Usage:
+ * @code
+ * // callback signature required by the API of interest
+ * typedef void (*callback_t)(int, const char*, void*, double);
+ * // old-style API that accepts a classic-C callback function pointer
+ * void oldAPI(callback_t callback, void* userdata);
+ * // but I want to pass a lambda that references data local to my function!
+ * // (We don't need to name the void* parameter in the C++ callable;
+ * // ClassicCallback already used it to locate the lambda instance.)
+ * auto ccb{
+ * makeClassicCallback<callback_t>(
+ * [=](int n, const char* s, void*, double f){ ... }) };
+ * oldAPI(ccb.get_callback(), ccb.get_userdata());
+ * // If the passed callback is called before oldAPI() returns, we can now
+ * // safely destroy ccb. If the callback might be called later, consider
+ * // HeapClassicCallback instead.
+ * @endcode
+ *
+ * If you have a callable object in hand, and you want to pass that to
+ * ClassicCallback, you may either consume it by passing std::move(object), or
+ * explicitly specify a reference to that object type as the CALLABLE template
+ * parameter:
+ * @code
+ * CallableObject obj;
+ * ClassicCallback<callback_t, void*, CallableObject&> ccb{obj};
+ * @endcode
+ */
+// CALLABLE should either be deduced, e.g. by makeClassicCallback(), or
+// specified explicitly. Its default type is meaningless, coded only so we can
+// provide a useful default for USERDATA.
+template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
+class ClassicCallback
+{
+ typedef ClassicCallback<SIGNATURE, USERDATA, CALLABLE> self_t;
+
+public:
+ /// ClassicCallback binds any modern C++ callable.
+ ClassicCallback(CALLABLE&& callable):
+ mCallable(std::forward<CALLABLE>(callable))
+ {}
+
+ /**
+ * ClassicCallback must not itself be copied or moved! Once you've passed
+ * get_userdata() to some API, this object MUST remain at that address.
+ */
+ // However, we can't yet count on C++17 Class Template Argument Deduction,
+ // which means makeClassicCallback() is still useful, which means we MUST
+ // be able to return one to construct into caller's instance (move ctor).
+ // Possible defense: bool 'referenced' data member set by get_userdata(),
+ // with an llassert_always(! referenced) check in the move constructor.
+ ClassicCallback(ClassicCallback const&) = delete;
+ ClassicCallback(ClassicCallback&&) = default; // delete;
+ ClassicCallback& operator=(ClassicCallback const&) = delete;
+ ClassicCallback& operator=(ClassicCallback&&) = delete;
+
+ /// Call get_callback() to get the necessary function pointer.
+ SIGNATURE get_callback() const
+ {
+ // This declaration is where the compiler instantiates the correct
+ // signature for the call() function template.
+ SIGNATURE callback = call;
+ return callback;
+ }
+
+ /// Call get_userdata() to get the opaque USERDATA pointer to pass
+ /// through the classic-C callback API.
+ USERDATA get_userdata() const
+ {
+ // The USERDATA userdata is of course a pointer to this object.
+ return static_cast<USERDATA>(const_cast<self_t*>(this));
+ }
+
+protected:
+ /**
+ * This call() method accepts one or more callback arguments. It assumes
+ * the first USERDATA parameter is the userdata.
+ */
+ // Note that we're not literally using C++ perfect forwarding here -- it
+ // doesn't work to specify (Args&&... args). But that's okay because we're
+ // dealing with a classic-C callback! It's not going to pass any move-only
+ // types.
+ template <typename... Args>
+ static auto call(Args... args)
+ {
+ auto userdata = extract_userdata(std::forward<Args>(args)...);
+ // cast the userdata param to 'this' and call mCallable
+ return static_cast<self_t*>(userdata)->
+ mCallable(std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ static USERDATA extract_userdata(Args... args)
+ {
+ // Search for the first USERDATA parameter type, then extract that pointer.
+ // extract value from parameter pack: http://stackoverflow.com/a/24710433/5533635
+ return std::get<index_of<0, USERDATA, Args...>::value>(std::forward_as_tuple(args...));
+ }
+
+ CALLABLE mCallable;
+};
+
+/**
+ * Usage:
+ * @code
+ * auto ccb{ makeClassicCallback<classic_callback_signature>(actual_callback) };
+ * @endcode
+ */
+template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
+auto makeClassicCallback(CALLABLE&& callable)
+{
+ return std::move(ClassicCallback<SIGNATURE, USERDATA, CALLABLE>
+ (std::forward<CALLABLE>(callable)));
+}
+
+/*****************************************************************************
+* HeapClassicCallback
+*****************************************************************************/
+/**
+ * HeapClassicCallback is like ClassicCallback, with this exception: it MUST
+ * be allocated on the heap because, once the callback has been called, it
+ * deletes itself. This addresses the problem of a callback whose lifespan
+ * must persist beyond the scope in which the callback API is engaged -- but
+ * naturally this callback must be called exactly ONCE.
+ *
+ * Usage:
+ * @code
+ * // callback signature required by the API of interest
+ * typedef void (*callback_t)(int, const char*, void*, double);
+ * // here's the old-style API
+ * void oldAPI(callback_t callback, void* userdata);
+ * // want to call someObjPtr->method() when oldAPI() fires the callback,
+ * // sometime in the future after the enclosing function has returned
+ * auto ccb{
+ * makeHeapClassicCallback<callback_t>(
+ * [someObjPtr](int n, const char* s, void*, double f)
+ * { someObjPtr->method(); }) };
+ * oldAPI(ccb.get_callback(), ccb.get_userdata());
+ * // We don't need a smart pointer for ccb, because it will be deleted once
+ * // oldAPI() calls the bound lambda. HeapClassicCallback is for when the
+ * // callback will be called exactly once. If the classic API might call the
+ * // passed callback more than once -- or might never call it at all --
+ * // manually construct a ClassicCallback on the heap and manage its lifespan
+ * // explicitly.
+ * @endcode
+ */
+template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
+class HeapClassicCallback: public ClassicCallback<SIGNATURE, USERDATA, CALLABLE>
+{
+ typedef ClassicCallback<SIGNATURE, USERDATA, CALLABLE> super;
+ typedef HeapClassicCallback<SIGNATURE, USERDATA, CALLABLE> self_t;
+
+ // This destructor is intentionally private to prevent allocation anywhere
+ // but the heap. (The Design and Evolution of C++, section 11.4.2: Control
+ // of Allocation)
+ ~HeapClassicCallback() {}
+
+public:
+ HeapClassicCallback(CALLABLE&& callable):
+ super(std::forward<CALLABLE>(callable))
+ {}
+
+ // makeHeapClassicCallback() only needs to return a pointer -- not an
+ // instance -- so we can lock down our move constructor too.
+ HeapClassicCallback(HeapClassicCallback&&) = delete;
+
+ /// Replicate get_callback() from the base class because we must
+ /// instantiate OUR call() function template.
+ SIGNATURE get_callback() const
+ {
+ // This declaration is where the compiler instantiates the correct
+ // signature for the call() function template.
+ SIGNATURE callback = call;
+ return callback;
+ }
+
+ /// Replicate get_userdata() from the base class because our call()
+ /// method must be able to reconstitute a pointer to this subclass.
+ USERDATA get_userdata() const
+ {
+ // The USERDATA userdata is of course a pointer to this object.
+ return static_cast<const USERDATA>(const_cast<self_t*>(this));
+ }
+
+private:
+ // call() uses a helper class to delete the HeapClassicCallback when done,
+ // for two reasons. Most importantly, this deletes even if the callback
+ // throws an exception. But also, call() must directly return the callback
+ // result for return-type deduction.
+ struct Destroyer
+ {
+ Destroyer(self_t* p): mPtr(p) {}
+ ~Destroyer() { delete mPtr; }
+
+ self_t* mPtr;
+ };
+
+ template <typename... Args>
+ static auto call(Args... args)
+ {
+ // extract userdata at this level too
+ USERDATA userdata = super::extract_userdata(std::forward<Args>(args)...);
+ // arrange to delete it when we leave by whatever means
+ Destroyer destroy(static_cast<self_t*>(userdata));
+
+ return super::call(std::forward<Args>(args)...);
+ }
+};
+
+template <typename SIGNATURE, typename USERDATA=void*, typename CALLABLE=void(*)()>
+auto makeHeapClassicCallback(CALLABLE&& callable)
+{
+ return new HeapClassicCallback<SIGNATURE, USERDATA, CALLABLE>
+ (std::forward<CALLABLE>(callable));
+}
+
+#endif /* ! defined(LL_CLASSIC_CALLBACK_H) */
diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index 022a5d4659..8b4a0ee6d8 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -37,7 +37,7 @@
#ifdef LL_USESYSTEMLIBS
# include <zlib.h>
#else
-# include "zlib/zlib.h" // for davep's dirty little zip functions
+# include "zlib-ng/zlib.h" // for davep's dirty little zip functions
#endif
#if !LL_WINDOWS
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp
index f717b2cf34..9b6bb3826c 100644
--- a/indra/llcommon/llsys.cpp
+++ b/indra/llcommon/llsys.cpp
@@ -36,7 +36,7 @@
#ifdef LL_USESYSTEMLIBS
# include <zlib.h>
#else
-# include "zlib/zlib.h"
+# include "zlib-ng/zlib.h"
#endif
#include "llprocessor.h"
@@ -456,6 +456,8 @@ LLOSInfo::LLOSInfo() :
dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild;
mOSVersionString.append(dotted_version_string.str());
+ mOSBitness = is64Bit() ? 64 : 32;
+ LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL;
}
#ifndef LL_WINDOWS
@@ -511,6 +513,11 @@ const std::string& LLOSInfo::getOSVersionString() const
return mOSVersionString;
}
+const S32 LLOSInfo::getOSBitness() const
+{
+ return mOSBitness;
+}
+
//static
U32 LLOSInfo::getProcessVirtualSizeKB()
{
@@ -564,6 +571,25 @@ U32 LLOSInfo::getProcessResidentSizeKB()
return resident_size;
}
+//static
+bool LLOSInfo::is64Bit()
+{
+#if LL_WINDOWS
+#if defined(_WIN64)
+ return true;
+#elif defined(_WIN32)
+ // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate
+ BOOL f64 = FALSE;
+ return IsWow64Process(GetCurrentProcess(), &f64) && f64;
+#else
+ return false;
+#endif
+#else // ! LL_WINDOWS
+ // we only build a 64-bit mac viewer and currently we don't build for linux at all
+ return true;
+#endif
+}
+
LLCPUInfo::LLCPUInfo()
{
std::ostringstream out;
diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h
index 5ab97939b9..cb92cb0ac6 100644
--- a/indra/llcommon/llsys.h
+++ b/indra/llcommon/llsys.h
@@ -51,6 +51,8 @@ public:
const std::string& getOSStringSimple() const;
const std::string& getOSVersionString() const;
+
+ const S32 getOSBitness() const;
S32 mMajorVer;
S32 mMinorVer;
@@ -59,6 +61,7 @@ public:
#ifndef LL_WINDOWS
static S32 getMaxOpenFiles();
#endif
+ static bool is64Bit();
static U32 getProcessVirtualSizeKB();
static U32 getProcessResidentSizeKB();
@@ -66,6 +69,7 @@ private:
std::string mOSString;
std::string mOSStringSimple;
std::string mOSVersionString;
+ S32 mOSBitness;
};
diff --git a/indra/llcommon/tests/classic_callback_test.cpp b/indra/llcommon/tests/classic_callback_test.cpp
new file mode 100644
index 0000000000..c060775c24
--- /dev/null
+++ b/indra/llcommon/tests/classic_callback_test.cpp
@@ -0,0 +1,144 @@
+/**
+ * @file classic_callback_test.cpp
+ * @author Nat Goodspeed
+ * @date 2021-09-22
+ * @brief Test ClassicCallback and HeapClassicCallback.
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Copyright (c) 2021, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "classic_callback.h"
+// STL headers
+#include <iostream>
+#include <string>
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+
+/*****************************************************************************
+* example callback
+*****************************************************************************/
+// callback_t is part of the specification of someAPI()
+typedef void (*callback_t)(const char*, void*);
+void someAPI(callback_t callback, void* userdata)
+{
+ callback("called", userdata);
+}
+
+// C++ callable I want as the actual callback
+struct MyCallback
+{
+ void operator()(const char* msg, void*)
+ {
+ mMsg = msg;
+ }
+
+ void callback_with_extra(const std::string& extra, const char* msg)
+ {
+ mMsg = extra + ' ' + msg;
+ }
+
+ std::string mMsg;
+};
+
+/*****************************************************************************
+* example callback accepting several params, and void* userdata isn't first
+*****************************************************************************/
+typedef std::string (*complex_callback)(int, const char*, void*, double);
+std::string otherAPI(complex_callback callback, void* userdata)
+{
+ return callback(17, "hello world", userdata, 3.0);
+}
+
+// struct into which we can capture complex_callback params
+static struct Data
+{
+ void set(int i, const char* s, double f)
+ {
+ mi = i;
+ ms = s;
+ mf = f;
+ }
+
+ void clear() { set(0, "", 0.0); }
+
+ int mi;
+ std::string ms;
+ double mf;
+} sData;
+
+// C++ callable I want to pass
+struct OtherCallback
+{
+ std::string operator()(int num, const char* str, void*, double approx)
+ {
+ sData.set(num, str, approx);
+ return "hello back!";
+ }
+};
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct classic_callback_data
+ {
+ };
+ typedef test_group<classic_callback_data> classic_callback_group;
+ typedef classic_callback_group::object object;
+ classic_callback_group classic_callbackgrp("classic_callback");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("ClassicCallback");
+ // engage someAPI(MyCallback())
+ auto ccb{ makeClassicCallback<callback_t>(MyCallback()) };
+ someAPI(ccb.get_callback(), ccb.get_userdata());
+ // Unfortunately, with the side effect confined to the bound
+ // MyCallback instance, that call was invisible. Bind a reference to a
+ // named instance by specifying a ref type.
+ MyCallback mcb;
+ ClassicCallback<callback_t, void*, MyCallback&> ccb2(mcb);
+ someAPI(ccb2.get_callback(), ccb2.get_userdata());
+ ensure_equals("failed to call through ClassicCallback", mcb.mMsg, "called");
+
+ // try with HeapClassicCallback
+ mcb.mMsg.clear();
+ auto hcbp{ makeHeapClassicCallback<callback_t>(mcb) };
+ someAPI(hcbp->get_callback(), hcbp->get_userdata());
+ ensure_equals("failed to call through HeapClassicCallback", mcb.mMsg, "called");
+
+ // lambda
+ // The tricky thing here is that a lambda is an unspecified type, so
+ // you can't declare a ClassicCallback<signature, void*, that type>.
+ mcb.mMsg.clear();
+ auto xcb(
+ makeClassicCallback<callback_t>(
+ [&mcb](const char* msg, void*)
+ { mcb.callback_with_extra("extra", msg); }));
+ someAPI(xcb.get_callback(), xcb.get_userdata());
+ ensure_equals("failed to call lambda", mcb.mMsg, "extra called");
+
+ // engage otherAPI(OtherCallback())
+ OtherCallback ocb;
+ // Instead of specifying a reference type for the bound CALLBACK, as
+ // with ccb2 above, you can alternatively move the callable object
+ // into the ClassicCallback (of course AFTER any other reference).
+ // That's why OtherCallback uses external data for its observable side
+ // effect.
+ auto occb{ makeClassicCallback<complex_callback>(std::move(ocb)) };
+ std::string result{ otherAPI(occb.get_callback(), occb.get_userdata()) };
+ ensure_equals("failed to return callback result", result, "hello back!");
+ ensure_equals("failed to set int", sData.mi, 17);
+ ensure_equals("failed to set string", sData.ms, "hello world");
+ ensure_equals("failed to set double", sData.mf, 3.0);
+ }
+} // namespace tut