locked
Error: Calling C++ dll function in C# RRS feed

  • Question

  • Hi,
     
    I am trying to use functions in C++ dll from C#, but I got an error: "attempt to read or write protected memory. This is often indication that other memory is corrupt"  (At the last line)
    Anyone know how to fix ?
     
    Here is C++ functions:
     
    typedef void *DGNHandle;
     
     __declspec(dllexport) DGNHandle CPL_DLL    DGNOpen( const char *, int );
     __declspec(dllexport) DGNElemCore CPL_DLL *DGNReadElement( DGNHandle )
    
     
    Here is structure in C++:
    typedef struct {
        int         offset;
        int         size;
     
        int         element_id;     /*!< Element number (zero based) */
        int         stype;          /*!< Structure type: (DGNST_*) */
        int         level;          /*!< Element Level: 0-63 */
        int         type;           /*!< Element type (DGNT_) */
        int         complex;        /*!< Is element complex? */
        int         deleted;        /*!< Is element deleted? */
     
        int         graphic_group;  /*!< Graphic group number */
        int         properties;     /*!< Properties: ORing of DGNPF_ flags */
        int         color;          /*!< Color index (0-255) */
        int         weight;         /*!< Line Weight (0-31) */
        int         style;          /*!< Line Style: One of DGNS_* values */
     
        int         attr_bytes;     /*!< Bytes of attribute data, usually zero. */
        unsigned char *attr_data;   /*!< Raw attribute data */
     
        int         raw_bytes;      /*!< Bytes of raw data, usually zero. */
        unsigned char *raw_data;    /*!< All raw element data including header. */
    } DGNElemCore; 
     
    And below converted codes are in C#:
     
    [StructLayout(LayoutKind.Sequential )]
        public class DGNElemCore
        {
            public int attr_bytes;
            public byte[] attr_data;
            public int color;
            public int complex;
            public int deleted;
            public int element_id;
            public int graphic_group;
            public int level;
            public int offset;
            public int properties;
            public int raw_bytes;
            public byte[] raw_data;
            public int size;
            public int style;
            public int stype;
            public int type;
            public int weight;
     
        }
    [DllImport("DgnLib.dll", EntryPoint = "DGNOpen")]
            public static extern IntPtr  DGNOpen(string fileName, int bUpdate);
    [DllImport("DgnLib.dll", EntryPoint = "DGNReadElement")]
            public static extern DGNElemCore DGNReadElement(IntPtr DGNHandle)
     
    Codes for testing:
     
    DGNElemCore element = new DGNElemCore();
    element = DgnFile.DGNReadElement(dgnFile.oDgnFile) 
    Monday, October 22, 2012 8:01 AM

Answers

  • Great ! I've just tried it: IntPtr newPtr = new IntPtr(ptrElement.ToInt64() + 128); 

    Now, it returns correct values.

    Thank you so much !

    • Marked as answer by Taibc Wednesday, October 24, 2012 8:32 AM
    Wednesday, October 24, 2012 8:32 AM

All replies

  • There are 2 problems with DGNElemCore:

    1. The order of fields is different in the C# version, it seems that they have been sorted alphabetically.
    2. attr_data and raw_data cannot be marshaled from native to managed because the marshaler doesn't know the size of those arrays. In this case you need to use IntPtr instead of byte[] and then manually copy the data from the native memory to a byte[] (using Marshal.Copy for example).
    Monday, October 22, 2012 9:03 AM
  • Thanks Mike Danes, you are right. But I am having another error: when try to cast between above classes:

     I have the function in C:

     __declspec(dllexport) DGNElemCore CPL_DLL *DGNReadElement( DGNHandle );

    This function will read a file and return a pointer to either DGNElemCore or DGNElemText, and I can using casting between them.

    But, when I convert codes into C# as below, I can't cast types of these structs:

    [DllImport("DgnLib.dll", EntryPoint = "DGNReadElement")]              
           
    public static extern IntPtr DGNReadElement(IntPtr DGNHandle);    
     
    public static DGNElemCore ReadElement(IntPtr DGNHandle)
           
    {
               
    DGNElemCore element = new DGNElemCore ();
               
    IntPtr itr = DGNReadElement(DGNHandle);
               
    MessageBox.Show(itr.GetType ().ToString ());

                element
    = (DGNElemCore)Marshal.PtrToStructure(itr, typeof(DGNElemCore));                          
               
    return element;            
           
    }  

    And the codes for testing:

    DGNElemCore element = new DGNElemCore();
    element
    = DgnFile.ReadElement(DGNHandle);

    while (element != null)
    {
       
    if (element.type == 17 ) // Element is a DGNElemText
       
    {
             
    DGNElemText txtElement = new DGNElemText();
             txtElement
    = (DGNElemText)element; **// throw error: Unable to cast object of type DgnLib.DGNElemCore to type DgnLib.DGNElemText**

             
    ...... // More codes

       
    }
    }

    Structs in C#:


    [StructLayout (LayoutKind.Sequential)]
       
    public class DGNElemText : DGNElemCore
       
    {
           
    // Fields
           
    DGNElemCore core;

           
    public int font_id;
           
    public int justification;
           
    public double length_mult;
           
    public double height_mult;
           
    public double rotation;      
           
    public DGNPoint origin;        
           
    public string text;        
       
    }

    Do you know how to fix this error ?

    Thank you very much !

    Tuesday, October 23, 2012 9:51 AM
  • Calling GetType on an IntPtr will simply return the type IntPtr, the native pointer type is not preserved across PInvoke calls. The only way to know which type of element you have is to first marshal to DGNElemCore, check the type field and then marshal again to DGNElemText, something along these lines:

            [StructLayout(LayoutKind.Sequential)]
            public class DGNElemCore {
                ...
                public int type;
                ...
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public class DGNElemText : DGNElemCore {
                ...
            }
    
            [DllImport("DgnLib.dll", EntryPoint = "DGNReadElement")]
            public static extern IntPtr DGNReadElement(IntPtr DGNHandle);
    
            public DGNElemCore ReadElement(IntPtr handle) {
                IntPtr ptrElement = DGNReadElement(handle);
    
                if (ptrElement == IntPtr.Zero)
                    return  null;
    
                DGNElemCore core = (DGNElemCore)Marshal.PtrToStructure(ptrElement, typeof(DGNElemCore));
    
                switch (core.type) {
                    case 17:
                        return (DGNElemText)Marshal.PtrToStructure(ptrElement, typeof(DGNElemText));
                    default:
                        return core;
                }
            }
    
     

    Note that DGNElemText needs to either inherit from DGNElemCore or have the first field of type DGNElemCore, not both. In my example I used inheritance.

    Tuesday, October 23, 2012 10:06 AM
  • Thanks Mike Danes very much. 

    But I got the error: "vshost32-clr2.exe has stopped working" when run to the statement: 

    return (DGNElemText)Marshal.PtrToStructure(ptrElement, typeof(DGNElemText));

    Do you know why ?

    Tuesday, October 23, 2012 10:32 AM
  • Hmm, if you run the application din debugger it should tell you what exception occurred. Probably there's something wrong about DGNElemText class.
    Tuesday, October 23, 2012 10:45 AM
  • Thanks Mike Danes,

    I discovered that lack of the below line in DGNElemText class :

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]

    above the line:

    public string   text;  

    However, the text value is always empty (""), instead of returning expected values such as :"Hello", "abc", "123",...

    I also try to use IntPtr and Marshal .PtrToStringAnsi  instead of using string as above, but still give empty values.

    Please see the struct DGNElemText in C again:

    typedef struct {
        DGNElemCore core;
        
        int         font_id;       /*!< Microstation font id, no list available*/
        int         justification; /*!< Justification, see DGNJ_* */
        double      length_mult;   /*!< Char width in master (if square) */
        double      height_mult;   /*!< Char height in master units */
        double      rotation;      /*!< Counterclockwise rotation in degrees */
        DGNPoint    origin;        /*!< Bottom left corner of text. */
        char        string[1];     /*!< Actual text (length varies, \0 terminated*/
    } DGNElemText;

    and in C#

     [StructLayout (LayoutKind.Sequential)]
        public class DGNElemText : DGNElemCore 
        {
            // Fields
            //DGNElemCore core;
    
            public int font_id;
            public int justification;
            public double length_mult;
            public double height_mult;
            public double rotation;       
            public DGNPoint origin;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
            public string   text;        
        }

    Do you know why ?

    Thanks and regards,

    Wednesday, October 24, 2012 2:03 AM
  • I see, a variable length string embedded at the end of the native struct...

    You'll have to remove the text from the DGNElemText class and unmarshall it manually, something like:

    [StructLayout (LayoutKind.Sequential)]
    public class DGNElemTextNative : DGNElemCore {
        ...
        public DGNPoint origin;
    }
    
    public class DGNElemText : DGNElemTextNative {
        public string text;
    }
    
    switch (core.type) {
    case 17:
        DGNElemTextNative elemTextNative = (DGNElemTextNative)Marshal.PtrToStructure(ptrElement, typeof(DGNElemTextNative));
        string text = Marshal.PtrToStringAnsi(ptrElement + Marshal.SizeOf(typeof(DGNElemTextNative)));
        DGNElemText elemText = new DGNElemText();
        // copy data from elemTextNative to elemText
        elemText.text = text
        return elemText;
    
    In general, due to the complexity of the native interface you may need to create separate types for marshaling and for actual use in the rest of the C# code. And if you happen to know C++ it may be simple to create a managed wrapper for that native dll using C++/CLI.
     
    Wednesday, October 24, 2012 5:52 AM
  • Thank you. But there is error: Operator '+' cannot be applied to operands of type 'System.IntPtr' and 'int'   at the statement: Marshal.PtrToStringAnsi(ptrElement + Marshal.SizeOf(typeof(DGNElemTextNative)));

    So I used the below lines: 

    IntPtr newPtr = new IntPtr(ptrElement.ToInt64() + Marshal.SizeOf(typeof(DGNElemTextNative)));
    string text = Marshal.PtrToStringAnsi(newPtr);

    But, the returned value is not correct.

    Do you know why ? 

    PS: I can work with C++, but haven't found out more about using C++/CLI yet. Do you think this is better way for my project (using C#)?

    Thanks and regards


    Wednesday, October 24, 2012 6:51 AM
  • "there is error: Operator '+' cannot be applied to operands of type 'System.IntPtr' and 'int'  "

    Oops, sorry about that. Your version is correct.

    "But, the returned value is not correct."

    Hmm, probably there's a problem with sizeof. Try Marshal.OffsetOf(typeof(DGNElemText), "text") instead. If that doesn't work either then you need to figure out the exact offset of the text field on the C++ side and hardcode it in the C# code.

    "I can work with C++, but haven't found out more about using C++/CLI yet. Do you think this is better way for my project (using C#)?"

    If you have many more such types to deal with then it should be easier in C++/CLI. If you only have these 2 element types then C# should be good enough.

    Wednesday, October 24, 2012 7:22 AM
  • Still error :(  

    Type 'DgnLib.DGNElemText' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

    I used codes:

    case 17:
              DGNElemTextNative elemTextNative = (DGNElemTextNative)Marshal.PtrToStructure(ptrElement, typeof(DGNElemTextNative));
              IntPtr newPtr = new IntPtr(ptrElement.ToInt64() + Marshal.OffsetOf(typeof(DGNElemText), "text").ToInt64());
              string text = Marshal.PtrToStringAnsi(newPtr);
    Could you give advices or tell me:  how to "figure out the exact offset of the text field on the C++ side and hardcode it in the C# code."

    Wednesday, October 24, 2012 7:46 AM
  • Hi Mike Danes,

    I forgot add " [StructLayout (LayoutKind.Sequential )]" above the class DGNElemText.

    However, I still received an incorrect Text value as before.


    Wednesday, October 24, 2012 7:56 AM
  • "Type 'DgnLib.DGNElemText' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed."

    Add [StructLayout(LayoutKind.Sequential)] to DGNElemText. I didn't add it in my original example because in that example I wasn't using that type for marshaling.

    "figure out the exact offset of the text field on the C++ side and hardcode it in the C# code."

    In C++ you can use the offsetof macro to get the offset of a struct field:

    #include <stdio.h>
    #include <stddef.h>
    #include "you dgn header"
    int main() {
        printf("%d\n", offsetof(DGNElemText, string));
        return 0;
    }
    

    Wednesday, October 24, 2012 7:56 AM
  • Hi Mike Danes,

    It shows value: 128

    Could you please tell me how to do next ?

    Wednesday, October 24, 2012 8:20 AM
  • Did you try to add that StructLayout first? It would be preferable to use Marshal.OffsetOf if it works. If it doesn't then just replace the Marshal.OffsetOf call with 128.
    Wednesday, October 24, 2012 8:26 AM
  • Great ! I've just tried it: IntPtr newPtr = new IntPtr(ptrElement.ToInt64() + 128); 

    Now, it returns correct values.

    Thank you so much !

    • Marked as answer by Taibc Wednesday, October 24, 2012 8:32 AM
    Wednesday, October 24, 2012 8:32 AM
  • Good job,Taibc!

    Thanks for sharing the solution ! Have a nice day !

    Regards,


    Lisa Zhu [MSFT]
    MSDN Community Support | Feedback to us

    Thursday, October 25, 2012 1:21 AM
  • Hi Mike Danes,

    I am getting another error while trying to marsharling a struct array.

    The struct in C is: 

    typedef struct {
      DGNElemCore   core;
    
      int           num_vertices;  /*!< Number of vertices in "vertices" */
      DGNPoint      vertices[2];   /*!< Array of two or more vertices */
    
    } DGNElemMultiPoint;  

    and in C#:

    [StructLayout(LayoutKind.Sequential)]
        public class DGNElemMultiPoint : DGNElemCore
        {
            // Fields        
            public int num_vertices;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public DGNPoint[] vertices;     
            // public IntPtr vertices;   
        }

    I got the error "Exception of type 'System.ExecutionEngineException' was thrown."  with the statement: 

    DGNElemMultiPoint elemMultiPoint = (DGNElemMultiPoint)Marshal.PtrToStructure(ptrElement, typeof(DGNElemMultiPoint));

    The above statement is executed successful if I use "IntPtr" instead of DGNPoint[], but I will get another error: No parameterless constructor defined for this object   at the statement: 

    DGNPoint[] mulPoint = new DGNPoint[2];
    
    mulPoint = (DGNPoint[]) Marshal.PtrToStructure(ptrElement, typeof(DGNPoint[]));

    Do you know what is the solution for this ?

    Thanks and regards,



    Thursday, October 25, 2012 8:51 AM
  • This is basically the same problem as in the case of DGNElemText. You have a variable sized array embedded at the end of the struct. If you use ByValArray and SizeConst = 2 the marshaler will only see 2 vertices, it will ignore all the rest up to num_vertices.

    "I got the error "Exception of type 'System.ExecutionEngineException' was thrown."  with the statement: "

    Hmm, looks like there's an additional problem. What type is DGNPoint in C#? struct or class? It should be a struct.

    "The above statement is executed successful if I use "IntPtr" instead of DGNPoint[], but I will get another error: No parameterless constructor defined for this object   at the statement: "

    You can't put a IntPtr there, you have to do the offsetof trick you did for DGNElemText.

    Thursday, October 25, 2012 9:10 AM
  • Great, thank you !

    After changing DGNPoint class to struct, it is ok now. Could you tell me why we use "Struct" in this case ?

    Normal, it only has 2 vertices. But if it has more than 2 vertices, do you think that my program will be error ? (Can I fix it)

    Kind regards,


    Thursday, October 25, 2012 9:33 AM
  • "Could you tell me why we use "Struct" in this case ?"

    A struct is marshaled as a value, you get an array of points. A class is marshaled as a pointer, you get an array of pointers to points and that's not what the C version uses.

    "Normal, it only has 2 vertices. But if it has more than 2 vertices, do you think that my program will be error ? (Can I fix it)"

    It won't crash during marshaling but the array will always contain 2 vertices. If num_vertices is > 2 and you try to access the vertex at index 2 for example then you'll get an index out of range exception. You can fix it by using the same technique as in the text case except instead of Marshal.PtrToStringAnsi you need to use something like this:

    IntPtr verticesPtr = new IntPtr(ptrElement.ToInt64() + whateverOffsetVerticesFieldHasOnTheCSide);
    DGNPoint[] vertices = new DGNPoint[multipointElement.num_vertices];
    for (int i = 0; i < vertices.Length; ++i) {
        vertices[i] = (DGNPoint)Marshal.PtrToStructure(verticesPtr, typeof(DGNPoint));
        verticesPtr = new IntPtr(verticesPtr.ToInt64() + Marshal.SizeOf(DGNPoint));
    }

    Thursday, October 25, 2012 9:54 AM
  • Thanks Mike Danes,

    So, I need to identify the values for: whateverOffsetVerticesFieldHasOnTheCSide  ?


    Thursday, October 25, 2012 10:25 AM
  • Yes.
    Thursday, October 25, 2012 10:25 AM
  •  You can fix it by using the same technique as in the text case except instead of Marshal.PtrToStringAnsi you need to use something like this:
    IntPtr verticesPtr = new IntPtr(ptrElement.ToInt64() + whateverOffsetVerticesFieldHasOnTheCSide);
    DGNPoint[] vertices = new DGNPoint[multipointElement.num_vertices];
    for (int i = 0; i < vertices.Length; ++i) {
        vertices[i] = (DGNPoint)Marshal.PtrToStructure(verticesPtr, typeof(DGNPoint));
        verticesPtr = new IntPtr(verticesPtr.ToInt64() + Marshal.SizeOf(DGNPoint));
    }

    Thank you. I can use the above way to for 2 vertices, but not successful if have more than 2 vertices (without error). Do I need change the below statements ?

     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public DGNPoint[] vertices;     

    Friday, October 26, 2012 3:21 AM
  • In general this can be done exactly like in the text case, remove the vertices field from the marshaled class and add it back to another class that inherits from the marshaled class. DGNElemMultiPointNative and DGNElemMultiPoint, similar to DGNElemTextNative and DGNElemText.

    Another approach could be to leave the class DGNElemMultiPoint as is and do something like this:

    IntPtr verticesPtr = new IntPtr(ptrElement.ToInt64() + whateverOffsetVerticesFieldHasOnTheCSide + 2 * Marshal.SizeOf(DGNPoint));
    Array.Resize(ref elemMultiPoint.vertices, elemMultiPoint.num_vertices);
    for (int i = 2; i < vertices.Length; ++i) {
        vertices[i] = (DGNPoint)Marshal.PtrToStructure(verticesPtr, typeof(DGNPoint));
        verticesPtr = new IntPtr(verticesPtr.ToInt64() + Marshal.SizeOf(DGNPoint));
    }
    That is, you use normal marshaling for the first 2 vertices and then you resize the array to the correct number of vertices and marshal the rest of vertices manually.
    Friday, October 26, 2012 5:47 AM
  • "Could you tell me why we use "Struct" in this case ?"

    A struct is marshaled as a value, you get an array of points. A class is marshaled as a pointer, you get an array of pointers to points and that's not what the C version uses.

    "Normal, it only has 2 vertices. But if it has more than 2 vertices, do you think that my program will be error ? (Can I fix it)"

    It won't crash during marshaling but the array will always contain 2 vertices. If num_vertices is > 2 and you try to access the vertex at index 2 for example then you'll get an index out of range exception. You can fix it by using the same technique as in the text case except instead of Marshal.PtrToStringAnsi you need to use something like this:

    IntPtr verticesPtr = new IntPtr(ptrElement.ToInt64() + whateverOffsetVerticesFieldHasOnTheCSide);
    DGNPoint[] vertices = new DGNPoint[multipointElement.num_vertices];
    for (int i = 0; i < vertices.Length; ++i) {
        vertices[i] = (DGNPoint)Marshal.PtrToStructure(verticesPtr, typeof(DGNPoint));
        verticesPtr = new IntPtr(verticesPtr.ToInt64() + Marshal.SizeOf(DGNPoint));
    }

    Hi Mike Danes,

    There was one my mistake. Now, it is successful for more than 2 vertices by using above statements.

    Thanks and regards,

    Have a good weekend !

    Friday, October 26, 2012 7:15 AM