Skip site navigation (1) Skip section navigation (2)

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 (view raw or flat)
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


pgsql-cygwin by date

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

Privacy Policy | About PostgreSQL
Copyright © 1996-2014 The PostgreSQL Global Development Group