Determining DLLImport arguments and safely calling an unmanaged C function - c#

As a follow-up to my previous question, I finally got the C dll exported and usable in C#, but I'm stuck trying to figure out the proper argument types and calling method.
I've researched here on SO but there doesn't seem to be a pattern to how variable types are assigned.
I see some people suggest a StringBuilder for uchar*, others a byte[], some references to 'unsafe' code, etc. Can anyone recommend a solution based on this specific use-case?
Also note the exception generated as the code stands now, right after the call to the C function.
C function import:
[DllImport("LZFuncs.dll")]
internal static extern long LZDecomp(ref IntPtr outputBuffer, byte[] compressedBuffer, UInt32 compBufferLength); //Originally two uchar*, return is size of uncompressed data.
C function signature:
long LZDecomp(unsigned char *OutputBuffer, unsigned char *CompressedBuffer, unsigned long CompBufferLength)
Used as below:
for (int dataNum = 0; dataNum < _numEntries; dataNum++)
{
br.BaseStream.Position = _dataSizes[dataNum]; //Return to start of data.
if (_compressedFlags[dataNum] == 1)
{
_uncompressedSize = br.ReadInt32();
byte[] compData = br.ReadBytes(_dataSizes[dataNum] - 4);
IntPtr outData = IntPtr.Zero;
LZFuncs.LZDecomp(ref outData, compData, Convert.ToUInt32(compData.Length));
var uncompData = new byte[_uncompressedSize]; //System.ExecutionEngineException was unhandled
Marshal.Copy(outData, uncompData, 0, Convert.ToInt32(_uncompressedSize));
BinaryWriter bw = new BinaryWriter(new FileStream("compData" + dataNum + ".txt", FileMode.CreateNew));
bw.Write(uncompData);
bw.Close();
}
else
{
BinaryWriter bw = new BinaryWriter(new FileStream("uncompData" + dataNum + ".txt", FileMode.CreateNew));
bw.Write(br.ReadBytes(_dataSizes[dataNum]));
bw.Close();
}
}
I assume the C code is clobbering the memory pretty severely if it's breaking the C# caller with a CLR exception like that, but due to how the C code is written, there's absolutely no way to modify it without breaking the functionality, it's effectively a black box. (Written in assembly, for the most part.)
For reference, just a few questions I've read over in an effort to solve this myself:
How do I return a byte array from C++ to C#
Correct way to marshall uchar[] from native dll to byte[] in c#
There have been others but those are the most recent.

OK, that's not too hard to work with. The two buffer parameters are byte arrays. You should declare them as byte[]. The calling convention is Cdecl. Remember that C++ long is only 32 bits wide on Windows, so use C# int rather than C# long since the latter is 64 bits wide.
Declare the function like this:
[DllImport("LZFuncs.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int LZDecomp(
[Out] byte[] outputBuffer,
[In] byte[] compressedBuffer,
uint compBufferLength
);
You are decompressing compressedBuffer into outputBuffer. You'll need to know how large outputBuffer needs to be (the code in the question shows that you already handle this) and allocate a sufficiently large array. Beyond that I think it's obvious how to call this.
The calling code will this look like this:
_uncompressedSize = br.ReadInt32();
byte[] compData = br.ReadBytes(_dataSizes[dataNum] - 4);
byte[] outData = new byte[_uncompressedSize];
int len = LZFuncs.LZDecomp(outData, compData, (uint)compData.Length);

This is an old question but a real issue and can lead to serious security issues so I thought I give it my 2 cents
Whenever I use [DllImport] I always add the location you consider safe, one option is to specify is for windows DLL's
[DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
However, have a look at your options to have it match your needs, you might load a private DLL that is located elsewhere.

Related

Passing A Byte[] Segment From C# To C++ DLL Without Creating a Copy

I need to slice a byte[] and pass the sliced section to a C# DLL for processing. I am avoiding Array.Copy, because I am trying not to copy anything to hinder performance. I have been made aware of the ArraySegment class as well as Span and Memory. The confusion I am having is how to actually pass these to the DLL, as I am passing an UnmanagedType.LPArray like so:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int ProcessVideoFrame_t([MarshalAs(UnmanagedType.LPArray)] byte[] bytes, uint len);
Is there any way to get an underlying byte[] from these classes, or somehow pass the segment without making a copy?
My current code is:
byte[] bytes = new byte[packet.Payload.Length - headerSize];
Array.Copy(packet.Payload, headerSize, bytes, 0, bytes.Length);
helper.ProcessVideoFrame(CookieHelperDictionary[packet.Header.Cookie], bytes, (uint)bytes.Length);```
unsafe
{
fixed (byte* p = &packet.Payload[headerSize])
{
helper.ProcessVideoFrame(CookieHelperDictionary[packet.Header.Cookie], (IntPtr)p, (uint)(packet.Payload.Length - headerSize));
}
}
I also changed the function definition to this:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int ProcessVideoFrame_t(IntPtr rdh, IntPtr buffer, uint len);

How can you Marshal a byte array in C#?

I'm trying to call the following C++ function that's wrapped up into a DLL:
unsigned char * rectifyImage(unsigned char *pimg, int rows, int cols)
My import statement looks like the following:
[DllImport("mex_rectify_image.dll")]
unsafe public static extern IntPtr rectifyImage(
byte[] data, int rows, int columns);
And my call routine looks like the following:
byte[] imageData = new byte[img.Height * img.Width * 3];
// ... populate imageData
IntPtr rectifiedImagePtr = rectifyImage(imageData, img.Height, img.Width);
Byte[] rectifiedImage = new Byte[img.Width * img.Height * 3];
Marshal.Copy(rectifiedImagePtr, rectifiedImage, 0, 3 * img.Width * img.Height);
However, I keep getting a runtime error:
A first chance exception of type System.AccessViolationException occurred in xxx.dll
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I'm just wondering if the fault lies in the way I'm marshaling my data or in my imported DLL file... anyone have any ideas?
This is likely occurring because the calling convention of the method is not as the marshaller is guessing it to be. You can specify the convention in the DllImport attribute.
You don't need the 'unsafe' keyword in the C# declaration here as it's not 'unsafe' code. Perhaps you were trying it with a 'fixed' pointer at one point and forgot to remove the unsafe keyword before posting?
not sure if this is your problem, but in general C++ pointers map to IntPtr. so try modifying your import statement to this:
[DllImport("mex_rectify_image.dll")]
unsafe public static extern IntPtr rectifyImage(
IntPtr pData, int rows, int columns);
rectifyImage Is looking for a ponter to a the 1st byte in a block for data you are sending in the block. Try imageData[0]

Object to Network serialization - with an existing protocol

I'm writing a client for a server program written in C++. As is not unusual, all the networking protocol is in a format where packets can be easily memcopied into/out of a C++ structure (1 byte packet code, then different arrangements per packet type).
I could do the same thing in C#, but is there an easier way, especially considering lots of the data is fixed-length char arrays that I want to play with as strings? Or should I just suck it up and convert types as needed? I've looked at using the ISerializable interface, but it doesnt look as low level as is required.
I wrote a post on this in 2004 which covers some of the options available for converting a binary stream to a .NET memory structure. I reposted it on my new blog since the old blog site no longer exists.
http://taylorza.blogspot.com/2010/04/archive-structure-from-binary-data.html
Basically you have three options
Use C++ style memory pointers in C# which requires the /unsafe switch
Use the .NET marshaling to allocate an unmanaged block of memory, copy the bytes to the unmanaged memory and then use Marshal.PtrToStructure to marshal the data back to the managed heap mapping it into your structure.
Use a BinaryReader to manualy read the byte stream and pack the data into the structure. Personally, this was my preferred option.
When you consider the options you should also take into account how the byte ordering might affect you.
As an example I will use the IP header as an example, since at the time of the post I was working with Raw TCP packets.
You need to define your .NET structure that the binary data will be mapped to. For example the IP Header looks like the following.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct IpHeader
{
public byte VerLen;
public byte TOS;
public short TotalLength;
public short ID;
public short Offset;
public byte TTL;
public byte Protocol;
public short Checksum;
public int SrcAddr;
public int DestAddr;
}
Note that the StructLayout attribute is only required for first two options, and of course you will need to set the packing as appropriate for the structure that is being serialized from the server.
So in C/C++, given a pointer to a block of memory that contains the data bytes that map to the C/C++ structure you can use the following bit of code to view the block of data as a structure piece of memory, where packet is a byte* to the memory.
IpHeader *pHeader = (IpHeader*)packet;
Doing the same is C# using /unsafe option and the struct defined above you count use the following code.
IpHeader iphdr;
unsafe
{
fixed ( byte *pData = packet)
{
iphdr = *(IpHeader*)pData;
}
}
//Use iphdr...
The marshaling option would look like the following
IntPtr pIP = Marshal.AllocHGlobal( len );
Marshal.Copy( packet, 0, pIP, len );
iphdr = (IpHeader)Marshal.PtrToStructure( pIP, typeof(IpHeader) );
Marshal.FreeHGlobal( pIP );
And finally you can use the BinaryReader to do this entirely in managed code.
MemoryStream stm = new MemoryStream( packet, 0, len );
BinaryReader rdr = new BinaryReader( stm );
iphdr.VerLen = rdr.ReadByte();
iphdr.TOS = rdr.ReadByte();
iphdr.TotalLength = rdr.ReadInt16();
iphdr.ID = rdr.ReadInt16();
iphdr.Offset = rdr.ReadInt16();
iphdr.TTL = rdr.ReadByte();
iphdr.Protocol = rdr.ReadByte();
iphdr.Checksum = rdr.ReadInt16();
iphdr.SrcAddr = rdr.ReadInt32();
iphdr.DestAddr = rdr.ReadInt32();
As I mentioned earlier, you might need to consider byte ordering. For example, the above code is not quite correct because the IpHeader does not use the same byte order as is assumed by ReadInt16. ReadInt32 etc. Resolving the issue with the above solution is as simple as using IPAddress.NetworkToHostOrder.
iphdr.VerLen = rdr.ReadByte();
iphdr.TOS = rdr.ReadByte();
iphdr.TotalLength = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.ID = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.Offset = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.TTL = rdr.ReadByte();
iphdr.Protocol = rdr.ReadByte();
iphdr.Checksum = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.SrcAddr = IPAddress.NetworkToHostOrder(rdr.ReadInt32());
iphdr.DestAddr = IPAddress.NetworkToHostOrder(rdr.ReadInt32());

Is there a BinaryReader in C++ to read data written from a BinaryWriter in C#?

I've written several ints, char[]s and the such to a data file with BinaryWriter in C#. Reading the file back in (in C#) with BinaryReader, I can recreate all of the pieces of the file perfectly.
However, attempting to read them back in with C++ yields some scary results. I was using fstream to attempt to read back the data and the data was not reading in correctly. In C++, I set up an fstream with ios::in|ios::binary|ios::ate and used seekg to target my location. I then read the next four bytes, which were written as the integer "16" (and reads correctly into C#). This reads as 1244780 in C++ (not the memory address, I checked). Why would this be? Is there an equivalent to BinaryReader in C++? I noticed it mentioned on msdn, but that's Visual C++ and intellisense doesn't even look like c++, to me.
Example code for writing the file (C#):
public static void OpenFile(string filename)
{
fs = new FileStream(filename, FileMode.Create);
w = new BinaryWriter(fs);
}
public static void WriteHeader()
{
w.Write('A');
w.Write('B');
}
public static byte[] RawSerialize(object structure)
{
Int32 size = Marshal.SizeOf(structure);
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(structure, buffer, true);
byte[] data = new byte[size];
Marshal.Copy(buffer, data, 0, size);
Marshal.FreeHGlobal(buffer);
return data;
}
public static void WriteToFile(Structures.SomeData data)
{
byte[] buffer = Serializer.RawSerialize(data);
w.Write(buffer);
}
I'm not sure how I could show you the data file.
Example of reading the data back (C#):
BinaryReader reader = new BinaryReader(new FileStream("C://chris.dat", FileMode.Open));
char[] a = new char[2];
a = reader.ReadChars(2);
Int32 numberoffiles;
numberoffiles = reader.ReadInt32();
Console.Write("Reading: ");
Console.WriteLine(a);
Console.Write("NumberOfFiles: ");
Console.WriteLine(numberoffiles);
This I want to perform in c++. Initial attempt (fails at first integer):
fstream fin("C://datafile.dat", ios::in|ios::binary|ios::ate);
char *memblock = 0;
int size;
size = 0;
if (fin.is_open())
{
size = static_cast<int>(fin.tellg());
memblock = new char[static_cast<int>(size+1)];
memset(memblock, 0, static_cast<int>(size + 1));
fin.seekg(0, ios::beg);
fin.read(memblock, size);
fin.close();
if(!strncmp("AB", memblock, 2)){
printf("test. This works.");
}
fin.seekg(2); //read the stream starting from after the second byte.
int i;
fin >> i;
Edit: It seems that no matter what location I use "seekg" to, I receive the exact same value.
You realize that a char is 16 bits in C# rather than the 8 it usually is in C. This is because a char in C# is designed to handle Unicode text rather than raw data. Therefore, writing chars using the BinaryWriter will result in Unicode being written rather than raw bytes.
This may have lead you to calculate the offset of the integer incorrectly. I recommend you take a look at the file in a hex editor, and if you cannot work out the issue post the file and the code here.
EDIT1
Regarding your C++ code, do not use the >> operator to read from a binary stream. Use read() with the address of the int that you want to read to.
int i;
fin.read((char*)&i, sizeof(int));
EDIT2
Reading from a closed stream is also going to result in undefined behavior. You cannot call fin.close() and then still expect to be able to read from it.
This may or may not be related to the problem, but...
When you create the BinaryWriter, it defaults to writing chars in UTF-8. This means that some of them may be longer than one byte, throwing off your seeks.
You can avoid this by using the 2 argument constructor to specify the encoding. An instance of System.Text.ASCIIEncoding would be the same as what C/C++ use by default.
There are many thing going wrong in your C++ snippet. You shouldn't mix binary reading with formatted reading:
// The file is closed after this line. It is WRONG to read from a closed file.
fin.close();
if(!strncmp("AB", memblock, 2)){
printf("test. This works.");
}
fin.seekg(2); // You are moving the "get pointer" of a closed file
int i;
// Even if the file is opened, you should not mix formatted reading
// with binary reading. ">>" is just an operator for reading formatted data.
// In other words, it is for reading "text" and converting it to a
// variable of a specific data type.
fin >> i;
If it's any help, I went through how the BinaryWriter writes data here.
It's been a while but I'll quote it and hope it's accurate:
Int16 is written as 2 bytes and padded.
Int32 is written as Little Endian and zero padded
Floats are more complicated: it takes the float value and dereferences it, getting the memory address's contents which is a hexadecimal

How to get IntPtr from byte[] in C#

I want to pass a byte[] to a method takes a IntPtr Parameter in C#, is that possible and how?
Another way,
GCHandle pinnedArray = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
IntPtr pointer = pinnedArray.AddrOfPinnedObject();
// Do your stuff...
pinnedArray.Free();
The following should work but must be used within an unsafe context:
byte[] buffer = new byte[255];
fixed (byte* p = buffer)
{
IntPtr ptr = (IntPtr)p;
// Do your stuff here
}
Beware: you have to use the pointer within the fixed block. The GC can move the object once you are no longer within the fixed block.
Not sure about getting an IntPtr to an array, but you can copy the data for use with unmanaged code by using Mashal.Copy:
IntPtr unmanagedPointer = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
// Call unmanaged code
Marshal.FreeHGlobal(unmanagedPointer);
Alternatively you could declare a struct with one property and then use Marshal.PtrToStructure, but that would still require allocating unmanaged memory.
Edit: Also, as Tyalis pointed out, you can also use fixed if unsafe code is an option for you
You could use Marshal.UnsafeAddrOfPinnedArrayElement to get a memory pointer to the array (or to a specific element in the array). Keep in mind that the array must be pinned first as per the API documentation:
The array must be pinned using a GCHandle before it is passed to this method. For maximum performance, this method does not validate the array passed to it; this can result in unexpected behavior.
Here's a twist on #user65157's answer (+1 for that, BTW):
I created an IDisposable wrapper for the pinned object:
class AutoPinner : IDisposable
{
GCHandle _pinnedArray;
public AutoPinner(Object obj)
{
_pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned);
}
public static implicit operator IntPtr(AutoPinner ap)
{
return ap._pinnedArray.AddrOfPinnedObject();
}
public void Dispose()
{
_pinnedArray.Free();
}
}
then use it like thusly:
using (AutoPinner ap = new AutoPinner(MyManagedObject))
{
UnmanagedIntPtr = ap; // Use the operator to retrieve the IntPtr
//do your stuff
}
I found this to be a nice way of not forgetting to call Free() :)
Marshal.Copy works but is rather slow. Faster is to copy the bytes in a for loop. Even faster is to cast the byte array to a ulong array, copy as much ulong as fits in the byte array, then copy the possible remaining 7 bytes (the trail that is not 8 bytes aligned). Fastest is to pin the byte array in a fixed statement as proposed above in Tyalis' answer.
In some cases you can use an Int32 type (or Int64) in case of the IntPtr. If you can, another useful class is BitConverter. For what you want you could use BitConverter.ToInt32 for example.

Categories

Resources