4
\$\begingroup\$

First, I've read the a few questions regarding this issue, the most helpful being: https://stackoverflow.com/questions/27463876/passing-bitmap-from-c-sharp-to-c

I was unable to get the provided solutions to work as I kept getting an AccessViolationException.

What I'm attempting to do is pass bitmap data to an unmanaged c++ dll. To do this I created a struct which holds a pointer to the image data as well as its length. I'm using a struct as I plan on passing in multiple images (in a single call) to the unmanaged API.

What I implemented works but I have a feeling there is probably some serious drawbacks so I'm curious as to what those drawbacks could be.

My current solution uses a generic pointer to hold the image data. This of course would be a drawback as I lose type safety. Anyway here is the relevant code.


C++ dll

raw_image.h

struct raw_image
{
   void* data;
   int size;
};

alignment.cpp (exports)

ALIGNMENT_API void submit( raw_image& img )
{
    cv::Mat mat = cv::imdecode( cv::_InputArray(
        static_cast<uchar*>( img.data ), img.size ), cv::IMREAD_COLOR );
    cv::imshow( "image", mat );
    cv::waitKey( );
    cv::destroyWindow( "image" );
}

C# dll

RawImage.cs

[StructLayout( LayoutKind.Sequential )]
internal unsafe struct RawImage
{
    internal void* ImageData;
    internal int Size;
}

Aligner.cs (import)

[DllImport( "alignment-vc141-mtd-x64.dll", CallingConvention = 
 CallingConvention.Cdecl )]
    static extern void submit( RawImage img );

And this is where I pass the image to the unmanaged API.

using( var bitmap = new Bitmap( "AlignmentCenter.jpg" ) )
using( var stream = new MemoryStream( ) )
{
    bitmap.Save( stream, ImageFormat.Jpeg );
    var source = stream.ToArray( );
    fixed( void* ptr = source )
    {
        var raw = new RawImage
        {
            ImageData = ptr,
            Size = source.Length
        };
        submit( raw );
     }
  }

Is what I'm doing unsafe? Am I copying more than I should? One last thing, I know about EmguCv and I've used it in the past but I won't be using it here.

\$\endgroup\$
  • 2
    \$\begingroup\$ You could create a c++/cli wrapper for the native API and let it do the hard lifting with pointers and calling the native function so that you can keep your C# code clean. \$\endgroup\$ – t3chb0t Nov 20 '18 at 12:35
  • \$\begingroup\$ @t3chb0t I originally planned on using C++/CLI for this, but ended up just using pinvoke for its simplicity. I'm not very familiar with C++/CLI. That being said, at the end of the day, if C++/CLI is the better option than I'll write a wrapper around the unmanaged dll. \$\endgroup\$ – WBuck Nov 20 '18 at 12:39
  • \$\begingroup\$ When not using system functions I find such a wrapper easier to use than pinvoke because with it you can use C# more naturally without the native stuff and you can work with the native stuff without workarounds inside the C++/CLI wrapper because it can more naturally communicate with native code. \$\endgroup\$ – t3chb0t Nov 20 '18 at 12:43
  • \$\begingroup\$ What do you mean by "Am I copying more than I should?"? \$\endgroup\$ – Henrik Hansen Nov 20 '18 at 16:18
  • \$\begingroup\$ @HenrikHansen Currently I'm copying the contents of the Bitmap in to the MemoryStream. I was curious if this was necessary, or if I should use the Bitmap's scan0 property, and locking the Bitmap's pixels and then passing an IntPtr to the unmanaged API.. \$\endgroup\$ – WBuck Nov 20 '18 at 16:46
3
\$\begingroup\$

There is not much to review. The only thing I can contribute with is that you don't have to run in unsafe mode:

If you define the data structure as:

  [StructLayout(LayoutKind.Sequential)]
  internal struct RawImage
  {
    internal IntPtr ImageData;
    internal int Size;
  }

Then you can run the .NET side as:

  using (var bitmap = new Bitmap(@"fileName.Jpeg"))
  using (var stream = new MemoryStream())
  {
    bitmap.Save(stream, ImageFormat.Jpeg);
    var source = stream.ToArray();

    IntPtr handle = IntPtr.Zero;

    try
    {
      handle = Marshal.AllocHGlobal(source.Length);
      Marshal.Copy(source, 0, handle, source.Length);

      var raw = new RawImage
      {
        ImageData = handle,
        Size = source.Length
      };
      submit(raw);
    }
    catch (Exception ex)
    {
      if (handle != IntPtr.Zero)
      {
        Marshal.FreeHGlobal(handle);
      }
    }
  }

The above works with the same signature for submit(RawImage img).

It's a little more code, but you are not limiting the use of your module.

\$\endgroup\$
  • \$\begingroup\$ Yeah I prefer this solution, more C# friendly. \$\endgroup\$ – WBuck Nov 21 '18 at 2:15
3
\$\begingroup\$

Both your and Henrik's approach can be optimized by replacing stream.ToArray() with stream.GetBuffer(). This returns the MemoryStream's internal buffer array instead of making a copy.

You do have to take into account that the buffer can be larger than the actual data, so use the Length of the stream, not of the buffer. It's also possible that the data doesn't start at index 0, but that's only the case if you used one of the MemoryStream constructors that accepts an array, index and count. No problem here, but something to keep in mind if you want to generalize this.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.