Question

I have a very straightforward C# delegate passed as a callback to an unmanaged .DLL written in C, which inexplicably crashes after a few hundred iterations (callbacks). C# first communicates the callback to C and then calls an infinite C-loop, which calls the callback every second.

using System;
using System.Runtime.InteropServices;

class tst {
    public const String DLL_NAME = @"dll-tst3.dll";
    public delegate void CallBackType(Int32 fst, Int32 snd);

    [DllImport(DLL_NAME)]
    public static extern void SetCallback(CallBackType cb);

    [DllImport(DLL_NAME)]
    public static extern void UnmanagedInfiniteLoop();

    static UInt32 nCallbackCalls = 0;

    public static void CallBack(Int32 fst, Int32 snd) {
        Console.WriteLine("nCallbacks={0}, fst={1}, snd={2}",
               ++nCallbackCalls, fst, snd);
        DateTime dt = DateTime.Now;
        String logLine = String.Format("{0}: {1}, {2}", 
               dt.ToString("yyyy-MM-dd HH:mm:ss.fff"), fst, snd);
        Console.WriteLine("{0}", logLine);
        GC.KeepAlive(callback); // prevent garbage collection
    }

    static CallBackType callback = new CallBackType(CallBack);

    static void Main(string[] args) {
        SetCallback(callback); // register callback in C
        UnmanagedInfiniteLoop(); // C code calling callback indefinitely
    }
}

My C code is this:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

typedef void(__cdecl *callback_t) (int fst, int snd);

callback_t callback;

extern "C" //Every method defined in this block is not mangled 
{
__declspec(dllexport) void __cdecl SetCallback (callback_t cb) {
    callback = cb;
}

__declspec(dllexport) void __cdecl UnmanagedInfiniteLoop () {
    int a = 0;
    int b = 1;
    for (;;) {
        Sleep(1000);
        callback(a++, b++); // C# callback
    }
}
} //End 'extern "C"' to prevent name mangling

It fails, always around after 550 callbacks, with "A problem caused the program to stop working correctly. Windows will close the program and notify you if a solution is available."

nCallbacks=550, fst=549, snd=550
2013-11-24 00:12:34.509: 549, 550
nCallbacks=551, fst=550, snd=551
2013-11-24 00:12:35.510: 550, 551
nCallbacks=552, fst=551, snd=552
2013-11-24 00:12:36.511: 551, 552

At this point the above "A problem..." error message pops up.

It crashes after a fewer callbacks, if the C# CallBack does something more substantial, like writing (appending) logLine to a stream.

Note that additional decorations

[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]

do not help.

Stupid hypotheses:

  1. One cannot call I/O functions from a C# callback.

  2. In

    static CallBackType callback = new CallBackType(CallBack);

the destination object is being moved around by GC, since it has no knowledge whether it is being used by the unmanaged C code. After all, the C code just copies the reference to this object...

Était-ce utile?

La solution

  typedef void(__cdecl *callback_t) (int fst, int snd);

You have a mismatch on the calling convention on the callback. This will imbalance the stack and will quickly crash your program when it underflows. Fix:

  [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  public delegate void CallBackType(Int32 fst, Int32 snd);

or on the C side:

  typedef void (__stdcall *callback_t)(int fst, int snd);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top