1
votes

I've been several days into this, and I've read a lot of questions that helped me to arrive to where I am right now. But I still need some help.

I will explain. I have a C++ DLL that I want to wrap for use it in c#. I have the documentation of the DLL, but I can't change anything of it. A lot of functions work with basic dllimport setup, but I have some functions that doesn't work properly, this is one of them:

DLL documentation
struct stChannel LookForAvailableChannels (const char *dataBaseFolder, int serialNumber, double firstLogTime, double lastLogTime)

I also have these structs:

struct stChannelInfo
{
 char ChannelTag[17];
 char ChannelEnabled;
}

struct stChannel
{
 int ChannelNumber;
 struct stChannelInfo *ChannelInfo;
}

So trying different things, and after reading a lot, I've come to a solution that "partialy" works:

C# Code
    [StructLayout(LayoutKind.Sequential)]
    public struct stChannelInfo
    {
        public IntPtr ChannelTag;
        public byte ChannelEnabled;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct stChannel {
        public int ChannelNumber;      
        public stChannelInfo ChannelInfo;
    };

[DllImport("NG.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern stChannel LookForAvailableChannels(string dataBaseFolder, int serialNumber, double firstLogTime, double lastLogTime);

stChannel Estructura = new stChannel();

I have a button that calls fires up this code:

Estructura = LookForAvailableChannels("C:\\Folder", 12345678, FechaInicio, FechaFinal);

Then I marshal Estructura.ChannelInfo.ChannelTag:

string btListFile = Marshal.PtrToStringAnsi(Estructura.ChannelInfo.ChannelTag);

This actually works, it returns data I know it's correct. But I'm only receiving the first element of an array, because stChannelInfo struct inside stChannel is a pointer, and I don't know how to handle this in c#.

It should be done in a way that this code I use right now:

Marshal.PtrToStringAnsi(Estructura.ChannelInfo.ChannelTag);

Should be

Marshal.PtrToStringAnsi(Estructura.ChannelInfo[i].ChannelTag);

But everything i've used right now just doesn't work. I would appreciate any help.

Thank you.

EDIT:

Thanks to user Adriano Repetti now I have this:

C# Code [StructLayout(LayoutKind.Sequential)] public struct stChannelInfo { [MarshalAs(UnmanagedType.LPStr, SizeConst = 17)] public string ChannelTag; public byte ChannelEnabled; };

    [StructLayout(LayoutKind.Sequential)]
    public struct stChannel {
        public int ChannelNumber;
        public IntPtr ChannelInfo;
    };

[DllImport("NG.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern stChannel LookForAvailableChannels(string dataBaseFolder, int serialNumber, double firstLogTime, double lastLogTime);

stChannel Estructura = new stChannel();

I have a button that calls fires up this code:

Estructura = LookForAvailableChannels("C:\\Folder", 12345678, FechaInicio, FechaFinal);

var channelinf = (stChannelInfo)Marshal.PtrToStructure(Estructura.ChannelInfo, typeof(stChannelInfo));


        for (int i = 0; i < 4; i++)
        {
            var ptr = IntPtr.Add(Estructura.ChannelInfo, Marshal.SizeOf(typeof(stChannelInfo)) * i);
            var channelll =  (stChannelInfo)Marshal.PtrToStructure(ptr, typeof(stChannelInfo));
        }

The problem now it's that I get an AccessViolationException here:

Estructura = LookForAvailableChannels("C:\\Folder", 12345678, FechaInicio, FechaFinal);

But I don't really know why, I would appreciate any help.

1

1 Answers

0
votes

Unfortunately your array ChannelInfo has not fixed size then automatic marshaling with [MarshalAs(UnamangedType.LPArray)] doesn't work. Doing marshaling manually means that ChannelInfo must be declared as IntPtr:

[StructLayout(LayoutKind.Sequential)]
public struct stChannel {
    public int ChannelNumber;      
    public IntPtr ChannelInfo;
};

When you need it you need to convert it to a struct:

var channelInfo = (stChannelInfo)Marshal.PtrToStructure(
    Estructura.ChannelInfo,
    typeof(stChannelInfo));

Now you're accessing to first element, to access array items you need an offset:

var ptr = IntPtr.Add(Estructura.ChannelInfo,
    Marshal.SizeOf(typeof(stChannelInfo)) * itemIndex);

And then use Marshal.PtrToStructure() over it. You may want to write an helper method:

static GetUnmanagedArrayItem<T>(IntPtr baseAddress, int index) {
    var ptr = IntPtr.Add(baseAddress, Marshal.SizeOf(typeof(T)) * index);
    return (T)Marshal.PtrToStructure(ptr, typeof(T));
}

Used like this:

var channelInfo = GetUnamangedArrayItem<stChannelInfo>(Estructura.ChannelInfo, 1);

You didn't explicitly ask but note that you can't manually marshal unamanged char* string with IntPtr for a fixed length char array

You may declare it as string decorating that field with `UnmanagedType.LPStr`: [MarshalAs(UnmanagedType.LPStr, SizeConst=17)] public string ChannelTag;

EDIT: I was wrong about this, String can't be used in return values because it's not blittable, of course IntPtr is wrong because you have a fixed length array, I'd suggest to use:

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 17)]
public byte[] ChannelTag;

You can get original string simply decoding this array with Encoding.ASCII.GetString(yourStruct.ChannelTag);. As alternative you may follow what suggested by JaredPar in this post.