Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. Calling C++ Image API from C#

Calling C++ Image API from C#

Scheduled Pinned Locked Moved C#
csharpc++data-structuresjsonperformance
24 Posts 4 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • E econner

    I am working on implimenting a 3rd party API written in C++. The API pulls an image from a scanner. I have tried a few different ways of of working with the image struct but every attempt causes memory crashes with P/Invoke. Any suggestions would be appreciated. Here is the C++ code and sample that comes with the device.

    //(From header file)
    //SCANDLL_API int __stdcall WScanSelectBuf(MY_IMAGE *simage_down,MY_IMAGE *simage_up,int select);

    //(structure from C++ documentation
    typedef struct {
    int width; // image width
    int height; // image height
    int info; // bit-count (1,4,8 bit)
    unsigned char* pbuf; // image buffer
    } MY_IMAGE; // defines the image information

    //(Use of method in sample C++ code)

    MY_IMAGE img1,img2;

    img1.width = 864;
    img1.height = 3000;
    img1.info = 8; // 8bit
    img1.pbuf = (UCHAR *)new UCHAR[2*nWidth*nHeight];

    img2.width = 864;
    img2.height = 3000;
    img2.info = 8; // 8bit
    img2.pbuf = (UCHAR *)new UCHAR[2*nWidth*nHeight];

    ret=WScanSelectBuf(&img1, &img2,0);


    //C# conversion

    [StructLayout(LayoutKind.Explicit)]
    public struct MY_IMAGE
    {
    [FieldOffset(0)]
    public int width;

        \[FieldOffset(4)\]
        public int height;
        
        \[FieldOffset(8)\]
        public int info;
        
        //\[FieldOffset(12)\]
        //public byte\[\] pbuf; // image buffer as byte array???
    
        //Stuck here? 
        \[FieldOffset(12), MarshalAs(UnmanagedType.SysUInt)\]
        public IntPtr pbuf; // image buffer
    }
    

    [DllImport("scandll.dll")]
    private extern static int WT_ScanSelectBuf(ref MY_IMAGE image_down, ref MY_IMAGE image_up, int select);

    public static int ScanSelectBuf(ref MY_IMAGE image_down, ref MY_IMAGE image_up, int select)
    {
    return WScanSelectBuf(ref image_down, ref image_up, select);
    }

    ImageInfo img1 = new ImageInfo();
    ImageInfo img2 = new ImageInfo();

    int nWidth = 864;
    int nHeight = 1000;

    img1.width = nWidth;
    img1.height = nHeight;
    img1.info = 8; // 8bit
    //img1.pbuf = new byte[2 *nWidth * nHeight];
    //img1.pbuf = IntPtr.Zero;

    img2.width = nWidth;
    img2.height = nHeight;
    img2.info = 8; // 8bit
    //img2.pbuf = new byte[2 * nWidth * nHeight];
    //img2.pbuf = IntPtr.Zero;

    //CRASHES WITH MEMORY ACCESS/CORRUPT ERRORS
    ret = ScanSelectBuf(ref img1,ref img2, 0);

    J Offline
    J Offline
    jschell
    wrote on last edited by
    #15

    econner wrote:

    Any suggestions would be appreciated.

    Don't embed it in in your C#/.Net code. Instead wrap it in a C++ executable with a communication (socket/file/stdio) API and run it from C# via Process and write C# code to talk to the communication API. The advantage is that it can't take down your C# app and you can test the parts independently. Note the first part as that can happen even if your code is perfect if there is a bug in the library itself.

    1 Reply Last reply
    0
    • _ _Erik_

      Create a byte[] array with the same size you need in C++ for UCHAR. Use GCHandle class to pin this array, so the GC will not reallocate it in case of a garbage collection, and set its address to the pbuf field. Have a look at the documentation of GCHandle class.

      E Offline
      E Offline
      econner
      wrote on last edited by
      #16

      I have modified the code as per my understanding of GCHandle. I am still getting memory errors. I believe it has something to do with the IntPtr of the pbuf in the MY_IMAGE struct. Here is my code so far:

      [StructLayout(LayoutKind.Sequential)]
      public class MY_IMAGE
      {
      public int width;
      public int height;
      public int info;
      public IntPtr pbuf; // image buffer
      }

      [DllImport("scandll.dll")]
      private extern static int WScanSelectBuf(IntPtr image_down, IntPtr image_up, int select);

      public static int ScanSelectBuf(MY_IMAGE image_down, MY_IMAGE image_up, int select)
      {

      GCHandle image\_downHandle = GCHandle.Alloc(image\_down, GCHandleType.Pinned);
      GCHandle image\_upHandle = GCHandle.Alloc(image\_up, GCHandleType.Pinned);
      
      int ret = WScanSelectBuf(image\_downHandle.AddrOfPinnedObject(),
                                    image\_upHandle.AddrOfPinnedObject(), select);
      

      //CRASHES HERE WITH ERROR:
      //Attempted to read or write protected memory.
      //This is often an indication that other memory is corrupt.

      image\_downHandle.Free();
      image\_upHandle.Free();
      
      return ret;
      

      }

      MY_IMAGE img1 = new MY_IMAGE();
      MY_IMAGE img2 = new MY_IMAGE();

      int nWidth = 864;
      int nHeight = 1000;

      img1.width = nWidth;
      img1.height = nHeight;
      img1.info = 8; // 8bit
      //img1.pbuf = new byte[2 *nWidth * nHeight];

      img2.width = nWidth;
      img2.height = nHeight;
      img2.info = 8; // 8bit
      //img2.pbuf = new byte[2 * nWidth * nHeight];

      //HOW TO DEFINE BYTE[] AND PIN AND THEN ASSIGN TO BUFFER?????

      ret = ScanSelectBuf(img1, img2, 0);

      Anything I am missing ???

      _ 1 Reply Last reply
      0
      • E econner

        I have modified the code as per my understanding of GCHandle. I am still getting memory errors. I believe it has something to do with the IntPtr of the pbuf in the MY_IMAGE struct. Here is my code so far:

        [StructLayout(LayoutKind.Sequential)]
        public class MY_IMAGE
        {
        public int width;
        public int height;
        public int info;
        public IntPtr pbuf; // image buffer
        }

        [DllImport("scandll.dll")]
        private extern static int WScanSelectBuf(IntPtr image_down, IntPtr image_up, int select);

        public static int ScanSelectBuf(MY_IMAGE image_down, MY_IMAGE image_up, int select)
        {

        GCHandle image\_downHandle = GCHandle.Alloc(image\_down, GCHandleType.Pinned);
        GCHandle image\_upHandle = GCHandle.Alloc(image\_up, GCHandleType.Pinned);
        
        int ret = WScanSelectBuf(image\_downHandle.AddrOfPinnedObject(),
                                      image\_upHandle.AddrOfPinnedObject(), select);
        

        //CRASHES HERE WITH ERROR:
        //Attempted to read or write protected memory.
        //This is often an indication that other memory is corrupt.

        image\_downHandle.Free();
        image\_upHandle.Free();
        
        return ret;
        

        }

        MY_IMAGE img1 = new MY_IMAGE();
        MY_IMAGE img2 = new MY_IMAGE();

        int nWidth = 864;
        int nHeight = 1000;

        img1.width = nWidth;
        img1.height = nHeight;
        img1.info = 8; // 8bit
        //img1.pbuf = new byte[2 *nWidth * nHeight];

        img2.width = nWidth;
        img2.height = nHeight;
        img2.info = 8; // 8bit
        //img2.pbuf = new byte[2 * nWidth * nHeight];

        //HOW TO DEFINE BYTE[] AND PIN AND THEN ASSIGN TO BUFFER?????

        ret = ScanSelectBuf(img1, img2, 0);

        Anything I am missing ???

        _ Offline
        _ Offline
        _Erik_
        wrote on last edited by
        #17

        Yes, you are pinning the MY_IMAGE objects, but that is not what you have to pin. When you call a function using P/Invoke, the CLR automatically pinnes in the managed heap the references you pass, so there is no need to explicitly pin them as you have done. However, the pbuf field of each MY_IMAGE class is an IntPtr, what means that it only contains the memory address of the buffer allocated to receive de image pixels. As it is only an address, the CLR will not pin these buffers automatically, and that is why the programmer has to pin them manually before using the P/Invoke call. In this case you have not even allocated the buffers, so the values of the pbuf fields of the MY_IMAGE objects you are passing to the unmanaged function are IntPtr.Zero, I mean, the default value for IntPtr. This is what you have to do: Before you call the WScanSelectBuf you have to create the two MY_IMAGE objects and allocate the byte[] arrays where that function will place the image pixel:

        MY_IMAGE img1 = new MY_IMAGE();
        MY_IMAGE img2 = new MY_IMAGE();

        // Initialize the other fields, I mean, width, height and info
        // Then create the byte[] arrays
        // with the size given by the documentation
        byte[] buffer1 = new byte[2*img1.width*img1.height];
        byte[] buffer2 = new byte{2*img2.width*img2.height];

        // Pin these two arrays in the managed heap
        GCHandle bufferHandle1 = GCHandle.Alloc(buffer1, GCHandleType.Pinned);
        GCHandle bufferHandle2 = GCHandle.Alloc(buffer2, GCHandleType.Pinned);

        // Set the address of the buffers to each pbuf field
        img1.pbuf = bufferHandle1.AddrOfPinnedObject();
        img2.pbuf = bufferHandle2.AddrOfPinnedObject();

        // And now we can make the P/Invoke call
        int ret = WScanSelectBuf(img1, img2, select);

        // Do not forget to free the handles
        bufferHandle1.Free();
        bufferHandle2.Free();

        // Here you should have the pixel information in the buffer1 and buffer2 arrays

        Tell us if it works.

        E 1 Reply Last reply
        0
        • _ _Erik_

          Yes, you are pinning the MY_IMAGE objects, but that is not what you have to pin. When you call a function using P/Invoke, the CLR automatically pinnes in the managed heap the references you pass, so there is no need to explicitly pin them as you have done. However, the pbuf field of each MY_IMAGE class is an IntPtr, what means that it only contains the memory address of the buffer allocated to receive de image pixels. As it is only an address, the CLR will not pin these buffers automatically, and that is why the programmer has to pin them manually before using the P/Invoke call. In this case you have not even allocated the buffers, so the values of the pbuf fields of the MY_IMAGE objects you are passing to the unmanaged function are IntPtr.Zero, I mean, the default value for IntPtr. This is what you have to do: Before you call the WScanSelectBuf you have to create the two MY_IMAGE objects and allocate the byte[] arrays where that function will place the image pixel:

          MY_IMAGE img1 = new MY_IMAGE();
          MY_IMAGE img2 = new MY_IMAGE();

          // Initialize the other fields, I mean, width, height and info
          // Then create the byte[] arrays
          // with the size given by the documentation
          byte[] buffer1 = new byte[2*img1.width*img1.height];
          byte[] buffer2 = new byte{2*img2.width*img2.height];

          // Pin these two arrays in the managed heap
          GCHandle bufferHandle1 = GCHandle.Alloc(buffer1, GCHandleType.Pinned);
          GCHandle bufferHandle2 = GCHandle.Alloc(buffer2, GCHandleType.Pinned);

          // Set the address of the buffers to each pbuf field
          img1.pbuf = bufferHandle1.AddrOfPinnedObject();
          img2.pbuf = bufferHandle2.AddrOfPinnedObject();

          // And now we can make the P/Invoke call
          int ret = WScanSelectBuf(img1, img2, select);

          // Do not forget to free the handles
          bufferHandle1.Free();
          bufferHandle2.Free();

          // Here you should have the pixel information in the buffer1 and buffer2 arrays

          Tell us if it works.

          E Offline
          E Offline
          econner
          wrote on last edited by
          #18

          I modified the code as per above. The img1.pbuf and img2.pbuf are now showing an address. However, the native API funtion of WScanSelectBuf(img1, img2, select) is still crashing. I have tried passing in:

          WScanSelectBuf(img1, img2, select) (MY_IMAGE)
          WScanSelectBuf(ref img1, ref img2, select) (ref of MY_IMAGE)
          WScanSelectBuf(img1, img2, select) (IntPtr)

          and changed the code in the DLLImport and wrapper method without any success. Is there anything I am missing with Marshaling, etc? I am coming to the conclusion that it could be an issue with the 3rd party's API libary and is doing something odd with the function. However, it does work in their C++ sample. I have contacted the vendor and they are not familar with .NET. However, they are reviewing this thread and for any suggestions.

          _ 1 Reply Last reply
          0
          • E econner

            I modified the code as per above. The img1.pbuf and img2.pbuf are now showing an address. However, the native API funtion of WScanSelectBuf(img1, img2, select) is still crashing. I have tried passing in:

            WScanSelectBuf(img1, img2, select) (MY_IMAGE)
            WScanSelectBuf(ref img1, ref img2, select) (ref of MY_IMAGE)
            WScanSelectBuf(img1, img2, select) (IntPtr)

            and changed the code in the DLLImport and wrapper method without any success. Is there anything I am missing with Marshaling, etc? I am coming to the conclusion that it could be an issue with the 3rd party's API libary and is doing something odd with the function. However, it does work in their C++ sample. I have contacted the vendor and they are not familar with .NET. However, they are reviewing this thread and for any suggestions.

            _ Offline
            _ Offline
            _Erik_
            wrote on last edited by
            #19

            Ok, it's pretty weird... I am not sure what is going on here. The correct way to import MY_IMAGE type and the function is as I told you before, there is no need of marshaling for any field. You might try a little change, using Marshal.UnsafeAddrOfPinnedArrayElement instead of AddrOfPinnedObject method of GCHandle class, I mean, you first pin the buffers with GCHandle and then set the address in pbuf fields this way:

            img1.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer1, 0);
            img2.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer2, 0);

            If it still does not work, you might try allocating the buffers directly in the global heap, with Marshal.AllocHGlobal (remember to free this memory when you don't need it any more), instead of creating them in the managed heap as we did before when instantiated the buffers with new byte[size]. If this works you should then copy the buffers allocated in the global heap to the managed heap with the method Marshal.Copy.

            E 3 Replies Last reply
            0
            • _ _Erik_

              Ok, it's pretty weird... I am not sure what is going on here. The correct way to import MY_IMAGE type and the function is as I told you before, there is no need of marshaling for any field. You might try a little change, using Marshal.UnsafeAddrOfPinnedArrayElement instead of AddrOfPinnedObject method of GCHandle class, I mean, you first pin the buffers with GCHandle and then set the address in pbuf fields this way:

              img1.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer1, 0);
              img2.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer2, 0);

              If it still does not work, you might try allocating the buffers directly in the global heap, with Marshal.AllocHGlobal (remember to free this memory when you don't need it any more), instead of creating them in the managed heap as we did before when instantiated the buffers with new byte[size]. If this works you should then copy the buffers allocated in the global heap to the managed heap with the method Marshal.Copy.

              E Offline
              E Offline
              econner
              wrote on last edited by
              #20

              Ok, below are the changes I tried. However, I am receiving an error of: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

              [DllImport("scandll.dll")]
              private extern static int WScanSelectBuf(MY_IMAGE image_down, MY_IMAGE image_up, int select);

              public static int ScanSelectBuf(MY_IMAGE image_down, MY_IMAGE image_up, int select)
              {
              return WScanSelectBuf(image_down, image_up, select);
              }

              int nWidth = 100;
              int nHeight = 200;

              img1.width = nWidth;
              img1.height = nHeight;
              img1.info = 8; // 8bit

              img2.width = nWidth;
              img2.height = nHeight;
              img2.info = 8; // 8bit

              byte[] buffer1 = new byte[2 * img1.width * img1.height];
              byte[] buffer2 = new byte[2 * img2.width * img2.height];

              // Pin these two arrays in the managed heap
              GCHandle bufferHandle1 = GCHandle.Alloc(buffer1, GCHandleType.Pinned);
              GCHandle bufferHandle2 = GCHandle.Alloc(buffer2, GCHandleType.Pinned);

              // Set the address of the buffers to each pbuf field
              //img1.pbuf = bufferHandle1.AddrOfPinnedObject();
              //img2.pbuf = bufferHandle2.AddrOfPinnedObject();

              img1.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer1, 0);
              img2.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer2, 0);

              // And now we can make the P/Invoke call
              ret = ScanSelectBuf(img1, img2, 0);

              // Do not forget to free the handles
              bufferHandle1.Free();
              bufferHandle2.Free();

              1 Reply Last reply
              0
              • _ _Erik_

                Ok, it's pretty weird... I am not sure what is going on here. The correct way to import MY_IMAGE type and the function is as I told you before, there is no need of marshaling for any field. You might try a little change, using Marshal.UnsafeAddrOfPinnedArrayElement instead of AddrOfPinnedObject method of GCHandle class, I mean, you first pin the buffers with GCHandle and then set the address in pbuf fields this way:

                img1.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer1, 0);
                img2.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer2, 0);

                If it still does not work, you might try allocating the buffers directly in the global heap, with Marshal.AllocHGlobal (remember to free this memory when you don't need it any more), instead of creating them in the managed heap as we did before when instantiated the buffers with new byte[size]. If this works you should then copy the buffers allocated in the global heap to the managed heap with the method Marshal.Copy.

                E Offline
                E Offline
                econner
                wrote on last edited by
                #21

                Erik - Thanks for your help. I have it working now. The reason it was failing after the last step was due to other settings that had to be set on the scanner. The vendor makes a single side and double side scanner. The sample in the documetation does not match the process. I dug around in the C++ sample and found other settings that I was able to specify before scanning the card.

                E 1 Reply Last reply
                0
                • E econner

                  Erik - Thanks for your help. I have it working now. The reason it was failing after the last step was due to other settings that had to be set on the scanner. The vendor makes a single side and double side scanner. The sample in the documetation does not match the process. I dug around in the C++ sample and found other settings that I was able to specify before scanning the card.

                  E Offline
                  E Offline
                  econner
                  wrote on last edited by
                  #22

                  One of the functions was not in the documentation and the sample I was using. I found the function when reviewing the header file and the C++ test app code.

                  1 Reply Last reply
                  0
                  • _ _Erik_

                    Ok, it's pretty weird... I am not sure what is going on here. The correct way to import MY_IMAGE type and the function is as I told you before, there is no need of marshaling for any field. You might try a little change, using Marshal.UnsafeAddrOfPinnedArrayElement instead of AddrOfPinnedObject method of GCHandle class, I mean, you first pin the buffers with GCHandle and then set the address in pbuf fields this way:

                    img1.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer1, 0);
                    img2.pbuf = Marshal.UnsafeAddrOfPinnedArrayElement(buffer2, 0);

                    If it still does not work, you might try allocating the buffers directly in the global heap, with Marshal.AllocHGlobal (remember to free this memory when you don't need it any more), instead of creating them in the managed heap as we did before when instantiated the buffers with new byte[size]. If this works you should then copy the buffers allocated in the global heap to the managed heap with the method Marshal.Copy.

                    E Offline
                    E Offline
                    econner
                    wrote on last edited by
                    #23

                    Erik, I wanted to thank you again for the assistance. I was able to confirm that the code require the buffer and pinning/marshaling of the memory and it will not work correctly without the suggestions you provided. I also wanted to let you know what compounded the testing issue with the error was due to incomplete documentation. After getting the process to work, I was able to change certain settings and was able to cause the process to fail. Basically, the image class has to define the height and width to the MAXIMUM allowed image that the scanner can accept. Then the buffer size is set to the max height * max width * single/double scanner mode. Once the scanner returns the image, it adjusts the height and buffer values to be what are read in from the scanner. If the image sizes are specified incorrectly/too small, the API will cause a memory error. For example, the image of the scanned area may be 4" but the scanner's read head is capable of scanning a wider area. So the image width has to be pre-set the maximum width of the read head and not the width of the item that is being scanned. BTW, these maximum values are not specified in the documentation or samples. I actually had to dig thru a C++ sample application and the header files to find these settings. Thanks again.

                    _ 1 Reply Last reply
                    0
                    • E econner

                      Erik, I wanted to thank you again for the assistance. I was able to confirm that the code require the buffer and pinning/marshaling of the memory and it will not work correctly without the suggestions you provided. I also wanted to let you know what compounded the testing issue with the error was due to incomplete documentation. After getting the process to work, I was able to change certain settings and was able to cause the process to fail. Basically, the image class has to define the height and width to the MAXIMUM allowed image that the scanner can accept. Then the buffer size is set to the max height * max width * single/double scanner mode. Once the scanner returns the image, it adjusts the height and buffer values to be what are read in from the scanner. If the image sizes are specified incorrectly/too small, the API will cause a memory error. For example, the image of the scanned area may be 4" but the scanner's read head is capable of scanning a wider area. So the image width has to be pre-set the maximum width of the read head and not the width of the item that is being scanned. BTW, these maximum values are not specified in the documentation or samples. I actually had to dig thru a C++ sample application and the header files to find these settings. Thanks again.

                      _ Offline
                      _ Offline
                      _Erik_
                      wrote on last edited by
                      #24

                      You're welcome. I'm glad to know you finally solved it.

                      1 Reply Last reply
                      0
                      Reply
                      • Reply as topic
                      Log in to reply
                      • Oldest to Newest
                      • Newest to Oldest
                      • Most Votes


                      • Login

                      • Don't have an account? Register

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • World
                      • Users
                      • Groups