3

I have an Excel VSTO COM addin, which I can successfully load an refer to in Excel VBA. I can exchange basic types (like strings), but can I also use complex classes from my addin in VBA?

Let's say I have this class in C#:

public class TestClass {
    public string GetTextFunc() => "check";
    public string TextProp => "doublecheck";
    public int RollDice() => 4; // chosen by fair dice roll
}

...and my addin provides an object of this class to Excel:

[ComVisible(true)]
public interface IUtilities {
    string GetString();
    TestClass GetTestClass();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Utilities: IUtilities {
    public string GetString() => "Hello Excel";
    public TestClass GetTestClass() => new TestClass();
}

So everything is pretty default VSTO-tutorial-stuff, except for that class.

Now when I use my addin in the Excel VBA block (e.g. in "ThisWorksheet"), by defining a function like such:

Sub TestAddin()
    Dim addIn As COMAddIn
    Dim automationObject As Object
    Set addIn = Application.COMAddIns("MyExcelAddin")
    Set automationObject = addIn.Object
    
    ActiveSheet.Range("A1").Value2 = automationObject.GetString()
    
    Dim tc
    Set tc = automationObject.GetTestClass()
    ActiveSheet.Range("A2").Value2 = tc.GetTextFunc()
End Sub

...then A1 is correctly set, as expected, and I can debug-step all the way to the last line (so getting the object of tc still works!), but on the last line I get an error "Object required". I can see that tc is of type Variant/Object, so I assume VBA just doesn't know what to do with it. Is there any way to

  1. Tell VBA how this class looks like and
  2. actually have it work, so that calling a function on tc in VBA calls the correct code in my .Net library?
Ralf Stauder
  • 145
  • 1
  • 7
  • I would try `Set tc = automationObject.TestClass`... Or `Dim tc As New automationObject.TestClass`. – FaneDuru Jul 15 '20 at 14:15
  • In VBA there are two types of bindings 1) Early Binding : where an object type is determined by the compiler 2) Late Binding : Where the object type is determined by header info at run time. You are having issues with VBA recognizing the late binding type at runtime. See : https://learn.microsoft.com/en-us/visualstudio/vsto/late-bindin – jdweng Jul 15 '20 at 14:32
  • Take a look in the object browser. This will allow you to identfy the name and class you should use when using new. – freeflow Jul 15 '20 at 15:27
  • @FaneDuru Then I get the compilation error of "User defined type not defined". – Ralf Stauder Jul 16 '20 at 11:34
  • @jdweng Your link is broken, but I found https://learn.microsoft.com/en-us/visualstudio/vsto/late-binding-in-office-solutions which I assume is what you meant? But how will that article help me, it talks about these bindings in regular VB (or C#) projects, which is not VBA. To clarify: I have my custom class in my (C#) addin, but I want to access it within an Excel VBA macro. – Ralf Stauder Jul 16 '20 at 11:37
  • @freeflow I can only see what I assume to be all the default Excel things, with the exception of my own function `TestAddin` in "ThisWorkbook". Where would I look for COM/VSTO addin classes/functions? – Ralf Stauder Jul 16 '20 at 11:38
  • You are building a addin in c# and the object types have to be defined properly for the VBA to recognize the types dynamically (late binding). An object/property has a header which contains the type so the type can be validated before using. See following : https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic – jdweng Jul 16 '20 at 11:43

1 Answers1

3

TestClass class must be implemented just like your Utilities class - it must implement a public IDispatch-derived interface.

[ComVisible(true)]
public interface ITestClass {
    string GetTextFunc();
    string TextProp ();
    int RollDice();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class TestClass : ITestClass {
    public string GetTextFunc() => "check";
    public string TextProp => "doublecheck";
    public int RollDice() => 4;
}

Now the VBA call has a Class Interface to the methods such as GetTextFunc:

Dim tc
Set tc = automationObject.GetTestClass()
ActiveSheet.Range("A2").Value2 = tc.GetTextFunc()
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
Dmitry Streblechenko
  • 62,942
  • 4
  • 53
  • 78
  • How would OP go about changing their code? Guessing wrap an Attribute over `TestClass`? I know you're right, I'd like to learn from this how it's done. – Jeremy Thompson Jul 17 '20 at 03:24
  • It is exactly the same as Utilities class - define an interface, have a class that implements it and is marked with the ComVisible attribute, etc. – Dmitry Streblechenko Jul 17 '20 at 04:47
  • Thanks a lot, that did answer the question in regards to the example I had given... Any additional pointers, though, to what I could do (other than complete wrappers) if I didn't just want to pass on my own "TestClass", but an object of an external library, which I cannot just wrap an interface around and slap "ComVisible" to? ^^ – Ralf Stauder Jul 20 '20 at 11:41
  • 1
    Wrapping it as your own interface is pretty much the only thing you can do. – Dmitry Streblechenko Jul 20 '20 at 18:18