1

This is a continuation of https://reverseengineering.stackexchange.com/questions/31886/use-3rd-party-dll-in-my-own-application. You can see that question for background on what I'm trying to do.

I've written a basic application using a 3rd-party DLL (same one mentioned in the other question), that parses serial data sent from water meters, and, for now, just displays the parsed data in a Label on a Form.

Complete code:

  public partial class Form1 : Form
    {
        private static D3GTech d3g = new D3GTech();
        private static d3g_tech_managed.BeaconClient beaconClient = new d3g_tech_managed.BeaconClient(d3g, "");
        private static d3g_tech_managed.BeaconClient beaconClientBroadcast = new d3g_tech_managed.BeaconClient(d3g);

        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(Object sender, EventArgs e) {
            d3g.LoadRuntimeDefinitions(Application.StartupPath+"\\TechnicianNET.d3g");
            d3g.FunctionTimeout = 0x00007530;
            d3g.RetriesInterval = 0x000000C8;
            d3g.FunctionRetries = 1;
            d3g.RFType = RFTypes.ETMW;
            d3g.SyncLength = 0;

            d3g.Open(15, "baud=9600 parity=N data=8 stop=1 ");

            beaconClient.BeaconArrived += new BeaconArrivedHandler(this.ReceiveBeacon);
            beaconClientBroadcast.BeaconArrived += new BeaconArrivedHandler(this.ReceiveBeaconBroadcast);  
        }

        private void ReceiveBeacon(byte fs1, byte cmdid, string data) {
            label1.Text += "\n" + data;
        }
        private void ReceiveBeaconBroadcast(byte fs1, byte cmdid, string data) {
            label3.Text += "\n"+data ;
        }

        private void button1_Click(Object sender, EventArgs e) {
            d3g.InvokeFunctionBroadcast("DumpAll", "", beaconClientBroadcast);
        }
    }

When I run my application by double-clicking on the .EXE or using "Start Without Debugging", the application works perfectly.

Works properly when run without debugging

When I run the application using "Start Debugging", the application does not receive any data from the meters.

Does not receive data when run with debugging

The application does, however, successfully send the data to the meters, as can be seen in the serial monitor. That tells me that the 3rd-party library, hardware, etc., are working. Additionally, the meters do respond (serial monitor), but it appears that my code is not being "notified" that a beacon has been received.

I've seen this same behaviour also happen if another 3rd-party DLL isn't in the application folder (sending data works fine, no error or response when receiving), but that isn't the case now (since whether I start with or without debugging, the same executable in the same location, with the same files, is being run). I've verified that with the Command Line in Task Manager. Actually, after additional testing, the application is receiving the data; it just appears to be returning from the function before doing anything with it -- at least, when starting with debugging.

I've tried setting the "Working directory" in Project Properties > Debug to the bin\x86\Debug\ folder, as well as the project source code folder (which also contains all DLLs), but same behaviour. I have also tried toggling various settings on the Debug and Build tabs to try to get a different result, but same every time (Define DEBUG/TRACE constant, allow unsafe code, optimize code, generate serialization assembly, working directory, native code debugging).

The same problem happens using the Release configuration, as well. Running either configuration (Release or Debug) without debugging works fine; running either config with debugging has that problem.

I searched, but I couldn't find anything specific about an external DLL having different behaviour when running an application with vs without debugging, and most of the results complain about something working with debugging and not working without (I'm experiencing the opposite of that).

UPDATE: So, the problem appears to specifically be related to starting the application with debugging. If I start it without debugging then attach the debugger afterwards, everything seems to work fine, including breakpoints, locals, etc. While this is acceptable as a workaround, I'm interested to know what the key difference(s) is that would cause this behaviour. Also nice to be able to press F5 and do it all in one shot.

UPDATE 2: The problem doesn't appear to be the 3rd-party library at all... instead, it appears to be the this.label1.Text += part!(?)

I changed ReceiveBeacon to this:

        private void ReceiveBeacon(byte fs1, byte cmdid, string data) {
            MessageBox.Show("Entered");
            MessageBox.Show(data);
            MessageBox.Show("Before Label");
            this.label1.Text += "\n" + data;
            MessageBox.Show("After Label");
            MessageBox.Show(data);
        }

Running without debugging works as you expect: Shows a message box with "Entered", click OK, then another one with the data, then a third with "Before Label", the label text is appended with the data, then two more message boxes with a string and the data.

However, running with debugging, the first, second, and third message boxes show correctly, including all the data (so the 3rd-party library is doing its job). Then nothing. No label text update, no fourth or fifth message boxes. If I set a breakpoint on MessageBox.Show("Entered"); and step through, it goes through the three message boxes normally, then highlights the label1.Text+=, and after I click Next, nothing. It appears to just return out of the function entirely.

At least, that's what happened as I was writing this. After I finished writing, I retested the exact same thing again, and now it hits the breakpoint on the first MessageBox, but stepping through doesn't even make it to the second one; it returns immediately. Nothing changed from my description in the previous paragraph to now (at least, I didn't change anything). But now I just tried combining the first three MessageBox lines into a single MessageBox.Show("Entered" + data + "Before Label"); and it's working as I described: shows the message box, then returns from the function after highlighting this.label1.Text += "\n" + data;.

I tried that several times back-to-back, and it performed as I described. After coming here and writing this text, I went back to try again (didn't change any code), and now it's back to correctly hitting the breakpoint and showing the correct data in the Locals tab, but returning from the function immediately (not even showing a single message box). The application didn't "crash," because the "Dump All" button still work correctly, including sending data to the serial port, and getting responses from the meters.

I can't find any particular pattern... so far, it seems like behaviour changes (worsens) if I just leave it alone for some time, which doesn't make sense to me.

If I retry without debugging, it still works normally, including attaching the debugger after it loads.

Any ideas?

Thanks!

Using Visual Studio 2022 17.1.1
Target framework: .NET Framework 4.7.2

Chris
  • 96
  • 6
  • 1
    Don't use [MessageBox](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.messagebox?view=windowsdesktop-7.0) for debugging - it's blocking and may result in the behavior that you're describing. Either write to a log file or use [Debug.WriteLine](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.writeline?view=net-7.0). – Tu deschizi eu inchid May 21 '23 at 22:32
  • @Tudeschizieuinchid, thanks for the response. The problem was happening without the message boxes; I actually added the message boxes afterwards to help understand what was happening by using something that couldn't be missed or bypassed. Originally, it was supposed to just append the data string to the label text, and that wasn't working (only when starting with debugging). But, yes, I'll rewrite the example (and update the question) to remove the message boxes, and use other elements to show what is/isn't happening. – Chris May 23 '23 at 00:20
  • So, ended up finding out what was wrong. Thanks to @Tudeschizieuinchid for giving me the hint to use Debug.WriteLine, because that's where I saw ```Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll``` which is ignored by the debugger by default. That led me to https://stackoverflow.com/a/17433615/7268556 which showed how to get the details for the exception: ```Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.``` A solution for that was given in https://stackoverflow.com/a/15831292/7268556. – Chris May 23 '23 at 21:48
  • Then to answer the question about why it behaves differently with a debugger vs without, that appears to be answered by https://stackoverflow.com/a/3972836/7268556. Basically, for that particular exception, the framework(?) explicitly checks to see if a debugger is attached, and only throws that exception if it is. After implementing the changes in the above answers, my test program works perfectly. Thanks again for your help! – Chris May 23 '23 at 21:54
  • @Chris Hi Chris, since you verified and found the detailed steps to solve the issue, you can [post your self answer](https://i.stack.imgur.com/4gQ69.png) and [mark it as the answer](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work/5235#5235) to end this question. This will be beneficial to others when searching the issue in stackoverflow after meeting a similar issue. If only comment, the typesetting and readability of the solution are not very good, and there is no obvious "answered" mark for the community members. :) – Bowman Zhu-MSFT May 24 '23 at 02:08
  • Okay, will do. If not tonight (EDT), then hopefully tomorrow. – Chris May 24 '23 at 19:29

1 Answers1

1

I ended up finding out what was wrong. Thanks to @Tudeschizieuinchid for giving me the hint to use Debug.WriteLine, because that's where I saw an error: Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll which is ignored by the debugger by default.

Debug output with exception

The option to view the Output pane is View > Output

View > Output

The Output from the debugger shows that there was an exception, but doesn't give any details about it, because the debugger wasn't configured to break when it was thrown. The answer here shows how to enable it: Open Exception Settings, then select the exception(s) to catch.

View Exception Settings

Select the exception

Now that the debugger is set to break on that exception, it reveals the details about it:

System.InvalidOperationException: 'Cross-thread operation not valid: Control '<ControlName>' accessed from a thread other than the thread it was created on.'

Exception Thrown pop-up

That now makes a lot of sense, because the code to handle the received beacons is called by an event handler, and I presume that it runs in a different thread from the main UI. Possible solutions to resolve this are given in this answer.

In my case, instead of directly calling something like label1.Text = data, I call ThreadHelperClass.SetText(this, label1, data);. This is the code for ThreadHelperClass:

public static class ThreadHelperClass
{
    //https://stackoverflow.com/a/15831292/7268556
    delegate void SetTextCallback(Form f, Control ctrl, string text);
    /// <summary>
    /// Set text property of various controls
    /// </summary>
    /// <param name="form">The calling form</param>
    /// <param name="ctrl"></param>
    /// <param name="text"></param>
    public static void SetText(Form form, Control ctrl, string text) {
        // InvokeRequired required compares the thread ID of the 
        // calling thread to the thread ID of the creating thread. 
        // If these threads are different, it returns true. 
        if (ctrl.InvokeRequired) {
            SetTextCallback d = new SetTextCallback(SetText);
            form.Invoke(d, new object[] { form, ctrl, text });
        } else {
            ctrl.Text = text;
        }
    }
}

Then to answer the question about why the application behaves differently with a debugger vs without, that appears to be answered here.

Basically, for that particular exception, the framework explicitly checks to see if a debugger is attached, and only throws that exception if one is. When running without debugging from the IDE, or by double-clicking on the .EXE, that exception isn't thrown, so the code execution isn't terminated. When running with debugging, even if the debugger isn't set to break on that exception, the exception is still thrown, and the thread that only handles the beacon event is terminated. The UI thread continues to run, so there isn't any obvious indication that an error occurred.

Chris
  • 96
  • 6