struct remains unaltered after passing by reference into unmanaged C DLL function

戏子无情 提交于 2019-12-10 10:30:08

问题


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#.


回答1:


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



回答2:


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?




回答3:


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.



来源:https://stackoverflow.com/questions/25586914/struct-remains-unaltered-after-passing-by-reference-into-unmanaged-c-dll-functio

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!