I have a C# console application (ExcelTest) and a C# class library (TestDLL) (both of them are .NET Framework v4.7.2).
I would like to send user defined objects (an instance of TestClass) from C# code to an Excel template file (.xltm) via COM interop, so I:
- marked the assembly of TestDLL as COM visible (in VS -> Project Properties -> Application -> Assembly Information...)
- and annotated the suitable classes with the proper attributes, like
ComVisible,GuidandClassInterfaceattribute.
In the Excel template file, I referenced the TestDLL in the "Microsoft Visusal Basic for Applications" (Tools menu -> References).
The C# and VBA code used can be seen below (Sources section).
And finally, the problem, because "everything" works 99% ...
On the other side (I mean the Excel Template file), when I debugging, I can not read a specific property.
In the Locals window (see image below), when I expand the highlighted row (TestItemValues), I get an exception in VS:
The server threw an exception. (Exception from HRESULT: 0x80010105 (RPC_E_SERVERFAULT)),
And of course the debugging of Excel template also crashes. I do not understand what is the problem, because the type of TestItem is recognized, I can also read the other properties below the firstParameter, like:
ClassName->stringClassId->intTestItemExample->TestItemStringValues->string[]
If I change the signature of ProcessData from (firstParameter As TestClass) to (firstParameter() As TestItem) and change the testClass parameter to testClass.TestItemValues in the C# code -> Helper.XlApp.Run("ProcessData", testClass.TestItemValues) then it is OK, so in this case I can read all of the TestItemValues properties (see image below).
And finally, my last question(s):
What causes this strange problem? (I guess the marshaling has something to do with it.)
Is there any idea how can I eliminate this "bug"?
What did I miss from the C# / VBA code?
Thank you very much, if you can help me :)
ā Sources ā
ExcelTest \ Program.cs
using System;
using System.Collections.Generic;
using TestDLL;
namespace ExcelTest
{
class Program
{
public static TestItem[] GenerateTestItems()
{
var result = new List<TestItem>();
TestItem testItem1 = new TestItem() { Name = "First", Id = 1 };
TestItem testItem2 = new TestItem() { Name = "Second", Id = 2 };
TestItem testItem3 = new TestItem() { Name = "Third", Id = 3 };
TestItem testItem4 = new TestItem() { Name = "Fourth", Id = 4 };
result.Add(testItem1);
result.Add(testItem2);
result.Add(testItem3);
result.Add(testItem4);
return result.ToArray();
}
static void Main(string[] args)
{
try
{
string path = @"C:\ExcelTest\test.xltm";
Helper.XlApp.Visible = true;
Microsoft.Office.Interop.Excel.Workbook xlWorkBook = Helper.XlApp.Workbooks.Open(path);
TestClass testClass = new TestClass
{
ClassName = "MyClass",
ClassId = 1,
TestItemValues = GenerateTestItems(),
TestItemExample = new TestItem() { Name = "FirstTestItem", Id = 1},
StringValues = new string[] { "String1", "String2", "String3", "String4" }
};
bool? closeWorkbook = Helper.XlApp.Run("ProcessData", testClass) as bool?;
}
catch (Exception e)
{
}
finally
{
Console.ReadKey();
}
}
}
}
TestDLL \ TestClass.cs
using System.Runtime.InteropServices;
namespace TestDLL
{
[ComVisible(true)]
[Guid("C8F7BB4F-6065-47C6-BE67-4BACF8BABE6A")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class TestClass
{
public TestClass()
{
}
[ComVisible(true)]
public string ClassName { get; set; }
[ComVisible(true)]
public int ClassId { get; set; }
[ComVisible(true)]
public TestItem[] TestItemValues { get; set; }
[ComVisible(true)]
public TestItem TestItemExample { get; set; }
[ComVisible(true)]
public string[] StringValues { get; set; }
}
}
TestDLL \ TestItem.cs
using System.Runtime.InteropServices;
namespace TestDLL
{
[ComVisible(true)]
[Guid("D2EADEED-F9C4-4821-BA2F-6943448B5E4B")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class TestItem
{
public TestItem()
{
}
[ComVisible(true)]
public int Id { get; set; }
[ComVisible(true)]
public string Name { get; set; }
}
}
ExcelTest \ Helper.cs
using System;
using Microsoft.Office.Interop.Excel;
namespace ExcelTest
{
public class Helper
{
private static Application xlApp;
internal static Application XlApp
{
get
{
if (IsExcelInstalled)
{
try
{
string def = xlApp._Default;
}
catch
{
xlApp = new Application();
}
return xlApp;
}
return null;
}
private set { xlApp = value; }
}
private static bool isExcelInstalled;
public static bool IsExcelInstalled
{
get
{
if (!triedToCreateExcelApp)
{
triedToCreateExcelApp = true;
isExcelInstalled = TryToCreateExcelApp();
}
return isExcelInstalled;
}
}
private static bool triedToCreateExcelApp;
private static bool TryToCreateExcelApp()
{
try
{
if (Type.GetTypeFromProgID("Excel.Application") == null)
{
return false;
}
return CanCreateExcelApp();
}
catch
{
return false;
}
}
private static bool CanCreateExcelApp()
{
try
{
// When Excel cannot be started properly due to registry errors, Office tries to correct the error and this call
// does not raise an exception, but later on errors appear
// Typically:
xlApp = new Application();
// So as to get an exception here early when xlApp cannot be used, this line is added, which tries to access a
// general object from inside Excel application. It raises the exception if any.
var cells = xlApp._Default;
return true;
}
catch
{
return false;
}
}
}
}
Excel Template file \ Test module
Public Function ProcessData(firstParameter As TestClass) As Boolean
Dim dummyValue As String
dummyValue = "hello"
ProcessData = True
End Function

