Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MissingMethodException when trying to call a proxy target from different project #676

Open
chrisbewz opened this issue Jan 29, 2024 · 0 comments

Comments

@chrisbewz
Copy link

I'm working on a library for COM interop recently to communicate with a software used internally on my job, but I'm experiencing some strange behavior on dynamic proxy creation for the classes i use to exchange and call methods from interop. Below are some code referring to how the classes actually are defined and how the dynamic proxies are integrated into the code itself.

[InterceptableFrom(typeof(ComMethodLoggerInterceptor))]
public class ApplicationProxy :
    IProxyBase<TComInterface>
{
    #region Properties
        
    private bool _disposed;
        
    private static Lazy<TComInterface> _instance;
        
    public int Id { get; private set; }
        
    public TComInterface Instance
    {
        get 
        {
            return _instance.Value; 
        }
        private set 
        {
            _instance = new(() => this.GetDynamicProxyForInterface( interfaceRef: value));
        }
    }
        
    #endregion
        
    #region Constructors
        
    public ApplicationProxy (TComInterface instance)
    {
        Instance = instance;
    }

    public ApplicationProxy ()
    {
        _instance = this.ResolveInstance<TComInterface>();
    }
        
    #endregion
        
    #region IProxyBase_{TComInterface}

    public int SetId(int id)
    {
        Id = id;
        return (int)Instance!.InvokeDispatchMethod(nameof(SetId), id);
    }

    public int GetId()
    {
        return (int)Instance!.InvokeDispatchMethod(nameof(GetId));
    }
        
    #endregion
        
    #region IDisposable

    public void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
        }

        ReleaseReferences();
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
        
    #endregion

    private void ReleaseReferences()
    {
        if (!Marshal.IsComObject(Instance))
            throw new InvalidOperationException($"{nameof(Instance)} {InteropExceptionResources.EX_IS_NOT_COM_TYPE}");
        if (Instance != null)
            Marshal.FinalReleaseComObject(Instance);
        Instance = default;
    }
}

The main idea is when this proxy class is instantiated, a lazy reference for the COM interface is wrapped by a dynamic proxy so all method calls made to the "instance" property are expected to be intercepted by the class defined on the InterceptableFromAttribute.
To give some context on how COM reference is being wrapped as a dynamic proxy, below is the code for the "GetDynamicProxyForInterface" and "ResolveInstance" methods called on implementation.

    public static Lazy<TInterface> ResolveInstance<TInterface>(this IProxyBase<TInterface> proxy)
        where TInterface : class
    {
        Func<TInterface> lazyDelegate;
        
        IInterceptor[]? interceptors;

        interceptors = GetInterceptorsFromProxyMetadata(proxy);

        // Creating the lazy delegate instance initializer for proxy instance property
        lazyDelegate = () => GetDynamicProxyForInterfaceInternal(proxy, interceptors);

        return new Lazy<TInterface>(lazyDelegate);
    }

    private static TInterface GetDynamicProxyForInterfaceInternal<TInterface>(this IProxyBase<TInterface> proxy
        ,IInterceptor[]? interceptors,
        TInterface? interfaceRef = default)
    where TInterface : class
    {
        var generator = Generator;
        TInterface interfaceProxy = default;
        
        // In case the interface is not an empty/null reference
        if (interfaceRef is not null)
        {
            interfaceProxy = (TInterface)generator.CreateInterfaceProxyWithTargetInterface(
                interfaceRef,
                interceptors);
        }
        else if (interfaceRef is null)
        {
            TInterface tInterfaceReference = proxy.CreateNew<TInterface>();
            interfaceProxy = (TInterface)generator.CreateInterfaceProxyWithTargetInterface(
                tInterfaceReference,
                interceptors);
        }
            
        return interfaceProxy;
    }

The problem is that if i copy/paste this code in a new class file in one project (let's consider a console app for example) and call any method through instance property the code runs fine and as expected is intercepted by the target class. But when I put this class definition in a separated class library project and then reference it on the console app example, the dynamic proxy is also created when the class is instantiated, but for all method calls the following exception is shown:

System.MissingMethodException : Error: Missing method 'instance <return_type> [com_assembly] t_com_interface::SomeMethod()' from class 'Castle.Proxies.ApplicationProxy'.
   at t_com_interface::SomeMethod()
   // Rest of stack trace

Obs.: It appears even when a method that is virtual (so it's possible to intercept) is called.

I tried to create a COM object directly through Activator.CreateInstance and also removing the logic of dynamic proxy creation from the constructor and it's working fine. It only appears to struggle when the dynamic proxy is called from another project/assembly that it's not where it was created.

It's the first time I'm trying to use the library so it's kinda complicated to figure out why this is happening, does anyone have an idea what could be the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant