0

Problem:

I am writing a C# wrapper for an unmanaged C DLL. The DLL contains a struct zint_symbol, which contains a variable char[] *bitmap. In my C# struct I have public byte[] bitmap;.

This struct is passed in to an external function ZBarcode_Encode_and_Buffer which is supposed to render a PNG image and write the bitmap data to bitmap in zint_symbol.

When I invoke ZBarcode_Encode_and_Buffer and check bitmap, it remains null.

In fact, every variable in my C# struct that is supposed to be written by the DLL remains null.

What I've tried:

  1. Marshalling bitmap as a fixed-size array with a very large constant, i.e.,

    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 25454)]
    public byte[] bitmap; 
    

    ...which yields a SystemAccessViolation:

    An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll
    
    Additional information: Attempted to read or write protected memory. This is often an indication     that other memory is corrupt.
    
  2. Changing the type of ibtmap to char[] and string. This changed nothing.

  3. Adding the [In,Out] decorator to the struct parameter I am passing into my C# function.

  4. Changing the type of bitmap from byte[] to IntPtr.

What I cannot do:

I cannot seem to compile the library, despite having the source (the project is open source but abandoned years ago). This means I cannot change the C code.

Code/Working example

A small but complete working example can be downloaded here.

C# class containing my struct:

class ZintLib
{
    public struct zint_symbol
    {
        public int symbology;

        public int height;
        public int whitespace_width;
        public int border_width;

        public int output_options;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string fgcolour;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string bgcolour;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string outfile;

        public float scale;
        public int option_1;
        public int option_2;
        public int option_3;
        public int show_hrt;

        public int input_mode;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string text;

        public int rows;
        public int width;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string primary;

        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 25454)]
        public byte[] encoded_data;

        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 178)]
        public int[] row_height;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string errtxt;

        public byte[] bitmap;

        public int bitmap_width;
        public int bitmap_height;
        public IntPtr rendered;
    }

    [DllImport("zint.dll", EntryPoint = "ZBarcode_Create", CallingConvention = CallingConvention.Cdecl)]
    public extern static IntPtr Create();

    [DllImport("zint.dll", EntryPoint = "ZBarcode_Encode_and_Buffer", CallingConvention = CallingConvention.Cdecl)]
    public extern static int EncodeAndBuffer(
      [In, Out] ref zint_symbol symbol,
     String input,
     int length,
     int rotate_angle);
}

The function calling EncodeAndBuffer:

        // call DLL function to generate pointer to initialized struct
        ZintLib.zint_symbol s = (ZintLib.zint_symbol)

        // generate managed counterpart of struct
        Marshal.PtrToStructure(ZintLib.Create(), typeof(ZintLib.zint_symbol));

        // change some settings
        s.symbology = 71;
        s.outfile = "datamatrix.png";

        // DLL function call to generate output file using changed settings -- WORKS --
        //System.Console.WriteLine(ZintLib.EncodeAndPrint(ref s, "12345", 5, 0));

        // DLL function to generate data in s.bitmap, s.bitmapheight, s.bitmapwidth -- DOES NOT WORK managed struct is unaltered --
        System.Console.WriteLine(ZintLib.EncodeAndBuffer(ref s, (String)"12345", 5, 0));

        if (s.bitmap == null)
            Console.WriteLine("bitmap is null.");
        else
            Console.WriteLine("bitmap is not null.");

The struct and export methods in C:

struct zint_symbol {
    int symbology;
    int height;
    int whitespace_width;
    int border_width;
    int output_options;
    char fgcolour[10];
    char bgcolour[10];
    char outfile[256];
    float scale;
    int option_1;
    int option_2;
    int option_3;
    int show_hrt;
    int input_mode;
    unsigned char text[128];
    int rows;
    int width;
    char primary[128];
    unsigned char encoded_data[178][143];
    int row_height[178]; /* Largest symbol is 177x177 QR Code */
    char errtxt[100];
    char *bitmap;
    int bitmap_width;
    int bitmap_height;
    struct zint_render *rendered;
};

#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(_MSC_VER)
#  if defined (DLL_EXPORT) || defined(PIC) || defined(_USRDLL)
#    define ZINT_EXTERN __declspec(dllexport)
#  elif defined(ZINT_DLL)
#    define ZINT_EXTERN __declspec(dllimport)
#  else
#    define ZINT_EXTERN extern
#  endif
#else
#  define ZINT_EXTERN extern    
#endif

ZINT_EXTERN struct zint_symbol *ZBarcode_Create(void);
ZINT_EXTERN void ZBarcode_Clear(struct zint_symbol *symbol);
ZINT_EXTERN void ZBarcode_Delete(struct zint_symbol *symbol);

ZINT_EXTERN int ZBarcode_Encode(struct zint_symbol *symbol, unsigned char *input, int length);
ZINT_EXTERN int ZBarcode_Encode_File(struct zint_symbol *symbol, char *filename);
ZINT_EXTERN int ZBarcode_Print(struct zint_symbol *symbol, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_File_and_Print(struct zint_symbol *symbol, char *filename, int rotate_angle);

ZINT_EXTERN int ZBarcode_Render(struct zint_symbol *symbol, float width, float height);

ZINT_EXTERN int ZBarcode_Buffer(struct zint_symbol *symbol, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_and_Buffer(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_File_and_Buffer(struct zint_symbol *symbol, char *filename, int rotate_angle);

ZINT_EXTERN int ZBarcode_ValidID(int symbol_id);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* ZINT_H */

Note: A similar question was asked but never resolved in this thread. I've been scratching my head over this for a week. Any ideas?

  • You cannot just arbitrarily keep all of those fields out of your C# struct declaration. You need to make sure that Marshal.SizeOf(C#-struct) == sizeof(C-struct) first. – Hans Passant Aug 29 '14 at 19:25
  • @HansPassant I left the fields out for brevity of the post, but they are in the working example. I put them back in. Forgive my ignorance, but how can I ensure the size of the struct matches? –  Aug 29 '14 at 19:32
  • Use Marshal.SizeOf in the C# and sizeof in the C++ to check the sizes match – David Heffernan Aug 29 '14 at 19:35
  • @DavidHeffernan I cannot edit the C++ code. –  Aug 29 '14 at 19:36
  • You can compile the header file into a program that emits the value of sizeof(struct zint_symbol) – David Heffernan Aug 29 '14 at 19:38
  • @HansPassant It turns out that Marshal.SizeOf(C#-struct) != sizeof(C-struct). See: http://stackoverflow.com/questions/25586914/struct-remains-unaltered-after-passing-by-reference-into-unmanaged-c-dll-functio –  Sep 06 '14 at 15:45

1 Answers1

0

You are going to have to declare the bitmap member as IntPtr and marshal it manually.

If the caller allocates the memory do so with Marshal.AllocHGlobal. And then use Marshal.Copy to copy between the unmanaged memory and your managed representation of the bitmap. Or pin the managed byte array that represents the bitmap.

If the callee allocates the memory then you just need Marshal.Copy to copy from unmanaged to managed.

It's quite a complex interface and it seems plausible that there will be other errors. I've just looked at the bitmap field since it's what you asked about.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I put the missing fields into my post, see edit (I left them out to make the post shorter). –  Aug 29 '14 at 19:34
  • Yup, I can see that. I tweaked my answer to match. – David Heffernan Aug 29 '14 at 19:37
  • Very interesting. I declared `bitmap` as `IntPtr` and in the c# caller I used `Marshal.AllocHGlobal` followed by `Marshal.Copy`. Now I am seeing data in `bitmap` of 154 bytes (the appropriate size for the image I'm creating).However, the data in `bitmap` isn't a valid PNG. Is there something in my code that could be screwing it up? –  Aug 29 '14 at 20:34
  • Hard for me to say. I don't know the library. – David Heffernan Aug 30 '14 at 02:06
  • I figured it out--the problem is still not solved. Each time I run the program I get a different (random) image. I would assume that this is because the C code never writes data to it. The C library does seem to allocate the right space for the array in memory however. –  Aug 30 '14 at 02:17
  • You know the library. I don't. You have docs. I don't. I just told you how to marshal, as you asked. I answered the question. Now you need to solve the problem. – David Heffernan Aug 30 '14 at 02:48
  • Never mind. I'm sure you'll be able to solve your problem for yourself. All I can try to do is to answer the question that you asked. Which I believe I did. – David Heffernan Aug 30 '14 at 04:14