I have an unmanaged C/C++ library that I use to decode a variable sized buffer of bytes. These bytes constituent a complex datagram that is extracted from a SQLite BLOB. In the C/C++ code it is very easy to overlay a struct on top of he data using pointers. Is there anyway to do the same thing in C#? I am passing a ref to the Byte[] to the unmanaged side by using the Marshal class. I can also pass references to structures on the managed side that get filled in. I would to pull all this over to C#. Any thoughts, anyone?
TIA,
Doug
This answer assumes a few things:
Your structs only contain primitive value types (i.e. no arrays, no reference types, no decimal values, no string and so on).
You have an array of bytes containing one or more structs at various offsets.
You are using .Net Core 3.1 or later.
You are using C# 7.0 or later.
It's possible to "overlay" a struct over a section of a byte array so that you can access the data in the array via the struct without making additional copies of the data. To do this, you use Span<byte> and MemoryMarshal.AsRef<T>.
When doing this, the structure packing is important of course. You must specify the packing for the structs to match the packing in the byte array. This is exactly the same as you have to do when declaring structs for use with P/Invoke.
(Despite what some may claim, the StructLayoutAttribute.Pack setting does affect the layout of structs in memory; it's not just for P/Invoke.)
If you are using char values in your structs, you must also specify the CharSet as Unicode. If your source structs are not using UTF16 for the chars, (if they are using ANSI or ASCII) then you will need to declare those fields as byte rather than char because C# chars are always UTF16.
Once you've laid out your structs correctly and you have a byte[] array containing one or more structs you can overlay a struct over a section of the array as follows:
Create a Span<byte> using the Span<byte>(byte[] array, int start, int length) constructor to reference the subset of the array that corresponds to the bytes of the struct.
Use MemoryMarshal.AsRef<T> to reference the struct within the byte array. This references the struct directly in the byte array without copying any data.
The following program demonstrates this approach.
It defines three different structs, Demo1, Demo2 and Demo3 each with a different packing.
It implements a toByteArray() method, the only purpose of which is to convert the structs to a byte array with some spare bytes at the start and end. The implementation of this isn't important; you'd get your byte array from P/Invoke.
It also implements a helper method AsSpan(). This is quite a useful method; it converts any blittable (unmanaged) struct to a Span<byte>().
The interesting method is consumeStructs(). This overlays three different structs over the given byte[] array and then prints out their contents.
If you run this program, you will see that the output matches the struct initialisation in Main().
Note that the structs are declared as ref var. This means that no data is copied; the structs are actually referencing data directly in the data[] array passed to the method.
I demonstrate that this is the case by modifying one of the bytes in the data[] array that corresponds to the start of the Demo2 struct like so:
data[prefixByteCount + Marshal.SizeOf<Demo1>()] = (byte)'*';
After making that change and reprinting the struct, the output shows that the CharValue has changed from 0 to *. This demonstrates that the struct really does reference data directly in the data[] array.
Here's the compilable Console application:
(Try it online)
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace Demo
{
static class Program
{
static void Main()
{
var demo1 = new Demo1
{
BoolValue = true,
DoubleValue = -1
};
var demo2 = new Demo2
{
CharValue = '0',
ShortValue = 1,
IntValue = 2,
LongValue = 3,
FloatValue = 4,
DoubleValue = 5.4321
};
var demo3 = new Demo3
{
ByteValue = 128,
FloatValue = 1.23f
};
const int PREFIX_BYTE_COUNT = 29;
const int POSTFIX_BYTE_COUNT = 17;
var bytes = toByteArray(PREFIX_BYTE_COUNT, POSTFIX_BYTE_COUNT, ref demo1, ref demo2, ref demo3);
consumeStructs(PREFIX_BYTE_COUNT, bytes);
}
static byte[] toByteArray(int prefixByteCount, int postfixByteCount, ref Demo1 demo1, ref Demo2 demo2, ref Demo3 demo3)
{
var demo1Bytes = AsSpan(ref demo1);
var demo2Bytes = AsSpan(ref demo2);
var demo3Bytes = AsSpan(ref demo3);
var prefixBytes = Enumerable.Repeat((byte)0, prefixByteCount);
var postfixBytes = Enumerable.Repeat((byte)0, postfixByteCount);
return prefixBytes
.Concat(demo1Bytes.ToArray())
.Concat(demo2Bytes.ToArray())
.Concat(demo3Bytes.ToArray())
.Concat(postfixBytes)
.ToArray();
}
static void consumeStructs(int prefixByteCount, byte[] data)
{
var demo1Bytes = new Span<byte>(data, prefixByteCount, Marshal.SizeOf<Demo1>());
var demo2Bytes = new Span<byte>(data, prefixByteCount + Marshal.SizeOf<Demo1>(), Marshal.SizeOf<Demo2>());
var demo3Bytes = new Span<byte>(data, prefixByteCount + Marshal.SizeOf<Demo1>() + Marshal.SizeOf<Demo2>(), Marshal.SizeOf<Demo3>());
ref var demo1Ref = ref MemoryMarshal.AsRef<Demo1>(demo1Bytes);
ref var demo2Ref = ref MemoryMarshal.AsRef<Demo2>(demo2Bytes);
ref var demo3Ref = ref MemoryMarshal.AsRef<Demo3>(demo3Bytes);
Console.WriteLine(demo1Ref);
Console.WriteLine(demo2Ref);
Console.WriteLine(demo3Ref);
Console.WriteLine("Modifying first byte of Demo2 struct in byte buffer.");
data[prefixByteCount + Marshal.SizeOf<Demo1>()] = (byte)'*';
Console.WriteLine(demo2Ref);
}
public static Span<byte> AsSpan<T>(ref T val) where T : unmanaged
{
var valSpan = MemoryMarshal.CreateSpan(ref val, 1);
return MemoryMarshal.Cast<T, byte>(valSpan);
}
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct Demo1
{
public bool BoolValue;
public double DoubleValue;
public override string ToString()
{
return $"byte = {BoolValue}, double = {DoubleValue}";
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct Demo2
{
public char CharValue;
public short ShortValue;
public int IntValue;
public long LongValue;
public float FloatValue;
public double DoubleValue;
public override string ToString()
{
return $"char = {CharValue}, short = {ShortValue}, int = {IntValue}, long = {LongValue}, float = {FloatValue}, double = {DoubleValue}";
}
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Demo3
{
public byte ByteValue;
public float FloatValue;
public override string ToString()
{
return $"byte = {ByteValue}, float = {FloatValue}";
}
}
}
Related
The struct in c++ dll is defined like this:
struct WAVE_INFO {
int channel_num;
int audio_type;
char *wave_data;
int wave_length;
};
And the calling method like this:
extern "C" STRUCTDLL_API int processStruct(WAVE_INFO *pIn, WAVE_INFO *pOut);
The wave_data in my c# struct has to be byte array (byte[])****, not char[] or string. How should I defind the struct and the method in c# where the dll is called? And the length of wave_date is fixed, let's say like 100.
First of all, I would say that the C++ struct is declared incorrectly. The payload is binary data so the array should be unsigned char* rather than char*.
Leaving that aside, the struct is a little fiddly to marshal because of the array. It goes something like this:
[StructLayout(LayoutKind.Sequential)]
struct WAVE_INFO
{
public int channel_num;
public int audio_type;
public IntPtr wave_data;
public int wave_length;
}
We can't use byte[] in the struct to be marshalled. Instead we have to declare the array as IntPtr and handle the marshalling ourselves. The cleanest way is to declare byte[] arrays and pin them with GCHandle.
The imported function looks like this:
[DllImport(dllfilename, CallingConvention = CallingConvention.Cdecl)]
static extern int processStruct(ref WAVE_INFO infoIn, ref WAVE_INFO infoOut);
And the rather messy call to the function goes like this:
var dataIn = new byte[256];
// populate the input data array
var dataOut = new byte[256];
GCHandle dataInHandle = GCHandle.Alloc(dataIn, GCHandleType.Pinned);
try
{
GCHandle dataOutHandle = GCHandle.Alloc(dataOut, GCHandleType.Pinned);
try
{
WAVE_INFO infoIn;
infoIn.audio_type = 1;
infoIn.channel_num = 2;
infoIn.wave_data = dataInHandle.AddrOfPinnedObject();
infoIn.wave_length = dataIn.Length;
WAVE_INFO infoOut = new WAVE_INFO();
infoOut.wave_data = dataOutHandle.AddrOfPinnedObject();
infoOut.wave_length = dataOut.Length;
int retval = processStruct(ref infoIn, ref infoOut);
// dataOut should have been populated by processStruct
}
finally
{
dataOutHandle.Free();
}
}
finally
{
dataInHandle.Free();
}
My assumption here is that the first parameter is used for input, and the second parameter for output. But that the onus is on the caller to allocate the wave data array for the output struct.
I've also assumed a calling convention, but you'd have to inspect the C++ macro STRUCTDLL_API to determine what the true calling convention is.
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);
I've been trying to invoke a method that have been created in Delphi in the following way:
function _Func1(arrParams: array of TParams): Integer;stdcall;
type
TParams = record
Type: int;
Name: string;
Amount : Real;
end;
My code is:
[DllImport("some.dll", EntryPoint = "_Func1", CallingConvention = CallingConvention.StdCall)]
public static extern int Func(
[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct)] TParams[] arrParams)
And the struct is:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TParams
{
public int Type;
[MarshalAs(UnmanagedType.AnsiBStr)]
public string Name;
public double Amount;
}
When I am calling this method I'm getting the error:
Cannot marshal field 'Name' of type 'TParams': Invalid managed/unmanaged type combination (String fields must be paired with LPStr, LPWStr, BStr or ByValTStr).
However none of those combinations works, as Delphi's strings are prefixed with its length and it is Ansi for sure (I've tried it with other string parameters). Does anyone have a clue how to solve this?
There are two main problems with this, use of open arrays and use of Delphi string.
Open arrays
Delphi open arrays are implemented by passing a pointer to the first element of the array and an extra parameter specifying the index of the last item, high in Delphi terminology. For more information see this answer.
Delphi strings
The C# marshaller cannot interop with a Delphi string. Delphi strings are private types, only to be used internally to a Delphi module. Instead you should use a null-terminated string, PAnsiChar.
Putting it all together you can write it like this:
Delphi
type
TParams = record
_Type: Integer;//Type is a reserved word in Delphi
Name: PAnsiChar;
Amount: Double;
end;
function Func(const arrParams: array of TParams): Integer; stdcall;
C#
[StructLayoutAttribute(LayoutKind.Sequential)]
public struct TParams
{
public int Type;
public string Name;
public double Amount;
}
[DllImport("some.dll")]
public static extern int Func(TParams[] arrParams, int high);
TParams[] params = new TParams[len];
...populate params
int retval = Func(params, params.Length-1);
To compliment David's answer, you can marshal to a Delphi string, but it's ugly. In C#, you have to replace all of your strings in the struct with IntPtr.
private static IntPtr AllocDelphiString(string str)
{
byte[] unicodeData = Encoding.Unicode.GetBytes(str);
int bufferSize = unicodeData.Length + 6;
IntPtr hMem = Marshal.AllocHGlobal(bufferSize);
Marshal.WriteInt32(hMem, 0, unicodeData.Length); // prepended length value
for (int i = 0; i < unicodeData.Length; i++)
Marshal.WriteByte(hMem, i + 4, unicodeData[i]);
Marshal.WriteInt16(hMem, bufferSize - 2, 0); // null-terminate
return new IntPtr(hMem.ToInt64() + 4);
}
This can directly be sent to Delphi, where it'll properly be read as a string.
Remember that you must free this string when you're done with it. However, GlobalFree() can't be called directly on the pointer to the string, because it doesn't point to the start of the allocation. You'll have to cast that pointer to a long, then subtract 4, then cast it back to a pointer. This compensates for the length prefix.
I hope someone can assist me with the problem I'm currently experiencing. We have a lot of Delphi legacy code, and need to convert some of our Delphi applications to C#.
The legacy code I'm currently struggling with is that of calling a function from a 3rd party application's non-COM DLL.
Here is the C-style header and struct used for the specific function:
/*** C Function AwdApiLookup ***/
extern BOOL APIENTRY AwdApiLookup( HWND hwndNotify, ULONG ulMsg,
BOOL fContainer, CHAR cObjectType,
SEARCH_CRITERIA* searchCriteria,
USHORT usCount, USHORT usSearchType,
VOID pReserved );
/*** C Struct SEARCH_CRITERIA ***/
typedef struct _search_criteria
{
UCHAR dataname[4];
UCHAR wildcard;
UCHAR comparator[2];
UCHAR datavalue[75];
} SEARCH_CRITERIA;
In our Delphi code, we have converted the above function and structure as:
(*** Delphi implementation of C Function AwdApiLookup ***)
function AwdApiLookup(hwndNotify: HWND; ulMsg: ULONG; fContainer: Boolean;
cObjectType: Char; pSearchCriteria: Pointer; usCount: USHORT;
usSearchType: USHORT; pReserved: Pointer): Boolean; stdcall;
external 'AWDAPI.dll';
(*** Delphi implementation of C Struct SEARCH_CRITERIA ***)
TSearch_Criteria = record
dataname: array [0..3] of char;
wildcard: char;
comparator: array [0..1] of char;
datavalue: array [0..74] of char;
end;
PSearch_Criteria = ^TSearch_Criteria;
and the way we call the above mentioned code in Delphi is:
AwdApiLookup(0, 0, true, searchType, #criteriaList_[0],
criteriaCount, AWD_USE_SQL, nil);
where criteriaList is defined as
criteriaList_: array of TSearch_Criteria;
After all that is said and done we can now look at the C# code, which I cannot get to work. I'm sure I'm doing something wrong here, or my C header is not translated correctly. My project does compile correctly, but when the function is called, I get a "FALSE" value back, which indicates that the function did not execute correctly in the DLL.
My C# code thus far:
/*** C# implementation of C Function AwdApiLookup ***/
DllImport("awdapi.dll", CharSet = CharSet.Auto)]
public static extern bool AwdApiLookup(IntPtr handle, ulong ulMsg,
bool fContainer, char cObjectType,
ref SearchCriteria pSearchCriteria,
ushort usCount, ushort usSearchType,
Pointer pReserverd);
/*** C# implementation of C Struct SEARCH_CRITERIA ***/
[StructLayout(LayoutKind.Sequential)]
public struct SearchCriteria
{
private readonly byte[] m_DataName;
private readonly byte[] m_Wildcard;
private readonly byte[] m_Comparator;
private readonly byte[] m_DataValue;
public SearchCriteria(string dataName, string comparator, string dataValue)
{
m_DataName = Encoding.Unicode.GetBytes(
dataName.PadRight(4, ' ').Substring(0, 4));
m_Wildcard = Encoding.Unicode.GetBytes("0");
m_Comparator = Encoding.Unicode.GetBytes(
comparator.PadRight(2, ' ').Substring(0, 2));
m_DataValue = Encoding.Unicode.GetBytes(
dataValue.PadRight(75, ' ').Substring(0, 75));
}
public byte[] dataname { get { return m_DataName; } }
public byte[] wildcard { get { return m_Wildcard; } }
public byte[] comparator { get { return m_Comparator; } }
public byte[] datavalue { get { return m_DataValue; } }
}
My C# call to the C# function looks like this
var callResult = UnsafeAwdApi.CallAwdApiLookup(IntPtr.Zero, 0, true, 'W',
ref searchCriteria[0], criteriaCount,
66, null);
where searchCriteria and criteriaCount is defined as
List<SearchCriteria> criteriaList = new List<SearchCriteria>();
var searchCriteria = criteriaList.ToArray();
var criteriaCount = (ushort)searchCriteria.Length;
and adding data to searchCriteria:
public void AddSearchCriteria(string dataName, string comparator, string dataValue)
{
var criteria = new SearchCriteria();
criteria.DataName = dataName;
criteria.Wildcard = "0";
criteria.Comparator = comparator;
criteria.DataValue = dataValue;
criteriaList.Add(criteria);
}
Like I said, my code compiles correctly, but when the function executes, it returns "FALSE", which should not be the case as the Delphi function does return data with the exact same input.
I know I'm definitely doing something wrong here, and I've tried a couple of things, but nothing seems to be working.
Any assistance or nudge in the right direction would be greatly appreciated.
Thanks, Riaan
Several things here.
First of all C++ ULONG is a 32-bit integer, and becomes uint in C# - ulong is 64-bit.
For the struct, you don't need to mess with byte arrays. Use strings, and ByValTStr. Also, it's not really worth bothering with readonly and properties for interop structs. Yes, mutable value types are generally bad in a pure .NET API, but in this case it's the existing API, there's no point in masking it. So:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SearchCriteria
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4]
public string m_DataName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1]
public string m_Wildcard;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2]
public string m_Comparator;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 75]
public string m_DataValue;
}
If you really want to do all the string conversions yourself, it may be easier to just use unsafe and fixed-size arrays:
public unsafe struct SearchCriteria
{
public fixed byte m_DataName[4];
public byte m_Wildcard;
public fixed byte m_Comparator[2];
public fixed byte m_DataValue[75];
}
[EDIT] Two more things.
CHAR cObjectType should become byte cObjectType, and not char cObjectType that you currently use.
Also, yes, there is a problem with array marshaling in your example. Since your P/Invoke declaration is ref SearchCriteria pSearchCriteria - i.e. a single value passed by reference - that's precisely what P/Invoke mashaler will do. Keep in mind that, unless your struct only has fields of unmanaged types in it (the one with fixed arrays above is that, the one with string is not), the marshaler will have to copy the structs. It won't just pass address to the first element of the array directly. And in this case, since you didn't tell it it's an array there, it will only copy the single element you reference.
So, if you use the version of the struct with string, fields, you need to change the P/Invoke declaration. If you only need to pass SEARCH_CRITERIA objects into the function, but won't need to read data from them after it returns, just use an array:
public static extern bool AwdApiLookup(IntPtr handle, uint ulMsg,
bool fContainer, byte cObjectType,
SearchCriteria[] pSearchCriteria,
ushort usCount, ushort usSearchType,
Pointer pReserverd);
And call it like this:
var callResult = UnsafeAwdApi.CallAwdApiLookup(
IntPtr.Zero, 0, true, (byte)'W',
searchCriteria, criteriaCount,
66, null);
If function writes data into that array, and you need to read it, use [In, Out]:
[In, Out] SearchCriteria[] pSearchCriteria,
If you use the version with fixed byte[] arrays, you can also change the P/Invoke declaration to read SearchCriteria* pSearchCriteria, and then use:
fixed (SearchCriteria* p = &searchCriteria[0])
{
AwdApiLookup(..., p, ...);
}
This will require unsafe as well, though.
I am doing some C# interop work. I have the following struct:
#pragma pack(push,1)
typedef struct
{
unsigned __int64 Handle;
LinkType_t Type;
LinkState_t State;
unsigned __int64 Settings;
signed __int8 Name[MAX_LINK_NAME];
unsigned __int8 DeviceInfo[MAX_LINK_DEVINFO];
unsigned __int8 Reserved[40];
} LinkInfo_t;
This is my attempt to convert it into a C# struct:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LinkInfo_t
{
[MarshalAs(UnmanagedType.U8)]
public UInt64 Handle;
[MarshalAs(UnmanagedType.I4)]
public LinkType_t Type;
[MarshalAs(UnmanagedType.I4)]
public LinkState_t State;
[MarshalAs(UnmanagedType.U8)]
public UInt64 Settings;
[MarshalAs(UnmanagedType.LPStr, SizeConst = MAX_LINK_NAME)]
public string Name;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_LINK_DEVINFO, ArraySubType = UnmanagedType.U1)]
public byte[] DeviceInfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40, ArraySubType = UnmanagedType.U1)]
public byte[] Reserved;
}
However, whenever I initialize the struct the Name, DeviceInfo, and Reserved fields are all set to null. How do I fix this?
For the arrays, try to use the fixed keyword:
public fixed byte DeviceInfo[MAX_LINK_DEVINFO];
public fixed byte Reserved[40];
whenever I initialize the struct the
Name, DeviceInfo, and Reserved fields
are all set to null
This is correct, and your definition looks OK to me (BTW, you don't need [MarshalAs] on the primitive fields, the default behaviour is to do what you specified there). Because your array fields are null, the marshaler won't do anything about them when marshaling your struct to unmanaged memory, but it's going to create the strings and arrays when unmarshaling.
What Anton Tykhyy says is correct. I just want to clarify with some examples. Using 'fixed' works, but that forces you to use 'unsafe' as well. I like to avoid using unsafe wherever possible. Using Marshal is a way to get around that.
First, let's say that I have a library that was created in C with the following definitions.
typedef struct {
int messageType;
BYTE payload[60];
} my_message;
/**
* \param[out] msg Where the message will be written to
*/
void receiveMessage(my_message *msg);
/*
* \param[in] msg The message that will be sent
*/
void sendMessage(my_message *msg);
In C#, the following structure would be equivalent to the one in C.
[StructLayout(LayoutKind.Sequential, Size = 64), Serializable]
struct my_message
{
int messageType;
[MarshalAs(UnmanagedType.ByValArray,SizeConst = 60)]
byte[] payload;
public initializeArray()
{
//explicitly initialize the array
payload = new byte[60];
}
}
Since the msg in receiveMessage() is documented as [out], you don't need to do anything special to the array in the structure before passing it to the function. i.e.:
my_message msg = new my_message();
receiveMessage(ref msg);
byte payload10 = msg.payload[10];
Since the msg in sendMessage() is documented as [in], you will need to fill the array before calling the function. Before filling the array, the array needs to be explicitly instantiated before using it. i.e.:
my_message msg = new my_message();
msg.initializeArray();
msg.payload[10] = 255;
sendMessage(ref msg);
Calling initializeArray() should instantiate the array in the previously allocated space created within the struct for this array.