Rake, .NET, COM and dynamic

    Lived - was an ancient dinosaur era code


    Given: hell is a coder working with 16 different versions of the same "oh what" product. COM, Interop, interfaces, implementations, signaltones with factors, patterns with antipatterns, modules and other flaws of the open source . Standard set. Grew up, husbands and matured that kodarnik seven years. So far, once another fix did not lead to the correction of mass copy-paste in 16 modules. If anyone is interested - foreach on for changed.

    After suffering, we conducted a study. Copy-paste is 95% identical, only the names of packages from interops differ.

    Is it possible to somehow write so as not to turn hundreds and hundreds of functions into your wrappers, plus the boxing / anboxing pens of these wrappers?

    There is a keyword dynamic!


    And then hellish pasta of such a wonderful view
    standard horror
        public abstract class Application : IDisposable
        {
            public abstract void Close();
            public abstract Document CreateDocument();
            public abstract Document OpenDocument(string doc_path);
    // еще 200 методов
    // куча пропертей типа версий, путей и так далее
            void IDisposable.Dispose() {
                Close();
            }
        }
        public class ClientApplication : Application
        {
            protected ClientApplication(){
                string recovery_path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                recovery_path = Path.Combine(
                    recovery_path,
                    String.Format(
                        @"...\Version {0}\en_GB\Caches\Recovery", Version));
                try {
                    foreach (string file in Directory.GetFiles(recovery_path)){
                        try { File.Delete(file); }
                        catch { }
                    }
                }
                catch {}
    // еще подпорок из палок и веревок
            }
            public override void Close() {
                if (Host != null) {
                    Marshal.ReleaseComObject(Host);
                    Host = null;
                }
            }
        }
        public class ClientApplication7_5 : ClientApplication
        {
            protected ClientApplication7_5() {
                Type type = Type.GetTypeFromProgID("....Application." + Version, true);
                _app = Activator.CreateInstance(type) as Interop75.Application;
                Host = app;
    // ...
            }
            public override Document CreateDocument() {
                return new ClientDocument7_5(this, _app.Documents.Add());
            }
            public override Document OpenDocument(string doc_path) {
                return new ClientDocument7_5(this, _app.Open(doc_path, true, ...) as Interop75.Document);
            }
    // и еще 200 врапперов
            public override ComObject Host { get { return _app; } set { _app = value as Interop75.Application; }  }
            private Interop75.Application _app;
    // и еще пропертей с версиями прог-айди и прочим
        }
        public class ServerApplication : Application
        {
            public ServerApplication() {}
    ...
        }
    // та же трава что и для клиент аппликейшен, еще 8 раз
    

    becomes unnecessary, and the code that used this disgrace

    var app = Factory.GetApplication();
    var doc = app.Documents.Add();
    doc.DocumentPreferences.PreserveLayoutWhenShuffling = false;
    doc.DocumentPreferences.AllowPageShuffle = true;
    doc.DocumentPreferences.StartPageNumber = 1;
    


    does not change.

    Profit? Hooray, it works! Two dozen megabytes of half-generated horror films are successfully thrown into the trash. Support for new versions is greatly simplified.

    Lithuanian holiday "Oblomaitis"


    Run the tests. Bam!

    Not until all calls of that coma return OK - then it works super too. But it was worth the wait for the test

    try {
        var app = Factory.GetApplication();
        var doc = app.Documents.Add();
        doc.DocumentPreferences.PreserveLayoutWhenShuffling = false;
        doc.DocumentPreferences.AllowPageShuffle = true;
        doc.DocumentPreferences.StartPageNumber = -1;
    }
    catch (COMException ok) {
        .... // должны быть тут и красиво в лог записать "нишмагла"
    }
    catch(Exception bad) {
        ... // мы вот тут, а bad - это NullReferenceException БЕЗ StackTrace!!!
    }
    


    Shock, scandals, intrigues, investigations. If anyone is interested - a confirmed bug in Microsoft will be fixed no earlier than 5.0. Sad and bored.

    An inquiring mind does not give rest - after all, if you walk through interopes, is everything there as it should? The debugger shows the type of our document as System .__ ComObject. But what about RCW? Just didn't figure it out?

    Change the test to

    try {
        var app = Factory.GetApplication();
        var doc = app.Documents.Add() as Interop75.Document;
        doc.DocumentPreferences.PreserveLayoutWhenShuffling = false;
        doc.DocumentPreferences.AllowPageShuffle = true;
        doc.DocumentPreferences.StartPageNumber = -1;
    }
    catch (COMException ok) {
        .... // и мы опять на своем месте
    }
    catch(Exception bad) {
        ... 
    }
    

    and ... the test is passed.

    The hypothesis is interesting. So maybe it just can't figure out the type? Check

        var app = Factory.GetApplication();
        var doc = app.Documents.Add();
        var typeName = Microsoft.VisualBasic.Information.TypeName(doc);
    

    Hmm hm. Quite to myself.

    Ideas are over.

    But wait - is there raw? We look, we smoke, we admire the skill of obfuscation. Started from here: __ComObject . Flowed smoothly here: Type.cs . Finished ildasm. In the process of smoking, an understanding came - so there are clearly several places processing these comas in different ways. And what will happen if you replace

    doc.DocumentPreferences.StartPageNumber = -1;
    

    on the

    Type type = doc.DocumentPreferences.GetType();    
    type.InvokeMember("StartPageNumber", BindingFlags.SetProperty, null, doc.DocumentPreferences, new object[] { -1 });
    

    In theory - nothing?

    Haberdashery and cardinal are power


    And here it is changing. Test passed again. And what to do? Turning such a beautiful code into pasta does not smile, and there is a lot of it.

    Late, in the evening, I try to thickly troll and defuse the situation - so maybe I’ll slip my realization of the speakers on the reflexes? I still understand the thought - and this is the thought!

    We try.

    ComWrapper extends DynamicObject
    public class ComWrapper : DynamicObject
    {
    	public ComWrapper(object comObject) {
    		_comObject = comObject;
    		_type = _comObject.GetType();
    	}
    	public object WrappedObject { get { return _comObject; } } // вдруг кому будет надо
    // стандартно пропертя гет + сет
    	public override bool TryGetMember(GetMemberBinder binder, out object result) {
    		result = Wrap(_type.InvokeMember(binder.Name, BindingFlags.GetProperty, null, _comObject, null));
    		return true;
    	}
    	public override bool TrySetMember(SetMemberBinder binder, object value) {
    		_type.InvokeMember(
    			binder.Name, BindingFlags.SetProperty, null, _comObject,
    			new object[] { Unwrap(value) }	);
    		return true;
    	}
    // та же трава про вызов метода
    	 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
    		result = Wrap(_type.InvokeMember(
    			binder.Name, BindingFlags.InvokeMethod, null, _comObject,
    			args.Select(arg => Unwrap(arg)).ToArray()
    		));
    		return true;
    	}
    // наш ручной боксинг - анбоксинг
    	private object Wrap(object obj) {
    		return obj != null && obj.GetType().IsCOMObject ? new ComWrapper(obj) : obj;
    	}
    	private object Unwrap(object obj) {
    		ComWrapper wrapper = obj as ComWrapper;
    		return wrapper != null ? wrapper._comObject : obj;
    	}
    // очевидно то что нам передали в конструкторе + тип переданного чтобы сто раз не считать
    	private object _comObject;
    	private Type _type;
    }
    


    Great - it does everything itself, it works as it should, all that is needed is to wrap them with the result of Factory.GetApplication (). Right there and wrap. There is really a nuance - forgot about the collection. So a little later they added this:

    some more backups
    // наш енумератор на коленке
    	private IEnumerable Enumerate() {
    		foreach (var item in (IEnumerable)_comObject)
    			yield return Wrap(item);
    	}
    // автоконвертация к enumerable
    	public override bool TryConvert(ConvertBinder binder, out object result) {
    		if (binder.Type.Equals(typeof(IEnumerable)) && _comObject is IEnumerable) {
    			result = Enumerate();
    			return true;
    		}
    		result = null;
    		return false;
    	}
    // и поддержка работы как с массивом, по индексу. На всякий случай
    	public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)	{
    		if (indexes.Length == 1) {
    			dynamic indexer = _comObject;
    			result = Wrap(indexer[indexes[0]]);
    			return true;
    		}
    		result = null;
    		return false;
    	}
    	public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)	{
    		if (indexes.Length == 1) {
    			dynamic indexer = _comObject;
    			indexer[indexes[0]] = Unwrap(value);
    			return true;
    		}
    		return false;
    	}
    


    Now is the victory.

    Suddenly someone comes in handy.

    Also popular now: