(продолжение статьи)
Как показало дальнейшее. Имеется трудноразрешимая проблема. Если в старых программах на С++ все работало на ура, то при использовании из .NET вылезают проблемы. Когда мы получаем и отправляем редкие, одиночные пакеты — все хорошо, но стоит появиться большому потоку данных, то про вызове нашего делегата, в конце концов происходит нарушение защиты. Я пробовал перевести все используемые функции и переменные в static, чтобы их не трогал сборщик мусора. Становилось лучше, но через несколько минут работы все равно происходило нарушение защиты. Так как у меня старый и уже не поддерживаемый прибор, я связался с предыдущим местом работы (там у нас был еще один PCI вариант, который до сих пор поддерживается производителем. Перекомпилил все с использованием последней версии библиотеки. И все равно проблема осталась. Почитав документацию к последней версии, я увидел, что они не рекомендуют больше использовать callback функции. А рекомендуют использовать CiWaitEvent (который под виндами использует WaitForSingleObject).
Видимо, как я думаю, тоже столкнулись с этой проблемой и уперлись в какую-то неразрешимую заковыку. (ну это мои домыслы). В исходниках консольной canmon что идет с библиотекой, так же уже все поменяно с каллбаков на _beginthreadex и собственный поток обработчик использующий CiWaitEvent. В общем, упразднил я таковые из импорта и сделал свои обработчики (код слегка урезан, чтоб не загромождать текст и без того большими кусками кода):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
public delegate void ci_handler(Byte channel, Chai.Events ev); // прототип обработчика public class Chai { // channels adapter public class Channel { public ci_handler OnRcv = null; public ci_handler OnTrmt = null; public ci_handler OnErr = null; private System.Threading.Thread chEventThread; private bool thExit = false; public Byte chNo; public Byte flags; public int boardIndex; public canboard_t board { get{ return boards[boardIndex]; } } private void chEvent() {// Обработчик событий драйвера(отдельный поток) int ret = 0; int ret1 = 0; canwait_t cw = new canwait_t(); canerrs_t errs = new canerrs_t(); cw.chan = chNo; cw.wflags = CI_WAIT_RC | CI_WAIT_TR | CI_WAIT_ER; while(!thExit) { ret = CiWaitEvent(ref cw, 1, 200); if (ret < 0) { // error occured } else if (ret > 0) { if (((cw.rflags & CI_WAIT_RC) != 0) && (OnRcv != null)) OnRcv(chNo, Chai.Events.CIEV_RC); if (((cw.rflags & CI_WAIT_TR) != 0) && (OnTrmt != null)) OnTrmt(chNo, Chai.Events.CIEV_TR); if (((cw.rflags & CI_WAIT_ER) != 0) && (OnErr != null)) { ret1 = CiErrsGetClear(chNo, ref errs); if (ret1 > 0 ) { if (errs.boff > 0) OnErr(chNo, Events.CIEV_BOFF); if (errs.ewl > 0) OnErr(chNo, Events.CIEV_EWL); if (errs.hwovr > 0) OnErr(chNo, Events.CIEV_HOVR); if (errs.swovr > 0) OnErr(chNo, Events.CIEV_SOVR); if (errs.wtout > 0) OnErr(chNo, Events.CIEV_WTOUT); } } }// else timeout } } public Channel(Byte chan, Byte aFlags) { chNo = chan; flags = aFlags; boardIndex = -1; } public Int16 Open() { thExit = false; chEventThread = new System.Threading.Thread(new System.Threading.ThreadStart(chEvent)); return CiOpen(chNo, flags); } public Int16 Close() { thExit = true; return CiClose(chNo); } public Int16 Start() { if (chEventThread.ThreadState == System.Threading.ThreadState.Unstarted) chEventThread.Start(); else chEventThread.Resume(); return CiStart(chNo); } public Int16 Stop() { chEventThread.Suspend(); return CiStop(chNo); } } [DllImport("chai.dll", CallingConvention = CallingConvention.Cdecl)] //cwCount must be 1 unsafe public static extern Int16 CiWaitEvent(ref canwait_t cw, int cwcount, int tout); // тут дальше остальные импорты, как в повествовании выше [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct canwait_t{ public byte chan; public byte wflags; public byte rflags; }; // тут дальше остальные структуры, как в повествовании выше public enum Events { CIEV_RC = 1, // Receive CIEV_TR = 2, // Transmit CIEV_CANERR = 6, // Can Error CIEV_EWL = 3, // Error Warning Limit CIEV_BOFF = 4, // Bus OFF CIEV_HOVR = 5, // HW Overrun CIEV_WTOUT = 7, // Write Timeout CIEV_SOVR = 8 // SW Overrun } public static List<Channel> channels; public static List<canboard_t> boards; public static void Init() { channels = new List<Channel>(); int ret = CiInit(); boards = new List<canboard_t>(); for( int brd_i=0; brd_i < CI_BRD_NUMS; brd_i++ ) { canboard_t brd = new canboard_t(); brd.brdnum = (byte)brd_i; if (0 <= CiBoardInfo(ref brd) ) { boards.Add(brd); int brd_idx = boards.Count - 1; for (int i = 0; i < 4; i++) { if (brd.chip[i] >= 0) { Channel item = new Channel((Byte)brd.chip[i], CIO_CAN11 | CIO_CAN29); item.boardIndex = brd_idx; channels.Add(item); } } } } } public static void Dispose() { foreach (Channel ch in channels) ch.Close(); } } |
Ну и вот так, это использовать (исключительно тестировал, не падает ли при большом количестве входящих сообщений):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public static void OnRcv(Byte channel, Chai.Events ev) { if (ev != Chai.Events.CIEV_RC) return; int cnt = Chai.CiRcGetCnt(channel); Chai.canmsg_t[] msg = new Chai.canmsg_t[cnt]; int cnt1 = Chai.CiRead(channel, msg, (short)cnt); for (int i=0; i < cnt1; i++) { staticThis().logCanMsg(channel, "rx:", ref msg[i]); cnt--; } } public static void OnErr(Byte channel, Chai.Events ev) { String s = channel.ToString() + ":" + Chai.EventsToString(ev); staticThis().log(s); } private void button1_Click(object sender, EventArgs e) { Chai.Init(); Chai.channels[0].OnRcv += OnRcv; Chai.channels[0].OnErr += OnErr; Chai.channels[0].Open(); Chai.channels[0].Start(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { Chai.Dispose(); } public static Form1 staticThis() { return (Form1)Application.OpenForms["Form1"]; } |
Без Chai.Dispose() по закрытии программы, поток обработчика не прекращается. Так что не надо забывать освобождать этот поток, когда обработчик больше не нужен.
Кроме того, часть функций, статическая. А статические функции не имеют доступа к динамическим полям формы. Так как в нашем случае форма одна единственная, и других экземпляров этого класса не предвидится, мы можем сделать не совсем корректную вещь, и получать к ним доступ обращаясь к экземпляру класса по имени. Что и делает функция staticThis().
В button1_Click() мы инициализируем класс интерфейс библиотеки. При этом сканируется обороудование и заполняется два списка borаds и channels. Первый исключительно информационный, и нужен больше для интерфейса. Второй же это список доступных каналов, на всем подключенном CHAI-совместимом оборудовании. (а вдруг мы несколько устройств подключили, а ди на одном интерфейсе может быть несколько каналов).
У каждого канала есть свой собственный набор обработчиков .OnRcv(), .OnTrmt() , .OnErr() которым может быть присвоена пользовательская функция. Метод .Open() канала открывает канал и создает поток обрабочика сообщений. А метод .Start(); запускает канал и так же запускает поток обработчика сообщений.
Большое человечкоское спасибо! Был затык и в этой статье нашел ответ!
Добрый день. Есть вопросы по использованию вашего приложения. Напишете пожалуйста на почту. (Не нашел я регистрацию, а без нее «НАША ПОЧТА» не работает)
Добрый день, Сергей. Регистрация у нас закрыта. Раздел «наша почта» не содержит наши контакты — это список доменов, на которых у нас доступны личные почтовые ящики.
Ваши вопросы вы можете задать в комментариях. Правда, не могу гарантировать, что автор быстро ответит.
Ну, автор периодически заглядывает. А с пинком животворящим от nornad, так еще и относительно оперативно 😉