dabide
dabide

Reputation: 996

System.AccessViolationException when passing struct to unmanaged code

I'm trying to use an unmanaged API from C#, and banging my head against the wall. (I'm rather a beginner when it comes to PInvoke.)

The relevant parts of the header file look like this:

#define CTAPICALL       __stdcall
#ifdef __cplusplus
    extern "C" {
#endif

extern  BOOL    CTAPICALL   ctTagReadEx(HANDLE,LPCSTR,LPSTR,DWORD,CT_TAGVALUE_ITEMS*);      /* read extended data from tag          */

#ifdef __cplusplus
}
#endif

CT_TAGVALUE_ITEMS looks like this:

typedef struct
{
    DWORD                   dwLength;                           /* size, in bytes, of this structure    */
    unsigned __int64        nTimestamp;                         /*  timestamp                           */
    unsigned __int64        nValueTimestamp;                    /*  value timestamp                     */
    unsigned __int64        nQualityTimestamp;                  /*  quality timestamp                   */
    BYTE                    bQualityGeneral;                    /*  quality general                     */
    BYTE                    bQualitySubstatus;                  /*  quality substatus                   */
    BYTE                    bQualityLimit;                      /*  quality limit                       */
    BYTE                    bQualityExtendedSubstatus;          /*  quality extended substatus          */
    UINT                    nQualityDatasourceErrorCode;        /*  quality datasource error            */
    BOOLEAN                 bOverride;                          /*  quality override flag               */
    BOOLEAN                 bControlMode;                       /*  quality control mode flag           */
}   CT_TAGVALUE_ITEMS;

My C# method declaration:

    [DllImport("ctapi.dll", SetLastError = true)]
    public static extern bool ctTagReadEx(
        IntPtr hCTAPI,
        [MarshalAs(UnmanagedType.LPStr)] string tag,
        [MarshalAs(UnmanagedType.LPStr)] System.Text.StringBuilder value,
        int length,
        CtTagValueItems tagValueItems);

The C# struct:

[StructLayout(LayoutKind.Sequential)]
public struct CtTagValueItems
{
    public int dwLength;
    public ulong nTimestamp;
    public ulong nValueTimestamp;
    public ulong nQualityTimestamp;
    public byte bQualityGeneral
    public byte bQualitySubstatus;
    public byte bQualityLimit;
    public byte bQualityExtendedSubstatus;
    public uint nQualityDatasourceErrorCode;
    public uint bOverride;
    public uint bControlMode;
}

When I call it like this (from a test assembly built as x86), I get a System.AccessViolationException : Attempted to read or write protected memory:

StringBuilder valueBuilder = new StringBuilder(300);
CtTagValueItems tagValueItems = new CtTagValueItems {dwLength = Marshal.SizeOf(typeof (CtTagValueItems))};
bool ok = CTAPI.ctTagReadEx(new IntPtr(handle), "TIC_Hold_PV", valueBuilder, valueBuilder.Capacity, tagValueItems);

I've been trying all kinds of things, like using LayoutKind.Explicit and/or CallingConvention = CallingConvention.Cdecl, but to no avail.

Can anybody help?

Upvotes: 3

Views: 2045

Answers (3)

usr
usr

Reputation: 171178

  1. Why did you map UINT as ushort. Doesn't it have 4 bytes?
  2. The native BOOLEAN type maps to 4 bytes, AFAIK.
  3. You need to pass CtTagValueItems by ref (as class or ref).
  4. Check the calling convention.
  5. Check whats written in the comments.

Upvotes: 2

qxn
qxn

Reputation: 17574

Where does the handle variable in your C# call come from?

I much prefer to use IntPtr in my DllImport method definitions. It seems easier to manage and marshal that way.

I've changed quite a bit of the struct definition because I don't have the same defines as you. I also don't have much of a body in my ctTagReadEx function (I'll try to flesh it out to make sure the parameters passed in match the parameters received). But this works for me.

Update: It looks like all the parameters and struct values are passed correctly.

C

typedef struct
{
    int                     dwLength;                           /* size, in bytes, of this structure    */
    unsigned long           nTimestamp;                         /*  timestamp                           */
    unsigned long           nValueTimestamp;                    /*  value timestamp                     */
    unsigned long           nQualityTimestamp;                  /*  quality timestamp                   */
    int                     bQualityGeneral;                    /*  quality general                     */
    int                     bQualitySubstatus;                  /*  quality substatus                   */
    int                     bQualityLimit;                      /*  quality limit                       */
    int                     bQualityExtendedSubstatus;          /*  quality extended substatus          */
    unsigned int            nQualityDatasourceErrorCode;        /*  quality datasource error            */
    int                     bOverride;                          /*  quality override flag               */
    int                     bControlMode;                       /*  quality control mode flag           */
}   CT_TAGVALUE_ITEMS;


CTAPICALL int ctTagReadEx(void *, const char *, char *, int, CT_TAGVALUE_ITEMS *);

int ctTagReadEx(void * hCTAPI, const char * tag, char * value, int length, CT_TAGVALUE_ITEMS *tagValueItems) {
    return 15;
}

C#

[StructLayout(LayoutKind.Sequential)]
public struct CtTagValueItems {
    public int dwLength;
    public ulong nTimestamp;
    public ulong nValueTimestamp;
    public ulong nQualityTimestamp;
    public int bQualityGeneral;
    public int bQualitySubstatus;
    public int bQualityLimit;
    public int bQualityExtendedSubstatus;
    public uint nQualityDatasourceErrorCode;
    public int bOverride;
    public int bControlMode;
}

[DllImport("ctapi.dll")]
static extern int ctTagReadEx(IntPtr hCTAPI, IntPtr tag, IntPtr value, int length, IntPtr tagValueItems);

public void TestMe() {

    var tagValueItems = new CtTagValueItems();
    var tagValueItemsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CtTagValueItems)));
    Marshal.StructureToPtr(tagValueItems, tagValueItemsPtr, true);

    var tag = "tag";
    var tagPtr = Marshal.StringToHGlobalAnsi(tag);

    var value = "value";
    var valuePtr = Marshal.StringToHGlobalAnsi(value);

    int length = value.Length;

    var result = ctTagReadEx(IntPtr.Zero, tagPtr, valuePtr, length, tagValueItemsPtr);
    if (result != 15) throw new Exception();

    Marshal.FreeHGlobal(tagValueItemsPtr);
    Marshal.FreeHGlobal(tagPtr);
    Marshal.FreeHGlobal(valuePtr);
}

Upvotes: 1

gabba
gabba

Reputation: 2880

The problem might be in alignment. try somting like

StructLayout(LayoutKind.Sequential, Pack = 1)

Upvotes: 1

Related Questions