At work we have a native C code responsible for reading and writing to a proprietary flat file database. I have a wrapper written in C# that encapsulates the P/Invoke calls into an OO model. The managed wrappers for the P/Invoke calls have grown in complexity considerably since the project was started. Anecdotally the current wrapper is doing fine, however, I'm thinking that I actually need to do more to ensure correct operation.
A couple of notes brought up by the answers:
- Probably don't need the KeepAlive
- Probably don't need the GCHandle pinning
- If you do use the GCHandle, try...finally that business (CER questions not addressed though)
Here is an example of the revised code:
[DllImport(@"somedll", EntryPoint="ADD", CharSet=CharSet.Ansi,
ThrowOnUnmappableChar=true, BestFitMapping=false,
SetLastError=false)]
[ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)]
internal static extern void ADD(
[In] ref Int32 id,
[In] [MarshalAs(UnmanagedType.LPStr)] string key,
[In] byte[] data, // formerly IntPtr
[In] [MarshalAs(UnmanagedType.LPArray, SizeConst=10)] Int32[] details,
[In] [MarshalAs(UnmanagedType.LPArray, SizeConst=2)] Int32[] status);
public void Add(FileId file, string key, TypedBuffer buffer)
{
// ...Arguments get checked
int[] status = new int[2] { 0, 0 };
int[] details = new int[10];
// ...Make the details array
lock (OPERATION_LOCK)
{
ADD(file.Id, key, buffer.GetBytes(), details, status);
// the byte[], details, and status should be auto
// pinned/keepalive'd
if ((status[0] != 0) || (status[1] != 0))
throw new OurDatabaseException(file, key, status);
// we no longer KeepAlive the data because it should be auto
// pinned we DO however KeepAlive our 'file' object since
// we're passing it the Id property which will not preserve
// a reference to 'file' the exception getting thrown
// kinda preserves it, but being explicit won't hurt us
GC.KeepAlive(file);
}
}
My (revised) questions are:
- Will data, details, and status be auto-pinned/KeepAlive'd?
- Have I missed anything else required for this to operate correctly?
EDIT: I recently found a diagram which is what sparked my curiosity. It basically states that once you call a P/Invoke method the GC can preempt your native code. So while the native call may be made synchronously, the GC could choose to run and move/remove my memory. I guess now I'm wondering if automatic pinning is sufficient (or if it even runs).