2

I'm writing a wrapper in C# for an unmanaged C DLL. In the DLL I have the following method which returns pointer struct (struct code near end of post):

struct zint_symbol *ZBarcode_Create()
{
    struct zint_symbol *symbol = (struct zint_symbol*)calloc(1, sizeof(struct zint_symbol));

    if (!symbol) return NULL;

    symbol->symbology = BARCODE_CODE128;
    strcpy(symbol->fgcolour, "000000");
    strcpy(symbol->bgcolour, "ffffff");
    strcpy(symbol->outfile, "out.png");
    symbol->scale = 1.0;
    symbol->option_1 = -1;
    symbol->option_3 = 928; // PDF_MAX
    symbol->show_hrt = 1; // Show human readable text
    return symbol;
}

The extern methods I am using are:

extern struct zint_symbol* ZBarcode_Create(void);
extern int ZBarcode_Encode_and_Buffer(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
extern int ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);

ZBarcode_Encode_and_Buffer renders an image and saves the bitmap in a variable in my struct called bitmap. ZBarcode_Encode_and_Print renders an image and saves it to the file system. They both return 0 when they are successful, and a number between 1-8 when they fail. Each time for me, they return 0.

My C# looks like the following:

[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(
 ref zint_symbol symbol,
 string input,
 int length,
 int rotate_angle);

[DllImport("zint.dll", EntryPoint = "ZBarcode_Encode_and_Print", CallingConvention = CallingConvention.Cdecl)]
public extern static int EncodeAndPrint(
 ref zint_symbol symbol,
 string input,
 int length,
 int rotate_angle);

public static void Render()
{
    // call DLL function to generate pointer to initialized struct
    zint_symbol s = (zint_symbol)

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

    // change some settings
    s.symbology = 71;
    s.outfile = "baro.png";
    s.text = "12345";

    String str = "12345";

    // DLL function call to generate output file using changed settings -- DOES NOT WORK --
    System.Console.WriteLine(ZintLib.EncodeAndBuffer(ref s, str, str.Length, 0));

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

    // notice that these variables are set in ZBarcode_Create()?
    Console.WriteLine("bgcolor=" + s.bgcolour + ", fgcolor=" + s.fgcolour + ", outfile=" + s.outfile);

    // these variables maintain the same values as when they were written to in ZBarcode_Create().
    if (s.errtxt != null)
        Console.WriteLine("Error: " + s.errtxt);
    else
        Console.WriteLine("Image size rendered: " + s.bitmap_width + " x " + s.bitmap_height);
}

All of the variables in s remain unchanged, even though the DLL is supposed to change some of them such as bitmap, bitmap_width, bitmap_height, etc.

I suspect that there are two copies of zint_symbol in memory; one that my C# code has (created by ZintLib.Create()) and another one that the DLL is writing to. I am certain that the library works correctly, however.

The C struct:

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

The C# struct:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    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 = 100)]
        public string errtxt;

        public float scale;

        public int option_1;
        public int option_2;
        public int option_3;
        public int show_hrt;
        public int input_mode;
        public int rows;
        public int width;
        public int bitmap_width;
        public int bitmap_height;

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

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

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

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

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

        public IntPtr bitmap;
        public IntPtr rendered;
    }

I have written a small Windows forms example (the DLLs are in there and also available here. The library documentation (page 18/61 explains the struct variables) is available here and the entire C source code is available here (zint-2.4.3.tar.gz). Files of particular interest are zint.h, library.c and datamatrix.c. The C source code is the same version of the DLLs.

Edit

The struct layouts appear to be mismatched. sizeof(zint_symbol) in C != sizeof(zint_symbol) in C#.

  • Without knowing what the protocol for the interface are, we can't really give you much detailed advice. What specs are you following? Do you have sample C or C++ code that succeeds? – David Heffernan Aug 30 '14 at 21:31
  • Is `int` size on both C++ and C# the same? – Vlad Aug 30 '14 at 21:34
  • @DavidHeffernan I don't have C code that succeeds in writing to the struct. I included a line in C# which succeeds in rendering a file, as well as documentation to the library (see edit). –  Aug 30 '14 at 21:35
  • @Vlad I believe int in both C++ and C# are 4 bytes. –  Aug 30 '14 at 21:36
  • @josh: In C#, it's guaranteed. In C++, it's platform- and implementation-specific. The question is whether this holds for _your_ platform. – Vlad Aug 30 '14 at 21:37
  • @josh: you can easily track which variables are transferred correctly by assigning some maker values to the fields your struct (say, `symbology = 101`, `height = 102`, etc.), setting a breakpoint in C code and checking which are the values as seen from C side. – Vlad Aug 30 '14 at 21:40
  • 1
    @Vlad The platform is Windows, on which `int` is 32 bits. – David Heffernan Aug 30 '14 at 21:41
  • @Vlad I cannot edit the C code. However, when I set `symbology=140` for instance the library does the right thing. The library takes in everything correctly, it just doesn't write to the struct. –  Aug 30 '14 at 21:43
  • @Vlad Much easier is for josh to do as Hans suggested in the last question and use the MS compiler to report `sizeof` for the structure. And then do the same on the C# side with `Marshal.SizeOf`. Since Hans suggested that already I can only assume that josh has already done so and confirmed that the sizes match. – David Heffernan Aug 30 '14 at 21:43
  • @josh If you don't have a spec, and have no sample code to work with, you are into reverse engineering. How are you supposed to know what to pass, let alone us know? – David Heffernan Aug 30 '14 at 21:44
  • @DavidHeffernan I see your point and I don't expect anyone to reverse-engineer anything. But, I figure that I wrote so little code for this wrapper (and the fact that it does not work) then there must be something I'm obviously doing wrong (like in my previous question). I'm looking into Hans' idea now, which I have not yet tried. –  Aug 30 '14 at 21:49
  • Beyond proving to yourself (and us) that the layout is correct, one wonders whether you did the basics of checking for errors in the return value of the function call. What value was returned? What does that mean? – David Heffernan Aug 30 '14 at 21:50
  • @DavidHeffernan the value for the function is 0 when it is successful. It returns 0 each time. –  Aug 30 '14 at 21:51
  • Ah, now I see your comment above. I can't believe you've not checked the layout is correct. That's got to be step 1. You are wasting everyone's time, especially yours, by not doing so. Please do that right now. And then please report the error code and tell us what it means. And finally summarise the relevant parts of the docs so that we can check how you initialized the struct. – David Heffernan Aug 30 '14 at 21:56
  • Comments crossed again. Return value of 0 means it's all good right! So, what went wrong? – David Heffernan Aug 30 '14 at 21:57
  • @DavidHeffernan What is wrong is that the variables that should be written to (within the struct) are not. For instance, if I purposely introduce a bad input, the function call returns a 5 and `zint_symbol.errtxt` ceases to be null. However, the array is completely empty. Likewise, any variable that is supposed to be written to within the struct is empty, blank or 0. –  Aug 30 '14 at 22:02
  • @DavidHeffernan I'm going to put my comment above in my post and try to summarize the doc as best as possible (though I fear making the post too long/dreadful). –  Aug 30 '14 at 22:03
  • 1
    The use of PtrToStructure looks odd to me. This means that although you seem to be expected to get the dll to create and destroy the struct, you subsequently copy to a managed struct. Surely the library expects you to pass it structs that it allocated. You don't do so. – David Heffernan Aug 30 '14 at 22:14
  • have you considered creating some c++/cli wrappers for this library? I've seen this done successfully in the past for complex unmanaged APIs – dbc Aug 30 '14 at 22:21
  • @DavidHeffernan I think you're on to something. `ZintLib.Create()` returns a pointer which I convert to a `zint_symbol` struct and then pass into my function as a parameter, but, `ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);` (the function in the library) implies that the parameter is a pointer. –  Aug 30 '14 at 22:30
  • @DavidHeffernan I created a pointer `testPtr` whose value is `ZintLib.Create()`, then marshalled it to a `zint_symbol` struct, then marshalled it back into `testPtr`. `EncodeAndPrint()` still ran successfully, but when I introduced a bad input, `errtxt` remained null (before it allocated the array, but it was empty). I don't think it's a copying issue. I added an example from the docs. –  Aug 30 '14 at 23:01
  • You still have not presented proof that the layouts match. – David Heffernan Aug 31 '14 at 16:41
  • 1
    @josh: The fact remains, though, that the overload of `PtrToStructure` that you are using here is a function returning a newly created object that you should - presumably - subsequently use. – 500 - Internal Server Error Sep 02 '14 at 14:44
  • Do the layouts match yet? – David Heffernan Sep 02 '14 at 18:31
  • 1
    I've been testing your code with the zint.dll (available here for those looking: http://sourceforge.net/projects/zint/) and the 1st remark is you should add Charset=Ansi to the DllImport (for the input parameters). The 2nd remark is the call crashes "sometimes" in native code for me, so it looks like the native DLL has some issues. Side note: the ZBarcode_Create only initializes the struct with default values. – Simon Mourier Sep 02 '14 at 21:40
  • @DavidHeffernan How can I prove that the layouts match? –  Sep 02 '14 at 22:23
  • @SimonMourier I agree, `Charset=Ansi` should be there. But adding/removing `Charset=Ansi` did not affect anything for me. –  Sep 02 '14 at 22:24
  • Start by doing what Hans and I described all that time ago in your previous question. Why do you ask for advice and then ignore it? We know how to solve these problems. We've done so time and time again. Do you want to learn or not? – David Heffernan Sep 02 '14 at 22:29
  • @DavidHeffernan if you're wondering whether Marshal.SizeOf(C# struct) == sizeof(C-struct) I cannot answer that because I cannot compile the C code (nor the header) because they have dependencies that I cannot get to work. –  Sep 02 '14 at 22:33
  • I suggest that you resolve those dependencies. I don't believe a bounty is enough. I think the problem is with the question. – David Heffernan Sep 02 '14 at 22:34
  • @DavidHeffernan I am going to try again to resolve the dependencies and check the size of the struct. Unfortunately I cannot close the question/put it on hold due to the bounty. Although if I manage to resolve the dependencies, compiling the header into a wrapper should be trivially easy. –  Sep 02 '14 at 23:55
  • @simon *so it looks like the native DLL has some issues*. Could be a problem with your calling code. – David Heffernan Sep 05 '14 at 06:53
  • @DavidHeffernan - My code works fine sometimes, and crashes sometimes. When it crashes, it crashes in the dll. And it's not my code, it's more or less the one in the question with the latest dll (with charset ansi, etc.). – Simon Mourier Sep 05 '14 at 11:48
  • @SimonMourier Crashing in the DLL can happen if the code is sent the wrong parameters. Without a clear spec of what must be sent to this function I feel it's impossible to draw conclusions. – David Heffernan Sep 05 '14 at 11:51
  • @DavidHeffernan - take a look at the zint code, there's nothing fancy at entry/api level, it just crashes somewhere (again, sometimes) deep in it, hence my remark. – Simon Mourier Sep 05 '14 at 12:14
  • @SimonMourier Yes, but you are supplying pointers into which it writes, and if there's a mismatch, then that's an obvious source of AVs. That said, step 1 surely has to be to get robust C code, because if there is indeed a problem with the unmanaged interface, then that will be insurmountable. And just be confounded by the pinvoke translation. So yes, I agree that starting with some C or C++ code is the way to tackle the problem. – David Heffernan Sep 05 '14 at 12:16
  • @DavidHeffernan I got the C code to compile (yay!). In C, `sizeof(zint_symbol)` is 33100 while in C#, `sizeof(zint_symbol)` is 26866. Now, why the structs do not match is beyond me. –  Sep 06 '14 at 15:32
  • Use offsetof in C to calculate offsets to members. On C# side delete members one by one and use SizeOf to see how it lays it out. Until you match the structs you cannot proceed. – David Heffernan Sep 06 '14 at 15:47
  • I am having the exact same issue with an unmanaged `FORTRAN` dll. – John Alexiou Sep 06 '14 at 16:05
  • @DavidHeffernan Ok, I fixed the discrepancies. Two marshalled arrays had incorrect constants. The sizeof C struct now matches the C# struct. This resolved the mysterious random numbers. However, this still did not resolve the original issue that the variables are not being written to. I'm still investigating... –  Sep 06 '14 at 16:37
  • How about I add an answer to that effect to help you award the bounty. Oh, never mind, Hans has beaten me to it. – David Heffernan Sep 06 '14 at 17:22

3 Answers3

2

You have an odd mistake here:

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

Which in the .h file is:

#define ZINT_ROWS_MAX 178
#define ZINT_COLS_MAX 178
    uint8_t encoded_data[ZINT_ROWS_MAX][ZINT_COLS_MAX];

Just declare it the same way:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 178 * 178)]
public byte[] encoded_data;

One more mistake, FILENAME_MAX is 260:

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

Now you should get a proper match:

static void Main(string[] args) {
    var len = Marshal.SizeOf(typeof(zint_symbol));              // 33100
    var offs = Marshal.OffsetOf(typeof(zint_symbol), "errtxt"); // 32984
}

and in the C test program:

#include <stddef.h>
//...
int main()
{
    size_t len = sizeof(zint_symbol);                // 33100
    size_t offs = offsetof(zint_symbol, errtxt);     // 32984
    return 0;
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
0

Looking at the differences in your P/Invoke declarations, we can see that the successful interface uses the [In, Out] attributes. you should add those to the other function, too.

see here: Are P/Invoke [In, Out] attributes optional for marshaling arrays?

Community
  • 1
  • 1
ths
  • 2,858
  • 1
  • 16
  • 21
0

The zint_symbol structure is defined differently in the PDF you linked to. It looks as though you have an incompatibility between your structure and the one the dll version is using.

For instance, in the PDF, scale appears after option_3, whereas you have it before option_1. And the last element in the PDF is bitmap_height, whereas yours continues on after that. There may be other differences, those are just what I noticed.

Although you don't have access to the dll source, in this kind of situation you can test your api by creating a simple dll yourself. Create a simple WinForms solution that has just a form with one button on it, and add a CPP Win32 project to the solution (project type: Win32 Project; application type: DLL). For simplicity, you can just add the struct and a stub function in the existing dllmain.cpp:

struct zint_symbol {
/*
.
.
.
*/
};

extern "C"
{
    __declspec(dllexport) int ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle)
    {
        symbol->bitmap_width = 456;

        symbol->errtxt[0] = 'e';
        symbol->errtxt[1] = 'r';
        symbol->errtxt[2] = 'r';
        symbol->errtxt[3] = 0;

        return 123;
    }
}

And in the WinForms button click event handler etc:

public static void doStuff()
{
    zint_symbol symbol = new zint_symbol();

    int retval = EncodeAndPrint(ref symbol, "input string", 123, 456);

    Console.WriteLine(symbol.bitmap_width);
    Console.WriteLine(symbol.errtxt);
}

Set a breakpoint in the dll to set/inspect other values as desired. To do this, you'll need to set "Enable unmanaged code debugging" on your C# project properties debug tab.

Using the above code with the C and C# structures you posted, I found no problems; values I set in the dll were apparent to the C# in the struct.

You can experiment further using this approach, but the bottom line is, you need the right struct definition for the dll version you're using, and currently your struct doesn't match the PDF you linked to. Hopefully you can find the right version of the dll, or the right struct definition for your existing dll.

Reg Edit
  • 6,719
  • 1
  • 35
  • 46