简体   繁体   中英

C#: How to set member arrays of an unsafe struct

I am working on a VS 2015 MVC C# web application that loads a 3rd party C++ DLL I do not have source code for. The DLL requires an input param. The new spec requires a couple of array members. One is an int array and the other is a char array.

My C# struct defines the intended char array as byte to match the 8-bit C++ char:

[StructLayout(LayoutKind.Sequential)]
public unsafe struct MyDLLInput
{
    public fixed int SomeList[288];
    public fixed byte PathToData[256];
};

The struct seems correct to me, but now I need to set values and I'm not having any success.

MyDLLInput dllInput = new MyDLLInput()
{
    SomeList = new int[] {0,12,33,67,93},
    PathToData = "C:\\some\\path\\to\\data"
}

// Call 3rd Party DLL
MyDLLOutput = MyDLL.EntryPoint(MyDLLInput);

For both member arrays I am getting the following error:

Fixed size buffers can only be accessed through locals or fields.

There are at least a couple of things going on here - aside from the proper way of setting the values using a local I also have to make an encoding conversion from string to byte[] .

Can someone provide me with a code example of a clean way to set these values?

Is there some reason you're using an unsafe struct? Can you not use marshalling attributes? See https://msdn.microsoft.com/en-us/library/eshywdt7(v=vs.110).aspx

Regardless, you need to know how you're converting from a C# string to a byte array, and that depends on what encoding your C++ DLL expects that string to be in. For example, on Windows, it is often the "ANSI code page", but on Linux/Unix it might be either "current locale" or explicitly "UTF-8".

So, one option that gives you the most control over the encoding would be to do somethiing like:

    [StructLayout(LayoutKind.Sequential)]
    public struct MyDllInput
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 288)]
        public int[] SomeList;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
        public byte[] PathToData;
    }
    public static void Main()
    {
        MyDllInput dllInput = new MyDllInput()
        {
            SomeList = new int[288],
            PathToData = new byte[256]
        };

        var listData = new int[] { 0, 12, 33, 67, 93 };
        Array.Copy(listData, dllInput.SomeList, listData.Length);

        var pathToDataBytes = Encoding.UTF8.GetBytes("C:\\some\\path\\to\\data");
        Array.Copy(pathToDataBytes, dllInput.PathToData, pathToDataBytes.Length);
    }

Alternatively, instead of doing the encoding conversion directly, you can try declaring the PathToData as a string and then using a marshalling attribute to have C# convert it for you; see https://msdn.microsoft.com/en-us/library/s9ts558h(v=vs.110).aspx :

    [StructLayout(LayoutKind.Sequential)]
    public struct MyDllInput
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 288)]
        public int[] SomeList;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst =256)]
        public String PathToData;
    }
    public static void Main()
    {
        MyDllInput dllInput = new MyDllInput()
        {
            SomeList = new int[288],
            PathToData = "C:\\some\\path\\to\\data"
        };            

        var listData = new int[] { 0, 12, 33, 67, 93 };
        Array.Copy(listData, dllInput.SomeList, listData.Length);
    }

In the second case, it's important that when you declare EntryPoint you set the CharSet property on the DllImportAttribute to get the string conversion to happen the way you want. In your case, you probably want CharSet.Ansi since your DLL takes a char* and not a wchar_t*. For example,

    [DllImport("MyDll.dll", CharSet = CharSet.Ansi)]
    private static extern void EntryPoint(ref MyDllInput input);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM