Как я могу отправить объект (или указатель) из приложения С++ .NET в приложение VB

У меня есть 2 приложения.

Приложение VB написано на .NET 3.5. Это довольно большое приложение. Я не могу переписать это на С++ по нескольким причинам. Я не уверен, что это имеет значение, но это приложение x86.

Приложение C++ написано на .NET 4.0. Это сборка x64, поддержки x86 не будет. Пока что это управляемый код с небольшим количеством кода на ассемблере. Я буду смешивать управляемые и неуправляемые языки позже, когда узнаю больше о C++. Это сборка x64, и она должна оставаться такой.

Предполагается расширить возможности VB-приложения - захватывать кадры с камеры, что-то с ними делать и отправлять обработанные изображения в VB-приложение. Изображения довольно большие (1920x1080x24bpp), и мне нужно обрабатывать 30-60 кадров в секунду, так что это должно быть эффективным способом.

Мои цели:

  1. «Отправить» растровое изображение из приложения C++ в приложение VB, и приложение VB должно запустить какой-либо метод, когда это растровое изображение пришло.

  2. "Отправить" некоторую информацию другим путем, из приложения VB в приложение C++. Предполагается изменить параметры обработки приложения C++ из графического интерфейса приложения VB.

  3. Если возможно, отправьте только указатель и размер растрового изображения вместо копирования целых данных в ОЗУ.


Допустим, я хочу что-то вроде этого:

Сторона ВБ:

Function receivedBitmapFromCpp(BMP_POINTER?, BMP_SIZE_X?, BMP_SIZE_Y?, BMP_BPP?) As Integer Handles ????

End Function 

Сторона С++:

void sendBitmapToVb(BMP_POINTER?, BMP_SIZE_X?, BMP_SIZE_Y?, BMP_BPP?)
{
    int bitmapsendingresult = ?????
}

Это может быть System.Drawing.Bitmap или просто массив, который я конвертирую в System.Drawing.Bitmap в приложении VB. Это не имеет большого значения.


Мой вопрос:

Может кто-нибудь объяснить, как я могу:

  • отправить некоторые данные объекта (например, System.Drawing.Bitmap) или лучший указатель на эти данные из приложения VB в приложение C++
  • получить эти данные в приложении С++
  • запустить некоторую функцию С++ (с каким-то событием?), когда данные получены/готовы

person Kamil    schedule 16.07.2015    source источник
comment
Поскольку вы указываете, что это два отдельных исполняемых приложения с разными целевыми платформами, избегайте предложений (таких как ответ, предоставленный darkfirewave) использовать любую форму взаимодействия, поскольку существует проблема адресного пространства и процесса, а также конфликт платформы. Требуется IPC (внутренняя связь процесса). Для оптимальной производительности вам придется использовать разделяемую память и объекты ядра для сигнализации. Файлы с отображением памяти (которые не обязательно являются файлами) - это то, как именованные каналы (и некоторые другие технологии) достигают этого. Это потребует некоторой работы, но 60+ кадров в секунду, безусловно, достижимо.   -  person Jeff    schedule 18.07.2015
comment
Какое приложение выделяет память для растрового изображения? Приложение VB или приложение C++, или это не имеет значения?   -  person Hadi Brais    schedule 20.07.2015
comment
@HadiBrais Это не имеет значения.   -  person Kamil    schedule 20.07.2015
comment
Не могли бы вы скомпилировать свою программу VB как программу .Net 4.0? Если да, то сможете ли вы использовать System.IO.MemoryMappedFiles?   -  person ChicagoMike    schedule 20.07.2015
comment
@ChicagoMike Я думаю, что смогу.   -  person Kamil    schedule 20.07.2015
comment
@Kamil Если вы можете использовать 4.0 для своей программы VB, вам следует запустить тест, можете ли вы использовать пространство имен MemoryMappedFiles для решения вашей проблемы. Я бы начал с попытки просто передать некоторые простые данные между двумя программами, чтобы посмотреть, как это будет работать. Учебник, на который я ссылался, должен дать вам некоторое представление о том, как это сделать (по крайней мере, на стороне VB.Net). Думаю, вы также можете поискать в MSDN примеры C++. Если вам нужна дополнительная помощь по этому пути, оставьте свои вопросы здесь, и я посмотрю, что я могу сделать.   -  person ChicagoMike    schedule 20.07.2015
comment
Другие уже указали, что требуется IPC. Пожалуйста, изучите то же самое и задайте более конкретный вопрос.   -  person Vikas Gupta    schedule 22.07.2015


Ответы (5)


Используйте циклический буфер с общей памятью для обмена данными между процессами. Это может быть реализовано с использованием межпроцессного взаимодействия в качестве DLL C++, а затем эта DLL может быть импортирована в ваши приложения .Net. Обратите внимание, что вам нужно будет собрать 32- и 64-разрядные версии boost и dll с общей памятью. Я подготовил пример 32-битных и 64-битных приложений, которые вы можете запустить и посмотреть, насколько это быстро. Я думаю, что это должно быть достаточно быстро, но если это не так, то можно использовать многопоточность.

64-битный производитель:

#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <cstring>
#include <cstdlib>
#include <string>
#include <iostream>
#include <chrono>

struct shmem_info
{
    boost::interprocess::interprocess_mutex mutex;
    uint64_t pos;
    bool run;
};

int main(int argc, char *argv[])
{
    using namespace boost::interprocess;

    struct shm_remove
    {
        shm_remove() { shared_memory_object::remove("MySharedMemory"); shared_memory_object::remove("MySharedMemoryInfo");}
        //~shm_remove() { shared_memory_object::remove("MySharedMemory"); shared_memory_object::remove("MySharedMemoryInfo");}
    } remover;
    const size_t width = 1920;
    const size_t height = 1080;
    const size_t bytes_per_pixel = 3;
    const size_t frame_size = width*height*bytes_per_pixel;
    const size_t frames = 60;
    const size_t shmem_frames = 3 * frames;
    const size_t shmem_size = width * height * bytes_per_pixel * shmem_frames;

    std::cout << "Generating data ..." << std::endl;
    std::vector<uint8_t> frame(frame_size);
    
    // generate frame data
    for (size_t x = 0; x < width*height; ++x)
        for (size_t y = 0; y < bytes_per_pixel; ++y)
            frame[x*bytes_per_pixel + y] = (x%252) + y;

    std::cout << "Creating shared memory files ..." << std::endl;
    shared_memory_object shm(create_only, "MySharedMemory", read_write);
    shared_memory_object shm_info(create_only, "MySharedMemoryInfo", read_write);

    //Set size
    shm.truncate(shmem_size);
    shm_info.truncate(sizeof(shmem_info));

    //Map the whole shared memory in this process
    mapped_region region(shm, read_write);
    mapped_region region_info(shm_info, read_write);

    shmem_info *info = new (region_info.get_address()) shmem_info;
    {
        scoped_lock<interprocess_mutex> lock(info->mutex);
        info->pos = 0;
        info->run = true;
    }

    char c;
    std::cout << "Ready. Now start client application and wait for it to be ready." << std::endl;
    std::cout << "Then press a key and enter to start" << std::endl;
    std::cin >> c;
    std::cout << "Running ..." << std::endl;

    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    size_t times = 10;
    for (size_t t = 0; t < times; ++t)
    {
        for (size_t f = 0; f < shmem_frames; ++f)
        {
            // get pointer to the beginning of shared memory
            uint8_t *ptr = static_cast<uint8_t*>(region.get_address());
            // move pointer to the next frame
            ptr += f*frame_size;

            // modify first data point for testing purposes
            frame[0] = f;
            frame[1] = f + 1;
            frame[2] = f + 2;

            // copy data to shared memory
            memcpy(ptr, &frame[0], frame_size);

            // update the position each "frames" number, doing that too frequently kills the performance
            if (f % frames == 0)
            {
                // this will lock access to the pos for the time of updating the pos only
                scoped_lock<interprocess_mutex> lock(info->mutex);
                info->pos += frames;
                std::cout << "write pos = " << info->pos << std::endl;
            }
        }
    }

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    size_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << (double(times*shmem_frames*1000) / double(ms)) << " fps." << std::endl;

    winapi::sleep(2000);

    // stop run
    {
        scoped_lock<interprocess_mutex> lock(info->mutex);
        info->run = false;
    }

    return 0;
}

32-битный потребитель:

#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <cstring>
#include <cstdlib>
#include <string>
#include <iostream>
#include <chrono>

struct shmem_info
{
    boost::interprocess::interprocess_mutex mutex;
    uint64_t pos;
    bool run;
};

int main(int argc, char *argv[])
{
    using namespace boost::interprocess;

    const size_t width = 1920;
    const size_t height = 1080;
    const size_t bytes_per_pixel = 3;
    const size_t frame_size = width*height*bytes_per_pixel;
    const size_t frames = 60;
    const size_t shmem_frames = 3 * frames;
    const size_t shmem_size = width * height * bytes_per_pixel * shmem_frames;

    std::vector<uint8_t> frame(frame_size);

    std::cout << "Opening shared memory files ..." << std::endl;

    //Open already created shared memory object.
    shared_memory_object shm(open_only, "MySharedMemory", read_write);
    shared_memory_object shm_info(open_only, "MySharedMemoryInfo", read_write);

    //Map the whole shared memory in this process
    mapped_region region(shm, read_only);
    mapped_region region_info(shm_info, read_write);

    shmem_info *info = static_cast<shmem_info*>(region_info.get_address());

    std::cout << "Ready." << std::endl;

    bool run = true;

    // first wait for processing to be started
    while (true)
    {
        {
            scoped_lock<interprocess_mutex> lock(info->mutex);
            if (info->run)
                break;
        }
        winapi::Sleep(1);
    }

    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    uint64_t pos = 0;
    uint64_t shm_pos = 0;
    while(run)
    {
        // wait for a new data
        {
            scoped_lock<interprocess_mutex> lock(info->mutex);
            run = info->run;
            if (info->pos == pos)
            {
                winapi::Sleep(1);
                continue;
            }
            // we've got the new data
            shm_pos = info->pos;
        }

        while (pos < shm_pos)
        {
            // get pointer to the beginning of shared memory
            uint8_t *ptr = static_cast<uint8_t*>(region.get_address());
            // calculate the frame position in circular buffer and move pointer to that frame 
            ptr += (pos%shmem_frames)*frame_size;

            // copy data from shared memory
            memcpy(&frame[0], ptr, frame_size);

            //winapi::Sleep(1);
            ++pos;
            if (pos % frames == 0)
                std::cout << "read pos: " << pos << std::endl;
        }
    }

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    size_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    ms -= 2000; // producer waits 2 seconds before sets run=false
    std::cout << (double(pos*1000) / double(ms)) << " fps." << std::endl;

    return 0;
}

Я использовал Boost 1.58, первый круг всегда медленный, вы можете запустить разминку, прежде чем начать использовать общую память. Данные необходимо скопировать в разделяемую память, но для чтения указатель shmem на фрейм может быть передан приложению .Net. Затем вам нужно убедиться, что ваше приложение .Net считывает данные вовремя, прежде чем они будут перезаписаны.

Полезные ссылки:

повысить межпотоковое взаимодействие

Простой пример

EDIT: я изменил исходный код, чтобы показать количество кадров в секунду, которое примерно может быть достигнуто. На моей машине это 190+ кадров в секунду, поэтому я ожидаю, что она будет выше требуемых 60 кадров в секунду, принимая во внимание накладные расходы на передачу данных/указателя между приложением .Net и С++ dll.


Приведенный выше код должен дать вам хорошее начало, вам нужно реорганизовать код общей памяти производителя и потребителя в общий класс и сделать его dll. Существует несколько способов импорта dll С++ в .Net. Как-to-Marshal-aC-Class объясняет некоторые из них довольно хорошо.

Теперь к вашим вопросам:

Может кто-нибудь объяснить, как я могу:

отправить некоторые данные объекта (например, System.Drawing.Bitmap) или лучший указатель на эти данные из приложения VB в приложение C++

Вам нужно будет получить HBITMAP из Bitmap с помощью метода GetHbitmap() и передать его в c++ dll. Затем в С++ dll скопируйте данные пикселей и другую растровую информацию, если это необходимо, в общую память (указатель на данные не будет работать). Как это сделать между .Net и С++, см. c-get-raw-pixel- данные из hbitmap. Особенно полезным будет этот ответ.

получить эти данные в приложении С++

Затем, чтобы прочитать данные из общей памяти, вам, вероятно, потребуется сначала создать пустой Bitmap того же размера, что и в общей памяти, и передать его HBITMAP в C++ dll для заполнения данных пикселей.

запустить некоторую функцию С++ (с каким-то событием?), когда данные получены/готовы

Вам просто нужно постоянно опрашивать общую память на наличие новых данных, как в приведенном выше коде.

person doqtor    schedule 20.07.2015

вам следует скомпилировать ваше приложение VB в архитектуру x64. Поскольку один из ваших двоичных файлов находится в x64, с ним все должно быть в порядке. Из вашего описания я понял, что вы используете управляемый C++ .NET. Ваш проект C++ должен быть скомпилирован в dll, потому что он не делает ничего, кроме расширения VB. Таким образом, вы можете просто импортировать эту .NET dll в свое приложение VB. Теперь вы можете использовать управляемые функции C++ .NET внутри VB. Однако если вы не используете управляемый C++ .NET. Вы можете выбрать один из следующих. Вы можете сделать нативную оболочку C для своего C++. Это может быть довольно просто, вы можете сделать функцию, которая принимает некоторые аргументы и указатель функции в качестве обратного вызова (вам нужен обратный вызов), чтобы получить растровое изображение. Используйте интероперации, чтобы проверить вашу C++ dll на наличие этих функций (интероператоры находятся в пространстве имен Runtime.Interop). Создайте оболочку VB вокруг этих функций внутри вашего приложения VB. Вы можете преобразовывать методы внутри VB в делегаты, представляющие указатели на функции, и передавать эти делегаты как обратные вызовы. Или вы можете создать управляемую оболочку C++ .NET вокруг собственных методов C++. Опять же, это может быть базовым, вы можете создать базовые методы класса внутри управляемого C++ .NET, которые просто пересылают аргументы в собственный код. Скомпилируйте все в управляемую dll C++ .NET и включите эту dll в свое приложение VB и используйте ее функциональные возможности. Ваше здоровье.

person Radon    schedule 23.07.2015


Вы можете сделать следующее:

  1. создайте папку, в которой приложение С++ генерирует изображения.
  2. в вашем приложении vb добавьте FileSystemWatcher, поэтому, когда первое приложение (С++) добавит любой файл в общую папку, второе приложение автоматически прочитает его

вы можете использовать эту ссылку на узнать больше о FileSystemWatcher

вы можете позволить первому приложению назвать файл определенным образом, например, p-dd-mm-yyyy-hh-mm-ss, а второму приложению переименовать его в c-dd-mm-yyyy-hh-mm-ss где p: в ожидании, c: завершено и значение даты и времени дд-мм-гггг-чч-мм-сс

основываясь на вашем комментарии, надеюсь, вы сможете найти решение, упомянутое здесь Как сделать CreateFileMapping в C++ DLL и получить к нему доступ в C#, VB и C++

person Monah    schedule 18.07.2015
comment
Боюсь, эта файловая система слишком медленная для меня. - person Kamil; 18.07.2015
comment
@Kamil хорошо, совместное использование файловой системы будет медленным, пожалуйста, посмотрите эту ссылку: codeproject.com/Articles/242386/, который позволяет вам совместно использовать значение памяти между двумя разными запущенными приложениями и этим msdn.microsoft.com/en-us/library/windows/desktop/ - person Monah; 18.07.2015

Вы используете .Net. Таким образом, вы можете использовать библиотеку взаимодействия. Он содержит класс IntPtr, который можно использовать для ограничения доступа к указателю c++. Класс растрового изображения даже имеет конструктор с IntPtr. Взгляните на MSDN для Bitmap и IntPtr.

person darkfirewave    schedule 16.07.2015
comment
Могу ли я попросить простой пример с взаимодействием? - person Kamil; 16.07.2015
comment
Эй, мне жаль, что у меня нет времени, чтобы написать пример в данный момент. Возможно, просто взгляните на это: from-c?forum=csharpgeneral" rel="nofollow noreferrer">social.msdn.microsoft.com/Forums/vstudio/en-US/ или этот msdn.microsoft.com/en-us/library/vstudio/ - person darkfirewave; 16.07.2015
comment
@darkfirewave - я проголосовал за ваш ответ. Подробности в вопросе указывают на то, что это два исполняемых приложения с разными целями (x64-x86). По нескольким причинам ваше решение для взаимодействия не будет работать в этом случае. - person Jeff; 18.07.2015