【问题标题】:struct remains unaltered after passing by reference into unmanaged C DLL function通过引用传递给非托管 C DLL 函数后,结构保持不变
【发布时间】:2014-09-07 01:04:43
【问题描述】:

我正在用 C# 为非托管 C DLL 编写一个包装器。在 DLL 中,我有以下方法返回指针结构(帖​​子末尾附近的结构代码):

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

我使用的外部方法是:

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 渲染图像并将位图保存在我的结构中名为bitmap 的变量中。 ZBarcode_Encode_and_Print 渲染图像并将其保存到文件系统。成功时它们都返回 0,失败时返回 1-8 之间的数字。每次对我来说,它们都返回 0。

我的 C# 如下所示:

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

s 中的所有变量都保持不变,即使 DLL 应该更改其中的一些变量,例如 bitmapbitmap_widthbitmap_height 等。 p>

我怀疑内存中有两个zint_symbol 的副本;我的 C# 代码拥有的一个(由ZintLib.Create() 创建)和 DLL 正在写入的另一个。但是,我确定该库可以正常工作。

C 结构:

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

C# 结构:

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

我写了一个小的Windows forms example(DLL 在那里,还有available here。库文档(第 18/61 页解释了结构变量)is available here 和整个 C 源代码可用here (zint-2.4.3.tar.gz) . 特别感兴趣的文件是 zint.h、library.c 和 datamatrix.c。C 源代码是相同版本的 DLL。

编辑

结构布局似乎不匹配。 C 中的 sizeof(zint_symbol) != C# 中的 sizeof(zint_symbol)。

【问题讨论】:

  • 在不知道接口的协议是什么的情况下,我们真的不能给你太多详细的建议。你遵循什么规格?您是否有成功的示例 C 或 C++ 代码?
  • @Vlad 平台为 Windows,int 为 32 位。
  • PtrToStructure 的使用对我来说看起来很奇怪。这意味着尽管您似乎希望让 dll 来创建和销毁结构,但您随后会复制到托管结构。当然,库希望您将其分配的结构传递给它。你不这样做。
  • @josh:但事实仍然是,您在此处使用的 PtrToStructure 的重载是一个返回新创建的对象的函数,您应该 - 大概 - 随后使用它。
  • 我一直在使用 zint.dll 测试您的代码(可以在此处查看:sourceforge.net/projects/zint),第一条评论是您应该将 Charset=Ansi 添加到 DllImport(用于输入参数)。第二条评论是调用在本机代码中“有时”崩溃,所以看起来本机 DLL 有一些问题。旁注:ZBarcode_Create 仅使用默认值初始化结构。

标签: c# c pinvoke dllimport


【解决方案1】:

这里有一个奇怪的错误:

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

.h 文件中的内容是:

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

只需以同样的方式声明即可:

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

还有一个错误,FILENAME_MAX 是 260:

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

现在你应该得到一个正确的匹配:

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

在 C 测试程序中:

#include <stddef.h>
//...
int main()
{
    size_t len = sizeof(zint_symbol);                // 33100
    size_t offs = offsetof(zint_symbol, errtxt);     // 32984
    return 0;
}

【讨论】:

    【解决方案2】:

    查看您的 P/Invoke 声明中的差异,我们可以看到成功的接口使用了 [In, Out] 属性。您也应该将它们添加到其他函数中。

    请看这里:Are P/Invoke [In, Out] attributes optional for marshaling arrays?

    【讨论】:

    • 默认的 ref 参数是 [In, Out]
    【解决方案3】:

    zint_symbol 结构在您链接到的 PDF 中的定义不同。看起来您的结构与 dll 版本正在使用的结构不兼容。

    例如,在 PDF 中,scale 出现在 option_3 之后,而您在 option_1 之前出现。 PDF 中的最后一个元素是bitmap_height,而您的则继续。可能还有其他差异,这些只是我注意到的。

    虽然您无权访问 dll 源,但在这种情况下,您可以通过自己创建一个简单的 dll 来测试您的 api。创建一个简单的 WinForms 解决方案,其中只有一个带有一个按钮的表单,并将 CPP Win32 项目添加到解决方案中(项目类型:Win32 项目;应用程序类型:DLL)。为简单起见,您可以在现有的 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;
        }
    }
    

    并在 WinForms 按钮单击事件处理程序等:

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

    在 dll 中设置断点以根据需要设置/检查其他值。为此,您需要在 C# 项目属性调试选项卡上设置“启用非托管代码调试”。

    将上面的代码与您发布的 C 和 C# 结构一起使用,我没有发现任何问题;我在 dll 中设置的值对于结构中的 C# 是显而易见的。

    您可以使用这种方法进一步试验,但最重要的是,您需要为您正在使用的 dll 版本提供正确的结构定义,并且当前您的结构与您链接到的 PDF 不匹配。希望您能找到正确的 dll 版本,或者为您现有的 dll 找到正确的结构定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-19
      • 1970-01-01
      • 2015-05-28
      • 1970-01-01
      相关资源
      最近更新 更多