Bi-directional control across a C# Forms App and C++ DLL Interface
This video is the culmination of the preceding tutorials on Application Development. Bi-directional control is achieved across the C#/C++ DLL interface using message queues. Also discussed is the use of threads bringing life to our C++ DLL and removing all processing from the C# Forms App.
Source code:
Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Runtime.InteropServices; //Marshal.AllocHGlobal(255); namespace Cyprus { public partial class Form1 : Form { public Form1() { InitializeComponent(); fitPictureBox1(); Cyprus.init(pictureBox1.Width, pictureBox1.Height); timer1.Enabled = true; updateImage(); } private void fitPictureBox1() { //images can only have a width with interger mulitples of 4. int hPadding = 6; double _picWidth = Math.Floor((panel1.Width - hPadding) / (double)4) * 4; pictureBox1.Width = (int)_picWidth; //show user changes status.Text = "Panel Size: " + panel1.Width + " x " + panel1.Height; status.Text += ", bitMap size: " + _picWidth; status.Text += ", width Difference " + (panel1.Width - pictureBox1.Width - hPadding); } private void groupBox1_Paint(object sender, PaintEventArgs e) { GroupBox box = sender as GroupBox; DrawGroupBox(box, e.Graphics, Color.Lime, Color.Lime); } private void DrawGroupBox(GroupBox box, Graphics g, Color textColor, Color borderColor) { if (box != null) { Brush textBrush = new SolidBrush(textColor); Brush borderBrush = new SolidBrush(borderColor); Pen borderPen = new Pen(borderBrush); SizeF strSize = g.MeasureString(box.Text, box.Font); Rectangle rect = new Rectangle(box.ClientRectangle.X, box.ClientRectangle.Y + (int)(strSize.Height / 2), box.ClientRectangle.Width - 1, box.ClientRectangle.Height - (int)(strSize.Height / 2) - 1); // Clear text and border g.Clear(this.BackColor); // Draw text g.DrawString(box.Text, box.Font, textBrush, box.Padding.Left, 0); // Drawing Border //Left g.DrawLine(borderPen, rect.Location, new Point(rect.X, rect.Y + rect.Height)); //Right g.DrawLine(borderPen, new Point(rect.X + rect.Width, rect.Y), new Point(rect.X + rect.Width, rect.Y + rect.Height)); //Bottom g.DrawLine(borderPen, new Point(rect.X, rect.Y + rect.Height), new Point(rect.X + rect.Width, rect.Y + rect.Height)); //Top1 g.DrawLine(borderPen, new Point(rect.X, rect.Y), new Point(rect.X + box.Padding.Left, rect.Y)); //Top2 g.DrawLine(borderPen, new Point(rect.X + box.Padding.Left + (int)(strSize.Width), rect.Y), new Point(rect.X + rect.Width, rect.Y)); } } private void panel1_Resize(object sender, EventArgs e) { if ((pictureBox1.Width > 0) && (pictureBox1.Height > 0)) { fitPictureBox1(); Cyprus.updateScreenSize(pictureBox1.Width, pictureBox1.Height); updateImage(); } } private void updateImage() { IntPtr _imgPtr = Cyprus.getScreenPtr(); Bitmap bm = new Bitmap( pictureBox1.Width, pictureBox1.Height, pictureBox1.Width * 3, //Stride = 24 bit = 3 bytes per pixel System.Drawing.Imaging.PixelFormat.Format24bppRgb, _imgPtr); pictureBox1.Image = bm; pictureBox1.Refresh(); } private void timer1_Tick(object sender, EventArgs e) { checkMessages(); } private void checkMessages() { while (Cyprus.isMessage() == 1) { IntPtr pnt = Marshal.AllocHGlobal(255); // allocate 255 bytes of unmanaged memory int retval = Cyprus.getNextMessage(pnt, 255); // call dll to fill string message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(pnt); //parse messages: switch (message) { case "Event: Update Image": updateImage(); break; default: addStatus(message); break; } } } private void addStatus(string _string) { status.AppendText("\r\n" + _string); string keyword_Error = "ERROR:"; int indexOf, position = 0; bool _continue = true; while (_continue) { indexOf = status.Text.IndexOf(keyword_Error, position); if (indexOf != -1) { status.Select(indexOf, keyword_Error.Length); status.SelectionColor = Color.Magenta; position = indexOf + keyword_Error.Length; } else { _continue = false; } } keyword_Error = "warning:"; position = 0; _continue = true; while (_continue) { indexOf = status.Text.IndexOf(keyword_Error, position); if (indexOf != -1) { status.Select(indexOf, keyword_Error.Length); status.SelectionColor = Color.DeepSkyBlue; position = indexOf + keyword_Error.Length; } else { _continue = false; } } string keyword_success = "ยค"; position = 0; _continue = true; while (_continue) { indexOf = status.Text.IndexOf(keyword_success, position); if (indexOf != -1) { status.Select(indexOf, keyword_success.Length); status.SelectionColor = Color.Magenta; position = indexOf + keyword_success.Length; } else { _continue = false; } } status.Select(status.TextLength, 0); status.ScrollToCaret(); this.ActiveControl = null; } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { addStatus("releaseing resources...."); Cyprus.release(); } } }
Cyprus.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; namespace Cyprus { class Cyprus { [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern void init(int _screenWidth, int _screenHeight); [DllImport("ChimeraCpp", CallingConvention = CallingConvention.Cdecl)] public static extern void release(); [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern void updateScreenSize(int _width, int _height); [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr getScreenPtr(); [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern int isMessage(); [DllImport("CyprusDll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern int getNextMessage(IntPtr ptr, int _bufferLength); } }
CyprusDll.h
#pragma once extern "C" __declspec(dllexport) void __cdecl init(int _screenWidth, int _screenHeight); extern "C" __declspec(dllexport) void __cdecl release(); extern "C" __declspec(dllexport) int __cdecl isMessage(); extern "C" __declspec(dllexport) int __cdecl getNextMessage(char* _buffer, size_t _bufferLength); extern "C" __declspec(dllexport) void __cdecl updateScreenSize(int _width, int _height); extern "C" __declspec(dllexport) unsigned char* __cdecl getScreenPtr();
CyprusDll.cpp
// CyprusDll.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" #include <iostream> #include <thread> #include "CyprusDll.h" #include "alx_system_csDisplay.h" #include "alx_system_messageQueue.h" //----CyprusCPP_h.h------------ alx::system::csDisplay display; alx::system::MessageQueue messages; void dllThread(); void checkMessages(); //---dll variables---- bool dllThreadContinue = false; //for main dll thread bool dllThreadRunning = false; //for main dll thread extern "C" __declspec(dllexport) void __cdecl init(int width, int height) { //load spash screen images display.setBackground("bkgImage.jpg"); display.setData("sm.png"); display.setSize(width, height); display.render(); //---fire dll thread---- dllThreadContinue = true; messages.addBack("starting dll thread..."); std::thread st(dllThread); st.detach(); } extern "C" __declspec(dllexport) void __cdecl release() { messages.addBack("waiting for threads..."); dllThreadContinue = false; int _timeOut = 0; while (_timeOut < 500) { bool _isRunning = false; if (dllThreadRunning) { _isRunning = true; } if (!_isRunning) break; _timeOut++; Sleep(10); } std::cout << "threads closed..." << std::endl; } extern "C" __declspec(dllexport) void __cdecl updateScreenSize(int width, int height) { display.setSize(width, height); display.render(); } extern "C" __declspec(dllexport) unsigned char* __cdecl getScreenPtr() { return display.getImagePtr(); } extern "C" __declspec(dllexport) int __cdecl isMessage() { if (messages.isMessage()) return 1; else return 0; } extern "C" __declspec(dllexport) int __cdecl getNextMessage(char* _buffer, size_t _bufferLength) { return messages.getNext(_buffer, _bufferLength); } void dllThread() { dllThreadRunning = true; std::cout << "dll thread started..." << std::endl; int _sleepTime = 100; int _threadCount = 0; bool _pic1 = false; while (dllThreadContinue) { //----gather messages---- checkMessages(); //----do something----- if (_threadCount == 30) { _threadCount = 0; if (_pic1) { display.setData("sm.png"); messages.addBack("Event: Update Image"); _pic1 = false; } else { display.setData("sm2.png"); messages.addBack("Event: Update Image"); _pic1 = true; } } else _threadCount++; Sleep(_sleepTime); } std::cout << "dll thread exited..." << std::endl; dllThreadRunning = false; } void checkMessages() { const int _msgLen = 255; char msg[_msgLen] = { "/0" }; while (display.isMessage()) { display.getNextMessage(msg, _msgLen); messages.addBack(msg); } }
alx_system_csDisplay.h
#pragma once #include "opencv/cv.hpp" #include "alx_system_messageQueue.h" namespace alx { namespace system { class csDisplay { int m_height; int m_width; cv::Mat m_background; //background image for screen cv::Mat m_data; //data to show, full size cv::Mat m_rendered; //rendered image that matches screen size alx::system::MessageQueue m_messages; public: csDisplay(); ~csDisplay(); void setSize(int width, int height); void setBackground(char* filePath); void setBackground(const char* filePath); void setBackground(unsigned char* dataPtr, int width, int height); void setData(char* filePath); void setData(const char* filePath); void setData(unsigned char* dataPtr, int width, int height); void render(); unsigned char* getImagePtr(); bool isMessage(); int getNextMessage(char* _buffer, size_t _bufferLength); void addMessage(const char* _message); }; } }
alx_system_csDisplay.cpp
#include "stdafx.h" #include <iostream> #include "alx_system_csDisplay.h" namespace alx { namespace system { csDisplay::csDisplay() { //load default images m_background = cv::Mat(100, 100, CV_8UC3, cv::Scalar(0, 25, 0)); //BGR m_data = cv::Mat(100, 100, CV_8UC3, cv::Scalar(0, 51, 0)); //BGR } csDisplay::~csDisplay() {} void csDisplay::setSize(int width, int height) { m_width = width; m_height = height; m_rendered = cv::Mat(m_height, m_width, CV_8UC3); render(); } void csDisplay::setBackground(char* filePath) { try { m_background = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { m_messages.addBack("ERROR: loading background image..."); } } void csDisplay::setBackground(const char* filePath) { try { m_background = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { m_messages.addBack("ERROR: loading background image..."); } } void csDisplay::setBackground(unsigned char* dataPtr, int width, int height) { //creates a Mat header for existing data try { m_background = cv::Mat(height, width, CV_8UC3, dataPtr); render(); } catch (...) { m_messages.addBack("ERROR: creating header for image..."); } } void csDisplay::setData(char* filePath) { try { m_data = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { m_messages.addBack("ERROR: loading image..."); } } void csDisplay::setData(const char* filePath) { try { m_data = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { m_messages.addBack("ERROR: loading image..."); } } void csDisplay::setData(unsigned char* dataPtr, int width, int height) { //creates a Mat header for existing data try { m_data = cv::Mat(height, width, CV_8UC3, dataPtr); render(); } catch (...) { m_messages.addBack("ERROR: creating header for image..."); } } void csDisplay::render() { //Strategy: // -1. m_rendered should already be the same size as the display // -1. m_data is updated elsewhere so it is most current // 1. place background into m_rendered // 2. resize screenTile to fit into rendered // 3. copy screenTile into m_rendered (using ROI) double _aspectRatioData = m_data.cols / ((double)m_data.rows); double _aspectRatioScreen = m_rendered.cols / ((double)m_rendered.rows); int _tileWidth = 0; int _tileHeight = 0; int _tileOffsetX = 0; int _tileOffsetY = 0; if (_aspectRatioScreen >= _aspectRatioData) { //set heights equal, scale width _tileWidth = (int)(m_height * _aspectRatioData); _tileHeight = m_height; _tileOffsetX = (m_width - _tileWidth) / 2; } else { _tileWidth = m_width; _tileHeight = (int)(m_width / _aspectRatioData); _tileOffsetY = (m_height - _tileHeight) / 2; } cv::Mat _tile = cv::Mat(_tileHeight, _tileWidth, CV_8UC3); cv::resize(m_background, m_rendered, m_rendered.size(), 0, 0, cv::INTER_LINEAR); cv::resize(m_data, _tile, _tile.size(), 0, 0, cv::INTER_LINEAR); _tile.copyTo( m_rendered( cv::Rect( _tileOffsetX, _tileOffsetY, _tile.cols, _tile.rows ) ) ); } unsigned char* csDisplay::getImagePtr() { return m_rendered.data; } bool csDisplay::isMessage() { return m_messages.isMessage(); } int csDisplay::getNextMessage(char* _buffer, size_t _bufferLength) { return m_messages.getNext(_buffer, _bufferLength); } void csDisplay::addMessage(const char* _message) { m_messages.addBack(_message); } } }
alx_system_messageQueue.h
#pragma once //#include <queue> #include <deque> namespace alx { namespace system{ class MessageQueue { //std::queue <const char*> msgs; std::deque <char*> msgs; public: MessageQueue(); ~MessageQueue(); void addBack_so(char* _message); void addBack(char* _message); void addBack_so(const char* _message); void addBack(const char* _message); void addFront_so(char* _message); void addFront(char* _message); void addFront_so(const char* _message); void addFront(const char* _message); bool isMessage(); int getNext(char* _buffer, size_t _bufferLength); }; } }
alx_system_messageQueue.cpp
#include "stdafx.h" //#include "pch.h" #include "alx_system_messageQueue.h" #include <iostream> namespace alx { namespace system { MessageQueue::MessageQueue() {} MessageQueue::~MessageQueue() {} void MessageQueue::addBack_so(char* _message) { size_t _strLen = strlen(_message)+1; //get Length + term char char* _buffer = new char[_strLen]; //alocate internal buffer memcpy(_buffer, _message, _strLen); //copy _message msgs.push_back(_buffer); //add pointer to queue } void MessageQueue::addBack(char* _message) { addBack_so(_message); std::cout << _message << std::endl; } void MessageQueue::addBack_so(const char* _message) { size_t _strLen = strlen(_message) + 1; //get Length + term char char* _buffer = new char[_strLen]; //alocate internal buffer memcpy(_buffer, _message, _strLen); //copy _message msgs.push_back(_buffer); //add pointer to queue } void MessageQueue::addBack(const char* _message) { addBack_so(_message); std::cout << _message << std::endl; } void MessageQueue::addFront_so(char* _message) { size_t _strLen = strlen(_message) + 1; //get Length + term char char* _buffer = new char[_strLen]; //alocate internal buffer memcpy(_buffer, _message, _strLen); //copy _message msgs.push_front(_buffer); //add pointer to queue } void MessageQueue::addFront(char* _message) { addFront_so(_message); std::cout << _message << std::endl; } void MessageQueue::addFront_so(const char* _message) { size_t _strLen = strlen(_message) + 1; //get Length + term char char* _buffer = new char[_strLen]; //alocate internal buffer memcpy(_buffer, _message, _strLen); //copy _message msgs.push_front(_buffer); //add pointer to queue } void MessageQueue::addFront(const char* _message) { addFront_so(_message); std::cout << _message << std::endl; } bool MessageQueue::isMessage() { return !msgs.empty(); } int MessageQueue::getNext(char* _buffer, size_t _bufferLength) { int retVal = 0; if (!msgs.empty()) { size_t _msgLen = strlen(msgs.front())+1; //get Length + term char if (_msgLen < _bufferLength) { memset(_buffer, '\0', _bufferLength); //reset buffer memcpy(_buffer, msgs.front(), _msgLen); //copy to local buffer retVal = (int)_msgLen; } else { char _eStr[255] = "Error: incomplete msg-> "; //prepend message with error size_t _eLen = strlen(_eStr); //length of error length, not including term char for (size_t i = 0; i < _bufferLength; i++) { if (i < _eLen) _buffer[i] = _eStr[i]; else _buffer[_eLen+i] = msgs.front()[i]; } _buffer[_bufferLength] = '\0'; //should already be null from reset retVal = _bufferLength; } delete[] msgs.front(); //delete char's in internal buffer (leaves pointer) msgs.pop_front(); //delete message from queue } return retVal; } } }