Index: src/client/python/xplra.py
===================================================================
--- src/client/python/xplra.py	(revision 32)
+++ src/client/python/xplra.py	(revision 34)
@@ -127,4 +127,8 @@
         """Determine if we are connected to the simulator."""
         return self._stream is not None
+
+    def createMultiGetter(self):
+        """Create a new multi-dataref getter for this X-Plane object."""
+        return MultiGetter(self)
 
     def getInt(self, name):
@@ -354,2 +358,222 @@
 
 #-------------------------------------------------------------------------------
+
+class MultiBuffer(object):
+    """Buffer for querying or setting multi-dataref values."""
+    @staticmethod
+    def _getDefault(type, length):
+        """Get the default value for the given type."""
+        if type==TYPE_INT:
+            return 0
+        elif type==TYPE_FLOAT:
+            return float(0.0)
+        elif type==TYPE_DOUBLE:
+            return 0.0
+        elif length<=0:
+            return []
+        elif type==TYPE_FLOAT_ARRAY:
+            return [float(0.0)] * length
+        elif type==TYPE_INT_ARRAY or type==TYPE_BYTE_ARRAY:
+            return [0] * length
+
+    def __init__(self, xplane, registerCommand, unregisterCommand):
+        """Construct the buffer for the given XPlane instance and with the
+        given register/unregister command values."""
+        self._xplane = xplane
+        self._registerCommand = registerCommand
+        self._unregisterCommand = unregisterCommand
+
+        self._dataRefs = []
+        self._values = None
+
+        self._registeredID = None
+
+    def addInt(self, name):
+        """Add an integer to the buffer with the given name
+
+        Returns an ID (or index) of the dataref."""
+        return self._add(name, TYPE_INT)
+
+    def addFloat(self, name):
+        """Add a float to the buffer with the given name
+
+        Returns an ID (or index) of the dataref."""
+        return self._add(name, TYPE_FLOAT)
+
+    def addDouble(self, name):
+        """Add a double to the buffer with the given name
+
+        Returns an ID (or index) of the dataref."""
+        return self._add(name, TYPE_DOUBLE)
+
+    def addFloatArray(self, name, length = -1, offset = 0):
+        """Add a floating point array to the buffer with the given name.
+
+        Returns an ID (or index) of the dataref."""
+        return self._add(name, TYPE_FLOAT_ARRAY, length, offset)
+
+    def addIntArray(self, name, length = -1, offset = 0):
+        """Add an integer array to the buffer with the given name.
+
+        Returns an ID (or index) of the dataref."""
+        return self._add(name, TYPE_INT_ARRAY, length, offset)
+
+    def addByteArray(self, name, length = -1, offset = 0):
+        """Add a byte array to the buffer with the given name.
+
+        Returns an ID (or index) of the dataref."""
+        return self._add(name, TYPE_BYTE_ARRAY, length, offset)
+
+    def finalize(self):
+        """Finalize the buffer, if not finalized yet.
+
+        It initializes the array of values with some defaults.
+
+        Returns whether there is any data in the buffer."""
+        if self._values is None:
+            self._values = [self._getDefault(type, length)
+                            for (_, type, length, _) in self._dataRefs]
+        return self._values!=[]
+
+    def register(self):
+        """Register the buffer in X-Plane."""
+        if self.finalize() and self._registeredID is None:
+            self._writeSpec(self._registerCommand)
+            self._registeredID = self._xplane._readU32()
+
+    def unregister(self):
+        """Unregister the buffer from X-Plane."""
+        if self._registeredID is not None:
+            self._xplane._writeU8(self._unregisterCommand)
+            self._xplane._writeU32(self._registeredID)
+            self._xplane._flush()
+            self._xplane._checkResult()
+            self._registeredID = None
+
+    def execute(self):
+        """Perform the querying or the setting of the values.
+
+        It first checks if the buffer is finalized. If not, it will be
+        finalized. However, if it is not finalized but also registered, it will
+        first be unregistered and the re-registered after finalizing."""
+        if self._values is None and self._registeredID is not None:
+            self.unregister()
+            self.register()
+        else:
+            self.finalize()
+
+        if self._registeredID is None:
+            self._executeUnregistered()
+        else:
+            self._executeRegistered()
+
+    def getString(self, id):
+        """Get the value of the dataref with the given ID as a string.
+
+        The dataref should be of type byte array."""
+        if self._dataRefs[id][1]!=TYPE_BYTE_ARRAY:
+            raise TypeError("xplra.MultiBuffer.getString: only byte arrays can be converted to strings")
+        if self._values is None:
+            self.finalize()
+        s = ""
+        for c in self._values[id]:
+            if c==0: break
+            s += chr(c)
+        return s
+
+    def _add(self, name, type, length = None, offset = None):
+        """Add a scalar to the buffer with the given name and type"""
+        index = len(self._dataRefs)
+        self._values = None
+        self._dataRefs.append( (name, type, length, offset) )
+        return index
+
+    def _writeSpec(self, command):
+        """Write the specification preceded by the given command and check for
+        the result.
+
+        The specification is basically the list of the datarefs."""
+        self._xplane._writeU8(command)
+        self._xplane._writeU32(len(self._dataRefs))
+
+        for (name, type, length, offset) in self._dataRefs:
+            self._xplane._writeString(name)
+            self._xplane._writeU8(type)
+            if length is not None and offset is not None:
+                self._xplane._writeS32(length)
+                self._xplane._writeS32(offset)
+
+        self._xplane._flush()
+        self._xplane._checkResult()
+
+    def __len__(self):
+        """Get the number of value items in the buffer."""
+        if self._values is None:
+            self.finalize()
+        return len(self._values)
+
+    def __getitem__(self, id):
+        """Get the item with the given ID."""
+        if self._values is None:
+            self.finalize()
+        return self._values[id]
+
+    def __setitem__(self, id, value):
+        """Set the item with the given ID."""
+        if self._values is None:
+            self.finalize()
+        type = self._dataRefs[id][1]
+        if type==TYPE_INT:
+            self._values[id] = int(value)
+        elif type==TYPE_FLOAT:
+            self._values[id] = float(value)
+        elif type==TYPE_DOUBLE:
+            self._values[id] = double(value)
+        elif type==TYPE_FLOAT_ARRAY:
+            self._values[id] = [float(x) for x in value]
+        elif type==TYPE_INT_ARRAY:
+            self._values[id] = [int(x) for x in value]
+        elif type==TYPE_BYTE_ARRAY:
+            if instanceof(value, str):
+                self._values[id] = [ord(x) for x in value]
+            else:
+                self._values[id] = [int(x) for x in value]
+
+    def __iter__(self):
+        """Get an iterator over the values of this buffer."""
+        if self._values is None:
+            self.finalize()
+        return iter(self._values)
+
+
+#-------------------------------------------------------------------------------
+
+class MultiGetter(MultiBuffer):
+    """Multi-dataref buffer for querying."""
+    def __init__(self, xplane):
+        """Construct the getter."""
+        super(MultiGetter, self).__init__(xplane,
+                                          COMMAND_REGISTER_GET_MULTI,
+                                          COMMAND_UNREGISTER_GET_MULTI)
+
+    def _executeRegistered(self):
+        """Execute the query if the buffer is registered."""
+        self._xplane._writeU8(COMMAND_EXECUTE_GET_MULTI)
+        self._xplane._writeU32(self._registeredID)
+        self._xplane._flush()
+
+        self._xplane._checkResult()
+
+        self._readValues()
+
+    def _executeUnregistered(self):
+        """Execute the query if the buffer is not registered."""
+        self._writeSpec(COMMAND_GET_MULTI)
+        self._readValues()
+
+    def _readValues(self):
+        """Read the values into the values array."""
+        for i in range(0, len(self._dataRefs)):
+            self._values[i] = self._xplane._readValue(self._dataRefs[i][1])
+
+#-------------------------------------------------------------------------------
Index: test/multigettest.py
===================================================================
--- test/multigettest.py	(revision 34)
+++ test/multigettest.py	(revision 34)
@@ -0,0 +1,106 @@
+# Multi-dataref query test program for the Python client library
+
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    import sys
+    import time
+    from xplra import XPlane, ProtocolException
+
+    xplane = XPlane()
+
+    dontregister = "dontregister" in sys.argv
+
+    try:
+        print "Connecting to X-Plane..."
+        xplane.connect()
+        print "Connected to X-Plane."
+        print
+
+        getter = xplane.createMultiGetter()
+
+        tailnumID = getter.addByteArray("sim/aircraft/view/acf_tailnum", 40)
+        daysID = getter.addInt("sim/time/local_date_days")
+        zuluSecID = getter.addFloat("sim/time/zulu_time_sec")
+        pausedID = getter.addInt("sim/time/paused")
+        latitudeID = getter.addDouble("sim/flightmodel/position/latitude")
+        longitudeID = getter.addDouble("sim/flightmodel/position/longitude")
+        replayID = getter.addInt("sim/operation/prefs/replay_mode")
+        overspeedID = getter.addInt("sim/flightmodel/failures/over_vne")
+        stalledID = getter.addInt("sim/flightmodel/failures/stallwarning")
+        onTheGroundID = getter.addInt("sim/flightmodel/failures/onground_any")
+        emptyWeightID = getter.addFloat("sim/aircraft/weight/acf_m_empty")
+        payloadWeightID = getter.addFloat("sim/flightmodel/weight/m_fixed")
+        grossWeightID = getter.addFloat("sim/flightmodel/weight/m_total")
+        headingID = getter.addFloat("sim/flightmodel/position/psi")
+        pitchID = getter.addFloat("sim/flightmodel/position/theta")
+        bankID = getter.addFloat("sim/flightmodel/position/phi")
+        iasID = getter.addFloat("sim/flightmodel/position/indicated_airspeed2")
+        machID = getter.addFloat("sim/flightmodel/misc/machno")
+        groundSpeedID = getter.addFloat("sim/flightmodel/position/groundspeed")
+        vsID = getter.addFloat("sim/flightmodel/position/vh_ind_fpm2")
+        radioAltitudeID = getter.addFloat("sim/flightmodel/position/y_agl")
+        altitudeID = getter.addFloat("sim/flightmodel/position/elevation")
+        gLoadID = getter.addFloat("sim/flightmodel/forces/g_nrml")
+        flapsControlID = getter.addFloat("sim/flightmodel/controls/flaprqst")
+        flapsLeftID = getter.addFloat("sim/flightmodel/controls/flaprat")
+        flapsRightID = getter.addFloat("sim/flightmodel/controls/flap2rat")
+        navLightsID = getter.addInt("sim/cockpit/electrical/nav_lights_on")
+        beaconLightsID = getter.addInt("sim/cockpit/electrical/beacon_lights_on")
+        strobeLightsID = getter.addInt("sim/cockpit/electrical/strobe_lights_on")
+        landingLightsID = getter.addInt("sim/cockpit/electrical/landing_lights_on")
+        pitotID = getter.addInt("sim/cockpit/switches/pitot_heat_on")
+        parkingID = getter.addFloat("sim/flightmodel/controls/parkbrake")
+        gearControlID = getter.addInt("sim/cockpit2/controls/gear_handle_down")
+        noseGearID = getter.addFloatArray("sim/flightmodel2/gear/deploy_ratio", 1)
+        altimeterID = getter.addFloat("sim/cockpit/misc/barometer_setting")
+        qnhID = getter.addFloat("sim/flightmodel/misc/Qstatic")
+        navTypesID = getter.addIntArray("sim/cockpit/radios/nav_type", 6)
+        busVoltsID = getter.addFloatArray("sim/cockpit2/electrical/bus_volts", 4)
+
+        if dontregister:
+            getter.finalize()
+        else:
+            print "Registering getter..."
+            getter.register()
+            print "Registered getter."
+            print
+
+        while True:
+            getter.execute()
+            print "tail number: '%s', days: %d, zuluSec: %f" % \
+                (getter.getString(tailnumID), getter[daysID], getter[zuluSecID])
+            print "paused=%d, replay=%d, overspeed=%d, stalled=%d, onTheGround=%d" % \
+                (getter[pausedID], getter[replayID], getter[overspeedID],
+                 getter[stalledID], getter[onTheGroundID])
+            print "emptyWeight=%f, payloadWeight=%f, grossWeight=%f" % \
+                (getter[emptyWeightID], getter[payloadWeightID],
+                 getter[grossWeightID])
+            print "latitude=%f, longitude=%f, radioAltitude=%f, altitude=%f, gLoad=%f" % \
+                (getter[latitudeID], getter[longitudeID],
+                 getter[radioAltitudeID]/.3048, getter[altitudeID]/.3048,
+                 getter[gLoadID])
+            print "heading=%f, pitch=%f, bank=%f, ias=%f, mach=%f, groundSpeed=%f, vs=%f" % \
+                (getter[headingID], getter[pitchID], getter[bankID],
+                 getter[iasID], getter[machID], getter[groundSpeedID],
+                 getter[vsID]);
+            print "flapsControl=%f, flapsLeft=%f, flapsRight=%f" % \
+                (getter[flapsControlID], getter[flapsLeftID],
+                 getter[flapsRightID]);
+            print "Lights: NAV: %d, beacon: %d, strobe: %d, landing: %d" % \
+                (getter[navLightsID], getter[beaconLightsID],
+                 getter[strobeLightsID], getter[landingLightsID]);
+            print "pitot: %d, parking: %f, gearControl: %d, noseGear: %f" % \
+                (getter[pitotID], getter[parkingID],
+                 getter[gearControlID], getter[noseGearID][0]);
+            print "altimeter: %f, qnh: %f" % \
+                (getter[altimeterID], getter[qnhID]);
+            print "NAV types:", getter[navTypesID].tolist()
+            print "bus voltages:", getter[busVoltsID].tolist()
+            print
+            time.sleep(1.0)
+
+    except Exception as e:
+        print ">>>>>>>>>>>>>>>>>>>>>> Exception caught:", str(e)
+    finally:
+        xplane.disconnect()
