Pass struct* from c# to c++ dll - c#

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.

Related

C# Is it possible to overlay a byte buffer with a structure?

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}";
}
}
}

Using Delphi's stuct arrays and strings in C#

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.

Converting Delphi code for unmanaged dll to C#

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.

C#: marshalling a struct that contains arrays

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.

Passing pointers from unmanaged code

I have a C# project that imports a C dll, the dll has this function:
int primary_read_serial(int handle, int *return_code, int *serial, int length);
I want to get access to the serial parameter. I've actually got it to return one character of the serial parameter, but I'm not really sure what I'm doing and would like to understand what is going, and of course get it working.
So, I'm very sure that the dll is being accessed, other functions without pointers work fine. How do I handle pointers? Do I have to marshal it? Maybe I have to have a fixed place to put the data it?
An explanation would be great.
Thanks!
Richard
You will have to use an IntPtr and Marshal that IntPtr into whatever C# structure you want to put it in. In your case you will have to marshal it to an int[].
This is done in several steps:
Allocate an unmanaged handle
Call the function with the unamanged array
Convert the array to managed byte array
Convert byte array to int array
Release unmanaged handle
That code should give you an idea:
// The import declaration
[DllImport("Library.dll")]
public static extern int primary_read_serial(int, ref int, IntPtr, int) ;
// Allocate unmanaged buffer
IntPtr serial_ptr = Marshal.AllocHGlobal(length * sizeof(int));
try
{
// Call unmanaged function
int return_code;
int result = primary_read_serial(handle, ref return_code, serial_ptr, length);
// Safely marshal unmanaged buffer to byte[]
byte[] bytes = new byte[length * sizeof(int)];
Marshal.Copy(serial_ptr, bytes, 0, length);
// Converter to int[]
int[] ints = new int[length];
for (int i = 0; i < length; i++)
{
ints[i] = BitConverter.ToInt32(bytes, i * sizeof(int));
}
}
finally
{
Marshal.FreeHGlobal(serial_ptr);
}
Don't forget the try-finally, or you will risk leaking the unmanaged handle.
If I understand what you're trying to do, this should work for you.
unsafe
{
int length = 1024; // Length of data to read.
fixed (byte* data = new byte[length]) // Pins array to stack so a pointer can be retrieved.
{
int returnCode;
int retValue = primary_read_serial(0, &returnCode, data, length);
// At this point, `data` should contain all the read data.
}
}
JaredPar gives you the easy way to do it however, which is just to change your external function declaration so that C# does the marshalling for you in the background.
Hopefully this gives you an idea of what's happening at a slightly lower level anyway.
When writing your P/invoke declaration of that function, try using keyword ref for the pointer parameters like this:
[DllImport("YouDll.dll", EntryPoint = "primary_read_serial")]
public static extern int primary_read_serial(int, ref int, ref int, int) ;
I'm not sure if you need to specify the parameters' name in C#. And remember, when calling that method, you will also have to use ref in the arguments you're passing by reference.

Categories

Resources