Report: writing a C extension dll on Cygwin, linking to cpp code

Поиск
Список
Период
Сортировка
От Brian K Boonstra
Тема Report: writing a C extension dll on Cygwin, linking to cpp code
Дата
Msg-id 4E89C003-CCA4-11D8-915E-00039319807C@boonstra.org
обсуждение исходный текст
Список pgsql-cygwin
Hi there

   I recently found it a bit of a pain in the neck to figure out all the
bits for making some QuantLib capabilities available to PostgreSQL
running under Cygwin.  This post is to document my progress so far in
the interest of saving somebody else a little time.  Some details might
be slightly useful to users of OSX or Linux.

Background:
    QuantLib is a library of financial objects written in C++.  It has a
Microsoft VC6/7 port, but also compiles under Cygwin.  I wanted a
proof-of-concept, embedding e.g. the vanilla option pricer into
Postgresql as a C extension.  There appears to be no documentation on
how to write C extensions using Cygwin; what I have learned is culled
from various mailing list posts.

Overview:
    (1) Get some kind of C extension to compile and run under Cygwin (the
example in the pg docs works fine).
    (2) Write some C++ wrapper code into a cpp file.
    (3) Figure out the link step with both C and C++ object files.

Exposition:
Since Windows expects a DLL rather than a .so, the first thing you have
to know is that you are going to end up making a DLL.  To do that in
Cygwin, use something like this:

        cc -c mycfile.c -o mycfile.o
        dlltool --export-all --output-def mylib.def  mycfile.o;
        dllwrap mylib.dll --dllname mylib.dll --def mylib.def mycfile.o
-L/lib -lpostgres
        cp mylib.dll /usr/lib/postgresql/

Try to load the function using fairly ordinary commands:
        CREATE FUNCTION concat_text(text, text) RETURNS text AS 'mylib',
'concat_text' LANGUAGE C STRICT;
where we note that the DLL suffix is left off.

Once you have that working, you can write the cpp wrapper.  Maybe it
looks a little like this:
        extern "C" {
            double wrap_foo_do_bar(double x) {
                Foo aFoo(x);
                return aFoo.bar();
            }
        }
and write the C extension wrapper into your C file like this:
        PG_FUNCTION_INFO_V1(foobar);
        Datum f(PG_FUNCTION_ARGS)
        {
            float8   arg = PG_GETARG_FLOAT8(0);
            PG_RETURN_FLOAT8( wrap_foo_do_bar(arg) );
        }


Now you have to get everything to compile and link properly.  You can
compile the C file as before, but beware makefiles for the C++ file,
since that one might use $CPP, which uses an unwanted -E argument.  In
addition, you need a C++ aware linker in your dllwrap command.  You
basically want
        cc -c mycfile.c -o mycfile.o
        g++ -c mycppfile.cpp -o mycppfile.o
        dlltool --export-all --output-def mylib.def  mycfile.o mycppfile.o;
        dllwrap mylib.dll --driver-name g++ --dllname mylib.dll --def
mylib.def mycfile.o -L/lib -L. -lpostgres -lmycpplib
        cp mylib.dll /usr/lib/postgresql/




Worked Example:
Here is a makefile, a SQL command, a C file, and a C++ file, all of
which manage together to make a DLL called pgfin.dll enabling
PostgreSQL to value a vanilla option using the Black-Scholes formula
found in QuantLib:

------------------------------------------------------
Makefile
------------------------------------------------------

INSTALL_DIR := $(shell pg_config --pkglibdir)
EXTRA_LIBS = -lQuantLib
EXTRA_LIB_PATHS = -L/cygdrive/D/Brian/QuantLib-0.3.6/ql/.libs
#INSTALL_DIR = /usr/lib/postgresql/

CPP=g++

#Automatic DLL name
cur-dir   := $(shell pwd)
TARGET_NAME = $(notdir $(cur-dir))
DLL_NAME=$(TARGET_NAME).dll
DLL_DEBUG_NAME=$(TARGET_NAME)_d.dll
DLL_DEBUG_PROF_NAME=$(TARGET_NAME)_pd.dll

DEF_NAME=$(TARGET_NAME).def

# Only needed for certain platforms
# CFLAGS := $(CFLAGS) -fpic

OTHER_HEADER_FLAGS =  -I /usr/include/postgresql/server -I
/cygdrive/D/Brian/QuantLib-0.3.6  -I.

# Automatic inclusion of C and C++ files found
CFILES := $(wildcard *.c)
CPPFILES := $(wildcard *.cpp)

OBJECT_DIR=obj
DEBUG_OBJECT_DIR=obj_debug

LIB_DIR=libs
LIB_TARGET  := $(DLL_NAME)
DLIB_TARGET := $(DLL_DEBUG_NAME)
PLIB_TARGET := $(DLL_DEBUG_PROF_NAME)
DEF_PATH  = $(LIB_DIR)/$(DEF_NAME)

SRCFILES = $(CFILES) $(HFILES) $(CPPFILES) $(CPPFILES:.C=.h)

OFILES = $(CFILES:.c=.o) $(CPPFILES:.cpp=.o)

VPATH = $(OBJECT_DIR)

ALL_CFLAGS = $(CFLAGS) $(OTHER_HEADER_FLAGS) -Wall

default: library

library:
    ${MAKE} "cur-dir=${cur-dir}" "CFLAGS=$(CFLAGS) -O" $(LIB_TARGET)

debug:
    ${MAKE} "cur-dir=${cur-dir}" "CFLAGS=$(CFLAGS) -g"
"OBJECT_DIR=$(DEBUG_OBJECT_DIR)" $(DLIB_TARGET)

profile:
    ${MAKE} "cur-dir=${cur-dir}" "CFLAGS=$(CFLAGS) -pg" $(PLIB_TARGET)

.c.o:
    $(CC) $(ALL_CFLAGS) -c $*.c -o $(OBJECT_DIR)/$*.o

.cpp.o:
    $(CPP) $(ALL_CFLAGS) $(CPPFLAGS) -c $*.cpp -o $(OBJECT_DIR)/$*.o

$(LIB_TARGET): $(LIB_DIR)/$(LIB_TARGET)

$(DLIB_TARGET): $(LIB_DIR)/$(DLIB_TARGET)

$(PLIB_TARGET): $(LIB_DIR)/$(PLIB_TARGET)

$(LIB_DIR)/$(LIB_TARGET) $(LIB_DIR)/$(DLIB_TARGET)
$(LIB_DIR)/$(PLIB_TARGET):$(LIB_DIR) $(OBJECT_DIR) $(OFILES) $(CFILES)
$(SRCFILES)
    -/bin/rm $@
    (cd $(OBJECT_DIR); dlltool --export-all --output-def ../$(DEF_PATH)
$(OFILES); dllwrap --driver-name g++ -o ../$@ --dllname ../$@ --def
../$(DEF_PATH) $(OFILES) -L/lib $(EXTRA_LIB_PATHS)  $(EXTRA_LIBS)
-lpostgres );
    chmod a+rx $@

$(OBJECT_DIR):
    mkdir $(OBJECT_DIR)

$(LIB_DIR):
    mkdir $(LIB_DIR)

objonly:  $(OBJECT_DIR) $(OFILES)

install: $(LIB_DIR)/$(LIB_TARGET)
    cp $(LIB_DIR)/$(LIB_TARGET) $(INSTALL_DIR);

installdebug: $(LIB_DIR)/$(DLIB_TARGET)
    cp $(LIB_DIR)/$(DLIB_TARGET) $(INSTALL_DIR);

#
# Cleaning Rules
#

GARBAGE = *~ $(OBJECT_DIR)/*.o $(DEBUG_OBJECT_DIR)/*.o *.o *.def
*.tab.h Makefile.dependencies Solaris_obj Solaris_debug_obj
Solaris_profile_obj

PRODUCTS = $(LIB_DIR)/$(LIB_TARGET) $(LIB_DIR)/$(DLIB_TARGET)
$(LIB_DIR)/$(PLIB_TARGET) $(DEF_PATH)

.PHONY: clean mostlyclean announce-clean

clean: announce-clean
    $(RM) -rf $(GARBAGE) $(PRODUCTS)

mostlyclean: announce-clean
    $(RM) -rf $(GARBAGE)

announce-clean:
    $(SILENT) $(ECHO) == Cleaning $(TARGET_NAME) ==



------------------------------------------------------
C File
------------------------------------------------------
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
extern double BS(int callput, double underlying, double strike, double
volatility, double r, double q, double t);
PG_FUNCTION_INFO_V1(BlackScholes);
Datum BlackScholes(PG_FUNCTION_ARGS)
{
     int32   callput = PG_GETARG_INT32(0);
     float8   spot = PG_GETARG_FLOAT8(1);
     float8   strike = PG_GETARG_FLOAT8(2);
     float8   vol = PG_GETARG_FLOAT8(3);
     float8   r = PG_GETARG_FLOAT8(4);
     float8   q = PG_GETARG_FLOAT8(5);
     float8   t = PG_GETARG_FLOAT8(6);
     float8   val=0.0;

     val = BS(callput,spot,strike, vol, r, q, t);
     PG_RETURN_FLOAT8(val);
}

------------------------------------------------------
CPP File
------------------------------------------------------
// Basically stripped down from QuantLib example
#include <ql/config.hpp>
#include <ql/quantlib.hpp>
using namespace QuantLib;
extern "C" {
double BS(int callput, double underlying, double strike, double
volatility, double r, double q, double t)
{
         Option::Type type(Option::Call);
         double value=-1;
         Spread dividendYield = q;
         Rate riskFreeRate = r;
         Date todaysDate(15, May, 1998);
         Date settlementDate(17, May, 1998);
         Date exerciseDate(17, May, 1999);
         DayCounter rateDayCounter = Actual365();
         Time maturity = rateDayCounter.yearFraction(settlementDate,
             exerciseDate);
         Date midlifeDate(19, November, 1998);
         Handle<Exercise> exercise(new EuropeanExercise(exerciseDate));
         RelinkableHandle<Quote> underlyingH(
             Handle<Quote>(new SimpleQuote(underlying)));
         RelinkableHandle<TermStructure> flatTermStructure(
             Handle<TermStructure>(
                 new FlatForward(todaysDate, settlementDate,
                                 riskFreeRate, rateDayCounter)));
         RelinkableHandle<TermStructure> flatDividendTS(
             Handle<TermStructure>(
                 new FlatForward(todaysDate, settlementDate,
                                 dividendYield, rateDayCounter)));
         RelinkableHandle<BlackVolTermStructure> flatVolTS(
             Handle<BlackVolTermStructure>(
                 new BlackConstantVol(settlementDate, volatility)));
         Handle<StrikedTypePayoff> payoff(new
             PlainVanillaPayoff(type, strike));
         Handle<BlackScholesStochasticProcess> stochasticProcess(new
             BlackScholesStochasticProcess(underlyingH, flatDividendTS,
                 flatTermStructure,
                 flatVolTS));
         VanillaOption option(stochasticProcess, payoff, exercise,
             Handle<PricingEngine>(new AnalyticEuropeanEngine()));
         option.setPricingEngine(Handle<PricingEngine>(
             new AnalyticEuropeanEngine()));
         value = option.NPV();
         return value;
}
------------------------------------------------------
SQL
------------------------------------------------------
CREATE FUNCTION BlackScholes(integer, double precision, double
precision, double precision, double precision, double precision, double
precision) RETURNS double precision AS 'pgfin', 'BlackScholes' LANGUAGE
C STRICT;



           - Brian


В списке pgsql-cygwin по дате отправления:

Предыдущее
От: K-Sudhakaran
Дата:
Сообщение: Problem running psql on win32 - reg.
Следующее
От: "Leeuw van der, Tim"
Дата:
Сообщение: Re: Problems with installation of cygwin/postgreSQL