2013-11-23 3 views
0

У меня есть очень простой делегат C#, переданный как обратный вызов неуправляемого .DLL, написанного на C, который необъяснимо сбой после нескольких сотен итераций (обратных вызовов). C# сначала передает обратный вызов на C, а затем вызывает бесконечный C-loop, который вызывает обратный вызов каждую секунду.C# callback из C необъяснимо терпит неудачу после многих итераций

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 
    } 
} 

Мой C код это:

#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 

Это терпит неудачу, всегда вокруг после 550 обратных вызовов, с «Проблема вызвана программа прекращению работы Windows, будет закрыть программу и уведомить вас, если. доступно решение ».

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 

В этот момент появляется сообщение об ошибке «A problem ...».

Сбой после нескольких обратных вызовов, если C# CallBack делает что-то более существенное, например запись (добавление) logLine в поток.

Обратите внимание, что дополнительные украшения

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

не помогают.

Глупые гипотезы:

  1. нельзя назвать функции ввода/вывода из C# обратного вызова.

  2. В

    статического CallBackType обратного вызова = новый CallBackType (CallBack);

объект назначения перемещается GC, так как он не знает, используется ли он неуправляемым кодом C. В конце концов, код C просто копирует ссылку на этот объект ...

ответ

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

У вас есть несоответствие на вызовах на обратного вызова. Это приведет к дисбалансу стека и приведет к быстрому сбою вашей программы при ее переполнении. Fix:

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

или на стороне C:

typedef void (__stdcall *callback_t)(int fst, int snd); 
+0

Спасибо, второе решение работает прекрасные (для> 30000 обратных вызовов), и я буду тестировать первый позже. Правильно ли я понимаю, что C# (а не C) хочет/нуждается/должен очистить стек? Действительно, в моем коде C# выше extern C-функции должны быть дополнительно украшены (в [DllImport]) с CallingConvention = CallingConvention.Cdecl? Действительно, в этом случае (Cdecl) очистка стека выполняется вызывающим абонентом (т. Е. C#), а функция обратного вызова (в C) оформляется __stdcall (как вы предполагали), то есть очистка стека формируется вызываемой функцией (т.е. CallBack в C#). – user2770141

+0

Не могли бы вы предложить любые справочные данные обо всем этом бизнесе конвенций с управляемыми/неуправляемыми вызовами. Я читал ~ 100 веб-страниц, объясняющих это, но все это выглядит как алхимия. – user2770141

+0

Вам просто нужно иметь матч. Значение по умолчанию в C# pinvoke - __stdcall, значение по умолчанию в C - __cdecl, поэтому кто-то должен изменить свое объявление. Вам решать, какой именно. Вы найдете больше алхимии в [этом ответе] (http: // stackoverflow.com/a/15664100/17034). –

Смежные вопросы