C# Platform-invoke, c-style union with reference a

2019-08-11 05:35发布

问题:

I am trying to Marshall the following structure

struct OpalMessage {
  OpalMessageType m_type;   ///< Type of message
  union {
    const char *             m_commandError;       ///< Used by OpalIndCommandError
    OpalParamGeneral         m_general;            ///< Used by OpalCmdSetGeneralParameters
    OpalParamProtocol        m_protocol;           ///< Used by OpalCmdSetProtocolParameters
    OpalParamRegistration    m_registrationInfo;   ///< Used by OpalCmdRegistration
    OpalStatusRegistration   m_registrationStatus; ///< Used by OpalIndRegistration
    OpalParamSetUpCall       m_callSetUp;          ///< Used by OpalCmdSetUpCall/OpalIndProceeding/OpalIndAlerting/OpalIndEstablished
    const char *             m_callToken;          ///< Used by OpalCmdHoldcall/OpalCmdRetrieveCall/OpalCmdStopRecording
    OpalStatusIncomingCall   m_incomingCall;       ///< Used by OpalIndIncomingCall
    OpalParamAnswerCall      m_answerCall;         ///< Used by OpalCmdAnswerCall/OpalCmdAlerting
    OpalStatusUserInput      m_userInput;          ///< Used by OpalIndUserInput/OpalCmdUserInput
    OpalStatusMessageWaiting m_messageWaiting;     ///< Used by OpalIndMessageWaiting
    OpalStatusLineAppearance m_lineAppearance;     ///< Used by OpalIndLineAppearance
    OpalStatusCallCleared    m_callCleared;        ///< Used by OpalIndCallCleared
    OpalParamCallCleared     m_clearCall;          ///< Used by OpalCmdClearCall
    OpalStatusMediaStream    m_mediaStream;        ///< Used by OpalIndMediaStream/OpalCmdMediaStream
    OpalParamSetUserData     m_setUserData;        ///< Used by OpalCmdSetUserData
    OpalParamRecording       m_recording;          ///< Used by OpalCmdStartRecording
    OpalStatusTransferCall   m_transferStatus;     ///< Used by OpalIndTransferCall
    OpalStatusIVR            m_ivrStatus;          ///< Used by OpalIndCompletedIVR
  } m_param;
};

over to C#. The obvious problem is the two strings which will inevitably be reference types.

So, I tried this:

    [StructLayout(LayoutKind.Explicit)]
    public struct OpalMessageStrUnion
    {
      [FieldOffset(0)]
      [MarshalAs(UnmanagedType.LPStr)]
      public string m_commandError;       ///< Used by OpalIndCommandError

      [FieldOffset(0)]
      [MarshalAs(UnmanagedType.LPStr)]
      public string m_callToken;          ///< Used by OpalCmdHoldcall/OpalCmdRetrieveCall/OpalCmdStopRecording
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct OpalMessageUnion
    {
      [FieldOffset(0)]      
      public OpalParamGeneral m_general;            ///< Used by OpalCmdSetGeneralParameters

      [FieldOffset(0)]
      public OpalParamProtocol m_protocol;           ///< Used by OpalCmdSetProtocolParameters

      [FieldOffset(0)]
      public OpalParamRegistration m_registrationInfo;   ///< Used by OpalCmdRegistration

      [FieldOffset(0)]
      public OpalStatusRegistration m_registrationStatus; ///< Used by OpalIndRegistration

      [FieldOffset(0)]
      public OpalParamSetUpCall m_callSetUp;          ///< Used by OpalCmdSetUpCall/OpalIndProceeding/OpalIndAlerting/OpalIndEstablished     

      [FieldOffset(0)]
      public OpalStatusIncomingCall m_incomingCall;       ///< Used by OpalIndIncomingCall

      [FieldOffset(0)]
      public OpalParamAnswerCall m_answerCall;         ///< Used by OpalCmdAnswerCall/OpalCmdAlerting

      [FieldOffset(0)]
      public OpalStatusUserInput m_userInput;          ///< Used by OpalIndUserInput/OpalCmdUserInput

      [FieldOffset(0)]
      public OpalStatusMessageWaiting m_messageWaiting;     ///< Used by OpalIndMessageWaiting

      [FieldOffset(0)]
      public OpalStatusLineAppearance m_lineAppearance;     ///< Used by OpalIndLineAppearance

      [FieldOffset(0)]
      public OpalStatusCallCleared m_callCleared;        ///< Used by OpalIndCallCleared

      [FieldOffset(0)]
      public OpalParamCallCleared m_clearCall;          ///< Used by OpalCmdClearCall

      [FieldOffset(0)]
      public OpalStatusMediaStream m_mediaStream;        ///< Used by OpalIndMediaStream/OpalCmdMediaStream

      [FieldOffset(0)]
      public OpalParamSetUserData m_setUserData;        ///< Used by OpalCmdSetUserData

      [FieldOffset(0)]
      public OpalParamRecording m_recording;          ///< Used by OpalCmdStartRecording

      [FieldOffset(0)]
      public OpalStatusTransferCall m_transferStatus;     ///< Used by OpalIndTransferCall

      [FieldOffset(0)]
      public OpalStatusIVR m_ivrStatus;          ///< Used by OpalIndCompletedIVR

    }

    /// <summary>
    /// Message to/from OPAL system.
    /// This is passed via the OpalGetMessage() or OpalSendMessage() functions.
    /// </summary> 
    [StructLayout(LayoutKind.Explicit)]
    public struct OpalMessage
    {
      //this guy is an enumeration b.t.w.
      /// <summary>
      /// type of message
      /// </summary> 
      [FieldOffset(0)]
      public OpalMessageType m_type;

      [FieldOffset(4)]
      public OpalMessageUnion m_param;

      [FieldOffset(4)]
      public OpalMessageStrUnion m_strParam;
    }

However, I am still getting the marshaling error telling me that this won't work because I am mixing an object with non-object types in the same memory location. I am now assuming that the structures themselves (i.e. OpalParamGeneral and the like) also cannot mix reference and value types even though they are laid out sequentially?

Doing a separate function call for each structure is not an option by the way. I would rather write a COM wrapper than do that.

回答1:

The [StructLayout(LayoutKind.Explicit)] attribute is unusual, it affects the layout of a structure not only when it is marshaled but also affects the layout of the managed version of a struct. Which can be quite handy, it allows implementing unions in a managed language even when the language itself doesn't support it.

They cause trouble however, the garbage collector has a problem with them. It needs to be able to find references to reference types and cannot do so reliably in a union. Since it cannot tell exactly what type is referenced. It is also a reliability problem, you could overlap a reference type with, say, an IntPtr and discover the address of the object that way.

The CLR class loader checks for this condition and throws a TypeLoadException if overlap occurs.

The workaround for this is clear, you need to make sure to only use blittable types. All of your OpalXxx types need to be structures that themselves contain only blittable types. The workaround for the strings is to declare them as IntPtr instead and use the Marshal class to convert the values. More about blittable types in this MSDN library article.