You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Alexei Kosut <ak...@organic.com> on 1997/07/08 03:38:56 UTC

Dynamic loading of Windows modules (+mod_dll.c_

Whee! I've finally (after a week of trying - so please excuse me if I
seem rather enthused and I babble a little) made the NT version of
Apache capable of dynamically loading modules that have been compiled as
DLLs. I've stated before that, since 99% of Windows users don't own
compilers, this is an important component of a Windows release, much
moreso than with Unix. What it means to the user is that they can have a
pre-compiled module file, and can add it to the server simply by placing
it into a directory and adding a line to their configuration file, without
recompilation. The only downside is that it is neccessary, in order to
have this work, is that the standard Apache binary cannot simply be an
"apache.exe" file. It must be both an "apache.exe" file and a
"libapache.dll" that must tag along. I personally don't see this as much
of a program, but maybe it is?

The rest of this email will detail the technical aspects of how I've done
it, for those who care.

First, I wrote a mod_dll.c. I've attached it to the end of this email.
Basically, it's a mod_dld.c that has been converted to use the Windows DLL
functions. Put it in the nt directory and add the appropriate lines to
nt/modules.c.

Because the Win32 DLL functionality is not nearly as flexible as Unix
shared libraries, it is slightly complex to get it to work: First, take
the existing apache.mak. Turn it into a DLL (add /dll to the linker
options). I name the output "libapache.dll"; it doesn't really matter.
Also create a .DEF file with all the functions you'd like to export to
modules - I just crunched the output of nm on a Unix machine (see the
Visual C++ manual for the .DEF file format), and removed the Unix-only
functions. I can provide that file on request, but it should probably be
given a little more thought. Then add "/def:libapache.def" to the link
options. Now compile. A libapache.dll will be produced.

In order to run the server, an .exe file of course is needed. So I made
one, linking to libapache.lib (a library containing stubs to the exported
functions from the DLL - it will cause Windows to look for the DLL and
load it). I couldn't get VC++ to work without at least one source file, so
I added a blank dummy one. This creates an apache.exe. As long as
libapache.dll is in the same directory as apache.exe or in %PATH%,
apache.exe works just like it did before.

It's very simple to compile a module as a DLL. Compile yourself a DLL,
using just the module's source, and again linking in the libapache.lib
import library. However, you do need to make one small change to the
module: because you cannot export variables from a DLL, only functions,
mod_dld can't get at the module record unless there's a function that
returns it. So add one:

#ifdef WIN32
module __declspec(dllexport) *get_foo_module(void) {
    return &foo_module;
}
#endif

This doesn't interfere with normal, compiled-in, operations, so it's safe
to leave it in. It just returns the module record. It's neccessary to name
the function with a "get_" prefix so that it doesn't conflict with the
module record. The __declspec(dllexport) does the same thing as a .DEF
file - it exports the function. This is, however, the only function you
need to export. All the other functions have pointers in the module
record, so Apache can get at them from within the DLL, without needing
exporting.

Then just put mod_foo.dll into the server root, throw in a
"LoadModule foo_module mod_foo.dll", and voila! Note that this only works
at server startup. If the server is restarted (HUP or USR1), it will
ignore any new/changed/deleted LoadModules. Kill and start again.

Anyhow, it's pretty simple. I can't contribute Makefiles if we want to
keep with Visual C++ 4.2 - I only have VC++ 5.0, and they're not
compatible with older versions. But I think we should make the default
distribution (the binary/non-source one) an apache.exe/libapache.dll with
the default set of modules, then include the extras (usertrack, proxy,
etc..) as DLLs. Should make things easier, neh?

Anyhow, here's mod_dll.c (be sure to remove my sig from the end):

/* ====================================================================
 * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
 * mod_dll.c - DLL module loader for Windows
 * by Alexei Kosut, based on mod_dld.c by rst
 *
 * This module loads another module into the server that has been
 * compiled as a DLL. It doesn't work perfectly, but well enough.
 *
 * To use, compile the module into a DLL. Then add the following to the
 * server's config file (before any directives belonging to the loaded module):
 *
 * LoadModule module_name mod_name.dll
 *
 * module_name should be the name of the module (e.g. includes_module),
 * and mod_name.dll should be the name of the DLL, relative to the server
 * root.
 *
 * There is also a directive that will load a non-module DLL, if you'd
 * like to load additional libraries into the server:
 *
 * LoadFile filename.dll
 *
 * Compiling a module as a DLL (using Microsoft Visual C++):
 *
 * 1. Add the following to the module source file (if the module will be
 *    used for both Unix and Windows, surround with #ifdef WIN32). Replace
 *    "foo_module" with the name of your module (e.g. "includes_module"):
 *
 *    module __declspec(dllexport) *get_foo_module(void) {
 *        return &foo_module;
 *    }
 *
 *    Note that your module should still work just fine compiled-in
 *    with this code bit there. It only activates when using the module
 *    as a DLL.
 *
 * 2. Create a DLL file with just the module source file (and any associated
 *    files). Be sure to link it against the libapache.lib created when
 *    compiling libapache.dll. You may also have to tweak the settings to
 *    find all of the Apache includes files correctly. After creating the
 *    DLL, follow the above instructions to load it into Apache.
 */

#include "../httpd.h"
#include "../http_config.h"
#include "../http_conf_globals.h"	/* server_argv0.  Sigh... */

/*
 * The hard part of implementing LoadModule is deciding what to do about
 * rereading the config files.  This proof-of-concept implementation takes the 
 * cheap way out:  we only actually load the modules the first time through.
 */

static int been_there_done_that = 0; /* Loaded the modules yet? */
static int have_symbol_table = 0;

char *load_module (cmd_parms *cmd, void *dummy, char *modname, char *filename)
{
    HINSTANCE modhandle;
	char *getmodname = pstrcat(cmd->pool, "get_", modname, NULL);
    module *modp;
	module *(*getmodfunc)(void);

    if (been_there_done_that) return NULL;
    
    if (!(modhandle = LoadLibrary(server_root_relative(cmd->pool, filename))))
	return pstrcat (cmd->pool, "Cannot load ", filename, " into server",
			NULL);
 
    /* If I knew what the correct cast is here, I'd be happy. But 
     * I don't. So I'll use (void *). It works.
     */
    if (!(getmodfunc = (void *)(GetProcAddress (modhandle, getmodname)))) {
	return pstrcat (cmd->pool, "Can't find module ", modname,
			" in file ", filename, NULL);
    }
	
    modp = (*getmodfunc)();

    add_module (modp);

    /* Alethea Patch (rws,djw2) - need to run configuration functions
       in new modules */

    if (modp->create_server_config)
      ((void**)cmd->server->module_config)[modp->module_index]=
	(*modp->create_server_config)(cmd->pool, cmd->server);

    if (modp->create_dir_config)
      ((void**)cmd->server->lookup_defaults)[modp->module_index]=
	(*modp->create_dir_config)(cmd->pool, NULL);


    return NULL;
}

char *load_file (cmd_parms *cmd, void *dummy, char *filename)
{
   if (been_there_done_that) return NULL;
    
	if (!LoadLibrary(server_root_relative(cmd->pool, filename)))
		return pstrcat (cmd->pool, "Cannot load ", filename, " into server", NULL);
 
	return NULL;
}

void check_loaded_modules (server_rec *dummy, pool *p)
{
    if (been_there_done_that) return;

    been_there_done_that = 1;
}

command_rec dll_cmds[] = {
{ "LoadModule", load_module, NULL, RSRC_CONF, TAKE2,
  "a module name, and the name of a file to load it from"},
{ "LoadFile", load_file, NULL, RSRC_CONF, ITERATE,
  "files or libraries to link into the server at runtime"},
{ NULL }
};

module dll_module = {
   STANDARD_MODULE_STUFF,
   check_loaded_modules,	/* initializer */
   NULL,			/* create per-dir config */
   NULL,			/* merge per-dir config */
   NULL,			/* server config */
   NULL,			/* merge server config */
   dll_cmds,			/* command table */
   NULL,			/* handlers */
   NULL,			/* filename translation */
   NULL,			/* check_user_id */
   NULL,			/* check auth */
   NULL,			/* check access */
   NULL,			/* type_checker */
   NULL,			/* logger */
   NULL				/* header parser */
};


-- Alexei Kosut <ak...@organic.com>