IndexOutOfRangeException - Cannot see call stack using PInvoke - c#

I'm developing a C# app that takes data from a SerialPort, then it uses a C++ project (that I cannot change) to compute the read data.
The C++ project is using some native C code, that will call C# functions when the data are computed.
This is some examples of the called C# code which calls the C++ function using PInvoke:
[DllImport("MyLib.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void PacketHandler_HandlePacket(IntPtr packetHandler, IntPtr packet, int packetType);
[SuppressUnmanagedCodeSecurity]
[DllImport("MyLib.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern float DeviceManager_AddSampleToBattery(IntPtr self, float sample, double sampleRate);
[SuppressUnmanagedCodeSecurity]
[DllImport("MyLib.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern double DeviceManager_GetTimestamp(IntPtr self, int packetType);
[DllImport("MyLib.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern void PacketHandler_AddCallbackBattery(IntPtr self, Delegate #delegate);
Then, the C++ code:
extern "C"
{
__declspec(dllexport) void _cdecl PacketHandler_HandlePacket(int * packetHandler, char * packet, int packetType){
PacketHandler_handlePacket((PacketHandlerStruct *)packetHandler, packet, (PacketHandlerPacketType)packetType);}
}
This C function is calling a C# function that I've set like this:
__declspec(dllexport) void _cdecl PacketHandler_AddCallbackBattery(int * packetHandler, void(*f)(void * obj, unsigned short int sample)){
PacketHandlerStruct * myPointer = ((PacketHandlerStruct *)packetHandler);
myPointer->delegate.addSampleToBattery = f;
}
Finally, the C code will call the "addSampleToBattery" function which is this C# callback (in where the first two are PInvoke calls like the first one I've posted)
private static float CallbackBattery(IntPtr self, ushort sample)
{
var value = DeviceManager_AddSampleToBattery(DeviceManager, sample, Const.BaseBvpSampleRate);
var timestamp = DeviceManager_GetTimestamp(DeviceManager, (int)PacketHandlerPacketType.Battery);
SocketManager.OnNewBatteryArrived(value, timestamp);
return value;
}
Other details:
The C# delegates are declared as follows:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate float DelegateBattery(IntPtr self, short sample);
private static readonly DelegateBattery DelegateCallbackBattery = CallbackBattery;
And setted like this:
var intptrDelegate = Marshal.GetFunctionPointerForDelegate(DelegateCallbackBattery);
var a = Marshal.GetDelegateForFunctionPointer(intptrDelegate, typeof(DelegateBattery));
PacketHandler_AddCallbackBattery(packetHandler, a);
So, everything seems to works, but a lot of times a IndexOutOfRangeException occurs. The main issue is that, even in Debug Mode with all the symbols loaded, I can't see the line that is throwing the exception because only the Disassembly View is available, and of course I can't get meaningful info from it.
Unhandled Exception: 'MyProgram.exe' (Win32):
The program '[3000] MyProgram.exe' has exited with code 0 (0x0).
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Threading.ThreadPoolWorkQueue.Dequeue(ThreadPoolWorkQueueThreadLocals tl, IThreadPoolWorkItem& callback, Boolean& missedSteal)
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
Thanks!

Semantics in correspondence with an IndexOutOfRangeException indicate that you've attempted to access memory too far from a base memory address, and thus you shouldn't be trying to deference that point in memory.
Think of an int array of 3 elements. If you try to index [3], that is *(base_address + (sizeof(int) * 3))* being dereferenced so that the next 4 bytes at that place in memory are provided for the value as an int. The only valid indexes are 0, 1, and 2, because those offsets are within the range of the 3 elements that we've allocated the space for.
We still need more code, as I don't see where any of this is relevant to the exception message.

Related

AccessViolationException when accessing function with char * argument in c++ dll with C#

Problem Trying to use a function in a c++ dll with the following prototype...
int connectDfuBootloader(char * usbIndex)
...from C#, using any of these P/Invoke signatures, causes an AccessViolationException:
IntPtr...
[DllImport(#"CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int connectDfuBootloader(IntPtr usbIndex);
public static int connectDfuBootloader(string usbIndex)
{
IntPtr value = Marshal.StringToHGlobalAuto(usbIndex);
return connectDfuBootloader(value);
}
String...
[DllImport(#"CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int connectDfuBootloader(string usbIndex);
StringBuilder...
[DllImport(#"CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int connectDfuBootloader(StringBuilder usbIndex);
Variations on the theme of MarshalAs, tried LPStr, LPTStr etc...
[DllImport(#"CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int connectDfuBootloader([MarshalAs(UnmanagedType.LPStr)]string usbIndex);
Variations on the theme of specifying the character set, ANSI, Unicode, etc
[DllImport(#"CubeProgrammer_API.dll", CallingConvention = CallingConvention.Cdecl, CharSet =CharSet.Ansi)]
public static extern int connectDfuBootloader(string usbIndex);
Can anyone offer any suggestions?
Other things I've tried
Various StackOverflow posts, such as
AccessViolationException when accessing unmanaged C++ DLL with C#
Also, I tried making my own small C++ dll for test purposes, which contained a function with the same signature as the problem one above:
int functionAlpha(char * a)
{
if (a[0] == 'a')
return 10;
else
return 20;
}
This function I was able to access from C# without issue, using any of the methods above, e.g.
[DllImport(#"test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int functionAlpha(string a);
Which makes me think there is something special about the 'int connectDfuBootloader(char * usbIndex)' function. But they have the same signature, why would one work and the other not? I do not have the source code for the dll, so can't look in there to see if there are any other differences.
Update - Tried C++/CLI wrapper as suggested by #Taekahn below, same thing!
So, I have this in a header in the C++/CLI wrapper project:
#include "include/CubeProgrammer_API.h"
using namespace System;
namespace CLI
{
public ref class CubeWrapper
{
public:
int wrappedConnectDfuBootloader(String^ a);
};
}
Then in a cpp file in the wrapper project, I have:
namespace CLI
{
static char* string_to_ncchar_array(String^ string)
{
char* str = (char*)(Marshal::StringToHGlobalAnsi(string)).ToPointer();
return str;
}
int CubeWrapper::wrappedConnectDfuBootloader(String^ a)
{
a = "USB0";
// Calls the function from the linked c++ dll I'm trying to wrap
return connectDfuBootloader(string_to_ncchar_array(a));
}
}
And it gives the same result, System.AccessViolationException! What could be going on here?
Also tried using the dll from c++
Tried the same sequence of operations with the dll from a c++ application, and it worked fine.
Turns out that dxiv was on the right track; looked through some of the c++ example code supplied with the API and saw that several callbacks had to be setup first. Once I sorted those, problem fixed.

Second value not getting passed to C++ function during pinvoke

I have a c++ function which I am calling from c# using pinInvoke. Following is my cpp method-
int test(DWORD verb,DWORD verb2 )
{
return verb2 *100;
}
My function is exposed as -
extern "C" {
__declspec(dllexport) int test(DWORD verb, DWORD verb2);
}
Following is my c# code where I am calling the above method:
public class API
{
[DllImport("mydll.dll", EntryPoint = "test", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U4)]
public static extern uint test(
[MarshalAs(UnmanagedType.U8)]ulong verb,
[MarshalAs(UnmanagedType.U8)]ulong verb2);
static void Main(string[] args)
{
uint x = DPAPI.test(26,10);
Console.Write("result is-"+x);
}
}
Here the second value is getting passed as 0,so wrong result is coming. Am I doing something wrong while passing the value?
What I have tried:
I am relatively new to Pinvoke. So I tried debugging to see whether the value is not getting passed to c++ code or whether the c++ code is not returning proper values.I found that the value getting passed itself was wrong.
DWORD is a 32 but unsigned integer. You are mapping that to a 64 bit type. That is the problem. Your p/invoke should be:
[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int test(uint verb, uint verb2);
Note that I removed the needless EntryPoint and the erroneous SetLastError from the DllImport attribute.
I also wonder why you have selected DWORD. Unless you are passing those values onto Win32 functions it would likely make more sense to use intrinsic types. Why not use int or unsigned int in your C++ code?

Buffer overrun detected

I have an one question when I use C# DllImport C++ dll, I use the visual studio 2010 & checked the "Enable unmanaged code debugging", when it's running, always show the message "Buffer overrun detected! ... A buffer overrun has been detected which has corrupted the program's internal state. The program cannot safely continue execution and must now be terminated."
This dll is Third-party vendors to provide, and they says it's no error to run this dll, how can i fixed it?
My M++ dll function is,
int avc_to_avi_convert(char* chPath_avc, char* chPath_avi, int pPrivate, PROGRESS_CALLBACK progress_callback);
void avc_to_avi_close(int* pFd_avi);
typedef void (*PROGRESS_CALLBACK)(int iPercent, int pPrivate);
And i am use it in my C# dllimport like this :
public delegate void PROGRESS_CALLBACK(int _iPercent, int _pPrivate);
[DllImportAttribute(#"..\xxx.dll", EntryPoint = "avc_to_avi_convert", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern unsafe int avc_to_avi_convert([MarshalAs(UnmanagedType.LPStr)] StringBuilder _inputAVC, [MarshalAs(UnmanagedType.LPStr)] StringBuilder _ouputAVI, int pPrivate, PROGRESS_CALLBACK _pg);
[DllImportAttribute(#"..\xxx.dll", EntryPoint = "avc_to_avi_close", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern unsafe void avc_to_avi_close(int pFd_avi);
private void button1_Click(object sender, EventArgs e)
{
int i = 0;
StringBuilder inputFile = new StringBuilder(Application.StartupPath + #"\avc\abc.avc");//(#"C:\avc\abc.avc");
StringBuilder outputFile = new StringBuilder(Application.StartupPath + #"\avi\abc.avi");//(#"C:\avi\abc.avi");
if (avc_to_avi_convert(
inputFile,
outputFile,
1,
progress_func) > 0) {
}
}
public void progress_func(int iProgress, int pPrivate)
{
if (iProgress == 100)
{
//success
}
else if (iProgress == -1)
{
//convert error
}
else if (iProgress == -2)
{
//Not enough disk space
}
else
{
//update progress
}
}
I changed my code to,
[DllImportAttribute(#"..\xxx.dll", EntryPoint = "avc_to_avi_convert", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public static extern unsafe int avc_to_avi_convert([MarshalAs(UnmanagedType.BStr)] String _inputAVC, [MarshalAs(UnmanagedType.BStr)] String _ouputAVI, int pPrivate, PROGRESS_CALLBACK _pg);
[DllImportAttribute(#"..\xxx.dll", EntryPoint = "avc_to_avi_close", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public static extern void avc_to_avi_close(ref int avi);
And ran it, and I still get the same error.
1.) Are you sure about the calling convention? Try CallingConvention.StdCall. Read this:
http://blogs.msdn.com/b/adam_nathan/archive/2003/05/21/56690.aspx
2.) Try a different values for the CharSet attribute and use String instead of StringBuilder for the path arguments. This is a good reference:
http://msdn.microsoft.com/en-us/library/s9ts558h.aspx
3.) Also, the avc_to_avi_close method should look like this:
[DllImportAttribute("xxx.dll", EntryPoint="avc_to_avi_close")]
public static extern void avc_to_avi_close(ref int avi);
4.) Other things you my want to try:
Use unusual combinations of CharSet and MarshalAs. For example you know from the link above that Unicode should be used with LPWStr and Ansi with LPStr but you can of course try other combinations.
Your extern methods need not to be marked unsafe.
If you have exhausted all other options, why don't you try to write a C++ program that is analogous to your C# code:
If it crashes you proved that the error is in the library.
If it doesn't crash you know the error is in your use of PInovke, and you can go through all the 12+ combinations from my answer one by one.
You are passing a delegate to p/invoke and then letting the garbage collector free it. This causes the crash, because when the delegate object is finalized, the trampoline set up to allow calls from native code is deallocated. Then the native code invokes a dangling function pointer.
The garbage collector is totally unaware of any objects being used from inside native code. You MUST keep a reference to the delegate alive for the duration of the call. Try adding a delegate variable (don't use implicit conversion which creates a temporary) and then use GCHandle to keep the delegate alive for as long as the native code is using it.

Return contents of a std::wstring from C++ into C#

I have an unmanaged C++ DLL that I have wrapped with a simple C interface so I can call PInvoke on it from C#. Here is an example method in the C wrapper:
const wchar_t* getMyString()
{
// Assume that someWideString is a std::wstring that will remain
// in memory for the life of the incoming calls.
return someWideString.c_str();
}
Here is my C# DLLImport setup.
[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl )]
private static extern string GetMyString();
However the string is not correctly marshalled, often screwing up the first character or sometimes way off showing a bunch of chinese characters instead. I have logged output from the implementation on the C side to confirm that the std::wstring is correctly formed.
I have also tried changing the DLLImport to return an IntPtr and convert with a wrapped method using Marshal.PtrToStringUni and it has the same result.
[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl )]
private static extern IntPtr GetMyString();
public string GetMyStringMarshal()
{
return Marshal.PtrToStringUni( GetMyString() );
}
Any ideas?
Update with Answer
So as mentioned below, this is not really an issue with my bindings but the lifetime of my wchar_t*. My written assumption was wrong, someWideString was in fact being copied during my calls to the rest of the application. Therefore it existed only on the stack and was being let go before my C# code could finish marshalling it.
The correct solution is to either pass a pointer in to my method as described by shf301, or make sure my wchar_t* reference does not get moved / reallocated / destroyed before my C# interface has time to copy it.
Returning the std::wstring down to my C layer as a "const &std::wstring" means my call to c_str() will return a reference that won't be immediately dealloc'd outside the scope of my C method.
The calling C# code then needs to use Marshal.PtrToStringUni() to copy data from the reference into a managed string.
You are going to have to rewrite your getMyString function for the reasons mentioned in Hans Passant's answer.
You need to have the C# code pass a buffer in to your C++ code. That way the your code (ok, the CLR Marshaller) controls the lifetime of the buffer and you don't get into any undefined behavior.
Below is an implementation:
C++
void getMyString(wchar_t *str, int len)
{
wcscpy_s(str, len, someWideString.c_str());
}
C#
[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode )]
private static extern void GetMyString(StringBuffer str, int len);
public string GetMyStringMarshal()
{
StringBuffer buffer = new StringBuffer(255);
GetMyString(buffer, buffer.Capacity);
return buffer.ToString();
}
You need to specify MarshalAs attribute for the return value:
[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
[return : MarshalAs(UnmanagedType.LPWStr)]
private static extern string GetMyString();
Make sure the function is indeed cdecl and that the wstring object is not destroyed when the function returns.

How to import void * C API into C#?

Given this C API declaration how would it be imported to C#?
int _stdcall z4ctyget(CITY_REC *, void *);
I've been able to get this far:
[DllImport(#"zip4_w32.dll",
CallingConvention = CallingConvention.StdCall,
EntryPoint = "z4ctygetSTD",
ExactSpelling = false)]
private extern static int z4ctygetSTD(ref CITY_REC args, void * ptr);
Naturally in C# the "void *" doesn't compile.
Some Googling indicates that it should be translated as "object." Which seems like it should work. But others indicate that "Void * is called a function pointer in C/C++ terms which in C# terms is a delegate". That doesn't make a whole lot of sense here as what would it delegate to? Some similar calls for other APIs found through Googling use other functions in the respective API. But in this API no other call would make sense.
The documentation for the call shows an example:
z4ctyget(&city, “00000”);
Which seems to show that even a static value could be passed.
It will compile with object in place of the void *. I don't know whether this is right and I haven't had an opportunity to test it (licensing issue).
For the void* parameter you can just use an IntPtr
[DllImport(#"zip4_w32.dll",
CallingConvention = CallingConvention.StdCall,
EntryPoint = "z4ctygetSTD",
ExactSpelling = false)]
private extern static int z4ctygetSTD(ref CITY_REC args, IntPtr ptr);
You can also use void* if you mark your class as unsafe.
It really depends on what the API is looking for in that parameter.
You can add IntPtr or Object* to get past compiler, but you will still need to pass it the correct data when you call it.
As far as I can tell the C declaration of z4ctyget is:
int z4ctyget(CITY_REC *cityrec, char *zipcode);
The second parameter is a 5 character ANSI string representing the zip code at which you want to start your search or "00000" to start at the beginning of the file. So your declaration should be:
[DllImport(#"zip4_w32.dll", CharSet = CharSet.Ansi)]
private extern static int z4ctygetSTD(ref CITY_REC args, string zipcode);

Categories