From 3bf609679c76acbfe95167e2e44b9133dcc85b40 Mon Sep 17 00:00:00 2001 From: Nicholas Little Date: Sun, 15 Feb 2015 14:03:52 +0000 Subject: [PATCH] properties: Correct test execution This commit: - Corrects the failing Property unit test; - Adds ExportObject.HandlePropertyCall(MessageContainer); - Adds Mapper.GetInterfaceType(Type, string); - Adds Mapper.GetPublicProperties(Type); - Adds a structure, PropertyCallers to hold the property type and a MethodCaller (or null) for the accessor and mutator; - Adds TypeImplementor.GenPropertyCallers(PropertyInfo) which produces the above structure; - Adds IEnumerable MessageReader.ReadValues() method which will yield objects based on the signature of the received Message the MessageReader was constructed with. --- src/BusObject.cs | 3 +- src/ExportObject.cs | 112 ++++++++++++++++++++++++++++++++-- src/Mapper.cs | 11 ++++ src/Protocol/MessageReader.cs | 7 +++ src/TypeImplementer.cs | 87 ++++++++++++++++++++++++++ 5 files changed, 214 insertions(+), 6 deletions(-) diff --git a/src/BusObject.cs b/src/BusObject.cs index d7827d9..db8af86 100644 --- a/src/BusObject.cs +++ b/src/BusObject.cs @@ -3,6 +3,7 @@ // See COPYING for details using System; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Collections.Generic; @@ -171,7 +172,7 @@ public object SendPropertyGet (string iface, string property) MessageReader reader = SendMethodCall ("org.freedesktop.DBus.Properties", "Get", "ss", writer, typeof(object), out exception); - return reader.ReadVariant (); + return reader.ReadValues ().FirstOrDefault (); } public void SendPropertySet (string iface, string property, object value) diff --git a/src/ExportObject.cs b/src/ExportObject.cs index df9c195..18939ad 100644 --- a/src/ExportObject.cs +++ b/src/ExportObject.cs @@ -14,16 +14,27 @@ namespace DBus { using Protocol; + internal class PropertyCallers { + public Type Type { get; set; } + public MethodCaller Get { get; set; } + public MethodCaller Set { get; set; } + } + //TODO: perhaps ExportObject should not derive from BusObject internal class ExportObject : BusObject, IDisposable { //maybe add checks to make sure this is not called more than once //it's a bit silly as a property bool isRegistered = false; + + Dictionary propertyInfoCache = new Dictionary (); + Dictionary methodInfoCache = new Dictionary (); static readonly Dictionary mCallers = new Dictionary (); + static readonly Dictionary pCallers = new Dictionary (); + public ExportObject (Connection conn, ObjectPath object_path, object obj) : base (conn, null, object_path) { Object = obj; @@ -79,11 +90,25 @@ public static ExportObject CreateExportObject (Connection conn, ObjectPath objec return new ExportObject (conn, object_path, obj); } + private static string Key (string iface, string member) + { + return string.Format ("{0}.{1}", iface, member); + } + public virtual void HandleMethodCall (MessageContainer method_call) { + switch (method_call.Interface) { + case "org.freedesktop.DBus.Properties": + HandlePropertyCall (method_call); + return; + } + + var cache_key = Key (method_call.Interface, method_call.Member); MethodInfo mi; - if (!methodInfoCache.TryGetValue (method_call.Member, out mi)) - methodInfoCache[method_call.Member] = mi = Mapper.GetMethod (Object.GetType (), method_call); + if (!methodInfoCache.TryGetValue (cache_key, out mi)) { + mi = Mapper.GetMethod (Object.GetType (), method_call); + methodInfoCache [cache_key] = mi; + } if (mi == null) { conn.MaybeSendUnknownMethodError (method_call); @@ -110,6 +135,13 @@ public virtual void HandleMethodCall (MessageContainer method_call) raisedException = e; } + IssueReply (method_call, outSig, retWriter, mi, raisedException); + } + + private void IssueReply (MessageContainer method_call, Signature outSig, MessageWriter retWriter, MethodInfo mi, Exception raisedException) + { + Message msg = method_call.Message; + if (!msg.ReplyExpected) return; @@ -118,6 +150,7 @@ public virtual void HandleMethodCall (MessageContainer method_call) if (raisedException == null) { MessageContainer method_return = new MessageContainer { Type = MessageType.MethodReturn, + Destination = method_call.Sender, ReplySerial = msg.Header.Serial }; replyMsg = method_return.Message; @@ -138,12 +171,81 @@ public virtual void HandleMethodCall (MessageContainer method_call) replyMsg = method_call.CreateError (Mapper.GetInterfaceName (raisedException.GetType ()), raisedException.Message); } - if (method_call.Sender != null) - replyMsg.Header[FieldCode.Destination] = method_call.Sender; - conn.Send (replyMsg); } + private void HandlePropertyCall (MessageContainer method_call) + { + Message msg = method_call.Message; + MessageReader msgReader = new MessageReader (msg); + MessageWriter retWriter = new MessageWriter (); + + object[] args = MessageHelper.GetDynamicValues (msg); + + string face = (string) args [0]; + string name = (string) args [1]; + + PropertyInfo p = GetPropertyInfo (Object.GetType (), face, name); + PropertyCallers pcs = GetPropertyCallers (p); + + MethodCaller pc; + MethodInfo mi; + + switch (method_call.Member) { + case "Set": + pc = pcs.Set; + mi = p.GetSetMethod (); + break; + case "Get": + pc = pcs.Get; + mi = p.GetGetMethod (); + break; + case "GetAll": + throw new NotImplementedException (); + default: + throw new ArgumentException (string.Format ("No such method {0}.{1}", method_call.Interface, method_call.Member)); + } + + if (null == pc || null == mi) { + throw new MissingMethodException (); + } + + Exception raised = null; + try { + pc (Object, msgReader, msg, retWriter); + } catch (Exception e) { + raised = e; + } + + Signature inSig, outSig; + TypeImplementer.SigsForMethod (mi, out inSig, out outSig); + + IssueReply (method_call, outSig, retWriter, mi, raised); + } + + private PropertyInfo GetPropertyInfo (Type type, string @interface, string property) + { + var key = Key (@interface, property); + PropertyInfo pi; + if (!propertyInfoCache.TryGetValue (key, out pi)) { + pi = Mapper.GetPublicProperties(Mapper.GetInterfaceType (type, @interface)).First (x => property == x.Name); + propertyInfoCache [key] = pi; + } + + return pi; + } + + private static PropertyCallers GetPropertyCallers (PropertyInfo pi) + { + PropertyCallers pCaller; + if (!pCallers.TryGetValue (pi, out pCaller)) { + pCaller = TypeImplementer.GenPropertyCallers (pi); + pCallers[pi] = pCaller; + } + + return pCaller; + } + public object Object { get; private set; diff --git a/src/Mapper.cs b/src/Mapper.cs index dcfe170..db95802 100644 --- a/src/Mapper.cs +++ b/src/Mapper.cs @@ -35,6 +35,16 @@ public static string GetArgumentName (ICustomAttributeProvider attrProvider, str return argName; } + public static Type GetInterfaceType(Type type, string iface) + { + return type.GetInterfaces ().FirstOrDefault (x => iface == GetInterfaceName (x)); + } + + public static IEnumerable GetPublicProperties (Type type) + { + return type.GetProperties (BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + } + public static IEnumerable> GetPublicMembers (Type type) { //note that Type.GetInterfaces() returns all interfaces with flattened hierarchy @@ -74,6 +84,7 @@ static IEnumerable GetDeclaredPublicMembers (Type type) public static MethodInfo GetMethod (Type type, MessageContainer method_call) { var mems = Mapper.GetPublicMembers (type).ToArray (); + foreach (var memberForType in mems) { //this could be made more efficient by using the given interface name earlier and avoiding walking through all public interfaces if (method_call.Interface != null) diff --git a/src/Protocol/MessageReader.cs b/src/Protocol/MessageReader.cs index 5779699..c7ba390 100644 --- a/src/Protocol/MessageReader.cs +++ b/src/Protocol/MessageReader.cs @@ -72,6 +72,13 @@ public bool DataAvailable { } } + public IEnumerable ReadValues () + { + for (int i = 0; i < message.Signature.Length; ++i) { + yield return ReadValue (message.Signature[i]); + } + } + public object ReadValue (Type type) { if (type == typeof (void)) diff --git a/src/TypeImplementer.cs b/src/TypeImplementer.cs index 5c222e8..e13433b 100644 --- a/src/TypeImplementer.cs +++ b/src/TypeImplementer.cs @@ -31,6 +31,7 @@ class TypeImplementer static MethodInfo messageReaderReadArray = typeof (MessageReader).GetMethod ("ReadArray", Type.EmptyTypes); static MethodInfo messageReaderReadDictionary = typeof (MessageReader).GetMethod ("ReadDictionary", Type.EmptyTypes); static MethodInfo messageReaderReadStruct = typeof (MessageReader).GetMethod ("ReadStruct", Type.EmptyTypes); + static MethodInfo messageHelperGetDynamicValues = typeof (MessageHelper).GetMethod ("GetDynamicValues", new [] { typeof (Message) }); static Dictionary writeMethods = new Dictionary (); static Dictionary typeWriters = new Dictionary (); @@ -513,6 +514,92 @@ internal static MethodInfo GetReadMethod (Type t) return null; } + internal static PropertyCallers GenPropertyCallers (PropertyInfo target) + { + var pc = new PropertyCallers { + Type = target.PropertyType, + Get = GenGetMethod (target), + Set = GenSetMethod (target) + }; + + return pc; + } + + internal static MethodCaller GenGetMethod (PropertyInfo target) + { + var mi = target.GetGetMethod (); + + if (null == mi) { + return null; + } + + Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter) }; + var method = new DynamicMethod ("PropertyGet", typeof(void), parms, typeof(MessageReader)); + + var ilg = method.GetILGenerator (); + + ilg.Emit (OpCodes.Ldarg_0); + ilg.EmitCall (mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi, null); + + LocalBuilder retLocal = ilg.DeclareLocal (mi.ReturnType); + ilg.Emit (OpCodes.Stloc, retLocal); + + ilg.Emit (OpCodes.Ldarg_3); + ilg.Emit (OpCodes.Ldloc, retLocal); + GenWriter (ilg, mi.ReturnType); + + ilg.Emit (OpCodes.Ret); + + return (MethodCaller) method.CreateDelegate (typeof(MethodCaller)); + } + + internal static MethodCaller GenSetMethod (PropertyInfo target) + { + var mi = target.GetSetMethod (); + + if (null == mi) { + return null; + } + + Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter) }; + var method = new DynamicMethod ("PropertySet", typeof(void), parms, typeof(MessageReader)); + + var ilg = method.GetILGenerator (); + + if (null == messageHelperGetDynamicValues) { + throw new MissingMethodException (typeof(MessageHelper).Name, "GetDynamicValues"); + } + + var args = ilg.DeclareLocal (typeof(object[])); + var arg = ilg.DeclareLocal (typeof(object)); + var v = ilg.DeclareLocal (target.PropertyType); + + ilg.Emit (OpCodes.Ldarg_2); + ilg.Emit (OpCodes.Call, messageHelperGetDynamicValues); + ilg.Emit (OpCodes.Stloc, args); + + ilg.Emit (OpCodes.Ldloc, args); + ilg.Emit (OpCodes.Ldc_I4_2); + ilg.Emit (OpCodes.Ldelem, typeof(object)); + ilg.Emit (OpCodes.Stloc, arg); + + var cast = target.PropertyType.IsValueType + ? OpCodes.Unbox_Any + : OpCodes.Castclass; + + ilg.Emit (OpCodes.Ldloc, arg); + ilg.Emit (cast, target.PropertyType); + ilg.Emit (OpCodes.Stloc, v); + + ilg.Emit (OpCodes.Ldarg_0); + ilg.Emit (OpCodes.Ldloc, v); + ilg.Emit (mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); + + ilg.Emit (OpCodes.Ret); + + return (MethodCaller) method.CreateDelegate (typeof(MethodCaller)); + } + internal static MethodCaller GenCaller (MethodInfo target) { DynamicMethod hookupMethod = GenReadMethod (target);