Для обмена данными между библиотекой DLL, написанной на языке C/C++ и приложением, написанном на языке C#, удобно и эффективно использовать Отображаемые в Памяти Файлы (MemoryMappedFile). По сути, это выделенный участок оперативной памяти компьютера (скорость!), который имеет свое уникальное имя и размер в байтах. Оба эти параметра задаются программистом. В дальнейшем можно, как читать из этой памяти, так и писать в нее, подключившись к ней в библиотеке DLL и в приложении C#.
Был проведен тест скорости обмена сообщениями, в тесте принимали участие следующие технологии: MemoryMappedFile, NamedPipes и Socket. Создавались по два отдельных приложения на C#, сервер и клиент, которые должны были обменяться друг с другом текстовыми сообщениями размером 120 символов 500 000 раз. На все это у них ушло следующее количество времени:
MemoryMappedFile: 1,5 секунды
NamedPipes: 12,5 секунд
Socket: 14 секунд
При этом, количество требуемого кода, так же, было меньше всего у MemoryMappedFile, по моему, выбор очевиден!
Пример создания и использования именованной памяти с именем "MyMemory" и размером 256 байт. В примере реализован следующий алгоритм:
QLua(Lua)
- Подключает библиотеку DLL
- Запускает функцию отправки сообщений в C#
- Останавливает функцию отправки сообщений в C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | -- Подключает библиотеку DLL require("QluaCSharpConnector"); -- Флаг для поддержания работы функции main IsRun = true; function main() -- Запускает функцию отправки сообщений в C# QluaCSharpConnector.StartSendHi(); -- Обеспечивает работу скрипта и библиотеки до остановки скрипта пользователем while IsRun do sleep(1000); end; end; function OnStop() -- Останавливает функцию отправки сообщений в C# QluaCSharpConnector.StopSendHi(); -- Останавливает цикл в функции main IsRun = false; end; |
-- Подключает библиотеку DLL require("QluaCSharpConnector"); -- Флаг для поддержания работы функции main IsRun = true; function main() -- Запускает функцию отправки сообщений в C# QluaCSharpConnector.StartSendHi(); -- Обеспечивает работу скрипта и библиотеки до остановки скрипта пользователем while IsRun do sleep(1000); end; end; function OnStop() -- Останавливает функцию отправки сообщений в C# QluaCSharpConnector.StopSendHi(); -- Останавливает цикл в функции main IsRun = false; end;
- Библиотека DLL создает/подключается к именованной памяти.
- Отправляет (записывает в память) текстовое сообщение: "Привет из C/C++".
- Читает память с периодичностью в 1 секунду, если память стала чиста, сообщение отправляется вновь.
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 | #include <windows.h> //=== Необходимые для Lua константы ============================================================================// #define LUA_LIB #define LUA_BUILD_AS_DLL //=== Заголовочные файлы LUA ===================================================================================// extern "C" { #include "Lua\lauxlib.h" #include "Lua\lua.h" } //=== Получает указатель на выделенную именованную память =====================================================// // Имя для выделенной памяти TCHAR Name[] = TEXT("MyMemory"); // Создаст, или подключится к уже созданной памяти с таким именем HANDLE hFileMapMyMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, Name); //=== Стандартная точка входа для DLL ==========================================================================// BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } // Флаг необходимости отправлять сообщение bool Run = true; //=== Реализация функций, вызываемых из LUA ====================================================================// static int forLua_StartSendHi(lua_State *L) // Отправляет сообщения для C# { // Если указатель на память получен if (hFileMapMyMemory) { // Получает доступ (представление) непосредственно к чтению/записи байт PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapMyMemory, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256)); // Если доступ получен if (pb != NULL) { // Очищает память при первом обращении for (int i = 0; i < 256; i++)pb[i] = '\0'; // Бесконечный цикл пока Run == true while (Run) { // Если запись пустая (либо первая отправка, либо C# очистил память, подтвердив получение) if (pb[0] == 0) { // Записывает текст сообщения в память char *Str = "Привет из C/C++"; memcpy(pb, Str, strlen(Str)); } // Пауза в 1 секунду Sleep(1000); } // Закрывает представление UnmapViewOfFile(pb); // Закрывает указатель на память CloseHandle(hFileMapMyMemory); } } return(0); } static int forLua_StopSendHi(lua_State *L) // Прекращает отправку сообщений для C# { Run = false; // Выключает флаг return(0); } //=== Регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua ================================// static struct luaL_reg ls_lib[] = { { "StartSendHi", forLua_StartSendHi }, // из скрипта Lua эту функцию можно будет вызывать так: QluaCSharpConnector.StartSendHi(); здесь можно указать любое другое название { "StopSendHi", forLua_StopSendHi }, // соответственно { NULL, NULL } }; //=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================// extern "C" LUALIB_API int luaopen_QluaCSharpConnector(lua_State *L) { luaL_openlib(L, "QluaCSharpConnector", ls_lib, 0); return 0; } |
#include <windows.h> //=== Необходимые для Lua константы ============================================================================// #define LUA_LIB #define LUA_BUILD_AS_DLL //=== Заголовочные файлы LUA ===================================================================================// extern "C" { #include "Lua\lauxlib.h" #include "Lua\lua.h" } //=== Получает указатель на выделенную именованную память =====================================================// // Имя для выделенной памяти TCHAR Name[] = TEXT("MyMemory"); // Создаст, или подключится к уже созданной памяти с таким именем HANDLE hFileMapMyMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, Name); //=== Стандартная точка входа для DLL ==========================================================================// BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } // Флаг необходимости отправлять сообщение bool Run = true; //=== Реализация функций, вызываемых из LUA ====================================================================// static int forLua_StartSendHi(lua_State *L) // Отправляет сообщения для C# { // Если указатель на память получен if (hFileMapMyMemory) { // Получает доступ (представление) непосредственно к чтению/записи байт PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapMyMemory, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256)); // Если доступ получен if (pb != NULL) { // Очищает память при первом обращении for (int i = 0; i < 256; i++)pb[i] = '\0'; // Бесконечный цикл пока Run == true while (Run) { // Если запись пустая (либо первая отправка, либо C# очистил память, подтвердив получение) if (pb[0] == 0) { // Записывает текст сообщения в память char *Str = "Привет из C/C++"; memcpy(pb, Str, strlen(Str)); } // Пауза в 1 секунду Sleep(1000); } // Закрывает представление UnmapViewOfFile(pb); // Закрывает указатель на память CloseHandle(hFileMapMyMemory); } } return(0); } static int forLua_StopSendHi(lua_State *L) // Прекращает отправку сообщений для C# { Run = false; // Выключает флаг return(0); } //=== Регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua ================================// static struct luaL_reg ls_lib[] = { { "StartSendHi", forLua_StartSendHi }, // из скрипта Lua эту функцию можно будет вызывать так: QluaCSharpConnector.StartSendHi(); здесь можно указать любое другое название { "StopSendHi", forLua_StopSendHi }, // соответственно { NULL, NULL } }; //=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================// extern "C" LUALIB_API int luaopen_QluaCSharpConnector(lua_State *L) { luaL_openlib(L, "QluaCSharpConnector", ls_lib, 0); return 0; }
- Приложение на C# создает/подключается к именованной памяти.
- Читает память с периодичностью в 1 секунду, если в памяти появилось текстовое сообщение: "Привет из C/C++", выводит его в текстовое поле и очищает память, сообщая тем самым DLL что сообщение получено.
Разместите на форме один TextBox (из вкладки «Панель элементов»), установите его свойство «Multiline» в «True», а свойство «ScrollBars» в «Vertical» и растяните его по форме, чтобы получилось примерно следующее:
Кликните по шапке формы, перейдите в окне «Свойства» на вкладку «События» и сделайте двойной клик в пустой строке напротив поля «FormClosing». Таким образом в файл «Form1.cs» добавится новая функция «Form1_FormClosing», которая будет вызываться после того, как Вы нажмете на кнопку закрытия формы запущенного приложения.
Снова кликните по шапке формы, перейдите в окне «Свойства» на вкладку «События» и сделайте двойной клик в пустой строке напротив поля «Shown». Таким образом в файл «Form1.cs» добавится новая функция «Form1_Shown», которая будет вызываться при первом показе формы.
Сейчас полностью замените код в файле «Form1.cs» на этот:
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 | using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Test { public partial class Form1 : Form { // Выделенная именованная память public MemoryMappedFile Memory; // Объект для чтения из памяти StreamReader SR_Memory; // Объект для записи в память StreamWriter SW_Memory; // Флаг необходимости получать сообщения bool Run = true; public Form1() { InitializeComponent(); // Создаст, или подключится к уже созданной памяти с таким именем Memory = MemoryMappedFile.CreateOrOpen("MyMemory", 256, MemoryMappedFileAccess.ReadWrite); // Создает поток для чтения SR_Memory = new StreamReader(Memory.CreateViewStream(), System.Text.Encoding.Default); // Создает поток для записи SW_Memory = new StreamWriter(Memory.CreateViewStream(), System.Text.Encoding.Default); } // Делегат нужен для того, чтобы безопасно обратиться к TextBox из другого потока private delegate void TB(string Msg); private void AppText(string Msg) { // Добавляет к сообщению символ перехода на новую строку textBox1.AppendText(Msg + Environment.NewLine); } private void GetMessage()// Получает сообщения от DLL, выводит их в текстовое поле, очищает память { string Msg = ""; // Цикл работает пока Run == true while(Run) { // Встает в начало потока для чтения SR_Memory.BaseStream.Seek(0, SeekOrigin.Begin); // Считывает данные из потока памяти, обрезая ненужные байты Msg = SR_Memory.ReadToEnd().Trim('\0', '\r', '\n'); // Если в потоке нужное сообщение ("Привет из C/C++") if (Msg == "Привет из C/C++") { // Потокобезопасно выводит сообщение в текстовое поле BeginInvoke(new TB(AppText), Msg); // Встает в начало потока для записи SW_Memory.BaseStream.Seek(0, SeekOrigin.Begin); // Очищает память, заполняя "нулевыми байтами" for (int i = 0; i < 256; i++) SW_Memory.Write("\0"); // Очищает все буферы для SW_Memory и вызывает запись всех данных буфера в основной поток SW_Memory.Flush(); } //Пауза в 1 секунду Thread.Sleep(1000); } // По завершению цикла, закрывает все потоки и освобождает именованную память SR_Memory.Close(); SW_Memory.Close(); Memory.Dispose(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { Run = false;// Выключает флаг } private void Form1_Shown(object sender, EventArgs e) { // Запускает функцию чтения и вывода сообщений в отдельном потоке, чтобы форма отвечала на действия пользователя new Thread(() => { GetMessage(); }).Start(); } } } |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Test { public partial class Form1 : Form { // Выделенная именованная память public MemoryMappedFile Memory; // Объект для чтения из памяти StreamReader SR_Memory; // Объект для записи в память StreamWriter SW_Memory; // Флаг необходимости получать сообщения bool Run = true; public Form1() { InitializeComponent(); // Создаст, или подключится к уже созданной памяти с таким именем Memory = MemoryMappedFile.CreateOrOpen("MyMemory", 256, MemoryMappedFileAccess.ReadWrite); // Создает поток для чтения SR_Memory = new StreamReader(Memory.CreateViewStream(), System.Text.Encoding.Default); // Создает поток для записи SW_Memory = new StreamWriter(Memory.CreateViewStream(), System.Text.Encoding.Default); } // Делегат нужен для того, чтобы безопасно обратиться к TextBox из другого потока private delegate void TB(string Msg); private void AppText(string Msg) { // Добавляет к сообщению символ перехода на новую строку textBox1.AppendText(Msg + Environment.NewLine); } private void GetMessage()// Получает сообщения от DLL, выводит их в текстовое поле, очищает память { string Msg = ""; // Цикл работает пока Run == true while(Run) { // Встает в начало потока для чтения SR_Memory.BaseStream.Seek(0, SeekOrigin.Begin); // Считывает данные из потока памяти, обрезая ненужные байты Msg = SR_Memory.ReadToEnd().Trim('\0', '\r', '\n'); // Если в потоке нужное сообщение ("Привет из C/C++") if (Msg == "Привет из C/C++") { // Потокобезопасно выводит сообщение в текстовое поле BeginInvoke(new TB(AppText), Msg); // Встает в начало потока для записи SW_Memory.BaseStream.Seek(0, SeekOrigin.Begin); // Очищает память, заполняя "нулевыми байтами" for (int i = 0; i < 256; i++) SW_Memory.Write("\0"); // Очищает все буферы для SW_Memory и вызывает запись всех данных буфера в основной поток SW_Memory.Flush(); } //Пауза в 1 секунду Thread.Sleep(1000); } // По завершению цикла, закрывает все потоки и освобождает именованную память SR_Memory.Close(); SW_Memory.Close(); Memory.Dispose(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { Run = false;// Выключает флаг } private void Form1_Shown(object sender, EventArgs e) { // Запускает функцию чтения и вывода сообщений в отдельном потоке, чтобы форма отвечала на действия пользователя new Thread(() => { GetMessage(); }).Start(); } } }
Скомпилируйте проект.
Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!