WuPiN G XiN BloG

It is All about Traffic, and Programming

Search

Archives

0 visitors online now
0 guests, 0 members

Aimsun scripting: A different perspective

Aimsun is an Object-Oriented Framework and Unified Platform for Traffic Modeling.  It  provides quite a few programming interfaces for advanced applications and customization. To name a few,  those  include Aimsun Simulator API,  Aimsun SDK,  Aimsun Python-based Scripting,  MicroSDK,  Logistics SDK, Parking APIs.  These SDKs and APIs  together with Aimsun main software  provide powerful tools for its users.

Interestingly, by design all these APIs, albeit having different focuses, have ONE in common, i.e., they MUST either be invoked within Aimsun host ( e.g., Aimsun SDK and Aimsun Pyhton-based Scripting), or called-back during run time (i.e., simulator API and MicroSDK). This means Aimsun cannot be used as a COM automation sever, and therefore it may not be invoked by external applications.

Actually, it is only partially true.  First of all, this is not something to complain about.  Aimsun is a cross-platform software package;  it runs on Linux, MacOS, and Windows. COM is a very much Windows specific technology, so in spite of that it is doable it simply doesn’t seem to make much sense to dedicate Aimsun to a platform specific feature.  Second some the COM-like functionality can be achieved by using ANGApp class, which is fully accessible by Python scripting.

In this post,  I would like to discuss a way of porting ANGApp class by Python C API,  so that some of  its advanced functionality becomes available to a common compiler-based language.  To start with,  the following shows a Python wrapper class that encapsulate some COM-like interfaces, on top of the ANGApp class:

from PyANGBasic   import *
from PyANGApp     import *
from PyANGAimsun  import *
from PyANGKernel  import *
from PyANGConsole import *

__author__    = "Wuping Xin"
__company__   = "No Inc."
__copyright__ = "Copyright (c) No Rights Reserved"
__license__   = "python"
__version__   = "$1.0.0 $"
__date__      = "$Date: 2009/07/15 11:57 $"

class AimsunApp:
"Wrapper class of Aimsun ANGApp class for C++ application with Python C API"
def __init__(self, isCon = False):
  self.angInstance = None
  self.simulator   = None
  self.model       = None
  self.angGui      = None
  self.isConsole   = isCon
  def initInstance(self):
  pass

def freeInstance(self):
pass

def openNetwork(self,filename):
pass

def runInteractive(self):
  self.angInstance.run()

def __setModel(self):
  self.model = self.angInstance.getModel()

def initSimulator(self,repID,simMode = 0):

.......
def runSingleStep(self):
  self. angInstance.processEvents()
  self.simulator.simulationStep()

With this wrapper class ready, we can use the following C code to invoke:

Py_Initialize();
..........

pName    = PyString_FromString("aimsunpy_interface");
pModule  = PyImport_Import(pName);
pDict    = PyModule_GetDict(pModule);

// Build the name of a callable class
pClass = PyDict_GetItemString(pDict, "AimsunApp");

// Create an instance of the wrapper cls.
if (PyCallable_Check(pClass))
{
    pInstance = PyObject_CallObject(pClass, NULL);
}

PyObject_CallMethod(pInstance, "initInstance", NULL);
PyObject_CallMethod(pInstance, "openNetwork","s","C:\\Test\\test.ang");
PyObject_CallMethod(pInstance, "initSimulator","i",346);

This is a nuance to note – when trying to load a C-Extension module ( i.e., *.pyd file),  if the host program that embeds Python interpreter links with pythonxx_d.dll, then PyImport_Import will import the xxxx_d.pyd, even if the module’s name is specified without “_d”!  Also this approach is dependent on Aimsun’s embedded Python  interpreter hence there is some run-time performance hit. But if that is not an issue, this approach would make it possible to integrate Aimsun with external applications, for example, MATLAB platform.

Inside Aimsun ANGApp.close

Aimsun ANGApp is a class available to python scripting. It is a very interesting class that TSS provides to its general users, and can be very powerful if the user really appreciates how it works.

In the past a few days, I have been cleaning up my previous codes, while working on a project proposal that may require Aimsun micro + meso. And for some reason, one code snippet that has worked previously crashes on ANGApp.close … …

This time I am pretty hesitant to write to TSS for help – though I am sure they are going to help, as always TSS is very kind and prompt in supporting their users – sometimes a little delay if they are busy. And for these couple of days, I know they are being working their tails off on the new release of Aimsun 6.1.0 – couldn’t be more busier.

So I decided not to bother them and find out myself.

All rightee ……the question is, what is really going on inside ANGAPP.close()? And why there is a crash? Let’s take a partial look at the code segment:

OK, save the current state of ESI by pushing it to stack. This is a no brainer.
.text:00B620E0 push esi

Note, ECX always holds the “this” pointer for C++ class. So, after mov ESI, ECX, ESI stores the starting address of the current ANGApp instance
.text:00B620E1 mov esi, ecx

Now, since ESI+0 is the starting address of the subject ANGApp instance, ESI + 8 must be the address for some private member-variable, and by looking at the instruction at 00B620EA , we can immediately get the hint that it must be the address for a GGui* pointer . Now, what about ESI + 4? Note the fact that ANGApp inherits from QObject, thus ESI+4 actually is the address for a protected pointer of type QObjectData. What about ESI+0xC? Hey, are you as curious as I am? That is a good question! That is actually the address for a pointer of type GKModel *.

Doh~!
.text:00B620E3 mov ecx, [esi+8]

IS the GGui* NULL?
.text:00B620E6 test ecx, ecx

.text:00B620E8 jz short loc_B620FC

If the GGui* is not NULL then call GGui.getActiveModel
.text:00B620EA call ds:__imp_?getActiveModel@GGui@@QBEPAVGKModel@@XZ ;

.text:00B620F0 mov ecx, [esi+8]

Now after invoking GGui.getActiveModel, EAX holds the returned pointer for GKModel
.text:00B620F3 push eax

Then GGui.closeDocument is called with the returned GKModel pointer from above instruction
.text:00B620F4 call ds:__imp_?closeDocument@GGui@@QAE_NPAVGKModel@@@Z

Okaydokay. So far so good! Now restore ESI.
.text:00B620FA pop esi
.text:00B620FB retn
.text:00B620FC ; —————————————————————————
.text:00B620FC

.text:00B620FC loc_B620FC:
[ESI + 0xC] is the address that holds the pointer of type GKModel*
.text:00B620FC mov ecx, [esi+0Ch]

Now ECX holds the pointer to GKModel
.text:00B620FF test ecx, ecx

.text:00B62101 jz short loc_B62112

Here is the tricky part. [ECX +0] is the address of the v-table of GKModel class
.text:00B62103 mov eax, [ecx]

Now EDX stores the address of the v-table of GKModel class
.text:00B62105 mov edx, [eax]

Index 1 means the first virtual function in the v-table. Note, GKModel inherits from GKObject. Therefore, the first virtual function in the v-table is the destructor ~GKObject()
.text:00B62107push 1

Now call the destructor
.text:00B62109 call edx

Reset the GKModel* pointer to zero.
.text:00B6210B mov dword ptr [esi+0Ch], 0

This is perfect code logically showing no problem at all! And it turns out that the crash was caused by a corrupted memoy I artificially manipulated which screw up ANGApp’s initernal data!