【问题标题】:How can I declare a pointer to a struct for P/Invoke?如何为 P/Invoke 声明指向结构的指针?
【发布时间】:2014-10-12 14:27:54
【问题描述】:

我正在尝试使用P/Invoke Interop Assistant 在 C# 中调用 C++ Dll。大多数标题都转换得很好,但我遇到了这个问题:

#define FULLOCTAVE_BINS                12
#define THIRDOCTAVE_BINS               36

typedef struct tagTimeHistory
{
    UINT m_nAction;
    int m_nFlag;
    int m_nRecordNum; 
    int m_nTimeStamp;
    int m_nMiscStartIndex;
    float m_pfTHFloatVals[256]; // Number of valid values given by m_nNumFloatVals in Settings.
    float m_pfTH11OBAVals[4][FULLOCTAVE_BINS];  // 0-4 spectra given by m_nNumOBA11Vals in Settings
    float m_pfTH13OBAVals[4][THIRDOCTAVE_BINS]; // 0-4 spectra given by m_nNumOBA13Vals in Settings
    float m_fDuration;
} stTimeHistory_t;

typedef struct tagSlmBulkRecords
{
    int nRecType;
    union
    {
        stTimeHistory_t *m_ThRecs;
        stInterval_t    *m_Interval;
        stExceedence_t  *m_Exceedences;
        stRunRecord_t   *m_RunRecord;
        stSpeechData_t  *m_VoiceRecord;
        stSpeechData_t  *m_AudioRecord;
    };
} stSlmBulkRecord_t;

正在转换为:

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
public struct Anonymous_d2bf9406_c664_4664_9196_800cc23f445a {

    /// stTimeHistory_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_ThRecs;

    /// stInterval_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_Interval;

    /// stExceedence_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_Exceedences;

    /// stRunRecord_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_RunRecord;

    /// stSpeechData_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_VoiceRecord;

    /// stSpeechData_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_AudioRecord;
}

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct tagSlmBulkRecords {

    /// int
    public int nRecType;

    /// Anonymous_d2bf9406_c664_4664_9196_800cc23f445a
    public Anonymous_d2bf9406_c664_4664_9196_800cc23f445a Union1;
}

但是当 m_ThRecs 只是一个 System.IntPtr 时,我该如何使用它呢?有没有办法明确声明它是指向 stTimeHistory_t 的指针?我要移植到 C# 的 C++ 代码是这样使用的:

stSlmBulkRecord_t   bulkRecord;
bulkRecord.m_ThRecs = new stTimeHistory_t[dataCounts.m_nNumTH];

但是如果我在 C# 中尝试这个:

tagSlmBulkRecords bulkRecord;
bulkRecord.Union1.m_ThRecs = new tagTimeHistory[dataCounts.m_nNumTH];

我明白了:

错误 1 ​​无法将类型“SlmTest.Program.tagTimeHistory[]”隐式转换为“SlmTest.Program.tagTimeHistory””

如果我尝试一个不安全的定义:

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct tagTimeHistory
{
    /// UINT->unsigned int
    public uint m_nAction;

    /// int
    public int m_nFlag;

    /// int
    public int m_nRecordNum;

    /// int
    public int m_nTimeStamp;

    /// int
    public int m_nMiscStartIndex;

    /// float[256]
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 256, ArraySubType = System.Runtime.InteropServices.UnmanagedType.R4)]
    public float[] m_pfTHFloatVals;

    /// float[48]
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 48, ArraySubType = System.Runtime.InteropServices.UnmanagedType.R4)]
    public float[] m_pfTH11OBAVals;

    /// float[144]
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 144, ArraySubType = System.Runtime.InteropServices.UnmanagedType.R4)]
    public float[] m_pfTH13OBAVals;

    /// float
    public float m_fDuration;
}

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
public unsafe struct Anonymous_d2bf9406_c664_4664_9196_800cc23f445a
{
    /// stTimeHistory_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public tagTimeHistory *m_ThRecs;
    /// stInterval_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr *m_Interval;
    /// stExceedence_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_Exceedences;
    /// stRunRecord_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_RunRecord;
    /// stSpeechData_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_VoiceRecord;
    /// stSpeechData_t*
    [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
    public System.IntPtr m_AudioRecord;
}

我明白了:

错误 CS0208:无法获取托管类型的地址、大小或声明指向托管类型的指针

【问题讨论】:

  • 您在 C# 中的 stSlmBulkRecord_t 是声明为 struct 还是 class
  • stSlmBulkRecord_t 没有在 C# 中声明,只有 tagSlmBulkRecords 是一个如上所示的结构。
  • 这里没有明显的不安全需要
  • 否则会报错?

标签: c# struct pinvoke


【解决方案1】:

如果你真的想和原生代码互操作,可以使用fixed操作符:

var array = new tagTimeHistory[dataCounts.m_nNumTH];
fixed (tagTimeHistory* ptr = array)
{
    // do anything with the raw pointer
}

请注意,C# 中的 fixed 运算符和指针类型需要 unsafe 功能。您可能希望用正确的指针类型替换 IntPtrs 以提高类型安全性(尽管有一种方法可以将指针转换为 IntPtrs 并返回)。

另一种方法是通过Marshal class的方法。

编辑。这是一个修改后的 unsafe 定义的有趣命名联合结构的示例:

[StructLayout(LayoutKind.Explicit)]
public unsafe struct Anonymous_d2bf9406_c664_4664_9196_800cc23f445a
{
    [FieldOffset(0)]
    public stTimeHistory_t* m_ThRecs;

    [FieldOffset(0)]
    public stInterval_t* m_Interval;

    [FieldOffset(0)]
    public stExceedence_t* m_Exceedences;

    [FieldOffset(0)]
    public stRunRecord_t* m_RunRecord;

    [FieldOffset(0)]
    public stSpeechData_t* m_VoiceRecord;

    [FieldOffset(0)]
    public stSpeechData_t* m_AudioRecord;
}

您应该在代码中定义所有结构,例如stTimeHistory_t(或将您不关心的结构替换为通用IntPtrs)。

关于用Marshal创建结构体数组:native memory pool没有structure array这样的概念;它只关心字节。例如,您可以使用Marshal.AllocHGlobal method:

IntPtr myPtr = Marshal.AllocHGlobal(Marshal.SizeOf<tagTimeHistory>() * dataCounts.m_nNumTH);
// ... write something to an array, use it
// And don't forget to free it to prevent memory leaks!
Marshal.FreeHGlobal(myPtr);

编辑 2. 关于“无法获取托管类型的地址、大小或声明指向托管类型的指针”错误 - 您的定义并非完全不受托管。不安全的定义和使用编组逻辑的定义并不总是相等的;在这里,它认为您的类是“托管的”,因为其中包含数组引用。尝试固定数组:

[StructLayout(LayoutKind.Sequential)]
public unsafe struct tagTimeHistory
{
    public uint m_nAction;
    public int m_nFlag;
    public int m_nRecordNum;
    public int m_nTimeStamp;
    public int m_nMiscStartIndex;
    public fixed float m_pfTHFloatVals[256];
    public fixed float m_pfTH11OBAVals[48];
    public fixed float m_pfTH13OBAVals[144];
    public float m_fDuration;
}

【讨论】:

  • 当你说“你可能想用正确的指针类型替换'IntPtr's以获得更多类型安全”时,这是如何完成的?或者,如何使用编组来创建结构数组?
  • 我尝试了不安全的定义,但我得到“错误 CS0208:无法获取托管类型的地址、大小或声明指向托管类型的指针”
  • @parsley72,请在您的问题中显示您的tagTimeHistory 的定义。我敢打赌那是class 而不是struct
  • 不 - 查看添加的详细信息。
  • 顺便说一下,请注意我的帖子讨论了两种选择:使用Marshal 类和使用不安全指针。并非在所有情况下都可以组合它们。
猜你喜欢
  • 2021-12-03
  • 2013-07-07
  • 2020-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多