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.


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



	}
}