【问题标题】:Call a method through Interop which returns an instance of a struct通过 Interop 调用返回结构实例的方法
【发布时间】:2016-02-05 18:08:39
【问题描述】:

我是 Interop 的新手,需要从 C# 调用托管 C++ 方法,该方法返回以下 struct 的实例:

typedef struct DataBlock_ {
  unsigned char data[10240];
  unsigned int numberOfBytes;
  unsigned long int startAddr;    
} DataBlock;

返回实例的C++方法声明如下:

__declspec(dllexport) DataBlock getDefaultPass( void ) 
{
    DataBlock default_pass = {
        {
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF
        },
        32,
        0xFFE0
    };
    return default_pass;
}

我在 C# 中声明了结构和方法如下:

public static partial class My
{
    [StructLayout(LayoutKind.Sequential)]
    public struct DataBlock
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10240)]
        public byte[] data;
        //public fixed byte data[10240]; <-- this requires 'unsafe' but still doesn't work      
        public UInt32 numberOfBytes;
        public UInt32 startAddr;
    }

    [DllImport("my.dll")]
    static public extern DataBlock getDefaultPass( );

    [DllImport("my.dll")]
    static public extern byte sendPassword(DataBlock data);
}

我从 C# 调用方法如下:

var defaultPassword = My.getDefaultPass();
var response = My.sendPassword(defaultPassword);

但是对getDefaultPass() 的调用会抛出

未处理的类型异常 'System.Runtime.InteropServices.MarshalDirectiveException' 发生在 ConsoleApplication1.exe

附加信息:方法的类型签名不是 PInvoke 兼容。

基于this question,我尝试将data的声明更改为public fixed byte data[10240]并将结构标记为unsafe,但随后该方法返回一个实例,其中numberOfBytesstartAddr设置为0,并且随后对sendPassword() 的调用失败(请注意,在this answer 中,随后的调用使用指向结构的指针,而不是实例本身,就像我的情况一样)。那么我应该如何从 C# 调用方法?

该项目面向 .NET 3.5 和 x86。

提前感谢您的帮助。

【问题讨论】:

  • @MatthewWatson 不,该结构实际​​上是 blittable。虽然返回这样的大型结构很棘手,但 blittability 不是问题。
  • 您是否使用 GCC 编译本机代码?如果是这样,它很可能会发出函数以实际采用ref 参数而不是返回结构。真正的签名将类似于static public extern void getDefaultPass(out DataBlock data)
  • @MatthewWatson 见msdn.microsoft.com/en-us/library/75dwhxf7(v=vs.110).aspxbyte 是 blittable。 blittable 类型的固定大小数组本身是 blittable。因此,byte[](由 OP 定义)是 blittable。
  • @Luaan 我被告知它也是在 Visual Studio 中编译的(我不维护它,也不认为我可以更改它)。这是否有帮助,还是您需要有关构建/链接配置的更多细节?

标签: c# c++ .net interop


【解决方案1】:

struct 很好 - 它满足在 P/Invoke 中用作返回值的所有规则。

您需要使用正确的调用约定(在您的情况下为CallingConvention.Cdecl)。

还有一些编译器使用的额外优化,其中一个大结构(例如您的)通过引用传递,而不是返回。您可以像这样在 C# 中复制它:

static public extern void getDefaultPass(out DataBlock data);

【讨论】:

    【解决方案2】:

    为了完整起见并补充 Luaan 的回答,由于问题中的 C++ 方法没有参数,我想介绍一个方法确实有参数的情况,特别是因为它与 out 的位置有关方法接受 2 个或更多参数时的参数。

    考虑 C++ 方法

    __declspec(dllexport) DataBlock readText(char * dataArray , int bytesToRead)
    

    out 参数在 C# 方法中应该是第一个还是最后一个并不是很明显。与框架将out参数放在最后一个参数(例如TryParse)的约定相反,这里必须是第一个参数,否则调用会失败:

    [DllImport("my.dll", CallingConvention = CallingConvention.Cdecl)]
    static public extern void readText(out DataBlock dataBlock, string dataArray, int bytesToRead);
    

    【讨论】:

      猜你喜欢
      • 2012-03-01
      • 2016-12-24
      • 2022-07-07
      • 2018-07-10
      • 1970-01-01
      • 1970-01-01
      • 2014-03-12
      • 2018-06-06
      • 1970-01-01
      相关资源
      最近更新 更多