OSDN Git Service

Implement wrappers for emulation of POSIX dlfcn API.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Tue, 11 Nov 2014 18:13:14 +0000 (18:13 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Tue, 11 Nov 2014 18:13:14 +0000 (18:13 +0000)
mingwrt/ChangeLog
mingwrt/Makefile.in
mingwrt/include/dlfcn.h [new file with mode: 0644]
mingwrt/mingwex/dlfcn.c [new file with mode: 0644]

index 8406336..b909330 100644 (file)
@@ -1,3 +1,25 @@
+2014-11-11  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Implement wrappers for emulation of POSIX dlfcn API.
+
+       * include/dlfcn.h: New file; it declares the API.
+       * mingwex/dlfcn.c: New file; implement it, with access via...
+       (__mingw_dlfcn): ...this publicly addressable vector table.
+
+       * Makefile.in (dlfcn.$OBJEXT): Add build requisite.
+       (dlopen, dlsym, dlclose, dlerror): Build call stubs, using...
+       (__LIBIMPL__): ...this automatic interface generator; implement it.
+       (libstub_refnames): New macro; it is adapted from and replaces...
+       (jmpstub_refs): ...this; when it is then invoked twice to build...
+       (Makefile.stub): ...this, it handles both JMPSTUB and LIBIMPL.
+       (jmpstub_awk_script): Adapt it to work with 'libstub_refnames'.
+       (libimpl_awk_script): New inline script; it handles automated builds
+       of LIBIMPL interfaces, as 'jmpstub_awk_script' does for JMPSTUB.
+       (libimpl_sed_script): New inline script; it prepares intermediate C
+       source code for LIBIMPL interfaces, as required to satisfy these...
+       (%.libimpl, %.libimpl.$OBJEXT): ...new implicit build objectives.
+       (LIBIMPL_CFLAGS): New macro; define it.
+
 2014-11-08  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Move libgen functions to __mingw_ pseudo-namespace.
index 3cdb394..ed7c9f9 100644 (file)
@@ -464,7 +464,7 @@ libmingwex.a: $(addsuffix .$(OBJEXT), _Exit atoll lltoa lltow \
   ulltoa ulltow wtoll)
 
 libmingwex.a: gettimeofday.$(OBJEXT)
-libmingwex.a: $(addsuffix .$(OBJEXT), dirent wdirent)
+libmingwex.a: $(addsuffix .$(OBJEXT), dirent wdirent dlfcn)
 
 libmingwex.a: $(addsuffix .$(OBJEXT), fwide mbrtowc mbsinit wcrtomb wcstof \
   wcstold wctob wmemchr wmemcmp wmemcpy wmemmove wmemset)
@@ -490,21 +490,37 @@ $(addsuffix .$(OBJEXT), % %f %l): %_generic.c
 $(addsuffix .$(OBJEXT), llround llroundf llroundl): %.$(OBJEXT): lround_generic.c
        $(CC) -c -D FUNCTION=$* $(CPPFLAGS) $(ALL_CFLAGS) -o $@ $<
 
+# Historically, MinGW.org's libm.a has been a dummy, delivering
+# nothing of value; FIXME: IMO, this sucks; it should deliver the
+# non-MSVCRT.DLL math functions, as noted above.
+#
+all-mingwrt-libs install-mingwrt-libs: libm.a
+libm.a: libm_dummy.$(OBJEXT)
+libm_dummy.c: Makefile
+       echo "static int __mingw_libm_dummy;" > $@
+
 # The mingwrt headers define a number of functions which are normally
 # expected to be compiled as inline code.  Each such function must also
 # be provided with an externally visible entry point; we provide such
 # entry points as stubs in libmingwex.a, via the following rules:
 #
 sinclude Makefile.stub
-jmpstub_refs = grep -lr '__JMPSTUB.*FUNCTION'
-jmpstub_prerequisites := $(shell $(jmpstub_refs) ${mingwrt_srcdir}/include)
-Makefile.stub: Makefile $(jmpstub_prerequisites)
+vpath jmpstub.sx ${mingwrt_srcdir}/mingwex
+libstub_refnames = grep -lr '__$1.*FUNCTION *=' ${mingwrt_srcdir}
+jmpstub_prerequisites := $(shell $(call libstub_refnames,JMPSTUB)/include)
+libimpl_prerequisites := $(shell $(call libstub_refnames,LIBIMPL)/include)
+Makefile.stub: Makefile $(jmpstub_prerequisites) $(libimpl_prerequisites)
        echo "# $@: automatically generated file -- do not edit!" > $@
-       $(jmpstub_awk_script) $(jmpstub_prerequisites) >> $@
+       $(call jmpstub_awk_script,$(jmpstub_prerequisites)) >> $@
+       $(call libimpl_awk_script,$(libimpl_prerequisites)) >> $@
        echo "# $@: end of file" >> $@
 
-vpath jmpstub.sx ${mingwrt_srcdir}/mingwex
-jmpstub_awk_script = test "$^" = Makefile || awk '\
+# Stubs are categorized into either of JMPSTUB or LIBIMPL classes;
+# the rules for building the JMPSTUP inplementations are written to
+# Makefile.stub, by processing each of their defining header files
+# through the following awk script...
+#
+jmpstub_awk_script = test -z "$1" || awk '\
   BEGIN { \
     symbol = "([A-Z_a-z][A-Z_a-z0-9]*)"; \
     fmt = "\nlib%s.a: %s\n%s: jmpstub.sx\n\t$$(COMPILE.sx) %s -o $$@ $$^\n"; \
@@ -512,22 +528,60 @@ jmpstub_awk_script = test "$^" = Makefile || awk '\
   /__JMPSTUB(__)? *[(].*FUNCTION/ { \
     LIB = "mingwex"; \
     FUNCTION = gensub( ".*[ ,(:]FUNCTION *= *"symbol".*", "\\1", 1 ); \
-    OBJNAME = gensub( "_*(.*)_*", "\\1", 1, FUNCTION )".stub.$$(OBJEXT)"; \
+    OBJNAME = gensub( "_*(.*)_*", "\\1", 1, FUNCTION )".jmpstub.$$(OBJEXT)"; \
     OBJNAME_CFLAGS = "-D FUNCTION="FUNCTION; \
     printf fmt, LIB, OBJNAME, OBJNAME, OBJNAME_CFLAGS; \
   } \
   END { \
     printf "\n"; \
-  }'
+  }' $1
 
-all-mingwrt-libs install-mingwrt-libs: libm.a
-# Historically, MinGW.org's libm.a has been a dummy, delivering
-# nothing of value; FIXME: IMO, this sucks; it should deliver the
-# non-MSVCRT.DLL math functions, as noted above.
+# ...while this establishes the dependencies which apply for each
+# of those in the LIBIMPL class.
 #
-libm.a: libm_dummy.$(OBJEXT)
-libm_dummy.c: Makefile
-       echo "static int __mingw_libm_dummy;" > $@
+libimpl_awk_script = test -z "$1" || awk '\
+  BEGIN { \
+    symbol = "([A-Z_a-z][A-Z_a-z0-9]*)"; \
+    fmt = "\nlib%s.a: %s\n%s.libimpl: %s\n"; \
+  } \
+  /__LIBIMPL(__)? *[(].*FUNCTION/ { \
+    LIB = "mingwex"; \
+    FUNCTION = gensub( ".*[ ,(:]FUNCTION *= *"symbol".*", "\\1", 1 ); \
+    OBJNAME = gensub( "_*(.*)_*", "\\1", 1, FUNCTION )".libimpl.$$(OBJEXT)"; \
+    printf fmt, LIB, OBJNAME, FUNCTION, FILENAME; \
+  } \
+  END { \
+    printf "\n"; \
+  }' $1
+
+# In contrast to JMPSTUB implementations, which are best handled
+# by individual compilation rules in Makefile.stub, generic rules
+# are sufficient for all LIBIMPL implementations; in each case, we
+# filter the static attribute from each defined implementation, in
+# its defining header file, to create an intermediate C source for
+# each individual function; (note that we also remove any pragma
+# which identifies the originating file as a system header).
+#
+libimpl_sed_script = sed \
+  -e '/__CRT_ALIAS  *__LIBIMPL.*FUNCTION *= *$1[ ,)].*)/d' \
+  -e '/pragma .* system_header/d'
+
+LIBIMPL_CFLAGS = $(CFLAGS) $(INCLUDES) -fno-align-functions
+
+# LIBIMPL dependencies are tracked using zero sized name.libimpl
+# files; the intermediate C files are generated as a side effect
+# of resolving these dependencies...
+#
+%.libimpl:
+       $(call libimpl_sed_script,$*) $< > $@.c
+       > $@
+
+# ...and ultimately discarded, after the requisite object file
+# has been compiled.
+#
+%.libimpl.$(OBJEXT): %.libimpl
+       $(CC) -c $(CPPFLAGS) $(LIBIMPL_CFLAGS) -o $@ $<.c
+       $(RM) $<.c
 
 # Thread support libraries.
 #
diff --git a/mingwrt/include/dlfcn.h b/mingwrt/include/dlfcn.h
new file mode 100644 (file)
index 0000000..3653579
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * dlfcn.h
+ *
+ * Public interface declarations for (approximately) POSIX conforming
+ * dlopen(), dlsym(), dlclose(), and dlerror() API functions.
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2014, MinGW.org Project
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice, this permission notice, and the following
+ * disclaimer shall be included in all copies or substantial portions of
+ * the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+#ifndef _DLFCN_H
+#define _DLFCN_H  1
+#pragma GCC system_header
+
+#include <_mingw.h>
+
+/* POSIX requires the following four definitions.  Our implementation
+ * (currently) does not support the behaviour associated with RTLD_LAZY;
+ * if specified, it is simply ignored; RTLD_NOW behaviour will prevail.
+ */
+#define RTLD_NOW       0
+#define RTLD_LAZY      1
+#define RTLD_GLOBAL    2
+#define RTLD_LOCAL     4
+
+/* POSIX does not yet require the following pair, but reserves them for
+ * future capabilities; they will be used in contexts where a DLL module
+ * handle is expected, so we reserve values of suitable type, which are
+ * unlikely to ever occur as real module handles in practice.
+ */
+#define RTLD_DEFAULT   (void *)(-1)
+#define RTLD_NEXT      (void *)(-3)
+
+/* The four dlfcn API functions, dlopen(), dlsym(), dlerror(), and dlclose(),
+ * are each defined privately, and made publicly accessible via corresponding
+ * function pointers, within the following publicly visible structure.
+ */
+_EXTERN_C struct __dlfcn__
+{ void *(*dlopen)( const char *, int );
+  void *(*dlsym)( void *__restrict__, const char *__restrict__ );
+  char *(*dlerror)( void );
+  int (*dlclose)( void * );
+} __mingw_dlfcn;
+
+/* Declare the public API for each of these functions...
+ */
+_BEGIN_C_DECLS
+
+/* In any event, we always declare prototypes for all four functions.
+ */
+void * dlopen( const char *, int );
+void * dlsym( void *__restrict__, const char *__restrict__ );
+int    dlclose( void * );
+char * dlerror( void );
+
+__CRT_ALIAS __LIBIMPL__(( FUNCTION = dlopen ))
+void *dlopen( const char *__name, int __mode )
+{ return __mingw_dlfcn.dlopen( __name, __mode ); }
+
+__CRT_ALIAS __LIBIMPL__(( FUNCTION = dlsym ))
+void *dlsym( void *__restrict__ __module, const char *__restrict__ __name )
+{ return __mingw_dlfcn.dlsym( __module, __name ); }
+
+__CRT_ALIAS __LIBIMPL__(( FUNCTION = dlclose ))
+int dlclose( void *__module ){ return __mingw_dlfcn.dlclose( __module ); }
+
+__CRT_ALIAS __LIBIMPL__(( FUNCTION = dlerror ))
+char *dlerror( void ){ return __mingw_dlfcn.dlerror(); }
+
+_END_C_DECLS
+
+#endif /* _DLFCN_H: $RCSfile$: end of file */
diff --git a/mingwrt/mingwex/dlfcn.c b/mingwrt/mingwex/dlfcn.c
new file mode 100644 (file)
index 0000000..59dbda2
--- /dev/null
@@ -0,0 +1,722 @@
+/*
+ * dlfcn.c
+ *
+ * Core implementation for (approximately) POSIX conforming dlopen(),
+ * dlsym(), dlclose(), and dlerror() API functions.
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2014, MinGW.org Project
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice, this permission notice, and the following
+ * disclaimer shall be included in all copies or substantial portions of
+ * the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ *
+ * This source file implements the core functionality for each of the POSIX
+ * dynamic loader API functions, dlopen(), dlsym(), dlclose(), and dlerror();
+ * each is implemented such that it is conveniently accessed via a macro, or
+ * an inline function representation, by way of a publicly visible table of
+ * entry point vectors (i.e. function pointers).
+ *
+ */
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+
+/* In addition to normal C runtime services, this implementation requires
+ * the use of the MS-Windows API; the light-weight subset will suffice...
+ */
+#define WIN32_LEAN_AND_MEAN
+/*
+ * ...while accessing it throught its standard interface.
+ */
+#include <windows.h>
+
+/* In addition to the POSIX constants which are defined in dlfcn.h,
+ * we also define some private manifest constants, which POSIX does
+ * not specify, but which facilitate our implementation:
+ *
+ *   RTLD_EXPLICIT, used to qualify RTLD_GLOBAL modules which have
+ *   been explicitly loaded by dlopen(), so an RTLD_DEFAULT search
+ *   need not consider them.
+ */
+# define RTLD_EXPLICIT     16
+/*
+ *   RTLD_ALL_GLOBAL, returned by dlopen() when the module name is
+ *   given as a NULL pointer; this is a fake module handle, similar
+ *   to RTLD_DEFAULT and RTLD_NEXT, representing the corpus of all
+ *   RTLD_GLOBAL modules, both implicitly and explicitly loaded.
+ */
+# define RTLD_ALL_GLOBAL  (void *)(-5)
+
+/* Before anything else, ensure that the dlerror() implementation
+ * is in place, so that other components may access it freely.
+ *
+ * We provide TWO reference pointers for error message buffering, so
+ * that we may continue to hold a reference to allocated memory, even
+ * after dlerror() has discarded the pointer to a pending message;
+ * initially, we mark both as unassigned.
+ */
+static char *dlfcn_error_pending = NULL;
+static char *dlfcn_error_message = NULL;
+
+static void dlfcn_store_error_message( const char *fmt, ... )
+{
+  /* This private function provides printf() style formatting for
+   * dlfcn error messages, storing them into dynamically allocated
+   * memory, and updating both reference pointers for subsequent
+   * retrieval by the dlerror() accessor function.
+   */
+  int msglen;
+  va_list argv;
+  va_start( argv, fmt );
+  msglen = 1 + vsnprintf( NULL, 0, fmt, argv );
+  if( (dlfcn_error_pending = realloc( dlfcn_error_message, msglen )) != NULL )
+    /*
+     * Store message, only if a buffer was successfully allocated.
+     */
+    vsnprintf( dlfcn_error_pending, msglen, fmt, argv );
+  dlfcn_error_message = dlfcn_error_pending;
+  va_end( argv );
+}
+
+static char *dlfcn_strerror( int errcode )
+{
+  /* This private function emulates strerror(), but substitutes an
+   * errcode obtained from GetLastError() for errno.
+   */
+  char *text;
+  uint32_t description = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER
+      | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
+      errcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (char *)(&text),
+      0, NULL
+    );
+  if( description )
+  {
+    /* We successfully obtained an error description; POSIX insists
+     * that it must not be encoded with any terminating newline.
+     */
+    char *tmp = text + strlen( text );
+    while( (*--tmp == '\n') || (*tmp == '\r') ) *tmp = '\0';
+
+    /* Ensure that we consistently return the error description in
+     * a buffer which may be released by the free() function, (since
+     * we cannot be sure that LocalFree() is compatible).
+     */
+    text = strdup( tmp = text );
+    LocalFree( tmp );
+  }
+  else
+  { /* We were unable to find an error description; substitute a
+     * formatted reference to the unknown error code.
+     */
+    char *fmt = "Unknown error %d";
+    char tmp[1 + snprintf( NULL, 0, fmt, errcode )];
+    snprintf( tmp, sizeof( tmp ), fmt, errcode );
+    text = strdup( tmp );
+  }
+  /* However we derived it, the error description is now available
+   * in a dynamically allocated text buffer which may be released
+   * by calling free().
+   */
+  return text;
+}
+
+static char *dlerror_internal( void )
+{
+  /* This is the internal implementation of the public dlerror() API.
+   * POSIX does not require this to be thread safe, so we take no care
+   * to make it such.
+   */
+  if( (dlfcn_error_pending == NULL) && (dlfcn_error_message != NULL) )
+  {
+    /* There is no pending message, but a buffer remains allocated
+     * to one which has already been retrieved; we may release it.
+     */
+    free( dlfcn_error_message );
+    dlfcn_error_message = NULL;
+  }
+  /* Mark any pending error message as "retrieved"...
+   */
+  dlfcn_error_pending = NULL;
+  /*
+   * ...and return it.
+   */
+  return dlfcn_error_message;
+}
+
+typedef
+/* A pointer type, representing a reference to a function for
+ * retrieving exported symbol references from any loaded module.
+ */
+int WINAPI (*lookup_fn)( HANDLE, HMODULE *, uint32_t, uint32_t * );
+
+/* POSIX requires that dlsym() be able to look up symbol references
+ * for any dynamically loaded module within the address space of the
+ * calling process, including those which have been implicitly loaded
+ * at process start-up.  To facilitate this, we will use Microsoft's
+ * PSAPI.DLL services, to enumerate such implicitly loaded modules,
+ * before we explicitly load any using dlopen(), or we perform any
+ * look-up using dlsym() itself.
+ */
+static struct
+{ /* Structure providing a convenient encapsulation of the PSAPI.DLL
+   * interface, which may itself need to be explicitly loaded.
+   */
+  HMODULE       dll;
+  const char   *dll_name;
+  const char   *lookup_fn_name;
+  lookup_fn     lookup;
+}
+psapi = { NULL, "psapi.dll", "EnumProcessModules", NULL };
+
+/* The two NULL fields, within the preceding structure, must be
+ * initialized before use; the following pair of inline functions
+ * facilitate this, with the first looking up the API entry...
+ */
+static __inline__ lookup_fn psapi_lookup_fn( HMODULE provider )
+{ return (lookup_fn)(GetProcAddress( provider, psapi.lookup_fn_name )); }
+
+/* ...when called from within the second, which both ensures that
+ * PSAPI.DLL is loaded, and assigns the requisite field values.
+ */
+static __inline__ lookup_fn psapi_lookup_fn_init( void )
+{ return psapi.lookup = ((psapi.dll = LoadLibrary( psapi.dll_name )) != NULL)
+    ? psapi_lookup_fn( psapi.dll ) : NULL;
+}
+
+/* After the PSAPI.DLL interface has been initialized, by calling
+ * the preceding function, we may call the following inline helper
+ * to retrieve a list of module handles...
+ */
+static __inline__ unsigned int psapi_enum_modules( HMODULE **modules )
+{
+  /* ...focussing on modules which are currently loaded within
+   * the address space of the current process.
+   */
+  HANDLE me = GetCurrentProcess();
+
+  /* We will dynamically allocate memory to store the handles;
+   * initially we request zero bytes of storage to receive them,
+   * then we use a while loop, which should execute exactly twice,
+   * to retrieve them; on the first pass, we determine the actual
+   * memory requirement and allocate it; on the second, we store
+   * the list of module handles into it...
+   */
+  uint32_t wanted, request = 0;
+  while( psapi.lookup( me, *modules, request, &wanted ) && (wanted > request) )
+    if( (*modules = realloc( *modules, request = wanted )) == NULL )
+    {
+      /* ...trying to record an appropriate diagnostic message on
+       * failure, (but noting that failure is likely to result from
+       * insufficient memory, so there may not be enough to record
+       * the message either)...
+       */
+      char *reason = dlfcn_strerror( ERROR_OUTOFMEMORY );
+      dlfcn_store_error_message( "dlfcn_init:enum_modules: %s", reason );
+      free( reason );
+    }
+  /* ...before ultimately returning the number retrieved.
+   */
+  return wanted / sizeof( HMODULE );
+}
+
+typedef struct dltab
+{
+  /* Structure used to map module handle references, and associated
+   * status, into the global modules list.
+   */
+  unsigned int   slots;
+  unsigned char *flags;
+  HMODULE      *modules;
+} dltab;
+
+/* The global modules list itself, initially empty, but suitable
+ * for reallocation on the heap.
+ */
+static dltab rtld = { 0, NULL, NULL };
+
+/* Microsoft's LoadLibrary() API is explicitly documented as being
+ * unable to handle regular slashes as directory separators in module
+ * path names, (in spite of their validity elsewhere), thus...
+ */
+static __inline__ wchar_t *normalized_form( wchar_t *pathname )
+{
+  /* ...we provide this helper, to replace them with backslashes.
+   */
+  wchar_t *scan = pathname;
+  do { if( *scan == L'/' ) *scan = L'\\'; } while( *scan++ != L'\0' );
+  return pathname;
+}
+
+static void dlopen_store_error_message( const char *name, unsigned status )
+{
+  /* A convenience helper, to record diagnostic messages to explain
+   * causes of failure encountered when calling dlopen().
+   */
+  char *reason = dlfcn_strerror( status );
+  dlfcn_store_error_message( "dlopen:'%s': %s", name, reason );
+  free( reason );
+}
+
+static void *dlopen_internal( const char *name, int mode )
+{
+  /* This is the formal implementation of the public dlopen() function;
+   * note that this (currently) ignores the RTLD_LAZY loading option, and
+   * processes each request as if specified as RTLD_NOW.
+   */
+  if( name == NULL )
+    /* POSIX specifies this as a special case, requiring us to return
+     * a handle, through which all symbols exported by all modules which
+     * are currently loaded with the RTLD_GLOBAL attribute, (explicitly
+     * or implicitly), would be accessible.
+     */
+    return RTLD_ALL_GLOBAL;
+
+  /* We will use LoadLibrary() to obtain a handle for the dlopen()ed
+   * module; Microsoft advise us that we should ensure that we always
+   * use backslashes, and to avoid use of POSIX compatible slashes, in
+   * any path name passed to this function.  To ensure this, the name
+   * argument should be normalized, (working in the UTF-16LE domain,
+   * to avoid inadvertent transformation of trail bytes within MBC
+   * sequences), to transform slashes accordingly.
+   */
+  size_t buflen; wchar_t internal_name[buflen = 1 + strlen( name )];
+  MultiByteToWideChar( CP_ACP, 0, name, -1, internal_name, buflen );
+
+  /* Now, we may safely call LoadLibrary(), to obtain a module handle.
+   */
+  void *module = LoadLibraryW( normalized_form( internal_name ) );
+  if( module == NULL )
+  {
+    /* The named module could not be opened; record an appropriate
+     * error message for retrieval by dlerror().
+     */
+    dlopen_store_error_message( name, GetLastError() );
+  }
+  else
+  { /* We got a handle for the requested module; we need to ensure
+     * that it is allocated a slot in our global symbol table, but we
+     * must first check that it isn't already present.
+     */
+    int index, insertion_point = rtld.slots;
+    for( index = 0; index < rtld.slots; index++ )
+    {
+      /* As we scan the list of already loaded modules, check for any
+       * existing slot which may be vacant...
+       */
+      if( rtld.flags[index] == 0 )
+      {
+       /* ...marking the first available, if any, as a candidate for
+        * possible insertion of a new entry.
+        */
+       if( index < insertion_point )
+         insertion_point = index;
+      }
+      else if( module == rtld.modules[index] )
+      {
+       /* The requested module appears to be loaded already; calling
+        * LoadLibrary() will have increased its reference count, but
+        * our management strategy doesn't require this; reduce it...
+        */
+       FreeLibrary( module );
+       /*
+        * ...but promote its existing status to RTLD_GLOBAL, if that
+        * is indicated as required by the requested mode...
+        */
+       rtld.flags[index] |= mode & RTLD_GLOBAL;
+       /*
+        * ...and immediately return the module handle.
+        */
+       return module;
+      }
+    }
+    /* If we get to here, there is no entry for the requested module,
+     * within the global modules list; we must add it now, either using
+     * an existing vacant slot, if there is one...
+     */
+    if( insertion_point < rtld.slots )
+    {
+      /* ...but noting that we cannot simply insert a new reference
+       * within it, since that would disrupt the order in which modules
+       * are subsequently searched, and POSIX requires that this is to
+       * preserve loading order, (strictly, symbol resolution order),
+       * when searching RTLD_GLOBAL modules, (and since any module may
+       * be promoted to RTLD_GLOBAL status, even after it was originally
+       * loaded as RTLD_LOCAL, this means that we must preserve loading
+       * order for ALL active modules).  Thus, we must pack the list of
+       * active modules after the nominated insertion point...
+       */
+      index = insertion_point;
+
+      /* ...after which, the first vacant slot will have been relocated
+       * to follow all active slots, and we may adjust the nominated
+       * insertion point accordingly.
+       */
+      do { /* First, we identify the first slot following the nominated
+           * insertion point, which is NOT vacant.
+           */
+          while( (index < rtld.slots) && (rtld.flags[index] == 0) )
+            ++index;
+
+          /* Now, we move that non-vacant slot, and any which follow
+           * it, upwards in the list, to fill vacant slots...
+           */
+          while( (index < rtld.slots) && (rtld.flags[index] != 0) )
+          {
+            /* ...by simply copying content from the non-vacant slots
+             * to overwrite content in the preceding slots...
+             */
+            rtld.modules[insertion_point] = rtld.modules[index];
+            rtld.flags[insertion_point++] = rtld.flags[index++];
+          }
+          /* ...repeating the entire procedure, until all vacant slots
+           * have been filled, and the nominated insertion point has
+           * been moved to follow the last relocated non-vacant entry.
+           */
+        } while( index < rtld.slots );
+
+      /* After packing, any already allocated slots after and including
+       * the relocated insertion point MUST be vacant; ensure that they
+       * are marked accordingly.
+       */
+      for( index = insertion_point; index < rtld.slots; index++ )
+       rtld.flags[index] = 0;
+    }
+    else
+    { /* There is no vacant slot: we must expand the allocated memory
+       * pool to create one; first increment the modules list size...
+       */
+      size_t slots = 1 + rtld.slots;
+      HMODULE *modules = rtld.modules;
+      if( (modules = realloc( modules, sizeof( HMODULE ) * slots )) != NULL )
+      {
+       /* ...and, having sucessfully increased the modules list memory
+        * allocation, do likewise for the associated flags...
+        */
+       unsigned char *flags = rtld.flags;
+       if( (flags = realloc( flags, slots )) != NULL )
+         /*
+          * ...initializing the new flags register, and incrementing
+          * the slots count, when that is also successful.
+          */
+         (rtld.flags = flags)[rtld.slots++] = 0;
+
+       /* Regardless of the success, or otherwise, of the flags memory
+        * adjustment, the modules list was reallocated, so we need to
+        * adjust its reference pointer accordingly.
+        */
+       rtld.modules = modules;
+      }
+      /* Before proceeding further, verify that the new slot has been
+       * fully created, and is ready to store the module data...
+       */
+      if( insertion_point == rtld.slots )
+      {
+       /* ...but if allocation failed, the nominated insertion point
+        * will lie beyond the available space, so we MUST fail, after
+        * discarding the now unreferenced module handle, and trying to
+        * record an appropriate diagnostic message, (but we note that
+        * this may also fail, due to insufficient memory).
+        */
+       FreeLibrary( module );
+       dlopen_store_error_message( name, ERROR_OUTOFMEMORY );
+       return NULL;
+      }
+    }
+    /* When we get to here, we now have a suitable slot in which to add
+     * the reference data for the newly dlopen()ed module, (either by
+     * allocation of a new slot, or by relocation of an existing vacant
+     * slot); we may safely store the appropriate reference data.
+     */
+    rtld.flags[insertion_point] = RTLD_EXPLICIT | mode;
+    rtld.modules[insertion_point] = module;
+  }
+  /* Finally, we return whatever module handle we got from LoadLibrary(),
+   * (which may be NULL, if this failed).
+   */
+  return module;
+}
+
+static void *dlsym_internal( void *module, const char *name )
+{
+  /* This is the formal implementation of the public dlsym() function.
+   */
+  static unsigned int index = 0;
+  static const char *last_named_symbol = NULL;
+  static unsigned char rtld_exclude = 0;
+  void *rtn;
+
+  if( module == RTLD_NEXT )
+  {
+    /* NOTE: We MUST identify this special case BEFORE any other!
+     *
+     * POSIX doesn't require this to be supported yet, but reserves it
+     * for future use; it should cause dlsym() to repeat its search for
+     * the named symbol, (provided it remains the same as last named in
+     * a global search), continuing from the next module in the current
+     * search order, following that in which the symbol was previously
+     * found; (this permits us to locate symbols which may defined in
+     * more than one loaded module).
+     */
+    index = (name == last_named_symbol) ? index + 1 : 0;
+    module = RTLD_ALL_GLOBAL;
+  }
+  else if( module == RTLD_DEFAULT )
+  {
+    /* NOTE: We MUST keep this AFTER the check for RTLD_NEXT!
+     *
+     * Once again, POSIX doesn't require this to be supported yet, but
+     * reserves it for future use to search for symbols which could have
+     * been found within the process address space prior to any explicit
+     * dlopen() call; this capability may be supported by searching all
+     * modules in the address space, (i.e. equivalent to a search with
+     * module == RTLD_ALL_GLOBAL), excluding those which have been
+     * explicitly loaded since process start-up.
+     */
+    module = RTLD_ALL_GLOBAL;
+    rtld_exclude = RTLD_EXPLICIT;
+    index = 0;
+  }
+  else
+    /* Neither RTLD_DEFAULT, nor RTLD_NEXT was specified; we must reset
+     * the RTLD_GLOBAL search index, and cancel all search exclusions.
+     */
+    index = rtld_exclude = 0;
+
+  if( module == RTLD_ALL_GLOBAL )
+  {
+    /* The RTLD_ALL_GLOBAL module reference represents a request to
+     * perform an in-order traversal of all modules within the process
+     * address space, either implicitly loaded, or explicitly loaded
+     * with the RTLD_GLOBAL mode attribute, either until the named
+     * symbol is found, or all such modules have been searched
+     * without finding it.
+     */ 
+    for( rtn = NULL; (rtn == NULL) && (index < rtld.slots); index++ )
+      if( ((rtld_exclude & rtld.flags[index]) == 0)
+      &&  ((rtld.flags[index] & RTLD_GLOBAL) == RTLD_GLOBAL)  )
+       rtn = GetProcAddress( rtld.modules[index], name );
+
+    /* Note the symbol named in the current search, so that we may
+     * check for consistency in the event that the next search is
+     * invoked as an RTLD_NEXT request.
+     */
+    last_named_symbol = name;
+  }
+  else
+  { /* When a specific module reference is specified, confine the
+     * search to the specified module alone...
+     */
+    rtn = GetProcAddress( (HMODULE)(module), name );
+
+    /* ...and inhibit any attempt to follow this search with one
+     * specifying RTLD_NEXT; (this would not be valid, since there
+     * is no concept of a "next" module to be searched, when not
+     * searching through an ordered list of modules).
+     */
+    last_named_symbol = NULL;
+  }
+
+  if( rtn == NULL )
+  {
+    /* The named symbol was not found in any module which was searched;
+     * record the appropriate error message for retrieval by dlerror().
+     */
+    char *reason = dlfcn_strerror( GetLastError() );
+    dlfcn_store_error_message( "dlsym:'%s': %s", name, reason );
+    free( reason );
+  }
+
+  /* Return the symbol address, as assigned to the return value;
+   * (this will be NULL, if the named symbol was not found).
+   */
+  return rtn;
+}
+
+static int dlclose_store_error_message( int status )
+{
+  /* A private helper function to record an appropriate dlerror()
+   * message, on failure of dlclose().
+   */
+  char *reason = dlfcn_strerror( status = GetLastError() );
+  dlfcn_store_error_message( "dlclose: %s", reason );
+  free( reason );
+  return status;
+}
+
+static __inline__ int dlclose_internal_check_for_error( int status )
+{
+  /* A private helper function to set the return status for dlclose(),
+   * while also recording a dlerror() message, when status is "failed".
+   */
+  return (status == 0) ? dlclose_store_error_message( status ) : 0;
+}
+
+static int dlclose_internal( void *module )
+{
+  /* This is the formal implementation of the public dlclose() function;
+   * it will call Microsoft's FreeLibrary() function passing the specified
+   * module handle, provided this is listed in the global module table as
+   * having been explicitly opened by our dlopen() function.
+   */
+  int index;
+  for( index = 0; index < rtld.slots; index++ )
+    if( module == rtld.modules[index] )
+    {
+      /* The specified module handle is present in the global modules list;
+       * while we could simply call FreeLibrary() immediately, it may not be
+       * prudent to do so in respect of implicitly loaded modules, but for
+       * those which we have explicitly loaded...
+       */
+      if( ((rtld.flags[index] & RTLD_EXPLICIT) == RTLD_EXPLICIT)
+      /*
+       * ...and which can be successfully released by FreeLibrary()...
+       */
+      &&  (dlclose_internal_check_for_error( FreeLibrary( module )) == 0)  )
+       /*
+        * ...we mark them as no longer available for dlsym() processing,
+        * and return immediately, indicating success...
+        */
+       return rtld.flags[index] = 0;
+
+      /* ...but when we didn't successfully release the module, we have
+       * no need to continue the search for its handle in the global list
+       * of modules, (because we've already found it); we may immediately
+       * abandon the search.
+       */
+      break;
+    }
+
+  /* If we get to here, we either didn't find the specified module handle in
+   * the global list of modules, or we found it but were unable to release
+   * it; in either case, we force a module error condition.
+   */
+  return dlclose_store_error_message( FreeLibrary( NULL ) );
+}
+
+static void dlfcn_init( void )
+{
+  /* This private initialization function must be called, as a
+   * prerequisite to the first use of either dlopen() or dlsym()
+   * in any process; it uses Microsoft's PSAPI.DLL interface to
+   * enumerate the implicitly loaded process modules, so that
+   * they may be searched implicitly by dlsym().
+   */
+  if( psapi_lookup_fn_init() == NULL )
+  {
+    /* Initialization of the PSAPI.DLL interface failed.  Record
+     * this for possible retrieval by dlerror(); we can do no more,
+     * so the list of implicitly loaded mudules will remain empty.
+     */
+    char *reason = dlfcn_strerror( GetLastError() );
+    dlfcn_store_error_message( "dlfcn_init:%s: %s", psapi.dll_name, reason );
+    free( reason );
+  }
+  else
+  { /* We've acquired a reference for the PSAPI EnumProcessModules()
+     * service; invoke it, to obtain a list of all modules which are
+     * currently accessible, within the address space of the current
+     * process, (which we assume to represent all modules which were
+     * loaded implicitly, at process start-up)...
+     */
+    rtld.slots = psapi_enum_modules( &(rtld.modules) );
+    /*
+     * ...but, noting that we explicitly added a refererence for the
+     * PSAPI.DLL services interface, which we needed to acquire this
+     * list, we may now discard that reference.
+     */
+    FreeLibrary( psapi.dll );
+
+    /* Provided we've obtained a non-empty list of modules...
+     */
+    if(  (rtld.modules != NULL)
+    /*
+     * ...and we can successfully allocate storage for an associated
+     * collection of flags registers, (one per module)...
+     */
+    &&  ((rtld.flags = realloc( rtld.flags, rtld.slots )) != NULL)  )
+    {
+      /* ...examine each reference in the modules list, and for each
+       * which is not NULL...
+       */
+      unsigned int index;
+      for( index = 0; index < rtld.slots; index++ )
+       if( (rtld.modules[index] != NULL)
+       /*
+        * ...and which is not the PSAPI.DLL reference we've already
+        * released...
+        */
+       &&  ((rtld.modules[index] != psapi.dll)
+       /*
+        * ...unless it continues to provide a valid reference to the
+        * PSAPI EnumProcessModules() service, (which indicates that
+        * the associated DLL had been implicitly loaded, BEFORE we
+        * explicitly requested it)...
+        */
+       ||  (psapi_lookup_fn( psapi.dll ) == psapi.lookup))  )
+         /*
+          * ...then we set the associated flags to indicate that the
+          * module was implicitly loaded and globally accessible...
+          */
+         rtld.flags[index] = RTLD_GLOBAL;
+       else
+         /* ...otherwise, we mark it as if dlclose() has already been
+          * implicitly called, to discard it.
+          */
+         rtld.flags[index] = 0;
+
+      /* Having thus populated the global modules list, on first call
+       * to either dlopen() or dlsym(), we don't need to (and indeed we
+       * shouldn't) do this again; redirect each of these two calls to
+       * their respective internal handlers.
+       */
+      __mingw_dlfcn.dlopen = dlopen_internal;
+      __mingw_dlfcn.dlsym = dlsym_internal;
+    }
+  }
+}
+
+/* The global symbol table needs to be initialized, before we process
+ * the first call to either dlopen() or dlsym(); the following pair of
+ * initializer functions take care of this requirement, before passing
+ * the first-time request to the appropriate internal handler.
+ */
+static void *dlopen_init( const char *name, int mode )
+{ dlfcn_init(); return dlopen_internal( name, mode ); }
+
+static void *dlsym_init( void *module, const char *name )
+{ dlfcn_init(); return dlsym_internal( module, name ); }
+
+/* Finally, we may define the __mingw_dlfcn structure, and set up its
+ * initial function pointers, referring to the four API functions...
+ */
+struct __dlfcn__ __mingw_dlfcn =
+{ dlopen_init,         /* dlopen() implementation, with initialization. */
+  dlsym_init,          /* dlsym() implementation, with initialization.  */
+  dlerror_internal,    /* dlerror() implementation; (direct reference). */
+  dlclose_internal     /* dlclose() implementation; (direct reference). */
+};
+
+/* $RCSfile$: end of file */