views:

239

answers:

4

I have a C API with the signature:

int GetBuffer(char* buffer, int* maxSize)

In C, I will call it this way:

char buffer[4096];
int maxSize = 4096;
GetBuffer(buffer, &maxSize);

maxSize is set to the buffer size, and set with the actual size filled.

I need to call it from C#. How do I do that under "safe mode"?

Thanks

+2  A: 

Having a handle to the pointer doesn't fit the "safe mode" model at all. If the resource isn't managed by the Framework, it is unsafe.

Chris Ballance
+2  A: 

You need to use what is called P\Invoke, and generate a function declaration that to reference the C function in the Dll from C#.

However, you have to be very careful when passing buffers in/out of unmanaged code. The framework will take care of some things for you but you may need to ensure that memory that you pass into the unmanaged call doesn't get moved by the Garbage collector.

[DllImport("Kernel32.dll", SetLastError=true)]
static extern Int32 GetBuffer(byte[] buffer,ref Int32 maxSize);

And to use it:

byte[] myBuf = new myBuf[4096];
Int32 maxSize = myBuf.Length;

GetBuffer(myBuf, ref maxSize);
Miky Dinescu
+4  A: 

One option is simply to use C# pointer types - this requires unsafe block (or modifier on method/class), and compiling with /unsafe:

[DllImport(...)]
static extern int GetBuffer(byte* buffer, ref int maxSize);

Buffer can be allocated in several different ways. One would be to use a pinned heap array:

fixed (byte* buffer = new byte[4096])
{
    int maxSize = buffer.Length;
    GetBuffer(buffer, ref maxSize);
}

Another is to use stackalloc, though this is only feasible for small buffers:

byte* buffer = stackalloc byte[4096];
int maxSize = 4096;
GetBuffer(buffer, ref maxSize);

This particular approach is virtually identical to your C code in terms of performance and allocation patterns.

Another option altogether is to use marshaling for heap arrays, and avoid pointers entirely.

[DllImport(...)]
static extern int GetBuffer([Out] byte[] buffer, ref int maxSize);

byte[] buffer = new byte[4096];
int maxSize = buffer.Length;
GetBuffer(buffer, ref maxSize);
Pavel Minaev
You can also avoid the unsafe code by allocating your array then using a GCHandle to get a pointer to the first element (http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle.aspx).
plinth
`char` in C is *technically* `sbyte` in most cases, but normally you end up working with them as `byte`. I changed it to `byte` in the code above.
280Z28
Thanks, it was indeed my mistake (and I don't know of any platform on which you can get .NET, and C `char` woudn't match C# `byte`).
Pavel Minaev
+2  A: 

This should work without unsafe code.

extern int GetBuffer(IntPtr buffer, ref int bufSize);

// ...
byte[] buf = new byte[kBufSize];
GCHandle handle = GCHandle.Alloc(buf, GCHandleType.Pinned); // possibly expensive call 
IntPtr p = handle.AddrOfPinnedObject();
int size = buf.Length;
int ret = GetBuffer(p, ref size);
handle.Free();
plinth