Question
I'm porting a C application into C#. The C app calls lots of functions from a 3rd-party DLL, so I wrote P/Invoke wrappers for these functions in C#. Some of these C functions allocate data which I have to use in the C# app, so I used IntPtr's, Marshal.PtrToStructure and Marshal.Copy to copy the native data (arrays and structures) into managed variables.
Unfortunately, the C# app proved to be much slower than the C version. A quick performance analysis showed that the above mentioned marshaling-based data copying is the bottleneck. I'm considering to speed up the C# code by rewriting it to use pointers instead. Since I don't have experience with unsafe code and pointers in C#, I need expert opinion regarding the following questions:
- What are the drawbacks of using
unsafecode and pointers instead ofIntPtrandMarshaling? For example, is it more unsafe (pun intended) in any way? People seem to prefer marshaling, but I don't know why. - Is using pointers for P/Invoking really faster than using marshaling? How much speedup can be expected approximately? I couldn't find any benchmark tests for this.
Example code
To make the situation more clear, I hacked together a small example code (the real code is much more complex). I hope this example shows what I mean when I'm talking about "unsafe code and pointers" vs. "IntPtr and Marshal".
C library (DLL)
MyLib.h
#ifndef _MY_LIB_H_ #define _MY_LIB_H_ struct MyData { int length; unsigned char* bytes; }; __declspec(dllexport) void CreateMyData(struct MyData** myData, int length); __declspec(dllexport) void DestroyMyData(struct MyData* myData); #endif // _MY_LIB_H_ MyLib.c
#include <stdlib.h> #include "MyLib.h" void CreateMyData(struct MyData** myData, int length) { int i; *myData = (struct MyData*)malloc(sizeof(struct MyData)); if (*myData != NULL) { (*myData)->length = length; (*myData)->bytes = (unsigned char*)malloc(length * sizeof(char)); if ((*myData)->bytes != NULL) for (i = 0; i < length; ++i) (*myData)->bytes[i] = (unsigned char)(i % 256); } } void DestroyMyData(struct MyData* myData) { if (myData != NULL) { if (myData->bytes != NULL) free(myData->bytes); free(myData); } } C application
Main.c
#include <stdio.h> #include "MyLib.h" void main() { struct MyData* myData = NULL; int length = 100 * 1024 * 1024; printf("=== C++ test ===\n"); CreateMyData(&myData, length); if (myData != NULL) { printf("Length: %d\n", myData->length); if (myData->bytes != NULL) printf("First: %d, last: %d\n", myData->bytes[0], myData->bytes[myData->length - 1]); else printf("myData->bytes is NULL"); } else printf("myData is NULL\n"); DestroyMyData(myData); getchar(); } C# application, which uses IntPtr and Marshal
Program.cs
using System; using System.Runtime.InteropServices; public static class Program { [StructLayout(LayoutKind.Sequential)] private struct MyData { public int Length; public IntPtr Bytes; } [DllImport("MyLib.dll")] private static extern void CreateMyData(out IntPtr myData, int length); [DllImport("MyLib.dll")] private static extern void DestroyMyData(IntPtr myData); public static void Main() { Console.WriteLine("=== C# test, using IntPtr and Marshal ==="); int length = 100 * 1024 * 1024; IntPtr myData1; CreateMyData(out myData1, length); if (myData1 != IntPtr.Zero) { MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData)); Console.WriteLine("Length: {0}", myData2.Length); if (myData2.Bytes != IntPtr.Zero) { byte[] bytes = new byte[myData2.Length]; Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length); Console.WriteLine("First: {0}, last: {1}", bytes[0], bytes[myData2.Length - 1]); } else Console.WriteLine("myData.Bytes is IntPtr.Zero"); } else Console.WriteLine("myData is IntPtr.Zero"); DestroyMyData(myData1); Console.ReadKey(true); } } C# application, which uses unsafe code and pointers
Program.cs
using System; using System.Runtime.InteropServices; public static class Program { [StructLayout(LayoutKind.Sequential)] private unsafe struct MyData { public int Length; public byte* Bytes; } [DllImport("MyLib.dll")] private unsafe static extern void CreateMyData(out MyData* myData, int length); [DllImport("MyLib.dll")] private unsafe static extern void DestroyMyData(MyData* myData); public unsafe static void Main() { Console.WriteLine("=== C# test, using unsafe code ==="); int length = 100 * 1024 * 1024; MyData* myData; CreateMyData(out myData, length); if (myData != null) { Console.WriteLine("Length: {0}", myData->Length); if (myData->Bytes != null) Console.WriteLine("First: {0}, last: {1}", myData->Bytes[0], myData->Bytes[myData->Length - 1]); else Console.WriteLine("myData.Bytes is null"); } else Console.WriteLine("myData is null"); DestroyMyData(myData); Console.ReadKey(true); } }