Search code examples
c#vbaunmanageddllexport

DllExport in a broker dll from VBA with reference to other .NET projects


I want to use DllExport, in a dll which has a broker-function: routing some calls from external code (VBA) to other .NET dll's behind.

I got the DllExport working on a single dll: the dll is generated, and I can use it. But... only with code from that single dll. When I call code from another (in .NET referenced) dll, the solution still builds, I still can use the code from the dll with dllexport, but when calling a method that routes to the referenced dll, VBA gives me a 'Could not load file or assembly 'myseconddllname, version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of iets dependencies. The system cannot find the file specified.

I also tried to add DllExport to the second dll (which shouldn't be necessary, no objects/info from the second dll should be exposed to Unmanaged code). This didn't help.

Any ideas how to solve this?

Edit: registered an issue on github: Calling referenced .NET assembly


Solution

  • For others reference: with help from here and github-issue found the solution:

    Having two projects:

    1. ClassLibrary1 to call from VBA
    2. ClassLibrary2 to be used from within ClassLibrary1 (without exposing something to VBA)

    In ClassLibrary1 we've a static class UnmanagedExport:

    static class UnmanagedExport
    {
        static UnmanagedExport()
            => AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    
        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            => Assembly.LoadFrom($@"{new FileInfo(args.RequestingAssembly.Location).DirectoryName}\{args.Name.Split(',')[0]}.dll");
    
        [DllExport]
        [return: MarshalAs(UnmanagedType.IDispatch)]
        static object CreateBroker()
            => new Class1();
    }
    

    CreateBroker() creates an instance of Class1 (which is supposed to be the broker between VBA and ClassLibrary2). ClassLibrary1.Class1:

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Class1
    {
        public void DoNothingOnClass2Typed()
            => new Class2().DoNothing();
    
        public void CreateClass2aFromClass2Typed()
            => new Class2().CreateClass2a();
    }
    

    Class2 (and Class2a) are both in ClassLibrary2 (which is referenced by ClassLibrary1):

    namespace ClassLibrary2
    {
        public class Class2
        {
            public void DoNothing()
            {
                return;
            }
    
            public void CreateClass2a()
            {
                var class2a = new Class2a();
            }
        }
    }
    

    From VBA this is called as:

    Declare Function CreateBroker Lib "C:\source\repos\DllExportTest\ClassLibrary1\bin\Debug\ClassLibrary1.dll" () As Object
    
    
    Public Sub RunTest()
      Dim class1 As Object
    
      Set class1 = CreateBroker()
    
      class1.DoNothingOnClass2Typed
    
      class1.CreateClass2aFromClass2Typed
    
      Set class1 = Nothing
    End Sub
    

    Works like a charm!

    Note: ClassLibrary2 can also be a .NET Standard dll, so the solution is compatible with .NET Core also.