如何从C++ .NET应用程序发送一个对象(或指针)到VB应用程序

14

我有两个应用程序。

VB应用程序是用.NET 3.5编写的,非常庞大。因为几个原因,我无法将其改写成C++。不确定这是否重要,但它是x86应用程序。

C++应用程序是用.NET 4.0编写的。它是x64版本,不支持x86。目前还是托管代码,但稍后会混合使用托管和非托管代码。它是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 = ?????
}

我的问题:

有人可以解释一下,我如何:

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

它可以是System.Drawing.Bitmap,或者只是我将在VB应用程序中转换为System.Drawing.Bitmap的一些数组。这并不那么重要。


2
由于您指出这是两个具有不同平台目标的可执行应用程序,请避免使用任何形式的Interop(例如darkfirewave提供的答案)建议,因为存在进程地址空间问题和平台冲突。需要IPC(内部进程通信)。为了实现最佳性能,您将需要使用共享内存和内核对象进行信号传递。内存映射文件(不一定是文件)是命名管道(以及其他几种技术)实现此目的的方法。虽然需要一些工作,但60+ fps肯定是可以实现的。 - Jeff
@HadiBrais 没关系。 - Kamil
@ChicagoMike 我想我能行。 - Kamil
@Kamil 如果您的VB程序可以使用4.0版本,您应该运行一个测试来确定是否可以使用MemoryMappedFiles命名空间来解决您的问题。我建议您首先尝试在两个程序之间传递一些简单的数据,以了解它的工作原理。我提供的教程应该会给您一些关于如何做到这一点的想法(至少在VB.Net方面)。您也可以在MSDN上搜索C++示例。如果您需要更多帮助,请在此处发布您的问题,我会尽力帮助您。 - ChicagoMike
其他人已经指出需要IPC。请进行研究,并发布更具体的问题。 - Vikas Gupta
显示剩余2条评论
5个回答

5
使用共享内存循环缓冲区在进程之间交换数据。可以使用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 简单示例 编辑:我修改了源代码以显示大致可以实现的每秒帧数。在我的机器上,这是190+ fps,因此我期望它仍将超过所需的60 fps,考虑到在.Net应用程序和c++ dll之间传输数据/指针的开销。
上面的代码应该会给你一个良好的开始,你需要将生产者和消费者共享内存的代码重构为一个通用类并将其制作成dll。有几种将c++ dll导入到.Net的方法。如何封送C ++类 解释得很好。
现在回答你的问题:

有人能解释一下,我怎么才能:

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

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

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

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

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

您只需要像上面的代码一样持续轮询共享内存以获取新数据即可。


1
你需要将VB应用程序编译为x64体系结构。由于你的二进制文件之一是x64,所以你应该没有问题。根据你的描述,我了解到你正在使用托管的C++ .NET。你的C++项目应该编译为dll,因为它除了扩展VB之外什么也不做。因此,你可以将该.NET dll导入你的VB应用程序中。现在你可以在VB中使用托管的C++ .NET功能。 如果你没有使用托管的C++ .NET。你可以选择以下其中一个。 你可以创建一个原生C包装器来封装你的C++。它可能非常基本,你可以创建一个函数,它接受一些参数和一个函数指针作为回调(你需要一个回调)以获取位图。使用interop操作来探测你的C++ dll是否具有这些功能(interop操作位于Runtime.Interop命名空间)。在VB应用程序中围绕这些函数创建VB包装器。你可以将VB中的方法转换为表示函数指针的委托,并将这些委托作为回调传递。 或者,你可以在原生C++方法周围创建托管C++ .NET包装器。同样,它可能很基本,你可以在托管C++ .NET内部创建基本类方法,它只是将参数转发到本机代码。将所有内容编译为托管C++ .NET dll,并将该dll包含在你的VB应用程序中并使用其功能。干杯。

1
我会使用一个命名管道。它允许您在进程之间发送和接收数据。一旦接收到数据,您可以对其进行任何操作。话虽如此,您将无法在进程之间共享对象,因此不要尝试发送指针。好消息是,根据这个页面, 命名管道得到了.NET 3.5和.NET 4.0的支持。如果您需要示例,有很多在线上。

0

您可以执行以下操作:

  1. 创建一个文件夹,让 C++ 应用程序在其中生成图像
  2. 在您的 VB 应用程序中添加 FileSystemWatcher,这样当第一个应用程序(C++)向公共文件夹添加任何文件时,第二个应用程序将自动读取它

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

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

根据您的评论,希望您能在此处找到提到的解决方案如何在 C++ DLL 中执行 CreateFileMapping 并在 C#、VB 和 C++ 中访问它


我担心文件系统对我来说太慢了。 - Kamil
@Kamil,文件系统共享将会变慢,请查看以下链接:http://www.codeproject.com/Articles/242386/VB-NET-MEMORY-MAPPING-ACROSS-DIFFERENT-RUNNING-APP ,该链接可让您在两个不同的运行应用程序之间共享内存值。还可以查看此链接:https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366878(v=vs.85).aspx - Monah

-2

你正在使用 .Net。因此,你可以使用 interop 库。它包含一个 IntPtr 类,你可以用它来封装对 c++ 指针的访问。Bitmap 类甚至有一个带有 IntPtr 的构造函数。请查看 MSDN 上的 Bitmap 和 IntPtr。


我可以请求一个简单的Interop示例吗? - Kamil
嘿,很抱歉我现在没有时间写一个例子。也许你可以看看这个链接: https://social.msdn.microsoft.com/Forums/vstudio/en-US/843f97f0-2dae-4217-8734-91453ab6dd5f/call-managed-c-funciton-from-c?forum=csharpgeneral 或者这个链接 https://msdn.microsoft.com/en-us/library/vstudio/ms235638%28v=vs.100%29.aspx - darkfirewave
2
@darkfirewave - 我给你的回答点了个踩。问题中的细节指出这是两个可执行应用程序,它们有不同的目标(x64 - x86)。由于多种原因,你的互操作解决方案在这种情况下不起作用。 - Jeff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接