Internally, every Babel object holds an implementation language specific pointer to the object’s private state data. If the object is implemented in C, the pointer points to a struct named struct package_Class__data. In Java, the private data pointer points to a Java object of the implementation type, Class_Impl, which calls are made on, and which actually holds the object state. The type of the private data is entirely implementation language dependent. The data is usually created or set by the Babel objects user-implemented constructor.
With the Backdoor Initializer, Babel allows a Babel user to set this private data pointer at construction with pre-initialized state. Of course, this is very dangerous. This can only be done if the client language creating the Babel object is the same as the language the object is implemented in, and if the pre-initialized state is of the correct type. If either of these constraints is unsatisfied, the behavior of the backdoor initializer in undefined.
Object construction is a very language specific problem, and therefore this Babel feature is exposed differently in each language. However, in every language, there is some way provided for the class implementor to determine in the constructor code whether the object is being constructed normally, or if the user provided pre-initialized state. In most languages there is a second constructor provided, only in Python is an argument used to determine if the user provided pre-initialized state or not. Most class implementers will not need to do anything with the new constructor. In the case of Backdoor Initialization, usually the right thing to do in a constructor is to do nothing, since the object state was preinitialized by the client. The constructor only needs to be used if the state of the object cannot fully be represented by its private data. For example, an object that opens a TCP/IP connection during construction would almost certainly need that code in the backdoor constructor as well.
The Backdoor Initializer is not a feature that most Babel users will need. However, there are certain cases where the Backdoor Initializer is absolutely required. The most obvious usage case is for wrapping up native objects in a Babelized interface. The allows the implementation language to access the data directly, but other languages must use the provided Babel interface. It was exactly this usage case that inspired the creation of the Backdoor Initializer.
A customer needed to use a Java visualization program to view a graph generated by a C++ library. The customer did not want to modify the Java program significantly. Instead, he created the graph data structure used by the visualizer in Java, and wrapped it in a Babel interface. He was then able to pass the Babelized object to the C++ library, which made calls on the Babel interface to add nodes and edges to the graph. When the C++ library finished, the Java visualizer was able to use the graph as if it was created natively in Java. The visualizer code did not have to be modified in any way to use the graph!
In this chapter we will use a single example for all Babel supported languages. This example is taken from the wrapper regression test. In this test, there are two sidl classes, wrapper.Data and wrapper.User.
package wrapper version 1.0 { class Data { void setString(in string s); void setInt(in int i); } class User { void accept(in wrapper.Data data); } } |
wrapper.Data wraps up some native data, which will be modified by the wrapper.User.accept() call. In this case, the data is just a string and an integer. In order to show the new constructor functionality, we set string called d_ctortest to “ctor was run” in the new constructor.
To reiterate, the client program creates and wraps language specific data in a wrapper.Data babel object. The alternate constructor code is run, which sets the d_ctortest string. The object is then passed to wrapper.User.accept(), which sets the data. The client program can then directly access the data and read what was set by User.accept().
In C, the Backdoor Initializer is used through a new _create like static method, _wrapObj. _wrapObj takes a pointer to the private data to be wrapped (a simple struct defined in wrapper_Data__Impl.h).
from wrapper_Data_Impl.h:
struct wrapper_Data__data { /* DO-NOT-DELETE splicer.begin(wrapper.Data._data) */ char* d_ctortest; char* d_string; int d_int; /* DO-NOT-DELETE splicer.end(wrapper.Data._data) */ }; |
From wrapper_Data_Impl.c; notice the new constructor ctor2, which is only called with backdoor initialization.
void impl_wrapper_Data__ctor2( /* in */ wrapper_Data self, /* in */ void* private_data, /* out */ sidl_BaseInterface *_ex) { /* DO-NOT-DELETE splicer.begin(wrapper.Data._ctor2) */ struct wrapper_Data__data *dptr = (struct wrapper_Data__data *) private_data; dptr->d_ctorTest = "ctor was run"; /* DO-NOT-DELETE splicer.end(wrapper.Data._ctor2) */ } void impl_wrapper_Data_setString( /* in */ wrapper_Data self, /* in */ const char* s, /* out */ sidl_BaseInterface *_ex) { *_ex = 0; /* DO-NOT-DELETE splicer.begin(wrapper.Data.setString) */ struct wrapper_Data__data *dptr = wrapper_Data__get_data(self); if (dptr) { dptr->d_string = "Hello World!"; } /* DO-NOT-DELETE splicer.end(wrapper.Data.setString) */ } void impl_wrapper_Data_setInt( /* in */ wrapper_Data self, /* in */ int32_t i, /* out */ sidl_BaseInterface *_ex) { /* DO-NOT-DELETE splicer.begin(wrapper.Data.setInt) */ struct wrapper_Data__data *dptr = wrapper_Data__get_data(self); if (dptr) { dptr->d_int = 3; } /* DO-NOT-DELETE splicer.end(wrapper.Data.setInt) */ } |
from the client program wraptest.c: (Note that we must include wrapper_Data_Impl.h)
#include "wrapper_User.h" #include "wrapper_Data.h" #include "wrapper_Data_Impl.h" int main(int argc, char** argv) { sidl_BaseInterface exception = NULL; wrapper_Data data = NULL; wrapper_User user = NULL; struct wrapper_Data__data *d_data = NULL; struct wrapper_Data__data *dptr = NULL; /*Create the data*/ dptr = malloc(sizeof(struct wrapper_Data__data)); /*Wrap the data*/ data = wrapper_Data__wrapObj(dptr, &exception); user = wrapper_User__create(&exception); ASSERT( strcmp(d_data->d_ctortest, "ctor was run") == 0); /* Test the data setting*/ wrapper_User_accept(user, data, &exception); ASSERT( strcmp(d_data->d_string, "Hello World!") == 0); ASSERT( d_data->d_int == 3); return 0; } |
In fortran 77, using the Backdoor Initializer is similar to using it in C. There is a special new constructor named _wrapObj that takes the private data pointer.
Of course, dynamically allocating data in fortran 77 is tricky, and requires very close cooperation with the Impl class that uses the data. Most of the complexity of this example code is caused by those problems, not so much the Backdoor Initializer itself.
Since we need to store 2 strings and an integer, we create 3 sidl arrays to hold the private data. We create an opaque array of 2 elements called pdata to hold the other two arrays. Then we create a string array of 2 elements called a_string, and an integer array of 1 element called a_int. d_string is element 0 of the string array, and d_ctortest is element 1. We then place a_string into pdata as element 0, and a_int in pdata as element 1. We then call _wrapObj, which takes pdata as an in argument as the first argument, and the object we are creating, data, as an out argument as the second argument.
Notice that we don’t have to include an Impl files to fortran 77, since, there aren’t actually any types.
Fairly complex, but here’s the client code from wraptest.f:
program wraptest implicit none integer*8 data, user, pdata, backup, throwaway integer*8 a_string, a_int integer*4 d_int character*80 d_string character*80 d_ctortest character*80 d_silly c pdata is the internal data, and holds two arrays, string an int. call sidl_opaque__array_create1d_f(2, pdata) call sidl_string__array_create1d_f(2, a_string) call sidl_int__array_create1d_f(1, a_int) c initialize the data arrays call sidl_string__array_set1_f(a_string, 0, d_string) call sidl_string__array_set1_f(a_string, 1, d_ctortest) call sidl_int__array_set1_f(a_int, 0, d_int) c initilize pdata call sidl_opaque__array_set1_f(pdata, 0, a_string) call sidl_opaque__array_set1_f(pdata, 1, a_int) call wrapper_User__create_f(user, throwaway) c private data first, then the object being created call wrapper_Data__wrapObj_f(pdata, data, throwaway) call sidl_opaque__array_get1_f(pdata, 0, a_string) call sidl_string__array_get1_f(a_string, 1, d_ctortest) print *, d_ctortest call wrapper_User_accept_f(user, data, throwaway) call sidl_string__array_get1_f(a_string, 0, d_string) call sidl_int__array_get1_f(a_int, 0, d_int) print *, d_string, ' ', d_int call wrapper_User_deleteRef_f(user, throwaway) call wrapper_Data_deleteRef_f(data, throwaway) end |
and the Impl side code from wrapper_Data_Impl.f
subroutine wrapper_Data__ctor2_fi(self, private_data, exception) implicit none integer*8 self integer*8 private_data integer*8 exception C DO-NOT-DELETE splicer.begin(wrapper.Data._ctor2) integer*8 a_string, pdata character*80 d_string, d_ctortest call sidl_opaque__array_get1_f(private_data, 0, a_string) call sidl_string__array_set1_f(a_string, 1, 'ctor was run') C DO-NOT-DELETE splicer.end(wrapper.Data._ctor2) end subroutine wrapper_Data_setString_fi(self, s, exception) implicit none integer*8 self character*(*) s integer*8 exception C DO-NOT-DELETE splicer.begin(wrapper.Data.setString) integer*8 data, a_string call wrapper_Data__get_data_f(self, data) if (data .ne. 0) then call sidl_opaque__array_get1_f(data, 0, a_string) call sidl_string__array_set1_f(a_string, 0, s) endif C DO-NOT-DELETE splicer.end(wrapper.Data.setString) end subroutine wrapper_Data_setInt_fi(self, i, exception) implicit none integer*8 self integer*4 i integer*8 exception C DO-NOT-DELETE splicer.begin(wrapper.Data.setInt) integer*8 data, a_int call wrapper_Data__get_data_f(self, data) if (data .ne. 0) then call sidl_opaque__array_get1_f(data, 1, a_int) call sidl_int__array_set1_f(a_int, 0, i) endif C DO-NOT-DELETE splicer.end(wrapper.Data.setInt) end |
The Fortran 90/95 backdoor initializer is very similar to C. Fortran 90/95 also has a _wrapObj, but it is actually defined in the wrapper_Data_Mod.F90 file, along with the private data type definition.
Here is the private data definition from wrapper_Data_Mod.F90:
type wrapper_Data_priv sequence ! DO-NOT-DELETE splicer.begin(wrapper.Data.private_data) ! Insert-Code-Here {wrapper.Data.private_data} (private data members) character(len=256) :: d_ctortest character(len=256) :: d_string integer(kind=sidl_int) :: d_int ! DO-NOT-DELETE splicer.end(wrapper.Data.private_data) end type wrapper_Data_priv |
Here is the client code from wraptest.F90. Notice wrapper_Data_impl is used. From wraptest.F90:
#include "wrapper_User_fAbbrev.h" #include "wrapper_Data_fAbbrev.h" #include "synch_RegOut_fAbbrev.h" #include "synch_ResultType_fAbbrev.h" program wraptest use sidl use sidl_BaseInterface use wrapper_User use wrapper_Data use wrapper_Data_impl type(sidl_BaseInterface_t) :: throwaway_exception type(wrapper_Data_wrap) :: pd type(wrapper_Data_t) :: data type(wrapper_User_t) :: user allocate(pd%d_private_data) pd%d_private_data%d_int = 0 pd%d_private_data%d_string = 'place holder' pd%d_private_data%d_ctortest = 'place holder' call new(user, throwaway_exception) call wrapObj(pd, data, throwaway_exception) print *, pd%d_private_data%d_ctortest call accept(user, data, throwaway_exception) print *, pd%d_private_data%d_string, ' ', pd%d_private_data%d_int call deleteRef(user, throwaway_exception) call deleteRef(data, throwaway_exception) ! Private data [should be] deallocated by the Impl dtor. call close(tracker, throwaway_exception) call deleteRef(tracker, throwaway_exception) end program wraptest |
Finally, the Impl code from wrapper_Data_Impl.F90:
recursive subroutine wrapper_Data__ctor2_mi(self, private_data, exception) use sidl use sidl_BaseInterface use sidl_RuntimeException use wrapper_Data use wrapper_Data_impl implicit none type(wrapper_Data_t) :: self ! in type(wrapper_Data_wrap) :: private_data type(sidl_BaseInterface_t) :: exception ! out ! DO-NOT-DELETE splicer.begin(wrapper.Data._ctor2) private_data%d_private_data%d_ctortest = 'ctor was run' ! DO-NOT-DELETE splicer.end(wrapper.Data._ctor2) end subroutine wrapper_Data__ctor2_mi recursive subroutine wrapper_Data_setString_mi(self, s, exception) use sidl use sidl_BaseInterface use sidl_RuntimeException use wrapper_Data use wrapper_Data_impl implicit none type(wrapper_Data_t) :: self ! in character (len=*) :: s ! in type(sidl_BaseInterface_t) :: exception ! out ! DO-NOT-DELETE splicer.begin(wrapper.Data.setString) type(wrapper_Data_wrap) :: dp call wrapper_Data__get_data_m(self, dp) dp%d_private_data%d_string = s ! DO-NOT-DELETE splicer.end(wrapper.Data.setString) end subroutine wrapper_Data_setString_mi recursive subroutine wrapper_Data_setInt_mi(self, i, exception) use sidl use sidl_BaseInterface use sidl_RuntimeException use wrapper_Data use wrapper_Data_impl implicit none type(wrapper_Data_t) :: self ! in integer (kind=sidl_int) :: i ! in type(sidl_BaseInterface_t) :: exception ! out ! DO-NOT-DELETE splicer.begin(wrapper.Data.setInt) type(wrapper_Data_wrap) :: dp call wrapper_Data__get_data_m(self, dp) dp%d_private_data%d_int = i ! DO-NOT-DELETE splicer.end(wrapper.Data.setInt) end subroutine wrapper_Data_setInt_mi |
In Object Oriented languages there is no _wrapObj method exposed to the user. Instead, the same functionality is achieved simply by calling “new” on the Impl class. Interestingly, this means the constructor functionality is NOT placed in a Babel ctor method, but is, instead, actually in the default object constructor.
Here is the private data definition from wrapper_Data_Impl.hxx:
namespace wrapper { class Data_impl : public virtual ::wrapper::Data .... public: char* d_string; int d_int; char* d_ctorTest; .... }; // end class Data_impl } // end namespace wrapper |
Here is the client code from wraptest.cxx. Notice wrapper_Data_Impl is included.
#include "wrapper_User.hxx" #include "wrapper_Data.hxx" #include "wrapper_Data_Impl.hxx" int main(int argc, char **argv) { wrapper::Data_impl data; wrapper::User user = wrapper::User::_create(); ASSERT( data.d_ctorTest == "ctor was run"); /* Test the data setting*/ user.accept(data); ASSERT( data.d_string == "Hello World!"); ASSERT( data.d_int == 3); return 0; } |
Finally, the Impl code from wrapper_Data_Impl.cxx, notice where the constructor code is placed.
// speical constructor, used for data wrapping(required). // Do not put code here unless you really know what you're doing! wrapper::Data_impl::Data_impl() : StubBase(reinterpret_cast< void*>(::wrapper::Data::_wrapObj(this)),false) , _wrapped(true) { // DO-NOT-DELETE splicer.begin(wrapper.Data._ctor2) d_ctorTest = "ctor was run"; // DO-NOT-DELETE splicer.end(wrapper.Data._ctor2) } void wrapper::Data_impl::setString_impl ( /* in */const ::std::string& s ) { // DO-NOT-DELETE splicer.begin(wrapper.Data.setString) d_string = "Hello World!"; // DO-NOT-DELETE splicer.end(wrapper.Data.setString) } void wrapper::Data_impl::setInt_impl ( /* in */int32_t i ) { // DO-NOT-DELETE splicer.begin(wrapper.Data.setInt) d_int = 3; // DO-NOT-DELETE splicer.end(wrapper.Data.setInt) } |
In Object Oriented languages there is no _wrapObj method exposed to the user. Instead, the same functionality is achieved simply by calling “new” on the Impl class. Interestingly, this means the constructor functionality is NOT placed in a Babel ctor method, but is, instead, actually in the default object constructor.
Here is an excerpt from the class definition for wrapper.Data_Impl:
public String d_string; public int d_int; public String d_ctorTest; public Data_Impl(){ d_ior = _wrap(this); // DO-NOT-DELETE splicer.begin(wrapper.Data._wrap) d_ctorTest = "ctor was run"; // DO-NOT-DELETE splicer.end(wrapper.Data._wrap) } public void setString_Impl ( /*in*/ java.lang.String s ) throws sidl.RuntimeException.Wrapper { // DO-NOT-DELETE splicer.begin(wrapper.Data.setString) d_string = s; return ; // DO-NOT-DELETE splicer.end(wrapper.Data.setString) } public void setInt_Impl ( /*in*/ int i ) throws sidl.RuntimeException.Wrapper { // DO-NOT-DELETE splicer.begin(wrapper.Data.setInt) d_int = i; return ; // DO-NOT-DELETE splicer.end(wrapper.Data.setInt) } |
Here is the client code from WrapTest.java:
public static void main(String args[]) { wrapper.Data_Impl d_data = new wrapper.Data_Impl(); wrapper.User d_user = new wrapper.User(); System.out.println(d_data.d_ctorTest); d_user.accept(d_data); System.out.println(d_data.d_string, d_data.d_int); } |
In Object Oriented languages there is no _wrapObj method exposed to the user. Instead, the same functionality is achieved simply by calling “new” on the Impl class.
However, writing the Python backdoor constructor is a little trickier than Java or C++. This is because there is no overloading in Python, so multiple constructors were a problem. Instead, the class implementor needs to determine if the object is being constructed directly by the user, or through the normal Babel process. This can be achieved with an if statement. If the argument IORself == None, then the user has called the backdoor constructor, if IORself != None, it is a normal Babel construction.
Here is an excerpt from the class definition for wrapper.Data_Impl.Data:
class Data: def __init__(self, IORself = None): if (IORself == None): self.__IORself = wrapper.Data.Data(impl = self) else: self.__IORself = IORself # DO-NOT-DELETE splicer.begin(__init__) if(IORself == None): self.d_string = "placeholder value" self.d_ctortest = "ctor was run" self.d_int = 0 # DO-NOT-DELETE splicer.end(__init__) def setString(self, s): # DO-NOT-DELETE splicer.begin(setString) self.d_string = s # DO-NOT-DELETE splicer.end(setString) def setInt(self, i): # DO-NOT-DELETE splicer.begin(setInt) self.d_int = i # DO-NOT-DELETE splicer.end(setInt) |
Here is the client code from WrapTest.java:
import wrapper.User import wrapper.Data import wrapper.Data_Impl if __name__ == '__main__': user = wrapper.User.User() data = wrapper.Data_Impl.Data() print data.d_ctortest user.accept(data._getStub()) print data.d_string + " " + d_int 0 |