Search code examples
c#memorystructpinvokemarshalling

Odd Errors from PInvoke struct/function


Im currently writing a C# wrapper for a C++ API, but a specific struct and a function that relies on this struct have been giving very strange errors when debugging.

C++ Struct:

typedef struct  
{  
    unsigned __int handle;  
    char name[80];  
    unsigned int unique_ID;  
} DeviceInfo;

Followed by this function:

int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);

The struct and function is imported as such:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]  
public struct DeviceInfo  
{
    public UInt32 handle;  
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)]  
    public String name;  
    public UInt32 unique_ID;  
}

[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]  
public static extern int get_device_info(ref DeviceInfo di, int length_of_di_array, ref numValidDevices);

The intended use of this struct and function is just to obtain some device info from the board Im accessing. Currently I do not have access to the function body in C++, so I can only assume it's working 100% (works fine in C++).

The issue is that when I use the function to run through an array of structs, it outputs the data I'm looking for, but also will begin to fail at runtime, giving me various error windows.

C# code:

static void Main()  
{    
    int numValidDevices = 0; //initialize variable  
    DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices  

    for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices  
    {  
        rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data  
        Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID);  
    }  
    Console.ReadLine(); //stops console from closing prematurely  
    API_close();  //custom close function from the C++ API
}

Errors while debugging (information is still shown): "An unhandled exception of type 'System.Threading.ThreadStateException' occurred in System.dll

Additional information: Thread has not been started."

"An unhandled exception of type 'System.ExecutionEngineException' occurred in mscorlib.dll"

Error while debugging (information is not shown, program fails to execute): "An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

When closing the console window: "The instruction at '0x7c9113c0' referenced memory at '0x00000000'. The memory could not be 'written'." (sometimes says 'read' instead of 'written').

I've done a lot of research on PInvoke and came across the Microsoft InteropAssistant application, various stack overflow articles such as this one, and this post seems even closer to what Im doing, but I'm still digging into how to use the Marshal.CoTaskMemAlloc/Free, and see if it even will do anyhting...

Thus far what I have for my struct and function are correct, I've tried changing the struct to use an IntPtr but that does not return a di.name value and the di.unique_ID becomes jibberish (oddly enough the di.handle stays valid)

C# code:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]  
public struct DeviceInfo  
{
    public UInt32 handle;  
    IntPtr p_name;
    public String name { get { return Marshal.PtrToStringAnsi(p_name); } }
    public UInt32 unique_ID;  
}

Intended output:

Handle: 3126770193  
Name: DEVICE_A  
Unique ID: 12345678  

IntPtr output:

Handle: 3126770193  
Name:  
Unique ID: 1145128264  

Oddly enough, using an IntPtr results in none of the errors above, and runs fine. This leads me to believe the issue lies with marshaling over the C++ char to a string, but I'm not sure if the issue lies with the marshaling, memory management (there is none?), or something I'm not catching entirely.

Any and all feedback would be really appreciated, I've been stumped on this for a number of weeks now...


Solution

  • The exceptions you get indicate that the unmanaged code you are pinvoking is destroying the garbage collected heap. It isn't crystal why, but you don't give the pinvoke marshaller much of a chance to do the Right Thing. It cannot properly pin the array. Start by declaring the function properly, it takes an array so declare one:

    [DllImportAttribute("MyC++API.dll", CallingConvention = CallingConvention.StdCall)]  
    public static extern int get_device_info(
        DeviceInfo[] di, 
        int length_of_di_array, 
        out int p_numValidDevices
    );
    

    Your first declaration of DeviceInfo is correct, the 2nd isn't since the string isn't a pointer.