0

I have read here that "C/C++ constants are installed as global Tcl variables containing the appropriate value", which applies to enum as well. I am trying to build a Tcl wrapper using swig for an enum class (called "Statement") that will result into the corresponding Tcl variables to be stored as string objects. The C++ code provides some ostream conversion facilities that I thought I could use to perform the conversion, but I cannot find a recipe that will work. I have tried the following:

    //%typemap(argout) Statement *out {
    //  ostringstream oss;
    //  oss << $1;
    //  $result = Tcl_NewStringObj(oss.str()->c_str(), oss.str().size());
    //}
    //%typemap(constcode) Statement {
    //  ostringstream oss;
    //  oss << $1;
    //  $result = Tcl_NewStringObj(oss.str()->c_str(), oss.str().size());
    //}
    //%typemap(out) Statement {
    //  ostringstream oss;
    //  oss << $1;
    //  $result = Tcl_NewStringObj(oss.str()->c_str(), oss.str().size());
    //}

Another (maybe related issue) is that no Tcl variables are created at all from the enums in my wrapper. I read from this follow up link that when you use static linking, the Tcl variables used to store constants will be put in the ::swig namespace. But this is not my problem here: I do not have a ::swig namespace, and info vars does not list any variables in the top namespace either.

LMNCA
  • 21
  • 6

2 Answers2

0

I have found the answer for the second issue. My wrapper SWIG code uses the %init directive which uses some magic to make use of the readline library. It was evaluating a Tcl script that starts the readline command processing loop before the rest of the application initialization got a chance to complete. The constant initialization code was generated after the block of code provided to the %init SWIG block, so it never got executed. By moving the SWIG declaration of the enum above the %init section, the relative order of inserted constant initialization code and %init segment was changed, and the issue was solved.

Bottom-line: the relative order of declaration and %init segment in your SWIG wrapper code matters.

LMNCA
  • 21
  • 6
0

I was able to resolve this using a typemap of the form:

    %typemap(out) enum NS::Statement  {
        ostringstream oss;
        oss << "NS_Statement(" << $1 << ")";
        Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), oss.str().size()));
    }

The reason it was not working previously is that the enum is defined within a namespace statement. Even though I had 'using namespace NS;' statement before the typemap declaration, it was not being applied until I provided the full namespace qualifier of the enum. Also, both typemap statements had to be provided before the wrapper code declaring the enum constants.

As you can see, the variable name that is returned is a Tcl array variable name. In order for things to be consistent, the global variables set by the generated code that contain the actual values of the enum need also be changed. I was able to achieve that using another typemap like so:

    %typemap(constcode,noblock=1) int {
      %set_constant("NS_Statement($symname)", SWIG_From_long(static_cast< int >($1)));
    }

In the case where you need to wrap multiple enum type, just insert a similar set of typemaps for each enum before its SWIG declaration, matching the Tcl array name part with the enum type being mapped. In case where there are non-enum constants to be declared in your SWIG code, or other enum types that you do not want to wrap in that way, add a last typemap(constcode) to reset to default behavior before you add the SWIG code declaring these other constants.

I have created a small example that illustrates that approach:

// file example.h
enum TOPETYPE {BI, DUL, BUC};
class MyClass {
public:
  enum ETYPE {ONE,TWO, THREE};
  static void Foo(ETYPE);
  static ETYPE Bar(int);
};
namespace NS {
  enum LIBENUM {LIB1, LIB2, LIB3};
}
extern const char * ETYPE2Str(MyClass::ETYPE);
extern const char * TOPETYPE2Str(TOPETYPE);
extern const char * LIBENUM2Str(NS::LIBENUM);
/* File : example.i */
%module example

%{
#include "example.h"
#include <sstream>
using namespace std;
%}

#define XX 0
%typemap(out) enum TOPETYPE {
#include <iostream>
   std::ostringstream oss;
   oss << "TOPETYPE(" << TOPETYPE2Str($1) << ")";
   Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), -1));
 }
%typemap(constcode,noblock=1) int {
  %set_constant("TOPETYPE($symname)", SWIG_From_long(static_cast< int >($1)));
 }
enum TOPETYPE {BI, DUL, BUC};
%typemap(out) enum MyClass::ETYPE {
#include <iostream>
   std::ostringstream oss;
   oss << "MyClass_ETYPE(MyClass_" << ETYPE2Str($1) << ")";
   Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), -1));
 }
%typemap(constcode,noblock=1) int {
  %set_constant("MyClass_ETYPE($symname)", SWIG_From_long(static_cast< int >($1)));
 }
class MyClass {
public:
  enum ETYPE {ONE,TWO, THREE};
  static void Foo(ETYPE);
  static ETYPE Bar(int);
};
%typemap(out) enum NS::LIBENUM {
#include <iostream>
   std::ostringstream oss;
   oss << "NS_LIBENUM(" << LIBENUM2Str($1) << ")";
   Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), -1));
 }
%typemap(constcode,noblock=1) int {
  %set_constant("NS_LIBENUM($symname)", SWIG_From_long(static_cast< int >($1)));
 }
namespace NS {
  enum LIBENUM {LIB1, LIB2, LIB3};
}
// file example.cpp
#include "example.h"
#include <iostream>
using namespace std;
void MyClass::Foo(MyClass::ETYPE typ)
{
  cout << "Enum value = " << typ << endl;
}
MyClass::ETYPE MyClass::Bar(int val)
{
  switch (static_cast<MyClass::ETYPE>(val)) {
  case MyClass::ETYPE::ONE: {return MyClass::ETYPE::ONE;}
  case MyClass::ETYPE::TWO: {return MyClass::ETYPE::TWO;}
  case MyClass::ETYPE::THREE: {return MyClass::ETYPE::THREE;}
  default: {return MyClass::ETYPE::THREE;}
  }
}
const char * ETYPE2Str(MyClass::ETYPE val) {
  switch (val) {
  case MyClass::ETYPE::ONE: {return "ONE";}
  case MyClass::ETYPE::TWO: {return "TWO";}
  case MyClass::ETYPE::THREE: {return "THREE";}
  default: {return "unknown";}
  }
}
const char * TOPETYPE2Str(TOPETYPE val) {
  switch (val) {
  case TOPETYPE::BI: {return "BI";}
  case TOPETYPE::DUL: {return "DUL";}
  case TOPETYPE::BUC: {return "BUC";}
  default: {return "unknown";}
  }
}
const char * LIBENUM2Str(NS::LIBENUM val) {
  switch (val) {
  case NS::LIB1: {return "LIB1";}
  case NS::LIB2: {return "LIB2";}
  case NS::LIB3: {return "LIB3";}
  default: {return "unknown";}
  }
}

You can try this out with:

swig -c++ -tcl8 example.i
g++ -c -fpic example_wrap.cxx example.cpp -I/usr/local/include
g++ -shared example.o example_wrap.o -o example.so

And then, inside tclsh:

% load example4.so
% info vars
XX tcl_rcFileName tcl_version argv0 argv tcl_interactive auto_path errorCode NS_LIBENUM errorInfo auto_execs auto_index env tcl_pkgPath MyClass_ETYPE TOPETYPE tcl_patchLevel swig_runtime_data_type_pointer4 argc tcl_library tcl_platform
% info commands
MyClass_Bar tell socket subst open eof pwd glob list pid exec auto_load_index time unknown eval lassign lrange fblocked lsearch auto_import gets case lappend proc break variable llength auto_execok return linsert error catch clock info split array if fconfigure concat join lreplace source fcopy global switch auto_qualify update close cd for auto_load file append lreverse format unload read package set binary namespace scan delete_MyClass apply trace seek while chan flush after vwait dict continue uplevel foreach lset rename fileevent regexp new_MyClass lrepeat upvar encoding expr unset load regsub history interp exit MyClass puts incr lindex lsort tclLog MyClass_Foo string
% array names NS_LIBENUM
LIB1 LIB2 LIB3
% array names MyClass_ETYPE
MyClass_TWO MyClass_ONE MyClass_THREE
% array names TOPETYPE
DUL BUC BI
% puts $XX
0
% MyClass_Bar $MyClass_ETYPE(MyClass_ONE)
MyClass_ETYPE(MyClass_ONE)
% MyClass_Foo $MyClass_ETYPE(MyClass_ONE)
Enum value = 0
% exit
LMNCA
  • 21
  • 6