C#调试入门

来源:互联网 发布:pump it up 2004 知乎 编辑:程序博客网 时间:2024/04/29 23:10

DotNet程序的调试,是DotNet程序员必备的技能之一,开发出稳定的程序、解决程序的疑难杂症都需要很强大的调试能力。DotNet调试有很多方法和技巧。现在本文就介绍一下借助DebugView工具进行调试的方法,以及由DebugView引申出来的知识点。

DebugView

DebugView 是一个查看调试信息的非常棒的工具,支持Debug、Release模式编译的程序,甚至支持内核程序,而且能够定制各种过滤条件,让你只看到关心的输出信息,而且可以定制高亮显示的内容等等,非常方便。

debugview

捕捉Release模式的Win32程序输出的调试信息,需要选中 Capture Global Win32 选项:

debugview_release

过滤与高亮功能

debugview_过滤与高亮

可以通过include、exclude设置过滤条件,包含指定字符串的输出信息将会被过滤。还可以通过exclude条件过滤掉对应进程ID的调试信息。多个条件使用“;”分隔,而且支持“*”通配符。

远程调试

DebugView支持远程捕捉调试信息。首先在远程机器上通过如下命令启动DebugView:

DebugView.exe /a /t /g /s

这样,DebugView就会以服务的方式运行,如下图:

debugview服务

然后在本地机器上启动DebugView,并通过Connect连接到远程机器的DebugView,当远程机器中有调试信息输出时,本地就会捕获到,并展示出来:

debugview_connect

输出信息到DebugView的几种方式

DebugView的一些功能是不是让你心动了呢。俗话说心动不如行动,但是在行动之前,首先要知道C#如何将调试信息输出到DebugView中。

通过编程输出一些调试信息到DebugView中,一共有三种方式:

  • Debug.WriteLine
  • Debugger.Log
  • Kernal32.dll中的OutputDebugString方法

一、Debug.WriteLine

通过Debug.WriteLine可以将调试信息写入到DebugView中,如下:

Debug.WriteLine("这是调试信息");

效果如下:

Debug_DebugView

不过此方式只能在Debug模式下有效。具体原因下面会详细介绍。

二、Debugger.Log

Debug.WriteLine已经很好用了,唯一的缺点就是在Release模式下无效。那么在Release模式下就可以使用Debugger.Log方法,示例如下:

Debugger.Log(0, null, "这是Debugger.Log输出的调试信息");

三、Kernel32.dll中的OutputDebugString方法

做C++开发的应该知道可以通过OutputDebugString这个API开实现输出调试信息到DebugView中吧。那么C++能做的,C#也能做。可以通过PInvoke的方式引入此方法,这个API属于Kernel32.dll,如下声明:

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]public static extern void OutputDebugString(string message);

然后就可以通过调用此方法,将调试信息输出到DebugView中。

DebugView与日志框架比较

可能有人会说,DebugView能做的事情,我用log4Net,NLog等日志框架也能做,为什么要用DebugView呢?

问的好,那么我就根据平时使用DebugView的经验来给你一个用DebugView的理由:

  • DebugView使用非常方便 。相比于日志框架庞大的体系,DebugView的使用可谓是十分的简单方便。DebugView只有几百K的大小,占用空间几乎可以忽略不计。从官网下载后,直接运行exe,几乎不需要任何配置就可以正常使用。而相比于DebugView,日志框架可以算的上庞然大物。而当你从官网获得log4Net后,需要进行各种繁杂的配置。甚至你要花上几天时间专门学习一下这套框架。由此可以看出DebugView的使用实在是方便的不能再方便。
  • DebugView是可视化工具,支持各种过滤和高亮 。DebugView可以通过过滤条件来过滤不关心的信息,只显示相关的调试信息。而日志框架输出的是文件等文本信息,这些信息会包含程序运行过程中的所有信息,虽然可以通过配置文件来指定只输出哪一类信息,但是不如DebugView来的方便简单。
  • DebugView可以实时监视 。DebugView中有“自动滚动”的功能,程序中输出的调试信息,基本上瞬间就会在DebugView中展示出来,当由于大量信息导致DebugView中的文本框满了后,DebugView可以通过自动滚动滚动条,让你随时都可以看到最新的一条信息,达到类似监视的效果。而日志框架由于其写文本的特性,很难达到这种效果,即使能达到,相信也是需要对日志框架相当清楚了解,才能完成这个效果。

这些理由应该足以让你使用DebugView了吧。使用DebugView的理由肯定还不止这些,如果你有更好的理由,还请分享出来。

当然,DebugView与日志框架,每个都有每个的用途。通过DebugView的方式,只适合短暂的调试,而正式发布的网站或者软件,需要一套记录程序长期以来的运行状态的工具,那么就非日志框架莫属了。所以DebugView与日志框架,要在合适的地方,发挥他们最大的功效。

声明

Log4Net等日志框架,功能足够强大,也足够丰富,相信上面说到的DebugView的功能,也可以通过日志框架来实现。但是和DebugView比较起来,会相对复杂一些。所以上面说到的使用DebugView的理由是基于方便性的比较,DebugView有足够的方便性来让你选择使用他。

ConditionalAttribute详解与条件编译

说到调试,那么肯定有开发人员遇到这种情况,开发产品的时候,遇到一些问题,就在代码中加入了大量的调试输出信息,比如通过Console.WriteLine、MessageBox.Show或者通过Ilog.Log记录日志,甚至临时改变逻辑来验证逻辑的正确性等。经过这些调试信息的帮助,终于解决了产品的问题。但此时又遇到了新的问题,产品最终发布的时候,肯定是不能有这些调试信息的,可是已经加了这么多调试信息,难道要全部删除掉吗。这显然不是一个好办法,这么多代码,手一抖,很容易就删除了不相关的代码,造成不可预估的后果。

做过C/C++开发的,可以从各种跨平台的开源库中看到,一堆一堆的#if....#else....#endif,这就是条件编译,这也是C/C++跨平台所依赖的最基本的东西,在Linux系统,编译这段代码,在Windows系统又编译那段代码,最终实现了代码级别的跨平台。

那么C#中有没有类似的功能呢,答案当然是有,而且有两种:

  • 通过给方法加上ConditionalAttribute特性
  • 使用#if..#else..#endif,来控制代码的编译

ConditionalAttribute特性

下面是ConditionalAttribute的构造函数:

public ConditionalAttribute(    string conditionString)

构造函数中的参数conditionString,是一个区分大小写的条件编译符号的字符串。

上面提到Debug.WriteLine时,说到这个功能只在Debug模式下才有用,Release下就不起作用了。

我们从MSDN中看一下Debug.WriteLine的说明:

[ConditionalAttribute("DEBUG")]public static void WriteLine(    string message)

由此也就明白了Debug.WriteLine只能在Debug模式下使用,而在Release模式下无效的原因了。

条件编译#if..#else..#endif

C/C++中有#if..#else..#endif,C#中也有这些,他们都被称为预处理器。通过预定义的条件编译符号,来控制编译时编译哪部分代码。如下:

public bool HasPermission(string userName){#if DEBUG  //Debug模式下,不需要做权限判断,直接返回true  return true;#else  //Release模式下,只有sa用户才有权限  if (!string.IsNullOrEmpty(userName) && userName == "sa")  {    return true;  }  else  {    return false;  }#endif}

预定义的Debug宏在什么地方

说到条件编译,是不是只有DEBUG 和 RELEASE两种情况呢,如果是这种情况的话,那也就是说DEBUG和RELEASE两种情况是定义好了的,俗话说就是“做死了”,这是作死的节奏啊。不作死就不会死,至少VS在这点上还没有作死。

让我们来一步步揭开DEBUG的面纱。

既然是条件编译,那么就应该和编译选项有关。我们知道C#项目,有一个属性页,可以设置很多编译的选项,如下:

VS项目生成属性页

从图中看到,条件编译是用的DEBUG常量,或者称为DEBUG条件编译符号,是在这个编译生成选项中定义的,如果去掉这个定义,那么编译后的 HasPermission方法就会根据用户名进行权限检查,程序中通过Debug.WriteLine输出的调试信息也会输出到DebugView中,也就相当于Release模式下的效果。

其实DEBUG常量与Debug、Release模式并无太大的关系,唯一的关系就是,VS生成的项目中,Debug模式下,默认会选中“定义DEBUG常量”,而Release模式下,默认不会选中。也就是说,Debug模式,默认会定义DEBUG常量,而Release不会,如下图:

Debug_Release默认配置

既然DEBUG常量与Debug模式无本质上的关联,那么为什么说到Debug,就认为DEBUG呢。道理其实很简单,世上本无路,走的人多了,便成了路。本来这个DEBUG常量只是Debug模式下的默认预定义的常量,只是因为大家习惯了,并且对它的这种预定义还比较认可,时间久了,就自然而然认为DEBUG就代表Debug模式。

虽然我们可以通过去掉DEBUG常量,来使条件编译在Debug模式下达到Release模式的效果,但是建议最好不要这样做,因为这就像是大家普遍都认可的一个约定,如果你一反常态,不遵守这个约定,对于程序,编译没有问题,但是后续维护肯定会相当麻烦,所以还请大侠手下留情。

使用自定义的编译常量

DEBUG常量作为一种普遍的约定,最好不要打破。如果有除DEBUG外的条件编译需要,可以使用自定义的编译常量。

自定义编译常量有两种方法:

  • 通过编译生成属性页中的条件编译输入框,定义自己的编译常量。
  • 在代码中使用#define预处理,来定义编译常量。

我们可以在条件编译的输入框中,定义自己的编译常量,多个常量之间用分号“;”隔开,而且支持中文常量,如下:

自定义条件编译符号

当然,我们也可以在代码中通过#define预处理来定义,比如:

#define 缘生梦#define hbccdf

但是有一点需要注意,define定义常量必须在using 命名空间之前,否则会造成编译错误。

通过VS和Resharper改变颜色来查看哪些代码真正会被编译

引入条件编译后,我们可以通过VS很快知道哪些代码会被编译:

VS_debug_release颜色对比

虽然条件编译的代码可以很直观的看出来,但是Conditional修饰的方法就看不出来了,这时就要借助神器Resharper了,如下图:

resharper_debug_release颜色对比

从图中看出,release模式,由于没有定义DEBUG、缘生梦两个常量,所以,调用Test、Debug.WriteLine方法的地方就会变暗,这样很直观就知道这些代码不会被编译。再次说明一个道理,神器对于开发有很大的帮助作用。

通过反编译查看生成后的代码

上面总是说,有些代码会被编译,有的则不会,那么真正编译后的效果是怎样的,我们不妨使用另外一个比较强大的工具,反编译工具Reflector,来查看一下反编译后的代码:

debug_release_反编译代码对比

从图中的反编译后的代码可以看出,满足条件的代码会真正编译到生成的程序集里,而不满足的代码则不会生成到程序集里。

Conditional修饰的方法,会有方法的实现,但是没有方法的调用。不适合在方法里做一些变量的修改。

总结Conditional和条件编译

  • Conditional为方法级别的条件编译,只能修饰方法;#if..#else..#endif为行级别的条件编译,可以指定任意代码。
  • Conditional只能修饰无返回值的方法,而且不适合在方法中处理一些变量的值。#if..#else..#endif没有任何限制。
  • 可以通过条件编译符号输入框,或者代码中的#define定义条件编译常量,这些常量对于Conditional、#if..#else..#endif均有效。

TRACE常量

从上面多幅图中,可以看到,在Debug和Release模式下都会定义一个TRACE常量。

现在我们知道DEBUG常量是用来控制调用Debug.WriteLine的语句是否被编译。那么TRACE常量呢。

相信很多人也用过System.Diagnostics.Trace类,由DEBUG常量与Debug有关,可以想到TRACE常量与Trace有关,我们来看一下MSDN对于Trace的定义。

从MSDN中,看到Trace类的定义以及可以调用的方法与Debug类似,都有WriteLine方法。下面是Trace的WriteLine方法定义:

[ConditionalAttribute("TRACE")]public static void WriteLine(    string message)

由此可以知道,TRACE常量是用来控制Trace类中WriteLine等方法是否被编译的作用。

Debug类与Trace类的区别

到现在为止,我们渐渐的了解了Debug类与Trace类,他们都可以通过WriteLine方法输出调试或跟踪信息,从他们的定义和暴露的方法中,可以看出他们非常相似,那么他们有什么区别呢。

有些人会根据使用的效果总结为:Debug类只能在Debug模式下执行,在Release模式下无效果,而Trace类在Debug和Release模式下都可以执行。

确实,在VS中新建一个项目,分别调用Debug.WriteLine和Trace.WriteLine方法,只有Trace.WriteLine输出的信息,在Release模式下有效。这也看似验证了上面的区别。

但这是他们两个真正的区别吗。我们一点一点来分析。

首先看这两个方法的定义:

MSDN_Debug_Trace对比

由图看到,每个方法都通过ConditionalAttribute关联了一个条件编译常量,Debug关联DEBUG常量,Trace关联TRACE常量。再来看一下这两个常量的定义:

常量定义_Debug_Release对比

从图中看到,TRACE在Debug和Release模式下都会定义,这样在Debug和Release模式下都会执行,而DEBUG只在Debug模式下才会定义,Debug.WriteLine只在Debug模式下执行。从而也验证了上面的结论。

但DEBUG与TRACE只是在默认的情况下的定义。当改变这些定义后,上面的结论就不再正确。

我们来做这样的实验:Debug模式下只定义TRACE常量,Release模式只定义DEBUG常量

常量测试_Debug_Release对比

然后在Debug和Release模式下分别执行Debug.WriteLine方法和Trace.WriteLine方法,Debug.WriteLine方法只在Release模式下有效,而Trace.WriteLine方法只在Debug模式下有效。上面的结论不再成立。

为了更好的验证一下我们的结论,对他们进行反编译:

反编译_Debug_Trace.WriteLine对比

由图中的反编译代码看到,除了关联的条件编译常量不同外,内部调用的方法均为TraceInternal.WriteLine,实现完全一样。

那么下面来总结一下Debug与Trace的区别:

  • Debug类关联DEBUG常量,Trace类关联TRACE常量。
  • 默认情况下,VS创建的项目Debug模式会定义DEBUG与TRACE,而Release模式只定义TRACE,使Debug只在Debug模式有效,而Trace在Debug和Release模式均有效。
  • 可以通过修改DEBUG、TRACE的默认定义,来改变Debug、Trace的默认行为。但是建议最好不要这样做,因为改变了这种默认的约定可能会出现意想不到的问题。

Debug、Debugger、Kernel32的联系

每个人大脑的空间都是有限的,零散的知识很容易忘掉,输出调试信息到DebugView中的三种方法,由于关联性不是很强,很容易会忘掉其中的一两种,那么既然实现相同的功能,他们之间有什么关联吗。

这就要从Debug的MSDN文档说起。

我们知道Debug编译的程序,运行的时候可以通过Debug.WriteLine方法输出调试i信息到DebugView,而MSDN中的解释

将后跟行结束符的消息写入 Listeners 集合中的跟踪侦听器。

并没有说是输出到DebugView中,而是写入到Listeners集合中的跟踪侦听器。为了弄明白原理,有必要深入的研究一下。这时就要依赖反编译工具Reflector了。

Debug类整体观赏

从整体看上去,Debug类的每一个方法都通过Conditional与DEBUG常量管理,也就是默认情况下,Debug类的所有方法在Debug模式下均不会编译。

我们再来具体看一下Debug.WriteLine方法:

[Conditional("DEBUG"), __DynamicallyInvokable]public static void WriteLine(string message){    TraceInternal.WriteLine(message);}

再来看一下TraceInternal方法:

public static void WriteLine(string message){  foreach (TraceListener listener in Listeners)  {    listener.WriteLine(message);    if (AutoFlush)    {      listener.Flush();    }  }  return;}

上面的代码是精简后的代码。从代码中看到会调用集合中的每一个listener的WriteLine方法。

那么这些listener的WriteLine又做了什么呢:

public abstract class TraceListener : MarshalByRefObject, IDisposable{    public abstract void WriteLine(string message);}

原来TraceListener的WriteLine是抽象类的抽象方法,那么我们得到的listener是具体类的一个抽象,相当于接口,这是微软的一贯做法,再继续下去,就需要知道是哪些具体的TraceListener了。

继续上面的线索,我们是从Listeners属性中获取到的TraceListener,那么就去看Listeners的get实现:

public static TraceListenerCollection Listeners{  get  {    InitializeSettings();    if (listeners == null)    {      lock (critSec)      {        if (listeners == null)        {          SystemDiagnosticsSection systemDiagnosticsSection = DiagnosticsConfiguration.SystemDiagnosticsSection;          if (systemDiagnosticsSection != null)          {            //从配置文件获取listener,但是由于此处没有设置配置文件,所以先不研究这个地方              listeners = systemDiagnosticsSection.Trace.Listeners.GetRuntimeObject();          }          else          {            //这里new了一个TraceListener的集合              listeners = new TraceListenerCollection();            //这里我们看到了TraceListener的具体实现类DefaultTraceListener            TraceListener listener = new DefaultTraceListener {              IndentLevel = indentLevel,              IndentSize = indentSize            };            listeners.Add(listener);          }        }      }    }    return listeners;  }}

从代码中,找到了具体的实现类DefaultTraceListener,那么就快点看看他的WriteLine方法吧,有点迫不及待了。

实际上,WriteLine方法会调用内部的Write方法:

private void Write(string message, bool useLogFile){    if (base.NeedIndent)    {         //写缩进,实际是空格          this.WriteIndent();    }    if ((message == null) || (message.Length <= 16384))    {         //输出消息          this.internalWrite(message);    }    else    {        //当消息很长时,会通过internalWrite方法将消息多次输出         int startIndex = 0;        while (startIndex < (message.Length - 16384))        {            this.internalWrite(message.Substring(startIndex, 16384));            startIndex += 16384;        }        this.internalWrite(message.Substring(startIndex));    }    if (useLogFile && (this.LogFileName.Length != 0))    {       //输出到日志文件中        this.WriteToLogFile(message, false);    }}

与输出信息有关的有两个地方,一个是调用internalWrite,另外一个是WriteToLogFile。从WriteToLogFile是有执行条件的。感兴趣的可以研究一下。重点来看一下internalWrite方法。

private void internalWrite(string message){  if (Debugger.IsLogging())  {     //调用Debugger.Log方法,这个方法可以输出信息到DebugView中    Debugger.Log(0, null, message);  }  else if (message == null)  {    SafeNativeMethods.OutputDebugString(string.Empty);  }  else  {     //调用Native方法    SafeNativeMethods.OutputDebugString(message);  }}

internalWrite中调用了两个比较重要的方法。其中Debugger.Log方法是不是很熟悉呢。我们刚刚在上面总结了三种输出调试信息到DebugView的方法,其中就包含了Debugger.Log,而Debug.WriteLine方法中,又会调用到Debugger.Log方法,这样,这两个方法就建立起了联系。

再来看SafeNativeMethods.OutputDebugString,看到这个类的命名,以及对Win32API的了解,就能想到,这肯定是通过PInvoke等方法对Win32API的封装,看其定义:

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]public static extern void OutputDebugString(string message);

果然,这就是对kernel32.dll中的OutputDebugString的封装调用,而这又是上面三种方法其中的一种。好了,这样Debug、Debugger、OutputDebugString就全都联系到了一起,他们之间有了联系,是不是就更容易记忆了。

配置TraceListener

从上面对Debug.WriteLine的一步步跟踪分析的过程中,我们看到了对于TraceListener的获取,一种方式是DefaultTraceListener,另外一种就是如下:

SystemDiagnosticsSection systemDiagnosticsSection = DiagnosticsConfiguration.SystemDiagnosticsSection;if (systemDiagnosticsSection != null){     //从配置文件获取listener,但是由于此处没有设置配置文件,所以先不研究这个地方    listeners = systemDiagnosticsSection.Trace.Listeners.GetRuntimeObject();}

这几行代码就是从配置文件的system.diagnostics中获取TraceListener。

关于配置文件具体的读取和解析过程,本文就不再详细介绍了,感兴趣的朋友可以自行研究,或者等到我的后面博文详细介绍。

那么现在主要说一下如何通过配置文件来设置TraceListener,如下:

<configuration>   <system.diagnostics>      <trace autoflush="true" indentsize="2">         <listeners>            <add name="myListener" type="System.Diagnostics.ConsoleTraceListener" />         </listeners>      </trace>   </system.diagnostics></configuration>

同样,有配置文件,那么就可以通过代码来实现同样的功能,如下:

Debug.Listeners.Add(new ConsoleTraceListener());Debug.AutoFlush = true;Debug.WriteLine("这是Debug.WriteLine输出的调试信息");

Debug编译与Release编译的区别

Debug与Release的区别,这个问题,相信很多人都会有疑问,也会有很多人有自己的答案,我听到过的答案有这些:

  • Release比Debug运行速度快,执行效率高。
  • Debug 是调试用的,可以打断点执行。Release是发布用的,不能打断点执行。
  • Debug编译的文件大,Release生成的程序集小。
  • Debug会生成pdb文件,Release不生成pdb文件。

这些答案看上去好像都对,但是为什么Debug与Release有这么大的区别呢,这就需要深入的思考一下。

在Debug类与Trace类的区别一节中,相信有些朋友已经明白,Debug编译与Release编译的区别其实是编译配置的不同,DEBUG、TRACE常量的定义就是其中不同的地方。那么Debug编译与Release编译还有什么不同呢。针对这个问题,我总结了一下,主要区别有下面几点:

  • 常量定义不同
  • 优化代码的不同
  • 包含的调试信息不同
  • 输出路径不同

接下来,我们详细的看一下。

常量定义不同

这个相信大家已经非常清楚了,Debug模式下会定义DEBUG、TRACE常量,而Release模式下只定义TRACE常量,如下图所示

常量定义_Debug_Release对比

优化代码不同

debug_release_优化代码选项对比

Debug模式下,默认不会进行代码的优化,而Release模式下,由于默认选中了“优化代码”选项,所以编译器会对生成的代码进行优化,以提高运行速度。

包含的调试信息不同

编译生成的属性页中有一个“高级”按钮,点击后会弹出一个对话框,然后可以对编译生成进行一些设置,如下

生成高级选项

Debug与Release的高级选项对比:

debug_release_生成高级选项对比

从图中看到,Debug模式下默认会生成全部的调试信息,而Release模式下只生成pdb文件。

输出路径不同

这一点详细大家都很清楚了,Debug模式下会输出到Debug目录,Release模式下会输出到Release目录,如图:

debug_release_输出目录对比

Debug编译与Release编译的区别总结

说了这么多,我们再来思考本节开始时说到的关于Debug编译与Release编译的区别的答案,可以得出这样的结论:

  • Release比Debug运行速度快,执行效率高。 由于Release模式编译会对生成的代码进行优化,所以会使生成的程序集在运行的时候比Debug编译的程序集运行速度快。
  • Debug 是调试用的,可以打断点执行。Release是发布用的,不能打断点执行。 由于Release模式对代码进行优化,生成的调试信息只包含pdb文件,所以导致调试起来会比较困难。
  • Debug编译的文件大,Release生成的程序集小。 依然是由于Release模式对生成的代码进行了优化,所以就导致生成的程序集相对来说比较小。
  • Debug会生成pdb文件,Release不生成pdb文件。 这个要看VS的默认选项,在2012中,Release模式默认会生成pdb文件的,其他版本的VS可能会有不同的情况。

知道这些区别后,我们就可以通过修改编译配置来使Debug程序达到Release的效果,使Release程序达到Debug的效果。当然,还是那句话,虽然可以实现这样的效果,但是建议绝对不要这样做,不然程序维护起来,肯定会遇到很多问题。

总结的重要性

有人说,互联网这么发达,遇到什么问题直接谷歌百度,内事不决问百度,外事不决问谷歌。但是即使你通过网络解决了问题,如果不消化吸收,经过总结变成自己的东西,那么下次还会遇到同样的问题。书到用时方恨少,一回首,已白了少年头。如果不总结,任他虐我千百遍,我却视他如初见,从网络上找到的方法、代码、解决方案,那是别人的知识,只有经过实践去验证,通过总结,消化吸收,才能将这些知识“据为己有”,为我所用。

那么至少从现在做起,把每天学到的知识,遇到的问题,得到的经验,总结下来,相信自己,这是大牛的节奏。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 下围棋太稳了怎么办 苹果5home键失灵怎么办 鱼咸了怎么办怎么补救 工作干不下去了怎么办 长治限行外地车怎么办 低压电工证丢了怎么办 秘讯密语是骗局,怎么办 公司不给小产假怎么办? 吃饭要发票不给怎么办 客厅吊灯买大了怎么办 贝聊等登不上去怎么办 酒店订错了时间怎么办 辞职后奖金不发怎么办 小孩被水母蛰了怎么办 被剧毒水母蛰了怎么办 被水母蛰了很痒怎么办 ipo被否了将来怎么办 科技布沙发裂了怎么办 35岁皮肤皱纹多怎么办 22岁皮肤皱纹多怎么办 腿上皮肤皱纹多怎么办 16岁皮肤有皱纹怎么办 28岁皮肤有皱纹怎么办 眼睛上有血管翳怎么办 鸽子家飞时间短怎么办 宝宝吃母乳不吃奶瓶怎么办 邮件群发超50人怎么办 鸽子拉白色水便怎么办 进京没办进京证怎么办 吃海鲜喝牛奶了怎么办 煎牛排油少了怎么办 网上订酒店去后怎么办 澳洲语言班没过怎么办 照片粘在玻璃上怎么办 照片粘玻璃上怎么办啊 个人3月旅游签证怎么办 澳洲签证拒签了怎么办 我想买房子可是没钱怎么办 微信身份证17位怎么办 身份证后4位泄露怎么办 车牌照掉了一个怎么办