Gnome Data Access | ||
---|---|---|
Prev | Chapter 3. GDA Providers |
As a sample implementation of a GDA provider the gda-odbc will be used.
First the relevant parts of the Makefile.am will be introduced.
(1) bin_PROGRAMS = gda-odbc-srv (2) lib_LTLIBRARIES = libgda_odbc.la (3) IDLFILES = $(top_srcdir)/gda/gda.idl \ $(top_srcdir)/gda/gda-command.idl \ $(top_srcdir)/gda/gda-connection.idl \ $(top_srcdir)/gda/gda-error.idl \ $(top_srcdir)/gda/gda-fieldx.idl \ $(top_srcdir)/gda/gda-parameter.idl \ $(top_srcdir)/gda/gda-recordset.idl (4) INCLUDES = -I. \ -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \ -I$(includedir) \ $(ORB_CFLAGS) \ $(GNOME_INCLUDEDIR) (5) gda_odbc_srv_SOURCES = main.c (6) gda_odbc_srv_LDADD = \ libgda_odbc.la \ $(top_builddir)/iodbc/libiodbc.la \ $(GNORBA_LIBS) \ $(INTLLIBS) (7) libgda_odbc_la_SOURCES = \ gda-types.h \ gda-command.h \ .......... (8) gnome-shlib.c (9) gda.h: $(IDLFILES) orbit-idl -I$(top_srcdir)/gda $(top_srcdir)/gda/gda.idl gda-common.c: $(IDLFIaLES) orbit-idl -I$(top_srcdir)/gda $(top_srcdir)/gda/gda.idl |
31Makefile.am Entries
(1) int main(int argc, char* argv[]) { CORBA_ORB orb; CORBA_Environment* ev; CORBA_Object connection_factory_obj; CORBA_Object name_service; gpointer servant; PortableServer_POA root_poa; PortableServer_POAManager pm; CORBA_char* objref; FILE* logstream; struct sigaction sa; int output_fd; sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, 0); logstream = fopen("/tmp/gda-odbc-srv.log", "a"); g_log_set_handler("GDA ODBC", 0xffffffff, g_log_2_file, logstream); ev = g_new0(CORBA_Environment,1); CORBA_exception_init(ev); orb = gnome_CORBA_init("gda-odbc-srv", "0.1", &argc, argv, GNORBA_INIT_SERVER_FUNC, ev); Exception(ev); root_poa = (PortableServer_POA)CORBA_ORB_resolve_initial_references(orb, "RootPOA", ev); Exception(ev); connection_factory_obj = connection_factory__create(root_poa, &servant, ev); Exception(ev); objref = CORBA_ORB_object_to_string(orb, connection_factory_obj, ev); Exception(ev); name_service = gnome_name_service_get(); if (!CORBA_Object_is_nil(name_service, ev)) { goad_server_register(name_service, connection_factory_obj, "gda-odbc", "object", ev); printf("%s\n", objref); fflush(stdout); } output_fd = fileno(logstream); setvbuf(stderr, 0, _IOLBF, 0); setvbuf(stdout, 0, _IOLBF, 0); dup2(output_fd, fileno(stdout)); dup2(output_fd, fileno(stderr)); close(output_fd); pm = PortableServer_POA__get_the_POAManager(root_poa, ev); Exception(ev); PortableServer_POAManager_activate(pm, ev); Exception(ev); CORBA_ORB_run(orb, ev); return 0; } |
One major aspect of all servers is the connection of the CORBA specific part anf the database specific part. Luckily the orbit-idl command supports you with it's -Eskeleton-impl flag. This flag produces a skeleton implementation file called gda-impl.c from the IDL file. This file is used as the starting point for writing the functions to really access the database.
To make this a little bit less painless, I'm adding the following rule to the Makefile.am file
implementation: orbit-idl -$$(datadir)/idl -I$(top_srcdir)/gda -Eskeleton_ipml $(top_srcdir)/gda/gda.idl |
We have to deal with two different objects here. The CORBA Connection object and the Gda_ODBC_Connection object. The CORBA connection object is the CORBA servant, the Gda_ODBC_Connection object is responsible for holding all relevant variables and state information needed for the specific database. Whwnever i use the term Connection object i mean the CORBA Connection object. The term ODBC_Connection will refer to the GDA_ODBC_Connection type.
I'm not sure if this method is a good one, but it decouples the CORBA data structures and the database specific part. Becuase the database specific types are used all over the server implementation, i thought it would not be a god idea to have CORBA data types all over the place. The problem is that for efficiency some CORBA memory allocation functions should be used in the server nayway. For example if memory is allocated to store the result of a VARCHAR(128) column and later passed back to the client, there are two possibilities:
use the CORBA_alloc() functions to allocate the memory once and use the CORBA_sequence_set_release() function so that the ORB doesn't release the memory itself. You have to use CORBA_free yourself after the meory isn't needed anymore.
use the CORBA_alloc() functions to allocate the memory and let CORBA free the memory after it's content has been sent to the client
use g_new() and copy the memory into a CORBA_alloc()ed buffer in the impl_* function. Then use g_free() to release the memory allocated and let the ORB take care f the CORBA_alloc()ed buffer.
The next thing you have to do is to find the servant structure definitions. They look like
typedef struct { POA_GDA_Connection servant; PortableServer_POA poa; CORBA_long attr_flags; CORBA_long attr_cmdTimeout; CORBA_long attr_connectTimeout; CORBA_char* attr_dsn; GDA_CursorLocation attr_cursor; CORBA_char* attr_defaultDsn; CORBA_char* attr_provider; CORBA_boolean attr_isOpen; CORBA_char* attr_version; GDA_ErrorSeq attr_errors; } impl_POA_GDA_Connection; |
structure loks like this typedef struct { POA_GDA_Connection servant; PortableServer_POA poa; CORBA_long attr_flags; CORBA_long attr_cmdTimeout; CORBA_long attr_connectTimeout; CORBA_char* attr_dsn; GDA_CursorLocation attr_cursor; CORBA_char* attr_defaultDsn; CORBA_char* attr_provider; CORBA_boolean attr_isOpen; CORBA_char* attr_version; GDA_ErrorSeq attr_errors; Gda_ODBC_Connection* cnc; } impl_POA_GDA_Connection; |
The next thing I'm doing is to provide a body for all the functions in the file. This might look like unnecessary work, but since the IDL interface is not fixed yet, and not all functions will be implemented by each server at once, this is the most secure way to do this. The function body consiste of one line is is just
g_error("%s not implemented", __PRETTY_FUNCTION__); |
The next thing to do is to change the *__create functions a little bit. Because we store a pointer to the data structure needed to implement a databsee connection in a seperate object, the cnc pointer needs to be initialized when a Connection object is created. For the Connection object this is simple. Find the __create function for the Connection object. The function is called impl_GDA_Connection__create(). The add a call to gda_odbc_connection_new() and asign it's return valeu to the cnc element of the newservant variable. Now the function should look like:
static GDA_Connection impl_GDA_Connection__create(PortableServer_POA poa, CORBA_Environment * ev) { GDA_Connection retval; impl_POA_GDA_Connection *newservant; PortableServer_ObjectId *objid; newservant = g_new0(impl_POA_GDA_Connection, 1); newservant->servant.vepv = &impl_GDA_Connection_vepv; newservant->poa = poa; newservant->cnc = gda_odbc_connection_new(); POA_GDA_Connection__init((PortableServer_Servant) newservant, ev); objid = PortableServer_POA_activate_object(poa, newservant, ev); CORBA_free(objid); retval = PortableServer_POA_servant_to_reference(poa, newservant, ev); return retval; } |
To illustrate the creation of a new CORBA object, take a look at the impl_GDA_Command_execute( function. This function returns a new Recordset object. The Recordset servant will hold a ODBC_Recordset pointer so that subsequent fetch calls will know which ODBC Recordset to use. The new Recordset is created with a call to the impl_GDA_Recordset__create() function, which takes care of object activation in the POA and the conversion of the Servant to a CORBA Object (GDA_Recordset). As an additional parameter a pointer to the ODBC Recordset object is passed and stored in the rs eleemt of the Recordset servant structure.