5

Background information:
I'm building a SCADA system, which runs on VBA and I seek some of the powers of C#. I build a DLL library in C# and got basic data to flow between the DLL and VBA.

[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
public class BE_Log
{
    public string DateTime
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string User
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string SCADA
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string Tag
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string Area1
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string Area2
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string Description
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string ValueOld
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }
    public string ValueNew
    {
        [return: MarshalAs(UnmanagedType.BStr)]
        get;
        [param: MarshalAs(UnmanagedType.BStr)]
        set;
    }

    public BE_Log(string DataTime, string User, string SCADA, string Tag, string Area1, string Area2,string Description)
    {
        this.DateTime = DateTime;
        this.User = User;
        this.SCADA = SCADA;
        this.Tag = Tag;
        this.Area1 = Area1;
        this.Area2 = Area2;
        this.Description = Description;
    }

    public BE_Log(string DataTime, string User, string SCADA, string Tag, string Area1, string Area2, string Description, string ValueOld, string ValueNew)
    {
        this.DateTime = DateTime;
        this.User = User;
        this.SCADA = SCADA;
        this.Tag = Tag;
        this.Area1 = Area1;
        this.Area2 = Area2;
        this.Description = Description;
        this.ValueOld = ValueOld;
        this.ValueNew = ValueNew;
    }

}

And I returned the class like this:

[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
public class TI
{
    private BLL_LogBook bll;

    public TI()
    {
        bll = new BLL_LogBook();
    }

    [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_USERDEFINED)]  //  SafeArrayUserDefinedSubType = typeof(BE_Log)
    public BE_Log[] CreateLogBook()
    {
        List<BE_Log> logs = bll.GetLogEntry();
        return logs.ToArray();
    }
}

My data layer:

public class BLL_LogBook
{
    public List<BE_Log> GetLogEntry()
    {
        List<BE_Log> logs = new List<BE_Log>();
        logs.Add(new BE_Log("05-05-2015", "some user", "scada01", "LA010NDA10CU12XQ12", "Ribe", "Esbjerg", "Some short description"));
        logs.Add(new BE_Log("06-05-2015", "test user", "scada01", "LA010NDA10CU12XB05", "Herning", "KBH", "Some long description"));
        logs.Add(new BE_Log("07-05-2015", "normal user", "scada02", "LA010NDA10CU12YQ01", "Åhus", "Tønder", "Some test description"));

        return logs;
    }
}

The static method VBA is calling:

static class UnmanagedExports
{
    [DllExport]
    [return: MarshalAs(UnmanagedType.IDispatch)]
    static Object TI_Object()
    {
        return new TI();
    }
}

In VBA i got the data this way:

Declare Function TI_Object Lib "<path>\\TJI.dll" () As Object

Sub TestTheTestClass()
    Dim TJI As Object
    Set TJI = TI_Object()

    Dim test As Variant
    test = TJI.CreateLogBook()

    Dim log As Variant
    Set log = test(0)

    Debug.Print log.User
End Sub

Now to my question:
How do I return an Array or List of the class 'BE_Log'
EDIT: This is where I'm stuck: http://puu.sh/hnPGe/472ff863d0.png

I have been trying to work out some of Microsofts documentations, without much luck.

The orginal guide i followed was this one:
http://www.analystcave.com/excel-use-c-sharp-in-excel-vba/

He states the following, however I dont completely understand it.

If you are using an array as an argument be sure to use the C# “ref” get by reference option e.g. ref int[] ar

I think it's something to do with 'MarshalAs' or the way I read the data in VBA

1 Answers1

1

If you are using an array as an argument be sure to use the C# “ref” get by reference option e.g. ref int[] ar

This is true, but you don't have any methods that take an array argument, so it doesn't apply to your situation.

Have you tried changing your method signature:

public Log CreateLogBook()

to a signature that returns an array:

public Log[] CreateLogBook()
Joe
  • 122,218
  • 32
  • 205
  • 338
  • I did try with both an array and a list `public Log[] CreateLogBook()` `public List CreateLogBook()` – Rasmus Plats Apr 17 '15 at 11:45
  • "I did try..." - and what was the result? – Joe Apr 17 '15 at 13:22
  • I cant get VBA to read the data, or maybe I didnt do the "Marshal" right? Can you provide an exampel of how to read an array in VBA, from a DLL? – Rasmus Plats Apr 18 '15 at 20:21
  • @RasmusPlats - what happens when you try to call the method? Do you get an exception (VBA runtime error), and if so, what error message? Do you get something unexpected returned, in which case inspect it with the VBA debugger – Joe Apr 18 '15 at 21:05
  • I get the Run-time error '424' (Object required) Dim test As Object Set test = DLL.CreateLogBook() – Rasmus Plats Apr 20 '15 at 06:00
  • However if Dim test as Variant, I can go further. But then I get a "Type mismatch" error: Dim log As Object Set log = test(0) – Rasmus Plats Apr 20 '15 at 06:03
  • Now you're making progress. Dim test as Variant, call your CreateLogBook method, and inspect the returned value "test" in the VBA watch window. – Joe Apr 20 '15 at 07:49
  • Great. Now it says: – Rasmus Plats Apr 20 '15 at 08:12
  • Ok. Now I'm getting really close. Did some more testing and fiddling around. Now I can see the array in VBA, however i have to declare it as a Variant and not a Object. So i cant get my data. `Dim log As Variant Set log = test(0) Debug.Print log.DateTime` – Rasmus Plats Apr 20 '15 at 08:44
  • Looks like your c# code is returning an array of BE_Log. The code you posted doesn't show the definition for BE_Log. – Joe Apr 20 '15 at 10:09
  • Sorry. I had renamed the definition. Its the same class as Log. But how do I get the data member from that array? When I only returned a single Log, I set it as an object and could access the data. That dont seam possible with a Variant ? – Rasmus Plats Apr 20 '15 at 10:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/75705/discussion-between-rasmus-plats-and-joe). – Rasmus Plats Apr 20 '15 at 11:17
  • 1
    Your screenshot shows "" for `test(0)` and `test(1)` which are of type BE_Log. I'd expect to see this if BE_Log was a collection type, and the collections were empty. Also you are accessing `log.DateTime` which isn't a valid property of the Log class you posted. It would help if you posted a complete, consistant sample - see http://stackoverflow.com/help/mcve – Joe Apr 20 '15 at 20:11
  • I can see what I did wrong. I'll strip away all code to a minimum and edit my question whit the complete code. – Rasmus Plats Apr 23 '15 at 08:12