none
Problems marshalling an array of structures in a DLLImport and getting NullReferenceException RRS feed

  • Question

  •  

    I'm trying to write up a function that is accessing a specialized card that has four serial ports. Sending data on it seems fine, but receiving the data is the problem. This is what I have to deal with on the C native code of the DLL:

     

    Code Snippet

    prototype.h (in native C)

     

    typedef struct

    {

    unsigned int inchar;

    unsigned int err;

    unsigned int timetag;

    }

    t_INDATA;

     

    int __declspec(dllexport ReceiveString(int chanhandle, t_INDATA *inbuf, int dwCount);

     

     

    What I am trying to do is use this in the unsafe format since I'm dealing with an array of structs by using pointers.

     

    This is what I have written:

    Code Snippet

    (C# code)

     

    class myDllInfterface

    {

    [StructLayout(LayoutKind.Sequential)]

    public struct t_INDATA

    {

    public IntPtr inchar;

    public uint err;

    public uint timetag;

    }

     

    // NOTE: channel_count will be defined by another function that will assign

    // how many bytes are being received in the FIFO buffer. It will NOT

    // be set to null

    public int channel_count;

     

    [DllImport("excserms.dll")]

    public static unsafe extern int ReceiveString(int chanhandle, t_INDATA** inbuf, int dwCount);

     

    //.... more code here for setting events on when the serial port receives data, and then assigning the channel_count variable. ...//

     

    public unsafe string receiveSerial(int ch)

    {

     

    StringBuilder sb = new StringBuilder(50);

    int test, i;

    t_INDATA* inbuf;

     

    if (channel_count == 0)

    return "";

     

    try

    {

    ReceiveString(ch, &inbuf, channel_count);

    t_INDATA* inCurrent = inbuf;

     

    for (i = 0; i < channel_count; i++, inCurrent++)

    {

     

    Marshal.ReadInt32(inCurrent->inchar);

    sb.Append((char)(inCurrent->inchar));

    Marshal.FreeCoTaskMem(inCurrent->inchar);

    }

    Marshal.FreeCoTaskMem((IntPtr)inbuf);

    }

    catch (Exception e)

    {

    MessageBox.Show(e.ToString());

    throw;

    }

    return sb.ToString();

    }

    }

     

     

     

    It's at Marshal.ReadInt32(inCurrent->inchar) that I am getting a NullReferenceException thrown and for the life of me I cannot figure out why it is doing that. I was using the "OutArrayOfStructs Sample" from the MSDN .NET Framework Developer's Guide to give me the basic outline on trying to deal with getting the data off the card. I'm basically modelled this function from the "UsingUnsafe()" example.

     

    the sample can be found here: http://msdn.microsoft.com/en-us/library/2k1k68kw(VS.85).aspx

     

    When I have another computer hooked up via by sending text by RS232 with this string "hello world\n", it accurately sets the channel_count to 12, yet when I debug it, the variable "i" in the for-loop is at zero when the exception is thrown. Besides, as you can see in the code, even if the channel_count is set to zero, it should return back a null string which does work.

     

    Anyone have any ideas on what's wrong with it?


    Steve

     

    Wednesday, April 30, 2008 10:10 AM

Answers

  • Okay, the only other logical explanation I can think of is that dwCount indicates the size of the inbuf[] array.  Which means the client must allocate and size the buffer rather than the native code creating the buffer for the client.  That's certainly preferrable and the way it would typically be done in 'C' as well, no guessing how the buffer needs to be released.  And no need for the "unsafe" keyword:

    [DllImport("excserms.dll")]
    public static extern int ReceiveString(int chanhandle, t_INDATA[] buf, int dwCount);

    public string receiveSerial(int ch) {  
      t_INDATA[] buf = new t_INDATA[channel_count];
      ReceiveString(ch, buf, channel_count);
      //...
    }
    Wednesday, April 30, 2008 4:06 PM
    Moderator

All replies

  • Your declaration of the dwCount argument of ReceiveString can't be right.  It has to at least be an "out" argument to allow the function to return the count.  And should be an int* at the unmanaged side.  Post the 'C' declaration of the function if you need more help.
    Wednesday, April 30, 2008 1:04 PM
    Moderator
  •  

    Hi Hans,

     

    It is right according to the manufacturer's source code from Excalibur (www.mil-1553.com). If you looked at the beginning of my post, you see what the declaration of the function looked like which is this line:

     

    int __declspec(dllexport ReceiveString(int chanhandle, t_INDATA *inbuf, int dwCount);

     

    It doesn't pass the dwCount as a reference but instead as a value, and therefore the int* is not needed. The only thing that is being passed by reference is the struct which is this in native C:

    typedef struct

    {

    unsigned int inchar;

    unsigned int err;

    unsigned int timetag;

     

    } t_INDATA;

     

    hey, this is their code, not mine.  

     

    All I'm trying to do is interop with the P/Invoke

     

    Steve

     

     

    Wednesday, April 30, 2008 1:22 PM
  • Okay, the only other logical explanation I can think of is that dwCount indicates the size of the inbuf[] array.  Which means the client must allocate and size the buffer rather than the native code creating the buffer for the client.  That's certainly preferrable and the way it would typically be done in 'C' as well, no guessing how the buffer needs to be released.  And no need for the "unsafe" keyword:

    [DllImport("excserms.dll")]
    public static extern int ReceiveString(int chanhandle, t_INDATA[] buf, int dwCount);

    public string receiveSerial(int ch) {  
      t_INDATA[] buf = new t_INDATA[channel_count];
      ReceiveString(ch, buf, channel_count);
      //...
    }
    Wednesday, April 30, 2008 4:06 PM
    Moderator
  • It did help in a way, but something strange on it is going on with it now. Instead of getting some kind of values, all I'm getting is zeros! I need to play with it for a while and see what else I can do, but at least on what you gave me is working. I was hoping that the unsafe code would work to prove a point that it can be done with the use of pointers in C#.

     

    As for the other part, I did look deeper in the DLL raw code that Excalibur put out on their website. Apparently they are NOT using any other means of memory allocation and it's solely based on the use of pointers. According to their documentation, dwCount is used as a mean to know when to stop going through memory, otherwise it will wait for more data to come in before returning back to the calling program once the dwCount has been met. That's a bit of a dangerous game they are playing since that can halt up the system and maybe cause some racing if it's in a thread, but then again, this was written on a low-level platform to optimize the code for their device driver and it's up to the end-user to take due care. 

     

    Steve

     

     

    Thursday, May 1, 2008 7:56 AM