diff --git a/glib/tests/constructor.c b/glib/tests/constructor.c
new file mode 100644
index 000000000..029f5a110
--- /dev/null
+++ b/glib/tests/constructor.c
@@ -0,0 +1,251 @@
+/* constructor.c - Test for constructors
+ *
+ * Copyright © 2023 Luca Bacci
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see .
+ */
+
+#include
+#include "../gconstructorprivate.h"
+
+#ifndef _WIN32
+#include
+#else
+#include
+#endif
+
+#define LIB_PREFIX "lib_"
+#define APP_PREFIX "app_"
+
+#ifdef BUILD_LIBRARY
+#define PREFIX LIB_PREFIX
+#else
+#define PREFIX APP_PREFIX
+#endif
+
+#if G_HAS_CONSTRUCTORS
+
+#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
+#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS (ctor)
+#endif
+
+G_DEFINE_CONSTRUCTOR (ctor)
+
+static void
+ctor (void)
+{
+ const char *string = PREFIX "ctor";
+
+ g_assert_null (g_getenv (string));
+ g_setenv (string, "1", FALSE);
+}
+
+#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
+#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS (dtor)
+#endif
+
+G_DEFINE_DESTRUCTOR (dtor)
+
+static void
+dtor (void)
+{
+ const char *string = PREFIX "dtor";
+
+ g_assert_null (g_getenv (string));
+ g_setenv (string, "1", FALSE);
+}
+
+#endif /* G_HAS_CONSTRUCTORS */
+
+#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS)
+
+extern IMAGE_DOS_HEADER __ImageBase;
+static inline HMODULE
+this_module (void)
+{
+ return (HMODULE) &__ImageBase;
+}
+
+G_DEFINE_TLS_CALLBACK (tls_callback)
+
+static void NTAPI
+tls_callback (PVOID hInstance,
+ DWORD dwReason,
+ LPVOID lpvReserved)
+{
+ /* The HINSTANCE we get must match the address of __ImageBase */
+ g_assert_true (hInstance == this_module ());
+
+#ifndef BUILD_LIBRARY
+ /* Yes, we can call GetModuleHandle (NULL) with the loader lock */
+ g_assert_true (hInstance == GetModuleHandle (NULL));
+#endif
+
+ switch (dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ {
+ const char *string = PREFIX "tls_cb_process_attach";
+
+#ifdef BUILD_LIBRARY
+ /* the library is explicitly loaded */
+ g_assert_null (lpvReserved);
+#endif
+
+ g_assert_null (g_getenv (string));
+ g_setenv (string, "1", FALSE);
+ }
+ break;
+
+ case DLL_THREAD_ATTACH:
+ break;
+
+ case DLL_THREAD_DETACH:
+ break;
+
+ case DLL_PROCESS_DETACH:
+#ifdef BUILD_LIBRARY
+ {
+ const char *string = PREFIX "tls_cb_process_detach";
+
+ /* the library is explicitly unloaded */
+ g_assert_null (lpvReserved);
+
+ g_assert_null (g_getenv (string));
+ g_setenv (string, "1", FALSE);
+ }
+#endif
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+#endif /* _WIN32 && G_HAS_TLS_CALLBACKS */
+
+#ifndef BUILD_LIBRARY
+
+void *library;
+
+static void
+load_library (const char *library_path)
+{
+#ifndef _WIN32
+ library = dlopen (library_path, RTLD_LAZY);
+ if (!library)
+ g_error ("%s (%s) failed: %s", "dlopen", library_path, dlerror ());
+#else
+ wchar_t *library_path_u16 = g_utf8_to_utf16 (library_path, -1, NULL, NULL, NULL);
+ g_assert_nonnull (library_path_u16);
+
+ library = LoadLibraryW (library_path_u16);
+ if (!library)
+ g_error ("%s (%s) failed with error code %u",
+ "FreeLibrary", library_path, (unsigned int) GetLastError ());
+
+ g_free (library_path_u16);
+#endif
+}
+
+static void
+unload_library (void)
+{
+#ifndef _WIN32
+ if (dlclose (library) != 0)
+ g_error ("%s failed: %s", "dlclose", dlerror ());
+#else
+ if (!FreeLibrary (library))
+ g_error ("%s failed with error code %u",
+ "FreeLibrary", (unsigned int) GetLastError ());
+#endif
+}
+
+static void
+test_app (void)
+{
+#if G_HAS_CONSTRUCTORS
+ g_assert_cmpstr (g_getenv (APP_PREFIX "ctor"), ==, "1");
+#endif
+#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS)
+ g_assert_cmpstr (g_getenv (APP_PREFIX "tls_cb_process_attach"), ==, "1");
+#endif
+}
+
+static void
+test_lib (gconstpointer data)
+{
+ const char *library_path = (const char*) data;
+
+ /* Check that the environment is clean */
+#if G_HAS_CONSTRUCTORS
+ g_assert_null (g_getenv (LIB_PREFIX "ctor"));
+#endif
+#if G_HAS_DESTRUCTORS
+ g_assert_null (g_getenv (LIB_PREFIX "dtor"));
+#endif
+#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS)
+ g_assert_null (g_getenv (LIB_PREFIX "tls_cb_process_attach"));
+ g_assert_null (g_getenv (LIB_PREFIX "tls_cb_process_detach"));
+#endif
+
+ /* Constructors */
+ load_library (library_path);
+
+#if G_HAS_CONSTRUCTORS
+ g_assert_cmpstr (g_getenv (LIB_PREFIX "ctor"), ==, "1");
+#endif
+#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS)
+ g_assert_cmpstr (g_getenv (LIB_PREFIX "tls_cb_process_attach"), ==, "1");
+#endif
+
+ /* Destructors */
+ unload_library ();
+
+#if G_HAS_DESTRUCTORS
+ g_assert_cmpstr (g_getenv (LIB_PREFIX "dtor"), ==, "1");
+#endif
+#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS)
+ g_assert_cmpstr (g_getenv (LIB_PREFIX "tls_cb_process_detach"), ==, "1");
+#endif
+}
+
+int
+main (int argc, char *argv[])
+{
+
+ const char *libname = G_STRINGIFY (LIB_NAME);
+ const char *builddir;
+ char *path;
+ int ret;
+
+ g_assert_nonnull ((builddir = g_getenv ("G_TEST_BUILDDIR")));
+
+ path = g_build_filename (builddir, libname, NULL);
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/constructor/application", test_app);
+ g_test_add_data_func ("/constructor/library", path, test_lib);
+
+ ret = g_test_run ();
+
+ g_free (path);
+ return ret;
+}
+
+#endif /* !BUILD_LIBRARY */
diff --git a/glib/tests/meson.build b/glib/tests/meson.build
index 09ecd5ab3..5a06e1f52 100644
--- a/glib/tests/meson.build
+++ b/glib/tests/meson.build
@@ -306,6 +306,20 @@ if host_machine.system() == 'windows'
}
endif
+if host_machine.system() == 'windows' or have_dlopen_dlsym
+ constructor_lib = shared_library('constructor-lib',
+ 'constructor.c',
+ c_args: ['-DBUILD_LIBRARY'],
+ dependencies: [libglib_dep],
+ install : false)
+ glib_tests += {
+ 'constructor' : {
+ 'install': false,
+ 'c_args': ['-DLIB_NAME=' + fs.name(constructor_lib.full_path())]
+ }
+ }
+endif
+
foreach test_name, extra_args : glib_tests
foreach std: extra_args.get('c_standards', [])
if c_standards.has_key(std)