【问题标题】:C++ string to C# using a struct使用结构的 C++ 字符串到 C#
【发布时间】:2020-03-26 22:23:19
【问题描述】:

有没有办法通过使用结构将字符串从 C++ 发送到 C#?我有一个带有图像数据(数据和大小)的结构,在这个结构中我也想从图像中获得条形码数据。我曾尝试通过 C# 中的 IntPtr 来执行此操作,但返回的是乱码并通过 Stringbuilder,但随后无法编组结构。请参阅下面的所有有问题的代码

C#结构

public struct ImageInfo
{
    public IntPtr data; // image data, works
    public int size;    // image data, works

    // string
    //public IntPtr barcodeType; // return gibberish
    //public IntPtr barcodeData; // return gibberish

    public StringBuilder barcodeType; //Cannot marshal field 'barcodeType' of type 'ImageProcessingOpenCv.ImageInfo'
    public StringBuilder barcodeData; //Cannot marshal field 'barcodeData' of type 'ImageProcessingOpenCv.ImageInfo'
}

C# 导入:

[DllImport("AlgorithmsCpp.dll", EntryPoint = "ScanBarcode", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void ScanBarcode(byte[] data, int dataLen, ref ImageInfo imTemplate);

调用 C++ 以及我如何尝试从 IntPtr 获取字符串:

ImageInfo imInfo = new ImageInfo();
AlgorithmCpp.ScanBarcode(sourceImagePixels, sourceImagePixels.Length, ref imInfo); // wont work with StringBuilder.

string stringa = Marshal.PtrToStringAnsi(imInfo.barcodeType); // result: gibberish
stringa = Marshal.PtrToStringAuto(imInfo.barcodeType);        // result: gibberish
stringa = Marshal.PtrToStringBSTR(imInfo.barcodeType);        // result: gibberish
stringa = Marshal.PtrToStringUni(imInfo.barcodeType);         // result: gibberish
stringa = Marshal.PtrToStringUTF8(imInfo.barcodeType);        // result: gibberish

// example what is returned: "\0\0\0\0\0\0\0\0\f\0\0\0"

C++ 结构:

struct ImageInfo
{
    unsigned char* data;
    int size;

    // String
    //unsigned char* barcodeType; // for IntPtr, return gibberish
    //unsigned char* barcodeData; // for IntPtr, return gibberish

    char* barcodeType; // for StringBuilder
    char* barcodeData; // for StringBuilder
};

我如何尝试从 C++ 返回字符串

#define DllExport extern "C" __declspec(dllexport)

DllExport void ScanBarcode(unsigned char* data, int dataLen, ImageInfo& imInfo)
{
    // do stuff to get barcode

    // for IntPtr
    std::string type = symbol->get_type_name();
    std::string data = symbol->get_data();

    imInfo.barcodeType = (unsigned char*)type.c_str();
    imInfo.barcodeData = (unsigned char*)data.c_str();

    // For StringBuilder
    strcpy(imInfo.barcodeData, symbol->get_data().c_str());
    strcpy(imInfo.barcodeType, symbol->get_type_name().c_str());
}

我希望通过上面的代码,我已经让您了解我尝试了什么以及我想要拥有什么。如果您需要任何进一步的信息,请告诉我,我会尽力提供。

【问题讨论】:

  • @Fildor 阅读互联网寻找可能的解决方案。我多次看到 StringBuilder,所以我厌倦了。我不知道为什么我应该或不应该使用它。
  • 对于IntPtr方法,你将imInfo.barcodeType/Data分配给局部变量type/data内部指针,在函数退出时被删除,因此指向乱码。
  • 每个specification char * 可以编组到stringStringBuilder
  • @PavelAnikhouski 是的,没有意识到这一点。可能相关:stackoverflow.com/a/15274893/982149stackoverflow.com/a/1450756/982149
  • @wes 看看 msdn samples,尝试用 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 装饰你的 struct,因为你使用的是 ansi 字符串。或者在字符串字段中添加MarshalAs

标签: c# c++ struct dllimport


【解决方案1】:

C++ 代码错误。

imInfo.barcodeType = (unsigned char*)type.c_str();
imInfo.barcodeData = (unsigned char*)data.c_str();

c_str() 给你一个指向字符串开头的指针,是的。你可以把它传递给一个函数,很好。但是你不能退货!谁拥有记忆?它的寿命是多少?什么时候解除分配? C++ 没有自动内存管理——你不能只返回对象和指针并期望它们神奇地工作。您甚至不能依靠这种不良行为来产生错误,正如您刚刚看到的那样——您会得到垃圾;有时你得到了正确的东西,有时你得到了错误的东西,有时你引入了一个可怕的安全漏洞。

ScanBarcode 返回时,typedata 被销毁。 c_str() 返回的指针并不指向您认为它指向的数据(不管c_str() 的内部实现如何)。您需要将该数据复制到共享内存,并确保它已正确释放。在非托管世界中传递内存不是一件容易的事。这是最初设计托管系统和 C# 等语言的重要原因之一。

设计互操作并非易事。以实际精心设计的 C API 为例。一件重要的事情是返回字符串的长度,例如(你很少想传递 just char*,而不指定长度;如果你不指定长度,这样做简直太疯狂了'不拥有被指向的内存)。首先,决定谁应该分配和释放内存(这通常应该是相同的)。

您已经可以看出这有点问题。 ImageInfo 由调用者分配。但是char*fields 是由被调用者分配的!没有办法明智地将对象的生命周期限制在 C++ 或 C# 端(同样,问题不是 C# - C++ 调用者也会有同样的问题)。过去处理这种情况的常用方法是使用预先分配的缓冲区——也就是说,imInfo.barcodeType 不是指向字符串的指针,而是一个数组。 C# 结构将如下所示:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct ImageInfo
{
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255 /* this must be synchronized with the C++ code! */)]
  public string barcodeType;
}

C++ 方面会做这样的事情(不要在生产代码中使用它,在某些方面几乎肯定是错误的 - 我的 C++ 非常生锈;请咨询 C++ 程序员):

struct ImageInfo
{
  char barcodeType[255];
}

if (type.barcodeType.length() < 254) 
{
  strcpy(imInfo.barcodeType, type.c_str());
}

同样,这是非常幼稚的,可能是错误的。这只是为了展示一种在调用者和被调用者之间传递数据的方法。好好照顾。

【讨论】:

  • 感谢您的解释,我已经从 C++ 中删除了 c_str() 并用 std::copy 替换它:std::copy(symbol-&gt;get_type_name().begin(), symbol-&gt;get_type_name().end(), imInfo.barcodeType);。可悲的是,这会返回相同的乱码。
  • @wes 您仍在随机尝试 API 并希望一切顺利。不受管理的世界不是这样运作的。内存管理很复杂。请参阅我的编辑以了解可行的方法大纲(但可能仍然非常错误)。 C# 方面是容易的部分; C++ 方面需要一个真正的 C++ 程序员。你所做的在 C++ 中是错误的,在 C++ 中调用该方法会给你同样的胡言乱语。在这种情况下,互操作不是罪魁祸首。
  • 感谢您的清晰解释。我在大学学习 C++ 大约一年了,但离这个还很远。我想我已经超出了我的深度试图让它发挥作用。
  • @wes 我做 C++ 已经 3 年了,很久以前做过一些游戏和嵌入式开发等。我仍然不敢写问题的 C++ 方面。我很清楚其中的陷阱和雷区,但我还不足以设计和实现万无一失的东西。如果必须,我会先重新熟悉内存模型、所有可能的故障模式等,然后再尝试编写代码。请注意,这仍将是 3 行代码 - 但这些行比 C# 中的代码要正确得多:)
【解决方案2】:

StringBuilder不是字符串。

将结构更改为:

public struct ImageInfo
{
    public IntPtr data; // image data, works
    public int size;    // image data, works

    // string
    //public IntPtr barcodeType; // return gibberish
    //public IntPtr barcodeData; // return gibberish

    public string barcodeType;
    public string barcodeData;
}

应该可以工作,因为编译器现在有一个已知的值(某种)类型,它可以传递给你的 C++ 结构。 StringBuilder 不起作用,因为这是一个无法转换为字符串的对象,编译器必须知道调用 .ToString() 方法。

【讨论】:

  • 如果不更改任何其他内容,程序只会在运行时崩溃,而只会给出退出代码:-1073740940 (0xc0000374)。我喜欢这个想法,但是我在互联网上看到的是不能将 C++ 字符串直接发送到 C# 字符串,不过我可能错了。
  • @wes C++ 字符串 (std::string) 不能; C字符串当然可以。您确实需要添加有关 C++ 结构的更多信息 - 结构的 typedef 并没有告诉我们有关 char* 字段中实际内容的任何信息。我们甚至不知道它是否 supposed 是一个字符串(char* 也用于字节数组),如果是,格式、编码等是什么。你不能只是随机尝试编组方法,直到找到一种似乎有效的方法。如果这是公共 API 的一部分(并且您没有源代码),则所有这些都应记录在案。
  • @Luaan 在两个 char* 中都可以有数字和字母。示例:“39123439”和“代码 128”。我不确定您对格式和编码的含义。我添加到帖子中的是所有有问题的代码。我遗漏了一些对这个问题不重要的代码以及有效的代码。
  • 要记住的一点是,C# 字符串是 Unicode (UTF-16)、C++ 和 C 字符串以及 ASCII,但您可以轻松地将 UTF-8 放入其中。因此,另一种可能性是将 byte[]s 放入您的 C# 结构中并使用 C# 编码类设置它们的值,方法是从 UTF-16 转换为 UTF-8
猜你喜欢
  • 2016-01-05
  • 2011-06-27
  • 2014-04-28
  • 2013-10-22
  • 1970-01-01
  • 2012-04-27
  • 1970-01-01
  • 2011-08-11
  • 1970-01-01
相关资源
最近更新 更多