none
URGENT: Passing Structure With Member Array From COM App to c# DLL RRS feed

  • Question

  • Hello,
    I'm stumped at this point have been unable to find an answer to this, so I hope someone here can help.  We have a legacy application that is com-based.  I'm writing the new COM DLL under .NET using c#.  I've been able to pass the data I need with one exception at this point and that being a structure which has a member that is an array of another structure.  The snippets below should help.  I basically keep getting this error every time I try to move the data to managed memory: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."  So here are the pieces:

    Here are my data structures in managed code:

            [StructLayout(LayoutKind.Sequential)]  
            public struct WF_TEST  
            {  
                [MarshalAs(UnmanagedType.BStr)]  
                public string m_strString;  
                [MarshalAs(UnmanagedType.VariantBool)]  
                public bool bBoolean;  
     
                // array of WF_PARAMETER types  
                public IntPtr m_parParameters;  
            }  
     
            [StructLayout(LayoutKind.Sequential)]  
            public struct WF_PARAMETER  
            {  
                [MarshalAs(UnmanagedType.BStr)]  
                public string m_strParameterName;  
                [MarshalAs(UnmanagedType.BStr)]  
                public string m_strParameterValue;  
            }  


    Here's my exposed method that's called from the COM application:

    1         [STAThread]  
    2         public void WFTest(ref CStructures.WF_TEST stTest, int iSize, int iItems)  
    3         {  
    4             try 
    5             {  
    6                 int iOffset = 0;  
    7  
    8                 IntPtr ptrBucket = IntPtr.Zero;  
    9                 do 
    10                 {  
    11                     iSize = Marshal.SizeOf(typeof(CStructures.WF_PARAMETER));  
    12                     ptrBucket = Marshal.AllocCoTaskMem(iSize);  
    13                     // always "blows up" on the following line  
    14                     ptrBucket = Marshal.ReadIntPtr(stTest.m_parParameters, iOffset);  
    15                     if ((int)ptrBucket == 0)  
    16                     {  
    17                         break;  
    18                     }  
    19  
    20                     CStructures.WF_PARAMETER dsParm =  
    21                         (CStructures.WF_PARAMETER)Marshal.PtrToStructure(  
    22                         ptrBucket, typeof(CStructures.WF_PARAMETER));  
    23  
    24                     Marshal.FreeCoTaskMem(ptrBucket);  
    25                     iOffset += iSize;  
    26  
    27                 } while ((int)ptrBucket != 0);  
    28             }  
    29             catch (Exception ex)  
    30             {  
    31                 m_strLastError = ex.Message;  
    32                 m_oNetAccessor.LastError = ex.Message;  
    33             }  
    34         }  

    This is how the COM application puts it together and makes the call:

        CString strText = "Test String";  
        /*
            WF_TEST unmanaged signature is:
            WF_TEST
            {
                BSTR m_strString;
                VARIANT_BOOL bBoolean;
                long m_parParameters;
            };
        */ 
        WF_TEST stTest;  
        stTest.bBoolean    = VARIANT_FALSE;  
        stTest.m_strString = strText.AllocSysString();  
     
        int iItems = 5;  
        WF_PARAMETER *parParms = (WF_PARAMETER*)CoTaskMemAlloc(sizeof(WF_PARAMETER) * iItems);  
        for (int i=0; i < iItems; i++)  
        {  
            CString strName, strValue;  
            strName.Format("Item %d", (i+1));  
            strValue.Format("Value %d", (i+1));  
     
            parParms[i].m_strParameterName  = strName.AllocSysString();  
            parParms[i].m_strParameterValue = strValue.AllocSysString();  
        }  
     
        stTest.m_parParameters = (long)parParms;  
     
        try 
        {  
            // make the COM call  
            pRA->WFTest(&stTest, sizeof(WF_PARAMETER), iItems);  
        }  
             ... 

    Note that the string and boolean values are passed correctly.  It's just the pointer address that keeps giving me grief.  I've tried different assignments to stTest.m_parParameters, e.g., (long)&parParms[0].  I've debugged into the client code too to make sure the address is converted correctly and it looks fine.  So I'm at a loss at this point.

    Any help would be greatly appreciated as I'm stuck in the mud at this point.  Thanks much.  Please let me know if I need to provide any more info.

    AY


    PRISMAY
    Tuesday, June 17, 2008 5:34 PM

Answers

  • I gave up on passing the structure using IntPtr as it just wasn't working.  I did find a solution using a SAFEARRAY instead based on the article found here: Collections Interoperability (thank you Meir Bechor).

    I changed my WF_TEST structure's m_parParameters to this:
    1     [MarshalAs(UnmanagedType.SafeArray)]  
    2     public Array m_parParameters; 

    Then, I can access what I need by casting it:
    1     CStructures.WF_PARAMETER[] ar = (CStructures.WF_PARAMETER[])stTest.m_parParameters; 

    Changing the managed code structure changes the unmanaged code signature for WF_TEST, so  now it's:
    1 WF_TEST  
    2 {  
    3     BSTR m_strString;  
    4     VARIANT_BOOL bBoolean;  
    5     SAFEARRAY * m_parParameters;  
    6 }; 

    In my application code, I just had to create a collection of the structure WF_PARAMETER, populate the collection and pass it to CreateUDTSafeArrayFromCol:
    1     std::vector<WF_PARAMETER> v;  // create collection object  
    2     for (int i=0; i < iItems; i++)  
    3     {  
    4         CString strName, strValue;  
    5         strName.Format("Item %d", (i+1));  
    6         strValue.Format("Value %d", (i+1));  
    7  
    8         parParms[i].m_strParameterName  = strName.AllocSysString();  
    9         parParms[i].m_strParameterValue = strValue.AllocSysString();  
    10  
    11         v.push_back(parParms[i]);  // populate collection  
    12     }  
    13  
    14     // Get the safearray pointer based on the collection  
    15     stTest.m_parParameters = CreateUDTSafeArrayFromCol(v, "..\\References\\MyCOMObject.tlb"); 

    I'd still like to get the IntPtr working because I don't understand why it doesn't, but at least this will get me going.

    I hope this will help someone else who may run into the same problem.
    PRISMAY
    • Edited by PRISMAY Thursday, June 19, 2008 8:35 PM changed link target to new window
    • Marked as answer by PRISMAY Thursday, June 19, 2008 10:42 PM
    Thursday, June 19, 2008 8:29 PM

All replies

  • You are treating m_parParameters as a pointer to an array of pointers to structures.  It isn't, it is a pointer to an array of structures.  Get rid of the ReadIntPtr() call.  Call PtrToStructure on m_parParameters + 0, m_parParameters + iSize, m_parParameters + iSize*2, etc.  Use Debug + Other Windows + Memory1 to debug your pointer logic.
    Hans Passant.
    Tuesday, June 17, 2008 8:24 PM
    Moderator
  • Hans,
    Thank you for the clarification.  I thought, earlier, that's what I may have been doing.  But when I tried that code, I ran into this error on the call to Marshal.PtrToStructure: "FatalExecutionEngineError was detected
    Message: The runtime has encountered a fatal error. The address of the error was at 0x7a13f210, on thread 0x6b0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
    "

    Here's the code I had earlier.  I tried several different ways using Marshal.Copy using IntPtr and IntPtr[] to move the data blocks around and just couldn't get it done successfully (Note that you can't pointer math directly on an IntPtr type, therefore the use of ToInt32()):
        for (int i=0; i < iItems; i++)  
        {  
            int iPtr = (stTest.m_parParameters.ToInt32() + (i * iSize));  
            CStructures.WF_PARAMETER dsParm =  
                (CStructures.WF_PARAMETER)Marshal.PtrToStructure(  
                (IntPtr)iPtr, typeof(CStructures.WF_PARAMETER));  
        } 

    My brain is exhausted from this.  Ugh.  Oh and no more hair either. ;)
    PRISMAY
    Tuesday, June 17, 2008 9:06 PM
  • Well, better, although it couldn't work on a 64-bit or /3GB operating system.  Use "long" instead of "int".  Put the value you calculate as the new pointer in the Memory1 window's Address box to verify you see the data you expect to copy.  Getting familiar with this window is the key to debugging this problem.
    Hans Passant.
    Tuesday, June 17, 2008 9:13 PM
    Moderator
  • Well, the memory window is definitely showing that the memory is not the same.  When I debug the application, the parParms memory block shows the addresses to its BSTRs.  I convert the hex memory value to decimal and it does match what gets assigned to the long m_parParameters and that value is correctly being passed to managed code.  When I convert the number, from managed code, back to hex and view that location, it just shows all question marks (??) rather than any kind of content.

    I'll play around with the memory window a bit more, but am not sure why managed code seems to be looking at a different location than what's being passed?  Thanks much.
    PRISMAY
    • Edited by PRISMAY Wednesday, June 18, 2008 2:32 PM fixed typo
    Wednesday, June 18, 2008 2:16 PM
  • Good.  The question marks is what makes your code crash.  You'd be looking at an address for which no virtual memory pages were allocated.
    Hans Passant.
    Wednesday, June 18, 2008 2:35 PM
    Moderator
  • I guess what I don't understand is, I have the memory address and I thought .NET would be able to "see" that block and then copy it or move it via marshaling so it can get the data into whatever buffer via Marshal.Copy or Marshal.PtrToStructure and so on.  I haven't read or found any articles on having to do any additional math on the address value passed as a pointer.  I thought .NET took care of this via the Marshal object?


    [EDIT]
    I wonder if my problem is how I'm allocating memory, i.e., via CoTaskMemAlloc.  I'm going to try allocating memory on the heap using HeapCreate, HeapAlloc and HeapFree and see if that makes a difference.

    [EDIT]
    Well, allocating memory on the heap didn't make a difference.  Sigh.  I'm just not sure what to do at this point honestly.  Here's the change I made to the code to allocate memory on the heap:
    1     int iBlockSize = sizeof(WF_PARAMETER) * iItems;  
    2     HANDLE hdlHeap = HeapCreate(NULL, iBlockSize, iBlockSize);  
    3     WF_PARAMETER *parParms = (WF_PARAMETER*)HeapAlloc(hdlHeap, HEAP_ZERO_MEMORY, iBlockSize);  
    4 ...  
    5     BOOL bHeapFreed = HeapFree(hdlHeap, HEAP_NO_SERIALIZE, parParms); 


    PRISMAY
    • Edited by PRISMAY Wednesday, June 18, 2008 3:17 PM added heap attempt info
    Wednesday, June 18, 2008 2:49 PM
  • Any other ideas please?  Thank you.
    PRISMAY
    Thursday, June 19, 2008 1:14 PM
  • I gave up on passing the structure using IntPtr as it just wasn't working.  I did find a solution using a SAFEARRAY instead based on the article found here: Collections Interoperability (thank you Meir Bechor).

    I changed my WF_TEST structure's m_parParameters to this:
    1     [MarshalAs(UnmanagedType.SafeArray)]  
    2     public Array m_parParameters; 

    Then, I can access what I need by casting it:
    1     CStructures.WF_PARAMETER[] ar = (CStructures.WF_PARAMETER[])stTest.m_parParameters; 

    Changing the managed code structure changes the unmanaged code signature for WF_TEST, so  now it's:
    1 WF_TEST  
    2 {  
    3     BSTR m_strString;  
    4     VARIANT_BOOL bBoolean;  
    5     SAFEARRAY * m_parParameters;  
    6 }; 

    In my application code, I just had to create a collection of the structure WF_PARAMETER, populate the collection and pass it to CreateUDTSafeArrayFromCol:
    1     std::vector<WF_PARAMETER> v;  // create collection object  
    2     for (int i=0; i < iItems; i++)  
    3     {  
    4         CString strName, strValue;  
    5         strName.Format("Item %d", (i+1));  
    6         strValue.Format("Value %d", (i+1));  
    7  
    8         parParms[i].m_strParameterName  = strName.AllocSysString();  
    9         parParms[i].m_strParameterValue = strValue.AllocSysString();  
    10  
    11         v.push_back(parParms[i]);  // populate collection  
    12     }  
    13  
    14     // Get the safearray pointer based on the collection  
    15     stTest.m_parParameters = CreateUDTSafeArrayFromCol(v, "..\\References\\MyCOMObject.tlb"); 

    I'd still like to get the IntPtr working because I don't understand why it doesn't, but at least this will get me going.

    I hope this will help someone else who may run into the same problem.
    PRISMAY
    • Edited by PRISMAY Thursday, June 19, 2008 8:35 PM changed link target to new window
    • Marked as answer by PRISMAY Thursday, June 19, 2008 10:42 PM
    Thursday, June 19, 2008 8:29 PM