StackTrace: at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
Source: mscorlib
I see in the target source something about reflection. So i'll post the very small amount of code that uses reflection.
I normally am able to find NullReferenceExceptions easily. This one though does not appear to be my code, and I can not figure it out to save my life. I have this code bot in VB.net, and C#.Net Both have the same problem. I think that my problem is in how my pointer works, but I could be wrong.
The code is straight forward. I have a barcode scanner attached via usb that is in COM mode. I copied some of my code from another program that uses this same barcode that works perfectly. But to sum up what it does is that it opens, then, by use of reflection, gets the Pointer to the open com port, sets up a DataEventListener. When I scan something i wait 100ms, then spit out the data in string form. Simple as could be. The difference between my code and the other one that works flawlesly is that I have to get the pointer. To turn on my scanner i need a pointer to the comport using IntPtr. C# I use IntPtr, in VB i use SafeFileHandle. Both have the same error. The only time the error occurs is when a data event is fired. This is why I think it has to be with the handle. What is strange is that the Handle is needed to turn on and off the imager so I know i have a valid handle. (just don't know if it is held on to) So does anyone have any help or resources as to why I am having this error?
First up.. VB.NET
Public Class ImagerOposDevice
Inherits AbstractOPOSDevice
Dim PortHandle As SafeFileHandle
Dim ImagerPort As SerialPort
Public exitCode = 1
Sub New(ByVal comName As String)
ImagerPort = New SerialPort(comName)
End Sub
Protected Overrides Function Open() As Boolean
ImagerPort.Open()
PortHandle = GetHandleFromSerialPort(ImagerPort)
If Not PortHandle.IsInvalid Then
AddHandler ImagerPort.DataReceived, AddressOf DataReceivedHandler
End If
Return (Not PortHandle.IsInvalid)
End Function
Protected Overrides Sub Close()
Try
ImagerPort.Close()
ImagerPort.Dispose()
Catch ex As Exception
Console.WriteLine(ex.Message)
Console.ReadLine()
End Try
End Sub
Protected Overrides Function RunCommand(ByVal X As Integer) As Boolean
If X = 0 Then
Return TurnImagerOn()
ElseIf X = 1 Then
Return TurnImagerOff()
Else
Return False
End If
End Function
Private Shared Function GetHandleFromSerialPort(ByVal sp As SerialPort) As SafeFileHandle
Dim BaseStream As Object = sp.BaseStream
Dim BaseStreamType As Type = BaseStream.GetType
Return BaseStreamType.GetField("_handle", BindingFlags.NonPublic Or BindingFlags.Instance).GetValue(BaseStream)
End Function
Private Sub DataReceivedHandler(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
Try
'Dim sp As SerialPort = CType(sender, SerialPort)
System.Threading.Thread.Sleep(100)
Dim len = ImagerPort.BytesToRead
Dim buffer(len - 1) As Byte
ImagerPort.Read(buffer, 0, len)
Dim indata As String = System.Text.ASCIIEncoding.ASCII.GetString(buffer)
Console.WriteLine("Data Received:")
Console.Write(indata)
TurnImagerOff()
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
exitCode = 0
End Sub
in my main form it is straight forward
imager = New ImagerOposDevice("COM7")
imager.OpenDevice()
imager.SendCommand(0)
For index = 1 To 100 Step 1
System.Threading.Thread.Sleep(100)
If imager.exitCode = 0 Then
Exit For
End If
Next
Console.WriteLine("Press Enter to Close")
Console.ReadLine()
CloseDevices()
Now for the C# code. It is a windows form, but the concept is almost identicle.
private SerialPort devicePort;
private IntPtr deviceHandle;
public Imager(string Port) : base()
{
devicePort = new SerialPort(Port);
}
protected override bool Open()
{
try
{
devicePort.Open();
deviceHandle = GetHandleFromSerialPort(devicePort);
Console.WriteLine("Device Handle:{0}", deviceHandle);
Console.WriteLine("Device Open:{0}", devicePort.IsOpen);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
return devicePort.IsOpen;
}
protected override bool Close()
{
devicePort.Close();
devicePort.Dispose();
return true;
}
protected override CommandReturnCodes RunCommand(int command)
{
switch (command)
{
case 0:
TurnImagerOn();
break;
case 1:
TurnImagerOff();
break;
case 2:
ReadLatch();
break;
case 3:
GetPartNumber();
break;
case 4:
GetSerialNumber();
break;
case 5:
GetProductString();
break;
default:
return CommandReturnCodes.FAIL;
}
return CommandReturnCodes.SUCCESS;
}
private static IntPtr GetHandleFromSerialPort(SerialPort sp)
{
Type t = typeof(SerialPort);
BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic;
FieldInfo fi = t.GetField("internalSerialStream", bf);
object ss = fi.GetValue(sp);
Type t2 = fi.FieldType;
FieldInfo fi2 = t2.GetField("_handle", bf);
SafeFileHandle _handle = (SafeFileHandle)fi2.GetValue(ss);
Type t3 = typeof(SafeFileHandle);
FieldInfo fi3 = t3.GetField("handle", bf);
IntPtr handle = (IntPtr)fi3.GetValue(_handle);
return handle;
}
public void TurnOnImagerEventListener()
{
devicePort.DataReceived += new SerialDataReceivedEventHandler(devicePort_DataReceived);
}
void devicePort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
System.Threading.Thread.Sleep(100);
SerialPort imager = sender as SerialPort;
byte[] buffer = new byte[imager.BytesToRead];
imager.Read(buffer, 0, imager.BytesToRead);
var scannedText = System.Text.Encoding.ASCII.GetString(buffer).Trim();
SendBarcodeEvent(scannedText);
TurnImagerOff();
Console.WriteLine(scannedText);
}
and to call it is same thing
im = new Imager(comportBox.Text);
im.OpenDevice();
im.TurnOnImagerEventListener();
im.BarcodeScanned += new DataAvailableHandler(DeviceInformationReceived);
Well it turns out that LightStriker helped me see past this fog that was blinding me yesterday. I still don't have the vb.net version working, but what it ended up being was that the IntPtr used in my C# program was not being retained when a seperate thread (IE the dataevent) called it. When I stepped through the data event portion it didn't fail until i called the TurnImagerOff() portion. It was a easy fix in that all i had to do was change both my serial port and my IntPtr into static Members. I tried that in the VB.net code...but evidently I'm not smart enough to figure that out. Oh well today is another day, and atleast 50% of my code works.
So for those of you who have a null reference error and can't find it any where. And the Stack Trace doesn't give you a line number, check anything that is running on another thread that is making a call to any members that are pointers. (Or reflected as a pointer). Check data events, Thread Pools, Background workers, Timers, delegates, etc. I hope that my follies will help someone else.
Related
So I am writing a c# program where the client recieves a character from ther server, and runs a background worker depending on which character it reiceves. The problem though is the button states not all code paths return a value:
private object SL_Click(object sender, EventArgs e)
{
try
{
TcpClient tcpclnt = new TcpClient();
Console.WriteLine("Connecting.....");
tcpclnt.Connect(RecieveIP.Text, 8001); // use the ipaddress as in the server program
MessageBox.Show("Connected");
Console.Write("Enter the string to be transmitted : ");
String str = Console.ReadLine();
Stream stm = tcpclnt.GetStream();
ASCIIEncoding asen = new ASCIIEncoding();
byte[] ba = asen.GetBytes(str);
MessageBox.Show("Transmitting.....");
stm.Write(ba, 0, ba.Length);
byte[] bb = new byte[100];
int k = stm.Read(bb, 0, 100);
for (int i = 0; i < k; i++)
Console.Write(Convert.ToChar(bb[i]));
string toattk = k.ToString();
if (toattk == "g")
{
googlesearch.RunWorkerAsync();
}
else if (toattk == "y")
{
ysearch.RunWorkerAsync();
}
else if (toattk == "a")
{
aolsearch.RunWorkerAsync();
}
else if (toattk == "yo")
{
youtubesearch.RunWorkerAsync();
}
else if (toattk == "s")
{
ssearch.RunWorkerAsync();
}
tcpclnt.Close();
}
catch (Exception)
{
MessageBox.Show("Could not find server computer.");
}
}
It says SLCLick - not all code paths return a value. This seems odd as there is an action for every if then statement? Thanks very much.
the method need to return object
private object SL_Click(object sender, EventArgs e){
.....
return whatObject
}
or change return type to void
private void SL_Click(object sender, EventArgs e){
.....
}
This seems odd as there is an action for every if then statement?
This isn't what the compiler is complaining about. Indeed it's possible to write if statements that do nothing:
if(someVariable == someValue){
} else {
}
That's perfectly legal C#; "not all code paths return a value" is not about your method here calling off into some other method at various opportunities
The message arises because you made a promise here:
private object SL_Click
^^^^^^
"I promise to return something"
and you haven't fulfilled it; a method that declares some type of object will be returned must, somewhere within its body code, have return ...something here....
The "something here" must be of a Type that is (convertible to) the Type you promised in the method header
You cannot:
public string MyMethod(){. //I promise to return a string
return 1; //not a string, not implicitly convertible to a string
}
Further, there must be no detectable route through the code, through all the ifs and elses etc, that means C# can get to the end of the method without hitting a return ...something here...
public string MyMethod(){ //I promise to return a string
if(DateTime.Today.DayOfWeek == DayOfWeek.Monday){
return "Hello";
}
}
This also gives "not all paths return a value" - you have to completely and explicitly tell C# what to do in every case; you can't just leave it to decide itself what to return if it's Tuesday
If you don't plan to return anything, declare the method void:
public void MyMethod(){
if(DateTime.Today.DayOfWeek == DayOfWeek.Monday){
return; //I don't work Mondays
}
}
You can still quit early in a void method, by doing a return without a value after it. Also you can let C# reach the end without encountering any return - C# knows what to do if it reaches the end of a void method without having encountered a return, but for anything non-void you have to be explicit about what value to return on every possible path through the code
In your case I'd say it should be a void method. Try also to avoid returning object - use a more specific type if you can.
We'll get to Task-returning methods some other time :)
Small additional, because it's bound to come up for you soon- how do you return more than one value? In C# you can only ever return one thing. If you want to return multiple values, you're free to make something that holds multiple values, make one of those things and put your multiple values inside it.
That one thing could be an array, a dedicated class, some built in convenient like a a tuple or anonymous type.. but all in remember you can only return one thing; if you have lots of values to return, put them inside one thing and return that.
It's like working in a bar and needing to carry 10 different drinks on one hand; you put the drinks on a tray and carry the tray.
I think this is an event handler, and if you look closely your method demands an object to be returned private object SL_Click(object sender, EventArgs e).
If you can, then change the return type of the method from object to void
private object SL_Click(object sender, EventArgs e)
In case you can't change the method signature, then you can just simple return any object (provided you don't need to return anything) at the end of the try and catch or add a finally block and return a dummy object.
finally
{
return "Event Handled";
}
I have been able to create an application that (ab)uses TAPI3Lib and understands incoming calls, puts them on hold, rings an internal number, connects the line that is on hold with the target number.
Below is the code getting the caller number
private void tapi_ITTAPIEventNotification_Event_Event(TAPI3Lib.TAPI_EVENT TapiEvent, object pEvent)
{
try
{
switch (TapiEvent)
{
case TAPI3Lib.TAPI_EVENT.TE_CALLNOTIFICATION:
TAPI3Lib.ITCallNotificationEvent cln = pEvent as TAPI3Lib.ITCallNotificationEvent;
if (cln.Call.CallState == TAPI3Lib.CALL_STATE.CS_OFFERING)
{
string c = cln.Call.get_CallInfoString(TAPI3Lib.CALLINFO_STRING.CIS_CALLERIDNUMBER);
string target = cln.Call.Address.DialableAddress;
.
.
.
Here i run a MSSQL query to a database to identify who is the owner of the number. and i display a form on screen with the potential user that is calling. A button on the form allows you to ring internal numbers (this action automatically adds the current line on hold.
IEnumCall ec = ia[line].EnumerateCalls();
uint arg = 0;
ITCallInfo ici;
try
{
ec.Next(1, out ici, ref arg);
ITBasicCallControl bc = (ITBasicCallControl)ici;
bc.Hold(true);
}
catch (Exception exp)
{
System.Windows.Forms.MessageBox.Show("May not have any call to put on hold!\n\n" + exp.ToString(), "TAPI3");
}
IEnumCall ec1 = ia[line].EnumerateCalls();
uint arg1 = 0;
ITCallInfo ici1;
try
{
ec1.Next(1, out ici1, ref arg1);
ITBasicCallControl bc1 = ia[line].CreateCall("107",TapiConstants.LINEADDRESSTYPE_IPADDRESS, TapiConstants.TAPIMEDIATYPE_AUDIO);
bc1.Connect(false);
}
catch (Exception exp)
{
System.Windows.Forms.MessageBox.Show("Target looks unreachable!\n\n" + exp.ToString(), "TAPI3");
}
This part connects the current active line with the line on hold;
ITBasicCallControl callOnHold = null;
ITBasicCallControl callConnected = null;
IEnumCall enumCall = ia[line].EnumerateCalls();
ITCallInfo callInfo;
uint dummy = 0;
while (true)
{
enumCall.Next(1, out callInfo, ref dummy);
if (callInfo == null)
return;
if (callInfo.CallState == CALL_STATE.CS_HOLD)
callOnHold = (ITBasicCallControl)callInfo;
else if (callInfo.CallState == CALL_STATE.CS_CONNECTED)
callConnected = (ITBasicCallControl)callInfo;
if (callOnHold != null && callConnected != null)
break;
}
callOnHold.Transfer(callConnected, true);
callConnected.Finish(FINISH_MODE.FM_ASTRANSFER);
The initiation is as follows;
ITAddress[] ia = new TAPI3Lib.ITAddress[10];
...
try
{
tobj = new TAPIClass();
tobj.Initialize();
IEnumAddress ea = tobj.EnumerateAddresses();
ITAddress ln;
uint arg3 = 0;
lines = 0;
cn = new callnotification();
tobj.ITTAPIEventNotification_Event_Event += new TAPI3Lib.ITTAPIEventNotification_EventEventHandler(cn.Event);
tobj.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION |
TAPI_EVENT.TE_DIGITEVENT |
TAPI_EVENT.TE_PHONEEVENT |
TAPI_EVENT.TE_CALLSTATE |
TAPI_EVENT.TE_GENERATEEVENT |
TAPI_EVENT.TE_GATHERDIGITS |
TAPI_EVENT.TE_REQUEST);
for (int i = 0; i < 10; i++)
{
ea.Next(1, out ln, ref arg3);
ia[i] = ln;
if (ln != null)
{
lines++;
}
else
break;
}
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.ToString());
}
--Scenario
CallA: Incoming Call
CallB: Internal Call
Operator: The main answering operator
Bob: A user of this application
Alice: The caller of CallA
Bob uses his cellphone to call Alice's call center. The Operator sees a pop-up screen containing Bob's name and number and then picks up the phone. (CallA is stored). Bob requests to talk to Alice. The Operator clicks on "Alice" on the screen (CallA is now on Hold and the Operator's phone creates CallB which rings Alice's phone). Alice answers the line and agrees to take the call. The Operator clicks "Transfer" on screen and CallA and CallB are connected.
--End of Scenario
--Expected result
When the Operator calls Alice, the event sees that 100 called 107 (with 100 being the Operator's address on the PBX and 107 Alice's. When the Operator connects the 2 calls, I want to be able to pop-up the same screen that the Operator got that includes the information of the connected caller. What am i missing in order to fire that event? Because when i try to check the cln.Call.get_CallInfoString(TAPI3Lib.CALLINFO_STRING.CIS_CALLERIDNUMBER) all i see is 100 which is the Operator that called Alice and that is correct. What is the check i need to do in order to identify that a call has been transferred thus → get the caller ID → pop up the screen
--End of Expected result
I wish i was able to describe it as i have it mind. Thank you for your time and excuse me for the lengthy post.
The TAPI3 lib may have a bit different naming but the fields you are looking for are called ConnectedId, RedirectingId and RedirectionId on TAPI itself.
But it is very dependent on the PBX itself whether these fields get filled or not.
In a transfered call scenario "theoretically" you should see this in the call information:
caller id: source of the original call (= the customer)
called id: destination of the original call (= the published number of the company or service)
connected id: the actual current other party (= who you are speaking with)
redirecting id: the number that transferred the call to you (= operator extension)
redirection id: the number the redirecting party (= operator) transferred the call to, which "should" be the device you are observing this from (if no further forwarding was involved)
But none of this enforced by TAPI so a PBX can fill in what it wants, and different vendors will :(
Following Kris Vanherck's suggestion, I did a temporary measure to handle the transfers until we figure out a proper solution;
string con="000";
while (con.Length ==3 && con.Length >=3)
{
con = cln.Call.get_CallInfoString(TAPI3Lib.CALLINFO_STRING.CIS_CONNECTEDIDNUMBER);
}
Silly approach but hey it works. Can't wait to see a more appropriate way to do this. Again thanks Kris for the direction.
You're making it too hard on yourself. You don't need to call Hold manually, the Transfer Method does it for you.
When a new call from B comes in (TAPI_EVENT.TE_CALLNOTIFICATION), you can take its ITBasicCallControl to call Transfer on:
case TAPI_EVENT.TE_CALLNOTIFICATION:
ITBasicCallControl incomingCall = callNotification.Call;
break;
The Transfer Method takes a pointer to a new created call for consultation, so make sure you create a new call before hand:
//In this case address is of type ITAddress and represents the operators address.
ITBasicCallControl consultationCall = address.CreateCall("107", TapiConstants.LINEADDRESSTYPE_PHONENUMBER, TapiConstants.TAPIMEDIATYPE_AUDIO);
Now you can call Transfer:
incomingCall.Transfer(consultationCall, false);
Finally, when the consultation call disconnects, and Alice wants to take the call, use the Finish Method to finish transfering:
case CALL_STATE.CS_DISCONNECTED:
ITBasicCallControl consultationCall = (ITBasicCallControl )callStateEvent.Call;
consultationCall.Finish(FINISH_MODE.FM_ASTRANSFER);
break;
I am trying to convert my C# code to VB.NET, but I am stuck.
The error I am getting is "Private Event FixationReceived (Byref data As Fixation, userdata As System.IntPtr) is an event and cannot be called directly. Use RaiseEvent to raise an event.
My original C# code is:
private event FixationDelegate FixationReceived;
That is how I setup the delegate handler:
FixationReceived += new FixationDelegate(SharpClient_FixationReceived);
IntPtr p;
p = Marshal.GetFunctionPointerForDelegate(FixationReceived);
_api.SetFixationCB(p.ToInt32(), IntPtr.Zero);
private void SharpClient_FixationReceived(ref Fixation data, IntPtr userData)
{
if (InvokeRequired)
{
BeginInvoke(new FixationDelegate(SharpClient_FixationReceived), new object[] { data, userData });
}
else
{
eventStreamLabel.Text = "Fix Start: " + data.timeStamp.ToString() + " Duration: " + data.duration.ToString();
}
}
I have tried to convert it to VB.NET with the following approach:
Private Event FixationReceived As FixationDelegate
Private Sub VBNET_FixationReceived(ByRef data As Fixation, userData As System.IntPtr)
Stop'is never called :-(
End Sub
And this is how I set up the handler:
Dim p As IntPtr
'In the next line the stated error is raised
p = Marshal.GetFunctionPointerForDelegate(FixationReceived)
_api.SetFixationCB(p.ToInt32(), IntPtr.Zero) 'this works fine
AddHandler(FixationEvent, AddressOf VBNET_FixationReceived )'here I am getting the error "FixationEvent is not an event of MyApplication.Form1"
BeginInvoke(New fixationDelegate(AddressOf vbnet_fixationreceived )'Here I get an error without any further explanation.
I have no experience converting C# delegates to VB.NET, perhaps somebody can shed some light on my mistakes.
Thank you very much!
Try this
AddHandler FixationDelegate, AddressOf SharpClient_FixationReceived
Dim p As IntPtr
p = Marshal.GetFunctionPointerForDelegate(FixationReceived)
_api.SetFixationCB(p.ToInt32(), IntPtr.Zero)
-
Private Sub SharpClient_FixationReceived(ByRef data As Fixation, userData As IntPtr)
If InvokeRequired Then
BeginInvoke(New FixationDelegate(AddressOf SharpClient_FixationReceived), New Object() {data, userData})
Else
eventStreamLabel.Text = "Fix Start: " + data.timeStamp.ToString() + " Duration: " + data.duration.ToString()
End If
End Sub
I have a tricky bug I can't find. I'm doing late-binding on from C# to a native DLL I wrote.
The late-binding seem to work fine. The problems started after I added the callbacks.
The callbacks are defined as so (in c)(On a global scale in the DLL):
typedef void (*FinishedDelegate) (ProcessResult a_bResult);
typedef void (*DownloadStatusUpdateDelegate) (int a_iParametersDownloaded, int a_iTotalParameters);
typedef void (*DownloadFinishedDelegate) (char* a_sConfiguration_values, ProcessResult a_bResult);
DownloadStatusUpdateDelegate pfDownloadStatusUpdateDelegate = NULL;
DownloadFinishedDelegate pfDownloadFinishedDelegate = NULL;
This function is exported:
PLUGIN_API BOOL DownloadConfigration(DownloadStatusUpdateDelegate a_pfStatusDelegate, DownloadFinishedDelegate a_pfFinishedDelegate);
And this is the native function implementation:
DWORD WINAPI DownloadThreadFunc(void* a_pParam)
{
while (g_iParameterDownloaded < PARAMETER_COUNT)
{
if (IsEventSignaled(g_hAbortEvent))
{
CallDownloadFinished(PROCESS_ABORT);
return 0;
}
Sleep(STATUS_DELAY);
CallDownloadStatusUpdate();
g_iParameterDownloaded += STATUS_PARAMS_JUMP;
}
CallDownloadFinished(PROCESS_SUCCESS);
return 0;
}
PLUGIN_API BOOL DownloadConfigration(DownloadStatusUpdateDelegate a_pfStatusDelegate, DownloadFinishedDelegate a_pfFinishedDelegate)
{
if (IsEventSignaled(g_hInProcessEvent))
return false;
pfDownloadStatusUpdateDelegate = a_pfStatusDelegate;
pfDownloadFinishedDelegate = a_pfFinishedDelegate;
g_iParameterDownloaded = 0;
DWORD l_dwResult = WaitForSingleObject(g_hThreadsStructGuardian, INFINITE);
if (l_dwResult == WAIT_OBJECT_0)
{
g_ahThreads[PLUGIN_THREAD_DOWNLOAD] = CreateThread(NULL, 0, DownloadThreadFunc, 0, 0, NULL);
ReleaseMutex(g_hThreadsStructGuardian);
return true;
}
return false;
}
On the managed side, the function is called here:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DownloadStatusUpdateDelegate(int a_iParametersDownloaded, int a_iTotalParameters);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void DownloadFinishedDelegate_Native(StringBuilder a_sConfigurationValues, EPluginProcessResult a_eResult);
private void OnDownloadStatusUpdate(int a_iParametersDownloaded, int a_iTotalParameters)
{
if (DownloadStatusUpdate != null)
{
DownloadStatusUpdate(a_iParametersDownloaded, a_iTotalParameters);
}
}
private void OnDownloadFinished(StringBuilder a_sConfigurationValues, EPluginProcessResult a_eResult)
{
if (DownloadFinished != null)
{
DownloadFinished(a_sConfigurationValues.ToString(), a_eResult);
}
}
public bool DownloadConfiguration()
{
bool l_bResult = DLLDownloadConfigration(OnDownloadStatusUpdate, OnDownloadFinished);
return l_bResult;
}
The weird thing is - It works for a while. I get a "Privileged Instruction" exception after some time, but as I lower STATUS_DELAY, it happens less. The exception shows up at the IsEventSignaled function - But there's simply nothing there.
The download thread syncs to the c# GUI thread to update the GUI.
I've been on this problem for way too much time. It looks like a classic calling conventions problem, but I verified it thoroughly!
Any ideas?
bool l_bResult = DLLDownloadConfigration(OnDownloadStatusUpdate, OnDownloadFinished);
The code isn't very clear, but this is the likely trouble spot. This creates two delegate objects, the callback bombs after the garbage collector runs and deletes the objects. It cannot track managed object references into unmanaged code. You'll need to create the delegates explicitly and store them in a class member so the garbage collector always sees at least one reference.
Have you tried using a lambda? As in:
bool l_bResult = DLLDownloadConfigration((downloaded, totalParams) => OnDownloadStatusUpdate(downloaded, totalParams), (values, result) => OnDownloadFinished(values, result));
My theory on this is that it is failing because your OnDownloadStatusUpdate and OnDownloadFinished methods aren't static. The underlying IL expects the 'this' object as the first invisible arg, but the C-method is not passing it when calling the callback.
EDIT: I think the answerer above me is correct, but can anyone shed some light on how the marshaller actually handles this?
I run the code example below with GC.KeepAlive() on and commented out, but it is the same. How to code so I can see the difference.
// A simple class that exposes two static Win32 functions.
// One is a delegate type and the other is an enumerated type.
public class MyWin32
{
// Declare the SetConsoleCtrlHandler function
// as external and receiving a delegate.
[DllImport("Kernel32")]
public static extern Boolean SetConsoleCtrlHandler(HandlerRoutine Handler,
Boolean Add);
// A delegate type to be used as the handler routine
// for SetConsoleCtrlHandler.
public delegate Boolean HandlerRoutine(CtrlTypes CtrlType);
// An enumerated type for the control messages
// sent to the handler routine.
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
}
public class MyApp
{
// A private static handler function in the MyApp class.
static Boolean Handler(MyWin32.CtrlTypes CtrlType)
{
String message = "This message should never be seen!";
// A switch to handle the event type.
switch(CtrlType)
{
case MyWin32.CtrlTypes.CTRL_C_EVENT:
message = "A CTRL_C_EVENT was raised by the user.";
break;
case MyWin32.CtrlTypes.CTRL_BREAK_EVENT:
message = "A CTRL_BREAK_EVENT was raised by the user.";
break;
case MyWin32.CtrlTypes.CTRL_CLOSE_EVENT:
message = "A CTRL_CLOSE_EVENT was raised by the user.";
break;
case MyWin32.CtrlTypes.CTRL_LOGOFF_EVENT:
message = "A CTRL_LOGOFF_EVENT was raised by the user.";
break;
case MyWin32.CtrlTypes.CTRL_SHUTDOWN_EVENT:
message = "A CTRL_SHUTDOWN_EVENT was raised by the user.";
break;
}
// Use interop to display a message for the type of event.
Console.WriteLine(message);
return true;
}
public static void Main()
{
// Use interop to set a console control handler.
MyWin32.HandlerRoutine hr = new MyWin32.HandlerRoutine(Handler);
MyWin32.SetConsoleCtrlHandler(hr, true);
// Give the user some time to raise a few events.
Console.WriteLine("Waiting 30 seconds for console ctrl events...");
// The object hr is not referred to again.
// The garbage collector can detect that the object has no
// more managed references and might clean it up here while
// the unmanaged SetConsoleCtrlHandler method is still using it.
// Force a garbage collection to demonstrate how the hr
// object will be handled.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Thread.Sleep(30000);
// Display a message to the console when the unmanaged method
// has finished its work.
Console.WriteLine("Finished!");
// Call GC.KeepAlive(hr) at this point to maintain a reference to hr.
// This will prevent the garbage collector from collecting the
// object during the execution of the SetConsoleCtrlHandler method.
GC.KeepAlive(hr);
Console.Read();
}
}
http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx
Thanks.
GC.KeepAlive(hr);
Is by itself a reference (later in the code) ...
So hr can't be collected at the point(s) where you cal GC.Collect().
What exactly did you expect to happen?