将复杂的结构(带有结构的内部数组)从 C# 传递到 C++

Posted

技术标签:

【中文标题】将复杂的结构(带有结构的内部数组)从 C# 传递到 C++【英文标题】:Passing a complex Struct (with inner array of struct) from C# to C++ 【发布时间】:2019-08-18 13:16:41 【问题描述】:

我正在开发扫描仪的驱动程序。从提供者那里得到 dll 和头文件,除了 PDF 中的手册,用本机 C++ 编写(没有源代码)。需要在 C# 项目中使用它,但我遇到了结构问题(尝试读取或发送它们)。

我使用命令提示符获得了 dll 方法,在网站中对它们进行了分解(因为它有 +100)。当然,我不会全部使用,只使用我需要的。使用原始数据类型的没有问题,实际上,使扫描仪打开/关闭,扫描等。

我的主要问题如下:我需要设置一些参数,因为使用默认参数我没有获得所需的信息(实际上这是我需要的最重要的东西)。唯一的方法是使用包含 2 个参数的方法:ID(只是一个 int)和设置(一个结构)。该结构内部不是一个,而是两个不同的结构实例(其中一个是数组,在另一种结构中作为参数之一)。换句话说,需要使用 4 个不同的结构。

我按照 .h 文件中提供的模板声明了所有结构,并导入了方法。当我尝试进行测试时,它一直给我一个错误。我相信问题在于结构数组。我尝试了正常的传递、编组、使用数组的引脚、更改数据类型、添加带有所有布尔变量的“MarshalAs”......似乎没有任何效果。

已经尝试解决这个问题好几天了。不知道我做错了什么(因为这是我第一次导入方法)。我读过关于 C++/Cli 的东西,但也从未使用过。

见下面的代码(由于信息保密,我稍微修改了一下)

这是.h文件中的定义(C++)

// The structs (won't add all parameters, but are basically the same type)
typedef struct _ImParam

  UINT Format;
  UINT Resolution;
  UINT ColorDepth;
 IM_PARAM;

typedef struct _sValues

  UINT Xpos;
  UINT Ypos;
  UINT Width;
  UINT Height;
  BOOL Milli; 
 S_VALUES;

typedef struct _sProperties

  BOOL Enable;
  S_VALUES Properties;
 S_PROPERTIES;

typedef struct _DevParam

  BOOL Enable;
  UINT Font;
  char Symbol;
  IM_PARAM Image1;
  IM_PARAM Image2;
  S_PROPERTIES Properties[10];
  UINT FeedMode;
 DevParam;

// more code, comments, etc. etc.

// This is how is defined
BOOL SetParameters( DWORD ID, DevParams DParam )

这就是我在 C# 中构建结构的方式

[StructLayout(LayoutKind.Sequential)]
public struct ImParam

   public uint Format;
   public uint Resolution;
   public uint ColorDepth;

   public ImParam(uint n)
   
       Format = n;
       Resolution = 300;
       ColorDepth = 256;
   
;

[StructLayout(LayoutKind.Sequential)]
public struct sValues

   public uint Xpos;
   public uint Ypos;
   public uint Width;
   public uint Height;
   public bool Milli;

   public sValues(uint n)
   
       Xpos = n;
       Ypos = n;
       Width = n;
       Height = n;
       Milli = false;
   
;

[StructLayout(LayoutKind.Sequential)]
public struct sProperties

   public bool Enable;
   public sValues Properties;

   public sProperties(int n)
   
       Enable = false;
       Front = false;
       Properties = new sValues(n);
   
;

// Commented code is from another attemp
[StructLayout(LayoutKind.Sequential)]
public struct DevParam

   public bool Enable;
   public uint Font;
   public char Symbol;
   public ImParam Image1;
   public ImParam Image2;
   public IntPtr Properties;
   //public sProperties[] Properties;
   public uint FeedMode;

   public DeviceParameters(IntPtr SnP) //(int n)
   
       Enable = true;
       Font = 0;
       Symbol = '?';
       Image1 = new ImParam(3);
       Image2 = new ImParam(3);
       Properties = SnP;
       /*Properties = new sProperties[n];
        *for(int i = 0; i < n; i++)
        *   Properties[i] = new sProperties(0);*/
       FeedMode = 1;
   
;

// .dll file path definition, some methods imported, etc. etc.
[DllImport(path, EntryPoint = "?SetParameters@@YGHKU_DevParam@@@Z")]
public static extern bool SetParameters(int ID, DevParam dParam);

这就是我调用它的时候(添加注释代码以向您展示我的尝试)

static void Main(string[] args)

    bool res = false;
    int ID;
    sProperties[] SnP = new sProperties[10];
    for (int i = 0; i < 10; i++)
        SnP[i] = new sProperties(0);

    try
    
        // Some code to turn on scanner, get ID value and such

        /* Attemp1: Passing the struct normaly.
         * Result: ArgumentException [HRESULT: 0x80070057 (E_INVALIDARG))]
         * try
         * 
         *     DevParam dParam = new DevParam(10);
         *     res = Class1.SetParameters(ID, dParam);
         *     Console.WriteLine(res);
         * 
         * catch (Exception e)  Console.WriteLine(e); */

        /* Attemp2: Marshaling each element of the array.
         * Result: The managed PInvoke signature doesnt mach the destination one
         * int S = Marshal.SizeOf(typeof(sProperties));
         * DevParam dParam = new DevParam(Marshal.AllocHGlobal(SnP.Length*S));
         * IntPtr ptr = dParam.Properties;
         * for (int i = 0; i < SnP.Length; i++)
         * 
         *     Marshal.StructureToPtr(SnP[i], ptr, false);
         *     ptr += S;
         * 
         * try
         * 
         *     res = Class1.SetDevParam(ID, dParam);
         *     Console.WriteLine(res);
         * 
         * finally  Marshal.FreeHGlobal(dParam.sProperties); */

         /* Attemp3: Adding a Pin Pointer to struct
          * Result: Exception (Object has no primitive data and it can't
          *     be transfered into bits blocks) */
         GCHandle SpHandle = GCHandle.Alloc(SnP, GCHandleType.Pinned);
         try
         
             DevParam dParam = new DevParam(SpHandle.AddrOfPinnedObject());
             res = Class1.SetParameters(ID, dParam);
             Console.WriteLine(res);
         
         catch (Exception e)  Console.WriteLine(e); 
         finally  SpHandle.Free(); 

         // More code for testing other methods and blahblahblah
     
     catch (Exception e)  Console.WriteLine(e); 
     Console.WriteLine("Finished");
     Console.ReadKey();

我期待什么?只获取一个布尔结果来查看方法是否成功执行(当然,如果为真,scanner 应该已经定义了新参数)

我会得到什么?一堆例外。

拜托,任何帮助都会很棒。提前致谢。

PD:很抱歉发了这么长的帖子。 PD2:我很摇滚,所以请尝试“为傻瓜”解释它

【问题讨论】:

您好,欢迎来到 ***。这确实是一篇很长的帖子,但至少发布您遇到的第一个异常可能会有所帮助。 嵌入式数组不是 IntPtr。只需使用普通数组[]。它需要 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] 正确编组。 谢谢佩德罗。这些例外在 cmets 中。第一个……就是这样(系统是这么说的) 嘿汉斯。正在跳跃你会回复。要试试那个。也许调整“Attempt2”代码?明天我会在工作中试试,然后告诉你。感谢反馈 【参考方案1】:

谢谢汉斯。好像成功了!

只是按照建议修改了结构:

[StructLayout(LayoutKind.Sequential)]
public struct DevParam

   public bool Enable;
   public uint Font;
   public char Symbol;
   public ImParam Image1;
   public ImParam Image2;
   //public IntPtr Properties;
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
   public sProperties[] Properties;
   public uint FeedMode;

   public DeviceParameters(int n) //(IntPtr SnP)
   
       Enable = true;
       Font = 0;
       Symbol = '?';
       Image1 = new ImParam(3);
       Image2 = new ImParam(3);
       //Properties = SnP;
       Properties = new sProperties[n];
        for(int i = 0; i < n; i++)
           Properties[i] = new sProperties(0);
       FeedMode = 1;
   
;

并使用了“Attemp1”代码。

【讨论】:

以上是关于将复杂的结构(带有结构的内部数组)从 C# 传递到 C++的主要内容,如果未能解决你的问题,请参考以下文章

如何将带有 unsigned char* 的结构从 C# 传递到 C++?

如何将结构数组从 c++ 传递到 c#?

将结构数组从 C#(.NET Core) 传递到 C++(未管理)

在 C#/C++ 之间编组复杂结构

传递结构时可以将指针编组到数组吗?

当结构仅在运行时已知时,将结构从 c++ 传递到 c#