OMNeT++ 代码迁移指南:从 3.x 到 4.0
第一章: 3.x 版本后的改变
概述
因为 C++ API 、 NED 、 ini 和 msg 文件的改变,为 OMNeT++ 3.x 版本写的仿真模型是不能直接在 OMNeT++ 4.0 及其以后的版本中使用的。本文档描述了如何将一个 3.x 的模型转化为可以在 OMNeT++ 4.0 下运行的模型。
在进行代码迁移之前,你应该已经熟悉 OMNeT++ 3.x 和 4.0 版本。我们建议在继续之前看看 4.0 中的示例仿真文件。
NED 文件
NED 语言被大幅度的修改,并增加了大量的语法。还包括有扩展的一系列新概念:继承,模块和信道接口,内部类型,双向连接, package 结构,元信息(属性)等等。下面列表显示了从 3.x 版本以来的重大改变。
报文消息文件( msg )
field 属性的语法改成了和 NED 文件一致。
初始值设置文件( ini )
项目配置文件( Makefile )
makefile 的生产和 make 过程已经被重写过。现在,一个 opp_makemake --deep 可以替代非常复杂的 makefile 系统,这可以用于多目录的仿真模型,如 INET 框架。使用 opp_makemake -h 来获取更多的信息。
makefile 生成器可以生成三种类型的 makefile :
C++ 代码( cc/h )
下面是从 3.x 版本以来 API 的主要变化。请参看include/ChangeLog来获取更详细的信息。
环境变量
OMNETPP_BITMAP_PATH环境变量被更名成OMNETPP_IMAGE_PATH。 OMNeT++ 将会在运行时进行检查,如果发现老的环境变量将会打印出警告信息。
命令行选项
第二章:迁移工具
代码迁移的过程有些步骤可以自动化执行。 OMNeT++ 4 提供了几个实用的命令行工具来帮助代码迁移。这些工具可以在 OMNeT++ 4 的 migrate 目录下找到。
migratened
这个工具递归的对当前目录下的 .ned 文件进行迁移。此工具将完成下面的过程:
migratemsg
这个工具递归的对当前目录下的 .msg 文件进行迁移。此工具将完成下面的过程:
migrateini
这个工具递归的对当前目录下的 .ini 文件进行迁移。此工具将完成下面的过程:
migratecpp
这个工具递归的对当前目录下的 .cc 和 .h 文件进行迁移。此工具将完成下面的过程:
opp_makemake
这个工具并不是一个迁移工具,但是可以使用其来生成新的 Makefile 文件。旧的 Makefile 文件不能够使用。
第三章:如何迁移代码
我们推荐你按照几个步骤来迁移你的仿真模型:
。尽快的工作在 4.0 版本下:
。使用自动迁移脚本
。对自己的模型进行手工迁移,并使用尽可能少的新特性
。检查你的模型是否能够工作,并看是否可以产生和旧模型一样的结果(精确的或者是统计上的)
。使用 OMNeT++ 4 版本的新特性对其进行改进。
使仿真模型可以工作
。前提条件:已经安装好了 OMNeT++ 4.0 并可以工作,同时熟悉 IDE 的操作。
。为你的仿真模型做个备份。因为可能在迁移成功之前需要做多次尝试。
。切换到你的仿真模型目录,并运行在 <omnetpp>/migrate 中的所有脚本文件。
$ cd MyModel
$ ../omnetpp-4.0/migrate/migratened
$ ../omnetpp-4.0/migrate/migrateini
$ ../omnetpp-4.0/migrate/migratemsg
$ ../omnetpp-4.0/migrate/migratecpp | tee migratecpp.out
这些脚本文件将转换 NED 、 ini 、 msg 和 C++ 文件到 4.0 的格式。结果可能还需要进一步处理,因为不是所有的东西都可以自动转换的。脚本将在你可能需要手工修改的地方打印警告信息,所以请注意这些打印信息。特别的,migratecpp 脚本将打印一系列的注意、警告和提示,认真的读读它们。
。如果你的仿真模型是基于 INET 框架的,则首先要安装新的 INET ,然后执行在 INET 下的子目录 migrate/ 中的脚本。这将会根据 INET 框架的变化而对源代码进行修改。
$ cd MyModel
$ ../INET/migrate/migratened
$ ../INET/migrate/migrateini
$ ../INET/migrate/migratemsg
$ ../INET/migrate/migratecpp | tee migratecpp.out
。接下里的工作你可以在命令行中完成,也可以在 OMNeT++ IDE 中完成。我们推荐使用后者。为了使用 IDE ,需要为仿真模型创建一个工程。在菜单中选择 File | New | OMNeT + + Project... ,将会出现一个向导。在第一页中,取消“ Use default location ”的复选并选择你的仿真模型目录,然后继续其他向导页,并在最后点击 Finish ,你应该可以看到在左边的“ Project Explorer ”有了一个新工程,其中包含了你的工程中的文件。如果发生了错误,选择工程并点击 DEL 。 IDE 将会询问是否将文件也从磁盘中删除,选择“ No ”。然后可以重新开始工程的创建。
。如果你的工程是依赖于 INET 或者其他工程,你可以设置一个依赖于 INET 的工程。为了做到这点, INET 工程必须已经导入和打开,然后在你的工程中打开Properties 对话框(选择工程,右键点击,从上下文菜单中选择Properties ),然后在Project References页中检查 INET 。这将使得 INET 中的 NED 类型可以用你的工程,同时将 INET 的目录加入到了 C++ 的头文件路径。确保 INET 框架编译成了库(静态或者动态),这样你的工程才可以链接到 INET 。可以通过检查 INET 工程的Project Properties对话框中的C/C++ Build / Makemake页面。
。 OMNeT++ 4.0 版本中的 NED 有着和 Java 类似的包系统。如果你的模型在不同的子目录中包含有 NED 文件,这些子目录意味着包, NED 文件需要包声明同时增加了 import 。这在 IDE 中可以自动完成。创建或者打开你的工程,从菜单中选择Navigate | Clean up NED files...。选择你的工程并点击 OK 。 IDE 将修复所有的包声明并导入你的 NED 文件。
你可能需要一个 package.ned 文件来定义你的根包——这将在后面一节进行讨论。
。对 NED 文件进行修订。包括:
。修订 volatile 型的参数,看看它们是否必须要使用 volatile
在原来模型中如果没有指定为 const 的参数, volatile 的限定符可能会出现的比较多。如果一个参数在仿真过程中是不变的,则可以将 volatile 限定符安全的删除。所以作为一个规则,只有在仿真期间被读取的变量才有可能需要设置为 volatile ,而不是在初始化阶段。如果只是在initialize()函数中使用了的参数,则可以将 volatile 限定符去掉。
检查为 double 的参数是否可以为 int 。
3.x 版本中的 numeric 参数类型将会被自动转换为 double 类型,但是你可以根据需要将其改为 int 。确保你同步修改了相应的 C++ 源代码。
。somepar = input; 将变成somepar; ——你可能需要删除它们。
提示:
NED 文件中已经不支持使用 input 关键字,但是你可以在 ini 文件中设置**.somepar=ask来达到同样的效果。
。移除多余的网络定义
3.x 风格的网络定义实际上是一个网路的复合模块。在 4.0 版本中,一个复合模块可以直接被定义成为一个网络,而不需要多余的步骤。举个例子, 3.x 版本中的网络定义:
network cqn : CQN
endmodule
将会被自动迁移脚本转换为继承的形式:
network cqn extends CQN {
}
实际上,你可以完全删除这个定义。可以使用 network 关键字来改变 CQN 的模块定义(如network CQN {...}),同时在 ini 文件中将network=cqn替换为network= CQN 。
。 "like" 模块类型应该被改为接口,而且真实的类型声明应该相像。
例如,你有一个子模块:
app: <appType> like App;
App 应该变为一个模块接口(一般习惯上在前面加上一个“ I ”):
moduleinterface IApp {
gates:
input in;
output out;
}
而具体的类型则应该根据 IApp 来修改:
simple BurstyApp like IApp { ... }
simple AnotherApp like IApp { ... }
。编译你的仿真模型(右键点击你的工程,从上下文菜单中选择“ Build ”,或者是关闭所有的其他工程然后按下 Ctrl+B )。常见的编译错误以及解决方法:
。不能将SimTime 转换为 double ("Cannot convert SimTime to double")
simtime_t现在是一个基于int64的SimTime类,而不是 double 。无论在什么地方一个simtime_t被赋值成 double 类型,考虑将其修改为simtime_t。新的 SimTime 没有提供和 double 之间的隐式转换,因为这可能会造成 C++ 的歧义性错误。检查迁移工具的输出,其中给出了应该修改变量的一些提示。
提示:
如果需要的话,使用SIMTIME_DBL(t) 来将一个 simtime_t 类型转换为double类型。在 printf 函数中,使用"%s" 和SIMTIME_STR(t)。使用这些宏而不是SimTime 方法的好处是可以编译成-DUSE_DOUBLE_SIMTIME的兼容模式。
提示:
如果你使用了很多 double 类型的时间变量,同时西王佐一个快速的移植,有一个比较不是很干净的方法,那就是在 CFLAGS 中指定 -DUSE_DOUBLE_SIMTIME ,从而使得其保持原来的行为。需要注意的是,如果这样做的话,需要使用此标志重编译所有的 OMNeT++ 库。我们建议如果可能的话尽量使用新的 SimTime 类型。
。"No such method setBitLength/getBitLength/encapsulate/decapsulate"
长度和封装属性已经移到了cPacket中,这是cMessage的一个子类。你可能需要将 .msg 文件中的 message 关键字改为 packet 关键字,从而使得生成的类使用 cPacket 作为基类。
message ABCPacket {...} ==> packet ABCPacket {...}
在handleMessage()和其他函数中,将cMessage* 指针指向cPacket*:
cPacket *pkt = check_and_cast<cPacket *>(msg);
。"Cannot open file csimul.h" (or any other header)
只有<omnetpp.h>是公共的 API 。其他的 OMNeT++ 头文件不应该被直接引用,因为有可能在以后的版本中改名或者删除。
。"sendDirect() does not take 3 (or 4) arguments"
sendDirect()函数已经更改。过去此函数经常接受延时作为第二个参数。现在此函数有两个变种,一个没有时延参数(所以,如果你的仿真模型中是 0.0 ,则可以直接删除),而另外一个则接受传播时延和传输持续时间。如果你准备使用第二种的话,你可能需要在目标模块中接收门的initialize()方法中调用setDeliverOnReceptionStart(true)。
。运行你的仿真模型。常见的错误和解决方法:
。"Cannot convert unit 'none' to 'seconds'"
物理类型必须写在表达式中,所以需要将 5 改为 5s ,而将exponential(1)改为exponential(1 s )。
。"Cannot convert unit 'none' to 'bps'"
现在的信道数据率参数有了物理单位: bps (bit/sec),这个单位必须写出,支持的单位包括Kbps, Mbps, Gbps。
。"No such module type 'X'"
如果你的模型动态创建了模块,模块类型必须使用一个完全的属性名称进行查找(如"some.package.X")。
第四章:使用新的 OMNeT++ 特性
NED 文件
。增加了默认的图标。
现在可以为模块类型指定一个显示字符串(包含有一个图标等)。在运行的时候默认值将和子模块的显示字符串合并成一个有效的显示字符串。为了能够使的模块显示默认图标,你可以将子模块中的"i="标签移动到相应的简单模块类型。结果将项下面一样:
simple Node {
@display("i=block/fork");
...
}
module Net {
submodules:
node1 : Node {
@display("p=240,100"); // note: "i" tag moved out
}
...
}
。在 ini 文件中定义的值可以放入对应的 NED 文件中作为默认值。如果你的 ini 文件中包含有大量的很少改动的参数,可以将其移动到 NED 文件中。使用下面的语法:
int somepar = default(42);
。使用 @unit为你的模块参数指定物理单位。这将强迫参数中包含有物理单位。
volatile double interArrivalTime @unit(s);
。使用模块的继承。
如果你有几个模块都有着同样的行为,而仅仅只是参数不同。你可以使用模块继承。举个例子:
simple Router {
int ports;
}
simple Router8 extends Router { // still uses the "Router" C++ class!
ports = 8;
}
simple Router16 extends Router {
ports = 16;
}
注意: C++ 类将从记模块中继承,也就是说,所有的三个模块都将使用 C++ 的Router类,即使在 C++ 中有 Router8 类等。为了能够将 C++ 类也替换,需要增加一个 @class 属性:
simple AdvancedRouter extends Router {
@class(AdvancedRouter); // makes it use the "AdvancedRouter" C++ class
}
继承同样可以用于将几个复合模块的相同部分组合成一个基类型。继承类可以继续增加子模块和连接,以及新的参数和门。
。使用 inout 门来表示双向连接。一对单向连接可以通过一个双向连接替代。下面是语法示例:
gates:
inout port;
...
connections:
node1.port <--> node2.port;
。使用内类型
如果只是在本地使用一个类型,则可以将其转化为内类型。这在信道定义的时候特别有用。
module Network {
types:
channel Ethernet extends ned.DatarateChannel {
datarate = 100Mbps;
};
...
connections:
node1.port <--> Ethernet <--> node2.port;
...
}
。如果你准备将你的模型给别人的话,为你的 NED 文件定义一个包的根目录。这可以避免和其他模型的名字冲突。为了做到这点,放一个