【问题标题】:How can I send an object (or pointer) from C++ .NET application to VB application如何将对象(或指针)从 C++ .NET 应用程序发送到 VB 应用程序
【发布时间】:2015-10-05 15:01:16
【问题描述】:

我有 2 个应用程序。

VB 应用程序 是用 .NET 3.5 编写的。这是一个相当大的应用程序。由于几个原因,我无法将其重写为 C++。我不确定这是否重要,但它是 x86 应用程序。

C++ 应用程序 是用 .NET 4.0 编写的。它是 x64 版本,将不支持 x86。现在 - 它是带有一些汇编代码的托管代码。稍后当我了解有关 C++ 的更多信息时,我将混合托管和非托管。它是 x64 版本,必须保持这种状态。

它应该扩展 VB 应用程序的功能 - 从相机捕获帧,对它们进行处理并将处理后的图像发送到 VB 应用程序。图像非常大(1920x1080x24bpp),我需要每秒处理 30-60 帧,所以它必须是有效的方式。

我的目标:

  1. “发送”位图从 C++ 应用程序到 VB 应用程序,当该位图到达时 VB 应用程序应该启动一些方法。

  2. 以另一种方式“发送”一些信息,从 VB 应用程序到 C++ 应用程序。它应该从 VB 应用程序 GUI 更改 C++ 应用程序处理参数。

  3. 如果可能 - 只发送一个指针和位图大小,而不是在 RAM 中复制整个数据。


可以说,我想要这样的东西:

VB端:

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

End Function 

C++ 方面:

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

它可能是 System.Drawing.Bitmap,或者只是一些我将在 VB 应用程序中转换为 System.Drawing.Bitmap 的数组。没关系。


我的问题:

谁能解释一下,我该怎么做:

  • 发送一些对象数据(例如 System.Drawing.Bitmap),或从 VB 应用程序到 C++ 应用程序的更好的指向该数据的指针
  • 在 C++ 应用程序中接收该数据
  • 在接收/准备好数据时启动一些 C++ 函数(带有一些事件?)

【问题讨论】:

  • 既然您指出这是两个具有不同平台目标的独立可执行应用程序,请避免使用任何形式的互操作的建议(例如,darkfirewave 提供的答案),因为同时存在和进程地址空间问题,平台冲突。需要IPC(内部进程通信)。为了获得最佳性能,您将不得不使用共享内存和内核对象来发送信号。内存映射文件(不一定是文件)是命名管道(以及其他几种技术)实现此目的的方式。这需要一些工作,但 60+ fps 肯定是可以实现的。
  • 哪个应用程序为位图分配内存? VB 应用程序或 C++ 应用程序还是没关系?
  • @HadiBrais 没关系。
  • 您能否将您的 VB 程序编译为 .Net 4.0 程序?如果是这样,您是否可以使用 System.IO.MemoryMappedFiles 命名空间?
  • @ChicagoMike 我想我可以。

标签: c++ vb.net interprocess


【解决方案1】:

使用共享内存循环缓冲区在进程之间交换数据。这可以使用 boost interprocess 作为 C++ dll 来实现,然后可以将该 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 应用在数据被覆盖之前按时读取数据。

有用的链接:

boost interpocess

Simple example

编辑:我修改了源代码以显示大致可以达到的每秒帧数。在我的机器上是 190+ fps,所以考虑到在 .Net 应用程序和 c++ dll 之间传输数据/指针的开销,我希望它仍然高于所需的 60 fps。


上面的代码应该给你一个好的开始,你需要将生产者和消费者共享内存代码重构为一个公共类并使其成为一个dll。将 c++ dll 导入 .Net 的方法很少。 How-to-Marshal-a-C-Class 很好地解释了其中一些。

现在回答你的问题:

谁能解释一下,我该怎么做:

发送一些对象数据(例如 System.Drawing.Bitmap),或者从 VB 应用程序到 C++ 应用程序的更好的指向该数据的指针

您需要使用GetHbitmap() 方法从Bitmap 获取HBITMAP 并将其传递给c++ dll。然后在 c++ dll 中,如果需要,将像素数据和其他位图信息复制到共享内存中(指向数据的指针不起作用)。如何在 .Net 和 c++ 之间执行此操作,请参阅c-get-raw-pixel-data-from-hbitmap。特别有用的是this answer

在 C++ 应用程序中接收该数据

然后要从共享内存中读取数据,您可能需要首先创建与共享内存中相同大小的空Bitmap,并将其 HBITMAP 传递给 C++ dll 以填充像素数据。

在接收/准备好数据时启动一些 C++ 函数(带有一些事件?)

您只需要像上面的代码一样不断地轮询共享内存以获取新数据。

【讨论】:

    【解决方案2】:

    您应该将您的 VB 应用程序编译为 x64 架构。由于您的二进制文件之一是 x64 格式,因此您应该可以接受。根据您的描述,我了解到您正在使用托管 C++ .NET。你的 C++ 项目应该被编译成一个 dll,因为它除了扩展 VB 之外什么都不做。因此,您只需将该 .NET dll 导入您的 VB 应用程序即可。现在您可以在 VB 中使用托管 C++ .NET 功能。 但是,如果您不使用托管 C++ .NET。您可以选择其中一种休闲方式。 您可以为您的 C++ 制作原生 C 包装器。它可以是非常基本的,你可以让一个函数接受一些参数,一个函数指针作为回调(你想要一个回调)来获取位图。使用 inter 操作来探测 C++ dll 中的那些函数(inter 操作符位于 Runtime.Interop 命名空间)。在您的 VB 应用程序中围绕这些函数制作一个 VB 包装器。您可以将 VB 中的方法转换为代表函数指针的委托,然后将这些委托作为回调传递。 或者,您可以围绕您的本机 C++ 方法制作托管 C++ .NET 包装器。同样,它可以是基本的,您可以在托管 C++ .NET 中创建一个基本类方法,只需将参数转发给本机代码。将所有内容编译为托管 C++ .NET dll,并将该 dll 包含到您的 VB 应用程序中并使用其功能。干杯。

    【讨论】:

      【解决方案3】:

      我会使用Named Pipe。它将允许您在进程之间发送和接收数据。一旦你收到数据,你就可以对它做任何你想做的事情。也就是说,您将无法在进程之间共享对象,因此不要尝试发送指针。好消息是,根据page,.NET 3.5 和 .NET 4.0 支持命名管道。如果你想要例子,网上有很多。

      【讨论】:

        【解决方案4】:

        您可以执行以下操作:

        1. 创建一个文件夹,C++ 应用程序在其中生成图像
        2. 在你的vb应用程序中添加FileSystemWatcher所以当第一个 应用程序 (c++) 自动将任何文件添加到公共文件夹 第二个应用程序会读取它

        您可以使用此链接learn 了解有关 FileSystemWatcher 的更多信息

        您可以让第一个应用程序以特定方式命名文件,例如 p-dd-mm-yyyy-hh-mm-ss,第二个应用程序将其重命名为 c-dd-mm-yyyy-hh-mm -ss wher p:待处理,c:已完成以及 dd-mm-yyyy-hh-mm-ss 日期时间值

        根据您的评论,希望您能找到这里提到的解决方案How to do CreateFileMapping in a C++ DLL and access it in C#, VB and C++

        【讨论】:

        【解决方案5】:

        您正在使用 .Net。所以你可以使用互操作库。它包含一个 IntPtr 类,您可以使用它来封装对 c++ 指针的访问。位图类甚至有一个带有 IntPtr 的构造函数。查看 MSDN 中的 Bitmap 和 IntPtr。

        【讨论】:

        • 我可以要求一个简单的互操作示例吗?
        • 嘿,对不起,我现在没有时间写一个例子。也许看看这个:social.msdn.microsoft.com/Forums/vstudio/en-US/… 或这个msdn.microsoft.com/en-us/library/vstudio/…
        • @darkfirewave - 我对你的回答投了反对票。问题中的详细信息指出,这是两个可执行应用程序,它们具有不同的目标(x64 - x86)。由于多种原因,您的互操作解决方案在这种情况下将不起作用。
        猜你喜欢
        • 2011-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-03
        • 2017-10-11
        • 2021-02-08
        • 1970-01-01
        相关资源
        最近更新 更多