<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Li Xiong</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/lixiong/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/lixiong/feed.xml</link>
    <description />
    <lastBuildDate>Tue, 16 Apr 2019 05:17:16 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>A true story about debug</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/a-true-story-about-debug</link>
      <pubDate>Tue, 14 Nov 2017 07:52:16 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/?p=425</guid>
      <description><![CDATA[My first job is Support Engineer for developer tools at Microsoft China. Most of the time we...]]></description>
      <content:encoded><![CDATA[<span><span style="color: #000000;font-family: Calibri">My first job is Support Engineer for developer tools at Microsoft China. Most of the time we remotely help Microsoft customer to solve their development issues, like deadlock, heap corruption and performance problem. For difficult case we would connect to customer machine to perform remote debug. In rare case our engineer would travel to customer site to do onsite support.</span></span>

<span><span style="color: #000000;font-family: Calibri">At that time there was a special engineer title called CPR, meaning Critical Problem Resolver. Only very few engineer were entitled for that. Any problem that other engineers could not solve, CRP takes it and solves it.</span></span>

<span><span style="color: #000000;font-family: Calibri">In the earliest stage of my career I worked closely with a CPR who was considered the youngest CPR. Getting mentoring from him is still the most important career experience for me. Some small trick I learned from him 10+ years ago still shocks my team members today when I demonstrated it. </span></span>

<span><span style="color: #000000;font-family: Calibri">The story I want to tell today is not about him. I heard the story from him. It’s a story of another legendary CPR. I don’t remember the name of the protagonist.</span></span>

<span><span style="color: #000000;font-family: Calibri">It was around year 2000. A traditional company decided to build their IT solution by using Microsoft tech stack. At that time, only banks and ISP would invest on IT infrastructure. The common IT infrastructures were mainframe system from IBM or SUN, based on UNIX like Solaris. Obvious this was a big bet for that customer. The customer signed a contract to use Microsoft consultant to help build the service.</span></span>

<span><span style="color: #000000;font-family: Calibri">In the pre-production test, the new developed server application hit random crash. The same application worked fine in dev environment. When running on production environment it crashed in different places. Our support team had been following up for weeks, but not clue for the root cause. The customer was losing confidence on Microsoft. With the deadline approaching, we sent our CPR to customer’s site.</span></span>

<span><span style="color: #000000;font-family: Calibri">Our CPR engineer sit on customer site, debugged day and night, analyzed tons of dumps, and tried different approaches. After several days, he (or she) told the customer: you got a bad hardware, contact the vendor to replace your motherboard.</span></span>

<span><span style="color: #000000;font-family: Calibri">The customer was angry: “you cannot find your bug and you want to blame the hardware?” At that time, hardware vendor would not replace it for no reason. More importantly, shipping new hardware from USA takes time (computers were considered high tech, not made in China). If it would not fix the problem, we miss the project deadline.</span></span>

<span><span style="color: #000000;font-family: Calibri">The customer eventually agreed to replace the hardware, but with a condition. If the problem is not related to the hardware, Microsoft should refund all the cost for this project including the consultant cost.</span></span>

<span><span style="color: #000000;font-family: Calibri">The CPR explained the situation to the team leader. The team leader and other Microsoft leadership decided to support the CPR and agreed customer’s condition. </span></span>

<span><span style="color: #000000;font-family: Calibri">Customer makes the order. The new hardware arrives. Power on, running for days, and the problem never happens. This is the end of the story.</span></span>

<span><span style="color: #000000;font-family: Calibri">The story left a seed in my heart. I was wondering, if someday, I could be so skillful to tell from software bug and hardware bug in my debugging session. I could be so confident to ask customer to replace hardware when software crashes. Would I have the courage to trust the team’s judgement in similar situation?</span></span>

<span><span style="color: #000000;font-family: Calibri">A decade passed. I am still in Microsoft but I am not in support role any more. I am working on Azure Compute team. We manage all Microsoft server asset like Bing.com and Azure. The whole cloud infrastructure is deployed and managed by my team, and we have our service code run on every server machines.</span></span>

<span><span style="color: #000000;font-family: Calibri">10+ years ago, the support team I worked was so worried about that single customer. We wanted the software running healthy, and any single crash mattered. Today, our team’s service is supporting every Microsoft cloud customer. A single crash still matters. With modern instrumentation, we know any crash almost immediately. For the past years I debugged crash dumps many times. I also introduced some bug that almost caused global outage. I saw disk corruption and memory corruption. I saw outlier crash callstack. From analysis some of them were likely caused by bad hardware issue. But I have not caught hardware failure in debugger lively and directly, until last weekend, 11-11-2017.</span></span>

<span><span style="color: #000000;font-family: Calibri">The following is my analysis on a live debug. I think I observed a hardware failure which caused software crash. The debugging process was not tricky, but I still feel so good and peaceful.</span></span>

<span><span style="color: #000000;font-family: Calibri"> </span></span>

<b><u><span><span style="color: #000000;font-family: Calibri">Problem Description:</span></span></u></b><span></span>

<span><span style="color: #000000;font-family: Calibri">GetAndXXXXXXX started constantly crashing on SXXPrdXXXXX XXXXXX-c125-XXXX-8202-513e917c5ddc since 2017-11-06 03:40:39.450 UTC.</span></span>

<span><span style="color: #000000;font-family: Calibri">The same binary is running everywhere without any issue and we verified the binary running on the problematic machine is not corrupted.</span></span>

<span><span style="font-family: Calibri"><span style="color: #000000"> <b><u>Dump analysis:</u></b></span></span></span>

<span><span style="color: #000000;font-family: Calibri">From mini dump we learned that the crash is Access Violation in string operation. Since this is AMD64 with optimization turned on, we could not confirm the actual broken pointer.</span></span>

<span><span style="color: #000000;font-family: Calibri">Since this is consist repro, we setup live debug.</span></span>

<span><span style="color: #000000;font-family: Calibri">From the live debug, the crash happens in inside this function call. A bad pointer was passed into the AppendF call.    </span></span>

<span><span style="color: #000000;font-family: Calibri">(line 1658)   serviceListIniBuffer.AppendF("<b>ServerList=%s\r\n</b>", m_newServiceList.config-&gt;GetStringParameter("Manifest", "ServerList", "").c_str());</span></span>

<span><span style="color: #000000;font-family: Calibri">We set breakpoint just at above line and got:</span></span>

<span><span style="color: #000000;font-family: Calibri">00007ff6`36961932 488b00          mov     rax,qword ptr [rax]
00007ff6`36961935 4c8bc0          mov     r8,rax
00007ff6`36961938 488d1541270b01  lea     rdx,[00007ff6`37a14080]
00007ff6`3696193f 488d4d20        lea     rcx,[rbp+20h]
&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; 00007ff6`36961943 e8981ef3ff      call    GetAndXXXXXXX!YYYYY&lt;127&gt;::AppendF (00007ff6`368937e0)</span></span>

<span><span style="color: #000000;font-family: Calibri"> </span></span>

<span><span style="color: #000000;font-family: Calibri">From there we know (we push parameter from right to left. this pointer goes to RCX.)</span></span>

<span><span style="color: #000000;font-family: Calibri">r8 -- output of m_newServiceList.config-&gt;GetStringParameter
<b>rdx -- should be hardcoded string "ServerList=%s\r\n"</b>
rcx -- this pointer to serviceListIniBuffer</span></span>

<span><span style="color: #000000;font-family: Calibri"> </span></span>

<span><span style="color: #000000;font-family: Calibri">The memory info for these:</span></span>

<span><span style="color: #000000;font-family: Calibri">0:000&gt; da r8
00000001`0a3a48c0  "25.66.164.187:25.66.164.4:25.66."
00000001`0a3a48e0  "164.251:25.66.165.4"
0:000&gt; da rdx
00007ff6`37a14080  "????????????????????????????????"
0:000&gt; dc rcx</span></span>

<span><span style="color: #000000;font-family: Calibri">00000001`075be190  00000239 00000000 0a3a5710 00000001  9........W:.....
00000001`075be1a0  3b0a0d3b 69685420 69662073 6920656c  ;..; This file i
00000001`075be1b0  65672073 6172656e 20646574 47207962  s generated by G</span></span>

<span><span style="color: #000000;font-family: Calibri">From above, we know the bad pointer is the 1<sup>st</sup> parameter. Interestingly, the 1<sup>st</sup> parameter is actually hardcoded string “<b>ServerList=%s\r\n</b>”. How could this be corrupted!</span></span>

<span><span style="color: #000000;font-family: Calibri"> If we look carefully, the rdx value is 00007ff6`37a1408. <b>Let’s flip a bit and change it to</b>: 00007ff6`36a14080, we get:</span></span>

<span><span style="color: #000000;font-family: Calibri">0:000&gt; da 00007ff6`36a14080
00007ff6`36a14080  "<b>ServerList=%s.</b>."</span></span>

<span><span style="color: #000000;font-family: Calibri"> This is 1 bit corruption. </span></span>

<span><span style="color: #000000;font-family: Calibri"> </span></span>

<b><u><span><span style="color: #000000;font-family: Calibri">Conclusion:</span></span></u></b><span></span>

<span><span style="color: #000000;font-family: Calibri">We high suspect this is hardware failure. My guess there is a CPU/Memory/BUS failure that when a specified memory sequence is loaded, a bit get corrupted. That’s the explanation I can think about why the same application fails all the time at the same place.</span></span>

<span style="font-family: Calibri"><span style="color: #000000"><span>BTW, the memory address does not change because the string is loaded from EXE, not a DLL. EXE always maps to the same start address.</span><span> </span></span></span>

<span><span style="color: #000000;font-family: Calibri"> </span></span>]]></content:encoded>
    </item>
    <item>
      <title>软件测试随想</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/23</link>
      <pubDate>Mon, 19 Nov 2012 00:02:25 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2012/11/19/23/</guid>
      <description><![CDATA[今天看到一则有意思的题目：
http://www.weibo.com/1401880315/z5UBR5NN6
对我来说，这类问题和讨论我已经自问自答地探讨过无数次，以至于除了问题本身外的讨论都显...]]></description>
      <content:encoded><![CDATA[<p>今天看到一则有意思的题目：</p>
<p>http://www.weibo.com/1401880315/z5UBR5NN6</p>
<p>对我来说，这类问题和讨论我已经自问自答地探讨过无数次，以至于除了问题本身外的讨论都显得无聊了。</p>
<p>以前我把我整理的思路总结成了一篇pdf叫做&ldquo;软件测试随想&rdquo;。我知道里面的观点在很长一段时间都不可能被接受，所以我写下来后从来都没有发表过。反正我现在也不干test了，不妨今天放上来，请见本帖附件。</p>
<p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/10/36/97/75/6F8FF64E4B6DD58B8F96F360.pdf" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-10-36-97-75/_6F8FF64E4B6DD58B8F96F360_.pdf">软件测试随想.pdf</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Web服务的性能，和BCrypt性能问题的解决方法</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/webbcrypt</link>
      <pubDate>Sat, 24 Dec 2011 11:54:21 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/12/24/webbcrypt/</guid>
      <description><![CDATA[在昨天的研究中，发现BCrypt最大的隐患在于性能。BCrypt的安全性是通过牺牲性能来获取的。BCrypt比传统hash+salt要安全一万倍，但是代价是使用BCrypt做认证对比密码和密文时候，性...]]></description>
      <content:encoded><![CDATA[<p><span>在<a href="http://blogs.msdn.com/b/lixiong/archive/2011/12/25/md5-sha1-salt-bcrypt.aspx">昨天的研究</a>中，发现</span>BCrypt<span>最大的隐患在于性能。</span>BCrypt<span>的安全性是通过牺牲性能来获取的。</span>BCrypt<span>比传统</span>hash+salt<span>要安全一万倍，但是代价是使用</span>BCrypt<span>做认证对比密码和密文时候，性能也比</span>hash+salt<span>要慢一万倍。</span></p>
<p><span>所以，我得出一个结论。如果使用传统</span>hash+salt<span>需要</span>N<span>台认证服务器的话，那用</span>BCrypt<span>就需要添加</span>10000*N<span>台服务器才能达到同样的性能。比如一个邮件系统使用了</span>2<span>台服务器来专门作认证，那使用</span>BCrypt<span>的话就需要再购买</span>2<span>万台。当然，小的应用，如果使用一台服务器</span>1%<span>的性能就可以做完认证的话，使用</span>BCrypt<span>只需要</span>100<span>台服务器。</span></p>
<p><span>这个听起来比较吓人，但是我通过另外一种计算方法，也能得出同样的结果。比如说</span>163<span>的邮件系统，峰值的时候每秒钟可能需要处理</span>10000<span>个登陆请求。比如每天早上</span>9<span>点到</span>9<span>点半之间，很多人都会登陆邮件系统。按照</span>BCrypt<span>提到的，每个请求需要花费</span>0.3<span>秒，那么处理</span>10000<span>个请求需要</span>3000<span>秒。这显然是不能接受的。如果要让客户都能在</span>1<span>秒钟登陆，那就需要添加服务器，把登陆请求均分到各个服务器上。对于单</span>CPU<span>的服务器，就一共需要</span>3000<span>台。</span></p>
<p><span>我把上面的想法和网友讨论后，得到了不同的意见。有人觉得</span>0.3<span>秒的处理时间其实不多阿，因为通常一个</span>web<span>请求，内存，磁盘数据库转一圈下来，离</span>0.3<span>秒也差不多了吧，所以</span>BCrypt<span>的</span>0.3<span>秒的开销是一个正常值，不会带来什么问题。有人提到普通</span>web<span>服务器每秒处理</span>1000<span>个请求是很常见的。如果按照我上面的计算方法，这就表示每个请求在</span>1ms<span>内完成了，这听起来有点不可能嘛。</span></p>
<p><span>根据上面的意见，我又仔细思考了一下。我想借这个话题，先讨论一下</span>Web<span>服务器的性能，再看看</span>BCrypt<span>带来的问题应该如何解决。</span></p>
<p><span>注意，下面所叙述的，是根据我现有知识和经验做的逻辑推理。我没有写半行</span>web<span>程序的</span>code<span>来验证，我也没有任何大规模</span>web<span>项目的经验。但是我非常喜欢先做这样&ldquo;纸上谈兵&rdquo;的推理，因为这样可以让我先仔细想清楚后再行动。</span></p>
<p><span>网页的加载时间，和单个</span>request<span>的处理时间是不一样的。单个网页中往往包含多个子元素，比如图片和</span>AJAX<span>调用。这些子元素的加载都会在额外的</span>request<span>里面完成。另外网页加载的时间除了</span>web<span>服务器的运算处理时间外，还有数据在网络上的传输时间，脚本运行时间，浏览器绘制时间，当然如果是第一次访问，还包括</span>DNS<span>查询和</span>TCP<span>握手时间等等。现在主流网站的页面加载时间都在</span>2<span>秒以内，每个页面大概包含</span>10-50<span>个子元素。客户端脚本和浏览器的处理占用了大概一半时间，所以跟服务器相关的时间大概就是</span>1<span>秒。由于部分子元素可能在浏览器中有</span>cache<span>，所以这</span>1<span>秒钟对应的大概就是</span>5-25<span>个请求。这样算下来每个请求大概在</span>200-40<span>毫秒之间。这里面大概</span>web<span>服务器的运算开销可能只有</span>25%<span>，那大概就是</span>50ms<span>到</span>10ms<span>之间。注意，</span>50ms-10ms<span>这个估算是指平均时间。</span></p>
<p><span>由于网络传输和客户端导致的开销不是今天的讨论范围，所以下面就只讨论</span>web<span>服务器运算一个</span>request<span>需要怎样的开销。处理一个</span>web request<span>，通常牵涉到下面一些工作。输出流添加，数据库读写，文件读写，复杂计算。输出流添加是最常见的，比如服务器会把</span>Response.Write<span>里面的内容加入到</span>Response<span>里面。输出流添加是非常快的。速度和</span>string.format<span>或者</span>StringBuilder.Append<span>应该在一个数量级。肯定比毫秒要快多个数量级。复杂计算是指需要</span>CPU<span>密集超过</span>50ms<span>的运算，比如正则表达式运算和</span>BCrypt<span>操作。数据库读写和文件读写所需要的时间往往也超过</span>50ms<span>，但是这两者不一定需要占用</span>web<span>服务器的</span>CPU<span>资源。</span></p>
<p><span>由此，在考量</span>web<span>服务器运算一个</span>request<span>所需的开销的时候，先看这个</span>request<span>是不是需要复杂运算。如果需要，那么这个复杂运算肯定是一个瓶颈。</span></p>
<p><span>那么数据库和文件操作是不是瓶颈呢。从处理</span>request<span>所需的时间上来说，这两者肯定也是瓶颈。因为再快也得先等到这两者返回。但是和复杂运算相比，复杂运算损害的不单单是</span>request<span>所需的时间，另外还损害了服务器的可伸缩性。</span></p>
<p><span>由于数据库和文件操作不占用</span>web<span>服务器的</span>CPU<span>，所以</span>web<span>服务器在等待数据库和文件操作结果的时候，可以把</span>CPU<span>资源用来处理新的</span>request<span>。比如来了</span>100<span>个请求，每个请求都需要</span>2<span>秒钟的数据库操作，不等于说服务器需要</span>200<span>秒才能处理完所有请求的。因为服务器可以让</span>100<span>个请求都进来，然后发送者</span>100<span>个请求的数据库操作，然后让这</span>100<span>个请求同时等待。</span>2<span>秒钟以后，这</span>100<span>个数据库操作的结果都返回回来后，服务器可以把这</span>100<span>个请求的结果都返回了。</span></p>
<p><span>但如果请求中牵涉到了复杂运算呢，服务器就没办法并发了。如果来了</span>100<span>个请求，每个请求都需要运算</span>2<span>秒钟，而服务器如果只有一个</span>CPU<span>的话，那就一定需要</span>200<span>秒。</span></p>
<p><span>由此可见，请求的平均处理时间和服务器的吞吐量不是简单的比例关系。这牵涉到这个请求具体是如何处理的。如果服务器的吞吐量是</span>1<span>秒钟</span>1000<span>个请求，不能简单地用</span>1000<span>毫秒除以</span>1000<span>，得到每个请求只需要</span>1<span>毫秒来处理。这个运算法则只在没有任何并发可能性的情况下才有效。这也在此证明了，合理使用异步调用，对服务器性能会有多大的提高。</span></p>
<p><span>接下来反过来想，对于一个不涉及数据库，不涉及文件，不涉及复杂运算的请求，合理的处理时间应该是多少呢。这个处理时间其实等同于同等复杂程度的函数调用时间。换句话说，如果只是计算出一个几十</span>k<span>的页面，花费的时间肯定在</span>1ms<span>的数量级。你肯能觉得这里没有考虑</span>web<span>服务器的&ldquo;仪式性&rdquo;开销，比如说处理</span>TCP<span>报文，分析</span>HTTP<span>头等等。其实，主流的</span>web<span>服务器在这方面已经足够优化，比如从</span>IIS6<span>开始，对于</span>HTTP<span>协议的处理都直接在内核里面做了，所以就算把这些开销都算上，也能做到</span>1ms<span>的数量级。对于计算机来说，</span>1ms<span>其实很长的，</span>1ms<span>表示一秒内调用函数</span>1000<span>次而已。不信你可以估算一下下面的代码，循环了</span>10000<span>次，大概需要多少时间：</span></p>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="590" valign="top">
<p><span>StringBuilder</span><span>&nbsp;dummy =&nbsp;<span>new</span>&nbsp;<span>StringBuilder</span>();</span></p>
<p><span>for</span><span>&nbsp;(<span>int</span>&nbsp;i = 0; i &lt; 10000; i++)</span></p>
<p><span>{</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dummy.AppendLine((<span>string</span>.Format(<span>"this is {0}"</span>, i)));</span></p>
<p><span>}</span></p>
<p><span>var</span><span>&nbsp;len = dummy.Length;</span></p>
<p>&nbsp;</p>
</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><span>另外，上诉的讨论还没有考虑一个非常重要的武器</span>:Cache<span>。如果使用</span>cache,&nbsp;<span>整个页面可能根本不需要任何运算，都不需要跑一行处理代码，</span>web<span>服务器就把结果给返回了。如果大量使用</span>cache<span>，别说一千次，一秒钟处理上十万次都可以。大多数的网站已经广泛使用</span>cache<span>了，图片是</span>cache<span>的，</span>blog<span>文章的内容是</span>cache<span>的，甚至评论都是</span>cache<span>的。正因为如此，把包含复杂运算，数据库和文件处理的慢操作跟带</span>cache<span>的块操作一平均，结果落到前面估算的</span>50ms-10ms<span>是很自然的事情。</span></p>
<p><span>有了上面的分析后，再来看文章前面的两个问题。第一个问题是，</span>BCrypt<span>的</span>300ms<span>开销，和数据库，磁盘文件兜一圈下来的开销也差不多阿，为什么说</span>BCrypt<span>就更严重呢。原因是</span>BCrypt<span>的</span>300ms<span>都压在了</span>CPU<span>上面，而人家的是可以并发的。当然，动不动就要去兜数据库和磁盘文件的</span>web<span>应用，设计上也有问题的。第二个问题是，我的服务器可以一秒钟处理</span>1000<span>个请求，按照原来的计算方法，就是</span>1ms<span>处理</span>1<span>个了，这个不可能吧。这个问题就要区分情况来回答了。首先，</span>1ms<span>处理</span>1<span>个请求是可能的，比如使用了</span>cache<span>，绝对能。其次，如果考虑并发的话，的确不能简单做除法。但问题关键是，</span>BCrypt<span>是不支持并发的阿。</span></p>
<p><span>所以，</span>BCrypt<span>最大的隐患的确就是在性能上。我所做的下列判断是正确的：&ldquo;如果使用传统</span>hash+salt<span>需要</span>N<span>台认证服务器的话，那用</span>BCrypt<span>就需要添加</span>10000*N<span>台服务器才能达到同样的性能&rdquo;。</span></p>
<p><span>接下来的问题是，</span>BCrypt<span>性能问题有解决办法么。在前面的逻辑推理中，我想到了一个最常见的办法：用空间换时间。我可以在内存中建立</span>cache<span>，把密码原文和用</span>BCrypt<span>计算后的</span>hash<span>缓存起来。这样每次处理认证请求的时候，就不需要计算了。当然，这里其实带来了新的安全隐患。如果有人盗窃了服务器的内存转储</span>(memory dump),&nbsp;<span>那么就能看到</span>cache<span>里面的东西，那可就是密码的原文明文阿。这有何去何从呢？</span></p>
<p><span>引起这片文章的讨论来源</span>:</p>
<p><a href="http://weibo.com/1709648133/xDtMxivam">http://weibo.com/1709648133/xDtMxivam</a></p>
<p><a href="http://weibo.com/1709648133/xDy7YjYX0">http://weibo.com/1709648133/xDy7YjYX0</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Retrospect different Microsoft technologies from Win8</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/retrospect-different-microsoft-technologies-from-win8</link>
      <pubDate>Mon, 05 Dec 2011 07:35:00 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/12/05/retrospect-different-microsoft-technologies-from-win8/</guid>
      <description><![CDATA[The Chinese version is&nbsp;available&nbsp;here...]]></description>
      <content:encoded><![CDATA[<p>The Chinese version is&nbsp;available&nbsp;here (中文链接在):<br />http://blogs.msdn.com/b/lixiong/archive/2011/12/03/win8.aspx</p>
<p></p>
<p>Learning how things work internally has always been my favorite. The first thing I did was to launch debugger when I got Win8 installed. Based on my research, Win8 is an amazing product. There are many old and new technologies involved in Win8. I&rsquo;d like to use this blog to retrospect different Microsoft technologies.</p>
<p>Please note, the following analysis was based on Win8 CTP. I expect many changes when Win8 gets to RTM.</p>
<p>This above information is just my person opinions.</p>
<p><strong>COM -Component Object Model</strong><b>&nbsp;</b></p>
<p>COM is a great technology invented in mid 90&rsquo;s. COM was indented to address the reuse of software components. Prior to COM, software reuses were usually done at source code level. The C/C++ library was a typical example. Source code level requires recompilation, and it is difficult for distribution. COM made it possible to reuse software components at binary level. Starting from Win95, OLE and Office95, COM has been a cornerstone of Microsoft platform. From DirectX API to the most common clipboard, and even the host interface of .NET Framework are all based on COM. But, there is no technology which is good for everything. The problem of COM is the following: <strong><span style="text-decoration: underline;"></span></strong></p>
<p><strong><span style="text-decoration: underline;">The threading model like STA/MTA/NTA is over complex</span></strong></p>
<p>The threading model of COM was designed to help developer. &nbsp;The implementation of threading model in COM Runtime was based on many system components like Win32 Message, RPC and Windows Services. COM developers must be familiar with these components if they want to use COM correctly. &nbsp;For example, a developer must understand the message loop and maintain it for any thread that hosts STA, otherwise deadlock is likely to happen. The complexity of threading model was a blocker for many developers who wanted to use COM at expert level.</p>
<p><strong><span style="text-decoration: underline;">Lack of development tool support</span></strong></p>
<p>The relationship between COM and Visual Studio 6 is very similar as CLR2/VS2005, CLR3.5/VS2008 and CLR4/VS2010 nowadays. You had to choose either VB6 or ATL to create COM component with VS6. VB6 was good for business applications, especially the popular MIS system at that time. But VB6 was not powerful and flexible enough. Due to the lack of muti-threading support, there was no way to use MTA in VB6. ATL was powerful and flexible, but it was difficult to use. Every time you want to extend an interface, you had to do a lot of ceremony works like using muti-inheriency, defining new templates and using lots of C macros. It was never a pleasure of using ATL.</p>
<p><strong><span style="text-decoration: underline;">The complexities were out of control when it expanded to DCOM, COM+, and DTC</span></strong></p>
<p>To fit into enterprise development, COM was extended and evolved to DCOM and COM+. &ldquo;Enterprise development&rdquo; means the requirements of security and scalability. The classic COM was an in-process model, in which the caller and callee ran under the same user account. Using impersonation was helpful, but a completed solution was to make different components run under different security boundaries. DCOM was born to make COM component possible to run from a different process, run from a different computer, run with different security levels for different interfaces, authenticate with domain account, and support different levels of encryption. You can click start, run and type dcomcnfg.exe. Try to expand some nodes and open the property page to see how complex it could be. For scalability, Microsoft further extended DCOM to COM+ by introducing object pooling and new sync model. The most famous COM+ based solution was ASP, which dominated the web development for decades on Windows platform, and eventually evolved to ASP.NET. Furthermore, Microsoft built DTC and MSMQ which were also based on COM+. You may not be aware of the niubility of DTC. DTC is short for Distributed Transaction Coordinator. It allowed you to run SQL statement to operate different database systems (like Oracle and MSSQL) under the same transaction without writing special code. This feature was super important when MSSQL competed with Oracle/DB2 at earlier days. These things made COM more horrible to use especially when development tools did not catch up.</p>
<p><strong>.NET Framework/CLR</strong></p>
<p>In my mind, CLR is flawless in almost all areas. But just because CLR was so good, Microsoft reduced the investment on the un-mamanged components starting from 2003.</p>
<p>Spreading and pushing CLR is never bad at all. Most developers write CRUD and UI code. You cannot let them to use C/C++ all the time. CLR is performant, the development tools are strong, and every new version brings huge improvements. But, there is a theory that has always been true: it is possible to write a C# application to get similar performance as using C/C++, but it costs more than using C/C++ at the first place. There are many reasons: the underlying API is unmanaged and the interop hits performance; the C# cannot control the details as much as C++; the JIT compiler has to generate additional code to achieve the CLR exception model and security model; this pointer is compared to test NullReferenceException as a cost before use. The situation is, there are many applications which still require performance strictly, but they were ignored during the fast development of CLR. From the years between Windows Server 2003 and Windows Server 2008 R2, the platform, API and development tools for unmanaged application had no equal improvement as CLR. This may not be a problem for Windows+PC platform, but you cannot expect people write mobile application with C#. The unbalance led little improvement on mobile and consumer areas for a long while.</p>
<p><strong>WPF</strong></p>
<p>WPF is a product with beautiful design. It solved 4 problems in traditional Win32 UI. 1) Win32 UI&rsquo;s rendering is Window object and GUI based. WPF used a new model with dedicated rending thread, which improved rendering performance and was able to use GPU for acceleration. 2) Win32 depends on GDI and HWND object. For complex UI application which creates many handle, it is common to get out of resource error. Many developers used tab control to reduce the usage of handle to solve this problem. WPF does not use any native child window. The control concepts are defined at an upper layer. This design avoided the massive usage of native resources. 3) Win32 relies on many low level Win32 APIs, which are not convenient to use and are difficult to learn. WPF introduced Dispatcher class and BeginInvoke pattern, which simplified these issues. 4) The data, visual and code were not well separated in Win32. WPF used data binding, XAML and code behind to make the patterns clean. It is more easy to apply modern patterns like MVC and MVVM in WPF. All of these are the beauty of design.</p>
<p>The side effort of this was over design. WPF is based on CLR, and the over design kills performance. If you are familiar with WPF, you can find half of the design patterns in textbook be used in the dependent property. WPF is an encyclopedia of design patterns. In my calculation, there are about 7 different ways to hook up a mouse click event. The 7 ways were designed to use in different situations. It is good to be flexible, but the cost of flexibility is performance. The magic of patterns and data bindings is reflection in CLR. This is why you cannot get the same sleek feelings as native Win32 UI when a WPF application gets bigger. <strong></strong></p>
<p><strong>The interoperability of Microsoft products</strong></p>
<p>Products from Microsoft are always interoperable. C# is able to invoke C++, and vice versa. Any application could embed a browser control to use the function of IE. VBScript is able to automate office applications. Management tools like MMC and Explorer expose programming interface to allow user to plug in new functions. I saw customer use VBScript generate excel at backend, send it to client with ASP.NET, use JavaScript to edit it in browser, and send back to MSSQL backend. Interoperability protects customer investment, and the magic behind the interoperability is using COM as the bridge. This leads to three issues. First, it increases the complexity due to COM as discussed previous. &nbsp;Second, it hits performance with COM interop. Last, the development experience is not good enough because of the necessary ceremony. For example, when CLR interop with COM, the CLR engine needs to build RCW/CCW, transit between security model, and marshalling data here and there. When the call happens, you can even have a sense of performance hits by the noise caused by sector movements inside the HDD.</p>
<p><strong>Win8</strong></p>
<p>It&rsquo;s time to discuss why I feel so good about Win8.</p>
<p>Win8 introduced Windows Runtime (WinRT). WinRT is an OS component, runs at user mode, between Win32 and the application, is intended to provide efficiency and friendly interfaces for developers. At the binary model, WinRT copied classic COM. There was no dependency between WinRT and CLR, and they could invoke each other. WinRT was implemented in C/C++. Performance was one aspect, and the other important aspect is projection. Projection is a new concept introduced by Win8. As the name says, it projects WinRT features to upper level programming languages to use in the most effective manner. The languages could be C#, C or JavaScript.</p>
<p>You can image projection as a new Windows API model. The classic API model was either DLL exported functions or COM interface. Both were not performant when invoking from CLR. The root cause is the incompatible memory and security models between CLR and unmanaged code. With projection, WinRT created the best way for upper language to invoke. This design avoided the cost &nbsp;and improved the programmer experience. Of course, this is also benefit by the development of Visual Studio tools, C# language and the CLR. For example, C# 5 introduced the await keyword, and WinRT introduced async operation pattern. Projection made the await statement to the WinRT async operation call without any redundant code. There was no CLR Engine involvement, no context switch, no marshaling and no proxies. It was just a simple assembly call to a stub from C# code, and then it jumped to the WinRT implementations. Please refer to Build conference to learn more about this. The callstack at the end is also vivid to explain this.</p>
<p>WinRT took the good part of COM as an efficiency and lightweight binary reuse model, and got rid of the complexity to be omnipotent. It let the upper programming languages and tools to achieve the flexibility. With projection, the performance issue got solved for interoperability. Microsoft changed the plan to use C/C++ to create a performant WinRT, and let CLR to do upper layer work. C/C++ is good at performance and bad with development cost. C# and CLR is opposite. Win8 makes the best of both.</p>
<p>This is why I think in Win8, different technologies are now in its best places. It is obvious that how Metro could fix WPF&rsquo;s problem: The rendering model and XAML are continued in Win8, but the infrastructures and controls are moved to WinRT with native implementation. It fits for C and JavaScripts, and the upper level is flexible to use any design patterns.<strong> </strong></p>
<p><strong>Appendix</strong></p>
<p>I attached some exemplary call stacks in Win8 here:</p>
<p>Stack1:</p>
<p>Windows_UI_Immersive!`Windows::Internal::CMessageDialog::ShowAsync'::`50'::&lt;lambda_32D66FEFF293CE6B&gt;::&lt;lambda_32D66FEFF293CE6B&gt;</p>
<p>Windows_UI_Immersive!Windows::Internal::CMessageDialog::ShowAsync+0x1a0</p>
<p>image08ee0000!DomainNeutralILStubClass.IL_STUB_CLRtoCOM()+0x8c</p>
<p>application1!Application1.MainPage+&lt;button_Click&gt;d__0.MoveNext()+0xcd</p>
<p>application1!Application1.MainPage.button_Click(System.Object, Windows.UI.Xaml.RoutedEventArgs)+0x80</p>
<p>stack2:</p>
<p>combase!CComActivator::DoCreateInstance+0x121</p>
<p>combase!CoCreateInstanceEx+0x51</p>
<p>combase!CoCreateInstance+0x65</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_TryToUnregisterForIHMNotifications+0x3b</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_HideWindow+0x31</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::HideWindow+0x9</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::DestroyWindow+0x24</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindow::Destroy+0x57</p>
<p>Windows_UI_Immersive!Windows::Internal::CClosePopupCommandHandler::Invoke+0xe 0x73</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_OnButtonPress+0xc0</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::OnMessage+0x18b</p>
<p>DUI70!DirectUI::NativeHWNDHost::WndProc+0x73</p>
<p>USER32!InternalCallWinProc+0x23</p>
<p>USER32!UserCallWinProcCheckWow+0x100</p>
<p>USER32!DispatchMessageWorker+0x3d4</p>
<p>USER32!DispatchMessageW+0x10</p>
<p>Windows_UI_Immersive!SHProcessMessagesUntilEventsEx+0xe2</p>
<p>Windows_UI_Immersive!Windows::Internal::PopupWindowOperation::Show+0x103</p>
<p>Stack3:</p>
<p>Windows_UI_Xaml!HWWalk::RenderChildren+0x7a</p>
<p>Windows_UI_Xaml!HWWalk::RenderContentAndChildren+0x2d1</p>
<p>Windows_UI_Xaml!HWWalk::Render+0x61e</p>
<p>Windows_UI_Xaml!HWWalk::RenderChildren+0x7a</p>
<p>Windows_UI_Xaml!HWWalk::RenderRoot+0x1c4</p>
<p>Windows_UI_Xaml!CCoreServices::NWDrawTree+0x698</p>
<p>Windows_UI_Xaml!CCoreServices::NWDraw+0x1d6</p>
<p>Windows_UI_Xaml!CRenderTarget::Draw+0x13</p>
<p>Windows_UI_Xaml!CWindowRenderTarget::Draw+0x40</p>
<p>Windows_UI_Xaml!CXcpBrowserHost::OnTick+0x88</p>
<p>Windows_UI_Xaml!CXcpDispatcher::Tick+0x184</p>
<p>Windows_UI_Xaml!CXcpDispatcher::OnReentrancyProtectedWindowMessage+0x133</p>
<p>Windows_UI_Xaml!CXcpDispatcher::ProcessMessage+0xa4</p>
<p>Windows_UI_Xaml!CXcpDispatcher::WindowProc+0x69</p>
<p>USER32!InternalCallWinProc+0x23</p>
<p>USER32!UserCallWinProcCheckWow+0x100</p>
<p>USER32!DispatchMessageWorker+0x3d4</p>
<p>USER32!DispatchMessageW+0x10</p>
<p>windows_ui!Windows::UI::Core::CDispatcher::ProcessMessage+0xc7</p>
<p>windows_ui!Windows::UI::Core::CDispatcher::ProcessEvents+0x6c</p>
<p>Windows_UI_Xaml!CJupiterWindow::RunCoreWindowMessageLoop+0x3b</p>
<p>Windows_UI_Xaml!CJupiterControl::RunMessageLoop+0x1d</p>
<p>Windows_UI_Xaml!CJupiterControlLight::RunMessageLoop+0x8</p>
<p>Windows_UI_Xaml!DirectUI::DXamlCore::RunMessageLoop+0x15</p>
<p>Windows_UI_Xaml!DirectUI::ViewProvider::Run+0x11</p>
<p>twinapi!Windows::ApplicationModel::Core::CoreApplicationView::ViewProviderThreadProc+0x27</p>
<p align="right"><em>This is the end</em></p>
<p>&nbsp;</p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>从Win8回顾微软平台的各种技术</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/win8</link>
      <pubDate>Fri, 02 Dec 2011 22:17:00 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/12/02/win8/</guid>
      <description><![CDATA[我安装好Win8 CTP后做的第一件事情就是用调试器研究Win8各个组件的协作关系. 从我半天的研究结果看来, Win8真是一个让我爱不释手的产品. Win8里面涉及到的很多技术正好也是我的兴趣所在....]]></description>
      <content:encoded><![CDATA[<p>我安装好Win8 CTP后做的第一件事情就是用调试器研究Win8各个组件的协作关系. 从我半天的研究结果看来, Win8真是一个让我爱不释手的产品. Win8里面涉及到的很多技术正好也是我的兴趣所在. 这篇文章简单回顾一下这些技术的变迁, 优缺点, 和对Win8的影响.</p>
<p>注意, 下面提到的对Win8的分析, 是基于公开的Win8 CTP来做的. 相信Win8面世的时候, 这些技术和细节, 都会发生重大改变. 所以这篇文章不具备实践上的指导价值.</p>
<p><b>COM -Component Object Model </b><b>通用组件模型</b><b></b></p>
<p>COM是上个世纪中期设计出来的伟大产品. COM旨在解决软件复用的问题. 在COM以前, 大家都是用代码级别的复用, 常见的就是C/C++的库, 无论是原代码库还是lib库, 都是需要编译后才能重用的. COM使得技术人员可以在二进制上进行复用. 从Win95, OLE32和Office95系列开始, COM就是微软平台上的一个技术基石, 无论是DirectX API, 还是最常见的剪贴板, 以及后来.NET Framework的host接口, 都离不开COM. 但任何伟大的产品, 都有局限的一面. COM在局限性在下面一些地方</p>
<p><b><span style="text-decoration: underline;">STA/MTA/NTA</span></b><b><span style="text-decoration: underline;">等等线程模型过于复杂</span></b></p>
<p>线程模型, 特别是STA, 设计的目的是方便使用者. 但COM的线程模型严重依赖于太多系统组件, 比如Win32 Message, RPC和Windows系统服务, 使得程序员需要熟悉和了解太多系统知识才可以正确地使用线程模型. 否则用STA导致死锁简直就是家常便饭.</p>
<p><b><span style="text-decoration: underline;">开发工具没有提供足够支持</span></b></p>
<p>COM和Visual Studio 6.0的关系, 就如同现在CLR2/VS2005, CLR3.5/VS2008和CLR4/VS2010的关系一样铁. 使用COM开发, 当时的选择要么是VB6, 要么用ATL. 这两者都有天生的局限. VB6适合企业开发, 特别是当时流行的MIS系统, 数据库系统这样的CS应用, 但是VB6不够灵活. 而且VB6里面由于缺少多线程支持, 无法用MTA的模型. ATL功能强大, 足够灵活, 但是使用起来特别复杂. 每次实现一个接口, 都要做一大堆C++的仪式性工作, 比如实现多重继承, 定义新的模板, 使用大量的C宏. 神经再粗大的程序员, 都经不起这样用C++的.</p>
<p><b><span style="text-decoration: underline;">无止境地扩充到</span></b><b><span style="text-decoration: underline;">DCOM, COM+, DTC, MSMQ</span></b><b><span style="text-decoration: underline;">以及后来的</span></b><b><span style="text-decoration: underline;">.NET Remoting/WCF, </span></b><b><span style="text-decoration: underline;">使得最后的复杂度无法控制</span></b></p>
<p>为了更好地适应企业级别的开发, COM被进一步延伸和演化成了DCOM和COM+. 所谓"企业级别", 其实是指对安全性和可伸缩性的更高要求. 经典的COM是一个进程内模型, 无法让不同的代码运行在不同的帐号下的, 因为同一个进程只能启在唯一的帐号下. 虽然通过impersonate等方法可以适当地解决问题, 但是为了让安全模型更为全面, 正确的做法是让不同的接口实现, 能够跨越进程甚至跨越机器等安全边界运行, 要能够赋予不同的接口不同的安全级别, 能够和域帐号集成, 支持不同等级的加密等等.远程通用组件模型, 也就是DCOM就这样诞生了. 读者可以尝试运行以下dcomcnfg.exe这个工具, 展开一些节点, 看看属性页, 就能体会到DCOM的功能是多么让人眼花缭乱了. 对于可伸缩性, 微软更上一层楼, 在DCOM的基础上加入了对象池和新的同步模型做成了COM+. 风靡十年的ASP, 就是运行在COM+框架下的最好例子. 更让人叹为观止的是, 微软把DTC和MSMQ的设计也和COM+模型绑定起来. 陌生的读者可能不了解DTC是个多么nb的东西. 简单说DTC是可以让程序员一行代码都不用写, 就让SQL Server和Oracle的数据库操作运行在同一个事务边界里面. 可以想象, 在MSSQL Server刚进入市场的时候, 微软推出这样的功能, 对于抢占Oracle的市场份额有多么重要. DTC和COM+当时成为了很多大公司的标准配置, 以至于后来设计.NET Remoting和WCF的时候, 都还是要考虑对DTC的支持. 这些强大而且复杂的功能, 让本来就复杂的COM更加恐怖. 这样的复杂度虽然也体现了当时的市场需求, 但由于缺乏配套的开发工具, 新的开发语言和更优美的抽象, 使得这条路最后越走越窄.</p>
<p><b>.NET Framework/CLR</b><b></b></p>
<p>在我眼中, CLR的各方面简直是无可挑剔的. 但可能正是因为CLR太好了, 让微软从2003年开始, 对unmamanged world的投资就不大了.</p>
<p>为了争取企业客户, 全力推广CLR是最正确的做法. 毕竟绝大多数的程序员, 一辈子都是和数据库, 和UI代码打交道. 你总不能让他们一辈子都用C去管理内容, 创建窗口句柄吧. CLR的性能也很有竞争力, 与之对应的编程语言和开发工具也非常给力, 每个新版本都带来长足进步. 但是, 这些再好, 也无法掩饰一个悖论: 要用用C#写出性能可以和C++一个数量级的程序, 不是不可能, 而是花费的代价往往比直接用C++写更大. 这里面有很多原因. 比如系统最底层的API还是unmanaged的, 通过CLR作interop的性能损失无法忽略. 比如用C#的话程序员的控制力很弱, 从工具和语言层面上很难对性能精雕细作, 一不注意就box/unbox了. 比如JIT编译器为了实现自己的安全模型和异常处理, 每次访问成员函数的都是都要生成代码确认this指针是否为空. 这个世界上还是有很多程序, 是需要对性能做严格控制的. 在CLR高速发展的几年中, 对应的系统平台, Win32 API, C++开发工具, 只有完善性的改善, 并没有重大突破. 这个问题对Windows平台本身影响不大, 但如果寄希望于在移动设备上, 大家都指着C#来开发, 就有点天方夜谭了. 这样的失衡, 使得微软在移动设备和消费者产品这两个严重需要unmanaged和系统投入的领域缓慢发展了很长时间.</p>
<p><b>WPF</b><b></b></p>
<p>在我看来, WPF是一个设计得很美的产品. WPF解决了传统Win32 UI程序的四大局限. 1) Win32的绘图是由各自Window元素独立控制, 基于GDI的. WPF引入了rendering thread来提高性能, 优化算法, 借用GPU加速. 2) Win32依赖于GDI Object, 在开发复杂窗口程序的时候, 很容易就遭遇资源泄露和资源不足. 比如早期的淘宝旺旺, 开到几十个窗口的时候, 程序就会出问题. 所以淘宝针对这个问题, 使用了统一控制台, 合并多个窗口到标签页的方法来解决. 而WPF只有最外面的窗口使用了Win32 Window和GDI, 内部的元素都是抽象成了WPF自己的元素, 不额外占用Win32 GDI资源的. 3) Win32窗体程序严重依赖Windows Message模型. 这要求程序员对系统知识有深入的了解. 而且Win32 API并不是非常利于使用, 比如要进行UI thread和Worker thread之间的通信, 往往需要和SendMessage这样的API打交道. 在WPF中, 引入了Dispatcher类和BeginInvoke方法, 把这些复杂问题抽象了. 加上CLR提供了更方便高效的开发环境, 使用WPF是很愉快的工作. 4) Win32缺乏数据, 设计和代码三者之间的模式抽象. 这三者在WPF中对应了数据绑定, XAML文件, 以及后台代码. 在WPF中可以更直观地使用各种模式比如MVC和MVVM. 这些都体现了WPF设计上的优美.</p>
<p>再优美的东西都还是有局限性的. WPF的问题在于过多的模式和对CLR过度的依赖. 了解WPF框架的人都知道, 就一个简单的dependent property, 就把设计模式这本书里面的模式用掉一大半了. 分析WPF框架代码的话, 简直就是看一本设计模式的百科全书. 我曾经统计过, 关于mouse click这样一个event回调, WPF里面有7种不同的实现方法, 分别各有好处, 旨在解决不同问题. 在这样高度灵活的背后, 牺牲的是程序性能. 无论是五花八门的模式, 还是最常用的数据绑定, 背后的主力都是CLR的reflection. 过度依赖于reflection导致WPF程序规模一大, 性能上就出问题. 就算再怎么优化, 也总找不到原生Win32程序那般流利的感脚. 使用reflection也体现了对CLR的依赖. 所以前面CLR的局限性, 也适用于WPF.</p>
<p><b>微软产品的互操作性</b><b></b></p>
<p>微软的产品线虽又长又多, 但是各个产品之间一定是能够互操作的. 比如C#可以和C++互相调用. 任何语言开发的程序都可以嵌入Browser Control来借用IE的功能. Office暴露了VBA接口, 通过VBScript都能够自动化Office程序. 各种管理工具无论是Explorer还是MMC, 都暴露了编程接口可以让程序员添加自己的功能. 我见过客户用ASP.NET在后台用VBScript生成Excel表格, 然后把表格嵌入在 IE浏览器中, 再使用JavaScript来帮助客户编辑, 最后提交回MSSQL数据库做完成报销功能的. 完善的互操作性充分保护了用户的投资, 使得客户对微软平台用一次就上瘾, 几乎没有不可能完成的任务. 仔细分析, 其实这些互操作有一个共性, 都是把暴露COM接口作为内部实现原理. 这个做法导致了三个局限性, 首先是牵涉到了前面提到的COM的复杂度. 其次是潜在的性能损耗. 最后是在具体开发的时候, 都需要一些仪式性的工作来引入或者定义COM接口, 使得开发过程不够自然流畅. 在CLR和COM互操作的调用栈里面, CLR的RCW, CCW, 安全处理, 列集拷贝等等, 耗费的时间带来的性能开销简直是可以到了肉眼可察觉的地步(听硬盘的声音和看任务管理器里面CPU的波动).</p>
<p><b>Win8</b><b></b></p>
<p>在讨论完这些技术背后的故事后, 再看看为啥我就对Win8爱不释手了.</p>
<p>Win8引入了Windows Runtime, 简称WinRT. WinRT是一个操作系统模块, 运行在用户态, 介于Win32的上层和应用程序的下层, 目的在于提供更高效友好的开发接口供Win8的程序员使用. WinRT在二进制模型上基本就是照搬了经典的COM. WinRT和CLR互不依赖, WinRT可以被CLR使用. WinRT通过C/C++实现, 效率高是一个方面, 更重要的时Win8引入了projection的概念, 就是可以把WinRT的API用最直接最高效的方法, 提供给上层的编程语言调用. 这个语言可以是C#, C或者JavaScript.</p>
<p>对于第一次接触Projection的朋友, 可以把Projection认为是一种新的Windows API模型. 传统的操作系统API, 要么是暴露DLL的方法, 要么是通过COM接口. 无论是哪一种, 在CLR中调用的时候都有不小的开销. 使用这些传统API的效率, 比调用一个C#自己的方法, 效率差了多个数量级, 根本的原因在于CLR的安全模型, 内存模型和传统的unmanaged模型不兼容, 所以跨越边界的调用需要额外的代码来处理. 而Projection提供的模型, 是在提供新功能的同时, 还针不同编程模型和语言, 提供了最利于它们调用的方法. 这样就主动避免了不同模型之间为了互相兼容导致的开销, 也使得程序员写代码的时候非常自然流畅, 调用的时候根本感觉不到和调用本地函数的区别. 当然, 能够实现这一点, 也是得益于CLR, C#语言和VS开发工具这十年的长足发展. 举个例子, C# 5.0中引入了await关键字, WinRT中引入了async operation. Projection技术把C#中的await语句转换为WinRT async operation的调用, 而且这个调用直接从managed code直接跳到unmanaged code, 中间没有任何冗余, 也不需要CLR Engine的介入. 进一步的信息, 可以参考Build大会关于WinRT的多个演讲. 后面的callstack也提供了直观的例子.</p>
<p>前面提到了COM的局限性在于一个轻量的二进制模型, 被硬生生的扩展成一个无所不能的框架. WinRT取其精华, 去其糟粕, 借用了COM的轻便, 舍弃了复杂性, 在扩展性上依托于上层的编程语言和工具. WinRT通过projection的技术解决了传统互操作性效率不高使用不方便的问题. 以前的路线是希望所有的产品和技术最后都统一到CLR上来. 现在修正为底层模型通过WinRT和C来实现, 然后把这一层高效的组件无缝提供给上层的开发技术比如CLR来使用. 这个转变重新重视unmanaged层面的二进制模型. 归纳为, unmanaged模型的优势在于执行效率高, 可以通吃所有场景, 缺点在于开发和使用成本也高. CLR的优势在于开发成本低, 缺点在于无法通吃各种需求. 现在微软自己用unmanaged来做WinRT, 然后把WinRT提供给上层语言, 这两者就可以取长补短了.</p>
<p>有了WinRT, 有了unmanaged的回归, 再加上微软开发工具和C#语言的长足发展, 前面介绍的各种技术在Win8里面就相得益彰了. Metro是如何修复WPF的缺陷就显而易见了: Win8的metro程序继续使用WPF中引入的rendering模型和XAML, 但是在control的基础设计和实现上, 从CLR转移到了C, 然后通过WinRT来暴露给使用者. 至于使用的灵活性, 比如要不要实现数据绑定, 就看上层使用者自己的选择了.</p>
<p><b>附录</b><b></b></p>
<p>下面是我研究过程中看到的一些Win8的callstack. 基于这些callstack和我以往的经验, 才写了上面的文章.</p>
<p>Stack1:</p>
<p>Windows_UI_Immersive!`Windows::Internal::CMessageDialog::ShowAsync'::`50'::&lt;lambda_32D66FEFF293CE6B&gt;::&lt;lambda_32D66FEFF293CE6B&gt;</p>
<p>Windows_UI_Immersive!Windows::Internal::CMessageDialog::ShowAsync+0x1a0</p>
<p>image08ee0000!DomainNeutralILStubClass.IL_STUB_CLRtoCOM()+0x8c</p>
<p>application1!Application1.MainPage+&lt;button_Click&gt;d__0.MoveNext()+0xcd</p>
<p>application1!Application1.MainPage.button_Click(System.Object, Windows.UI.Xaml.RoutedEventArgs)+0x80</p>
<p>stack2:</p>
<p>combase!CComActivator::DoCreateInstance+0x121</p>
<p>combase!CoCreateInstanceEx+0x51</p>
<p>combase!CoCreateInstance+0x65</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_TryToUnregisterForIHMNotifications+0x3b</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_HideWindow+0x31</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::HideWindow+0x9</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::DestroyWindow+0x24</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindow::Destroy+0x57</p>
<p>Windows_UI_Immersive!Windows::Internal::CClosePopupCommandHandler::Invoke+0xe 0x73</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_OnButtonPress+0xc0</p>
<p>Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::OnMessage+0x18b</p>
<p>DUI70!DirectUI::NativeHWNDHost::WndProc+0x73</p>
<p>USER32!InternalCallWinProc+0x23</p>
<p>USER32!UserCallWinProcCheckWow+0x100</p>
<p>USER32!DispatchMessageWorker+0x3d4</p>
<p>USER32!DispatchMessageW+0x10</p>
<p>Windows_UI_Immersive!SHProcessMessagesUntilEventsEx+0xe2</p>
<p>Windows_UI_Immersive!Windows::Internal::PopupWindowOperation::Show+0x103</p>
<p>Stack3:</p>
<p>Windows_UI_Xaml!HWWalk::RenderChildren+0x7a</p>
<p>Windows_UI_Xaml!HWWalk::RenderContentAndChildren+0x2d1</p>
<p>Windows_UI_Xaml!HWWalk::Render+0x61e</p>
<p>Windows_UI_Xaml!HWWalk::RenderChildren+0x7a</p>
<p>Windows_UI_Xaml!HWWalk::RenderRoot+0x1c4</p>
<p>Windows_UI_Xaml!CCoreServices::NWDrawTree+0x698</p>
<p>Windows_UI_Xaml!CCoreServices::NWDraw+0x1d6</p>
<p>Windows_UI_Xaml!CRenderTarget::Draw+0x13</p>
<p>Windows_UI_Xaml!CWindowRenderTarget::Draw+0x40</p>
<p>Windows_UI_Xaml!CXcpBrowserHost::OnTick+0x88</p>
<p>Windows_UI_Xaml!CXcpDispatcher::Tick+0x184</p>
<p>Windows_UI_Xaml!CXcpDispatcher::OnReentrancyProtectedWindowMessage+0x133</p>
<p>Windows_UI_Xaml!CXcpDispatcher::ProcessMessage+0xa4</p>
<p>Windows_UI_Xaml!CXcpDispatcher::WindowProc+0x69</p>
<p>USER32!InternalCallWinProc+0x23</p>
<p>USER32!UserCallWinProcCheckWow+0x100</p>
<p>USER32!DispatchMessageWorker+0x3d4</p>
<p>USER32!DispatchMessageW+0x10</p>
<p>windows_ui!Windows::UI::Core::CDispatcher::ProcessMessage+0xc7</p>
<p>windows_ui!Windows::UI::Core::CDispatcher::ProcessEvents+0x6c</p>
<p>Windows_UI_Xaml!CJupiterWindow::RunCoreWindowMessageLoop+0x3b</p>
<p>Windows_UI_Xaml!CJupiterControl::RunMessageLoop+0x1d</p>
<p>Windows_UI_Xaml!CJupiterControlLight::RunMessageLoop+0x8</p>
<p>Windows_UI_Xaml!DirectUI::DXamlCore::RunMessageLoop+0x15</p>
<p>Windows_UI_Xaml!DirectUI::ViewProvider::Run+0x11</p>
<p>twinapi!Windows::ApplicationModel::Core::CoreApplicationView::ViewProviderThreadProc+0x27</p>
<p align="right"><i>This is the end</i></p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Introduce JSCEX – A powerful Javascript library</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/introduce-jscex-a-powerful-javascript-library</link>
      <pubDate>Sun, 21 Aug 2011 09:41:00 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/08/21/introduce-jscex-a-powerful-javascript-library/</guid>
      <description><![CDATA[To evaluate the possibility of using pure HTML for our next UI, I started to learn Javascript,...]]></description>
      <content:encoded><![CDATA[<p></p>
<p>To evaluate the possibility of using pure HTML for our next UI, I started to learn Javascript, Jquery and relative technology to build a <a href="http://www.heijoy.com/atlanta/a.htm">prototype</a>, which is a&nbsp;HTML single paged application.</p>
<p>There is a test mode for testing purpose in my prototype. Within test mode, all the Ajax requests are mocked with local hardcoded JSON data. The following function is called when the user clicks the status icon to flip the status. If isTestMode is true, the code just sets the instance to the new status, and triggers the finish event. Otherwise, the code sends AJAX request to server, and change the status in the AJAX callback.</p>
<p>this.changeStatus = function (alertInstance, newStatus) {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (viewModel.isTestMode) {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong><span style="color: #800000;"> alertInstance.Status(newStatus);</span></strong></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(viewModel).trigger("updateStatusFinished", [true, alertInstance, newStatus]);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;</span></strong></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var idarray = new Array(alertInstance.Id);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var idarray_json = JSON.stringify(idarray);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jQuery.ajax({</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; url: '/Services/AlertServiceRest.svc/SetAlertStatus?newStatus=' + newStatus,</p>
<p><span class="Apple-tab-span" style="white-space: pre;"> </span>&hellip;</p>
<p><span class="Apple-tab-span" style="white-space: pre;"> </span>success: function () {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alertInstance.Status(newStatus);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(viewModel).trigger("updateStatusFinished", [true, alertInstance, newStatus]);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; },&nbsp;</p>
<p>&nbsp;&nbsp; &nbsp;}</p>
<p>&nbsp;</p>
<p>The code works perfectly fine, with only one issue:</p>
<p>In non-test mode, there is a delay caused by the AJAX call. To reflect the delay in UI, my code shows a spin icon. However, if it is in test mode, above code sets the new status immediately. I never have a change to see if the spin icon shows correctly in test mode.</p>
<p></p>
<p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/69/39/8524.spin.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-69-39/8524.spin.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/69/39/8524.spin.png" original-url="http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-00-69-39/8524.spin.png" border="0" alt="" /></a></p>
<p></p>
<p>To simulate the delay, the usual way is to register a timer call back. You cannot just add a sleep call between the lines because that just freezes the whole application.</p>
<p>In fact, the tricks with timer callback are used everywhere in Javascript, especially in animations. The tricks are error prone, difficult to read, and harm to code style. There is no simple workaround, until JSCEX comes out.</p>
<p>JSCEX is a powerful Javascript library. With JSCEX, I can just write my code to simulate the delay with the following:</p>
<p><strong><span style="color: #800000;">var testAlertChange = function (alertInstance, newStatus) {</span></strong></p>
<p><span style="color: #ff0000;"><strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $await(Jscex.Async.sleep(1000));</strong></span></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alertInstance.Status(newStatus);</span></strong></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(viewModel).trigger("updateStatusFinished", [true, alertInstance, newStatus]);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp; }</span></strong></p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; this.changeStatus = function (alertInstance, newStatus) {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (viewModel.isTestMode) {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong><span style="color: #800000;">var testAlertChangeAsync = eval(Jscex.compile("async", testAlertChange));</span></strong></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; testAlertChangeAsync(alertInstance,newStatus).start();</span></strong></p>
<p><strong><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;</span></strong></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var idarray = new Array(alertInstance.Id);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var idarray_json = JSON.stringify(idarray);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jQuery.ajax({</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; url: '/Services/AlertServiceRest.svc/SetAlertStatus?newStatus=' + newStatus,</p>
<p>&nbsp;</p>
<p><strong><span style="color: #339966;">No callback, no timer, direct sleep call, super clean!</span></strong></p>
<p>JSCEX is super easy to use. To find out how the magic happens, just click:</p>
<p><a href="https://github.com/JeffreyZhao/jscex">https://github.com/JeffreyZhao/jscex</a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Two years review, look back with Active Directory Administrative Center</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/two-years-review-look-back-with-active-directory-administrative-center</link>
      <pubDate>Sat, 20 Aug 2011 11:18:16 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/08/20/two-years-review-look-back-with-active-directory-administrative-center/</guid>
      <description><![CDATA[Windows Server 2008 R2 has been released about 2 years now. It&rsquo;s time to review my previous...]]></description>
      <content:encoded><![CDATA[<p>Windows Server 2008 R2 has been released about 2 years now. It&rsquo;s time to review my previous project which ships with Win2k8R2.</p>
<p>The project was <a href="http://technet.microsoft.com/en-us/library/dd378856(v=WS.10).aspx">ADAC</a> -- Active Directory Administrative Center. The main purpose for ADAC was to replace the old MMC snapin &ndash; ADUC. ADAC was designed in the following background:</p>
<ol>
<li>Windows Server 2008 R2 introduced the new Active Directory management interface which was based on powershell. New features may not be convenient to use with old LDAP/ADSI style interface. A new UI management tool was required to reflect this change.</li>
<li>The old MMC snapin was first created with WinNT4. There was little change in the past decade. There were many new UI concepts which can help the management tasks, but were difficult to apply to the old MMC.</li>
</ol>
<p>In this background, we wanted the new UI tool (ADAC) to be able to:</p>
<ol>
<li>Fully replace the old ADUC MMC snapin, if the environment supports ADAC. (The environment condition is included here because ADAC requires enabling Win2k8R2 features). There should be no main stream task that ADAC does not support but ADUC does. When the admin is familiar with ADAC, she should forget about ADUC.</li>
<li>Bring new UI concepts to admin to simplify the management works. For common tasks, if old ADUC requires 10 mouse clicks, ADAC should simplify it to less than 5 clicks.</li>
<li>Survive for the next decades as the main management UI to support new Windows Server Active Directory features.</li>
</ol>
<p>We created the following specified features in ADAC:</p>
<ol>
<li>Apply new UI concepts, like Win7 style navigation bar, no model dialog, quick task in main page and pin/unpin features.</li>
<li>In property page, use sections to replace tabs. Using tab means you cannot see everything together. Using section brings great usability improvements.</li>
<li>Make the creation UI and view UI the same. ADUC uses wizard style UI for creation and property page style for view. By putting the two UI the same in ADAC, users can work on the same information in the same place.</li>
<li>Make important information easy to touch, like the last-logon-time and when-created property. In ADUC, the admin needs to turn on the advanced feature and get these properties from the attribute editor.</li>
<li>Make the UI customizable by allowing the user to pin and unpin the favorites containers in the left navigation bar.</li>
<li>Add muti-domain and muti-forest support. Admin can add many domains in different forests into the single tool, and manage them cross domains.</li>
<li>Design the query feature in the scenario based way. Try to hide the technique details from the end user. The query in ADUC simplify maps the LDAP attribute query concept into UI. We redesigned that into scenarios like &ldquo;query for user who is going to expire in X days&rdquo;. We also added the feature to show the LDAP expression in which the scenario maps to.</li>
</ol>
<p>From the feedback in communities, these improvements were proved to be very successful. People really like the new UI.</p>
<p>However, we also had several features that we planned, but did not add into the new tool. The main reason was resource constrains. R2 release was a short release after Windows Server 2008. The release timeframe was mainly aimed with a small set of new features. &nbsp;As ADAC was created from the scratch, the timeframe was not perfect for us to balance the catch-up and new features. So the first priority for us was to make sure no function regression from ADUC, and new features came after that. It turned out that we did not have enough time to make ADAC perfect. Specifically, we observed the following items, but did not have time to fix them:</p>
<ol>
<li>The new Active Directory features in Windows Server 2008 R2 were not supported. Like the Service Account, and the most important one: <a href="http://technet.microsoft.com/en-us/library/dd391916(v=WS.10).aspx">recycle bin</a>.</li>
<li>The UI performances in some areas were worse than ADUC, like the startup time and batch operations. Using WPF was one reason, and the other reason was about the new API interface. The new API was web service based. It required more round trips so the performance was worse when batch jobs were requested.</li>
<li>The query feature may not be accurate because the domain policy was not taken into account. If we take domain policy settings, the complexity would increase dramatically in the product. We decided to drop it and document the special cases. (ADUC did not have this feature too).</li>
<li>We did not provide SDK or any way to let user extend ADAC. This was a regression from ADUC. In ADUC, the user could create COM based property sheet, and register it under display-specifier. ADUC loads the user defined sheet when property opens. In ADAC, we did not provide a way to let user add new UI sections. To compensate that, we only load the ADUC sheets into a separate section, which should include the user defined sheet.</li>
<li>Several small function regressions from ADUC. Like ADAC does not support creating new user by copy-paste from an existing one. When setting the home folder of the customer, ADAC does not help check and set the folder permission automatically.</li>
</ol>
<p>From the community feedbacks, some items really caused troubles. This <a href="http://blogs.technet.com/b/askds/archive/2011/06/16/fun-with-the-ad-administrative-center.aspx">article</a> is a good place for ADAC features and feedbacks (in the comment section). And this <a href="http://social.technet.microsoft.com/Search/en-US?query=Active%20Directory%20Administrative%20Center&amp;refinement=112&amp;beta=0&amp;ac=8">link</a> shows the question raised against ADAC in MSDN forum.</p>
<p>About me:</p>
<p>I was one of the testers for ADAC. I owned the following areas in ADAC:</p>
<ol>
<li>User management.</li>
<li>Query.</li>
<li>UI automation framework.</li>
</ol>
<p>The whole ADAC team (dev, test and pm) was in China. I worked on ADAC form day-1, until it shipped. After that, the product was transferred back to Redmond.</p>]]></content:encoded>
    </item>
    <item>
      <title>关于dev的贡献 About the contribution from a developer</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/dev-about-the-contribution-from-a-developer</link>
      <pubDate>Wed, 13 Apr 2011 07:24:10 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/04/13/dev-about-the-contribution-from-a-developer/</guid>
      <description><![CDATA[最近对developer的贡献有一些感想.
简单的说, developer的特有贡献不在于产品, 而在于代码.
考虑这样的情况. 假设做出了一个成功的产品, 把产品卖出去,...]]></description>
      <content:encoded><![CDATA[<p>最近对developer的贡献有一些感想.</p>
<p>简单的说, developer的特有贡献不在于产品, 而在于代码.</p>
<p>考虑这样的情况. 假设做出了一个成功的产品, 把产品卖出去, 和把这个产品的源代码卖出去, 是有本质差别的.&nbsp;</p>
<p>作为一个好的developer, 她会让这个产品的code base非常有价值. 因为任何人都可以很轻松的在上面进行二次生产.</p>
<p>而产品本身的好坏, 很多时候是受到这个团队左右的.</p>
<p>&nbsp;</p>
<p>Something I learned about being a good developer recently.</p>
<p>The dedicated contribution of a developer, is not about the product, it is about the code.</p>
<p>Even you have a great product. Selling the product is very different from selling the code base of the product.</p>
<p>As a good developer, you can make the code base very valuable, so that anyone can simply take it and continue work on.</p>
<p>For the product itself, it is usually influenced by the whole team.</p>]]></content:encoded>
    </item>
    <item>
      <title>再提test的贡献</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/test</link>
      <pubDate>Thu, 31 Mar 2011 08:19:13 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/03/31/test/</guid>
      <description><![CDATA[你可以说test对产品质量的贡献很重要
但是不要忘了
任何组织的资源都是有限的
有的组织
会冒险把很少很少的资源放在test上
这样他们在其它方面就会有显著的优势
所以
如果作为te...]]></description>
      <content:encoded><![CDATA[<p>
<p>你可以说test对产品质量的贡献很重要</p>
<p>但是不要忘了</p>
<p>任何组织的资源都是有限的</p>
<p>有的组织</p>
<p>会冒险把很少很少的资源放在test上</p>
<p>这样他们在其它方面就会有显著的优势</p>
<p>所以</p>
<p>如果作为test的你</p>
<p>你要仔细想想</p>
<p>你是否为产品的进步</p>
<p>做出了相应的贡献</p>
<p>你是否能够说服你自己</p>
<p>把你这个资源放在test上</p>
<p>比把这个资源放在其它领域</p>
<p>是更有价值的</p>
</p>
<p>&nbsp;</p>
<p>
<p>Yes, test is very important for quality</p>
<p>But don't forget</p>
<p>The resource is limited</p>
<p>Some organalization</p>
<p>They will take the risk to put very little resource on test</p>
<p>So that they can move fast in other area</p>
<p>You, as a tester</p>
<p>Need to think about</p>
<p>Do you worth it</p>
<p>Can you persuade yourself</p>
<p>To put the resource on you</p>
<p>Rather than other area</p>
<p>&nbsp;</p>
</p>
<p>我想, 这就是我做了4年test最重要的感想</p>]]></content:encoded>
    </item>
    <item>
      <title>最近二三事</title>
      <link>https://docs.microsoft.com/archive/blogs/lixiong/123</link>
      <pubDate>Fri, 25 Mar 2011 10:03:00 GMT</pubDate>
      <dc:creator><![CDATA[Li Xiong]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/lixiong/2011/03/25/123/</guid>
      <description><![CDATA[最近二三事
我们项目组的产品: SystemCenter Advisor, RC了....]]></description>
      <content:encoded><![CDATA[<p>最近二三事</p>
<p>我们项目组的产品: SystemCenter Advisor, RC了. 地址在:</p>
<p><a href="https://www.systemcenteradvisor.com">https://www.systemcenteradvisor.com</a></p>
<p>这是我给我们团队设计的T-Shirt.</p>
<p>&nbsp;<img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/69/39/8540.final-sample.png" original-url="http://blogs.msdn.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-69-39/8540.final_2D00_sample.png" border="0" /></p>
<p>在做了3年多的tester后, 我想尝试一下做dev的感觉. 希望我以后不要给tester带来太多麻烦.<br />在离开support后, 我基本上就不写blog了. 因为在研发领域, 我发现我不了解的东西太多了, 不敢妄言.</p>
<p>刚开始做test的时候, 觉得没有做support有趣.&nbsp; 不就是找bug么, 太容易了. 后来才发现, 找bug和推动产品进步, 简直不能同日而语. 还好我总能摊上好老板. 和我以前的老板相比, 他并不是高高在上在这个部门干了很多年那种. 和我后来的老板相比, 他不是资深到凡是你所知道的他都知道的那种. 这让我可以和他更随和的交流, 他追求卓越的热忱也更能够感染我. 世上还有比彼此进步更快乐的事情吗?</p>
<p>最近和新的dev老板聊了两次. 感想是, 这下总算摊上个牛人在身边了. 今天又在论坛上看到这个论调, 说:</p>
<p>"当然外企也有自身的缺点。在国内分部很难接触到公司的核心项目开发，往往是做一些边边角角的工作或总部不喜欢做的繁重体力活。这对个人技术发展不利，尤其是对刚刚参加工作的员工"</p>
<p>其实这是一种自怨自艾, 阻碍自己成长的想法. 所谓信任, 是自己挣来的. 何不先反思自己的态度, 而上来就责怪公司的制度呢? 其实很多外企的文化还是西方文化, 西方文化和东方文化, 一个差别就在, 东方文化是等着对方告诉你你需要做什么才可以挣到信任, 西方文化是请你主动找到方来来证明你自己. 对于我来说, 的确做的产品不是核心, 但是至少我现在还不觉得我有做核心产品的能力. 这又有什么关系呢. 那么多讨论组, 那么多热心的, 如雷贯耳的同事. 我尝试过给很多牛人写email, 请求他们帮我化解我心中的烦恼, 每次都拿到了超我期望的结果. 你想想, 当你不知道如何做一个好的dev的时候, 你可以给一个设计C#的资深dev写信, 可以给一个设计Windows Shell的资深dev写信, 可以和一个IE9 JavascriptEngine的设计师当面交流, 而且他们回信比你的原件还常长, 聊的时间比你们约定的时间还长, 你真觉得, 你活在一个阻碍你成长的环境中么?</p>
<p>除非你等着对方来找你.</p>]]></content:encoded>
    </item>
  </channel>
</rss>