简体   繁体   English

struct和IntPtr的元数据数组

[英]Marshal array of struct and IntPtr

I am very very close to a solution to my issue, but I need a little guidance on the finishing touches to make everything work. 我非常接近解决问题的方法,但是我需要一些收尾指导以使一切正常进行。 Over the last week, I have learned a lot about 在过去的一周里,我学到了很多关于

Just as a reference, I asked a similar question last week about the same topic, but due to a HUGE oversight on my side, I was asking the wrong question. 只是作为参考,我上周就相同的话题问了类似的问题,但是由于我方面的巨大监督,我提出了错误的问题。

I am trying to use an unmanaged c++ dll that is the API for communication with a connected device. 我正在尝试使用非托管的c ++ dll,该dll是用于与连接的设备进行通信的API。 I have successfully created the wrapper, and most of the other function calls, but this last one is driving me crazy. 我已经成功创建了包装器以及其他大多数函数调用,但是最后一个使我发疯。

For some background info (probably not required to answer this question - and keep in mind my basic thought process was flawed at the time) is here: Calling un-managed code with pointer (Updated) 有关某些背景信息(可能不需要回答此问题,请记住我当时的基本思维过程存在缺陷)在此处: 用指针调用非托管代码(已更新)

In my original question, I was asking about creating an IntPtr to a struct(1) containing an array of struct(2).... In fact, struct(1) does not contain an array at all, it contains a Pointer to an array. 在我最初的问题中,我问的是要为包含struct(2)数组的struct(1)创建一个IntPtr。...实际上,struct(1)根本不包含数组,它包含一个指向数组。

Here is the documentation from the API I am trying to implement as a reference: 这是我尝试实现作为参考的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

Here are my struct definitions as I currently have them defined 这是我目前定义的结构定义

[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
    }
}

Here is the snippet of code that sets up these into variables and calls the function which interfaces with the API 这是将这些代码设置为变量并调用与API接口的函数的代码段

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);

And Finally, here is the function that passes this information on to the dll: 最后,这是将这些信息传递给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;
    }
}

Before I realized my huge oversight in the basic idea, I never could even get C# to be happy, either I had the syntax wrong and the code would not compile, or it would compile but gave a runtime error (never even calling the external dll) 在意识到基本想法的巨大监督之前,我什至无法使C#高兴起来,要么我的语法错误并且代码无法编译,要么会编译但给出运行时错误(甚至都不会调用外部dll) )

These problems are behind me. 这些问题在我身后。 The code compiles fine now, and executes without any runtime errors. 该代码现在可以正常编译,并且可以在没有运行时错误的情况下执行。 In addition, the dll I am using has a logging function, so I can see that I am actually calling the correct function. 另外,我正在使用的dll具有日志记录功能,因此我可以看到实际上正在调用正确的函数。 I am even passing some data correctly to it. 我什至正确地传递了一些数据。 The NumOfParams variable is being read properly by the function, but the array of structs appears to be garbage data. 该函数正在正确读取NumOfParams变量,但是结构数组似乎是垃圾数据。

I have been reading a very helpful article here: http://limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/ 我在这里阅读了一篇非常有帮助的文章: http : //limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/

And I have been reading MSDN, but so far I have not been able to come across the magic combination of code that makes this thing work, so I am reaching out one more time for help. 而且我一直在阅读MSDN,但到目前为止,我还没有发现使这件事起作用的神奇的代码组合,因此我要花更多时间寻求帮助。

I am pretty sure my issue is that I am not setting the IntPtr variables up correctly, and they are not pointing to the correct area in memory. 我很确定我的问题是我没有正确设置IntPtr变量,并且它们没有指向内存中的正确区域。

I have tried various combinations of unsafe and safe code. 我尝试了不安全代码和安全代码的各种组合。 Also, I know that I am not explicitly freeing the memory at this point, so pointers on that would be helpful as well. 另外,我知道我现在还没有显式释放内存,因此对此的指针也将有所帮助。 In my research, here are some ideas that might work, I just cant seem to get them just right 在我的研究中,这里有一些可能有用的想法,但我似乎无法使它们正确无误

[MarshalAs(UnmanagedType.LPWStr)]

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

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

One final question: I assume that since the c++ declaration is unsigned long, then the UInt32 is the proper type in C#? 最后一个问题:我假设既然c ++声明是无符号的,那么UInt32是C#中正确的类型吗?

Lots of things wrong with the SConfig_List constructor in the snippet. 代码段中的SConfig_List构造函数有很多错误。 Biggest issue is that it allocates memory for the array but completely forgets to copy the structures as well. 最大的问题是,它为阵列分配了内存,但是完全忘记了复制结构。 So the native code gets the pointer okay but looks at uninitialized memory. 因此,本机代码可以正常使用指针,但可以查看未初始化的内存。 You can fix it like this: 您可以这样解决:

    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);
        }
    }

Be sure to not forget to call Marshal.FreeHGlobal() again after the call completed or you'll leak the memory. 请确保不要忘记在调用完成后再次调用Marshal.FreeHGlobal(),否则会浪费内存。

The simplest way to avoid marshaling the SConfig_List is to just provide a better declaration for the C function: 避免封送SConfig_List的最简单方法是为C函数提供更好的声明:

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

Which makes a decent wrapper method look like this: 这使得像样的包装方法看起来像这样:

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM