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
我已阅读
//%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 变量。我从
我找到了第二个问题的答案。我的包装器 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