How can I send an object (or pointer) from C++ .NET application to VB application
Asked Answered
S

5

14

I have 2 applications.

VB application is written in .NET 3.5. It is pretty big application. I can't rewrite this to C++ for few reasons. Im not sure if that matters, but it is x86 application.

C++ application is written in .NET 4.0. It is x64 build and there will be no x86 support. For now - it is managed code with a bit of assembler code. I will mix managed and unmanaged later when I learn more about C++. It is x64 build and has to stay like this.

It is supposed to extend VB application features - capture frames from camera, do something with them and send processed images to VB application. Images are pretty big (1920x1080x24bpp) and I need to process 30-60 frames per second like that, so it must be efficent way.

My goals:

  1. "Send" bitmap from C++ application to VB application, and VB application should start some method when that bitmap came.

  2. "Send" some information the other way, from VB application to C++ application. It is supposed to change C++ application processing parameters from VB application GUI.

  3. If possible - send just a pointer and size of bitmap instead of copying whole data in RAM.


Lets say, I want something like this:

VB side:

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

End Function 

C++ side:

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

It may be System.Drawing.Bitmap, or just some array that I will convert to System.Drawing.Bitmap in VB application. It doesn't matter that much.


My question:

Can someone explain, how can I:

  • send some object data (like System.Drawing.Bitmap for example), or better pointer to that data from VB application to C++ application
  • receive that data in C++ application
  • start some C++ function (with some event?) when data is received/ready
Stound answered 16/7, 2015 at 8:51 Comment(7)
Since you point out that these are two separate executable applications with different platform targets, avoid suggestions (such as the answer provided by darkfirewave) to use any form of interop, as there is both and process address space issue, and a platform conflict. IPC (inner process communication) is needed. For optimal performance, you will have to use shared memory and kernel objects for signaling. Memory mapped files (which are not necessarily files) are how named pipes (and several other technologies) achieve this. It will take some work, but 60+ fps is certainly attainable.Delimitate
Which application allocates memory for the bitmap? The VB app or the C++ app or it doesn't matter?Rosalynrosalynd
@HadiBrais It doesn't matter.Stound
Could you compile your VB program as a .Net 4.0 program? If so, would you then be able to use the System.IO.MemoryMappedFiles namespace?Meanwhile
@Meanwhile I think I can.Stound
@Stound If you can use 4.0 for your VB program, you should run a test whether you can use the MemoryMappedFiles namespace to solve your problem. I would start with trying to just pass some simple data between the two programs to see how that would work. The tutorial I linked to should give you some idea about how to do that (at least on the VB.Net side). You can also search MSDN for C++ examples, I imagine. If you need more help going down this road, post your questions here and I will see what I can do.Meanwhile
Others already indicated that IPC is needed.. Please research the same, and post more specific question.Floatfeed
B
5

Use shared memory circular buffer to exchange data between processes. This could be implemented using boost interprocess as a C++ dll and then that dll could be imported into your .Net applications. Note that you will need to build 32 and 64 bit versions of boost and your shared memory dll. I prepared an example of 32 bit and 64 bit apps which you can run and see how fast that is. I think it should be fast enough but if is not then still multithreading could be used.

64 bit producer:

#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 bit consumer:

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

I used boost 1.58, first circle is always slow, you may want to run a warm up circle before start using the shared memory. The data needs to be copied into shared memory but for reading the shmem pointer to the frame could be passed to the .Net application. Then you need to ensure your .Net app reads the data on time before it gets overwritten.

Useful links:

boost interpocess

Simple example

EDIT: I've modified the source code to show number of frames per seconds that roughly can be achieved. On my machine that is 190+ fps so I would expect it to be still above the required 60 fps taking into account the overhead of transferring data/pointer between .Net app and c++ dll.


Above code should give you a good start, you need to refactor producer and consumer shared memory code into a common class and make it a dll. There are few ways of importing c++ dll into .Net. How-to-Marshal-a-C-Class explains some of them quite well.

Now to your questions:

Can someone explain, how can I:

send some object data (like System.Drawing.Bitmap for example), or better pointer to that data from VB application to C++ application

You will need to get HBITMAP from Bitmap using GetHbitmap() method and pass it down to c++ dll. Then in c++ dll copy pixel data and other bitmap info if required into shared memory (pointer to data won't work). How to do that between .Net and c++ see c-get-raw-pixel-data-from-hbitmap. Especially useful will be this answer.

receive that data in C++ application

Then to read data from shared memory you will probably need to first create empty Bitmap of same size like in shared memory and pass its HBITMAP down to C++ dll to fill the pixel data.

start some C++ function (with some event?) when data is received/ready

You just need to continuously poll shared memory for new data like in the above code.

Bethannbethanne answered 20/7, 2015 at 22:51 Comment(0)
B
1

you should compile your VB application to x64 architecture. Since one of your binaries is in x64 so you should be ok with it. From your description I understood that youre using managed C++ .NET. Your C++ project should be compiled to a dll, because it doest not do anything else than just extending VB. So you can just import that .NET dll into your VB application. Now you can use managed C++ .NET functionality inside VB. If you however not using managed C++ .NET. You can choose one of fallowing. You can make native C wrapper to your C++. It can be pretty basic, you can make a function witch takes some arguments, and a function pointer as a callback (you wanted a callback) to get a bitmap. Use inter operations to probe you C++ dll for those functions (inter operators are found at Runtime.Interop namespace). Make a VB wrapper around those functions inside you VB application. You can convert methods inside VB to delegates representing function pointers, ant pass those delegates as callbacks. Or you can make managed C++ .NET wrapper around your native C++ methods. Again, it can be basic, you can make a basic class methods inside managed C++ .NET witch just forwards arguments to native code. Compile everything to managed C++ .NET dll, and include that dll to you VB application and use its functionality. Cheers.

Briney answered 23/7, 2015 at 18:20 Comment(0)
B
1

I would use a Named Pipe. It will allow you to send and receive data between processes. Once you receive the data, you can do whatever you want with it. That said, you won't be able to share objects between processes, so don't try sending a pointer. The good news is that named piped is supported by .NET 3.5 and .NET 4.0 according to this page. If you want examples, there are plenty online.

Brokaw answered 24/7, 2015 at 21:15 Comment(0)
E
0

You can do the following:

  1. create a folder that the c++ application generate the images in
  2. in you vb application add FileSystemWatcher so when the first application (c++) add any file to the common folder, automatically the second application will read it

you can use this link to learn more about the FileSystemWatcher

you might let the first application to name the file in a specific way for exmaple p-dd-mm-yyyy-hh-mm-ss and the second application rename it to c-dd-mm-yyyy-hh-mm-ss wher p: pending, c: completed and the dd-mm-yyyy-hh-mm-ss datetime value

based on your comment, hope you can find a solution mentioned here How to do CreateFileMapping in a C++ DLL and access it in C#, VB and C++

Eroticism answered 18/7, 2015 at 12:29 Comment(2)
I'm afraid that filesystem is waay too slow for me.Stound
@Stound ok, file system share will be slow, kindly have a look on this link : codeproject.com/Articles/242386/… which allows you to share memory value between 2 different running applications and this one msdn.microsoft.com/en-us/library/windows/desktop/…Eroticism
R
-2

You are using .Net. So you can use the interop lib. It contains a IntPtr class you can use to capsule the access to the c++ pointer. The bitmap class even has a constructor with an IntPtr. Have a look at the MSDN for Bitmap and IntPtr.

Redletter answered 16/7, 2015 at 9:36 Comment(3)
Can I ask for simple example with interop?Stound
Hey I'm sorry that I dont have the time to write an example at the moment. Perhaps just have look at this: social.msdn.microsoft.com/Forums/vstudio/en-US/… or this msdn.microsoft.com/en-us/library/vstudio/…Redletter
@Redletter - I down-voted your answer. The details in the question point out that these are two executable applications, which have different targets (x64 - x86). For multiple reasons, your interop solution will not work in this case.Delimitate

© 2022 - 2024 — McMap. All rights reserved.