使用 C# 的 c 联合编组复杂的 C 结构
我迫切希望为 C# 正确编组复杂的 C 数据类型。我已经阅读了有关该主题的所有其他帖子,但我已经没有想法了,尽管在我看来,它非常接近解决方案。
主要问题是 c 结构体具有两种不同结构类型的联合。仅使用基本类型和一种包括数组的类型,这会引起麻烦。
我创建了一个例子来展示这种情况。让我担心的结构称为 dataStreamConfiguration。 C 代码如下所示,所涉及的结构位于示例 C 代码的底部:
#include "stdint.h"
#include "stddef.h"
typedef enum viewCapEnum {
X = 0,
}viewCapEnum;
typedef struct fraction{
uint8_t nominator;
uint8_t denominator;
}fraction;
typedef struct comSize{
fraction A;
fraction B;
}comSize;
typedef enum someEnum{
A = 0,
B,
C,
D
}someEnum;
typedef struct someSize{
fraction X;
fraction Y;
}someSize;
typedef struct featTemplateCap{
someEnum A;
someSize Size;
}featTemplateCap;
typedef struct featTypeCap{
someEnum AB;
someSize CD;
}featTypeCap;
typedef struct viewCap{
uint8_t A;
uint8_t B;
size_t BCount;
viewCapEnum ViewCapEnum[50];
comSize MinComSize;
size_t CapaCount;
featTemplateCap TemplCap[14];
size_t TypeCapaCount;
featTypeCap FeatTypeCapa[14];
uint8_t GCount;
}viewCap;
typedef struct featX{
uint16_t A;
uint16_t B;
int16_t C;
int16_t D;
}featX;
typedef struct pathCap{
uint8_t Count;
uint8_t Size;
featX Feat;
}pathCap;
typedef struct dataStreamConfiguration{
size_t FeatureSelector;
union {
viewCap AsViewCap;
pathCap AsPathCap;
}dataStream;
}dataStreamConfiguration;
C 和 C# 世界之间的数据类型编组几乎适用于除此 dataStreamConfiguration 结构之外的所有结构。所以我得到了以下代码,其中不是将联合映射(以某种方式)到 c#,而是将两种数据类型依次放置。很明显这不能正常工作。它看起来像这样:
public unsafe struct UInt32Struct {
public UInt32 value;
}
public unsafe struct fraction{
public Byte nominator;
public Byte denominator;
}
public unsafe struct comSize{
public fraction A;
public fraction B;
}
public unsafe struct someSize{
public fraction X;
public fraction Y;
}
public unsafe struct featTemplateCap{
public UInt32 A;
public someSize Size;
}
public unsafe struct featTypeCap{
public UInt32 AB;
public someSize CD;
}
public unsafe struct viewCap{
public Byte A;
public Byte B;
public UInt16 BCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50]
public UInt32Struct[] ViewCapEnum;
public comSize MinComSize;
public UInt16 CapaCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 14]
public featTemplateCap[] TemplCap;
public UInt16 TypeCapaCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 14]
public featTypeCap FeatTypeCapa[14];
public Byte GCount;
}
public unsafe struct featX{
public UInt16 A;
public UInt16 B;
public Int16 C;
public Int16 D;
}
public unsafe struct pathCap{
public Byte Count;
public Byte Size;
public featX Feat;
}
public unsafe struct dataStreamConfiguration{
public UInt16 FeatureSelector;
public viewCap AsViewCap;
public pathCap AsPathCap;
}
因此,为了获得 c# 的联合,我遇到了 LayoutKind.Explicit 并执行了以下操作:
[StructLayout(LayoutKind.Explicit)]
public unsafe struct dataStreamConfiguration{
[FieldOffset(0)]
public UInt16 FeatureSelector;
[FieldOffset(2)]
public viewCap AsViewCap;
[FieldOffset(2)]
public pathCap AsPathCap;
}
由于对象类型的对齐,这不起作用,这些对象类型未正确对齐或被非对象字段重叠。谷歌搜索了很多。通过[StructLayout(LayoutKind.Explicit, Pack=4)]
将对齐方式调整为4。但是,4、8、16、32,无论我选择什么对齐方式,我在运行时都会遇到相同的错误 - 错误对齐或重叠问题。
接下来我做的事情(我感到非常幸运)是为 viewCap
结构中的所有数组展开 C# 数据类型中的所有数组。据我所知,这可能会导致对齐问题。嗯,没用。而且我发现内存已经被修改了,所以我找不到我在C中看到的值现在出现在C#中。 C# 中的大多数值都是 0。好的。 为了摆脱这种内存修改的东西,我将 C# 放入所有其他结构 [StructLayout(LayoutKind.Sequential)]
中,以保持元素在 C 中的顺序。遗憾的是,它没有多大帮助,我也找不到 c# 中 c-struct 的值。然而,当我摆脱联合并删除 AsViewCap
或 AsPathCap
(我盲目愤怒的软弱时刻)时,它终于起作用了。好吧,但这不是解决方案。
最后的帮助是尝试使用 IntPtr
,因此我创建了一个名为 dataStreamConfigurationPtr
的新结构:
public unsafe struct dataStreamConfigurationPtr{
public UInt16 FeatureSelector;
public void* Ptr;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct dataStreamConfiguration{
public UInt16 FeatureSelector;
public viewCap AsViewCap;
public pathCap AsPathCap;
}
而不是使用 StructLayout.Explicit
拥有重叠内存我使用 void*
来指向非托管内存位置。为此,我使用旧的结构定义来获取内存,而不是使用联合,而是使用第一个版本,其中两种类型都相互布局。我们的想法是这样使用它:
MyFunction(dataStreamConfigurationPtr X, int Status){
//Create obj and appropraite space for data
dataStreamConfiguration DataStream = new dataStreamConfiguration();
DataStream.FeatureSelector = X.FeatureSelector;
unsafe{
IntPtr Ptr = new IntPtr(&X.Ptr);
DataStream.AsViewCap = Marshal.PtrToStructure<viewCap>(Ptr);
DataSteram.AsPathCap = Marshal.PtrToStructure<pathCap>(Ptr);
}
WCFCallback(DataStream, Status);
}
现在 IntPtr 指向正确的内存,但是,这仅适用于结构的第一项。因此,对于 viewCap
,第一个项目 A
具有正确的数据,而项目 B、BCount 等所有其他项目似乎至少具有未对齐的值或意外值。我现在非常绝望该怎么办,我觉得我已经非常接近解决方案,但不知道如何从 c 到 c# 获取结构的其他数据。
非常欢迎任何建议和意见!
此致, 托比亚斯
i am desperate to get a complex c datatype correctly marshaled for C#. I already read all the other posts regarding that topic and i am running out of ideas although it seems to me to be quite close to the solution.
The main issue is that the c-struct is having a union of two different struct types. On with only basic types and one including arrays, which causes trouble.
I have created an example to showcase the situation. The struct worring me is called dataStreamConfiguration
. The c code looks like this, the struct in question is at the bottom of the example c-code:
#include "stdint.h"
#include "stddef.h"
typedef enum viewCapEnum {
X = 0,
}viewCapEnum;
typedef struct fraction{
uint8_t nominator;
uint8_t denominator;
}fraction;
typedef struct comSize{
fraction A;
fraction B;
}comSize;
typedef enum someEnum{
A = 0,
B,
C,
D
}someEnum;
typedef struct someSize{
fraction X;
fraction Y;
}someSize;
typedef struct featTemplateCap{
someEnum A;
someSize Size;
}featTemplateCap;
typedef struct featTypeCap{
someEnum AB;
someSize CD;
}featTypeCap;
typedef struct viewCap{
uint8_t A;
uint8_t B;
size_t BCount;
viewCapEnum ViewCapEnum[50];
comSize MinComSize;
size_t CapaCount;
featTemplateCap TemplCap[14];
size_t TypeCapaCount;
featTypeCap FeatTypeCapa[14];
uint8_t GCount;
}viewCap;
typedef struct featX{
uint16_t A;
uint16_t B;
int16_t C;
int16_t D;
}featX;
typedef struct pathCap{
uint8_t Count;
uint8_t Size;
featX Feat;
}pathCap;
typedef struct dataStreamConfiguration{
size_t FeatureSelector;
union {
viewCap AsViewCap;
pathCap AsPathCap;
}dataStream;
}dataStreamConfiguration;
The marshalling of datatypes between C and the C# world is working for almoust all but this dataStreamConfiguration struct. So I got the following code, where instead of mapping (somehow) a union to c# both datatypes have been put one after another. So clearly this was not working correctly. It looked like that:
public unsafe struct UInt32Struct {
public UInt32 value;
}
public unsafe struct fraction{
public Byte nominator;
public Byte denominator;
}
public unsafe struct comSize{
public fraction A;
public fraction B;
}
public unsafe struct someSize{
public fraction X;
public fraction Y;
}
public unsafe struct featTemplateCap{
public UInt32 A;
public someSize Size;
}
public unsafe struct featTypeCap{
public UInt32 AB;
public someSize CD;
}
public unsafe struct viewCap{
public Byte A;
public Byte B;
public UInt16 BCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50]
public UInt32Struct[] ViewCapEnum;
public comSize MinComSize;
public UInt16 CapaCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 14]
public featTemplateCap[] TemplCap;
public UInt16 TypeCapaCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 14]
public featTypeCap FeatTypeCapa[14];
public Byte GCount;
}
public unsafe struct featX{
public UInt16 A;
public UInt16 B;
public Int16 C;
public Int16 D;
}
public unsafe struct pathCap{
public Byte Count;
public Byte Size;
public featX Feat;
}
public unsafe struct dataStreamConfiguration{
public UInt16 FeatureSelector;
public viewCap AsViewCap;
public pathCap AsPathCap;
}
So to get the union to c# I came across the LayoutKind.Explicit and did following:
[StructLayout(LayoutKind.Explicit)]
public unsafe struct dataStreamConfiguration{
[FieldOffset(0)]
public UInt16 FeatureSelector;
[FieldOffset(2)]
public viewCap AsViewCap;
[FieldOffset(2)]
public pathCap AsPathCap;
}
This was not working due to the alignment of the object types, which are incorrectly aligned or overlapped by non-object fields.. I googled a lot. Adjusted the alignment to 4 by [StructLayout(LayoutKind.Explicit, Pack=4)]
. However, 4,8,16,32, whatever alignment i have choosen, I got the same error during runtime - incorrectly aligned or overlapped issue.
Next thing I did - I felt quite lucky about - was to unroll all the arrays in C# datatype for all the arrays in viewCap
struct. As I have read that this might cause alignment issues. Well, It didn't work. And I found that the memory has been modified, so I could not find the values I have seen in C appearing now in C#. Most of the values in C# are 0. Ok.
To get rid of this memory modification stuff I put in C# to all other structs [StructLayout(LayoutKind.Sequential)]
to keep the order of elements as they are in C. Sadly it didn't help much, I could not find the values of the c-struct in c# either. However, it was finally working, when I got rid of the union and deleted either AsViewCap
or AsPathCap
(my weak moment of blind rage). Ok, but that was not the solution.
Last help was having a try with IntPtr
, so i have created a new struct called dataStreamConfigurationPtr
:
public unsafe struct dataStreamConfigurationPtr{
public UInt16 FeatureSelector;
public void* Ptr;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct dataStreamConfiguration{
public UInt16 FeatureSelector;
public viewCap AsViewCap;
public pathCap AsPathCap;
}
Instead of having an overlapping memory with StructLayout.Explicit
I used an void*
to point to the unmanaged memory location. For this I used the old struct definition to get the memory and instead having a union I took the first version where both types are laid out one over another. The idea was to use it like that:
MyFunction(dataStreamConfigurationPtr X, int Status){
//Create obj and appropraite space for data
dataStreamConfiguration DataStream = new dataStreamConfiguration();
DataStream.FeatureSelector = X.FeatureSelector;
unsafe{
IntPtr Ptr = new IntPtr(&X.Ptr);
DataStream.AsViewCap = Marshal.PtrToStructure<viewCap>(Ptr);
DataSteram.AsPathCap = Marshal.PtrToStructure<pathCap>(Ptr);
}
WCFCallback(DataStream, Status);
}
Now the IntPtr is pointing to the right memory, however, this works only for the first item of the structs. So for viewCap
the first item A
has its correct data whereas item B, BCount,.. all the other item seem to have at least misalligned values or accidental values.. I am quite desperate what to do now, i feel i am so close to a solution but have no idea how to get the other data of the struct from c to c#.
Any suggestions and comments are highly welcome!
Best regards,
Tobias
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我假设您有两个用例,并且希望在 C# 端将基于
FeatureSelector
的联合部分解释为AsViewCap
或AsPathCap
。这意味着我假设您不打算进行类型双关。
然后可以在托管 C# 端创建两个结构:
然后您只能先检查
FeatureSelector
并根据结果将其解释为dataStreamConfigurationAsViewCap
或解释为dataStreamConfigurationAsPathCap
代码>.尺寸
您在 C 端有多个带有
size_t
的变量(FeatureSelector
、BCount
、CapaCount
、TypeCapaCount),您将其全部映射到
UInt16
,这是错误的。 UInt16 是 C 标准中的最小大小,但通常的实现(尤其是在运行 .NET 的平台上)更大,另请参阅此 不错的答案。例如在我的 macOS 机器上它是 8 个字节。也许从一个较小的测试用例开始并逐步扩展它是一个好主意,以便您可以识别此类问题。当您遇到问题时,您可以更轻松地创建最小的、完整的且可测试的示例。
这个方向的一种方法可能如下:
小测试用例
some.h
some.c
UnionFromC.cs
测试输出
I would assume you have two use cases and want to interprete the union part based
FeatureSelector
either asAsViewCap
or asAsPathCap
on C# side.That means I assume that you don't intend to do type punning.
One could create two structs then on the managed C# side:
You could then only inspect
FeatureSelector
first and based on the result either interprete itdataStreamConfigurationAsViewCap
or asdataStreamConfigurationAsPathCap
.Size
You have several variables with
size_t
on C side (FeatureSelector
,BCount
,CapaCount
,TypeCapaCount
) which you map all toUInt16
, which is wrong. UInt16 is the minimum size in the C standard, but the usual implementations especially on platforms running .NET are larger, see also this nice answer. For example on my macOS machine it is 8 bytes.Maybe it would be a good idea to start with a smaller test case and expand it step by step so that you can identify such problems. And when you encounter a problem, you can more easily create a minimal, complete and testable example.
One approach in this direction could be the following:
Small Test Case
some.h
some.c
UnionFromC.cs
Output of the Test
这是如何将数据从 C/C++ 获取到 C# 的一种解决方案。在这里我将描述我做错了什么以及需要注意什么。
回想一下,我的要求一直是(仍然是)在 C/C++ 中表示为联合的任何数据都需要在 C# 中表示为这样。这意味着以下结构:
AsViewCap 中的任何数据都必须在 AsPathCap 中具有其类型的表示形式,因为它的内存相同。如果这两者之一被修改,另一个也被修改。要在 C# 中处理 C/C++ 联合,您需要提供内存布局。 正如 Stephan Schlecht 已经指出的那样,了解对齐方式至关重要!我的项目是针对 32 位编译的,对齐方式位于4 字节边界。因此,我问题中的最初布局完全是错误的。您需要检查 C/C++ 项目中的布局,并在 C# 结构体定义中正确调整它: 这是我更正后的代码,两个联合成员都从第 4 个字节开始:
这样做,您就成功了一半!是的!但是还有一件事。通过此更改,代码将编译,但您将在运行时收到异常。是的,在运行时。相当快,但事实就是如此。 错误消息类似于:“偏移量 4 处的对象字段未正确对齐或被非对象字段重叠”
C# 存在 bug 是因为 C# 中存在整数等基本类型和引用类型。
如果我们没有正确处理这些引用类型,它们可能会给我们带来错误。 C# 有一个非常好的工作编组,但是对于联合,您需要将其做得尽可能好。
说明:
我的代码中的错误是 struct viewCap 有数组,由 C# 编组器编组。编组器正在履行其职责并创建数组。但是,数组是一种引用类型,并且在堆上创建。您将在堆栈上得到的内容(数据传输 C++ <-> C#)是堆上数组的引用地址。哼!因此,联合中的第二个结构及其基本类型将重叠地址,并从而使引用无效。很高兴运行时环境阻止我们这样做:-)此外,C# 正在对内存进行碎片整理。如果结构布局在内存使用方面效率不高,C# 将重新编码内容。您可以通过注释布局类型:顺序的结构来避免这种情况。
记住:
如果在属于联合 (C/C++) 的类型内有一个数组,则不能使用 C# 编组器!对联合使用 Layoutkind Explicit,对结构使用 Sequential!
解决方案:
可能还有其他几种我不知道的解决方案,但 100% 有效的方法是展开数组。是的,工作量很大。但它有效!
所以最终的结构如下所示:
快乐的运行环境,快乐的生活!
是的,对于这个特定的解决方案,您需要调整使用数组的代码。然而,它是防弹的并且可以理解它在引擎盖下的工作原理,这使得维护变得很容易。当我的代码生成时,展开的数组没什么大不了的。
This is one solution how to get data from C/C++ to C#. Here I will describe what I did wrong and what one has to take care of.
To recall in our minds my requirement has been (still is) that any data represented as union in C/C++, needs to be represented as such in C#. This means for following structure:
Any data inside AsViewCap must have its kind of representation in AsPathCap, because its simple the same memory. If one of these two are modified, the other is also. To handle C/C++ unions in C# you need to provide a memory layout. As Stephan Schlecht already meantioned it is vital to know the alignment! My project is compiled for 32bit the alignment is at the 4 Byte boarder. Thus my initial layout in my question was simply wrong. You need to check the layout in your C/C++ project and adjust it in the C# struct definion properly: Here is my corrected code, both union members start at 4th Byte:
Doing this you are half way there! Yes! But there is one more thing. With this change the Code will compile but, you will get an exception at runtime. Yes, at runtime. Quite quick but it is so. The error message something like: "object-field at offset 4 is incorrectly aligned or overlapped by a non-object field"
C# is bugging is because in C# there are basic types like Integer etc. and reference types.
These reference types can give us errors if we do not handel them correctly. C# has a very nice working marshalling, but in case of unions, its up to you to make it as nice as it can get.
Explenation:
What went wrong in my code is that the struct viewCap is having arrays, marshalled by the C# marshaller. The marshaller is doing his duty and creating an array. However, an array is a reference type and created on the heap. What you will get on the stack (data transfer C++ <-> C#) is the reference address to the array on the heap. Hmpf! Thus the second structure in the union with its basic types would overlapping the address and thus invalidating the reference. Gladly the runtime environment stops us from doing that :-) Moreover, C# is defragmenting the memory. If the struct layout is not efficient regarding memory usage C# will reoder the content. You can avoid this by annotating the struct which the layout kind: Sequential.
Remember:
If you got an array inside a type which is part of a union (C/C++), you cannot use the C# marshaller! Use Layoutkind Explicit for unions and Sequential for structs!
Solution:
There might be several other solutions I am not aware of, but what is 100% working is to unroll the arrays. Yes, it's a lot of work. But it works!
So the final struct looks like this:
Happy runtime environment, happy life!
Yes, for this specific solution you would need to adjust the code which is using the arrays. However, its bulletproof and understandable how this works under the hood which makes it easy for maintenance. As my code is generated, an unrolled array is no big deal.