I'm trying to convert an old Delphi app into C#. It does some stuff with binary files written via packed records which I've put below. However, only the BInfoRec record is guaranteed to be in the file and appearing first. The others may or may not be in there, and the order of them is unknown. I'm having trouble with one method in particular. It's getting the number of bytes being read in via FileStream.Read and reading those into one of the packed records. Then, in the first if statement (in the delphi version), it is allocating memory on the heap, and doing the same thing as before, but reading it into a pointer. I'm trying to figure out the best way to go about this but I'm by no means an expert with Delphi.
Delphi code:
StartP = packed record
x:SmallInt;
y:SmallInt;
end;
InfoP = packed record
Ycoord:double;
Xcoord:double;
//other vars here
end;
HeadP = packed record
NumP:DWORD;
SizeStruct:DWORD;
SizePoStruct:DWORD;
//other vars here
end;
BInfoRec = packed record
StructNum : WORD ;
in_size : WORD ;
//other variables here
end;
var
tStream:TFileStream;
bInfo:BInfoRec;
RestOfBFile:Pointer;
sizeofRest:Integer;
Function LoadBFile(FileName:String):Boolean;
var
sizeread:Integer;
begin
Try
LoadBFile:=False;
tStream:=TFileStream.Create(Filename,fmOpenRead );
sizeofRest:=tStream.Size-Sizeof(bInfo);
sizeread:=tStream.Read(bInfo,Sizeof(bInfo));
if sizeread = Sizeof(bInfo) then
begin //best way to convert this?
RestOfBFile:=AllocMem(sizeofRest);
sizeread:=tStream.Read(RestOfBFile^,sizeofRest);
if SizeofRest= SizeRead then
LoadBFile:=True;
end;
tStream.Free;
except
LoadBFile:=False;
tStream.Free;
end;
end;
C# (what I have so far):
[Serializable()]
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct StartP
{
public short x;
public short y;
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct InfoP
{
public double Ycoord;
public double Xcoord;
//other vars here
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct HeadP
{
public UInt32 NumP;
public UInt32 SizeStruct;
public UInt32 SizePoStruct;
//other vars here
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct BInfoRec
{
public ushort StructNum;
public ushort in_size;
}
BInfoRec bInfo;
int sizeOfRest;
private Boolean LoadBFile(string fileName)
{
int sizeRead;
byte[] buffer = new byte[Marshal.SizeOf(bInfo)];
try
{
using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
sizeOfRest = (int)stream.Length - Marshal.SizeOf(typeof(BInfoRec));
sizeRead = stream.Read(buffer, 0, Marshal.SizeOf(typeof(BInfoRec)));
if (sizeRead == Marshal.SizeOf(typeof(BInfoRec)))
{
//what goes here??
if (sizeOfRest == sizeRead)
{
return true;
}
}
}
}
catch (Exception ex)
{
return false;
}
}
I was thinking about creating a new byte array of unknown size and using a BinaryReader to read in the rest of the file to that array, then just checking the size of that. Not sure if it's the best way though?
It's a block of memory of arbitrary size. I don't see that you have many options other than a byte array. Yes you could allocate unmanaged memory (e.g. with Marshal.AllocHGlobal) but that would hardly be convenient.
So, yes, if I were you I would allocate a byte array, and read the contents into it.
Related
I've been banging my head all day and hope someone can help. I need to marshal a managed data structure to an unmanaged C dll. When I look at all the memory it appears that what I'm doing is working, but the C dll (a black box to me) is returning an error indicating the data is corrupt. Can anyone point out my error?
C declarations
typedef struct _TAG_Data
{
void *data; // Binary data
uint32_t size; // Data size bytes
} Data;
// Parse binary data, extract int value
ParseData(Data ∗ result, uint64_t parameter, int32_t ∗ value)
Managed object:
public class MyData
{
public byte[] data;
public UInt32 size;
}
Packed equivalent for moving to unmanaged memory:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyData_Packed
{
public IntPtr data;
public UInt32 size;
}
At this point I have a managed "MyData" struct called MyResult with valid data that needs to go into the dll. Here's what I'm doing:
[DllImport("Some.dll", EntryPoint = "ParseData", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern Int32 ParseData_Native(IntPtr result, UInt64 parameter, ref Int32 value);
IntPtr MyResultPackedPtr = new IntPtr();
MyData_Packed MyResultPacked = new MyData_Packed();
// Copy "MyResult" into un-managed memory so it can be passed to the C library.
// Allocate un-managed memory for the data buffer
MyResultPacked.data = Marshal.AllocHGlobal((int)MyResult.size);
// Copy data from managed "MyResult.data" into the unmanaged "MyResultPacked.data"
Marshal.Copy(MyResult.data, 0, MyResultPacked.data, (int)MyResult.size);
MyResultPacked.size = MyResult.size;
// Allocate unmanaged memory for the structure itself
MyResultPackedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyData_Packed)));
// Copy the packed struct into the unmanaged memory and get our pointer
Marshal.StructureToPtr(MyResultPacked, MyResultPackedPtr, false);
// Pass our pointer to the unmanaged struct, which points to the unmanaged data buffer, to the C dll
Int32 tmp = 0;
ErrorCode = ParseData_Native(MyResultPackedPtr, parameter, ref tmp);
When I look at the unmanaged memory pointed to by MyResultPacked.data, the data is correct so the copy was good. And when I look at MyResultPackedPtr in memory, the first 8 bytes are the address of the same unmanaged memory pointed to by MyResultPacked.data (64-bit machine), and the next 4 bytes are the proper size of the data. So it appears that MyResultPackedPtr points to a valid copy of MyResultPacked. But the return value from ParseData() indicates my data must be corrupt, so I must be doing something wrong.
To take it a step further, I wrote the same code 100% in C and it works. And the data in the binary buffer in C matched the data in the binary buffer in C#, going by the memory watch feature in Visual Studio, so it appears my data handling is correct. Which makes me think something is wrong with the way I'm passing MyResultPackedPtr to the dll. Unfortunately I don't have the source for the dll and cannot step into it. Can anyone offer a suggestion on what to try next?
I don't see why you need any of this custom marshalling code in the first place. You should be able to pass the struct with the byte[] array directly, and the marshaller will sort out the copying.
You also need to set the calling convention correctly.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyData_Packed
{
public byte[] data;
public UInt32 size;
}
[DllImport("Some.dll", EntryPoint = "ParseData", SetLastError = true, CallingConvention = CallingConvention.CDecl)]
private static extern int ParseData_Native(ref MyData_Packed result, ulong parameter, out int value);
var MyResultPacked = new MyData_Packed
{
data = MyResult,
size = MyResult.size,
};
ErrorCode = ParseData_Native(ref MyResultPacked, parameter, out var tmp);
I am looking for some guidance when it comes to call DeviceIoControl from C#, knowing that its generic aspect of accepting pointer parameters isn't always easy to express in C#.
Here are two examples and explanations laid out below.
Example 1:
This works but is cumbersome, you have a disposable scope but you have to pass the parameters to the function and at the end assign the output buffer value back to the variable.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
using (var scope = new UnmanagedMemoryScope<CDROM_TOC>(toc))
{
if (!UnsafeNativeMethods.DeviceIoControl(Handle, code, IntPtr.Zero, 0, scope.Memory, scope.Size, out _))
return Array.Empty<ITrack>();
toc = scope.Value; // this is weird
}
Example 1 helper:
internal struct UnmanagedMemoryScope<T> : IDisposable where T : struct
{
private bool IsDisposed { get; set; }
public uint Size { get; }
public IntPtr Memory { get; }
public T Value
{
get => Marshal.PtrToStructure<T>(Memory);
set => Marshal.StructureToPtr(value, Memory, true);
}
public UnmanagedMemoryScope(T value)
{
var size = Marshal.SizeOf<T>();
Memory = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(value, Memory, false);
Size = (uint)size;
IsDisposed = false;
}
public void Dispose()
{
if (IsDisposed)
return;
if (Memory != default)
Marshal.FreeHGlobal(Memory);
IsDisposed = true;
}
}
Example 2:
This one is already much more friendly, wrappers do marshalling and the value passed is ref.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
var ioctl = DeviceIoControl(Handle, code, ref toc);
// ...
Example 2 helper 1:
private static bool DeviceIoControl<TTarget>(
SafeFileHandle handle, uint code, ref TTarget target)
where TTarget : struct
{
var sizeOf = Marshal.SizeOf<TTarget>();
var intPtr = Marshal.AllocHGlobal(sizeOf);
Marshal.StructureToPtr(target, intPtr, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
IntPtr.Zero,
0u,
intPtr,
(uint)sizeOf,
out var lpBytesReturned
);
target = Marshal.PtrToStructure<TTarget>(intPtr);
Marshal.FreeHGlobal(intPtr);
return ioctl;
}
Example 2 helper 2:
private static bool DeviceIoControl<TTarget, TSource>(
SafeFileHandle handle, uint code, ref TTarget target, ref TSource source)
where TSource : struct
where TTarget : struct
{
var sizeOf1 = Marshal.SizeOf(source);
var sizeOf2 = Marshal.SizeOf(target);
var intPtr1 = Marshal.AllocHGlobal(sizeOf1);
var intPtr2 = Marshal.AllocHGlobal(sizeOf2);
Marshal.StructureToPtr(source, intPtr1, false);
Marshal.StructureToPtr(target, intPtr2, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
intPtr1,
(uint)sizeOf1,
intPtr2,
(uint)sizeOf2,
out var lpBytesReturned
);
Marshal.PtrToStructure(intPtr1, source);
Marshal.PtrToStructure(intPtr2, target);
Marshal.FreeHGlobal(intPtr1);
Marshal.FreeHGlobal(intPtr2);
return ioctl;
}
But I feel that I might be missing something and maybe there's a better approach...
Question:
What are some good tricks when it comes to call DeviceIoControl from C#?
Knowing that,
want to avoid the use of unsafe keyword
there are non-blittable types so fixed is out of question for them
the function accepts arbitrary types, it's only buffers for it in the end
Of course there's the C++/CLI route but well, it's not C# anymore...
Hope that makes sense to you, else let me know.
I usually do it like this.
Parameters structure:
ref struct CDROM_TOC
{
const int MAXIMUM_NUMBER_TRACKS = 100;
public const int sizeInBytes = 4 + MAXIMUM_NUMBER_TRACKS * 8;
readonly Span<byte> buffer;
public CDROM_TOC( Span<byte> buffer )
{
if( buffer.Length != sizeInBytes )
throw new ArgumentException();
this.buffer = buffer;
}
/// <summary>Fixed header of the structure</summary>
public struct Header
{
public ushort length;
public byte firstTrack, lastTrack;
}
/// <summary>Fixed header</summary>
public ref Header header =>
ref MemoryMarshal.Cast<byte, Header>( buffer.Slice( 0, 4 ) )[ 0 ];
public struct TRACK_DATA
{
byte reserved;
public byte controlAndAdr;
public byte trackNumber;
byte reserved2;
public uint address;
}
/// <summary>Tracks collection</summary>
public Span<TRACK_DATA> tracks =>
MemoryMarshal.Cast<byte, TRACK_DATA>( buffer.Slice( 4 ) );
// Make this structure compatible with fixed() statement
public ref byte GetPinnableReference() => ref buffer[ 0 ];
}
Usage example:
CDROM_TOC toc = new CDROM_TOC( stackalloc byte[ CDROM_TOC.sizeInBytes ] );
unsafe
{
fixed( byte* buffer = toc )
{
// Here you have unmanaged pointer for that C interop.
}
}
// If you want to return the tracks, need to copy to managed heap:
var header = toc.header;
return toc.tracks
.Slice( header.firstTrack, header.lastTrack - header.firstTrack + 1 )
.ToArray();
Couple more notes.
The answer assumes you have a modern C#, i.e. .NET 5 or newer, or any version of .NET Core.
The example does use unsafe, but only on the lowest level. If you absolutely don’t want that, use GCHandle instead. With GCHandleType.Pinned, it’s an equivalent to unsafe keyword, only slower.
Unlike your code, this method does not use any heap memory for the interop, neither managed nor native.
The instance of the structure is stack allocated, and it exposes higher-level API to access the fields of that structure. The complete stack is already fixed in memory, the fixed keyword going to nothing for that code, just return the address. Doing nothing is free performance-wise.
We've been given a program from another organization that collects data from multicast sources and collates and saves that data. It expects a C++ struct formatted as such:
#define SP_PACKET_SIZE 200
#define NAME_SIZE 64
struct spPacketStruct
{
int Size;
char Name[SP_PACKET_SIZE][NAME_SIZE];
double Value[SP_PACKET_SIZE];
};
obviously I can't use this struct in C# because a struct can't have preinitialized arrays, so I figured create individual bits and just serialize them. So now I have this in C#:
int SpPacketSize;
char[,] SpNames = new char[SP_PACKET_SIZE, NAME_SIZE];
double[] SpValues = new double[SP_PACKET_SIZE];
My previous experience is with a BinaryWriter...I don't need to deserialize in C#, I just need to get it to the C++ program. My test serialization code is as follows:
System.IO.MemoryStream outputstream = new System.IO.MemoryStream();
BinaryFormatter serializer = new BinaryFormatter();
serializer.TypeFormat = System.Runtime.Serialization.Formatters.FormatterTypeStyle.TypesWhenNeeded;
serializer.Serialize(outputstream, SpPacketSize);
serializer.Serialize(outputstream, SpNames);
serializer.Serialize(outputstream, SpValues);
byte[] buffer = outputstream.GetBuffer();
udpclient.Send(buffer, buffer.Length, remoteep);
And I get a binary packet but the length isn't right because it's still including the type formats. When I look at this packet in Wireshark I see a System.Int32 notation in the beginning of it. This is making the packet larger than expected and thus not deserialized properly on the C++ side.
I added the TypesWhenNeeded TypeFormat thinking I could minimize it, but it didn't change...and I noticed there was no option to not TypeFormat, unless I missed it somewhere.
Does anyone have any hints on how to properly serialize this data without the extra info?
While you cannot pre-initialize struct fields, nor directly put the size in MarshalAs attribute for ByValue 2D array, there is a little work-around you can do. You can define two structs like this:
const short SP_PACKET_SIZE = 200;
const short NAME_SIZE = 64;
struct spPacketStruct
{
public int Size;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
private fixedString[] names;
public fixedString[] Names { get { return names ?? (names = new fixedString[SP_PACKET_SIZE]); } }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
private double[] value;
public double[] Value { get { return value ?? (value = new double[SP_PACKET_SIZE]); } }
}
struct fixedString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAME_SIZE)]
public string Name;
}
By having additional struct to be a member of the original struct, you are able to specify the length of both dimensions by setting SizeConst in the original struct to the first dimension and setting it to the second dimension in the new struct. Making the field private and creating properties for them is merely for convenience, so you don't have to assign the array yourself when creating the struct.
Then you can serialize/deserialize the struct like this (code from this answer: https://stackoverflow.com/a/35717498/9748260):
public static byte[] GetBytes<T>(T str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
GCHandle h = default;
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
Marshal.StructureToPtr(str, h.AddrOfPinnedObject(), false);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return arr;
}
public static T FromBytes<T>(byte[] arr) where T : struct
{
T str = default;
GCHandle h = default;
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return str;
}
And one last thing when trying to serialize/deserialize structs like this, be aware of the struct alignment as it can mess with the struct size
I have a c# .net 2.0 CF application that interfaces with a native DLL implementing a function like this:
struct NATIVE_METHOD_REPLY
{
int other_irrelevant_data;
int data_size;
void* data;
}
// reply_buffer will contain an array of NATIVE_METHOD_REPLY structures
// and their data.
//
// returns an error code
int Foo(NATIVE_METHOD_REPLY* reply_buffer, int reply_size);
I've implemented it in C# like this:
[StructLayout(LayoutKind.Sequential)]
internal struct NATIVE_METHOD_REPLY
{
public Int32 OtherIrrelevantData;
public Int16 DataSize;
public IntPtr DataPtr;
}
[DllImport("my_lib.dll", SetLastError = true)]
internal static extern Int32 Foo(byte[] replyBuffer, Int32 replySize);
public byte[] void Bar()
{
// data returned to the user. May be an arbitrary size.
byte[] result_buffer = new byte[256];
// data sent to Foo()
byte[] reply_buffer =
new byte[Marshal.SizeOf(typeof(NativeMethods.NATIVE_METHOD_REPLY)) +
result_buffer.Length];
NativeMethods.Foo(reply_buffer, reply_buffer.Length);
// is there a better way of doing this?
NativeMethods.NATIVE_METHOD_REPLY reply;
GCHandle pinned_reply = GCHandle.Alloc(reply_buffer,
GCHandleType.Pinned);
try
{
reply = (NativeMethods.NATIVE_METHOD_REPLY)Marshal.PtrToStructure(
pinned_reply.AddrOfPinnedObject(),
typeof(NativeMethods.NATIVE_METHOD_REPLY));
Marshal.Copy(reply.DataPtr, result_buffer, 0, reply.DataSize);
}
finally
{
pinned_reply.Free();
}
// bonus point*: is this okay to do after the Free() call?
int test = reply.OtherIrrelevantData;
return result_buffer;
}
While this works correctly, I would like to know if this is the most efficient / most correct way of implementing this function.
Is there some method converting a managed byte array to a managed structure that doesn't involve an intermediate native handle and a copy? For instance, in C++, I would just do this:
NATIVE_METHOD_REPLY* reply = reinterpret_cast< NATIVE_METHOD_REPLY* >( reply.DataPtr );
*For a bonus point, is it okay to use data in the structure after the native handle has been freed?
Thanks,
PaulH
Edit: Updated solution
[DllImport("my_lib.dll", SetLastError = true)]
internal static extern Int32 Foo(IntPtr replyBuffer, Int32 replySize);
public byte[] void Bar()
{
byte[] result_buffer = new byte[256];
int reply_buffer_len = Marshal.SizeOf(typeof(NativeMethods.NATIVE_METHOD_REPLY)) + result_buffer.Length;
IntPtr reply_buffer = Marshal.AllocCoTaskMem(reply_buffer_len);
NativeMethods.NATIVE_METHOD_REPLY reply;
try
{
NativeMethods.Foo(reply_buffer, reply_buffer_len);
reply = (NativeMethods.NATIVE_METHOD_REPLY)Marshal.PtrToStructure(
reply_buffer,
typeof(NativeMethods.NATIVE_METHOD_REPLY));
Marshal.Copy(reply.DataPtr, result_buffer, 0, reply.DataSize);
}
finally
{
Marshal.FreeCoTaskMem(reply_buffer);
}
return result_buffer;
}
The structure has a fixed size. There's no point in passing an array, just pass the structure:
[DllImport("my_lib.dll", SetLastError = true)]
internal static extern Int32 Foo(out NATIVE_METHOD_REPLY replyBuffer, Int32 replySize);
You do have a memory management problem. Who owns the pointer?
Okay, the structure is actually variable sized and the pointer points into the array. You need nother approach. Simply allocate a chunk of unmanaged memory up front instead of letting the P/Invoke marshaller copy the data into a managed array. Which is in fact a hard requirement since the garbage collector can move the array, invalidating the pointer. Call Marshal.CoTaskMemAlloc() to reserve the memory, you'll have to free it later. And change the first argument of the function to IntPtr (not out).
You'll find that marshaling the structure a lot easier too, no need to pin the memory. Don't forget to Marshal.FreeCoTaskMem() when you're done.
In C# under the full framework, you can marshal the array directly. See Default Marshaling for Arrays. I don't know what the limitations are on the Compact Framework.
[EDIT] I changed the source as suggested by Stephen Martin (highlighted in bold). And added the C++ source code as well.
I'd like to call an unmanaged function in a self-written C++ dll. This library reads the machine's shared memory for status information of a third party software. Since there are a couple of values, I'd like to return the values in a struct. However, within the struct there are char [] (Arrays of char with a fixed size). I now try to receive that struct from the dll call like this:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_OUTPUT
{
UInt16 ReadyForConnect;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
String VersionStr;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
String NameOfFile;
// actually more of those
}
public partial class Form1 : Form
{
public SYSTEM_OUTPUT output;
[DllImport("testeshm.dll", EntryPoint="getStatus")]
public extern static int getStatus(out SYSTEM_OUTPUT output);
public Form1()
{
InitializeComponent();
}
private void ReadSharedMem_Click(object sender, EventArgs e)
{
try
{
label1.Text = getStatus(out output).ToString();
}
catch (AccessViolationException ave)
{
label1.Text = ave.Message;
}
}
}
I will post code from the c++ dll as well, I'm sure there's more to hunt down. The original struct STATUS_DATA has an array of four instances of the struct SYSTEM_CHARACTERISTICS and within that struct there are char[]s, that are not being filled (yet), resulting in a bad pointer. That's why I'm trying to extract a subset of the first SYSTEM_CHARACTERISTICS item in STATUS_DATA.
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#if defined(_MSC_VER)
#include <windows.h>
#define DLL extern "C" __declspec(dllexport)
#else
#define DLL
#endif
using namespace std;
enum { SYSID_LEN = 1024, VERS_LEN = 128, SCENE_LEN = 1024 };
enum { MAX_ENGINES = 4 };
struct SYSTEM_CHARACTERISTICS
{
unsigned short ReadyForConnect;
char VizVersionStr[VERS_LEN];
char NameOfFile[SCENE_LEN];
char Unimplemented[SCENE_LEN]; // not implemented yet, resulting to bad pointer, which I want to exclude (reason to have SYSTEM_OUTPUT)
};
struct SYSTEM_OUTPUT
{
unsigned short ReadyForConnect;
char VizVersionStr[VERS_LEN];
char NameOfFile[SCENE_LEN];
};
struct STATUS_DATA
{
SYSTEM_CHARACTERISTICS engine[MAX_ENGINES];
};
TCHAR szName[]=TEXT("E_STATUS");
DLL int getStatus(SYSTEM_OUTPUT* output)
{
HANDLE hMapFile;
STATUS_DATA* pBuf;
hMapFile = OpenFileMapping(
FILE_MAP_READ, // read access
FALSE, // do not inherit the name
szName); // name of mapping object
if (hMapFile == NULL)
{
_tprintf(TEXT("Could not open file mapping object (%d).\n"),
GetLastError());
return -2;
}
pBuf = (STATUS_DATA*) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
if (pBuf == NULL)
{
_tprintf(TEXT("Could not map view of file (%d).\n"),
GetLastError());
CloseHandle(hMapFile);
return -1;
}
output->ReadyForConnect = pBuf->engine[0].ReadyForConnect;
memcpy(output->VizVersionStr, pBuf->engine[0].VizVersionStr, sizeof(pBuf->engine[0].VizVersionStr));
memcpy(output->NameOfFile, pBuf->engine[0].NameOfFile, sizeof(pBuf->engine[0].NameOfFile));
CloseHandle(hMapFile);
UnmapViewOfFile(pBuf);
return 0;
}
Now I'm getting an empty output struct and the return value ist not 0 as intended. It is rather a changing number with seven digits, which leaves me puzzled... Have I messed up in the dll? If I make the unmanaged code executable and debug it, I can see, that output is being filled with the appropriate values.
Make sure your ReadyForConnect field is not filled up to 4 bytes. In my project it turned out all short int (2 bytes) fields were filled with dummy bytes to 4 bytes in unmanaged DLL. If that's the issue, you should marshall the struct this way:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_OUTPUT
{
[MarshalAs(UnmanagedType.I2)]
UInt16 ReadyForConnect;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I1, SizeConst=2)]
byte[] aligment; // 2 byte aligment up to 4 bytes margin
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
String VersionStr;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
String NameOfFile; // ...
}
If the strings are ANSI null terminated strings you can annotate them as:
[MarshalAs(UnmanagedType.LPStr)] public String VersionStr;
When returning information in a struct the standard method is to pass a pointer to a struct as a parameter of the method. The method fills in the struct members and then returns a status code (or boolean) of some kind. So you probably want to change your C++ method to take a SYSTEM_OUTPUT* and return 0 for success or some error code:
public partial class Form1 : Form
{
public SYSTEM_OUTPUT output;
[DllImport("testeshm.dll", EntryPoint="getStatus")]
public extern static int getStatus(out SYSTEM_OUTPUT output);
public Form1()
{
InitializeComponent();
}
private void ReadSharedMem_Click(object sender, EventArgs e)
{
try
{
if(getStatus(out output) != 0)
{
//Do something about error.
}
}
catch (AccessViolationException ave)
{
label1.Text = ave.Message;
}
}
}
You aren't actually marshaling any data over to the managed side. When you declare output on the managed side, it's default value is null. Then, on the unmanaged side, you never allocate any memory for output. You should allocate some unmanaged memory, pass the pointer to that memory to your dll function, then marshal the pointer for that memory to your struct:
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct SYSTEM_OUTPUT
{
UInt16 ReadyForConnect;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
String VersionStr;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
String NameOfFile;
// actually more of those
}
public partial class Form1 : Form
{
public SYSTEM_OUTPUT output;
[DllImport("testeshm.dll", EntryPoint="getStatus")]
public extern static int getStatus(IntPtr output);
public Form1()
{
InitializeComponent();
}
private void ReadSharedMem_Click(object sender, EventArgs e)
{
IntPtr ptr;
try
{
ptr = Marshall.AllocHGlobal(Marshall.SizeOf(typeof(SYSTEM_OUTPUT)));
int ret = getStatus(ptr);
if(ret == 0)
{
output = (SYSTEM_OUTPUT)Marshal.PtrToStructure(ptr, typeof(SYSTEM_OUTPUT));
}
//do something with output
label1.Text = ret;
}
catch (AccessViolationException ave)
{
label1.Text = ave.Message;
}
finally
{
Marshal.FreeHGlobal(ptr); //make sure to free the memory
}
}
}
Edit:
Your problem could be an issue with the difference between packing strategies. I've updated the struct definition.
EDIT: I am rewriting this whole answer.
I took all of both your C++ and C# code, dropped it into a solution and ran it -- and everything works for me. I didn't have your specific memory mapping stuff so I simulated it by filling pBuf with some fake data, and everything makes it back fine; both the return value and the output struct are correct.
Could something be amiss with your project settings? This sounds silly, but you mentioned running and debugging the unamnaged code; you are building a dll right?
What are you trying to do is possible, but I think you are solving the wrong problem.
Why not read the memory mapped file direct from C#?
Take a look at Winterdom.IO.FileMap
I have used it and it works fine.
MemoryMappedFile file = MemoryMappedFile.Open(FileMapRead, name);
using (Stream stream = memoryMappedFile.MapView(MapAccess.FileMapAllAccess, 0, length))
{
// here read the information that you need
}
With that you are not finished - you still have to convert a byte buffer to a struct, but you are all on the managed side and it will be easier.
who allocated the memory for the structure? You cannot delete native memory from the managed heap. Generally speaking the native DLL should allocate on the COM heap if it expect the caller to free the memory, or return a callback interface like IMalloc to free the returning memory. That means you need to receive the result memory address as IntPtr and use System.Runtime.InteropServices.Marshal to copy data from native to managed heap (may be to a structure) before freeing the memory.
Edit for the updated function signature:
use public static extern int getStatus(ref SYSTEM_OUTPUT output); You are not allocating on the COM heap in the native function, so out is unnecessary.
Have you considered adding a C++/CLI assembly to your project? That's an extremely easy and powerful way to bridge the gap between managed and unmanaged code. I use it quite a lot myself.