locked
Marshal Nested Stucture using PInvoke RRS feed

  • Question

  • Hi,

    I am using C# in a VisualStudio 2008 environment and
    have to retrieve data from a 3rd party DLL written in C.
    I can access all but two of the C functions successfully.
    Both of these functions require a nested structure
    to be passed, so the structure can be populated with data.

    // supplied C structure
    typedef  struct
    {
       float  fPeak;
       float  fMean;
       float  fStd; 
       int    iNoPeaks;
       struct
       {
        float  fArea;
        float  fPeak;
        float  fMean;
        float  fStd; 
        int    iNoSubpeaks;
        struct
        {
          float  fArea;   
          float  fPeak;   
          float  fMean;   
          float  fStd;    
        }
        aSubPeaks[5];
       }
       aPeaks[20];
    }
    pcs_distr_info;
    typedef  pcs_distr_info *  p_pcs_distr_info;
    // supplied C prototype
    int Analysis(
      pcs_handle hPcsObject        // handle
      p_pcs_distr_info  pPeaksInfo // problem with this line
    );
    
    
    // part of, example C code supplied
    pcs_distr_info  DistrInfo;
    ...
    int result = Analysis( hPcsObject, &DistrInfo );
    ///////////
    //C# attempt at retrieving data from the DLL function
    
    
     [DllImport("The.dll")]    
     public static extern int Analysis(System.IntPtr handle, 
                                       out DistrInfo distributionInfo);
    
    //----
    // structure in C#
    public const int PCS_MAX_NO_PEAKS     = 20;
    public const int PCS_MAX_NO_SUBPEAKS  = 5;
            
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct SubPeaks
    {
      public float fArea;    
      public float fPeak;    
      public float fMean;    
      public float fStd;     
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct Peaks
    {
      public float fArea;    
      public float fPeak;    
      public float fMean;    
      public float fStd;     
      public int iNoSubpeaks;
    
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = PCS_MAX_NO_SUBPEAKS)]
      public IntPtr subpeak;      
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct DistrInfo
    {
      public float fPeak;
      public float fMean;
      public float fStd;
      public int iNoPeaks;
    
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = PCS_MAX_NO_PEAKS)]
      public IntPtr peak;
    }
    
    // C# code
    System.IntPtr hPcsObject = IntPtr.Zero; // pointer 'handle' initialised to zero
    
    
    SubPeaks[] subPeaks = new SubPeaks[PCS_MAX_NO_SUBPEAKS];
    for (int i = 0; i < PCS_MAX_NO_SUBPEAKS; i++)
    {
      subPeaks[i] = new SubPeaks();
      subPeaks[i].fArea = 1.0f; // just fill with test values
      subPeaks[i].fMean = 1.0f;
      subPeaks[i].fPeak = 1.0f;
      subPeaks[i].fStd  = 1.0f;
    }
    
    Peaks peaks = new Peaks();
    peaks.subpeak = new IntPtr[PCS_MAX_NO_SUBPEAKS];
    for (int j = 0; j < PCS_MAX_NO_SUBPEAKS; j++)
    {
      int nSizeSubPk = Marshal.SizeOf(subPeaks[j]);
      peaks.subpeak[j] = Marshal.AllocHGlobal(nSizeSubPk);
      Marshal.StructureToPtr(subPeaks[j], peaks.subpeak[j], false);
    }
    
    Peaks[] peaksArr = new Peaks[PCS_MAX_NO_PEAKS];
    for (int k = 0; k < PCS_MAX_NO_PEAKS; k++)
    {
      peaksArr[k] = new Peaks();
      peaksArr[k].fArea = 2.0f;  // just fill with test values
      peaksArr[k].fMean = 2.0f;
      peaksArr[k].fPeak = 2.0f;
      peaksArr[k].fStd  = 2.0f;
      peaksArr[k].iNoSubpeaks = 2;
    }
    
    DistrInfo di = new DistrInfo();
    di.peak = new IntPtr[PCS_MAX_NO_PEAKS];
    
    for (int m = 0; m < PCS_MAX_NO_PEAKS; m++)
    {
      int nSizePk = Marshal.SizeOf(peaksArr[m]);
      di.peak[m] = Marshal.AllocHGlobal(nSizePk);
      Marshal.StructureToPtr(peaksArr[m], di.peak[m], false);
    }
    int nSizeTable = Marshal.SizeOf(di) + Marshal.SizeOf(peaks);
    IntPtr pDistribInfo = Marshal.AllocHGlobal(nSizeTable);
    Marshal.StructureToPtr(di, pDistribInfo, false);
    
    // C method call in DLL
    int result = Analysis(hPcsObject, out pDistribInfo);

    The code compiles ok, but when I try to debug the
    code the debugger closes when I [F10](StepOver) the
    Analysis(...) C function.

    I am not convinced that I am correctly marshalling between the managed
    and unmanaged code.

    The supplied DLL has been around for many years and working ok with C/C++ so should be fine.
    The supplier was not aware that anyone was using it in conjunction with managed code.

    Can anyone please help?

    Wednesday, February 17, 2010 11:25 PM

Answers

  • Your C++ structure is flattened. All its data exists in one huge chunk.

    The C# structure you're building is linked. The "child" structures exist in different places in memory and are pointed to from the parent structure.

    This is why it's failing.

    To fix it, you need to flatten your C# structure, like this:

    [StructLayout(LayoutKind.Sequential)]
    public struct DistrInfo
    {
      public float fPeak;
      public float fMean;
      public float fStd;
      public int iNoPeaks;
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = PCS_MAX_NO_PEAKS)]
      public Peaks[] peak;
    }


    Note that in managed code, flattened structures don't exist, so in the managed side it will be linked, but the declaration above will marshal it to a flattened structure.

    So in managed code you have to initialize it like this:
      DistrInfo info = new DistrInfo();
      info.peak = new Peaks[PCS_MAX_NO_PEAKS];
    and then fill in your information. There is no need for IntPtr, SizeOf, AllocHGlobal, or StructureToPtr.

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    • Marked as answer by Forest7677 Friday, February 19, 2010 1:52 PM
    Thursday, February 18, 2010 2:37 PM

All replies

  • Your C++ structure is flattened. All its data exists in one huge chunk.

    The C# structure you're building is linked. The "child" structures exist in different places in memory and are pointed to from the parent structure.

    This is why it's failing.

    To fix it, you need to flatten your C# structure, like this:

    [StructLayout(LayoutKind.Sequential)]
    public struct DistrInfo
    {
      public float fPeak;
      public float fMean;
      public float fStd;
      public int iNoPeaks;
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = PCS_MAX_NO_PEAKS)]
      public Peaks[] peak;
    }


    Note that in managed code, flattened structures don't exist, so in the managed side it will be linked, but the declaration above will marshal it to a flattened structure.

    So in managed code you have to initialize it like this:
      DistrInfo info = new DistrInfo();
      info.peak = new Peaks[PCS_MAX_NO_PEAKS];
    and then fill in your information. There is no need for IntPtr, SizeOf, AllocHGlobal, or StructureToPtr.

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    • Marked as answer by Forest7677 Friday, February 19, 2010 1:52 PM
    Thursday, February 18, 2010 2:37 PM
  • Brilliant

    I implemented your suggested change and it fixed the problem.

    The only other change I made was to the Peaks structure.
    I made the 'Public IntPtr subpeak' into a'Public SubPeaks[] subpeak' (as you did in the DistrInfo structure)and the DLL function populated the nested structure with data.

    What a simple solution.

    Thanks for your help.
    Friday, February 19, 2010 1:52 PM
  • Brilliant

    I implemented your suggested change and it fixed the problem.

    The only other change I made was to the Peaks structure.
    I made the 'Pblic IntPtr subpeak' into a'Public SubPeaks[] subpeak' (as you did in the DistrInfo structure)and the DLL function m4v populated the nested structure with data.

    What a simple solution.

    Thanks for your help.

    Nice writing, Thanks for your effort!
    Friday, August 6, 2010 3:02 AM
  • Hi Steve,

     

    I'm working on a project whose requirement is similar to Forest7677's, ie marshal nested structure using P/Invoke and my application is running on WIN CE.

    My project is compiled and built on VS 2008.

    I have implemented the nested structure and calling method as you have suggested but NotSupportedExcetpion is thrown when I call the provided C function.

    Any idea what went wrong?

    Many thanks

    Alvin.

    Thursday, October 21, 2010 1:13 AM