Passing Managed Structures With Arrays To Unmanaged Code Part 1

本文详细探讨了.NET开发中如何将包含数组的Managed结构顺利传递到Unmanaged代码,包括结构布局、内存对齐及使用Marshal属性进行适配。同时,文章还展示了如何将数组作为固定长度数组或SAFEARRAY传递,并分析了结构在Unmanaged代码中的内存位置。
//
you're reading...
INTEROP MARSHALING

Passing Managed Structures With Arrays To Unmanaged Code Part 1

1. Introduction

1.1 Managed structures that contain arrays are a common requirement in .NET development. Such structures, however, cannot be conveniently passed to unmanaged code. This is because arrays are referenced types and are not blittable. And because arrays are not blittable, the containing structure is also non-blittable.

1.2 This series of blogs will explore how managed structures (which contain arrays) are transferred to unmanaged code. I shall also endeavour to show low-level activities including the location of the actual structures as they are passed to unmanaged APIs.

2. Example Structure.

2.1 Let’s define an example structure in C# :

public struct TestStruct01
{
  public int m_int;
  public int[] m_int_array;
};

2.2 Note that a managed structure, when passed to unmanaged code, must be constructed based on the memory layout expected by the unmanaged code. For example, where the unmanaged code is C/C++, the memory layout of the structure must be expressed C/C++ style.

2.3 In order to ensure the required memory layout, the CLR uses the services of the interop marshaler. The interop marshaler is the entity that performs the actual marshaling and calling of unmanaged code. Hence such a structure, if it is to be successfully passed to unmanaged code, must be further specified for the benefit of the interop marshaler. These specifications come in the form of the StructLayoutAttribute and the MarshalAsAttribute.

2.4 The StructLayoutAttribute indicates how the structure members are to be laid out in memory. Two arguments for this attribute are important :

  • The LayoutKind argument.
  • The Pack argument.

The LayoutKind argument indicates how the members of the structure are to be arranaged. The “LayoutKind.Sequential” value is the most commonly used. It indicates that the members are laid out back to back as they appear in the declaration. For example, “m_int” is the first member in memory, followed by “m_int_array”, etc.

The Pack argument indicates how the members of the structure will be spaced out. This argument corresponds directly to the C/C++ concept of struct member alignment. This affects the byte offset of each member in the structure. It can be helpful for optimization purposes. For simplicity, we shall use the value of 1 (i.e. 1-byte alignment).

Note, however, that the corresponding unmanaged version of the structure must also use the same struct member alignment. Things can go awry if the alignments do not match.

2.5 Hence at minimum, the structure should be declared as follows with the StructLayoutAttribute :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct01
{
...
};

2.6 Now, a blittable structure (i.e. one that contains only members of blittable type), is directly accessible from unmanaged code. As an optimization technique, the interop marshaler may, in certain circumstances, simply pass a pointer to such structure (in managed memory) to unmanaged code. A non-blittable structure (i.e. one that contains at least one member of non-blittable type), on the other hand, when passed to unmanaged code, must be represented by an unmanaged version that resembles the memory layout of an equivalent structure expected by the unmanaged code.

2.7 If the unmanaged code is developed in C/C++, the unmanaged structure is the equivalent of the managed structure but with each non-blittable member flattened and serialized. It must eventually resemble a C/C++ structure.

2.8 For more information on unmanaged representation of managed structs, please refer to :

“Passing Structures between Managed and Unmanaged Code”

http://limbioliong.wordpress.com/2011/06/03/passing-structures-between-managed-and-unmanaged-code/

2.9 In order to be serialized properly into the unmanaged representation of the structure, each struct member must provide information on how this is to be done. This is where the MarshalAsAttribute comes into play. If no MarshalAsAttribute is specified for a member, default marshaling procedures will be used.

2.10 Now the “m_int” member is a blittable type and so default marshaling will be used. This member need not be further specified using MarshalAsAttributes. The “m_int_array” member, however, is a referenced type. It is not blittable. It must necessarily be specified further using MarshalAsAttributes. The purpose of the MarshalAsAttribute is to indicate to the interop marshaler how the “m_int_array” member is to be represented in unmanaged code.

2.11 There are 2 ways that the “m_int_array” member may be represented in an unmanaged C/C++ structure :

  • As a fixed-length array.
  • As a SAFEARRAY (non-fixed-length array).

2.12 In section 3, we will examine how to express “m_int_array” as a fixed-length array in an unmanaged C/C++ structure and in section 5, we will expore how “m_int_array” may be expressed as a SAFEARRAY.

3. Representing a Managed Array Struct Member as a Fixed-Length Array.

3.1 To achieve this, the MarshalAsAttribute must be expressed as follows :

[MarshalAs(UnmanagedType.ByValArray, SizeConst = <length of array>)]

Note the following points about the “UnmanagedType.ByValArray” argument :

  • The “UnmanagedType.ByValArray” argument indicates that the array is a  “ByVal” array.
  • This means that the array is passed  “by value” (as opposed to ”by reference”).
  • This also means that all elements of the array will be laid out in memory back-to-back in the containing unmanaged version of the structure.

Note the following point about the “SizeConst” argument :

  • The value set for the “SizeConst” argument indicates the number of elements in the array.

3.2 Hence a full declaration for the TestStruct01 structure is listed below :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct01
{
  public int m_int;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
  public int[] m_int_array;
};

The value 10 for the SizeConst argument is just an arbitrary value that I picked for example purposes.

3.3 The equivalent of such a structure in C/C++ would be :

#pragma pack(1)
struct TestStruct01
{
  int m_int;
  int m_int_array[10];
};

3.4 Let’s say we want to pass this structure to an unmanaged C++ API with the following code :

void __stdcall DisplayStruct01(TestStruct01 test_struct_01)
{
  printf ("test_struct_01.m_int : <%d>.\r\n", test_struct_01.m_int);

  for (int i = 0; i < 10; i++)
  {
    printf ("test_struct_01.m_int_array[%d] : <%d>.\r\n", i, test_struct_01.m_int_array[i]);
  }

  return;
}

Note that the “test_struct_01″ struct parameter is passed by value.

3.5 The following would be the way the API is declared in managed C# :

[DllImport(@"TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void DisplayStruct01(TestStruct01 test_struct_01);

3.6 The following is an example C# function that calls this API :

static void DisplayStruct01InUnmanagedCode()
{
  TestStruct01 test_struct_01 = new TestStruct01();

  test_struct_01.m_int = 0;
  test_struct_01.m_int_array = new int[10];

  for (int i = 0; i < 10; i++)
  {
    test_struct_01.m_int_array[i] = i;
  }

  DisplayStruct01(test_struct_01);
}

This function creates a TestStruct01 structure, fills it with values, and then calls the DisplayStruct01() API.

3.7 In the next section, we shall examine how the managed structure ends up being represented by an unmanaged version of it and where this unmanaged structure resides in memory so as to be accessible by the API.

4.Memory Location of the Structure.

4.1 Now, as mentioned in point 2.7, when a non-blittable structure is passed to unmanaged code, an unmanaged representation of this structure must be created and then passed to the unmanaged code. In the example that we have built up, where does this unmanaged representation reside ?

4.2 As things turn out, this unmanaged representation resides in the call stack of the called API. This is because the parameter is passed by value :

void __stdcall DisplayStruct01(TestStruct01 test_struct_01); 

[DllImport(@"TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void DisplayStruct01(TestStruct01 test_struct_01);

Hence a copy of the TestStruct01 structure is to be pushed onto the stack and will then be accessed by value by the DisplayStruct01() API. Now this is where the interop marshaler takes the opportunity to perform its task : it transforms the managed StructWithArray structure into its unmanaged equivalent using the call stack as the target memory.

Using the call stack is most natural not only because the structure is passed by value, but also because the memory is immediately recovered when the API returns.

5. Representing a Managed Array Struct Member as a SAFEARRAY.

5.1 To achieve this, the the MarshalAsAttribute must be expressed as follows :

[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I4)]

Note the following point about the UnmanagedType.SafeArray argument :

  • It indicates that the array is to be marshaled across to unmanaged code as a COM SAFEARRAY
  • Now, being a SAFEARRAY, the number of elements in the array need not be fixed. It can be set dynamically. This is one advantage of using a SAFEARRAY over an inline array.
  • This time, the corresponding array member in the C/C++ structure can no longer be declared as an inline array (as indicated in point 3.3).
  • It needs to be declared as a pointer to a SAFEARRAY (more on this below).

Note the following point about the SafeArraySubType argument :

  • It indicates the VARIANT Type which is to be contained inside the SAFEARRAY to be created.
  • In the case of our example, this is set to VT_I4 which fits the description of a 4-byte integer (i.e. a 32-bit integer).

As mentioned above, the corresponding array member of the unmanaged struct must be declared as a pointer to a SAFEARRAY instead of an inline array :

struct TestStruct02
{
  int m_int;
  SAFEARRAY* pSafeArrayOfInt;
};

Note that I have renamed the struct to TestStruct02 so as to differentiate it from the previously declared TestStruct01.

5.2 A full declaration for the managed TestStruct02 structure is listed below :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct02
{
  public int m_int;
  [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I4)]
  public int[] m_int_array;
};

Notice that besides the different arguments used for the MarshalAsAttribute, this struct is practically the same as the previous TestStruct01 structure.

5.3 This time, however, the unmanaged C++ code for displaying the array would have to use SAFEARRAY APIs :

void __stdcall DisplayStruct02(TestStruct02 test_struct_02)
{
  printf ("test_struct_02.m_int : [%d].\r\n", test_struct_02.m_int);

  long lLbound = 0;
  long lUbound = 0;

  // Obtain bounds information of the SAFEARRAY.
  SafeArrayGetLBound(test_struct_02.pSafeArrayOfInt, 1, &lLbound);
  SafeArrayGetUBound(test_struct_02.pSafeArrayOfInt, 1, &lUbound);
  long lDimSize = lUbound - lLbound + 1;

  for (int i = 0; i < lDimSize; i++)
  {
    long rgIndices[1];
    int value;

    rgIndices[0] = i;
    SafeArrayGetElement
    (
      test_struct_02.pSafeArrayOfInt,
      rgIndices,
      (void FAR*)&value
    );
    printf ("[%d]\r\n", value);
  }
}

5.4 And the C# code for calling this API is listed below together with the declaration for the DisplayStruct02() API :

[DllImport(@"TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void DisplayStruct02(TestStruct02 test_struct_02);

static void DisplayStruct02InUnmanagedCode()
{
  TestStruct02 test_struct_02 = new TestStruct02();

  test_struct_02.m_int = 0;
  test_struct_02.m_int_array = new int[9];

  for (int i = 0; i < 9; i++)
  {
    test_struct_02.m_int_array[i] = i;
  }

  DisplayStruct02(test_struct_02);
}

5.5.Notice that there is practically no difference between DisplayStruct02InUnmanagedCode() and the earlier DisplayStruct01InUnmanagedCode() (point 3.6) except that different but similar structs were used and different APIs were called.

5.6 The only significant difference is that the “m_int_array” member array is now variable in size and need not be restricted to a fixed number of elements. In the above example code, the upper bound of 9 is just an arbitrary number used for example purposes.

6.Memory Location of the Structure and the SAFEARRAY.

6.1 Now, similar to the previous example where TestStruct01 was passed to the DisplayStruct01() API, the TestStruct02 struct was also passed to the DisplayStruct02() API by value.

6.2 This means that the entire unmanaged representation of the “test_struct_02″ struct is copied to the call stack for the DisplayStruct02() API. 

6.3 Now, what about the SAFEARRAY member “pSafeArrayOfInt” ? This is where the interop marshaler plays the significant role of SAFEARRAY creation, data assignment and final destruction.

6.4 As the unmanaged representation of the “test_struct_02″ struct is being prepared for the DisplayStruct02() API, the interop marshaler creates a SAFEARRAY using the following APIs :

  • SafeArrayAllocDescriptorEx()
  • SafeArrayAllocData()

After SafeArrayAllocData() is called, the interop marshaler proceeds to directly copy the data of the managed array to the buffer of the SAFEARRAY using the “pvData” member of the created SAFEARRAY.

6.5 When all data has been copied, a pointer to this SAFEARRAY is then set to the “pSafeArrayOfInt” member of the TestStruct02 struct which is finally passed to the DisplayStruct02() API.

6.6 Then, when the API returns, the SAFEARRAY is destroyed using the SafeArrayDestroy() API.

7. In Conclusion.

7.1 In part 1, we have studied how an array containing structure may be passed to unmanaged code as a “by value” parameter.

7.2 We have also examined the various options available for expressing a managed array struct member in unmanaged code. That is, as an inline array or as a SAFEARRAY.

7.3 In part 2, we shall explore how to pass an array containing structure as a return (i.e. “out”) parameter.

在嵌入式系统编程中,特别是在涉及硬件接口(如GPIO)的开发过程中,开发者可能会遇到类似“passing argument to parameter 'gpio'”的错误或警告。这类问题通常与参数传递方式、数据类型不匹配或函数定义使用不当有关。 ### 参数传递中的常见问题 1. **数据类型不匹配** GPIO操作通常依赖于特定的数据结构和函数接口。如果调用函数时传递的参数类型与函数声明不一致,例如将整数传递给期望为指针类型的参数,则编译器会报错。例如: ```c int gpio_set_value(unsigned int gpio, int value); ``` 如果调用 `gpio_set_value(&my_gpio, 1);`,其中 `my_gpio` 是一个整数变量,而函数期望直接传入整数值而非其地址,这会导致参数类型错误[^1]。 2. **误用指针或引用** 在某些情况下,函数可能要求传递 `struct gpio_desc *` 类型的描述符,而不是简单的整数编号。若直接传递整数而非通过 `gpio_to_desc()` 或其他转换函数获取描述符,则可能导致无效访问或逻辑错误。 ```c void gpiod_set_value(struct gpio_desc *desc, int value); ``` 正确的调用方式应为: ```c struct gpio_desc *desc = gpio_to_desc(gpio_number); gpiod_set_value(desc, 1); ``` 3. **未初始化或无效的GPIO编号** 若传递的 `gpio` 值未经过有效验证(如超出芯片支持范围),或在设备树中未正确配置该引脚,则可能导致运行时错误。例如,在Linux内核模块中,应确保GPIO已通过 `devm_gpio_request()` 请求并配置为输出模式。 4. **函数调用顺序不当** 某些GPIO操作需要先进行配置(如设置方向),再执行读写操作。若跳过配置步骤直接调用 `gpiod_set_value()` 可能导致不可预测的行为。 ### 示例:正确的GPIO使用流程 以下是一个基于Linux内核模块的GPIO初始化与使用示例: ```c #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/platform_device.h> struct gpio_desc *my_gpio; static int example_probe(struct platform_device *pdev) { my_gpio = devm_gpiod_get(&pdev->dev, "example-gpio", GPIOD_OUT_LOW); if (IS_ERR(my_gpio)) { dev_err(&pdev->dev, "Failed to get GPIO\n"); return PTR_ERR(my_gpio); } gpiod_set_value(my_gpio, 1); // 设置GPIO为高电平 return 0; } static int example_remove(struct platform_device *pdev) { gpiod_set_value(my_gpio, 0); // 关闭GPIO return 0; } ``` ### 调试建议 - **检查函数原型**:确保调用的函数接受的参数类型与传递的值一致。 - **查阅文档**:参考相关平台的GPIO子系统文档,了解是否需要使用描述符结构体或特定API来操作GPIO。 - **启用调试输出**:在驱动程序中添加日志输出,确认GPIO编号是否正确分配,并在关键步骤后检查返回值。 - **静态分析工具**:使用如 `sparse` 或 `clang` 的静态分析功能,提前发现类型不匹配等问题。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值