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

From: Brian K Boonstra <pgsql-cygwin(at)boonstra(dot)org>
To: pgsql-cygwin(at)postgresql(dot)org
Subject: Report: writing a C extension dll on Cygwin, linking to cpp code
Date: 2004-07-03 03:51:47
Message-ID: 4E89C003-CCA4-11D8-915E-00039319807C@boonstra.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: 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

Browse pgsql-cygwin by date

  From Date Subject
Next Message zuhans@iname.com 2004-07-03 15:09:27 signal 12 with max_connections and shared_buffers (dbinit)
Previous Message Mark Lubratt 2004-07-03 01:18:36 Re: Upgrade 7.4.1 to 7.4.3 not working