2012-10-01 49 views
3
gcc (GCC) 4.7.2 

你好,设计模式加载到内存中

我开发一个大项目将包含2个模块(共享库),我会发展。

模块是由我在C中创建的共享库,它必须在彼此之间同步和交换消息。

enter image description here

管理器模块将控制和负载这两个模块(。所以)到存储器中。如果有人失败。经理可以尝试并重新加载它。

我想知道这是我第一次做这样的事情。有没有可以遵循的设计模式?

所有这些都将用C语言编写,并使用APR(Apache Portable Runtime)进行内存池管理,如果需要的话也许使用一些线程池。

  1. 启动管理器,将加载这两个模块。
  2. 经理然后调用他们两个的一些功能,可能启动和停止它们,并可能清理。
  3. 一旦两个模块都已加载并启动。他们应该能够相互交换一些信息。

这些模块将全部运行在运行Redhat的同一台计算机上。

非常感谢您的任何建议。

+2

什么是您的实际问题,您试图解决哪个问题?是的,我读过:“是否有任何设计模式...?”,但我不认为这是一个真正的问题 - 这太不明确了;-) – Frunsi

+0

我怀疑模块1和模块2必须通过管理器进行通信。更简单的实现,它会更安全?这是否正确? – Rolice

+0

使用这个演员模型!每个模块都是一个带有消息队列的进程。对于消息传递,你可以使用任何你想要的东西,如果你不需要外部依赖,SysV消息在Unix上排队,如果你需要移植消息,或者任何其他形式的消息传递,ZeroMQ。就容错而言,您可以使用简单的'fork()'和'wait()'supervisor应用程序。对于消息传递,你可以使用tpl或json。 – megazord

回答

11

管理器模块将控制和负载这两个模块(。所以)到存储器中。如果有人失败。经理可以尝试并重新加载它。

如果它在单个C进程中,这通常是一个坏主意 - 如果其中一个模块出现故障,您不可能安全地卸载它,更不用说再次加载它。如果您需要能够从模块故障中恢复,则必须使用独立进程。代码仍然可以在.so文件中 - 只要fork()管理器为每个模块加载一次;例如,这是chrome插件API使用的模型。

此外,处理组件故障本身可能非常非常棘手。仅仅因为重新启动并不意味着B已准备好与最近重新启动的A进行对话。您可能想尝试从erlang中收集一些想法,它通过鼓励将应用程序分解为消息传递子组件管理程序模块层次结构重新启动发生故障的组件。如果你只有两个模块,这可能有点矫枉过正,但至少应该考虑一下。

至于如何沟通,有一些范例。如果这些模块处于相同的过程中,则可以传递一个vtable。也就是说,例如:

// moduleA.h 

struct vtable_A { 
    void (*do_something)(); 
}; 

void set_vtable_B(struct vtable_B *); 
struct vtable_A *get_vtable_A(); 
void start_A(); 

// moduleB.h 
struct vtable_B { 
    void (*do_something)(); 
}; 

void set_vtable_A(struct vtable_A *); 
struct vtable_B *get_vtable_B(); 
void start_B(); 

您的经理将加载这两者,通过虚函数表到B,反之亦然,然后调用start例程。订购时要小心 - A必须在B准备就绪之前启动,反之亦然,并且它们需要与此保持一致。

如果他们在独立的进程中,消息传递通常是要走的路。它在本质上是一个网络协议 - 你的子进程将序列化的消息发送给管理器,管理器将它们路由到其他子进程。谈话可能看起来有点像这样:

MGR->A  START 
MGR->B  START 
A->MGR  REGISTER_ENDPOINT 'ProcessA' 
A->MGR  WATCH_ENDPOINT 'ProcessB' 
MGR->A  OK_REGISTER 'ProcessA' 
MGR->A  OK_WATCH 'ProcessB' 
B->MGR  REGISTER_ENDPOINT 'ProcessB' 
B->MGR  WATCH_ENDPOINT 'ProcessA' 
MGR->B  OK_REGISTER 'ProcessB' 
MGR->A  NEW_ENDPOINT 'ProcessB' 
A->MGR  APPLICATION_DATA TO:'ProcessB', PAYLOAD:"Hello, world!" 
MGR->B  OK_WATCH 'ProcessA' 
MGR->B  NEW_ENDPOINT 'ProcessA' 
MGR->B  APPLICATION_DATA FROM:'ProcessA', PAYLOAD:"Hello, world!" 

请记住,还有很多其他的方法来构建这种协议比上面的例子中,并在消息传递协议之上构建RPC。您可能有兴趣查看诸如DBUS(您可能可以直接使用!)或DCOM之类的东西,它们之前已经完成了此类事情。在这种协议之上的其他优化包括使用管理器在A和B之间建立某种类型的直接通道,并且只有在需要重新启动A或B时才再次涉及它。

也就是说,在弄清楚它需要做什么之前,不要深入了解经理的工作细节。设计插件<→管理器高级接口和插件<→插件协议;然后才设计插件< - > manager界面的细节。这是太容易得到sidetracked,并最终以太复杂的方式,如CORBASOAP

2

我有点过敏“模式谈”,但是这是我会怎样处理这:

  • 决定线程模型。

    • 将你的模块交换信息,利用他们控制的内存,或者经理?
    • 模块应该等待它们之间共享的条件变量还是由管理器拥有?
  • 决定您需要的经理的通用程度。

    • 它应该能够轮询模块目录或读取配置或两者?
    • 如果管理器管理消息,模块之间的信号需要什么?

当你知道这一点,剩下的应该是大多会住在模块的业务逻辑。

1

当我得到它,你需要去耦点1和2

  • 对于您应该有一个名为BootstrapManager一个单独的类,这将是 负责加载模块,如果他们不能重新加载。
  • 接下来,你需要的是一个抽象类,称为模块,该模块将有3种 方法,
    的start() - 开始一个模块, 停止() - 停止模块, 清理() - 清理活动, 通信() - 与另一个模块通信。
  • 现在Module1和Module2都将相应地扩展这个类并实现其自己的业务逻辑 。
5

根据您的请求项目的一个简单的例子后,这里:

的源代码架构可能是这样的:

src 
    |__handler1.c //containing the main function 
    |__handler2.c //containing other functions 
    |__lib1.c //containing lib1 source 
    |__lib2_file1.c //containing lib2 source 
    |__lib2_file2.c //containing lib2 source 
    |__Makefile // file which contains commands to build the project 
    |__inc 
     |__lib1.h 
     |__lib2.h 
     |__handler2.h 

handler1.c

#include <stdio.h> 
#include "lib1.h" 
#include "lib2.h" 
#include "handler2.h" 

int main() 
{ 
    char *s1, *s2; 
    print_hello_from_handler2(); 
    s1 = get_message_from_lib1_method1(); 
    get_message_from_lib1_method2(&s2); 

    printf("s1 = %s\n",s1); 
    printf("s2 = %s\n",s2); 
    printf("extern string_from_lib1 = %s\n",string_from_lib1); 
    printf("extern string_from_lib2 = %s\n",string_from_lib2); 
} 

handler2.c

#include <stdio.h> 

void print_hello_from_handler2() 
{ 
    printf("hello world from handler2\n"); 
} 

lib1.c

#include "lib2.h" 
char *string_from_lib1="message from lib1 variable"; 

char *get_message_from_lib1_method1() 
{ 
    return get_message_from_lib2_method1(); 
} 

void get_message_from_lib1_method2(char **s) 
{ 
    get_message_from_lib2_method2(s); 
} 

lib2_file1.c

char *string_from_lib2="message from lib2 variable"; 

char *str="message from lib2 method1"; 

char *get_message_from_lib2_method1() 
{ 
    return str; 
} 

lib2_file2.c

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 

void get_message_from_lib2_method2(char **s) 
{ 
    *s = malloc(30); 
    strcpy(*s,"message from lib2 method2"); 
} 

lib1.h

extern char *string_from_lib1; 

char *get_message_from_lib1_method1(); 
void get_message_from_lib1_method2(char **s); 

lib2.h

extern char *string_from_lib2; 

char *get_message_from_lib2_method1(); 
void get_message_from_lib2_method2(char **s); 

handler2.h

void print_hello_from_handler2(); 

生成文件

SHLIB_EXT=so 
LINK=$(CC) 
SHLIB1_FILE=libmodule1.$(SHLIB_EXT).1 
SHLIB2_FILE=libmodule2.$(SHLIB_EXT).1 
SHLIB1_FLAGS=-shared -Wl,-soname,$(SHLIB1_FILE) 
SHLIB2_FLAGS=-shared -Wl,-soname,$(SHLIB2_FILE) 
FPIC=-fPIC 

all: libmodule2.$(SHLIB_EXT) libmodule1.$(SHLIB_EXT) handler 


%.o: %.c 
    $(CC) -Iinc -c -o [email protected] $^ 

handler: handler1.o handler2.o 
    $(CC) -o [email protected] $^ -L. -lmodule2 -lmodule1 

lib2_file1.o: lib2_file1.c 
    $(CC) $(FPIC) -Iinc -c -o [email protected] $< 

lib2_file2.o: lib2_file2.c 
    $(CC) $(FPIC) -Iinc -c -o [email protected] $< 

libmodule2.$(SHLIB_EXT): lib2_file1.o lib2_file2.o 
    $(LINK) $(SHLIB2_FLAGS) -o $(SHLIB2_FILE) $^ 
    ln -sf $(SHLIB2_FILE) [email protected] 

libmodule1.o: lib1.c 
    $(CC) $(FPIC) -Iinc -c -o [email protected] $< 

libmodule1.$(SHLIB_EXT): libmodule1.o 
    $(LINK) $(SHLIB1_FLAGS) -o $(SHLIB1_FILE) $< -L. -lmodule2 
    ln -sf $(SHLIB1_FILE) [email protected] 


clean: 
    rm -f *.o *.so* handler 
    rm -f /usr/lib/$(SHLIB1_FILE) 
    rm -f /usr/lib/$(SHLIB2_FILE) 
    rm -f /usr/lib/libmodule1.$(SHLIB_EXT) 
    rm -f /usr/lib/libmodule2.$(SHLIB_EXT) 

install: 
    cp $(SHLIB1_FILE) /usr/lib/ 
    cp $(SHLIB2_FILE) /usr/lib/ 
    cp handler /usr/bin/ 
    ln -sf /usr/lib/$(SHLIB1_FILE) /usr/lib/libmodule1.$(SHLIB_EXT) 
    ln -sf /usr/lib/$(SHLIB2_FILE) /usr/lib/libmodule2.$(SHLIB_EXT) 

命令编译项目

linux$ cd src 
linux$ make 

,然后安装二进制文件和库

linux$ sudo make install 

清洁安装库和二进制文件,并清除构建二进制库和对象:

linux$ sudo make clean 

要运行该应用程序:

linux$ handler 
hello world from handler2 
s1 = message from lib2 method1 
s2 = message from lib2 method2 
extern string_from_lib1 = message from lib1 variable 
extern string_from_lib2 = message from lib2 variable 
linux$ 
1

这里的架构比较简单,所以你不需要复杂的设计模式。

的主要问题是数据的完整性。如果系统部分崩溃,您如何确保两者具有相同的数据副本?

由于您使用的短信你已经解决了一半问题了。你只需要做两两件事:

(1)存储最新的邮件列表,并创建一个回滚/更新机制,以恢复模块给定一个检查点备份以及自检查站的消息列表

(2 )确保消息是原子的;即你永远不想要部分消息或事务被接受,因为如果发送方在发送消息的过程中崩溃,接收方可能会通过接受不完整的信息而被破坏。

解决问题2将校验和或散列添加到事务结束。除非收到散列并与数据匹配,否则接收方不会确定它接受消息集。

1

一个关键的问题:你想以这种方式来实现这究竟是为什么呢?你“紧密”地耦合了本质上“松散”耦合的组件(因为共享库有各种与崩溃相关的问题:它们会让管理器失效)。为什么不能有一个Manager程序(可以)启动并重新启动,如果有必要的话,可以有2个或更多的子进程。

让子进程与Manager进行通信,或使用某种协议与对方进行通信。我会建议ZeroMQ不仅因为它是真棒,因为它完全隐藏的进程间通信,所以它可能是插座(不同的机器之间),或间线,或命名管道之间的一台机器,这是非常快的。这意味着,实现你的客户后,你可以决定要如何部署它们:共享库加载到经理,因为在相同的机器上运行单独的进程,或者作为单独的机器上运行的分布式流程,你就几乎不需要改变任何事情。这意味着非常可扩展的

但是,如果你致力于共享库的方法,有一个'设计模式',我会绝对推荐这个,虽然它可能有点棘手实施。但这将是值得的重量。

您的经理,模块之间传递消息前,应检查其时间戳和,如果有什么改变了,重新编译和重新加载它们。这将意味着您的代码更改是“热门”:您不必停止管理器,重新编译并重新启动管理器以查看更改。所以你可以用C语言编程,就像在js中开发的一样!这将节省您的时间和小时。

我前段时间使用C++和APR做了类似的事情(不是库间通信)。代码有点'hacky',但在这里它是无论如何;-)

请注意,它取决于有一个Makefile为每个子模块在其自己的目录中,由于依赖关系,我不检查时间戳,我只需重新编译每个请求。这可能不适合你,所以你可能需要重新考虑这个部分。

让它工作的最难的部分是在目录上获得正确的权限,但是想到它,那是因为我将它作为一个fcgi进程运行,所以当它真正运行时,它就像web服务器。你很可能不会遇到这些问题。

#ifndef _CMJ_RUN_HPP 
#define _CMJ_RUN_HPP 

#include <fcgio.h> 
#include <stdlib.h> 

#include <iostream> 
#include <string> 
#include <sstream> 
#include <vector> 

#include <apr.h> 
#include <apr_dso.h> 
#include <apr_pools.h> 
#include <apr_thread_proc.h> 

#include <boost/filesystem.hpp> 
#include <boost/algorithm/string.hpp> 
#include <boost/algorithm/string/case_conv.hpp> 

#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h> 
#include <stdexcept> 

#include <cstdarg> 

class Line { 
protected: 
    std::stringstream line_; 
    bool isError_; 
public: 
    Line(const char* line, bool isError) : line_(line), isError_(isError) {} 
    Line(const Line& rhs) : line_(rhs.line()), isError_(rhs.error()) {} 
    bool error() const { return isError_; } 
    const char* line() const { return line_.str().c_str(); } 
    const Line& operator = (const Line& rhs) { 
     line_.str() = rhs.line(); 
     isError_ = rhs.error(); 
     return rhs; 
    } 
}; 

class Run { 
protected: 
    int exitCode_; 
    std::vector<Line> out_; 
    bool errors_; 
protected: 
    void run(const char* dir, const char* cmd, std::vector<const char*> &args, apr_pool_t* parentPool) ; 
public: 
    Run(const char* dir, const char* cmd, std::vector<const char*> &args, apr_pool_t* parentPool); 
    Run(const char* dir, const char* cmd, apr_pool_t* parentPool); 
    int exitCode() { return exitCode_; } 
    bool errors() { return errors_; } 
    bool errors(std::ostream& out); 
    int size() { return out_.size(); } 
    Line& line(int i) { return out_[i]; } 
}; 

class dso_error: public std::runtime_error { 
public: 
    dso_error(const char* c) : std::runtime_error(c) {}; 
    dso_error(std::string err) : std::runtime_error(err) {}; 
    static dso_error instance(const char* format, ...) { 
     char errbuf[8192]; 
     va_list va; 
     va_start(va, format); 
     vsnprintf(errbuf, 8192, format, va); 
     va_end(va); 
     return dso_error(errbuf); 
    } 
}; 

/** 
* Provides a building and loading framework for Dynamic libraries, with the full power 
* of make behind it. 
* Usage: 
* <code> 
* DsoLib so("/var/www/frontier/echo","/var/www/frontier/echo/libecho.so",pool); 
* if (!so.errors(outStream)) { 
* void (*pFn)(void) = sym("initialize"); 
* (*pFn)(); 
* } 
* </code> 
*/ 
class DsoLib : public Run { 
protected: 
    apr_pool_t* pool_; 
    apr_dso_handle_t* dso_; 
    std::string dirname_; 
    std::string libname_; 
public: 
    /** dir is the directory where make should be executed, libname is full path to the library 
    * from current working directory. 
    */ 
    DsoLib(const char* dir, const char* libname, apr_pool_t* parentPool) throw(dso_error); 
    ~DsoLib(); 
    void* sym(const char* symbol) throw (dso_error); 
    void* sym(std::string symbol) throw (dso_error) { return sym(symbol.c_str()); } 
}; 

#endif 

并运行。cpp

#include "Run.hpp" 

#include <string> 
#include <sstream> 
#include <boost/filesystem.hpp> 
#include <cassert> 

#define DBGENDL " (" << __FILE__ << ":" << __LINE__ << ")" << endl 


using namespace std; 

Run::Run(const char* dir, const char* cmd, apr_pool_t* pool) : errors_(false) { 
    vector<const char *> args; 
    run(dir, cmd, args, pool); 
} 

Run::Run(const char* dir, const char* cmd, vector<const char*> &args, apr_pool_t* pool) : errors_(false) { 
    run(dir, cmd, args, pool); 
} 

void 
Run::run(const char* dir, const char* cmd, vector<const char*> &args, apr_pool_t* parentPool) { 
    cout << "Run::run(dir=" << ", cmd=" << cmd << ", args...)" << endl; 
    apr_status_t status; 
    char aprError[1024]; 
    struct aprPool_s { 
     apr_pool_t* pool_; 
     aprPool_s(apr_pool_t* parent) { 
      apr_pool_create(&pool_, parent); 
     } 
     ~aprPool_s() { 
      apr_pool_destroy(pool_); 
     } 
     operator apr_pool_t* () { return pool_; } 
    } pool (parentPool); 

    apr_procattr_t* attr; 
    if (APR_SUCCESS != (status = apr_procattr_create(&attr, pool))) { 
     cerr << "apr_procattr_create error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    if (APR_SUCCESS != (status = apr_procattr_dir_set(attr, dir))) { 
     cerr << "apr_procattr_dir_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    if (APR_SUCCESS != (status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_ENV))) { 
     cerr << "apr_procattr_cmdtype_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    if (APR_SUCCESS != (status = apr_procattr_io_set(attr, APR_NO_PIPE, APR_FULL_NONBLOCK, APR_FULL_NONBLOCK))) { 
     cerr << "apr_procattr_io_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    if (APR_SUCCESS != (status = apr_procattr_user_set(attr, "craig", "lateral"))) { 
     cerr << "apr_procattr_user_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    if (APR_SUCCESS != (status = apr_procattr_group_set(attr, "craig"))) { 
     cerr << "apr_procattr_group_set error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    apr_proc_t proc; 

    const char **argv = (const char**) new char*[ 2 + args.size() ]; 
    argv[0] = cmd; 
    size_t i=0; 
    size_t argc=args.size(); 
    for (i=0; i<argc; i++) { 
     argv[i+1] = args[i]; 
     cerr << "arg " << i << " = " << args[i]; 
    } 
    argv[i+1] = NULL; 
    argc++; 
    cerr << "About to execute " << cmd << " in dir " << dir << endl; 
    cerr << "ARGS:" << endl; 
    for (i=0; i<argc; i++) { 
     cerr << "[" << i << "]: " << argv[i] << endl; 
    } 

    if (APR_SUCCESS != (status = apr_proc_create(&proc, cmd, argv, NULL, attr, pool))) { 
     cerr << "apr_proc_create error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 

    apr_exit_why_e exitWhy; 
    cerr << "--- " << cmd << " ---" << endl; 
    while (APR_CHILD_NOTDONE == (status = apr_proc_wait(&proc, &exitCode_, &exitWhy, APR_NOWAIT))) { 
     char line[1024]; 
     status = apr_file_gets(line, sizeof(line), proc.out); 
     if (APR_SUCCESS==status) { 
      out_.push_back(Line(line, false)); 
      cerr << line << endl; 
     } 

     status = apr_file_gets(line, sizeof(line), proc.err); 
     if (APR_SUCCESS==status) { 
      out_.push_back(Line(line, true)); 
      errors_ = true; 
      cerr << "E:" << line ; 
     } 
    } 
    cerr << " -----" << endl; 

    delete[] argv; 

    if ((APR_CHILD_DONE != status) && (APR_PROC_EXIT != status)) { 
     cerr << "apr_proc_wait error: " << apr_strerror(status, aprError, sizeof(aprError)) << endl; 
    } 
    cerr << cmd << " exited " << ((APR_PROC_EXIT==exitWhy) ? "PROC_EXIT" : 
      ((APR_PROC_SIGNAL==exitWhy) ? "PROC_SIGNAL" : 
      ((APR_PROC_SIGNAL_CORE==exitWhy) ? "PROC_SIGNAL_CORE" : "Unknown"))) << endl; 
} 

bool 
Run::errors(std::ostream& os) { 
    cerr << "Run::errors(ostream) : errors()=" << errors() << endl; 
    if (errors()) { 
     cerr << "Writing errors to ostream" << endl; 
     os << "Content-type: text/html\r\n\r\n"; 
     os << "<html><head><title>Errors</title>" 
      << "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/frontier.css\"></link>" 
      << "</head>" 
      << "<body>"; 
     for (int i=0; i<size(); i++) { 
      Line& errline = line(i); 
      os << "<div class=\"" << ((errline.error() ? "error" : "out")) << "\">" 
        << errline.line() 
        << "</div>"; 
     } 
     os 
      << "</body>" 
      << "</html>"; 
    } 
    return errors(); 
} 

DsoLib::DsoLib(const char* dir, const char* libname, apr_pool_t* parentPool) throw (dso_error) : 
    Run(dir, "/usr/bin/make", parentPool), pool_(NULL), dso_(NULL), dirname_(dir), libname_(libname) 
{ 
    if (errors()) { 
     cerr << "Run encountered errors, quitting DsoLib::DsoLib()" << DBGENDL; 
     //throw dso_error::instance("Build failed for dir %s, library %s", dir, libname); 
     return; 
    } else { 
     cerr << "No errors encountered with Run in DsoLib::DsoLib" << DBGENDL; 
    } 

    apr_status_t status; 
    if (APR_SUCCESS != apr_pool_create(&pool_, parentPool)) { 
     cerr << "Failed to allocate pool" << DBGENDL; 
     throw dso_error("Failed to allocate apr_pool"); 
    } 

    cerr << "Created pool ok" << DBGENDL; //(" << __FILE__ << ":" << __LINE__ << ")" << endl; 

    if (APR_SUCCESS != (status = apr_dso_load(&dso_, libname, pool_))) { 
     cerr << "apr_dso_load(" << libname << ") failed" << DBGENDL; 
     char aprError[1024]; 
     throw dso_error::instance("dso_load failed, path=%s, error=%s", 
       libname, apr_strerror(status, aprError, sizeof(aprError))); 
    } 
    cerr << "Loaded dso ok" << DBGENDL; 
#if 0 
    void (*initialize)(apr_pool_t*) = reinterpret_cast< void(*)(apr_pool_t*) > (sym("initialize")); 
    if (initialize) { 
     cerr << "found initialize sym: about to call initialize" << DBGENDL; 
     initialize(pool_); 
     cerr << "initialize(pool) returned ok" << DBGENDL; 
    } else { 
     cerr << "initialize sym not found" << DBGENDL; 
    } 
#endif 
    cerr << "Exiting DsoLib::DsoLib(" << dir << ", " << libname << ") with success." << endl; 
} 

DsoLib::~DsoLib() { 
    cerr << "Entering DsoLib::~DsoLib(dir=" << dirname_ <<", " << "lib=" << libname_ << ")" << endl; 
    if (NULL!=dso_) { 
     void (*terminate)(void) = reinterpret_cast<void(*)()>(sym("terminate")); 
     if (terminate) terminate(); 
     apr_status_t status = apr_dso_unload(dso_); 
     if (APR_SUCCESS != status) { 
      char err[8192]; 
      cerr << "ERR apr_dso_unload failed: " << apr_dso_error(dso_, err, sizeof(err)) << endl; 
     } else { 
      cerr << "Unloaded " << libname_ << endl; 
     } 
    } else { 
     cerr << "ERR dso_ handle is NULL" << endl; 
    } 
    if (NULL!=pool_) apr_pool_destroy(pool_); 
} 

void * 
DsoLib::sym(const char* symbol) throw (dso_error) { 
    cerr << "sym('" << symbol << "')" << DBGENDL; 
    cerr << "dso_ == NULL ? " << ((NULL==dso_)?"true":"false") << DBGENDL; 
    cerr << "dso_ = " << dso_ << DBGENDL; 
    assert(NULL!=symbol); 
    assert(NULL!=dso_); 
    apr_status_t status; 
    void* p = NULL; 
    if (APR_SUCCESS != (status = apr_dso_sym((apr_dso_handle_sym_t*)&p, dso_, symbol))) { 
     cerr << "apr_dso_sym() DID NOT RETURN APR_SUCCESS" << DBGENDL; 
     char aprError[1024]; 
     stringstream err; 
     err << "dso_sym failed, symbol=" << symbol << ": " << apr_strerror(status, aprError, sizeof(aprError)); 
     cerr << err.str() << DBGENDL; 
    } else { 
     cerr << "sym succeeded for " << symbol << " in " << libname_ << DBGENDL; 
    } 
    return p; 
}