【问题标题】:Marshal array of struct and IntPtrstruct 和 IntPtr 的元帅数组
【发布时间】:2018-08-18 01:10:00
【问题描述】:

我非常接近我的问题的解决方案,但我需要一些关于收尾工作的指导以使一切正常。在过去的一周里,我学到了很多关于

作为参考,我上周就同一主题问了一个类似的问题,但由于我方面的巨大疏忽,我问错了问题。

我正在尝试使用非托管 c++ dll,它是用于与连接设备通信的 API。我已经成功创建了包装器和大多数其他函数调用,但最后一个让我发疯了。

对于一些背景信息(可能不需要回答这个问题 - 请记住我当时的基本思维过程有缺陷)在这里:Calling un-managed code with pointer (Updated)

在我最初的问题中,我问的是如何为包含 struct(2) 数组的 struct(1) 创建一个 IntPtr。事实上,struct(1) 根本不包含数组,它包含指向数组的指针。

以下是我尝试实现的 API 文档作为参考:

extern “C” long WINAPI PassThruIoctl
(
    unsigned long ChannelID,
    unsigned long IoctlID,
    void *pInput,
    void *pOutput
)


// *pInput Points to the structure SCONFIG_LIST, which is defined as follows:
// *pOutput is not used in this function and is a null pointer

typedef struct
{
    unsigned long NumOfParams; /* number of SCONFIG elements */
    SCONFIG *ConfigPtr; /* array of SCONFIG */
} SCONFIG_LIST

// Where:
// NumOfParms is an INPUT, which contains the number of SCONFIG elements in the array pointed to by ConfigPtr.
// ConfigPtr is a pointer to an array of SCONFIG structures.

// The structure SCONFIG is defined as follows:
typedef struct
{
    unsigned long Parameter; /* name of parameter */
    unsigned long Value; /* value of the parameter */
} SCONFIG

这是我目前定义的结构定义

[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1
public struct SConfig 
{
   public UInt32 Parameter;
   public UInt32 Value;
}



[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1
public struct SConfig_List
{
    public UInt32 NumOfParams;
    public IntPtr configPtr;

    public SConfig_List(UInt32 nParams, SConfig[] config)
    {
        this.NumOfParams = nParams;

        //  I have tried these 2 lines together
        IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams);
        this.configPtr = new IntPtr(temp.ToInt32());

        // I have tried this by itself
        // this.configPtr  = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams);


        // and this line
        // this.configPtr = Marshal.AllocHGlobal(sizeof(SConfig)*(int)nParams);  // this only complies with unsafe struct
    }
}

这是将这些设置为变量并调用与 API 接口的函数的代码的 sn-p

SConfig[] arr_sconfig;
arr_sconfig = new SConfig[1];

arr_sconfig[0].Parameter = 0x04;
arr_sconfig[0].Value = 0xF1;
SConfig_List myConfig = new SConfig_List(1, arr_sconfig);

m_status = m_APIBox.SetConfig(m_channelId, ref myConfig);

最后,这里是将这些信息传递给 dll 的函数:

public APIErr SetConfig(int channelId, ref SConfig_List config)
{
    unsafe
    {
        IntPtr output = IntPtr.Zero; // Output not used, just a null pointer for this function

        //   These 2 lines of code cause API dll to yell about invalid pointer (C# is happy but it doesnt work with dll)
        //   IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(J_2534_API.SConfig_List)));
        //   IntPtr input = new IntPtr(temp.ToInt32());

        //  The following 2 lines only compile with unsafe - but API dll "likes" the pointer - but I am not getting desired results
        //  The dll is properly getting the Number of Parameters (NumOfParams), but the data within the array is not being
        //  referenced correctly
        IntPtr input = Marshal.AllocHGlobal(sizeof(SConfig_List)); // Only works with unsafe
        Marshal.StructureToPtr(config, input, true);

        APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);

        return returnVal;
    }
}

在我意识到我对基本概念的巨大疏忽之前,我什至无法让 C# 高兴,要么我的语法错误并且代码无法编译,要么它会编译但给出了运行时错误(甚至从未调用外部dll)

这些问题已经过去了。该代码现在可以正常编译,并且可以在没有任何运行时错误的情况下执行。此外,我使用的 dll 有一个日志功能,所以我可以看到我实际上调用了正确的函数。我什至正确地将一些数据传递给它。函数正在正确读取 NumOfParams 变量,但结构数组似乎是垃圾数据。

我在这里阅读了一篇很有帮助的文章:http://limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/

而且我一直在阅读 MSDN,但到目前为止,我还没有遇到使这件事起作用的神奇代码组合,所以我再次寻求帮助。

我很确定我的问题是我没有正确设置 IntPtr 变量,并且它们没有指向内存中的正确区域。

我尝试过各种不安全和安全代码的组合。另外,我知道此时我并没有明确释放内存,因此对此的指针也会有所帮助。在我的研究中,这里有一些可能可行的想法,但我似乎无法让它们恰到好处

[MarshalAs(UnmanagedType.LPWStr)]

[MarshalAs(UnmanagedType.ByValArray, SizeConst=...)]

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst=100)]

最后一个问题:我假设由于 c++ 声明是 unsigned long,那么 UInt32 是 C# 中的正确类型?

【问题讨论】:

    标签: c# struct


    【解决方案1】:

    sn-p 中的 SConfig_List 构造函数有很多问题。最大的问题是它为数组分配内存,但也完全忘记了复制结构。因此,本机代码可以正常获取指针,但会查看未初始化的内存。你可以这样修复它:

        public SConfig_List(SConfig[] config) {
            this.NumOfParams = config.Length;
            int size = Marshal.SizeOf(config[0]);
            IntPtr mem = this.configPtr = Marshal.AllocHGlobal(size * config.Length);
            for (int ix = 0; ix < config.Length; ++ix) {
                Marshal.StructureToPtr(config[ix], mem, false);
                mem = new IntPtr((long)mem + size);
            }
        }
    

    请确保不要在调用完成后再次忘记调用 Marshal.FreeHGlobal(),否则会泄漏内存。

    避免封送 SConfig_List 的最简单方法是为 C 函数提供更好的声明:

    [DllImport(...)]
    private static extern ApiErr PassThruIoctl(
        int channelID, 
        uint ioctlID,
        ref SConfig_List input,
        IntPtr output);
    

    这使得一个体面的包装方法看起来像这样:

    public APIErr SetConfig(int channelId, SConfig[] config) {
        var list = new SConfig_List(config);
        var retval = PassThruIoctl(channelId, Ioctl.SET_CONFIG, ref list, IntPtr.Zero);
        Marshal.FreeHGlobal(list.configPtr);
        return retval;
    }
    

    【讨论】:

    • 非常感谢,这非常有帮助,在您的帮助下,我能够让事情顺利进行。我(还)不能实现您为 C 函数提供更好声明的想法,因为输入和输出指针与传递的实际 Ioctl 值的关系不同。在某些情况下,它们都是无效的(例如,只是发送一个清除缓冲区命令,所以两个指针都是空的。在另一个 Ioctl 场景中,一个指针指向一个字节数组。因此,我仍在使用 Set_Config 方法中的编组- 但它正在 100% 工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-24
    • 2020-04-03
    • 1970-01-01
    • 2019-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多