精华内容
下载资源
问答
  • Winform和WPF简单对比

    2021-01-21 14:26:00
    如果要新建一个窗口文件WPF中为Windows或者UserControl,文件以.xaml和.cs结尾。但在winform中,则需新建一个Windows Forms文件文件以.cs结尾,但在里面会自动包含.Designer.cs和.resx文件。 其对应关系大致如下...

    winforms 脚本都是基于c#,winforms是做客户端软件,WinForm是.Net开发平台中对Windows Form的一种称谓。
    是一种基于C#的软件开发平台。
    如果要新建一个窗口文件,WPF中为Windows或者UserControl,文件以.xaml和.cs结尾。但在winform中,则需新建一个Windows Forms文件,文件以.cs结尾,但在里面会自动包含.Designer.cs和.resx文件。
    其对应关系大致如下
    一个为窗体文件,一个为窗体逻辑代码文件
    resx中的字段数据是根据界面控件自动生成的,界面控件属性更改,resc会随着更改,反之不会

    展开全文
  • WPF 听写对比工具

    2012-05-02 21:50:00
    网上对于WPF的媒体播放器的实现,和文本对比算法的讨论也颇多,我将这两者结合制作了如下的听写对比的小工具。 设计方面借鉴了沪江小D的听写酷,写这个工具的本意是想方便自己做听写用,不过正好借此机会熟悉了...

    网上对于WPF的媒体播放器的实现,和文本对比算法的讨论也颇多,我将这两者结合制作了如下的听写对比的小工具。

     

     

    设计方面借鉴了沪江小D的听写酷,写这个工具的本意是想方便自己做听写用,不过正好借此机会熟悉了WPF各种控件,MVVM模式等。

     

    MediaElement

    使用MediaElement播放媒体文件,主要实现功能:

    打开文件、播放、暂停、停止、重复播放、进度控制、音量大小控制。

    键盘快捷键支持:F7:播放、暂停;F8:重复播放至当前位置(即播放从S到E)

     

    文本对比

    对比算法参照这位仁兄的剖析

    http://blog.csdn.net/clariones/article/details/1396880

    当然还存在很大缺陷,比如对于超长文本的比较。希望对此有研究的各位兄台多多指教。

    比较结果的显示如下:

     

    使用RichTextBox来控制文本的格式,当然如果仅仅是格式的显示,使用TextBlock也可达到效果。

     

    菜单(右键左上角的图标)

    对比的结果可以保存成Html 文件。

    使用序列化和反序列化听写保存成.dct文件,下次可以打开继续进行。

     

    暂时进行了个简单地介绍,接下来如果有必要以及有时间的话,会详细介绍下细节部分。勉强算是自己学习WPF和MVVM的一个小成果。

     

    代码下载

    转载于:https://www.cnblogs.com/morningcheer/archive/2012/05/02/2479651.html

    展开全文
  • 对比MFC资源文件谈谈WPF布局方式 MFC方式 对于传统的MFC基于UI的应用程序设计通常分两步走,首先是设计UI,使用的是RC文件,然后是代码文件,对RC文件进行操作,如下面Figure 1 的基于对话框的应用程序,其对应的...

    对比MFC资源文件谈谈WPF布局方式

    MFC方式

    对于传统的MFC基于UI的应用程序设计通常分两步走,首先是设计UI,使用的是RC文件,然后是代码文件,对RC文件进行操作,如下面Figure 1 的基于对话框的应用程序,其对应的代码如Figure 2所示,这就是MFC时代的所见即所得,如大家所见,每个控件的代码都和位置都是写死的坐标,这样会带来的问题是当你改变系统运行的的DPI或者软件需要支持本地化的时候,由于有的语言对于同样的意思需要比较长的文字表示,就会带来文字显示不下或者显示不完整的情况。解决问题的方式想必大家都遇见过,手动的去拖拽控件的大小,然后再在不同的语言的系统上进行测试,整个过程异常繁琐,而且错误百出。

             Figure 1 MFC对话框 UI设计

    IDD_MAINWIDNOW DIALOGEX 0, 0, 201, 250
    STYLE DS_SYSMODAL | DS_SETFONT | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
    CAPTION "SendMessage"
    MENU IDC_SENDMESSAGE
    CLASS "SendMessage"
    FONT 8, "MS Shell Dlg", 400, 0, 0x1
    BEGIN
        CONTROL         137,IDC_CAPTURE,"Static",SS_BITMAP,162,68,21,17
        GROUPBOX        "Message",IDC_STATIC,6,94,188,131
        COMBOBOX        IDC_WINDOWSMESSAGE,72,106,118,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
        EDITTEXT        IDC_LPARAM,72,124,118,14,ES_AUTOHSCROLL
        EDITTEXT        IDC_WPARAM,72,142,118,14,ES_AUTOHSCROLL
        LTEXT           "Results from window:",IDC_STATIC,12,162,70,8
        EDITTEXT        IDC_RESULTS,12,174,178,45,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
        PUSHBUTTON      "Send message",IDC_SENDMESSAGE,107,229,87,14
        GROUPBOX        "Window",IDC_STATIC,6,7,186,83
        CONTROL         "Message:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,12,108,54,8,WS_EX_RIGHT
        CONTROL         "wParam:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,12,126,54,8,WS_EX_RIGHT
        CONTROL         "lParam:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,12,144,54,8,WS_EX_RIGHT
        CONTROL         "Handle:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,12,18,54,8,WS_EX_RIGHT
        EDITTEXT        IDC_WINDOW_HANDLE,72,16,118,14,ES_AUTOHSCROLL
        CONTROL         "Window name:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,12,34,54,8,WS_EX_RIGHT
        EDITTEXT        IDC_WINDOW_NAME,72,32,118,14,ES_AUTOHSCROLL
        EDITTEXT        IDC_WINDOW_CLASS,72,48,118,14,ES_AUTOHSCROLL
        CONTROL         "Window class:",IDC_STATIC,"Static",SS_SIMPLE | WS_GROUP,12,50,54,8,WS_EX_RIGHT
        PUSHBUTTON      "Highlight Window",IDC_HIGHLIGHT_WINDOW,12,72,96,14
    END

     Figure 2 MFC对话框 UI代码

    WPF方式

    微软当然知道传统MFC程序的问题,终极的解决方案就是WPF布局,WPF的主流的最简单的布局方式有一下5种。

    -Canvas
    -StackPanel
    -WrapPanel
    -DockPanel
    -Grid
    举例来说DockPanel,简单的几行代码,对5个button进行了布局,大家可以看到,整个布局过程没有一个坐标,这样带来的还出就是改变窗口大小或者DPI,国际化,都不需要任何额外的工作,生活如此美好。

     

    <Window x:Class="WpfApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Hi There!" Height="350" Width="525">
    <DockPanel>
    <Button DockPanel.Dock=" Top" Background=" pink" >1 (Top)</Button>
    <Button DockPanel.Dock=" Left" Background=" Orange" >2 (Left)</Button>
    <Button DockPanel.Dock=" Right" Background=" Yellow" >3 (Right)</Button>
    <Button DockPanel.Dock=" Bottom" Background=" Lime" >4 (Bottom)</Button>
    <Button Background=" Aqua" >5</Button>
    </DockPanel>
    </Window>

     

      Figure 3 简单WPF布局方式


    Figure 4 简单WPF布局方式效果图

     

    当然WPF的能力不止于此,应用WPF完全可以做出基于MFC没办法做出来,或者很难做出来的效果,而且极其简单明了,这就是框架的力量,下面我们来看一个稍微复杂点的例子,当然这样的例子在网上随处可见,不作为奇,但是已经可以说明一定的问题。我们先来看看效果图,然后是源代码。

    Figure 5 一个稍微复杂的WPF布局方式效果图

     

    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Hi There!"
        >
    	<DockPanel>
    		<Menu DockPanel.Dock="Top">
    			<MenuItem Header="File"/>
    			<MenuItem Header="Edit"/>
    			<MenuItem Header="View"/>
    			<MenuItem Header="Project"/>
    			<MenuItem Header="Build"/>
    			<MenuItem Header="Data"/>
    			<MenuItem Header="Tools"/>
    			<MenuItem Header="Window"/>
    			<MenuItem Header="Community"/>
    			<MenuItem Header="Help"/>
    		</Menu>
    
    		<StackPanel Name="buttonBar" Orientation="Horizontal" DockPanel.Dock="Right">
    			<StackPanel.LayoutTransform>
    				<RotateTransform Angle="90"/>
    			</StackPanel.LayoutTransform>
    			<Button Name="pane1Button" MouseEnter="pane1Button_MouseEnter">
    				Toolbox
    			</Button>
    			<Button Name="pane2Button" MouseEnter="pane2Button_MouseEnter">
    				Solution Explorer
    			</Button>
    		</StackPanel>
    
    		<Grid Name="parentGrid" Grid.IsSharedSizeScope="True">
    
    			<Grid Name="layer0" MouseEnter="layer0_MouseEnter">
    				<!-- Define four rows: -->
    				<Grid.RowDefinitions>
    					<RowDefinition Height="Auto"/>
    					<RowDefinition/>
    					<RowDefinition/>
    					<RowDefinition/>
    				</Grid.RowDefinitions>
    
    				<!-- Define two columns: -->
    				<Grid.ColumnDefinitions>
    					<ColumnDefinition Width="Auto"/>
    					<ColumnDefinition/>
    				</Grid.ColumnDefinitions>
    
    				<Label    Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="Blue" Foreground="White"
    						  HorizontalContentAlignment="Center">
    					<Label.LayoutTransform>
    						<ScaleTransform ScaleX="2" ScaleY="2" />
    					</Label.LayoutTransform>
    					SolidMango
    				</Label>
    				<GroupBox Grid.Row="1" Grid.Column="0" Background="White"
    						  Header="Recent Projects">...</GroupBox>
    				<GroupBox Grid.Row="2" Grid.Column="0" Background="White"
    						  Header="Getting Started">...</GroupBox>
    				<GroupBox Grid.Row="3" Grid.Column="0" Background="White" Header="Headlines">...</GroupBox>
    				<GroupBox Grid.Row="1" Grid.Column="1" Grid.RowSpan="3" Background="White" Header="Online Articles">
    					<ListBox>
    						<ListBoxItem>Item #1</ListBoxItem>
                            <ListBoxItem>Item #2</ListBoxItem>
                            <ListBoxItem>Item #3</ListBoxItem>
                            <ListBoxItem>Item #4</ListBoxItem>
    					</ListBox>
    				</GroupBox>
    			</Grid>
    
    			<Grid Name="layer1" Visibility="Collapsed">
    				<Grid.ColumnDefinitions>
    					<ColumnDefinition/>
    					<ColumnDefinition SharedSizeGroup="column1" Width="auto"/>
    				</Grid.ColumnDefinitions>
    
    				<Grid Grid.Column="1" MouseEnter="pane1_MouseEnter"
    	Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}" >
    					<Grid.RowDefinitions>
    						<RowDefinition Height="auto"/>
    						<RowDefinition/>
    					</Grid.RowDefinitions>
    
    					<DockPanel Grid.Row="0">
    						<Button Width="26" Name="pane1Pin" DockPanel.Dock="Right" Click="pane1Pin_Click" Background="White">
    							<Image Name="pane1PinImage" Source="pinHorizontal.gif"/>
    						</Button>
    						<TextBlock Padding="8" TextTrimming="CharacterEllipsis" Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}" DockPanel.Dock="Left">Toolbox</TextBlock>
    					</DockPanel>
    					<ListBox Padding="10" Grid.Row="1">
    						<ListBoxItem>Button</ListBoxItem>
    						<ListBoxItem>CheckBox</ListBoxItem>
    						<ListBoxItem>ComboBox</ListBoxItem>
    						<ListBoxItem>Label</ListBoxItem>
    						<ListBoxItem>ListBox</ListBoxItem>
    					</ListBox>
    				</Grid>
    
    				<GridSplitter Width="5" Grid.Column="1" HorizontalAlignment="Left"/>
    			</Grid>
    
    			<Grid Name="layer2" Visibility="Collapsed">
    				<Grid.ColumnDefinitions>
    					<ColumnDefinition/>
    					<ColumnDefinition SharedSizeGroup="column2" Width="auto"/>
    				</Grid.ColumnDefinitions>
    
    				<Grid Grid.Column="1" MouseEnter="pane2_MouseEnter" Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}">
    					<Grid.RowDefinitions>
    						<RowDefinition Height="auto"/>
    						<RowDefinition Height="auto"/>
    						<RowDefinition/>
    					</Grid.RowDefinitions>
    					<DockPanel Grid.Row="0">
    						<Button Width="26" Name="pane2Pin" DockPanel.Dock="Right" Click="pane2Pin_Click" Background="White">
    							<Image Name="pane2PinImage" Source="pinHorizontal.gif"/>
    						</Button>
    						<TextBlock Padding="8" TextTrimming="CharacterEllipsis" Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}" DockPanel.Dock="Left">Solution Explorer</TextBlock>
    					</DockPanel>
    					<ToolBar Grid.Row="1">
    						<Button>
    							<Image Source="iconVSproperties.bmp"/>
    						</Button>
    						<Button>
    							<Image Source="iconVSshowall.bmp"/>
    						</Button>
    						<Button>
    							<Image Source="iconVSrefresh.bmp"/>
    						</Button>
    					</ToolBar>
    					<TreeView Grid.Row="2">
    						<TreeViewItem Header="My Solution">
    							<TreeViewItem Header="Project #1"/>
    							<TreeViewItem Header="Project #2"/>
    							<TreeViewItem Header="Project #3"/>
    						</TreeViewItem>
    					</TreeView>
    				</Grid>
    
    				<GridSplitter Width="5" Grid.Column="1" HorizontalAlignment="Left"/>
    			</Grid>
    		</Grid>
    	</DockPanel>
    </Window> 
    

      

      Figure 6 一个稍微复杂的WPF布局方式源代码

    总结

    对比两种UI设计方式,读者不难看出,WPF对于UI设计的优越性是MFC RC方式无可匹敌的,无论是从生产力还是界面的美观性上来讲,差一代的技术,差的还是很明显的,WPF的UI设计方式受HTML的启发,在里面能看到很多的HTML的影子,当年微软以一个轻量级的WPF--silverlight 剑指 HTML,虽然silverlight 已经一败涂地,但是WPF仍然是Windows UI 设计的首选。

    转载于:https://www.cnblogs.com/pugang/p/4333367.html

    展开全文
  • 摘要分析卡死问题时,可...Electron的部分代码还没及时跟上Chromium的改动,分析Electron的问题时,可多跟最新版Chrome进行对比。问题现象测试同学在基于Electron Windows的App上测试发送图片时,出现了整个界面卡...

    ced999b9a0e73cd06e4b3a6ae11850e6.png

    摘要

    • 分析卡死问题时,可结合WPT和Windbg一起分析。
    • Electron弹出的打开文件对话框存在必现的卡死场景,原因是弹窗线程COM反初始化卡死,而主线程在同步等待弹窗线程销毁。
    • Electron的部分代码还没及时跟上Chromium的改动,分析Electron的问题时,可多跟最新版Chrome进行对比。

    问题现象

    测试同学在基于Electron Windows的App上测试发送图片时,出现了整个界面卡死。

    现场信息分析

    现场信息捕获

    在测试同学测试时,出现了卡死,保留有现场。分别用以下工具抓取相关信息。

    • Windows Performance Toolkit,抓取了系统的Trace信息。
    • Process Explorer,抓取了主进程的Dump。

    使用WPT分析Trace

    UI Delays分析

    通过WPA的UI Delays可以捕获到UI卡死相关信息。

    • MsgCheck Delay为消息队列被阻塞
    • Input Delay为输入队列被阻塞

    从下面的数据可看出,主线程和文件弹窗线程都被卡死了,且都被卡主了295s,因此,这两个线程之前的卡死可能存在关联性。

    83ec7636cfb824d6380ab8ec5ab173d2.png

    CPU占用分析

    在Trace的CPU占用统计中,没有捕获到这两个线程的相关信息,原因是这两个线程已经被挂起了,CPU消耗很少。只能通过其他方法对问题进一步分析。

    c7d016e4367f5bde302961b61af78d89.png

    结论

    根据已有线索进行推论:有2个线程被卡死了,可能存在关联。

    • 主线程,id 6028, 0x178c
    • ElectronFileDialogThread线程,id 10076,0x275c

    使用Windbg分析Dump

    Dump详情

    相关操作

    • 打开Dump,加载符号
    • 查看卡死线程调用栈信息
    ||0:000> .symfix C:Symbols
    ||0:000> .reload
    ||0:000> !analyze -v
    EXCEPTION_RECORD: (.exr -1)ExceptionAddress: 00000000
    ExceptionCode: 80000003 (Break instruction exception)
    ExceptionFlags: 00000000
    NumberParameters: 0
    FAULTING_THREAD: 0000178c
    PROCESS_NAME: Electron.exe
    ||0:000> k
    # ChildEBP RetAddr
    00 095bf480 7614e2c9 ntdll!NtWaitForSingleObject+0xc
    WARNING: Stack unwind information not available. Following frames may be wrong.
    01 095bf4f4 7614e222 KERNELBASE!WaitForSingleObjectEx+0x99
    02 095bf508 01bcead8 KERNELBASE!WaitForSingleObject+0x12
    03 095bf558 01bd2cbc Electron!base::PlatformThread::Join+0x78
    04 095bf570 01be559b Electron!base::Thread::~Thread+0x2c
    05 095bf57c 00348551 Electron!base::Thread::~Thread+0xb
    06 095bf588 006bfe4c Electron!base::DeleteHelper<net::SerialWorker>DoDelete+0x11
    07 095bf594 01b6783b Electron!base::internal::Invoker<base::internal::BindStatev<oid ()(void ),unsigned char *>,void ()>::RunOnce+0xc
    08 095bf5d8 01b87903 Electron!base::debug::TaskAnnotator::RunTask+0xab
    09 095bf640 01b87b0d Electron!base::MessageLoopImpl::RunTask+0xc3
    0a 095bf660 01b87d5a Electron!base::MessageLoopImpl::DeferOrRunPendingTask+0x4d
    0b 095bf728 01b88b38 Electron!base::MessageLoopImpl::DoWork+0xca
    0c 095bf760 01b88541 Electron!base::MessagePumpForUI::DoRunLoop+0x78
    0d 095bf780 01b8775f Electron!base::MessagePumpWin::Run+0x41
    0e 095bf790 01ba1e03 Electron!base::MessageLoopImpl::Run+0x1f

    根据上面的信息可得出,当前卡死的线程id为线程0x178c,即主线程。调用栈信息表明,主线程之所以卡死了,是因为在等待另一个线程结束。要知道是在等待哪个线程,就需要拿到该线程的进一步信息。切换到对应的调用函数,查看相关变量的值即可获取到线程信息,即Thread变量的值。

    ||0:0:000> .frame 05
    05 095bf57c 00348551 Electron!base::Thread::~Thread+0xb
    ||0:0:000> dx this
    this : 0x1a67dea0 [Type: base::Thread *]
    [+0x004] com_status_ : STA (1) [Type: base::Thread::ComStatus
    [+0x008] joinable_ : true [Type: bool]
    [+0x009] stopping_ : true [Type: bool]
    [+0x00a] running_ : false [Type: bool]
    [+0x00c] running_lock_ [Type: base::Lock]
    [+0x010] thread_ [Type: base::PlatformThreadHandle]
    [+0x014] thread_lock_ [Type: base::Lock]
    [+0x018] id_ : 0x275c [Type: unsigned long]
    [+0x01c] id_event_ [Type: base::WaitableEvent]
    [+0x024] message_loop_ : 0x111ad860 [Type: base::MessageLoop *]
    [+0x028] run_loop_ : 0x2675f894 [Type: base::RunLoop *]
    [+0x02c] using_external_message_loop_ : false [Type: bool]
    [+0x030] timer_slack_ : TIMER_SLACK_NONE (0) [Type: base::TimerSlack]
    [+0x034] name_ : “ElectronFileDialogThread” [Type: std::basic_stringc<har,std::char_traitsc<har>,std::allocator<char> >]
    [+0x04c] start_event_ [Type: base::WaitableEvent]
    [+0x054] owning_sequence_checker_ [Type: base::SequenceChecker]

    以上信息说明,主线程在等待的线程名为ElectronFileDialogThread,id为0x275c。这些信息,跟WPT获取的信息正好互相印证。有了线程id,就可以切换到该线程去查看调用栈信息。

    ||0:0:000> ~~[0x275c]s
    eax=00000000 ebx=00000004 ecx=00000000 edx=00000000 esi=00000000 edi=0965a000
    eip=7781708c esp=2675e8f4 ebp=2675e968 iopl=0 nv up ei pl nz na po nc
    cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
    win32u!NtUserMsgWaitForMultipleObjectsEx+0xc:
    ||0:0:072> k
    # ChildEBP RetAddr
    00 2675e8f0 7526dfd9 win32u!NtUserMsgWaitForMultipleObjectsEx+0xc
    01 2675e968 7526df0d user32!RealMsgWaitForMultipleObjectsEx+0x79
    02 2675e98c 76d388e6 user32!MsgWaitForMultipleObjectsEx+0x4d
    03 2675ea0c 76d38735 combase!CCliModalLoop::BlockFn+0x166
    04 2675ea54 76d3ac8c combase!ModalLoop+0xbe
    05 2675ea6c 76d32d90 combase!ClassicSTAThreadDispatchCrossApartmentCall+0x4c
    06 (Inline) ——– combase!CSyncClientCall::SwitchAptAndDispatchCall+0x3f5
    07 2675eba0 76d39f6a combase!CSyncClientCall::SendReceive2+0x4d0
    08 (Inline) ——– combase!SyncClientCallRetryContext::SendReceiveWithRetry+0x29
    09 (Inline) ——– combase!CSyncClientCall::SendReceiveInRetryContext+0x29
    0a 2675ec6c 76d32446 combase!ClassicSTAThreadSendReceive+0x1ea
    0b 2675ee4c 76d5eb84 combase!CSyncClientCall::SendReceive+0x296
    0c (Inline) ——– combase!CClientChannel::SendReceive+0x75
    0d 2675ee74 75505fe5 combase!NdrExtpProxySendReceive+0xc4
    0e 2675f364 76d8de30 rpcrt4!NdrClientCall2+0x5f5
    0f 2675f384 76d85dcf combase!ObjectStublessClient+0x70
    10 2675f394 76d303c6 combase!ObjectStubless+0xf
    11 (Inline) ——– combase!RemoteReleaseRifRefHelper+0x123
    12 (Inline) ——– combase!RemoteReleaseRifRef+0x1de
    13 2675f4d0 76d21fcd combase!CStdMarshal::DisconnectCliIPIDs+0x826
    14 2675f538 76d7136d combase!CStdMarshal::DisconnectWorker_ReleasesLock+0x5ad
    15 2675f550 76d71322 combase!CStdMarshal::DisconnectSwitch_ReleasesLock+0x1d
    16 2675f578 76d71203 combase!CStdMarshal::DisconnectAndReleaseWorker_ReleasesLock+0x34
    17 2675f594 76cef085 combase!CStdMarshal::DisconnectAndRelease+0x35
    18 2675f750 76ceeecb combase!COIDTable::ThreadCleanup+0xd3
    19 (Inline) ——– combase!FinishShutdown::__l2::<lambda_52cd3ea394b0aaaaa4b6e0872859635a>::operator()+0x5
    1a 2675f788 76cee8ff combase!ObjectMethodExceptionHandlingAction<<lambda_52cd3ea394b0aaaaa4b6e0872859635a> >+0x15
    1b 2675f7a8 76cee072 combase!FinishShutdown+0x44
    1c 2675f7d0 76d410fe combase!ApartmentUninitialize+0x79
    1d 2675f7f0 76d41629 combase!wCoUninitialize+0xf8
    1e 2675f86c 01c1dfdc combase!CoUninitialize+0xf9
    1f 2675f87c 01be4601 Electron!base::win::ScopedCOMInitializer::~ScopedCOMInitializer+0x1c
    20 2675f8bc 01be5cc7 Electron!base::Thread::ThreadMain+0x171
    21 2675f8e0 76f96359 Electron!base::`anonymous namespace’::ThreadFunc+0xf7
    22 2675f8f0 778f7b74 kernel32!BaseThreadInitThunk+0x19
    23 2675f94c 778f7b44 ntdll!__RtlUserThreadStart+0x2f
    24 2675f95c 00000000 ntdll!_RtlUserThreadStart+0x1b

    FileDialog线程卡死,是在CoUninitialize时在等待某些COM接口的断开连接操作。

    结论

    • 主线程被卡死了,是因为在等待ElectronFileDialogThread线程结束。
    • ElectronFileDialogThread线程被卡死了,是因为在COM反初始化CoUninitialize时卡死了。

    代码分析

    Electron相关代码

    根据现有线索,其中一个线程名为ElectronFileDialogThread。使用ElectronFileDialogThread在代码中搜索,未搜索到相关信息。通过FileDialogThread,搜索到了相关代码。

    bool CreateDialogThread(RunState* run_state) {
      auto thread =
          std::make_unique<base::Thread>(ATOM_PRODUCT_NAME "FileDialogThread");
      thread->init_com_with_mta(false);
      if (!thread->Start())
        return false;
    
      run_state->dialog_thread = thread.release();
      run_state->ui_task_runner = base::ThreadTaskRunnerHandle::Get();
      return true;
    }
    

    根据代码往前追溯发现,主线程接收到文件选择弹窗时,会先创建一个新线程,然后把弹窗任务抛到新线程去执行。主线程的相关代码如下:

    void ShowOpenDialog(const DialogSettings& settings,
                        const OpenDialogCallback& callback) {
      RunState run_state;
      if (!CreateDialogThread(&run_state)) {
        callback.Run(false, std::vector<base::FilePath>());
        return;
      }
    
      run_state.dialog_thread->task_runner()->PostTask(
          FROM_HERE,
          base::Bind(&RunOpenDialogInNewThread, run_state, settings, callback));
    }
    

    弹窗的具体实现由FileDailog线程完成,相关步骤有

    • 弹窗,等待用户交互,用户操作后返回。
    • 把用户选择的路径通过回调返回给主线程。
    • 通知主线程可以把当前线程删除。
    void RunOpenDialogInNewThread(const RunState& run_state,
                                  const DialogSettings& settings,
                                  const OpenDialogCallback& callback) {
      std::vector<base::FilePath> paths;
      bool result = ShowOpenDialog(settings, &paths);
      run_state.ui_task_runner->PostTask(FROM_HERE,
                                         base::Bind(callback, result, paths));
      run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
    }
    

    最后一步调用DeleteSoon的作用,就是通知主线程在callback调用结束之后,删除FileDialog线程。

      template <class T>
      bool DeleteSoon(const Location& from_here, const T* object) {
        return DeleteOrReleaseSoonInternal(from_here, &DeleteHelper<T>::DoDelete,
                                           object);
    
    template <class T>
    class DeleteHelper {
     private:
      static void DoDelete(const void* object) {
        delete static_cast<const T*>(object);
      }
    };
    

    FileDialog线程的COM相关代码

    FileDialog线程的卡死跟COM有关,因此,需要分析下与COM有关的代码。其中,FileDialog线程采用的COM线程模型为STA。因此,线程启动和结束时,分别会调用ScopedCOMInitializer封装的COM初始化和反初始化CoUninitalize。

      void init_com_with_mta(bool use_mta) {
        DCHECK(!delegate_);
        com_status_ = use_mta ? MTA : STA;
      }
    
      std::unique_ptr<win::ScopedCOMInitializer> com_initializer;
      if (com_status_ != NONE) {
        com_initializer.reset(
            (com_status_ == STA)
                ? new win::ScopedCOMInitializer()
                : new win::ScopedCOMInitializer(win::ScopedCOMInitializer::kMTA));
      }
    

    修复方案

    这里面有2个问题需要解决

    • FileDialog线程的销毁卡死问题
    • 主线程不应该同步等待其他线程结束

    FileDialog线程COM反初始化卡死问题处理

    代码分析与查看文档相结合

    由于已经知道跟COM有关,主要从以下两个方面入手:

    • 排查FileDialog线程中与COM有关的代码,是否存在误用,如资源未释放的问题。
    • 通过MSDN查询与COM有关的文档。

    该问题是测试同学发现的问题,有一定复现概率,通过之前录得视频发现,测试同学进行了拖动操作,会跟OLE相关。查询MSDN了解到,在执行文件的拖放操作之前,必须要调用Ole初始化OleInitialize,否则可能会有问题。

    Applications that use the following functionality must call OleInitialize before calling any other function in the COM library:
    • Clipboard
    • Drag and Drop
    • Object linking and embedding (OLE)
    • In-place activation

    从代码中可以看出,FileDialog线程是没有进行OLE初始化的,而MSDN说明拖动必然依赖OLE初始化,因此,这是一个矛盾点。于是,把相关操作进行试验发现,如果在打开文件弹窗内进行拖动操作,是可以稳定复现这个卡顿的。

    使用OLE初始化替换COM初始化

    要解决这个问题,只需要在FileDailog线程中,用OLE替代COM的初始化和反初始化。
    通过前面的代码可以看出,Thread的变量com_status_==NONE时,是不会进行COM初始化。因此,修复方案如下:

    • 删除CreateDialogThread函数的thread->init_com_with_mta(false)操作。
    • 在线程的初始化添加OLE的初始化。

    为了对原有的代码浸入较小,本方案对base::Thread类进行了继承。对已有代码,只有2行代码的改动,其他都是新增代码。

    class FileDialogThead : public base::Thread {
    public:
      explicit FileDialogThead(const std::string& name) : base::Thread(name) {
      }
    protected:
      virtual void Init() {
        OleInitialize(NULL);
      }
      virtual void CleanUp() {
        OleUninitialize();
      }
    }
    
    bool CreateDialogThread(RunState* run_state) {
      auto thread =
          std::make_unique<FileDialogThead>(ATOM_PRODUCT_NAME "FileDialogThread");
      if (!thread->Start())
        return false;
      // thread->init_com_with_mta(false);
      run_state->dialog_thread = thread.release();
      run_state->ui_task_runner = base::ThreadTaskRunnerHandle::Get();
      return true;
    }
    

    通过实验发现,这种修复方式,把这个必现的bug变成了一个偶现的bug。这就说明,COM反初始化还有其他的坑,暂时还没有找到原因。本次修复策略,只是缓解了问题,没有彻底解决问题。

    去除主线程的同步等待

    由于找到了必现步骤,经过测试发现,Electron最新版和Chrome 66版本都能稳定复现,但Chrome 79内核版本修复了该问题。因此,只需要参考Chrome的修复方案即可。通过查看代码发现,Chrome主要有以下改动:

    • 增加了线程池,FileDialog线程不会实时销毁。
    • FileDailog线程已经移出了主进程,跟主进程无关。
    scoped_refptr<base::SingleThreadTaskRunner> CreateDialogTaskRunner() {
      return CreateCOMSTATaskRunner(
        {base::ThreadPool(), base::TaskPriority::USER_BLOCKING,
         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, base::MayBlock()},
        base::SingleThreadTaskRunnerThreadMode::DEDICATED);
    }
    

    后记

    Electron的部分代码是从较早版本的Chromium拷贝过来,没有及时跟上Chromium的步伐。Electron同步Chromium的最新代码,应该是可以为Electron开源社区做的一个事。

    参考资料

    • MsgCheck说明
    • Chromium源码

    关于作者

    微信公众号:程序员bingo
    Blog: https://bingoli.github.io/
    GitHub: https://github.com/bingoli

    相关文章

    程序员bingo:使用WPT分析第三方软件导致的软件卡顿问题zhuanlan.zhihu.com
    4b10bd2a69a1ced4671b9d4e93bdaf7d.png
    展开全文
  • WPF之路——用户控件对比自定义控件UserControl VS CustomControl) 将多个现有的控件组合成一个可重用的“组”。 由一个XAML文件和一个后台代码文件。 不能使用样式和模板。 继承自UserControl类。 ...
  • WPF中,概念上来说用户自己制作的控件有两种:用户控件和自定义控件。但是这两种控件之间有什么区别?这篇文章中列出了他们各自的特点,以便在项目中选择选择合适的控件类型。 用户控件(组合) 将多个现有的...
  • WPF中,概念上来说用户自己制作的控件有两种:用户控件和自定义控件。但是这两种控件之间有什么区别?这篇文章中列出了他们各自的特点,以便在项目中选择选择合适的控件类型。 用户控件(组合) 将多个现有...
  • 在Web开发中,我们通过CSS来控制页面元素的样式,一般常用三种方式: ...3. 外部样式表:即写一个独立的.css文件,然后再html页面上引入该文件,然后设置元素的class属性   具体如何操作,这里就不说了。不懂的去百
  • 在做WPF 开发时,有一次发现如上问题,对比很久之后,发现是本地话引起的,解决方法如下: 解决方法1:直接用记事本打开项目文件.csproj(Visual C# Project file ) 在编译时使用的是en-US选项进行编译并生成了en-...
  • wpf博大精深,除了数据绑定外,最突出的就是属性和触发器就是,它从html+css里继承了许多优秀的特点,本文就样式里属性展开,我用了5个button做对比,总共有三个文件:一个窗体xaml,一个资源字典Dictionary1.xaml,...
  • 对比起昨天,今天增加了菜单, 实现了动态添加选项框和组合框 并且动态添加 快捷菜单 而且每个选项,按钮都编写了相应的 响应事件 有两个快捷菜单: 一个针对姓名的清空操作 一个用于保存和清空的操作 ...
  • 生成的时候一直报“...不包含适合于入口点的静态‘Main...于是重新新建一个WPF项目,拿App.xaml和App.xaml.cs文件与现有项目自己添加的对比了一下,基本关键元素都有。没办法只能去看项目配置中哪地方可以配置启动关...
  • 项目中碰到这么个问题:在类库中添加自定义控件,...类库内缺少相关引用,添加引用后任然不行,那普通的类库和其他类库的区别在哪呢,只有工程文件了,对比发现如下区别: 把类库的工程文件记事本打开,添加缺少内...
  • Android之旅--Activity

    2010-11-26 16:02:00
    我们可以将其和WPF对比起来看。WPF中的界面是用xaml来表示的,Activity的界面也是用xml文件表示的,放置在res->layout下面。xaml对应的后台文件是一个.cs文件,Activity对应的后台文件是在src->packe...
  • android面试题整理

    2011-08-02 22:30:26
    什么是Activity? 答:通俗一点说Activity就是一个界面,这个...我们可以将其和WPF对比起来看。WPF中的界面是用xaml来表示的,Activity的界面也是用xml文件表示的,放置在res->layout下面。xaml对应的后台文件是一
  • android面试题

    千次阅读 2011-07-28 23:55:16
    什么是Activity? 答:通俗一点说Activity就是一个界面,这个...我们可以将其和WPF对比起来看。WPF中的界面是用xaml来表示的,Activity的界面也是用xml文件表示的,放置在res->layout下面。xaml对应的后台文件是一个.c
  • 我说说我的改造程序:通信和原程序一样,socket通信,多线程爬取网页及文件,忙了一阵,程序还有一些改进,有些功能需要添加,一些设置参数我写的比较死,未加上原程序的设置窗体,但这些参数我写在程序中,可以在...
  • 5.5.1 Oracle和SQL Server的常用函数对比 240 5.5.2 Oracle和SQL Server的语句区别 244 5.5.3 ASP.NET连接Oracle失败的解决方法 245 本章常见技术面试题 246 常见面试技巧之经典问题巧回答 246 本章小结 247 第6章 ...
  • 对比在MVC中,Controller是不能操作View的,View也没有提供相应的接口;而在MVP当中,Presenter可以操作View,View需要提供一组对界面操作的接口给Presenter进行调用;Model...

空空如也

空空如也

1 2
收藏数 23
精华内容 9
关键字:

wpf文件对比