From d752fa5d5ec6abbc9b949896b0e0f16498f39a18 Mon Sep 17 00:00:00 2001 From: Nicholas Little Date: Fri, 30 May 2014 17:18:47 +0100 Subject: [PATCH] proxy: Property get/set via CLR properties --- src/BusObject.cs | 23 +++++ src/ExportObject.cs | 119 +++++++++++++++++++++++++ src/Makefile.am | 1 + src/Protocol/MessageReader.cs | 5 ++ src/ReflectionExtensions.cs | 41 +++++++++ src/TypeImplementer.cs | 159 +++++++++++++++++++++++++++++++--- src/dbus-sharp.csproj | 1 + 7 files changed, 339 insertions(+), 10 deletions(-) create mode 100644 src/ReflectionExtensions.cs diff --git a/src/BusObject.cs b/src/BusObject.cs index 865c52a..1b738a8 100644 --- a/src/BusObject.cs +++ b/src/BusObject.cs @@ -173,6 +173,29 @@ public MessageReader SendMethodCall (string iface, string member, string inSigSt return retVal; } + public object SendPropertyGet (string iface, string property) + { + Exception exception; + MessageWriter writer = new MessageWriter (); + writer.Write (iface); + writer.Write (property); + + MessageReader reader = SendMethodCall ("org.freedesktop.DBus.Properties", "Get", "ss", writer, typeof(object), out exception); + + return reader.ReadValue (); + } + + public void SendPropertySet (string iface, string property, object value) + { + Exception exception; + MessageWriter writer = new MessageWriter (); + writer.Write (iface); + writer.Write (property); + writer.Write (typeof(object), value); + + SendMethodCall ("org.freedesktop.DBus.Properties", "Set", "ssv", writer, typeof(void), out exception); + } + public void Invoke (MethodBase methodBase, string methodName, object[] inArgs, out object[] outArgs, out object retVal, out Exception exception) { Invoke (methodBase, methodName, inArgs, null, out outArgs, out retVal, out exception); diff --git a/src/ExportObject.cs b/src/ExportObject.cs index 14c2eb3..37b0c93 100644 --- a/src/ExportObject.cs +++ b/src/ExportObject.cs @@ -74,6 +74,30 @@ internal static MethodCaller GetMCaller (MethodInfo mi) return mCaller; } + internal static MethodCaller GetPropertyCaller(PropertyInfo pi) + { + MethodInfo mi = pi.GetMethod; + MethodCaller mCaller; + if (!mCallers.TryGetValue(mi, out mCaller)) + { + mCaller = TypeImplementer.GenGetCall (pi); + mCallers[mi] = mCaller; + } + return mCaller; + } + + internal static MethodCaller SetPropertyCaller(PropertyInfo pi) + { + MethodInfo mi = pi.SetMethod; + MethodCaller mCaller; + if (!mCallers.TryGetValue(mi, out mCaller)) + { + mCaller = TypeImplementer.GenSetCall (pi); + mCallers[mi] = mCaller; + } + return mCaller; + } + public static ExportObject CreateExportObject (Connection conn, ObjectPath object_path, object obj) { return new ExportObject (conn, object_path, obj); @@ -81,6 +105,12 @@ public static ExportObject CreateExportObject (Connection conn, ObjectPath objec public virtual void HandleMethodCall (MessageContainer method_call) { + if (method_call.Interface == "org.freedesktop.DBus.Properties") + { + HandlePropertyCall (method_call); + return; + } + MethodInfo mi; if (!methodInfoCache.TryGetValue (method_call.Member, out mi)) methodInfoCache[method_call.Member] = mi = Mapper.GetMethod (Object.GetType (), method_call); @@ -148,6 +178,95 @@ public virtual void HandleMethodCall (MessageContainer method_call) } } + 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]; + + if ("GetAll" == method_call.Member) { + conn.MaybeSendUnknownMethodError (method_call); + return; + } + + string name = (string)args[1]; + + PropertyInfo pi = Object.GetType ().GetProperty (name); + + if (null == pi) + { + conn.MaybeSendUnknownMethodError (method_call); + return; + } + + MethodCaller pc = null; + MethodInfo mi = null; + Signature outSig, inSig = method_call.Signature; + + switch (method_call.Member) { + case "Set": + mi = pi.SetMethod; + pc = SetPropertyCaller (pi); + outSig = Signature.Empty; + break; + case "Get": + mi = pi.GetMethod; + pc = GetPropertyCaller (pi); + outSig = Signature.GetSig (mi.ReturnType); + break; + default: + conn.MaybeSendUnknownMethodError (method_call); + return; + } + + Exception raisedException = null; + try { + pc (Object, msgReader, msg, retWriter, null); + } catch (Exception e) { + raisedException = e; + } + + Message replyMsg; + + if (raisedException == null) + { + MessageContainer method_return = new MessageContainer + { + Type = MessageType.MethodReturn, + ReplySerial = msg.Header.Serial + }; + replyMsg = method_return.Message; + replyMsg.AttachBodyTo (retWriter); + replyMsg.Signature = outSig; + } + else { + // BusException allows precisely formatted Error messages. + BusException busException = raisedException as BusException; + if (busException != null) + replyMsg = method_call.CreateError (busException.ErrorName, busException.ErrorMessage); + else if (raisedException is ArgumentException && raisedException.TargetSite.Name == mi.Name) + { + // Name match trick above is a hack since we don't have the resolved MethodInfo. + ArgumentException argException = (ArgumentException)raisedException; + using (System.IO.StringReader sr = new System.IO.StringReader (argException.Message)) + { + replyMsg = method_call.CreateError ("org.freedesktop.DBus.Error.InvalidArgs", sr.ReadLine()); + } + } + else + 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); + } + public object Object { get; private set; diff --git a/src/Makefile.am b/src/Makefile.am index e7c4e99..9e923f1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,6 +15,7 @@ CSFILES = Address.cs \ Introspection.cs \ Mapper.cs \ TypeImplementer.cs \ + ReflectionExtensions.cs \ ArgDirection.cs \ ObjectPath.cs \ OSHelpers.cs \ diff --git a/src/Protocol/MessageReader.cs b/src/Protocol/MessageReader.cs index 25b91c2..977eb8a 100644 --- a/src/Protocol/MessageReader.cs +++ b/src/Protocol/MessageReader.cs @@ -121,6 +121,11 @@ public object ReadValue (Type type) } } + public object ReadValue() + { + return ReadValue (message.Signature); + } + public object ReadValue (DType dtype) { switch (dtype) diff --git a/src/ReflectionExtensions.cs b/src/ReflectionExtensions.cs new file mode 100644 index 0000000..5006c73 --- /dev/null +++ b/src/ReflectionExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; + +static class ReflectionExtensions +{ + public static bool IsProperty(this MethodBase method) + { + var properties = method.DeclaringType.GetProperties(); + foreach (var prop in properties) + { + if (method == prop.GetMethod) return true; + if (method == prop.SetMethod) return true; + } + return false; + } + + public static MethodBuilder DefineMethodOverride(this TypeBuilder typeB, MethodInfo declMethod) + { + ParameterInfo[] parms = declMethod.GetParameters(); + + Type[] parmTypes = new Type[parms.Length]; + for (int i = 0; i writeMethods = new Dictionary (); static Dictionary typeWriters = new Dictionary (); @@ -37,6 +38,8 @@ class TypeImplementer static MethodInfo sendMethodCallMethod = typeof (BusObject).GetMethod ("SendMethodCall", new Type[] { typeof (string), typeof (string), typeof (string), typeof (MessageWriter), typeof (Type), typeof (DisposableList), typeof (Exception).MakeByRefType () }); static MethodInfo sendSignalMethod = typeof (BusObject).GetMethod ("SendSignal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof (string), typeof (string), typeof (string), typeof (MessageWriter), typeof (Type), typeof (DisposableList), typeof (Exception).MakeByRefType () }, null); static MethodInfo toggleSignalMethod = typeof (BusObject).GetMethod ("ToggleSignal"); + static MethodInfo sendPropertyGetMethod = typeof(BusObject).GetMethod("SendPropertyGet"); + static MethodInfo sendPropertySetMethod = typeof(BusObject).GetMethod("SendPropertySet"); static Dictionary hookup_methods = new Dictionary (); static Dictionary readMethods = new Dictionary (); @@ -89,6 +92,11 @@ static void Implement (TypeBuilder typeB, Type iface, string interfaceName) Dictionary builders = new Dictionary (); foreach (MethodInfo declMethod in iface.GetMethods ()) { + if (declMethod.IsProperty()) + { + continue; + } + ParameterInfo[] parms = declMethod.GetParameters (); Type[] parmTypes = new Type[parms.Length]; @@ -121,16 +129,7 @@ static void Implement (TypeBuilder typeB, Type iface, string interfaceName) foreach (PropertyInfo declProp in iface.GetProperties ()) { - List indexers = new List (); - foreach (ParameterInfo pi in declProp.GetIndexParameters ()) - indexers.Add (pi.ParameterType); - - PropertyBuilder prop_builder = typeB.DefineProperty (declProp.Name, declProp.Attributes, declProp.PropertyType, indexers.ToArray ()); - MethodBuilder mb; - if (builders.TryGetValue ("get_" + declProp.Name, out mb)) - prop_builder.SetGetMethod (mb); - if (builders.TryGetValue ("set_" + declProp.Name, out mb)) - prop_builder.SetSetMethod (mb); + GenHookupProperty(typeB, declProp, interfaceName); } } @@ -410,6 +409,59 @@ public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, Meth ilg.Emit (OpCodes.Ret); } + public static void GenHookupProperty(TypeBuilder typeB, PropertyInfo declProp, string @interface) + { + List indexers = new List(); + foreach (ParameterInfo pi in declProp.GetIndexParameters()) + indexers.Add(pi.ParameterType); + + PropertyBuilder prop_builder = typeB.DefineProperty(declProp.Name, + declProp.Attributes, + declProp.PropertyType, + indexers.ToArray()); + + MethodInfo[] sources = { declProp.GetMethod, declProp.SetMethod }; + + foreach (MethodInfo source in sources) + { + if (null == source) + continue; + + MethodBuilder meth_builder = typeB.DefineMethodOverride (source); + + ILGenerator ilg = meth_builder.GetILGenerator(); + + bool isGet = typeof(void) != source.ReturnType; + + MethodInfo target = isGet ? sendPropertyGetMethod : sendPropertySetMethod; + + ilg.Emit(OpCodes.Ldarg_0); + ilg.Emit(OpCodes.Castclass, typeof(BusObject)); + ilg.Emit(OpCodes.Ldstr, @interface); + ilg.Emit(OpCodes.Ldstr, declProp.Name); + + bool box = declProp.PropertyType.IsValueType; + + if (!isGet) + { + ilg.Emit(OpCodes.Ldarg_1); + ilg.Emit(box ? OpCodes.Box : OpCodes.Castclass, declProp.PropertyType); + } + + ilg.Emit(OpCodes.Tailcall); + ilg.Emit(OpCodes.Callvirt, target); + if (isGet) + { + ilg.Emit(box ? OpCodes.Unbox_Any : OpCodes.Castclass, declProp.PropertyType); + } + ilg.Emit(OpCodes.Ret); + + if (isGet) + prop_builder.SetGetMethod(meth_builder); + else + prop_builder.SetSetMethod(meth_builder); + } + } public static bool SigsForMethod (MethodInfo mi, out Signature inSig, out Signature outSig, out bool hasDisposableList) { @@ -466,6 +518,93 @@ internal static MethodCaller GenCaller (MethodInfo target) return caller; } + internal static MethodCaller GenGetCall(PropertyInfo target) + { + var mi = target.GetGetMethod(); + + if (null == mi) { + return null; + } + + var parms = new Type[] { + typeof (object), + typeof (MessageReader), + typeof (Message), + typeof (MessageWriter), + typeof (DisposableList) + }; + var method = new DynamicMethod("PropertyGet", typeof(void), parms, typeof(MessageReader)); + + var ilg = method.GetILGenerator(); + + var retLocal = ilg.DeclareLocal(mi.ReturnType); + + ilg.Emit (OpCodes.Ldarg_0); + ilg.EmitCall (mi.IsFinal? OpCodes.Call : OpCodes.Callvirt, mi, null); + 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 GenSetCall(PropertyInfo target) + { + var mi = target.GetSetMethod(); + + if (null == mi) { + return null; + } + + var parms = new Type[] { + typeof (object), + typeof (MessageReader), + typeof (Message), + typeof (MessageWriter), + typeof (DisposableList) + }; + 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 DynamicMethod GenReadMethod (MethodInfo target) { Type[] parms = new Type[] { typeof (object), typeof (MessageReader), typeof (Message), typeof (MessageWriter), typeof (DisposableList) }; diff --git a/src/dbus-sharp.csproj b/src/dbus-sharp.csproj index 45ee10d..406936a 100644 --- a/src/dbus-sharp.csproj +++ b/src/dbus-sharp.csproj @@ -54,6 +54,7 @@ +