none
Method caching of dynamic objects RRS feed

  • Question

  • I instantiate a dynamic object by using the Activator.CreateInstance() method, e.g:
    dynamic dynObject = Activator.CreateInstance(Type.GetTypeFromProgID("SomeProgID"));

    The object created is a COM object with lots of methods (hundreds) defined and it takes quite some time to run a method the first time due to the runtime going through the objects capabilities (methods etc.) and caching them, in this case approx. 1500ms.

    Say I need less than 10% of the objects offered interface, is it possible to restrict the runtime to cache only a specified set of methods, e.g. by a method pattern or similar ? (possibly increase the cache later if needed).

    I have tried using reflection instead and it is initially faster, later slower and of course much more complex and error prone.

    Sunday, August 21, 2016 9:40 AM

Answers

  • If you create N different RCWs, putting them in separate namespaces, you could then discover or configure which one to create at runtime.  You can use interop to IDispatch to examine the methods at runtime, or you could use dynamic and try/catch to test for telltale method signatures.  Once you determine the installed version then load the appropriate RCW. 

    You may want to add wrapper classes for each RCW implement a shared (non-COM) interface to simplify access to the methods that are shared across versions.

    David


    David http://blogs.msdn.com/b/dbrowne/

    • Proposed as answer by Hart Wang Friday, August 26, 2016 2:09 AM
    • Marked as answer by EuroEager Friday, August 26, 2016 12:43 PM
    Thursday, August 25, 2016 2:46 PM

All replies

  • dynamic access to COM objects is geared towards convenience, not performance, so I doubt it.

    For performance you can describe one of the COM interfaces to .NET at design-time and create a Runtime-Callable-Wrapper for it, using traditional COM-Interop.

    Exposing COM Components to the .NET Framework

    David


    David http://blogs.msdn.com/b/dbrowne/

    Sunday, August 21, 2016 1:40 PM
  • Thanks, but my problem is that the COM component is out of my control, has only one interface defined and (!) this interface has changed over time.
    The GUIds (Typelibrary, DispInterface and Interface) are unchanged, however the signatures of some of the members are changed a few times over the years.

    My software (the client of the COM server) must of course be able to use all these versions.

    I.e. I think I have to find out (somehow) the version of the component and choose method signatures accordingly (by using dynamic or reflection and C# control statements).
    Please tell me if you know a better way.

    Sunday, August 21, 2016 6:29 PM
  • >I think I have to find out (somehow) the version of the component and choose

    If you have multiple versions of the Interface defined in .NET you should be able to cast the object to the correct one at runtime.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Sunday, August 21, 2016 8:56 PM
  • How do I do that if guids are identical ?

    I guess that mean they have identical registrations (registry)

    Monday, August 22, 2016 12:52 PM
  • Hi EuroEager,

    Thank you for posting here.

    >>this interface has changed over time.

    I would suggest to write fixed interface. This makes it easy to find an interface. Ii will save time.

    Best Regards,

    Hart


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place. Click HERE to participate the survey.

    Thursday, August 25, 2016 7:16 AM
  • Hi

    What do you mean ?
    Please note as explained earlier that the COM server is out of my control.

    Thursday, August 25, 2016 7:53 AM
  • Hi EuroEager,

    Thank you for feedback.

    >>this interface has changed over time.
    The GUIds (Typelibrary, DispInterface and Interface) are unchanged, however the signatures of some of the members are changed a few times over the years.

    As far as I know, When we want to use COM interface in .NET framework. we need to use Dllimport to import interface of function.

    If the interface is changed, I think the compiler will not find it.

    Best Regards,

    Hart


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place. Click HERE to participate the survey.

    Thursday, August 25, 2016 8:45 AM
  • Normally we use tlbimp.exe to make a RCW (Runtime Callable Wrapper) for COM objects, not the DllImportAttribute class which is made for PInvoke.

    In my case, the supplier tlbimp makes different rcw's depending on the version of the COM server (which is a fullblown application, .exe).

    The differences mirrors the interface changes as described in earlier posts.

    If I compile my client application with an RCW from a new version of the COM server it will not be backwards compatible, this is what I want to achieve by either dynamic or reflection and using some version info to choose the signature of the method call executed.

    Thursday, August 25, 2016 10:21 AM
  • If you create N different RCWs, putting them in separate namespaces, you could then discover or configure which one to create at runtime.  You can use interop to IDispatch to examine the methods at runtime, or you could use dynamic and try/catch to test for telltale method signatures.  Once you determine the installed version then load the appropriate RCW. 

    You may want to add wrapper classes for each RCW implement a shared (non-COM) interface to simplify access to the methods that are shared across versions.

    David


    David http://blogs.msdn.com/b/dbrowne/

    • Proposed as answer by Hart Wang Friday, August 26, 2016 2:09 AM
    • Marked as answer by EuroEager Friday, August 26, 2016 12:43 PM
    Thursday, August 25, 2016 2:46 PM
  • Thanks

    I am not 100% sure if I really understand your answer.

    So far I have tried two different approaches:

    1 Wrapping by Reflection.
    Works ok, more work and more error prone if more methods needs to be wrapped.

    2 Wrapping by dynamic type.Works ok, except the slow first-call.

    Note that in any case I will know which version my software is currently talking to (somehow, either by looking at the COM servers exe files attributes for product version, by configuration or by other means).
    I.e. I am able to do some choice in source code within the wrapper.

    Anyway, your post is interesting and marked as answer.

    Friday, August 26, 2016 12:43 PM
  • If you know the version, then this is easy.  Here's an example using ADO.

    Generate separate interop assemblies for each version in separate namespaces so the types don't collide:

    PS C:\libs> tlbimp /out:.\msado20.dll /namespace:msado20  "C:\Program Files\Common Files\system\ado\msado20.tlb"
    
    PS C:\libs> tlbimp /out:.\msado60.dll /namespace:msado60  "C:\Program Files\Common Files\system\ado\msado60.tlb"
    

    Then in C# add both interop assemblies as references, and use them something like this:

    (Note that using separate managed wrappers on top of the RCW enables you to use the IDisposable interface to release the COM object, working around the original design flaw in RCWs that rely on a finalizer to manage the COM component lifetime).

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication18
    {
    
        public interface IRecordset: IDisposable
        {
            void AddNew(object FieldList, object Values);
            void Cancel();
            void CancelBatch();
            void CancelUpdate();
            void Close();
            int CompareBookmarks(object Bookmark1, object Bookmark2);
            void Delete();
    
        }
    
        class Ado20RecordsetWrapper : IRecordset 
        {
            msado20.Recordset rs;
            public Ado20RecordsetWrapper(msado20.Recordset rs)
            {
                this.rs = rs;
                
            }
    
            public void AddNew(object FieldList, object Values)
            {
                rs.AddNew(FieldList, Values);
            }
    
            public void Cancel()
            {
                rs.Cancel();
            }
    
            public void CancelBatch()
            {
                rs.CancelBatch();
            }
    
            public void CancelUpdate()
            {
                rs.CancelUpdate();
            }
    
            public void Close()
            {
                rs.Close();
            }
    
            public int CompareBookmarks(object Bookmark1, object Bookmark2)
            {
                return (int)rs.CompareBookmarks(Bookmark1, Bookmark2);
            }
    
            public void Delete()
            {
                rs.Delete();
            }
    
            public void Dispose()
            {
                Marshal.ReleaseComObject(rs);
            }
        }
    
    
        class Ado60RecordsetWrapper : IRecordset
        {
            msado60.Recordset rs;
            public Ado60RecordsetWrapper(msado60.Recordset rs)
            {
                this.rs = rs;
    
            }
    
            public void AddNew(object FieldList, object Values)
            {
                rs.AddNew(FieldList, Values);
            }
    
            public void Cancel()
            {
                rs.Cancel();
            }
    
            public void CancelBatch()
            {
                rs.CancelBatch();
            }
    
            public void CancelUpdate()
            {
                rs.CancelUpdate();
            }
    
            public void Close()
            {
                rs.Close();
            }
    
            public int CompareBookmarks(object Bookmark1, object Bookmark2)
            {
                return (int)rs.CompareBookmarks(Bookmark1, Bookmark2);
            }
    
            public void Delete()
            {
                rs.Delete();
            }
    
            public void Dispose()
            {
                Marshal.ReleaseComObject(rs);
            }
        }
    
        internal class RecordsetFactory
        {
            static int versionToUse = 2;
            public static IRecordset GetRecordset()
            {
                if (versionToUse == 2 )//version 2.0 is installed
                {
                    return new Ado20RecordsetWrapper(new msado20.Recordset());
                }
                else //version 6.0 is installed
                {
                    return new Ado60RecordsetWrapper(new msado60.Recordset());
                }
            }
        }
        class Program
        {
    
            static void Main(string[] args)
            {
                using (var rs = RecordsetFactory.GetRecordset())
                {
                    Console.WriteLine(rs.GetType().Name);
                }
    
                Console.ReadKey();
            }
        }
    }
    

    David


    David http://blogs.msdn.com/b/dbrowne/

    Friday, August 26, 2016 1:31 PM
  • Thanks a lot, I think I understand it now, factory is a good idea here.
    I will try in a few days (have to collect the rcw's as dll's from the COM Server supplier))
    Friday, August 26, 2016 1:55 PM