Swig:如何为 Tcl 编写包装器代码以将枚举类型的成员映射为字符串常量

Swig: how to write wrapper code for Tcl to map member of enum type as string constants

我已阅读 "C/C++ constants are installed as global Tcl variables containing the appropriate value",它也适用于枚举。我正在尝试使用 swig 为枚举 class(称为 "Statement")构建 Tcl 包装器,这将导致相应的 Tcl 变量存储为字符串对象。 C++ 代码提供了一些我认为可以用来执行转换的 ostream 转换工具,但我找不到可行的方法。我尝试了以下方法:

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

另一个(可能是相关的问题)是我的包装器中的枚举根本没有创建任何 Tcl 变量。我从 中读到,当您使用静态链接时,用于存储常量的 Tcl 变量将放在 ::swig 命名空间中。但这不是我的问题:我没有 ::swig 命名空间,而且 info vars 也没有在顶级命名空间中列出任何变量。

我找到了第二个问题的答案。我的包装器 SWIG 代码使用 %init 指令,该指令使用一些魔法来使用 readline 库。它正在评估一个 Tcl 脚本,该脚本在应用程序初始化的其余部分有机会完成之前启动 readline 命令处理循环。常量初始化代码是在提供给 %init SWIG 块的代码块之后生成的,因此它从未被执行过。通过将枚举的 SWIG 声明移到 %init 部分上方,更改了插入的常量初始化代码和 %init 段的相对顺序,问题得到解决。

Bottom-line:SWIG 包装器代码中声明和 %init 段的相对顺序很重要。

我能够使用以下形式的类型映射解决此问题:

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

之前它不起作用的原因是枚举是在命名空间语句中定义的。尽管我在类型映射声明之前有 'using namespace NS;' 语句,但在我提供枚举的完整名称空间限定符之前,它不会被应用。此外,必须在声明枚举常量的包装器代码之前提供两个类型映射语句。

如您所见,返回的变量名是一个Tcl数组变量名。为了保持一致,由生成的代码设置的包含枚举实际值的全局变量也需要更改。我能够像这样使用另一个类型映射来实现这一点:

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

在需要包装多个枚举类型的情况下,只需在每个枚举的 SWIG 声明之前插入一组类似的类型映射,将 Tcl 数组名称部分与映射的枚举类型相匹配。如果有 non-enum 常量要在您的 SWIG 代码中声明,或者您不想以这种方式包装的其他枚举类型,请在添加之前添加最后一个类型映射(constcode)以重置为默认行为声明这些其他常量的 SWIG 代码。

我创建了一个小示例来说明该方法:

// 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() << ")";
   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 >()));
 }
enum TOPETYPE {BI, DUL, BUC};
%typemap(out) enum MyClass::ETYPE {
#include <iostream>
   std::ostringstream oss;
   oss << "MyClass_ETYPE(MyClass_" << ETYPE2Str() << ")";
   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 >()));
 }
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() << ")";
   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 >()));
 }
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";}
  }
}

你可以试试这个:

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

然后,在 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