📜 ⬆️ ⬇️

Rake, .NET, COM and dynamic

Once upon a time - there was an ancient dinosaur era code


It is given: hells Kodjarnik working with 16 different versions of the same "oh what" product. COM, Interop, interfaces, implementations, factor signatures, patterns with antipatterns, modules, and other fragments of a cryptic endpoint . Standard set. Grew up, mate and mater that coder of about seven years. So far, once another fix has not led to the correction of mass copy-paste in 16 modules. If anyone is interested - foreach for for changed.

Having suffered, conducted a study. Copy-paste is 95% identical, only package names from interop differ.

Is it possible to somehow write so as not to wrap hundreds and hundreds of functions in your own wrappers, plus the boxing / unboxing handles of these wrappers?
')
There is also a dynamic keyword!


And then hellish pasta that's 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  

It 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? Hurray, it works! Two dozen megabytes of half-generational horror film are successfully thrown into the trash. Support for new versions is radically simplified.

Lithuanian holiday "oblomaitis"


Run the tests. Bam!

Not until all the calls from that coma return OK - and that works great too. But it was worth waiting 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, scandal, intrigue, investigation. If anyone is interested - a confirmed bug in microsoft will be fixed no earlier than 5.0. Sad and boring.

Inquisitive mind does not give rest - because if you walk through the interopes then everything is as it should? The debugger shows our document type as System .__ ComObject. What about RCW? Just not figured out?

Change the test for

 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 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); 

Hm hmm Quite to myself.

Ideas are over.

But wait - is it raw? We look, we smoke, we admire the skill of entanglement. Started from here: __ComObject . Smoothly flowed here: Type.cs. Finished ildasm. In the process of smoking, an understanding came - so there are obviously several places that process these comas in different ways. What happens if you replace

 doc.DocumentPreferences.StartPageNumber = -1; 

on

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

In theory, nothing?

A haberdasher and cardinal are power


And that's changing. The test is passed again. And what to do? To turn such a beautiful code into macaroni does not smile, and a lot of it.

Late, evening, trying to thicken and defuse the situation - so how can we slip our implementation of the speakers - on reflectors? Still not finishing the thought I understand - and this is a 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 by itself, it works as it should, all you need to do is wrap the result of Factory.GetApplication (). Right there and wrapped. There is a true nuance - forgot about the collection. So a little later they added this:

some more props
 //     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 - the victory.

Suddenly someone come in handy.

Source: https://habr.com/ru/post/252371/


All Articles