У меня есть очень простой делегат 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)]
не помогают.
Глупые гипотезы:
нельзя назвать функции ввода/вывода из C# обратного вызова.
В
статического CallBackType обратного вызова = новый CallBackType (CallBack);
объект назначения перемещается GC, так как он не знает, используется ли он неуправляемым кодом C. В конце концов, код C просто копирует ссылку на этот объект ...
Спасибо, второе решение работает прекрасные (для> 30000 обратных вызовов), и я буду тестировать первый позже. Правильно ли я понимаю, что C# (а не C) хочет/нуждается/должен очистить стек? Действительно, в моем коде C# выше extern C-функции должны быть дополнительно украшены (в [DllImport]) с CallingConvention = CallingConvention.Cdecl? Действительно, в этом случае (Cdecl) очистка стека выполняется вызывающим абонентом (т. Е. C#), а функция обратного вызова (в C) оформляется __stdcall (как вы предполагали), то есть очистка стека формируется вызываемой функцией (т.е. CallBack в C#). – user2770141
Не могли бы вы предложить любые справочные данные обо всем этом бизнесе конвенций с управляемыми/неуправляемыми вызовами. Я читал ~ 100 веб-страниц, объясняющих это, но все это выглядит как алхимия. – user2770141
Вам просто нужно иметь матч. Значение по умолчанию в C# pinvoke - __stdcall, значение по умолчанию в C - __cdecl, поэтому кто-то должен изменить свое объявление. Вам решать, какой именно. Вы найдете больше алхимии в [этом ответе] (http: // stackoverflow.com/a/15664100/17034). –