.net 在不调试的情况下使用vs启动时,应用程序的行为会有所不同(不调试工作正常)

hof1towb  于 2023-05-30  发布在  .NET
关注(0)|答案(1)|浏览(144)

这是https://reverseengineering.stackexchange.com/questions/31886/use-3rd-party-dll-in-my-own-application的延续。你可以从这个问题中了解我正在做的事情的背景。
我已经使用第三方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或使用“不调试就启动”来运行应用程序时,应用程序工作得很好。

当我使用“开始调试”运行应用程序时,应用程序不会从 Jmeter 接收任何数据。

但是,应用程序会成功地将数据发送到 Jmeter ,如串行监视器所示。这告诉我第三方库、硬件等正在工作。此外, Jmeter 确实有响应(串行监视器),但似乎我的代码没有被“通知”已收到信标。
如果另一个第三方DLL不在应用程序文件夹中,我也会看到同样的行为(发送数据工作正常,接收时没有错误或响应),但现在情况并非如此(因为无论我是否开始调试,同一位置的相同可执行文件,具有相同的文件,正在运行)。我已经用任务管理器中的命令行验证了这一点。* 实际上,经过额外的测试,应用程序正在接收数据;它看起来只是在执行任何操作之前从函数返回--至少在开始调试时是这样。*
我试过在Project Properties > Debug中将“Working directory”设置为bin\x86\Debug\文件夹,以及项目源代码文件夹(其中也包含所有DLL),但同样的行为。我还尝试在Debug和Build选项卡上切换各种设置,试图获得不同的结果,但每次都是一样的(定义DEBUG/TRACE常量,允许不安全代码,优化代码,生成序列化程序集,工作目录,本机代码调试)。
使用Release配置时也会出现同样的问题。运行任一配置(发布或调试)而不进行调试都可以正常工作;运行任何一个带有调试的配置都有这个问题。
我搜索了一下,但我找不到任何关于外部DLL在不调试的情况下运行vs应用程序时具有不同行为的具体信息,并且大多数结果都抱怨某些东西在调试时工作,而没有调试时不工作(我正在经历相反的情况)。
更新:因此,问题似乎与启动调试应用程序有关。如果我在没有调试的情况下启动它,然后附加调试器,一切似乎都工作正常,包括断点,局部变量等。虽然这是一个可以接受的解决方案,但我很想知道导致这种行为的关键差异是什么。也很高兴能够按下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);
        }

不进行调试就运行,如您所料:显示一个带有“已输入”的消息框,单击“确定”,然后显示另一个带有数据的消息框,然后显示第三个带有“标签之前”的消息框,标签文本附加有数据,然后显示另外两个带有字符串和数据的消息框。
但是,在运行调试时,第一个、第二个和第三个消息框会正确显示,包括所有数据(因此第三方库正在执行其工作)。然后就什么都没有了没有标签文本更新,没有第四个或第五个消息框。如果我在MessageBox.Show("Entered");上设置一个断点并单步执行,它会正常地遍历三个消息框,然后突出显示label1.Text+=,单击Next后,什么都没有。它似乎完全从函数中返回。
至少,这就是我写这篇文章时发生的事情。在我完成编写之后,我再次重新测试了完全相同的内容,现在它在第一个MessageBox上遇到了断点,但是单步执行甚至没有到达第二个MessageBox;它会立即返回。从上一段到现在,我的描述没有任何变化(至少,我没有改变任何东西)。但现在我只是尝试将前三行MessageBox合并为一个MessageBox.Show("Entered" + data + "Before Label");,它的工作方式与我描述的一样:显示消息框,然后在突出显示this.label1.Text += "\n" + data;后从函数返回。
我连续试了几次,它的表现就像我描述的那样。来到这里并写了这段文字后,我又回去再试了一次(没有修改任何代码),现在又回到了正确命中断点并在Locals选项卡中显示正确数据,但立即从函数返回(甚至没有显示一个消息框)。应用程序没有“崩溃”,因为“Dump All”按钮仍然正常工作,包括向串行端口发送数据,并从 Jmeter 获得响应。
我找不到任何特定的模式...到目前为止,如果我不去管它一段时间,似乎行为会改变(恶化),这对我来说没有意义。

如果我不进行调试而重试,它仍然可以正常工作,包括在加载调试器后附加调试器。
有什么想法吗
谢谢!
使用Visual Studio 2022 17.1.1
目标框架:.NET Framework 4.7.2

eyh26e7m

eyh26e7m1#

最后我发现了问题所在。感谢@Tudeschizieuinchid给了我使用Debug.WriteLine的提示,因为这就是我看到错误的地方:Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll,默认情况下调试器会忽略它。

用于查看“输出”窗格的选项为“查看”>“输出

调试器的输出显示存在异常,但没有给予有关它的任何详细信息,因为调试器没有配置为在引发异常时中断。answer here显示了如何启用它:打开“异常设置”,然后选择要捕获的异常。

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

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

现在这很有意义,因为处理接收到的信标的代码是由事件处理程序调用的,我假设它运行在与主UI不同的线程中。this answer中给出了可能的解决方案。
在本例中,我没有直接调用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;
        }
    }
}

然后要回答为什么应用程序在有调试器的情况下与没有调试器的情况下行为不同的问题,这似乎是answered here
基本上,对于该特定异常,框架显式地检查是否附加了调试器,并且仅在附加了调试器的情况下抛出该异常。在没有从IDE进行调试的情况下运行时,或者通过双击.EXE运行时,不会引发该异常,因此不会终止代码执行。当运行调试时,即使调试器没有设置为在该异常时中断,仍然会引发该异常,并终止仅处理beacon事件的线程。UI线程继续运行,因此没有任何明显的错误指示。

相关问题