Understanding the Expression parameter in GetMetaObject()

Aug 6, 2013 at 12:54 AM
Edited Aug 6, 2013 at 12:56 AM
Ok, I have the following code inside a struct (for good reason) to support dynamic properties, but the ACTUAL properties and methods exist in an external dynamic object (pointed to by the struct value):
        public DynamicMetaObject GetMetaObject(Expression parameter)
        {
            IDynamicMetaObjectProvider dynObj = ManagedObject ?? Engine.CreateObject<V8NativeObject>(this);
            return new DynamicMetaObject(Expression.Parameter(((ParameterExpression)parameter).Type, ((ParameterExpression)parameter).Name),
                BindingRestrictions.Empty, dynObj);
        }
This doesn't work, and gives the following error: "An IDynamicMetaObjectProvider V8.Net.InternalHandle created an invalid DynamicMetaObject instance."

I'm trying to avoid having to extend 'DynamicMetaObject' to build a bridge to 'dynObj' if at all possible, since 'dynObj' is ALREADY dynamic, and ALREADY has its own 'GetMetaObject()' method. I'm aware that the "parameter" expression represents the current target, but is there a way to pass it along into the 'GetMetaObject()' method of the external object ('dynObj') without creating ANOTHER object as a bridge? I am trying to limit the number of objects as much as possible (i.e. trying to prevent having to pollute the GC with bridge objects).
Aug 9, 2013 at 2:35 PM
I don't really understand what you're trying to do, but I believe the DynamicMetaObject being returned has to reference the same ParameterExpression as the argument to this function.
Aug 10, 2013 at 12:27 AM
Originally I tried returning "{externalObject}.GetMetaObject(parameter)", but it goes into a cyclical loop - I'm guessing because "parameter" still points to the current object. The code above is me trying to create an expression that represents 'externalObject' instead of the current one.
Aug 12, 2013 at 3:23 PM
If "ManagedObject" is already an IDMOP, why can't it just be used directly?

My guess is that this kind of wrapping should be possible, but you'd definitely need to adapt both the value and the expression to make it work. If this is a class "Wrapper", then this code snippet shows what I mean.
public class Wrapper : IDynamicMetaObjectProvider {
    private IDynamicMetaObjectProvider _managedObject;

    public Wrapper(IDynamicMetaObjectProvider managedObject) { _managedObject = managedObject; }

    public IDynamicMetaObjectProvider ManagedObject { get { return _managedObject; } }

    public DynamicMetaObject GetMetaObject(Expression parameter) {
        // Life gets very complicated if the wrapper can point to different things at different times.
        // Assume that the first attempt at dynamic binding fixes the wrapped object.
        ManagedObject = ManagedObject ?? Engine.CreateObject<V8NativeObject>(this);
        return new DynamicMetaObject(
            Expression.Property(parameter, "ManagedObject"),
            BindingRestrictions.GetTypeRestriction(parameter, parameter.Type),
            ManagedObject);
    }
}
You may need to post a larger chunk of the class in question to get a better answer.
Aug 12, 2013 at 3:26 PM
Actually, that doesn't solve the real issue which is that you probably want to get the MetaObject from the wrapped object.
    public DynamicMetaObject GetMetaObject(Expression parameter) {
        ManagedObject = ManagedObject ?? Engine.CreateObject<V8NativeObject>(this);
        return ManagedObject.GetMetaObject(Expression.Property(parameter, "ManagedObject"));
    }
But then (like I said) I don't really understand what you're trying to do.
Aug 12, 2013 at 4:44 PM
Edited Aug 12, 2013 at 5:53 PM
Yes, that's around what I'm trying to do. :) This was regarding my V8.Net project. In answer to your question, the bind methods are a part of the handles, not the objects. The handles implement IDMOP, including the object, but the object just creates a new handle to act as a meta object to pass along the request.
Aug 12, 2013 at 6:28 PM
Edited Aug 12, 2013 at 6:31 PM
Actually, your code posts generate an error that "ManagedObject" is "not defined for type ...". You are on the right track however. My objects are tracked by handles that implement the IDMOP interface, but the objects THEMSELVES also implement it. I don't want to duplicate code, so I'm trying to pass along the request for the meta object to the handle that is wrapped inside the object itself.
Aug 12, 2013 at 6:40 PM
Edited Aug 12, 2013 at 7:04 PM
Ok, let me paint this a little better. I have the following two handle types:
InternalHandle - a struct that implements IDMOP.
Handle - A class that wraps an InternalHandle, derives from DynamicMetaObject, and implements IDMOP.

class V8NativeObject : IDynamicMetaObjectProvider
{
        Handle _Handle;

        public DynamicMetaObject GetMetaObject(Expression parameter)
        {
            return new Handle(_Handle._Handle, parameter);
        }
}
Both handles implement IDMOP. The aforementioned ManagedObject object (of type V8NativeObject) also implements IDMOP, but it should forward the request to the encapsulated handle "_Handle". Currently I was trying to do this by simply returning something like "new Handle(parameter)" (because Handle also serves the role as DynamicMetaObject as well), but I now realize that "parameter" is specific to the current object, and this seems to be working, but I'm getting a cyclical loop within the GetMetaObject() method in the handles. The loop is caused by an operation like "obj.x = anotherObject"
Aug 12, 2013 at 7:21 PM
Looking at the V8.NET project, I'm guessing that this is part of your solution "to allow accessing objects easily using a chain of property names, such as 'a.b.c.d...'". Is that right? And if so, would it be correct for me to restate the issue as "evaluate a.b.c.d for a V8 object a without having to create intermediate V8 objects for a.b and a.b.c?
Aug 12, 2013 at 7:35 PM
Edited Aug 12, 2013 at 7:54 PM
Yes, exactly. ;)

In the old method, there was just an object:
V8NativeObject obj;
var value = obj.AsDynamic.x;
Then "value" would actually be of type "InternalHandle". This is however where the break was. Users would then have to create another "managed" object to wrap the handle and then start again.
V8NativeObject obj;
var value = obj.AsDynamic.x.y.z; // ERRROR: Because X returns a handle, which is NOT dynamic.
What I'm trying to do is to get the last line working by reusing (somewhat re-purposing) the basic "Handle" object to act as the meta object as well.

Just a side note: I moved all the calls into the native side for the V8NativeObject objects into a special handle called "ObjectHandle" that derives from "Handle". The same methods on the objects now call the same methods in the handle instead, to support the dynamic process (keeping everything centered on handles). This almost works, except I'm getting infinite cyclical calls into BindSetMember() for the same set operation.
Aug 12, 2013 at 8:01 PM
Edited Aug 12, 2013 at 8:07 PM
So neither V8NativeObject nor InternalHandle implement IDMOP, but the AsDynamic property returns a dynamic wrapper?

I don't see any reason here to have one IDMOP type wrap another. The real only requirement is that the member evaluation ".x" has to return something which implements IDMOP. If you want to evaluate the V8 part lazily, this could be a different type or you could have the InternalHandle type encapsulate both the original handle and a partial access chain. In other words, instead of obj.AsDynamic.x.y returning a handle to the V8 object representing obj.x.y, it would return a handle to the V8 object representing obj plus the partial access path "x.y". Obviously, you then need a trigger to force evaluation of the access path. One way to do that is to provide a conversion binder so that someone who says

V8Object z = obj.AsDynamic.x.y.z;

would trigger the dynamic conversion to V8Object. This would also allow e.g.

string z = obj.AsDynamic.x.y.z;
Aug 12, 2013 at 8:09 PM
Edited Aug 12, 2013 at 8:19 PM
Everything has IDMOP. Handles also serve as meta objects as well. Consider this:
var handle = {V8Engine}.DynamicGlobalObject;
'DynamicGlobalObject' simply returns "(dynamic)GlobalHandle", and thus the "handle" variable is not a C# object. It's of type "dynamic" (if you call it a type), based on a handle. I don't want (nor need) to create another managed object just to call into the native side (not efficient at all), so I'm allowing this:
handle.x.y.z
HOWEVER, when a user DOES decided to derived from V8NativeObject to implement their own objects, I want this:
MyCustomObject obj = ....;
obj.AsDynamic.x.y.z
In this case, "AsDynamic" also returns a handle for the same reason.
Aug 12, 2013 at 8:20 PM
I'm curious -- why do you say that "handle" isn't a C# object?

In this environment, because of the way that the C# runtime binder works, "handle.x.y.z" must almost always create managed objects for the intermediaries handle.x and handle.y. That would be extremely difficult to avoid -- and avoiding it is likely to be more expensive in most cases than not avoiding it.
Aug 12, 2013 at 8:40 PM
Edited Aug 12, 2013 at 8:49 PM
Yes, I know, and that's what' I am trying to do.

I view it like this: "InternalHandle" is a struct, and contains all the ACTUAL methods for calling the native side. Everything else is based on it. The "Handle" type is an object that contains the same methods and properties as "InternalHandle", but its main purpose is to properly dispose of the wrapped InternalHandle value. "Handle" objects are intended for use by end users, and simply wrap InternalHandle values (which are used internally in the V8.NET system for a speed boost [to prevent GC clutter]). "Handle" simply forwards method/property access to the InternalHandle value. The two types, thus, act alike, but only one is an object, and the other is value (for the stack).

Now, "Handle", being an object, only has one value, "InternalHandle" (not an object), which in turn simply contains a native pointer. I was thinking then, instead of creating another object type that derives from DynamicMetaObject, I would simply reuse the "Handle" object, and make IT derive from DynamicMetaObject instead. This, theoretically would allow "Handle" to serve as BOTH a handle, AND the DynamicMetaObject in one.

Now, if I do "(Dynamic)SomeInternalHandle", a "Handle" meta object gets created. Same goes for "(Dynamic)Handle", and "obj.AsDynamic".

So, I'm not avoiding it, just trying to consolidate everything to the "Handle" object. :) (keeping in mind that the InternalHandle contains the actual method/property code)
Aug 12, 2013 at 9:53 PM
Ah. Well, there are a variety of reasons why this won't work. I think you're probably under the misimpression that the dynamic object is represented at runtime by DynamicMetaObject, and this is decidedly not the case. The dynamic object is represented at runtime by itself and only itself -- except, of course, that it will be boxed if it's a value type. DynamicMetaObjects aren't created unless dynamic binding is invoked, and the cost of dynamic binding is extremely high compared to the cost of allocating a single object. Because of that, any concern about the extra allocation of a DynamicMetaObject is a little misguided.

If I were designing this, I don't think I'd use a value type for InternalHandle. That's because I think I'd want it to implement IDisposable and have a finalizer to ensure that the underlying V8 object gets cleaned up when the managed wrapper is GC'd. But I imagine you've probably come up with a different way to control the lifespan of the V8 objects.

Going with that assumption, the split between value type and object wrapper is valuable only because the value type is used fairly frequently on its own and not just through a dynamic reference. That being the case, I definitely wouldn't implement IDMOP on the value type. I'd only implement it on the wrapper. This means that you can't meaningfully say "(dynamic)SomeInternalHandle" -- but that's okay because it would have caused boxing anyway and it significantly complicates the code. You could have an AsDynamic property on InternalHandle, of course. You might not want that to allocate a new Handle each time, so you could put a Handle field on the InternalHandle to cache the wrapper.
    public struct InternalHandle {
        private IntPtr _v8object;
        private Handle _wrapper;

        public Handle AsDynamic {
            get {
                _wrapper = _wrapper ?? new Handle(this);
                return _wrapper;
            }
        }
    }

    public class Handle : IDynamicMetaObjectProvider {
        private readonly InternalHandle _internalHandle;

        public Handle(InternalHandle internalHandle) {
            _internalHandle = internalHandle;
        }

        public DynamicMetaObject GetMetaObject(Expression parameter) {
            return new HandleMetaObject(parameter, this);
        }

        class HandleMetaObject : DynamicMetaObject {
            internal HandleMetaObject(Expression parameter, Handle value)
                : base(parameter, BindingRestrictions.GetTypeRestriction(parameter, typeof(Handle)), value) {
            }

            // Actual binder implementations go here
        }
    }
The InternalHandle class would still have all of the (non-DLR) stuff related to the V8 engine, and the Handle class might have helpers related to dynamic invocation. The actual call site code generation would happen in the binders on the HandleMetaObject class.
Aug 12, 2013 at 10:03 PM
To expand on that last example a little bit, I've pretended that V8 has a native "get member" function which takes an object and a member name and returns an object, and I've shown how that might be used in this hierarchy. Warning: this has not been tested or even shown to compile.
    public struct InternalHandle {
        private readonly IntPtr _v8object;
        private Handle _wrapper;

        [DllImport("v8")]
        internal static IntPtr V8GetMember(IntPtr obj, string member);

        internal InternalHandle(IntPtr v8object) {
            _v8object = v8object;
            _wrapper = null;
        }

        public Handle AsDynamic {
            get {
                _wrapper = _wrapper ?? new Handle(this);
                return _wrapper;
            }
        }

        public InternalHandle GetMember(string member) {
            return new InternalHandle(V8GetMember(_v8object, member));
        }
    }

    public class Handle : IDynamicMetaObjectProvider {
        private readonly InternalHandle _internalHandle;

        public Handle(InternalHandle internalHandle) {
            _internalHandle = internalHandle;
        }

        public Handle GetMember(string member) {
            return _internalHandle.GetMember(member).AsDynamic;
        }

        public DynamicMetaObject GetMetaObject(Expression parameter) {
            return new HandleMetaObject(parameter, this);
        }

        class HandleMetaObject : DynamicMetaObject {
            internal HandleMetaObject(Expression parameter, Handle value)
                : base(parameter, BindingRestrictions.GetTypeRestriction(parameter, typeof(Handle)), value) {
            }

            public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
                return new DynamicMetaObject(
                    Expression.Call(Expression, "GetMember", new[] { typeof(string) }, Expression.Constant(binder.Name)),
                    Restrictions);
            }
        }
    }
Aug 12, 2013 at 10:27 PM
Actually, IDisposable is already implemented in both, and the value type can be used with "using() {}" without boxing (his has been tested).

I think in trying to consolidate, I'm making things worse. I'll just create another class that can work with both, since they both contain the same methods, and see how that goes. :)

Aug 12, 2013 at 10:35 PM
Yes, Dispose() works without boxing in using blocks -- so as long as the lifespan of these objects is restricted to a using block and it's always guarded that way, there won't be any leaks. But if you say
using (var a = SomeNewJavascriptObject()) {
    System.Console.WriteLine(a.AsDynamic.b.c);
}
and native intermediates are created for a.b and a.b.c, then those won't get properly deallocated. I'm obviously not familiar with the environment that this code runs in, but for production code on my team that would not be acceptable.
Aug 12, 2013 at 11:07 PM
You are right, which is why I created "Handle", because I use InternalHandle values internally for speed, but provided something for people like you as well. ;) In tests I can now create millions of objects much faster now than before, if efficiency is critical, but also support "Handle" to allow the GC to take care of it. You are correct that I would have to return a "Handle" object to bridge across (and dispose itself), which is why I have the "ObjectHandle" class that derives from "Handle" for accessing properties on the native objects. ;)
Aug 13, 2013 at 1:44 AM
Edited Aug 14, 2013 at 7:55 PM
After creating a shared meta object to use between handles and objects, it seems to be working now.

Funny, I should have started this way first (actually, I did, but switched). Doing it this way made me realized I wasn't working with the restrictions correctly. ;) In fact, I think part my original issue was incorrect usage of the restrictions. I think the other issue was I wasn't always storing the same value in the meta object represented by the expression parameter (which was probably causing the cyclical issue). Anyhow, since I have no choice but to create a new meta object each time anyhow, I think I'll keep this method.

Thanks for your time. 8)