Application behaves differently when started with vs without debugging (without debugging works perfectly)

huangapple go评论75阅读模式
英文:

Application behaves differently when started with vs without debugging (without debugging works perfectly)

问题

这是https://reverseengineering.stackexchange.com/questions/31886/use-3rd-party-dll-in-my-own-application的延续。您可以查看该问题以了解我正在尝试做什么的背景信息。

我已经编写了一个基本的应用程序,使用第三方DLL(与其他问题中提到的相同),该DLL解析从水表发送的串行数据,并且目前仅在窗体上的标签中显示解析后的数据。

完整的代码:

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);
    }
}

当我通过双击.EXE文件或使用"无调试启动"来运行我的应用程序时,应用程序可以正常工作。

当我使用"开始调试"来运行应用程序时,应用程序不会接收来自水表的任何数据。

但是,应用程序成功将数据发送到水表,可以在串行监视器中看到。这告诉我第三方库、硬件等都在工作。此外,水表也会响应(在串行监视器中),但似乎我的代码没有被"通知"到已接收到信标。 实际上,在进行了额外测试后,应用程序正在接收数据;似乎它只是在执行任何操作之前从函数返回了—至少是在启动调试时如此。

我尝试过在"项目属性 > 调试"中设置"工作目录"为bin\x86\Debug\文件夹,以及包含所有DLL的项目源代码文件夹,但行为相同。我还尝试切换"调试"和"生成"选项卡上的各种设置,以尝试获得不同的结果,但每次都相同(定义DEBUG/TRACE常量、允许不安全代码、优化代码、生成序列化程序集、工作目录、本机代码调试)。

相同的问题也发生在使用发布配置时。使用或不使用调试运行任何配置(发布或调试)都没有问题。

我搜索了一下,但找不到有关在调试与不调试时运行应用程序时外部DLL有不同行为的具体信息,大多数结果都抱怨在调试时能正常工作而在不调试时不能正常工作(我遇到的情况与此相反)。

更新:问题似乎特别与启动带有调试的应用程序有关。如果我在不调试的情况下启动它,然后后来附加调试器,一切似乎都正常,包括断点、局部变量等。虽然这作为一种解决方法是可以接受的,但我有兴趣知道会导致这种行为的关键差异是什么。而且按F5键一次完成所有操作也很方便。

更新2:问题似乎与开始时的应用程序有关,而不是调试。如果我首先不使用调试启动,然后稍后附加调试器,一切似乎都正常,包括断点、局部变量等。尽管这可以作为一种解决方法,但我有兴趣知道导致这种行为的关键差异是什么。按F5键一次完成所有操作也很方便。

更新2:问题似乎与第三方库无关...相反,它似乎与this.label1.Text +=的部分有关!(?)

我将ReceiveBeacon更改为以下内容:

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);
}

在不调试的情况下运行会按照您期望的方式工作:显示一个带有"Entered"的消息框,点击"确定",然后显示另一个带有数据的消息框,然后显示第三个带有"Before Label"的消息框,标签文本附加了数据,然后显示两个带有字符串和数据的消息框。

然而,在调试的情况下运行,第一个、第二个和第三个消息框都会正确显示,包括所有数据(所以第三方库正在正常工作)。然后什么都没有。没有标签文本更新,也没有第四或第五个消息框。如果我在```MessageBox.Show("

英文:

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.

Application behaves differently when started with vs without debugging (without debugging works perfectly)

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](https://i.stack.imgur.com/s2Hzp.png "Does not receive data when run with debugging")

<strike>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.</strike> 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 &gt; 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(&quot;Entered&quot;);
            MessageBox.Show(data);
            MessageBox.Show(&quot;Before Label&quot;);
            this.label1.Text += &quot;\n&quot; + data;
            MessageBox.Show(&quot;After Label&quot;);
            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(&quot;Entered&quot;); 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(&quot;Entered&quot; + data + &quot;Before Label&quot;); and it's working as I described: shows the message box, then returns from the function after highlighting this.label1.Text += &quot;\n&quot; + 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

答案1

得分: 1

我最终发现了问题所在。感谢 @Tudeschizieuinchid 提供了使用 Debug.WriteLine 的提示,因为那里我看到了一个错误:Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll,这在调试器中默认被忽略。

查看“输出”窗格的选项在“视图” > “输出”中。

调试器的输出显示有一个异常,但没有提供关于它的任何详细信息,因为调试器没有配置为在抛出异常时中断。这里的答案 显示了如何启用它:打开异常设置,然后选择要捕获的异常。

现在调试器设置为在该异常上中断,它显示了有关它的详细信息:

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

这现在有很多意义,因为处理接收到的信标的代码是由事件处理程序调用的,我认为它在与主 UI 不同的线程上运行。解决这个问题的可能解决方案在这个答案中给出。

在我的情况下,我不直接调用类似 label1.Text = data 的东西,而是调用 ThreadHelperClass.SetText(this, label1, data);。这是 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;
        }
    }
}

然后回答为什么应用程序在使用调试器和不使用调试器时行为不同的问题,似乎在这里得到了解答

基本上,对于特定的异常,框架明确检查调试器是否已附加,仅在有调试器附加时抛出该异常。在从 IDE 中无调试运行或通过双击 .EXE 运行时,该异常不会被抛出,因此不会终止代码执行。在调试运行时,即使调试器未设置为在该异常上中断,异常仍然会被抛出,并且仅处理信标事件的线程会被终止。UI 线程继续运行,因此没有明显的指示发生错误。

英文:

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: &#39;System.InvalidOperationException&#39; in System.Windows.Forms.dll which is ignored by the debugger by default.

Application behaves differently when started with vs without debugging (without debugging works perfectly)

The option to view the Output pane is View > Output

Application behaves differently when started with vs without debugging (without debugging works perfectly)

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.

Application behaves differently when started with vs without debugging (without debugging works perfectly)

Application behaves differently when started with vs without debugging (without debugging works perfectly)

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

System.InvalidOperationException: &#39;Cross-thread operation not valid: Control &#39;&lt;ControlName&gt;&#39; accessed from a thread other than the thread it was created on.&#39;

Application behaves differently when started with vs without debugging (without debugging works perfectly)

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);
    /// &lt;summary&gt;
    /// Set text property of various controls
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;form&quot;&gt;The calling form&lt;/param&gt;
    /// &lt;param name=&quot;ctrl&quot;&gt;&lt;/param&gt;
    /// &lt;param name=&quot;text&quot;&gt;&lt;/param&gt;
    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.

huangapple
  • 本文由 发表于 2023年5月22日 06:08:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76302122.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定