Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

proxy: Enable DBus property extraction via CLR properties #37

Closed
wants to merge 10 commits into from
13 changes: 12 additions & 1 deletion dbus-sharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ Global
{736160C3-844E-43D9-8106-E492D74D92CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = src\dbus-sharp.csproj
Policies = $0
$0.TextStylePolicy = $1
$1.FileWidth = 120
$1.TabsToSpaces = False
$1.inheritsSet = VisualStudio
$1.inheritsScope = text/plain
$1.scope = text/x-csharp
$0.CSharpFormattingPolicy = $2
$2.AfterDelegateDeclarationParameterComma = True
$2.inheritsSet = Mono
$2.inheritsScope = text/x-csharp
$2.scope = text/x-csharp
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
24 changes: 24 additions & 0 deletions src/BusObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See COPYING for details

using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
Expand Down Expand Up @@ -162,6 +163,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.ReadValues ().FirstOrDefault ();
}

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)
{
outArgs = new object[0];
Expand Down
221 changes: 193 additions & 28 deletions src/ExportObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,109 @@ namespace DBus
{
using Protocol;

internal class PropertyCall {
public MethodCaller Get { get; set; }
public MethodCaller Set { get; set; }
public PropertyInfo MetaData { get; set; }
}

internal class MethodCall {
public Signature Out { get; set; }
public Signature In { get; set; }
public MethodCaller Call { get; set; }
public MethodInfo MetaData { get; set; }
}

internal class MethodDictionary : Dictionary<string, MethodCall> { }
internal class PropertyDictionary : Dictionary<string, PropertyCall> {
public MethodCaller All { get; set; }
}

internal class InterfaceMethods : Dictionary<string, MethodDictionary> { }
internal class InterfaceProperties : Dictionary<string, PropertyDictionary> { }

internal class DBusMemberTable {

public Type ObjectType { get; private set; }

public DBusMemberTable(Type type)
{
ObjectType = type;
}

InterfaceMethods Methods = new InterfaceMethods();
InterfaceProperties Properties = new InterfaceProperties();

public MethodCall GetMethodCall (string iface, string name)
{
return Lookup<InterfaceMethods, MethodDictionary, string, string, MethodCall> (
Methods,
iface,
name,
(i, n) => {
Type it = Mapper.GetInterfaceType (ObjectType, i);
MethodInfo mi = it.GetMethod (n);
return TypeImplementer.GenMethodCall (mi);
}
);
}

public MethodCaller GetPropertyAllCall (string iface)
{
PropertyDictionary calls;
if (!Properties.TryGetValue(iface, out calls)) {
Properties [iface] = calls = new PropertyDictionary ();
}

if (null == calls.All) {
Type it = Mapper.GetInterfaceType (ObjectType, iface);
calls.All = TypeImplementer.GenGetAllCall (it);
}

return calls.All;
}

public PropertyCall GetPropertyCall (string iface, string name)
{
return Lookup<InterfaceProperties, PropertyDictionary, string, string, PropertyCall> (
Properties,
iface,
name,
(i, n) => {
Type it = Mapper.GetInterfaceType(ObjectType, i);
PropertyInfo pi = it.GetProperty(n);
return TypeImplementer.GenPropertyCall (pi);
}
);
}

private static V Lookup<TMap1,TMap2,A,B,V> (TMap1 map, A k1, B k2, Func<A,B,V> factory)
where TMap2 : IDictionary<B, V>, new()
where TMap1 : IDictionary<A, TMap2>
{
TMap2 first;
if (!map.TryGetValue (k1, out first)) {
map [k1] = first = new TMap2 ();
}

V value;
if (!first.TryGetValue (k2, out value)) {
first [k2] = value = factory (k1, k2);
}

return value;
}

}

//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<string, MethodInfo> methodInfoCache = new Dictionary<string, MethodInfo> ();

static readonly Dictionary<MethodInfo, MethodCaller> mCallers = new Dictionary<MethodInfo, MethodCaller> ();
static readonly Dictionary<Type, DBusMemberTable> typeMembers = new Dictionary<Type, DBusMemberTable>();

public ExportObject (Connection conn, ObjectPath object_path, object obj) : base (conn, null, object_path)
{
Expand Down Expand Up @@ -64,52 +158,60 @@ internal virtual void WriteIntrospect (Introspector intro)
intro.WriteType (Object.GetType ());
}

internal static MethodCaller GetMCaller (MethodInfo mi)
{
MethodCaller mCaller;
if (!mCallers.TryGetValue (mi, out mCaller)) {
mCaller = TypeImplementer.GenCaller (mi);
mCallers[mi] = mCaller;
}
return mCaller;
}

public static ExportObject CreateExportObject (Connection conn, ObjectPath object_path, object obj)
{
Type type = obj.GetType ();
DBusMemberTable table;
if (!typeMembers.TryGetValue (type, out table)) {
typeMembers [type] = new DBusMemberTable (type);
}
return new ExportObject (conn, object_path, obj);
}

public virtual void HandleMethodCall (MessageContainer method_call)
{
MethodInfo mi;
if (!methodInfoCache.TryGetValue (method_call.Member, out mi))
methodInfoCache[method_call.Member] = mi = Mapper.GetMethod (Object.GetType (), method_call);

if (mi == null) {
conn.MaybeSendUnknownMethodError (method_call);
switch (method_call.Interface) {
case "org.freedesktop.DBus.Properties":
HandlePropertyCall (method_call);
return;
}

MethodCaller mCaller;
if (!mCallers.TryGetValue (mi, out mCaller)) {
mCaller = TypeImplementer.GenCaller (mi);
mCallers[mi] = mCaller;
MethodCall mCaller = null;

try {
mCaller = typeMembers[Object.GetType()].GetMethodCall(
method_call.Interface,
method_call.Member
);
}
catch { /* No Such Member */ }

Signature inSig, outSig;
TypeImplementer.SigsForMethod (mi, out inSig, out outSig);
if (mCaller == null) {
conn.MaybeSendUnknownMethodError (method_call);
return;
}

Signature inSig = mCaller.In,
outSig = mCaller.Out;

Message msg = method_call.Message;
MessageReader msgReader = new MessageReader (msg);
MessageWriter retWriter = new MessageWriter ();

Exception raisedException = null;
try {
mCaller (Object, msgReader, msg, retWriter);
mCaller.Call (Object, msgReader, msg, retWriter);
} catch (Exception e) {
raisedException = e;
}

IssueReply (method_call, outSig, retWriter, mCaller.MetaData, raisedException);
}

private void IssueReply (MessageContainer method_call, Signature outSig, MessageWriter retWriter, MethodInfo mi, Exception raisedException)
{
Message msg = method_call.Message;

if (!msg.ReplyExpected)
return;

Expand All @@ -118,6 +220,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;
Expand All @@ -138,12 +241,74 @@ 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];

if ("GetAll" == method_call.Member) {
Signature asv = Signature.MakeDict (Signature.StringSig, Signature.VariantSig);

MethodCaller call = typeMembers [Object.GetType ()].GetPropertyAllCall (face);

Exception ex = null;
try {
call (Object, msgReader, msg, retWriter);
} catch (Exception e) { ex = e; }

IssueReply (method_call, asv, retWriter, null, ex);
return;
}

string name = (string) args [1];

PropertyCall pcs = typeMembers[Object.GetType()].GetPropertyCall (
face,
name
);

MethodInfo mi;
MethodCaller pc;
Signature outSig, inSig = method_call.Signature;

switch (method_call.Member) {
case "Set":
mi = pcs.MetaData.GetSetMethod ();
pc = pcs.Set;
outSig = Signature.Empty;
break;
case "Get":
mi = pcs.MetaData.GetGetMethod ();
pc = pcs.Get;
outSig = Signature.GetSig(mi.ReturnType);
break;
default:
throw new ArgumentException (string.Format ("No such method {0}.{1}", method_call.Interface, method_call.Member));
}

if (null == pc) {
conn.MaybeSendUnknownMethodError (method_call);
return;
}

Exception raised = null;
try {
pc (Object, msgReader, msg, retWriter);
} catch (Exception e) {
raised = e;
}

IssueReply (method_call, outSig, retWriter, mi, raised);
}

public object Object {
get;
private set;
Expand Down
26 changes: 26 additions & 0 deletions src/Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ public static string GetArgumentName (ICustomAttributeProvider attrProvider, str
return argName;
}

public static Type GetInterfaceType(Type type, string iface)
{
return type.GetInterfaces()
.Concat(GetHierarchy(type))
.FirstOrDefault (x => iface == GetInterfaceName (x));
}

private static IEnumerable<Type> GetHierarchy(Type type)
{
if (IsPublic (type)) {
yield return type;
}

foreach (var super in GetHierarchy (type.BaseType)) {
if (IsPublic (type)) {
yield return super;
}
}
}

public static IEnumerable<PropertyInfo> GetPublicProperties (Type type)
{
return type.GetProperties (BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
}

public static IEnumerable<KeyValuePair<Type, MemberInfo>> GetPublicMembers (Type type)
{
//note that Type.GetInterfaces() returns all interfaces with flattened hierarchy
Expand Down Expand Up @@ -74,6 +99,7 @@ static IEnumerable<MemberInfo> 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)
Expand Down
7 changes: 7 additions & 0 deletions src/Protocol/MessageReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ public bool DataAvailable {
}
}

public IEnumerable<object> 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))
Expand Down
Loading