tag:blogger.com,1999:blog-3627969456732398002024-03-07T04:37:41.668-05:00Technical Is As Technical Does - Hugh Ang's BlogAdventure with .NET and WindowsHugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.comBlogger35125tag:blogger.com,1999:blog-362796945673239800.post-1596513646851434662011-12-21T21:08:00.005-05:002011-12-21T21:51:19.373-05:00How to Make Tracing and Caching Providers Work for EF 4.1 Code FirstThe <a href="http://code.msdn.microsoft.com/EFProviderWrappers-c0b88f32">tracing and caching providers</a> (or <a href="http://blogs.msdn.com/b/jkowalski/archive/2009/06/11/tracing-and-caching-in-entity-framework-available-on-msdn-code-gallery.aspx">here</a>) provide an excellent way to trace and cache EF queries. Unlike those that attempt to do the same above the EF API, these providers work under the covers and make tracing and caching non-invasive for the application code. Specifically, for cases where entities are lazy-loaded via EF and those entities are to be cached, this caching provider is truly more advantageous. <br /><br />However, these providers were written before EF 4.1 code first and they wouldn't work if you just follow the old sample. Luckily, it is not too difficult to make them work. The trick is to create a wrapping EFCachingConnection or EFTracingConnection that wraps a DbConnection (such as SqlConnection) and pass it to the DbContext constructor. I will just demonstrate the caching related code here but you should be able to chain tracing, caching and then the underlying SqlConnection using the same pattern.<br /><br />First, a minor modification to the EFCachingConnection constructor is needed. As shown in the following code, we need to be able to set the wrappedProviderInvariantName via the ctor:<br /><br /><div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt"><br /><div style="background: #fff; max-height: 300px; overflow: auto"><br /><ol start="26" style="background: #ffffff; margin: 0; padding: 0 0 0 5px;"><br /><li> </li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">public</span> EFCachingConnection(<span style="color:#2b91af">DbConnection</span> wrappedConnection, <span style="color:#0000ff">string</span> wrappedProviderInvariantName)</li><br /><li> {</li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">this</span>.WrappedConnection = wrappedConnection;</li><br /><li> <span style="color:#0000ff">this</span>.CachingPolicy = <span style="color:#2b91af">EFCachingProviderConfiguration</span>.DefaultCachingPolicy;</li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">this</span>.Cache = <span style="color:#2b91af">EFCachingProviderConfiguration</span>.DefaultCache;</li><br /><li> <span style="color:#0000ff">base</span>.wrappedProviderInvariantName = wrappedProviderInvariantName; <span style="color:#008000">// make it possible to set the wrappedProviderInvariantName via this ctor</span></li><br /><li style="background: #f3f3f3"> }</li><br /></ol><br /></div><br /></div><br /><br />The I use the following helper function to create an instance of the EFCachingConnection that wraps a SqlConnection:<br /><br /><div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt"><br /><div style="background: #fff; max-height: 300px; overflow: auto"><br /><ol start="56" style="background: #ffffff; margin: 0; padding: 0 0 0 5px;"><br /><li> </li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#2b91af">EFCachingConnection</span> CreateEFCachingConnectionWrapper(<span style="color:#0000ff">string</span> nameOrConnectionString)</li><br /><li> {</li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">string</span> connectionString;</li><br /><li> <span style="color:#0000ff">if</span> (nameOrConnectionString.Contains(<span style="color:#a31515">"="</span>))</li><br /><li style="background: #f3f3f3"> connectionString = nameOrConnectionString;</li><br /><li> <span style="color:#0000ff">else</span></li><br /><li style="background: #f3f3f3"> connectionString = <span style="color:#2b91af">ConfigurationManager</span>.ConnectionStrings[nameOrConnectionString].ConnectionString;</li><br /><li> </li><br /><li style="background: #f3f3f3"> <span style="color:#2b91af">DbConnection</span> connection = <span style="color:#0000ff">new</span> <span style="color:#2b91af">SqlConnection</span>(connectionString);</li><br /><li> </li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> <span style="color:#2b91af">EFCachingConnection</span>(connection, <span style="color:#a31515">"System.Data.SqlClient"</span>); </li><br /><li> }</li><br /></ol><br /></div><br /></div><br /><br />Now you would simply pass the newly constucted EFCachingConnection instance to the ctor of DbContext:<br /><br /><div style="border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt"><br /><div style="background: #fff; max-height: 300px; overflow: auto"><br /><ol start="100" style="background: #ffffff; margin: 0; padding: 0 0 0 5px;"><br /><li> </li><br /><li style="background: #f3f3f3"> <span style="color:#0000ff">public</span> MyContext(<span style="color:#2b91af">DbConnection</span> connection)</li><br /><li> : <span style="color:#0000ff">base</span>(connection, <span style="color:#0000ff">true</span>)</li><br /><li style="background: #f3f3f3"> {</li><br /></ol><br /></div><br /></div><br /><br />Note that MyContext is the application's EF code first context, which derives from DbContext and has probably overriden the OnModelCreating() method.<br /><br />Last and not the least you would need to call EFCachingProviderConfiguration.RegisterProvider() first when you start your application.<br /><br />Happy holidays and happy EF coding!Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com6tag:blogger.com,1999:blog-362796945673239800.post-58062291533352706012011-03-01T14:36:00.011-05:002011-03-01T15:21:45.038-05:00Recording VS.NET Web Performance Test for FlexWe are building a web application that integrates with a third party software by embedding their Flex objects in our ASPX pages. When we started using VS.NET 2010 Web Performance Test, everything worked fine except for the weborb requests made by the Flex objects to the server. Basically, the requests recorded by the web test, when played back and sent to the server, were causing the server to respond with errors. From googling, it looks like that <a href="http://tech.groups.yahoo.com/group/flashorb/message/7725">the same issue</a> has been experienced by other folks as well. At this point, it would be easy to simply dismiss VS.NET 2010 Web Performance Test as a viable tool for Flex. What a costly mistake this would have been if we had stopped doing more analysis! <br /><br />Looking at the initially recorded tests, I found out that all the data posted by the Flex objects was recorded and played back as string data type. Examining the payload in Fiddler, however, showed that the data is obviously binary. So our recorded test was sending the data with lost fidelity and the server just choked. <br /><br />Thinking that there must be configuration to tweak to make our tests record binary data, I started searching MSDN for documentation. Sure enough, I found <a href=http://msdn.microsoft.com/en-us/library/ff356200.aspx">this link</a>. I went ahead and added "application/x-amf" as the content type. Then I recorded the test again and ran the scripts. <br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzQxe40B5d3g_jGwMYENeHZ50Uki2e-W3AeaM2Bw2RshUErW3yUjAVqY8wF9AYhzFP6JHbNbWlKpjqQCsoECgbkCvhLFoRpxj7RX8yypt0PqwzxfenA_SWfrPnVz304w4QRTl7f0r1YaE/s1600/Webtest_amf_option.PNG"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 184px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzQxe40B5d3g_jGwMYENeHZ50Uki2e-W3AeaM2Bw2RshUErW3yUjAVqY8wF9AYhzFP6JHbNbWlKpjqQCsoECgbkCvhLFoRpxj7RX8yypt0PqwzxfenA_SWfrPnVz304w4QRTl7f0r1YaE/s320/Webtest_amf_option.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5579207812160350610" /></a>Voila, everything worked like a charm!Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-15265729740680423392010-05-10T22:09:00.004-04:002010-05-10T23:02:53.803-04:00Fixing My Custom Built PC After a Power OutageI usually wouldn't blog about hardware but since this experience felt just like debugging a software problem, here it goes:<br /><br />The NJ area experienced a pretty bad wind storm this past weekend. Downed tree branches knocked out some power lines, which caused scattered power outages in the area. My house was one of the unlucky ones that were hit with the outage. After the power came back in the next morning, one of my desktop PCs that I built several years ago wouldn't start. This is the PC that I have just recently upgraded from Vista to Windows 7 and I also built inside the host PC a VPC with VS.NET 2010 and all the other latest cool stuff (Silverlight, etc.). So I was determined to figure out what went bad and hoped to salvage as much as possible. <br /><br />The symptom of the problem is that initially it wouldn't even turn on when I pressed the power button. After I switched to a different circuit in the house, it could start with fans and lights on. So it was evident that the PSU was still good. But there was no display on the monitor and no system start beep sound either. What was even worse was I could smell burning plastic. Some board was fried during the outage and now with the power on, sending the current through the board is still burning it. <br /><br />I turned off the power and began visually inspecting the mobo, hoping to find where the burn was but to no avail. I started disconnecting the IDE cable to the DVD rom, SATA cables to the HDD, unplugging RAM. I would make one change at a time while powering on to see if there is a system beep. Nothing so far. <br /><br />The last item was the video card with dual DVI interfaces for my two monitors. After I unplugged the video card, bingo! the system started making beeping sounds. And as I flipped the video card over, sure enough I found the burning spot, which I wouldn't have been able to see when it was installed closely to the bottom of the tower in the facing down position. Fortunately I had a spare video card. After plugging that in I had no problem booting into Windows 7. It did require resetting the resolution after automatically installing the driver for the card. Minutes later, I noticed that automatic Windows update was complaining about not able to complete the updates and a closer look revealed that the system clock was set to 1/1/2006. I suspect this was done as I was resetting the BIOS during the diagnosis process - while the power cord was unplugged, I pressed and held the power button for a few moments. <br /><br />So fortunately it was only a small loss instead of the MOBO/CPU that are commonly getting fried during power outages. One thing I will be doing for sure going forward is to use the battery backup surge protector as opposed to a low end one I was using :-)Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-5359826521298566702009-12-16T01:07:00.027-05:002009-12-16T13:37:02.603-05:00Application Migration - Vista/Win7 CompatibilityIt has been a while since the last time I blogged. The theme of my work for the last 4 months or so is really migration, migration, and migration! <br /><br /><ul><br /><li>First I helped our team with a VB6 to VB.NET migration project that ran into some sticky interop problems.</li><br /><li>Secondly I worked as an advisor for a customer who was in the process of migrating their apps from Win2K to Vista. Please don't ask me why they chose to go to Vista while Win7 was coming out - it was a decision already rationalized by the management when I was brought in. <br /></li><br /><li>And lastly, just a couple of weeks ago, I helped Microsoft deliver a Win7 training course for a customer who is looking to migrate to Win7 in the next 6 months. Although it is not unusual that I present in a conference room or mentor developers privately, this was actually my first time to do a formal training. The sessions were recorded and are being made available for later reuse by the customer. Speaking of the sessions on tape, I actually saw Oliver Stone and Michael Douglas when they were shooting Wall Street 2 on site at the Borders book store at the corner of Wall Street and Broadway. And that was when I was working on the Vista migration mentioned above.<br /></li><br /></ul><br /><br />Enough with the chronology and my celebrity sightings. As the title suggests, I'd like to cover the most common compatibility issues for Vista/Win7from a developer perspective. So here are the two top issues I have seen so far:<br /><br /><ul><br /><li>Writing to %ProgramFiles% (e.g. C:\Program Files) and %SystemRoot% (e.g. C:\Windows) locations</li><br /><li>Writing to registry hive: HKLM\Software</li><br /></ul><br /><br />It is quite common that legacy apps write configuration data to the above locations. But with UAC enabled in Vista/Win7, an admin user will be running an app with standard user privileges unless the app has a manifest explicitly requesting admin privilege or the app is started with "Run as administrator" context menu; And since standard user will only have read/execute rights to those locations, the attempt made by those apps to write configuration data to those locations will fail. Using the icacls at the command prompt shows the ACL of %ProgramFiles% as in the following on my machine: <br /><br /><pre><br />C:\Program Files>icacls .<br />. NT SERVICE\TrustedInstaller:(F)<br /> NT SERVICE\TrustedInstaller:(CI)(IO)(F)<br /> NT AUTHORITY\SYSTEM:(M)<br /> NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)<br /> BUILTIN\Administrators:(M)<br /> BUILTIN\Administrators:(OI)(CI)(IO)(F)<br /> BUILTIN\Users:(RX)<br /> BUILTIN\Users:(OI)(CI)(IO)(GR,GE)<br /> CREATOR OWNER:(OI)(CI)(IO)(F)<br /></pre><br /><br />There is an additional twist here. The app may not be failing but you will not see files or registry keys written to the intended location. This is the work of data redirection(or UAC virtualization). The details can be found <a href="http://windowsteamblog.com/blogs/developers/archive/2009/08/04/user-account-control-data-redirection.aspx">here</a>. But basically:<br /><br /><ul><br /><li>Writing to %ProgramFiles%\MyApp\myapp.ini gets redirected to C:\Users\Username\AppData\Local\VirtualStore\Program Files\MyApp\myapp.ini</li><br /><li>Writing to HKLM\Software\MyApp\ gets redirected to HKCU\Software\Classes\VirtualStore\MACHINE\Software\MyApp</li><br /></ul><br /><br />When virtualization happens, you can fire up Process Monitor and notice a REPARSE event, followed by writing to those redirected locations. <br /><br />As the redirection is on per-user basis, this can mess up the intended behavior of the application. For example, the application may be intended to be used by multiple users on one machine and the configuration data should be shared. Microsoft is making no guarantee that the virtualization will be in future Windows. So what to do as a developer? The best practice for writing per machine configuration data is using %ProgramData% (e.g. C:\ProgramData). For per user configuration data, use %LocalAppData% (e.g. C:\Users\User\AppData\Local) for local or %AppData% for roaming user profile. <br /><br />Running icacls on %ProgramData% shows that a standard user would have additional AD(append data/add subdirectory), WD(write data/add file), WEA(write extended attributes) and WA(write attributes) privileges:<br /><br /><pre><br />C:\ProgramData>icacls .<br />. NT AUTHORITY\SYSTEM:(OI)(CI)(F)<br /> BUILTIN\Administrators:(OI)(CI)(F)<br /> CREATOR OWNER:(OI)(CI)(IO)(F)<br /> BUILTIN\Users:(OI)(CI)(RX)<br /> BUILTIN\Users:(CI)(WD,AD,WEA,WA)<br /></pre><br /><br />And lastly, don't hard code any of the paths we have talked about. There are well-known APIs to get them. In .NET, follow this sample:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red43\green145\blue175;}??\fs20 \cf1 string\cf0 path = \cf4 Environment\cf0 .GetFolderPath(\cf4 Environment\cf0 .\cf4 SpecialFolder\cf0 .CommonApplicationData);\par ??\par ??path = \cf4 Environment\cf0 .GetFolderPath(\cf4 Environment\cf0 .\cf4 SpecialFolder\cf0 .LocalApplicationData);\par ??\par ??path = \cf4 Environment\cf0 .GetFolderPath(\cf4 Environment\cf0 .\cf4 SpecialFolder\cf0 .ApplicationData);}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><br /><p style="margin: 0px;"><span style="color: blue;">string</span> path = <span style="color: #2b91af;">Environment</span>.GetFolderPath(<span style="color: #2b91af;">Environment</span>.<span style="color: #2b91af;">SpecialFolder</span>.CommonApplicationData);</p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;">path = <span style="color: #2b91af;">Environment</span>.GetFolderPath(<span style="color: #2b91af;">Environment</span>.<span style="color: #2b91af;">SpecialFolder</span>.LocalApplicationData);</p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;">path = <span style="color: #2b91af;">Environment</span>.GetFolderPath(<span style="color: #2b91af;">Environment</span>.<span style="color: #2b91af;">SpecialFolder</span>.ApplicationData);</p><br /></div><br /><br />And if you write C++ code, here is a sample:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red0\green128\blue0;}??\fs20 \cf1 void\cf0 ShowFolders(HWND hWnd)\par ??\{\tab \par ??\tab LPWSTR wszPath = NULL;\par ??\tab HRESULT hr = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath); \par ??\tab \cf1 if\cf0 ( SUCCEEDED(hr) )\par ??\tab \{\par ??\tab \tab \cf4 // work with wszPath\par ??\cf0 \tab \tab \par ??\tab \tab CoTaskMemFree(wszPath);\par ??\tab \}\par ??\par ??\tab hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath); \par ??\tab \cf1 if\cf0 ( SUCCEEDED(hr) )\par ??\tab \{\par ??\tab \tab \cf4 // work with wszPath\tab \tab \par ??\cf0 \tab \tab \par ??\tab \tab CoTaskMemFree(wszPath);\par ??\tab \}\par ??\par ??\tab hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath); \par ??\tab \cf1 if\cf0 ( SUCCEEDED(hr) )\par ??\tab \{\par ??\tab \tab \cf4 // work with wszPath\tab \par ??\cf0 \tab \tab \par ??\tab \tab CoTaskMemFree(wszPath);\par ??\tab \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><br /><p style="margin: 0px;"><span style="color: blue;">void</span> ShowFolders(HWND hWnd)</p><br /><p style="margin: 0px;">{ </p><br /><p style="margin: 0px;"> LPWSTR wszPath = NULL;</p><br /><p style="margin: 0px;"> HRESULT hr = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath); </p><br /><p style="margin: 0px;"> <span style="color: blue;">if</span> ( SUCCEEDED(hr) )</p><br /><p style="margin: 0px;"> {</p><br /><p style="margin: 0px;"> <span style="color: green;">// work with wszPath</span></p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;"> CoTaskMemFree(wszPath);</p><br /><p style="margin: 0px;"> }</p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;"> hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath); </p><br /><p style="margin: 0px;"> <span style="color: blue;">if</span> ( SUCCEEDED(hr) )</p><br /><p style="margin: 0px;"> {</p><br /><p style="margin: 0px;"> <span style="color: green;">// work with wszPath </span></p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;"> CoTaskMemFree(wszPath);</p><br /><p style="margin: 0px;"> }</p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;"> hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY, NULL, &wszPath); </p><br /><p style="margin: 0px;"> <span style="color: blue;">if</span> ( SUCCEEDED(hr) )</p><br /><p style="margin: 0px;"> {</p><br /><p style="margin: 0px;"> <span style="color: green;">// work with wszPath </span></p><br /><p style="margin: 0px;"> </p><br /><p style="margin: 0px;"> CoTaskMemFree(wszPath);</p><br /><p style="margin: 0px;"> }</p><br /><p style="margin: 0px;">}</p><br /></div>Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com1tag:blogger.com,1999:blog-362796945673239800.post-60307145759997501492009-09-02T16:58:00.004-04:002009-09-02T17:32:37.406-04:00Hotfix KB971030 ReleasedYou can finally download this hotfix from <a href="http://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=21630">here</a> and the detailed information is available <a href="http://support.microsoft.com/kb/971030">here</a>. According to the information posted there, it is a fix for an access violation problem when making virtual function calls to the IList<T>, IEnumerable<T>, or ICollection<T> interface. I am not sure yet if this is another manifestation of the same dispatch token problem I described in my <a href="http://tigerang.blogspot.com/2008/09/follow-up-of-piab-and-wcf-article.html">previous blog</a> or the CLR team simply bundled together several virtual dispatch bug fixes that just include <a href="http://tigerang.blogspot.com/2008/09/follow-up-of-piab-and-wcf-article.html">what I found</a>. <br /><br />I ran the same test harness that I used before and confirmed that the hotfix does indeed resolve the problem. <br /><br />I was having some trouble installing the hotfix today but eventually got it installed. It turned out that my OS was Vista SP1 and I had to upgrade it to SP2 in order to install the hotfix. If you have another OS and wondering about the same question as <a href="https://www.blogger.com/comment.g?blogID=362796945673239800&postID=6641481464743152310">Chris Dion</a> had, the hotfix should be applicable to Windows Server 2000, Windows Server 2003, Windows Server 2008 and Windows XP.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com3tag:blogger.com,1999:blog-362796945673239800.post-66414814647431523102009-05-15T13:51:00.002-04:002009-05-15T14:11:53.657-04:00CLR Virtual Call Stub Issue Resolved<strong>Good news</strong> - I have just received words from the Microsoft CLR team that the issue I detailed in <a href="http://tigerang.blogspot.com/2008/09/follow-up-of-piab-and-wcf-article.html">this blog</a> has been fixed in an upcoming QFE, which will be available for download through the Support/KB site using KB971030 in about a week. This also means that the fix will be automatically included in any future 3.5 service pack release. In addition, this has been fixed in the upcoming 4.0 release. I will try the fix in a week and keep everyone posted.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com4tag:blogger.com,1999:blog-362796945673239800.post-27386807156443959032009-02-18T11:46:00.005-05:002009-02-18T12:34:15.681-05:00Making Assumptions in Application DesignBack during the holidays, I wrote a small .NET program to manipulate my Canon XTi camera, which was connected to the PC via USB. Everything worked great when I was using my laptop that had Vista on it. But on my wife's laptop that had XP, the performance became unbearable. My utility program would wait for several minutes before I could start using it. The program's thread was pretty much idle during the waiting so I was asking myself: could this be the PC hardware? I tested this program on one of my desktops that also had XP. Nope, still the same poor performance. I then updated the camera's firmware and that didn't resolve the issue either. I began to suspect that this was OS related. Sure enough, when I plugged in the camera without my program running, XP took a much longer time than Vista to come up with the camera and scanners wizard. I noticed that while XP seemed to be cluelessly waiting, the CF card indicator light of the camera was flashing madly. I realized that XP was probably trying to download pictures from the camera, which was busy reading from the CF card. At the time, I had about 300 high resolution pictures on the 2GB CF card. After I switched it with an empty CF card, the performance problem just went away. <br /><br />The morale of the story is that we need to be extra careful when making assumptions for users when designing UI applications. In this case, XP made a not necessarily wise assumption about the intention of plugging a camera into the USB port. Even for users who do want to download pictures this way, there is no visual clue provided, leaving users wonder what is going on.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-40262133807068708382008-10-14T12:29:00.011-04:002008-10-14T13:40:26.252-04:00Reverse P/Invoke - Part 2In <a href="http://tigerang.blogspot.com/2008/09/reverse-pinvoke.html">my last post</a>, I described a loosely-coupled pattern for native code to call into managed code. That approach requires control of source code of the native library, although only minor change would be made. There are scenarios where native code is not available and we just can not make any changes to it. For example, I have a Canon Rebel XTi camera connected to my PC and I need to notify my Winforms application of a new picture just taken so that the application can download and display it. The Canon SDK is in native library and has exported functions for callback function pointers to be registered. So what do we do? Scenarios like this require us to call native API from managed code, passing delegates marshaled as function pointers. <br /><br />The solution is surprisingly straightforward. To demonstrate the technique, here is the native library code - note the definitions of data structure and callback function prototype:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red0\green128\blue0;\red163\green21\blue21;}??\fs20 \cf1 #define\cf0 NATIVELIB_API \cf1 __declspec\cf0 (\cf1 dllexport\cf0 )\par ??\par ??\cf4 // data structure for the callback function\par ??\cf1 struct\cf0 EventData\par ??\{\par ?? \cf1 int\cf0 I;\par ?? TCHAR* Message; \par ??\};\par ??\par ??\cf4 // callback function prototype\par ??\cf1 typedef\cf0 \cf1 void\cf0 (*FPCallBack)(EventData data);\par ??\par ??\cf4 // exported API\par ??\cf0 NATIVELIB_API \cf1 void\cf0 fnNativeAPI(\cf1 int\cf0 i, FPCallBack callBack)\par ??\{\par ??\tab EventData data;\par ??\tab data.I = i;\par ??\tab data.Message = L\cf5 "Hello from native code!"\cf0 ;\par ??\par ??\tab \cf4 // invoke the callback function\par ??\cf0 \tab callBack(data);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">#define</span> NATIVELIB_API <span style="color: blue;">__declspec</span>(<span style="color: blue;">dllexport</span>)</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: green;">// data structure for the callback function</span></p><p style="margin: 0px;"><span style="color: blue;">struct</span> EventData</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">int</span> I;</p><p style="margin: 0px;"> TCHAR* Message; </p><p style="margin: 0px;">};</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: green;">// callback function prototype</span></p><p style="margin: 0px;"><span style="color: blue;">typedef</span> <span style="color: blue;">void</span> (*FPCallBack)(EventData data);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: green;">// exported API</span></p><p style="margin: 0px;">NATIVELIB_API <span style="color: blue;">void</span> fnNativeAPI(<span style="color: blue;">int</span> i, FPCallBack callBack)</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> EventData data;</p><p style="margin: 0px;"> data.I = i;</p><p style="margin: 0px;"> data.Message = L<span style="color: #a31515;">"Hello from native code!"</span>;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// invoke the callback function</span></p><p style="margin: 0px;"> callBack(data);</p><p style="margin: 0px;">}</p></div><br /><br />And here is my corresponding interop code in C#. Notice that in the P/Invoke definition of the fnNativeAPI call, I added the MarshalAs(UnmanagedType.FunctionPtr) attribute in front of the CallBackDelegate parameter to instruct the runtime to marshal the delegate as a function pointer from managed code to native code.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red163\green21\blue21;}??\fs20 [\cf3 StructLayout\cf0 (\cf3 LayoutKind\cf0 .Sequential, CharSet = \cf3 CharSet\cf0 .Unicode)]\par ??\cf4 public\cf0 \cf4 struct\cf0 \cf3 EventData\par ??\cf0 \{\par ?? \cf4 public\cf0 \cf4 int\cf0 I;\par ?? \cf4 public\cf0 \cf4 string\cf0 Message;\par ??\}\par ??\par ??[\cf3 UnmanagedFunctionPointer\cf0 (\cf3 CallingConvention\cf0 .Cdecl)]\par ??\cf4 public\cf0 \cf4 delegate\cf0 \cf4 void\cf0 \cf3 CallBackDelegate\cf0 (\cf3 EventData\cf0 data);\par ??\par ??\cf4 public\cf0 \cf4 class\cf0 \cf3 Native\par ??\cf0 \{\par ?? [\cf3 DllImport\cf0 (\cf5 "NativeLib.dll"\cf0 )]\par ?? \cf4 public\cf0 \cf4 static\cf0 \cf4 extern\cf0 \cf4 void\cf0 fnNativeAPI(\cf4 int\cf0 i, [\cf3 MarshalAs\cf0 (\cf3 UnmanagedType\cf0 .FunctionPtr)] \cf3 CallBackDelegate\cf0 callBack);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[<span style="color: #2b91af;">StructLayout</span>(<span style="color: #2b91af;">LayoutKind</span>.Sequential, CharSet = <span style="color: #2b91af;">CharSet</span>.Unicode)]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">struct</span> <span style="color: #2b91af;">EventData</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">int</span> I;</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> Message;</p><p style="margin: 0px;">}</p><p style="margin: 0px;"> </p><p style="margin: 0px;">[<span style="color: #2b91af;">UnmanagedFunctionPointer</span>(<span style="color: #2b91af;">CallingConvention</span>.Cdecl)]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">delegate</span> <span style="color: blue;">void</span> <span style="color: #2b91af;">CallBackDelegate</span>(<span style="color: #2b91af;">EventData</span> data);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">Native</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> [<span style="color: #2b91af;">DllImport</span>(<span style="color: #a31515;">"NativeLib.dll"</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">extern</span> <span style="color: blue;">void</span> fnNativeAPI(<span style="color: blue;">int</span> i, [<span style="color: #2b91af;">MarshalAs</span>(<span style="color: #2b91af;">UnmanagedType</span>.FunctionPtr)] <span style="color: #2b91af;">CallBackDelegate</span> callBack);</p><p style="margin: 0px;">}</p></div><br /><br />Here is the C# code of the application:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red43\green145\blue175;\red0\green128\blue0;}??\fs20 \cf1 public\cf0 \cf1 partial\cf0 \cf1 class\cf0 \cf4 Form1\cf0 : \cf4 Form\par ??\cf0 \{\par ?? \cf1 private\cf0 CallBackDelegate _cb;\par ??\par ?? \cf1 public\cf0 Form1()\par ?? \{\par ?? InitializeComponent();\par ?? _cb = \cf1 new\cf0 CallBackDelegate(Foo);\par ?? \}\par ??\par ?? \cf1 private\cf0 \cf1 void\cf0 Form1_Load(\cf1 object\cf0 sender, \cf4 EventArgs\cf0 e)\par ?? \{\par ?? \cf5 // call the native API, passing our .NET delegate\par ??\cf0 \cf1 int\cf0 i = 200;\par ?? Native.fnNativeAPI(i, _cb);\par ?? \}\par ??\par ?? \cf5 // this is our callback function\par ??\cf0 \cf1 private\cf0 \cf1 void\cf0 Foo(EventData data)\par ?? \{\par ?? \cf4 Debug\cf0 .WriteLine(data.I);\par ?? \cf4 Debug\cf0 .WriteLine(data.Message);\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">partial</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">Form1</span> : <span style="color: #2b91af;">Form</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">private</span> CallBackDelegate _cb;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> Form1()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> InitializeComponent();</p><p style="margin: 0px;"> _cb = <span style="color: blue;">new</span> CallBackDelegate(Foo);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">void</span> Form1_Load(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: green;">// call the native API, passing our .NET delegate</span></p><p style="margin: 0px;"> <span style="color: blue;">int</span> i = 200;</p><p style="margin: 0px;"> Native.fnNativeAPI(i, _cb);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// this is our callback function</span></p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">void</span> Foo(EventData data)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">Debug</span>.WriteLine(data.I);</p><p style="margin: 0px;"> <span style="color: #2b91af;">Debug</span>.WriteLine(data.Message);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br />Careful readers may have noticed that I have kept the delegate as a class field. This is a best practice to prevent the delegate from being garbage-collected. It would be really bad if the native code holds an invalid function pointer and tries to invoke it. <br /><br />For VB programmers, here is the interop code in VB.NET:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red163\green21\blue21;}??\fs20 <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _\par ??\cf3 Public\cf0 \cf3 Structure\cf0 EventData\par ?? \cf3 Public\cf0 I \cf3 As\cf0 \cf3 Integer\par ??\cf0 \cf3 Public\cf0 Message \cf3 As\cf0 \cf3 String\par ??End\cf0 \cf3 Structure\par ??\par ??\cf0 <UnmanagedFunctionPointer(CallingConvention.Cdecl)> _\par ??\cf3 Public\cf0 \cf3 Delegate\cf0 \cf3 Sub\cf0 CallBackDelegate(\cf3 ByVal\cf0 data \cf3 As\cf0 EventData)\par ??\par ??\cf3 Public\cf0 \cf3 Class\cf0 Native\par ?? <DllImport(\cf4 "NativeLib.dll"\cf0 )> _\par ?? \cf3 Public\cf0 \cf3 Shared\cf0 \cf3 Sub\cf0 fnNativeAPI(\cf3 ByVal\cf0 i \cf3 As\cf0 \cf3 Integer\cf0 , _\par ?? <MarshalAs(UnmanagedType.FunctionPtr)> \cf3 ByVal\cf0 callBack \cf3 As\cf0 CallBackDelegate)\par ?? \cf3 End\cf0 \cf3 Sub\par ??End\cf0 \cf3 Class}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _</p><p style="margin: 0px;"><span style="color: blue;">Public</span> <span style="color: blue;">Structure</span> EventData</p><p style="margin: 0px;"> <span style="color: blue;">Public</span> I <span style="color: blue;">As</span> <span style="color: blue;">Integer</span></p><p style="margin: 0px;"> <span style="color: blue;">Public</span> Message <span style="color: blue;">As</span> <span style="color: blue;">String</span></p><p style="margin: 0px;"><span style="color: blue;">End</span> <span style="color: blue;">Structure</span></p><p style="margin: 0px;"> </p><p style="margin: 0px;"><UnmanagedFunctionPointer(CallingConvention.Cdecl)> _</p><p style="margin: 0px;"><span style="color: blue;">Public</span> <span style="color: blue;">Delegate</span> <span style="color: blue;">Sub</span> CallBackDelegate(<span style="color: blue;">ByVal</span> data <span style="color: blue;">As</span> EventData)</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">Public</span> <span style="color: blue;">Class</span> Native</p><p style="margin: 0px;"> <DllImport(<span style="color: #a31515;">"NativeLib.dll"</span>)> _</p><p style="margin: 0px;"> <span style="color: blue;">Public</span> <span style="color: blue;">Shared</span> <span style="color: blue;">Sub</span> fnNativeAPI(<span style="color: blue;">ByVal</span> i <span style="color: blue;">As</span> <span style="color: blue;">Integer</span>, _</p><p style="margin: 0px;"> <MarshalAs(UnmanagedType.FunctionPtr)> <span style="color: blue;">ByVal</span> callBack <span style="color: blue;">As</span> CallBackDelegate)</p><p style="margin: 0px;"> <span style="color: blue;">End</span> <span style="color: blue;">Sub</span></p><p style="margin: 0px;"><span style="color: blue;">End</span> <span style="color: blue;">Class</span></p></div><br /><br />And the application code:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red0\green128\blue0;}??\fs20 \cf1 Public\cf0 \cf1 Class\cf0 Form1\par ?? \cf1 Dim\cf0 _cb \cf1 As\cf0 CallBackDelegate\par ??\par ?? \cf1 Public\cf0 \cf1 Sub\cf0 \cf1 New\cf0 ()\par ??\par ?? \cf4 ' This call is required by the Windows Form Designer.\par ??\cf0 InitializeComponent()\par ??\par ?? \cf4 ' Add any initialization after the InitializeComponent() call.\par ??\cf0 _cb = \cf1 New\cf0 CallBackDelegate(\cf1 AddressOf\cf0 \cf1 Me\cf0 .Foo)\par ?? \cf1 End\cf0 \cf1 Sub\par ??\par ??\cf0 \cf1 Private\cf0 \cf1 Sub\cf0 Form1_Load(\cf1 ByVal\cf0 sender \cf1 As\cf0 System.Object, \cf1 ByVal\cf0 e \cf1 As\cf0 System.EventArgs) \cf1 Handles\cf0 \cf1 MyBase\cf0 .Load\par ?? \cf1 Dim\cf0 i \cf1 As\cf0 \cf1 Integer\cf0 = 200\par ?? \cf4 'call the native API, passing our .NET delegate\par ??\cf0 Native.fnNativeAPI(i, _cb)\par ?? \cf1 End\cf0 \cf1 Sub\par ??\par ??\cf0 \cf4 ' this is our callback function\par ??\cf0 \cf1 Private\cf0 \cf1 Sub\cf0 Foo(\cf1 ByVal\cf0 data \cf1 As\cf0 EventData)\par ?? Debug.WriteLine(data.I)\par ?? Debug.WriteLine(data.Message)\par ?? \cf1 End\cf0 \cf1 Sub\par ??End\cf0 \cf1 Class}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">Public</span> <span style="color: blue;">Class</span> Form1</p><p style="margin: 0px;"> <span style="color: blue;">Dim</span> _cb <span style="color: blue;">As</span> CallBackDelegate</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">Public</span> <span style="color: blue;">Sub</span> <span style="color: blue;">New</span>()</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">' This call is required by the Windows Form Designer.</span></p><p style="margin: 0px;"> InitializeComponent()</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">' Add any initialization after the InitializeComponent() call.</span></p><p style="margin: 0px;"> _cb = <span style="color: blue;">New</span> CallBackDelegate(<span style="color: blue;">AddressOf</span> <span style="color: blue;">Me</span>.Foo)</p><p style="margin: 0px;"> <span style="color: blue;">End</span> <span style="color: blue;">Sub</span></p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">Private</span> <span style="color: blue;">Sub</span> Form1_Load(<span style="color: blue;">ByVal</span> sender <span style="color: blue;">As</span> System.Object, <span style="color: blue;">ByVal</span> e <span style="color: blue;">As</span> System.EventArgs) <span style="color: blue;">Handles</span> <span style="color: blue;">MyBase</span>.Load</p><p style="margin: 0px;"> <span style="color: blue;">Dim</span> i <span style="color: blue;">As</span> <span style="color: blue;">Integer</span> = 200</p><p style="margin: 0px;"> <span style="color: green;">'call the native API, passing our .NET delegate</span></p><p style="margin: 0px;"> Native.fnNativeAPI(i, _cb)</p><p style="margin: 0px;"> <span style="color: blue;">End</span> <span style="color: blue;">Sub</span></p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">' this is our callback function</span></p><p style="margin: 0px;"> <span style="color: blue;">Private</span> <span style="color: blue;">Sub</span> Foo(<span style="color: blue;">ByVal</span> data <span style="color: blue;">As</span> EventData)</p><p style="margin: 0px;"> Debug.WriteLine(data.I)</p><p style="margin: 0px;"> Debug.WriteLine(data.Message)</p><p style="margin: 0px;"> <span style="color: blue;">End</span> <span style="color: blue;">Sub</span></p><p style="margin: 0px;"><span style="color: blue;">End</span> <span style="color: blue;">Class</span></p></div>Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-9138276058495209902008-09-30T19:24:00.000-04:002008-09-30T21:57:52.028-04:00Reverse P/InvokeWhile researching for ongoing/upcoming projects that will need approaches for interop between .NET and native code, specifically, for native code to call into .NET code, <a href="http://blogs.msdn.com/junfeng/archive/2008/01/28/reverse-p-invoke-and-exception.aspx">Reverse P/Invoke</a> has come to my attention as a viable option. Of course there is the official Microsoft recommendation to expose .NET classes as COM components, which are then callable from native code that talks COM.<br /><br />The Reverse P/Invoke approach allows native code to call into .NET delegate using a function pointer. So it could work well for my requirement, for which I need a way to fire an event from the native app to the .NET app, for instance, an application context change on the native side must be reflected on the .NET side.<br /><br />The blog by Junfeng, however, does not give a concrete example of such Reverse P/Invoke approach. So I came up with a POC, where I had a VS.NET solution with three projects: (1) a native console application (C++ project) (2) a managed class library (C# project) and (3) a mixed mode dll library with exported C++ function (C++/CLI project). <br /><br />So this POC is trying to simulate a native application (#1) that needs to notify managed code (#2) of data changes. I came up with a dll library compiled with /clr switch to handle the interop details. Both the native app and the managed code requires very minimum changes. <br /><br />On the .NET side, we have a managed class that has a Foo() function and a GetDelegate() function that hands out a delegate to Foo to its caller.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red43\green145\blue175;}??\fs20 \cf1 public\cf0 \cf1 class\cf0 \cf4 ManagedClass\par ??\cf0 \{\par ?? \cf1 private\cf0 CallBackDelegate _delegate;\par ??\par ?? \cf1 public\cf0 ManagedClass()\par ?? \{\par ?? _delegate = \cf1 new\cf0 CallBackDelegate(\cf1 this\cf0 .Foo);\par ?? \}\par ??\par ?? \cf1 public\cf0 CallBackDelegate GetDelegate()\par ?? \{\par ?? \cf1 return\cf0 _delegate;\par ?? \}\par ??\par ?? \cf1 public\cf0 \cf1 void\cf0 Foo(EventData data)\par ?? \{\par ?? \cf4 Debug\cf0 .WriteLine(data.I);\par ?? \cf4 Debug\cf0 .WriteLine(data.Message);\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">ManagedClass</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">private</span> CallBackDelegate _delegate;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> ManagedClass()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> _delegate = <span style="color: blue;">new</span> CallBackDelegate(<span style="color: blue;">this</span>.Foo);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> CallBackDelegate GetDelegate()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">return</span> _delegate;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">void</span> Foo(EventData data)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">Debug</span>.WriteLine(data.I);</p><p style="margin: 0px;"> <span style="color: #2b91af;">Debug</span>.WriteLine(data.Message);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br />The EventData is a data structure that shares the same binary layout as the one that will be created and marshaled from the native code.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;}??\fs20 [\cf3 StructLayout\cf0 (\cf3 LayoutKind\cf0 .Sequential, CharSet = \cf3 CharSet\cf0 .Unicode)]\par ??\cf4 public\cf0 \cf4 struct\cf0 \cf3 EventData\par ??\cf0 \{\par ?? \cf4 public\cf0 \cf4 int\cf0 I;\par ?? \cf4 public\cf0 \cf4 string\cf0 Message;\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[<span style="color: #2b91af;">StructLayout</span>(<span style="color: #2b91af;">LayoutKind</span>.Sequential, CharSet = <span style="color: #2b91af;">CharSet</span>.Unicode)]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">struct</span> <span style="color: #2b91af;">EventData</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">int</span> I;</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> Message;</p><p style="margin: 0px;">}</p></div><br /><br />And here is the delegate definition. Note the attribute UnmanagedFunctionPointer with the calling convention.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;}??\fs20 [\cf3 UnmanagedFunctionPointer\cf0 (\cf3 CallingConvention\cf0 .Cdecl)]\par ??\cf4 public\cf0 \cf4 delegate\cf0 \cf4 void\cf0 \cf3 CallBackDelegate\cf0 (EventData data);}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[<span style="color: #2b91af;">UnmanagedFunctionPointer</span>(<span style="color: #2b91af;">CallingConvention</span>.Cdecl)]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">delegate</span> <span style="color: blue;">void</span> <span style="color: #2b91af;">CallBackDelegate</span>(EventData data);</p></div><br /><br />In the mixed mode dll, here is the definition of the EventData data structure and function pointer:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red163\green21\blue21;\red0\green128\blue0;}??\fs20 \cf1 #pragma\cf0 \cf1 once\par ??\par ??#include\cf0 \cf4 <windows.h>\par ??\par ??\cf5 // data structure for the callback function\par ??\cf1 struct\cf0 EventData\par ??\{\par ??\tab \cf1 int\cf0 I;\par ??\tab TCHAR* Message; \par ??\};\par ??\par ??\cf5 // callback function prototype\par ??\cf1 typedef\cf0 \cf1 void\cf0 (*NativeToManaged)(EventData data);}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">#pragma</span> <span style="color: blue;">once</span></p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">#include</span> <span style="color: #a31515;"><windows.h></span></p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: green;">// data structure for the callback function</span></p><p style="margin: 0px;"><span style="color: blue;">struct</span> EventData</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">int</span> I;</p><p style="margin: 0px;"> TCHAR* Message; </p><p style="margin: 0px;">};</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: green;">// callback function prototype</span></p><p style="margin: 0px;"><span style="color: blue;">typedef</span> <span style="color: blue;">void</span> (*NativeToManaged)(EventData data);</p></div><br /><br />And the exported function is defined as in the following. Note how the .NET delegate gets invoked through the function pointer. <br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red0\green128\blue0;}??\fs20 \cf1 #define\cf0 INTEROPBRIDGE_API \cf1 __declspec\cf0 (\cf1 dllexport\cf0 )\par ??\par ??INTEROPBRIDGE_API \cf1 void\cf0 fnInteropBridge(EventData data)\par ??\{\par ??\tab ManagedLib::ManagedClass^ c = \cf1 gcnew\cf0 ManagedLib::ManagedClass();\par ??\tab IntPtr p = Marshal::GetFunctionPointerForDelegate(c->GetDelegate());\par ??\tab \par ??\tab NativeToManaged funcPointer = (NativeToManaged) p.ToPointer();\par ??\par ??\tab \cf4 // invoke the delegate\par ??\cf0 \tab funcPointer(data);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">#define</span> INTEROPBRIDGE_API <span style="color: blue;">__declspec</span>(<span style="color: blue;">dllexport</span>)</p><p style="margin: 0px;"> </p><p style="margin: 0px;">INTEROPBRIDGE_API <span style="color: blue;">void</span> fnInteropBridge(EventData data)</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> ManagedLib::ManagedClass^ c = <span style="color: blue;">gcnew</span> ManagedLib::ManagedClass();</p><p style="margin: 0px;"> IntPtr p = Marshal::GetFunctionPointerForDelegate(c->GetDelegate());</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> NativeToManaged funcPointer = (NativeToManaged) p.ToPointer();</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// invoke the delegate</span></p><p style="margin: 0px;"> funcPointer(data);</p><p style="margin: 0px;">}</p></div><br /><br />Now in the native app, I have code that creates a copy of EventData and invokes the .NET code through the exported dll function fnInteropBridge:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green128\blue0;\red255\green255\blue255;\red0\green0\blue255;\red0\green0\blue0;\red163\green21\blue21;}??\fs20 \cf1 // forward definition of the API function\par ??\cf3 void\cf0 fnInteropBridge(EventData data);\par ??\par ??\cf3 int\cf0 _tmain(\cf3 int\cf0 argc, _TCHAR* argv[])\par ??\{\par ??\tab EventData data;\par ??\tab data.I = 50;\par ??\tab data.Message = L\cf5 "Hello from native code!"\cf0 ;\par ??\par ??\tab fnInteropBridge(data);\par ??\tab \par ??\tab \cf3 return\cf0 0;\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: green;">// forward definition of the API function</span></p><p style="margin: 0px;"><span style="color: blue;">void</span> fnInteropBridge(EventData data);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">int</span> _tmain(<span style="color: blue;">int</span> argc, _TCHAR* argv[])</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> EventData data;</p><p style="margin: 0px;"> data.I = 50;</p><p style="margin: 0px;"> data.Message = L<span style="color: #a31515;">"Hello from native code!"</span>;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> fnInteropBridge(data);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">return</span> 0;</p><p style="margin: 0px;">}</p></div><br /><br />In summary, I like this approach it that it provides quite an easy and non-invasive way for native code to call into managed code. It should especially work well in my scenario, where application context changes initiated from the native app needs to be propagated to the managed code. Furthermore, besides polishing this up, I think I will add code to raise a .NET event from inside ManagedClass.Foo(). Then all interested .NET citizens on the managed app side can subscribe to it.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com17tag:blogger.com,1999:blog-362796945673239800.post-72745331322618239142008-09-23T23:09:00.000-04:002008-09-26T16:30:50.764-04:00Follow-up of PIAB and WCF ArticleSince publishing <a href="http://msdn.microsoft.com/en-us/magazine/cc136759.aspx">the MSDN article</a> on an approach to integrate PIAB into WCF, I have received quite a few feedback from folks using this approach in production. And I am glad to say it's working out successfully for those folks. <br /><br />As I <a href="http://tigerang.blogspot.com/2008/01/piab-and-wcf-article-published-on-msdn.html">mentioned earlier</a> in the comment, <a href="http://blogs.msdn.com/tomholl/">Tom Hollander</a> of p&p group has found an issue with our approach when two PIAB-enabled WCF services are hosted in the same IIS worker process. He graciously shared his code with us and I was able to reproduce the problem when the two WCF services were hosted in two separate AppDomains of the same IIS worker process. Although in production deployment scenarios, different WCF services would likely be hosted in separate processes or on different machines, I have still been wanting to figure out the root cause of this particular issue. Unfortunately with work and other things in my life, I just haven't had time until last week when I was finally able to sit down and focused on this problem. <br /><br />I have to admit this has been the most tedious debugging I have ever done as I was deep in the guts of WCF and CLR, inspecting dynamic types, JIT compiler, call stubs, etc. without a whole lot of background in this area - I only wish I were working for the CLR team :-) After a few days, I finally found out the cause of the problem, which is pretty close to my initial hunch. A sense of elation at last! <br /><br />Here it goes. <br /><br /><strong>1. The setup to repro the problem</strong><br />The setup is fairly straightforward. There are two WCF services, each implementing IService and IAnotherService respectively (the code is pretty much verbatim from Tom):<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;}??\fs20 [\cf3 ServiceContract\cf0 ]\par ??\cf4 public\cf0 \cf4 interface\cf0 \cf3 IService\par ??\cf0 \{\par ?? [\cf3 OperationContract\cf0 ]\par ?? Foo GetFoo(\cf4 int\cf0 id);\par ??\par ?? [\cf3 OperationContract\cf0 ]\par ?? \cf4 void\cf0 AddFoo(\cf4 int\cf0 i, [\cf3 NotNullValidator\cf0 ] Foo foo);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[<span style="color: #2b91af;">ServiceContract</span>]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">interface</span> <span style="color: #2b91af;">IService</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> [<span style="color: #2b91af;">OperationContract</span>]</p><p style="margin: 0px;"> Foo GetFoo(<span style="color: blue;">int</span> id);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">OperationContract</span>]</p><p style="margin: 0px;"> <span style="color: blue;">void</span> AddFoo(<span style="color: blue;">int</span> i, [<span style="color: #2b91af;">NotNullValidator</span>] Foo foo);</p><p style="margin: 0px;">}</p></div><br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;}??\fs20 [\cf3 ServiceContract\cf0 ]\par ??\cf4 public\cf0 \cf4 interface\cf0 \cf3 IAnotherService\par ??\cf0 \{\par ?? [\cf3 OperationContract\cf0 ]\par ?? \cf4 void\cf0 LogFoo(Foo foo);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[<span style="color: #2b91af;">ServiceContract</span>]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">interface</span> <span style="color: #2b91af;">IAnotherService</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> [<span style="color: #2b91af;">OperationContract</span>]</p><p style="margin: 0px;"> <span style="color: blue;">void</span> LogFoo(Foo foo);</p><p style="margin: 0px;">}</p></div><br /><br />Both services would be enabled with PIAB of course: <br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red163\green21\blue21;}??\fs20 [PolicyInjectionBehaviors.\cf3 PolicyInjectionBehavior\cf0 ]\par ??[\cf3 ValidationCallHandler\cf0 ]\par ??[\cf3 LogCallHandler\cf0 ]\par ??\cf4 public\cf0 \cf4 class\cf0 \cf3 Service\cf0 : \cf3 IService\par ??\cf0 \{\par ?? \cf4 private\cf0 \cf4 static\cf0 \cf3 Dictionary\cf0 <\cf4 int\cf0 , \cf3 Foo\cf0 > store = \cf4 new\cf0 \cf3 Dictionary\cf0 <\cf4 int\cf0 , \cf3 Foo\cf0 >();\par ?? \cf3 IAnotherService\cf0 anotherService;\par ??\par ?? \cf4 public\cf0 Service()\par ?? \{\par ?? \cf3 BasicHttpBinding\cf0 binding = \cf4 new\cf0 \cf3 BasicHttpBinding\cf0 ();\par ?? binding.SendTimeout = \cf4 new\cf0 \cf3 TimeSpan\cf0 (4, 0, 0);\par ??\par ?? anotherService = \cf4 new\cf0 AnotherServiceClient(binding, \cf4 new\cf0 \cf3 EndpointAddress\cf0 (\cf5 "http://localhost/AnotherTestService/AnotherTestService.svc"\cf0 ));\par ?? \}\par ??\par ?? \cf4 public\cf0 \cf4 void\cf0 AddFoo(\cf4 int\cf0 id, \cf3 Foo\cf0 foo)\par ?? \{\par ?? store[id] = foo;\par ?? anotherService.LogFoo(foo);\par ?? \}\par ??\par ?? \cf4 public\cf0 \cf3 Foo\cf0 GetFoo(\cf4 int\cf0 id)\par ?? \{\par ?? \cf4 if\cf0 (store.ContainsKey(id))\par ?? \{\par ?? anotherService.LogFoo(store[id]);\par ?? \cf4 return\cf0 store[id];\par ?? \}\par ?? \cf4 else\par ??\cf0 \cf4 return\cf0 \cf4 null\cf0 ;\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[PolicyInjectionBehaviors.<span style="color: #2b91af;">PolicyInjectionBehavior</span>]</p><p style="margin: 0px;">[<span style="color: #2b91af;">ValidationCallHandler</span>]</p><p style="margin: 0px;">[<span style="color: #2b91af;">LogCallHandler</span>]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">Service</span> : <span style="color: #2b91af;">IService</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">Dictionary</span><<span style="color: blue;">int</span>, <span style="color: #2b91af;">Foo</span>> store = <span style="color: blue;">new</span> <span style="color: #2b91af;">Dictionary</span><<span style="color: blue;">int</span>, <span style="color: #2b91af;">Foo</span>>();</p><p style="margin: 0px;"> <span style="color: #2b91af;">IAnotherService</span> anotherService;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> Service()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">BasicHttpBinding</span> binding = <span style="color: blue;">new</span> <span style="color: #2b91af;">BasicHttpBinding</span>();</p><p style="margin: 0px;"> binding.SendTimeout = <span style="color: blue;">new</span> <span style="color: #2b91af;">TimeSpan</span>(4, 0, 0);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> anotherService = <span style="color: blue;">new</span> AnotherServiceClient(binding, <span style="color: blue;">new</span> <span style="color: #2b91af;">EndpointAddress</span>(<span style="color: #a31515;">"http://localhost/AnotherTestService/AnotherTestService.svc"</span>));</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">void</span> AddFoo(<span style="color: blue;">int</span> id, <span style="color: #2b91af;">Foo</span> foo)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> store[id] = foo;</p><p style="margin: 0px;"> anotherService.LogFoo(foo);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: #2b91af;">Foo</span> GetFoo(<span style="color: blue;">int</span> id)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (store.ContainsKey(id))</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> anotherService.LogFoo(store[id]);</p><p style="margin: 0px;"> <span style="color: blue;">return</span> store[id];</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> <span style="color: blue;">else</span></p><p style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: blue;">null</span>;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red163\green21\blue21;}??\fs20 [PolicyInjectionBehaviors.\cf3 PolicyInjectionBehavior\cf0 ]\par ??[\cf3 ValidationCallHandler\cf0 ]\par ??[\cf3 LogCallHandler\cf0 ]\par ??\cf4 public\cf0 \cf4 class\cf0 \cf3 AnotherService\cf0 : IAnotherService\par ??\{\par ?? \cf4 public\cf0 \cf4 void\cf0 LogFoo(Foo foo)\par ?? \{\par ?? \cf3 Logger\cf0 .Write(\cf5 "LogFoo() called."\cf0 );\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[PolicyInjectionBehaviors.<span style="color: #2b91af;">PolicyInjectionBehavior</span>]</p><p style="margin: 0px;">[<span style="color: #2b91af;">ValidationCallHandler</span>]</p><p style="margin: 0px;">[<span style="color: #2b91af;">LogCallHandler</span>]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">AnotherService</span> : IAnotherService</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">void</span> LogFoo(Foo foo)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">Logger</span>.Write(<span style="color: #a31515;">"LogFoo() called."</span>);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br /><span style="color: #2b91af;">Foo</span> is simply a DataContract object that holds both an int and a string properties.<br /><br />Now the services would be hosted two AppDomains in the same IIS worker process. This is how it looks like on my Vista machine:<br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcu6AlVCAtAbRhEGvWA6aXQiC2qdlOSaT-yw8-1VLD0OtaflSkz0Q9htZcYiWjrLi85oLV_CgC_uW4hIMXzZj76FyVYM80XnqhzuuJ4Tqfv5zus7IEQuNz0fqI3GMPCvZoPxPHecM-37k/s1600-h/WCF_PIAB_IIS.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcu6AlVCAtAbRhEGvWA6aXQiC2qdlOSaT-yw8-1VLD0OtaflSkz0Q9htZcYiWjrLi85oLV_CgC_uW4hIMXzZj76FyVYM80XnqhzuuJ4Tqfv5zus7IEQuNz0fqI3GMPCvZoPxPHecM-37k/s400/WCF_PIAB_IIS.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5249646496821867042" /></a><br /><br />As you can see both services are in the DefaultAppPool. With both services set up, we can run the test harness, which first calls Service.AddFoo() and then Service.GetFoo(). The Service.Foo() is completed fine but Service.GetFoo() call fails with a System.Reflection.TargetException: Object does not match target. The stack trace is as follows:<br /><br /><pre><br />1017e344 79644832 System.Reflection.RuntimeMethodInfo.CheckConsistency(System.Object)<br />1017e350 793a4124 System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo, Boolean)<br />1017e39c 793a40a2 System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)<br />1017e3bc 0f96e699 Microsoft.Practices.EnterpriseLibrary.PolicyInjection.RemotingInterception.InterceptingRealProxy+<>c__DisplayClass1.<Invoke>b__0(Microsoft.Practices.EnterpriseLibrary.PolicyInjection.IMethodInvocation, Microsoft.Practices.EnterpriseLibrary.PolicyInjection.GetNextHandlerDelegate)<br />1017e3ec 0f968ed1 Microsoft.Practices.EnterpriseLibrary.PolicyInjection.HandlerPipeline.Invoke(Microsoft.Practices.EnterpriseLibrary.PolicyInjection.IMethodInvocation, Microsoft.Practices.EnterpriseLibrary.PolicyInjection.InvokeHandlerDelegate)<br />1017e404 0f968a2e Microsoft.Practices.EnterpriseLibrary.PolicyInjection.RemotingInterception.InterceptingRealProxy.Invoke(System.Runtime.Remoting.Messaging.IMessage)<br />1017e418 79374dc3 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(System.Runtime.Remoting.Proxies.MessageData ByRef, Int32)<br />1017e6b4 79f98b43 [TPMethodFrame: 1017e6b4] WcfPiabInstability.Services.<font color="red">IAnotherService.LogFoo</font>(WcfPiabInstability.Services.Foo)<br />1017e6c4 0efd08dc DynamicClass.<font color="red">SyncInvokeGetFoo</font>(System.Object, System.Object[], System.Object[])<br />1017e6d4 50b8d90b System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(System.Object, System.Object[], System.Object[] ByRef)<br />1017e74c 50b6d245 System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(System.ServiceModel.Dispatcher.MessageRpc ByRef)<br />1017e7a0 509137ad System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(System.ServiceModel.Dispatcher.MessageRpc ByRef)<br />1017e7e0 509136a6 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(System.ServiceModel.Dispatcher.MessageRpc ByRef)<br />1017e80c 50913613 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(System.ServiceModel.Dispatcher.MessageRpc ByRef)<br />1017e81c 50913459 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(System.ServiceModel.Dispatcher.MessageRpc ByRef)<br />1017e82c 50912257 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(System.ServiceModel.Dispatcher.MessageRpc ByRef)<br />1017e844 50911f8f System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean)<br />1017e888 509115ff System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(System.ServiceModel.Channels.RequestContext, Boolean, System.ServiceModel.OperationContext)<br />1017ea34 5090f8c9 System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(System.ServiceModel.Channels.RequestContext, System.ServiceModel.OperationContext)<br />1017ea78 5090f35e System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(System.IAsyncResult)<br />1017ea8c 5090f2f1 System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(System.IAsyncResult)<br />1017ea98 50232d68 System.ServiceModel.Diagnostics.Utility+AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult)<br />1017eac4 50904501 System.ServiceModel.AsyncResult.Complete(Boolean)<br />1017eb00 50992b36 System.ServiceModel.Channels.InputQueue`1+AsyncQueueReader[[System.__Canon, mscorlib]].Set(Item<System.__Canon>)<br />1017eb14 50992215 System.ServiceModel.Channels.InputQueue`1[[System.__Canon, mscorlib]].EnqueueAndDispatch(Item<System.__Canon>, Boolean)<br />1017eb7c 50991ffb System.ServiceModel.Channels.InputQueue`1[[System.__Canon, mscorlib]].EnqueueAndDispatch(System.__Canon, System.ServiceModel.Channels.ItemDequeuedCallback, Boolean)<br />1017eba4 5091d7e5 System.ServiceModel.Channels.SingletonChannelAcceptor`3[[System.__Canon, mscorlib],[System.__Canon, mscorlib],[System.__Canon, mscorlib]].Enqueue(System.__Canon, System.ServiceModel.Channels.ItemDequeuedCallback, Boolean)<br />1017ebc8 50977b7e System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(System.ServiceModel.Channels.HttpRequestContext, System.ServiceModel.Channels.ItemDequeuedCallback)<br />1017ec0c 5094f396 System.ServiceModel.Activation.HostedHttpTransportManager.HttpContextReceived(System.ServiceModel.Activation.HostedHttpRequestAsyncResult)<br />1017ec50 5094e4cf System.ServiceModel.Activation.HostedHttpRequestAsyncResult.HandleRequest()<br />1017ec68 5094defd System.ServiceModel.Activation.HostedHttpRequestAsyncResult.BeginRequest()<br />1017eca4 5094dea5 System.ServiceModel.Activation.HostedHttpRequestAsyncResult.OnBeginRequest(System.Object)<br />1017ecd0 50903c3c System.ServiceModel.Channels.IOThreadScheduler+CriticalHelper+WorkItem.Invoke2()<br />1017ed0c 50903b26 System.ServiceModel.Channels.IOThreadScheduler+CriticalHelper+WorkItem.Invoke()<br />1017ed20 50903ab5 System.ServiceModel.Channels.IOThreadScheduler+CriticalHelper.ProcessCallbacks()<br />1017ed54 5090390f System.ServiceModel.Channels.IOThreadScheduler+CriticalHelper.CompletionCallback(System.Object)<br />1017ed80 5090388b System.ServiceModel.Channels.IOThreadScheduler+CriticalHelper+ScheduledOverlapped.IOCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)<br />1017ed8c 50232e1f System.ServiceModel.Diagnostics.Utility+IOCompletionThunk.UnhandledExceptionFrame(UInt32, UInt32, System.Threading.NativeOverlapped*)<br />1017edc0 79405534 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)<br />1017ef60 79e7c74b [GCFrame: 1017ef60] <br />1017f0b8 79e7c74b [ContextTransitionFrame: 1017f0b8] <br /></pre><br /><br /><strong>2. Culprit - a subtle bug in the CLR?</strong><br />What is going on here? The exception is being thrown by System.Reflection.RuntimeMethodInfo.CheckConsistency() but the problem happened earlier. Notice the red in those two lines of the stack trace. DynamicClass.SyncInvokeGetFoo is the function generated by WCF using Lightweight Code Gen (LCG) to facilitate the GetFoo() call, which is in IService definition. But the next one on the call stack somehow becomes IAnotherService.LogFoo() - please be reminded that IAnotherService.LogFoo() call on the stack here is not to be confused with the one inside the Service.GetFoo() as the call hasn't reached to the actual object yet when exception happens. Using SOS command !clrstack -p and !dumpobject reveals that the object reference in the call context here is the PIAB proxy to the Service object (tp->rp->real object), which implements IService but not IAnotherService. The mismatch simply didn't manifest into an exception until later in System.Reflection.RuntimeMethodInfo.CheckConsistency(). So where the exception is thrown is not that important. We need to understand why IService.GetFoo() suddenly becomes IAnotherService.LogFoo().<br /><br />Let's review the high level picture of how the calls are being dispatched from the client site (we will only consider synchronous calls for now):<br /><br /><ol><br /><li>Client makes a call by sending an XML message to the WCF service</li><br /><li>WCF processes the message in the pipeline before it finally dispatches the call through System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke() as shown on the stack trace. System.ServiceModel.Dispatcher.InvokerUtil uses LCG to generate a delegate SyncInvokeXXXX, where XXXX is the target method name and SyncInvoke stands for synchronous call. SyncMethodInvoker.Invoke() simply passes control to SyncInvokeXXXX(). This part is not that difficult to figure out by using Reflector. </li><br /><li>CLR takes the buck from here. Starting with 2.0, .NET uses dispatch stub to handle interface calls. The runtime figures out the method disptach token and sends it along with the target object reference to mscorwks!ResolveWorkerAsmStub, which calls mscorwks!VirtualCallStubManager::ResolveWorkerStatic and then the heavy lifting mscorwks!VirtualCallStubManager::ResolveWorker to figure out the stub that contains the assembly code to make the actual call. </li><br /><li>PIAB proxy gets called. This is where the injection magic happens and service method finally gets called.</li><br /></ol><br /><br />The dispatch token is a 32 bit integer with hi as the type id of the interface and lo as the slot number of the method as shown in the Rotor(SSCLI) source code:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red0\green128\blue0;}??\fs20 \par ?? \cf3 static\cf0 \cf3 const\cf0 UINT_PTR MASK_TYPE_ID = 0x0000FFFF;\par ?? \cf3 static\cf0 \cf3 const\cf0 UINT_PTR MASK_SLOT_NUMBER = 0x0000FFFF;\par ??\par ?? \cf3 static\cf0 \cf3 const\cf0 UINT_PTR SHIFT_TYPE_ID = 0x10;\par ?? \cf3 static\cf0 \cf3 const\cf0 UINT_PTR SHIFT_SLOT_NUMBER = 0x0;\par ??\par ?? \cf4 //------------------------------------------------------------------------\par ??\cf0 \cf4 // Combines the two values into a single 32-bit number.\par ??\cf0 \cf3 static\cf0 UINT_PTR CreateToken(UINT32 typeID, UINT32 slotNumber)\par ?? \{\par ?? LEAF_CONTRACT;\par ?? CONSISTENCY_CHECK(((UINT_PTR)typeID & MASK_TYPE_ID) == (UINT_PTR)typeID);\par ?? CONSISTENCY_CHECK(((UINT_PTR)slotNumber & MASK_SLOT_NUMBER) == (UINT_PTR)slotNumber);\par ?? \cf3 return\cf0 ((((UINT_PTR)typeID & MASK_TYPE_ID) << SHIFT_TYPE_ID) |\par ?? (((UINT_PTR)slotNumber & MASK_SLOT_NUMBER) << SHIFT_SLOT_NUMBER));\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">static</span> <span style="color: blue;">const</span> UINT_PTR MASK_TYPE_ID = 0x0000FFFF;</p><p style="margin: 0px;"><span style="color: blue;">static</span> <span style="color: blue;">const</span> UINT_PTR MASK_SLOT_NUMBER = 0x0000FFFF;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">static</span> <span style="color: blue;">const</span> UINT_PTR SHIFT_TYPE_ID = 0x10;</p><p style="margin: 0px;"><span style="color: blue;">static</span> <span style="color: blue;">const</span> UINT_PTR SHIFT_SLOT_NUMBER = 0x0;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: green;">//------------------------------------------------------------------------</span></p><p style="margin: 0px;"><span style="color: green;">// Combines the two values into a single 32-bit number.</span></p><p style="margin: 0px;"><span style="color: blue;">static</span> UINT_PTR CreateToken(UINT32 typeID, UINT32 slotNumber)</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> LEAF_CONTRACT;</p><p style="margin: 0px;"> CONSISTENCY_CHECK(((UINT_PTR)typeID & MASK_TYPE_ID) == (UINT_PTR)typeID);</p><p style="margin: 0px;"> CONSISTENCY_CHECK(((UINT_PTR)slotNumber & MASK_SLOT_NUMBER) == (UINT_PTR)slotNumber);</p><p style="margin: 0px;"> <span style="color: blue;">return</span> ((((UINT_PTR)typeID & MASK_TYPE_ID) << SHIFT_TYPE_ID) |</p><p style="margin: 0px;"> (((UINT_PTR)slotNumber & MASK_SLOT_NUMBER) << SHIFT_SLOT_NUMBER));</p><p style="margin: 0px;">}</p></div><br /><br />Type ids are integer identifiers to represent types in an AppDomain. Slot numbers are integer values representing entries of interface methods in the method table. In the example I am using, IService has a type id of 0x0003 and AddFoo has a slot number of 0x0001, therefore yielding a token of 0x00030001. IService.GetFoo has a dispatch token of 0x00030000. And IAnotherService.LogFoo also has a token of 0x0003000. You see that both IService and IAnotherService, living in two AppDomains, happen to have the same type id: 0x0003. It is a coincidence, but not to be ignored. <br /><br />The reason why the dispatch token is critical here is because of the following:<br /><br /><ul><br /><li>all interface disptach stubs for our PIAB enabled services are handled by the VirtualCallStubManager in the shared domain of the process.</li><br /><li>the stub manager keeps a hash table to cache the stub code using those two keys: token and object type. In our example, the object type is always a transparent proxy for PIAB-enabled services. So effectively token becomes the only key that matters.</li><br /><li>the heavy lifting mscorwks!VirtualCallStubManager::ResolveWorker() is responsible for generating and caching the stub. Obviously it always first checks if there is a cached entry. If one is found using the keys, that entry will be returned. <br /></li><br /></ul><br /><br />When our unit test harness calls Service.AddFoo, which internally calls AnotherService.LogFoo, two dispatch stubs are created and cached by the shared stub manager, with tokens 0x00030001 and 0x00030000 as the effective key respectively. Now the unit test makes a different call Service.GetFoo. Note that IService.GetFoo also has the dispatch token as 0x00030000, same as IAnotherSevice.LogFoo, despite the fact they are two types in different domains. The stub manager of the shared domain hands out the previously cached dispatch stub for IAnotherService.LogFoo. This is why we saw the strange call stack above and the call eventually fails.<br /><br />To further prove this, I changed the definition of IAnotherService to the following to include 5 additional functions as fillers to the method table slots:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red0\green128\blue0;}??\fs20 [\cf3 ServiceContract\cf0 ]\par ??\cf4 public\cf0 \cf4 interface\cf0 \cf3 IAnotherService\par ??\cf0 \{\par ?? \cf5 // fillers \par ??\cf0 \cf4 void\cf0 Filler1();\par ?? \cf4 void\cf0 Filler2();\par ?? \cf4 void\cf0 Filler3();\par ?? \cf4 void\cf0 Filler4();\par ?? \cf4 void\cf0 Filler5();\par ??\par ?? [\cf3 OperationContract\cf0 ]\par ?? \cf4 void\cf0 LogFoo(Foo foo);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[<span style="color: #2b91af;">ServiceContract</span>]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">interface</span> <span style="color: #2b91af;">IAnotherService</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: green;">// fillers </span></p><p style="margin: 0px;"> <span style="color: blue;">void</span> Filler1();</p><p style="margin: 0px;"> <span style="color: blue;">void</span> Filler2();</p><p style="margin: 0px;"> <span style="color: blue;">void</span> Filler3();</p><p style="margin: 0px;"> <span style="color: blue;">void</span> Filler4();</p><p style="margin: 0px;"> <span style="color: blue;">void</span> Filler5();</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">OperationContract</span>]</p><p style="margin: 0px;"> <span style="color: blue;">void</span> LogFoo(Foo foo);</p><p style="margin: 0px;">}</p></div><br /><br />The purpose is to alter the slot number of IAnotherService.LogFoo to avoid the token clash. As shown in the following Windbg snippet, I did indeed get a token of 0x00030005 as opposed to the 0x0003000 that I had earlier: <br /><br /><pre><br />eax=<font color="red">00000003</font> ebx=0e3f6228 ecx=79e89e87 edx=00000003 esi=<font color="red">00000005</font> edi=01c45b18<br />eip=79eb45cf esp=1057dd4c ebp=1057dd80 iopl=0 nv up ei pl nz ac po cy<br />cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213<br />mscorwks!VirtualCallStubManager::GetCallStub+0x34:<br />79eb45cf c1e010 <font color="green">shl eax,10h</font><br />0:025> p<br />eax=<font color="red">00030000</font> ebx=0e3f6228 ecx=79e89e87 edx=00000003 esi=<font color="red">00000005</font> edi=01c45b18<br />eip=79eb45d2 esp=1057dd4c ebp=1057dd80 iopl=0 nv up ei pl nz ac pe nc<br />cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216<br />mscorwks!VirtualCallStubManager::GetCallStub+0x37:<br />79eb45d2 0bf0 <font color="green">or esi,eax</font><br />0:025> p<br />eax=00030000 ebx=0e3f6228 ecx=79e89e87 edx=00000003 esi=<font color="red">00030005</font> edi=01c45b18<br />eip=79eb45d4 esp=1057dd4c ebp=1057dd80 iopl=0 nv up ei pl nz na pe nc<br />cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206<br /></pre><br /><br />And the CLR happily makes all the calls without the dreadful exception! <br /><br /><strong>3. Summary</strong><br />It is interesting to see how the CLR team may have tried to safeguard the clashing of the keys by using both token and the object type. In our case, the object type, being the transparent proxy for PIAB-enabled services, takes that key out of the equation. The token, although having HI corresponding to the interface type and LO to the slot number, is scoped within the AppDomain where the type is loaded. The fewer the number of types and the fewer the methods of the operation contracts are defined for the WCF service in each AppDomain, the bigger the odds key clashing like this will happen. <br /><br />So what is the solution? Could other interface method related values such as MethodDesc, which seems to be unique across app domains, be a better candidate? That is the question for the CLR team.<br /><br />As for us who want to integrate PIAB into WCF while minimizing development team efforts, we should be fine by hosting WCF services in separate processes. If you really, really want to host everything in one IIS worker process like our contrived example, you can get around this issue by not using the default PIAB interception mechanism. Suppose you can come up with a LCG mechanism to generate one dynamic proxy (instead of System.Runtime.Remoting.Proxies.__TransparentProxy) for each target. Having a different object type as each service's PIAB intercepting proxy will therefore avoid the key clashing. <br /><br /><strong>4. Tools etc.</strong><br />Debugging is always an enlightening experience. And I can't imagine what life is going to be without Windbg and Reflector. Visual Studio 2008 is cool since you can configure it to debug into .NET framework code. But if the symbols are not available for the module you want to investigate or you need to dig deeper below the FCL layer, Reflector and Windbg are just indispensable. <br /><br />Also, having SSCLI source code is absolutely wonderful. Without it, debugging through machine code in windbg would be a lot harder. <br /><br />Most of the SSCLI code for this exercise is located inside <a href="http://www.koders.com/cpp/fidD25A3F8FFAB28F672B5E53EE4A4BF6D1C174E149.aspx">virtualcallstub.cpp</a> file.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com3tag:blogger.com,1999:blog-362796945673239800.post-57772170996606901082008-09-09T09:46:00.000-04:002008-09-22T18:21:02.305-04:00Interview QuestionsHaving a list of questions with standard answers to score candidates during technical screenings certainly has its benefits, especially maintaining consistencies across different interviewers. However if the interviewer simply compares the candidate's answers to the official ones, then she is not doing her job. Interview is an interactive process and should be leveraged as such. Many seemingly easy questions can be extended to discussions at both broader and deeper levels. You will get a better picture of candidate's overall skills and experiences this way. For example, there is usually a basic question on the differences between value and reference types. This question can be extended to boxing/unboxing, and the scenarios where boxing/unboxing can occur and the performance implications, which can be a good starting point to test candidate's knowledge on generics. There are semantic implications of boxing and unboxing as well. A boxed integer, e.g. is a brand new object with a copy of the initial integer value. The following is not allowed by the C# compiler:<br /><br />int i = 1; // class instance field<br /><br />lock(i)<br />{<br />//...<br />} <br /><br />You could hack it with:<br /><br />lock ((object)i)<br />{<br />//...<br />}<br /><br />But you will not get the intended lock semantics. I will leave it to you to answer why that is the case. If you know the answer, you will see that you can evaluate the candidate's knowledge of threading and synchronization, besides boxing/unboxing.<br /><br />And there is more! A related topic is passing by reference vs. passing by value in function calls (I had a <a href="http://tigerang.blogspot.com/2007/01/byref-snafu.html">previous blog</a> in this area). And you can take the initial question and extend it to a discussion on heap vs. stack and further on GC. <br /><br />So you see how one easy question can be extended quite a bit and become the vehicle for you to test candidate's overall knowledge. Of course you need to be sensitive to time and not go off to all directions. You usually will get a sense of the candidate's knowledge half way through and can decide whether to go further or not from that point.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-28732491824781043362008-06-20T15:46:00.000-04:002008-06-20T16:28:41.873-04:00I Am BackFor the last three months, I was working for a client on envisioning the next generation architecture of a highly impactful desktop application. Despite the short 3 months time, it was nevertheless a winding journey. Because of the impact of the application, business and politics drove a lot of the architecture decisions, and a lot of long nights too. Now that the project is on hold until the next phase, I can get back inside Visual Studio and be really technical for a short while.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-74364894678655826122008-03-14T16:31:00.000-04:002008-03-14T17:26:52.929-04:00Debug.Assert in ASP.NET ApplicationMy most recent project was an ASP.NET 2.0 application developed in Visual Studio 2005. During the first build and deployment into dev integration environment, everything went pretty well except for one page. Request for this page was just hanging. I started to investigate and I captured a hang dump using adplus. In windbg, I found out the longest running thread. Suspecting that is the thread that was hanging, I looked at its CLR stack:<br /><br /><pre><br />0:001> !clrstack<br />*********************************************************************<br />* Symbols can not be loaded because symbol path is not initialized. *<br />* *<br />* The Symbol Path can be set by: *<br />* using the _NT_SYMBOL_PATH environment variable. *<br />* using the -y <symbol_path> argument when starting the debugger. *<br />* using .sympath and .sympath+ *<br />*********************************************************************<br />PDB symbol for mscorwks.dll not loaded<br />OS Thread Id: 0x2fac (1)<br />ESP EIP <br />006bf414 77f88a77 [NDirectMethodFrameStandalone: 006bf414] Microsoft.Win32.SafeNativeMethods.MessageBox(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)<br />006bf430 7a4f839a System.Diagnostics.AssertWrapper.ShowMessageBoxAssert(System.String, System.String, System.String)<br />006bf460 7a4fabb2 System.Diagnostics.DefaultTraceListener.Fail(System.String, System.String)<br />006bf4a0 7a4faad7 System.Diagnostics.DefaultTraceListener.Fail(System.String)<br />006bf4a4 7a500a22 System.Diagnostics.TraceInternal.Fail(System.String)<br />006bf4e0 7a6e2523 System.Diagnostics.TraceInternal.Assert(Boolean, System.String)<br />006bf4e4 7a4fa6cb System.Diagnostics.Debug.Assert(Boolean, System.String)<br />006bf4e8 1ae8b7c9 CondosPaymentPageBase.GetResidentData()<br />006bf528 1ae8b567 CondosPaymentPageBase.OnLoad(System.EventArgs)<br />006bf534 66143ad0 System.Web.UI.Control.LoadRecursive()<br />006bf548 66155106 System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)<br />006bf700 66154a1b System.Web.UI.Page.ProcessRequest(Boolean, Boolean)<br />006bf738 66154967 System.Web.UI.Page.ProcessRequest()<br />006bf770 66154887 System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)<br />006bf778 6615481a System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)<br />006bf78c 1bb8ccae ASP.payment_3_history_aspx.ProcessRequest(System.Web.HttpContext)<br />006bf798 65ff27d4 System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()<br />006bf7cc 65fc15b5 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)<br />006bf80c 65fd32e0 System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception)<br />006bf85c 65fc0225 System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)<br />006bf878 65fc550b System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)<br />006bf8ac 65fc5212 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)<br />006bf8b8 65fc3587 System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)<br />006bfa68 79f35ee8 [ContextTransitionFrame: 006bfa68] <br />006bfab8 79f35ee8 [GCFrame: 006bfab8] <br />006bfc10 79f35ee8 [ComMethodFrame: 006bfc10] <br /></pre><br /><br />And the native stack looked like this:<br /><br /><pre><br />0:001> kb<br />ChildEBP RetAddr Args to Child <br />006bf1b0 77e4f9f0 50000018 00000003 00000003 NTDLL!ZwRaiseHardError+0xb<br />006bf20c 77e34398 00d3b684 00d3c298 00040212 USER32!ServiceMessageBox+0x16b<br />006bf35c 77e339cb 006bf36c 006bfa68 00000028 USER32!MessageBoxWorker+0x10a<br />006bf3b4 77e4fa54 00000000 00d3b684 00d3c298 USER32!MessageBoxExW+0x77<br />*** WARNING: Unable to verify checksum for System.ni.dll<br />006bf43c 7a4fad1d 00000000 00d1c4fc 00d28944 USER32!MessageBoxW+0x49<br />006bf454 7a4fabb2 00000000 00d24bf8 04cfda28 System_ni+0xbad1d<br />006bf494 7a4faad7 00000000 7a500a22 00000000 System_ni+0xbabb2<br />006bf4d8 7a6e2523 7a4fa6cb 1ae8b7c9 00d113c0 System_ni+0xbaad7<br />00000000 00000000 00000000 00000000 00000000 System_ni+0x2a2523<br /></pre><br /><br />At this point, it was clear to me that some data condition in dev int environment had caused Debug.Assert to fail. The assert failure message box was waiting to be closed. But on an IIS box with the ASP worker process running in the context of a service account, this UI interaction is simply not going to work. <br /><br />As you may know, Debug.Assert will be left out of the IL code by compilers for release build. Our MSBuild script that produced the deployment package did have the "release" switch turned on. So what went wrong? It turned out that <a href="http://msdn2.microsoft.com/en-us/asp.net/aa336619.aspx">the web deployment </a> project we had, "Generate debug information" option was checked. I initially thought this would give me pdb files. But obviously this meant a debug build, irrespective of the "Configuration=Release" setting for the MSBuild script.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-61498801581063821762008-01-13T11:27:00.000-05:002008-06-18T22:55:19.883-04:00PIAB and WCF Article Published on MSDN MagazineThe article that I have coauthored with my Avanade colleague <a href="http://www.mtelligent.com/">David San Filippo</a> is now published in the February issue of MSDN magazine. If you have subscribed to MSDN, you should have received it in your mail box by now. The online link is <a href="http://msdn.microsoft.com/en-us/magazine/cc136759.aspx">here</a>. Basically the article details how to integrate PIAB into WCF via .NET configuration or attribute so developers would not have to write code to apply the goodness of PIAB to WCF. <br /><br />It's been an absolutely wonderful experience working with MSDN editors, Howard Dierking, Nancy Michell and Debra Kelly so I'd like to extend a big "thank you" to them from here. My colleague David is one of the smartest developers I have worked with; it only took a few weekends for us to come up with a draft of this article after the brainstorming back in last August.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com7tag:blogger.com,1999:blog-362796945673239800.post-38990536985593606172008-01-09T15:17:00.000-05:002008-01-09T15:37:11.156-05:00A Good Book on WindbgSince I have never worked for any Microsoft product team, a good way for me to get internal system knowledge is through debugging, using tools like Windbg (Visual Studio as an IDE can be good for debugging too, but is limited in functionalities compared with Windbg). <br /><br />Over the years, I have gained my Windbg skills by reading blogs (thanks to those who post them) and just practicing it on my own whenever I have a chance. Most of the debugging books don't have enough coverage in this area. So I was really excited after I found out that Addison Wesley has published this <a href="http://www.bookpool.com/sm/0321374460">Advanced Windows Debugging</a> book. You should read this book if you are seriously thinking about learning Windbg.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-19112464137417127842007-12-18T14:36:00.000-05:002007-12-18T16:15:47.693-05:00Debugging MAPI ProblemsI have been quiet here for the last month or so, one of the reasons being that I have been bogged down by a lot of application issues in production. The most recent one had to do with 4 Windows services on the production database server. Those services, developed in VB long time ago, run as work flow engines, polling/querying the database on a timer, changing the state of work flow items according to business rules and emailing the next person in the queue using MAPI function. Those services were developed at different times but they have very similar functionalities. Most of the source code was just copied from the very first one unfortunately. <br /><br />About a week ago, those services started to hang - users are usually first to tell because the work flow items don't get routed after a couple of minutes as it is supposed to. I have been doing analysis with the hang dumps created by ADPLUS, arriving at the conclusion that MAPI call was the culprit. However, I haven't figured out exactly what in the MAPI call caused the problem due to lack of MAPI debug symbols. I am going to blog the debugging result anyway - hopefully someone with similar experiences with MAPI can point out the issue. <br /><br />The following shows the stack of the main thread of the services that are hanging:<br /><br /><code><br />0:000> kb<br />ChildEBP RetAddr Args to Child <br />0012e64c 7c59a072 000005a4 00000000 00000000 NTDLL!ZwWaitForSingleObject+0xb<br />0012e674 7c57b3e9 000005a4 ffffffff 00000000 KERNEL32!WaitForSingleObjectEx+0x71<br />*** ERROR: Symbol file could not be found. Defaulted to export symbols for MSMAPI32.DLL - <br />0012e684 35ffbee7 000005a4 ffffffff 0de623a0 KERNEL32!WaitForSingleObject+0xf<br />WARNING: Stack unwind information not available. Following frames may be wrong.<br />0012e6fc 35f789e7 00000000 02dad738 02da27d8 MSMAPI32!FPropContainsProp+0x2612<br />*** ERROR: Symbol file could not be found. Defaulted to export symbols for MAPI32.DLL - <br />0012e744 61dd2d09 00000000 02da33f8 02da2228 MSMAPI32!MAPILogonEx+0xa7<br />*** ERROR: Symbol file could not be found. Defaulted to export symbols for CDO.DLL - <br />0012e76c 0dd025ea 00000000 02da33f8 02da2228 MAPI32!MAPILogonEx+0x79<br />00000000 00000000 00000000 00000000 00000000 CDO!DllUnregisterServer+0x10eea<br /></code><br /><br />To see what WaitForSingleObject() is waiting on:<br /><br /><code><br />0:000> !handle 5a4 f<br />Handle 000005a4<br /> Type Mutant<br /> Attributes 0<br /> GrantedAccess 0x120001:<br /> ReadControl,Synch<br /> QueryState<br /> HandleCount 6<br /> PointerCount 12<br /> Name \BaseNamedObjects\4D4150494C6F676F6E0070B872C47FD7101B8BEA00AA0038C699_S-1-5-21-1346851753-1016681682-1202159320-1002<br /> Object specific information<br /> Mutex is Owned<br /></code><br /><br />If the export symbols are close enough, the MAPILogonEx() is waiting on this mutex, which has a name suffixed with a SID, which belongs to the service account the services are running as. The SID suffix seems to reenforce the conjecture that MAPILogonEx() is being called.<br /><br />While 3 out of the 4 services have their main threads like that, the forth one is different:<br /><br /><code><br />0:000> kb<br />ChildEBP RetAddr Args to Child <br />0012de34 7c59a072 00000664 00000000 00000000 NTDLL!ZwWaitForSingleObject+0xb<br />0012de5c 7c57b3e9 00000664 ffffffff 00000000 KERNEL32!WaitForSingleObjectEx+0x71<br />*** ERROR: Symbol file could not be found. Defaulted to export symbols for MSMAPI32.DLL - <br />0012de6c 35f98779 00000664 ffffffff 00000000 KERNEL32!WaitForSingleObject+0xf<br />WARNING: Stack unwind information not available. Following frames may be wrong.<br />0012de94 35f98691 00000201 0012deec 0012decc MSMAPI32!DDLUnwrapObjectEx+0xc65<br />*** ERROR: Symbol file could not be found. Defaulted to export symbols for EMSABP32.DLL - <br />0012dec4 35ae3c6e 00000004 00000201 00000000 MSMAPI32!DDLUnwrapObjectEx+0xb7d<br />0012df10 35ae3bbd 023e2144 00000001 023e2170 EMSABP32!ABProviderInit+0x2611<br />0012e028 35f72512 35f72570 360b6700 35f755d0 EMSABP32!ABProviderInit+0x2560<br />00000000 00000000 00000000 00000000 00000000 MSMAPI32!DDCS_Enter+0x16<br /></code><br /><br />This one is waiting on an event object:<br /><br /><code><br />0:000> !handle 664 f<br />Handle 00000664<br /> Type Event<br /> Attributes 0<br /> GrantedAccess 0x1f0003:<br /> Delete,ReadControl,WriteDac,WriteOwner,Synch<br /> QueryState,ModifyState<br /> HandleCount 2<br /> PointerCount 4<br /> Name <none><br /> No object specific information available<br /></code><br /><br />I suspect this process is the owner of the mutex, although I didn't have a chance to create a kernel dump to verify. <br /><br /><code><br />0:000> !handle 370 f<br />Handle 00000370<br /> Type Mutant<br /> Attributes 0<br /> GrantedAccess 0x1f0001:<br /> Delete,ReadControl,WriteDac,WriteOwner,Synch<br /> QueryState<br /> HandleCount 6<br /> PointerCount 12<br /> Name \BaseNamedObjects\4D4150494C6F676F6E0070B872C47FD7101B8BEA00AA0038C699_S-1-5-21-1346851753-1016681682-1202159320-1002<br /> Object specific information<br /> Mutex is Owned<br /></code><br /><br />We have moved the 4 services from the database server (Win2K) to a different box (Win2K3) and there hasn't been any hang problems so far, which suggests that the MAPI problem is probably local to that machine, rather than having to do with the exchange server. Here is the detail of MAPI32.dll on the database server:<br /><br /><code><br />61dd0000 61df1000 MAPI32 (export symbols) MAPI32.DLL<br /> Loaded symbol image file: MAPI32.DLL<br /> Image path: C:\Program Files\Common Files\System\MSMAPI\1033\MAPI32.DLL<br /> Image name: MAPI32.DLL<br /> Timestamp: Wed Jan 13 11:08:14 1999 (369CC4EE)<br /> CheckSum: 0002A798<br /> ImageSize: 00021000<br /> File version: 1.0.2536.0<br /> Product version: 1.0.2536.0<br /> File flags: 2 (Mask 3F) Pre-release<br /> File OS: 40004 NT Win32<br /> File type: 2.0 Dll<br /> File date: 00000000.00000000<br /> Translations: 0409.04e4<br /> CompanyName: Microsoft Corporation<br /> ProductName: Microsoft Exchange<br /> InternalName: MAPI32<br /> OriginalFilename: MAPI32.DLL<br /> ProductVersion: 1.0<br /> FileVersion: 1.0.2536.0<br /> FileDescription: Extended MAPI 1.0 for Windows NT<br /> LegalCopyright: Copyright © 1986-1999 Microsoft Corp. All rights reserved.<br /> LegalTrademarks: Microsoft® and Windows® are registered trademarks of Microsoft Corporation.<br /> Comments: Service Pack 3<br /></code><br /><br />Now back to investigating ...Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-34011105241273467002007-10-31T17:14:00.000-04:002007-11-03T21:42:36.544-04:00Thread PoolI mentioned in <a href="http://tigerang.blogspot.com/2007/10/net-code-instrumentation-independent-of.html">my last post</a> that I would cover some interesting topics that all relate to thread. Now here is the second one, on thread pool.<br /><br />Thread pool is an efficient way to achieve concurrency since it saves the cost of creating new threads all the time by reusing threads in a pool. To get a good understanding of how to leverage ThreadPool class in the .NET framework, you can start by reading Jeffery Richter's good <a href="http://msdn.microsoft.com/msdnmag/issues/03/06/NET/">article on MSDN</a>. However that was .NET 1.1. In .NET 2.0, the ThreadPool class supports a total of 4 types of uses as opposed to 3, which can be seen clearly from the enumeration in SSCLI2: <br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;}??\fs20 \cf1 enum\cf0 ThreadpoolThreadType\par ??\{\par ?? WorkerThread,\par ?? CompletionPortThread,\par ?? WaitThread,\par ?? TimerMgrThread\par ??\};}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">enum</span> ThreadpoolThreadType</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> WorkerThread,</p><p style="margin: 0px;"> CompletionPortThread,</p><p style="margin: 0px;"> WaitThread,</p><p style="margin: 0px;"> TimerMgrThread</p><p style="margin: 0px;">};</p></div><br /><br />With the new ThreadPool class, we can<br /><br /><ul><br /><li>1) call a method on a worker thread by QueueUserWorkItem</li><br /><li>2) call a method on an IO Completion port thread by UnsafeQueueNativeOverlapped</li><br /><li>3) call a method on an IO Completion port thread when a kernel object is signaled by RegisterWaitForSingleObject</li> <br /><li>4) call a method on a worker thread by using System.Threading.Timer - note this is the API seemingly irrelevant to ThreadPool</li><br /></ul><br /><br />While IO Completion Port (IOCP) is not new to native Windows, the inclusion of its API in thread pool is the latest feature added to .NET 2.0. IOCP is a mechanism on Windows to allow asynchronous IO. The IOCP threads will be woken up and start code execution if IO operation is complete. The Win32 API for this is GetQueuedCompletionStatus, which essentially doesn't return (thread back to the pool) until IO operation is done. And more interestingly, IOCP isn't just limited to being used for IO - ThreadPool.UnsafeQueueNativeOverlapped can be used just like ThreadPool.QueueUserWorkItem. Here is a code snippet for queueing a delegate to the IOCP threads via Overlapped data structure:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red43\green145\blue175;}??\fs20 \cf1 unsafe\par ??\cf0 \{\par ?? \cf4 Overlapped\cf0 overlapped = \cf1 new\cf0 \cf4 Overlapped\cf0 (0, 0, \cf4 IntPtr\cf0 .Zero, \cf1 null\cf0 );\par ?? \cf4 NativeOverlapped\cf0 * pOverlapped = overlapped.Pack(IocpThreadProc, \cf1 null\cf0 );\par ?? \cf4 ThreadPool\cf0 .UnsafeQueueNativeOverlapped(pOverlapped);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">unsafe</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: #2b91af;">Overlapped</span> overlapped = <span style="color: blue;">new</span> <span style="color: #2b91af;">Overlapped</span>(0, 0, <span style="color: #2b91af;">IntPtr</span>.Zero, <span style="color: blue;">null</span>);</p><p style="margin: 0px;"> <span style="color: #2b91af;">NativeOverlapped</span>* pOverlapped = overlapped.Pack(IocpThreadProc, <span style="color: blue;">null</span>);</p><p style="margin: 0px;"> <span style="color: #2b91af;">ThreadPool</span>.UnsafeQueueNativeOverlapped(pOverlapped);</p><p style="margin: 0px;">}</p></div><br /><br />Note that unsafe keyword is used as native pointer is created. And here is the delegate:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red43\green145\blue175;}??\fs20 \cf1 unsafe\cf0 \cf1 static\cf0 \cf1 void\cf0 IocpThreadProc(\cf1 uint\cf0 x, \cf1 uint\cf0 y, \cf4 NativeOverlapped\cf0 * p)\par ??\{ \par ?? \cf1 try\par ??\cf0 \{\par ?? Sort();\par ?? \}\par ?? \cf1 finally\par ??\cf0 \{\par ?? \cf4 Overlapped\cf0 .Free(p);\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">unsafe</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> IocpThreadProc(<span style="color: blue;">uint</span> x, <span style="color: blue;">uint</span> y, <span style="color: #2b91af;">NativeOverlapped</span>* p)</p><p style="margin: 0px;">{ </p><p style="margin: 0px;"> <span style="color: blue;">try</span></p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> Sort();</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> <span style="color: blue;">finally</span></p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">Overlapped</span>.Free(p);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br />Note that Free must be called in the finally block to ensure that memory is not leaked.<br /><br />The philosophy of IOCP is that the optimal number of concurrent running threads should be equal to the number of processors, because anything more would introduce the overhead of context switches between different threads. Peeking into the <a href="http://www.koders.com/cpp/fid7316886C8F2DB23F271506CC01D8F0D3ABB008F4.aspx">win32threadpool.cpp</a> file of SSCLI2, one can find that while both the number of worker threads and the number of IOCP threads can grow under the ceiling of upper limits set by ThreadPool.SetMaxThreads, IOCP has constraints from CPU utilization also. In other words, even if you set the max of IOCP threads to a number much higher than the number of processros, there won't be more concurrent threads than the number of processors if there is very high CPU utilization. On the other hand, the number of worker threads, under the situation of high CPU utilitzation, will likely grow under the ceiling with requests queued up. <a href="http://blogs.thinktecture.com/buddhike/archive/2007/08/05/414907.aspx">This blog</a> has shown empirical results proving this. The example is a bit of extreme in that a large number of highly computation-intensive requests are being queued to the thread pool. However it shouldn't be viewed that IOCP is always the better one to use. Remember now you can still call ThreadPool.SetMaxThreads to throttle the number of concurrent worker threads to achieve good results too. Measuring and tuning would be required in real world situations. While it's great that we have an additional option to be able to queue any requests to IOCP through UnsafeQueueNativeOverlapped, keep in mind that IOCP was designed to be a great mechanism to handle IO asynchronously. Examples, such as System.IO.FileStream, System.Net.Sockets.Socket, System.ServiceModel.Channels.MsmqQueue, etc. can be found in the FCL. So I'd conjecture that while using a custom IO device, if asynchronous IO is desired, the same pattern can be followed by passing the handle of the IO object to ThreadPool.BindHandle to associate the IO device to the IOCP of the ThreadPool. <br /><br />As to using System.Threading.Timer, <a href="http://msdn.microsoft.com/msdnmag/issues/04/02/TimersinNET/default.aspx">this great MSDN Article</a> discusses it pretty well in comparison with System.Timers.Timer and System.Windows.Forms.Timer. It's important to remember that System.Timers.Timer is a wrapper of System.Threading.Timer. System.Windows.Forms.Timer uses a completely different mechanism, which is SetTimer Win32 API, posting WM_TIMER message to the application queue. This means that your delegate added to the Tick event will be running on the same UI thread that created the main application windows forms. As the UI thread processes a lot of messages, including mouse and keyboard messages, this mechanism will surely not get you a lot of mileage for concurrency if that's what you're looking for. <br /><br />As server apps like ASP.NET, WCF already have concurrency built in, ThreadPool should not be overly used in there. ThreadPool can be ideal in UI applications, Windows service, and batch applications, etc, if they need a lot of concurrency.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com2tag:blogger.com,1999:blog-362796945673239800.post-20501397156765398992007-10-31T15:24:00.000-04:002008-12-08T15:58:52.159-05:00.NET Code Instrumentation That Is More Accurate?I recently ran into a few interesting topics that all relate to Thread and I will share them with you here.<br /><br />Someone asked an interesting question to our company's community on how to instrument for just the time spent only in the thread when it is executing the code. Thread switching, thread idling/sleeping while waiting for IO, etc should not be counted.<br /><br />This is indeed pretty interesting as on a machine where a lot of threads are running concurrently, using the elapsed time (e.g. the Tracer in EntLib) approach, may give you inaccurate results if you just want to focus on measuring algorithm of your code. It will be further skewed if the thread running your code is given lower priorities than the other ones as thread scheduling on Windows is priority-driven and preemptive. <br /><br />After digging around a bit, I found a reasonable good approach to this problem, with the help of BCL of .NET framework. The Process type in System.Diagnostics namespace has a Threads property, which is a collection that corresponds to the Win32 threads in the process. The .NET type that represents the underlying Win32 thread is ProcessThread. It turns out that ProcessThread has properties for processor time, which is key to the solution. Specifically, the property I want to use is UserProcessorTime. Here is a simple wrapper class that I wrote, following the Tracer class pattern from EntLib. I have only kept necessary code to show the point – email me (hugh dot ang at gmail dot com) if you want to have my Visual Studio project that has the complete source code.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red43\green145\blue175;\red163\green21\blue21;}??\fs20 \cf1 public\cf0 \cf1 class\cf0 \cf4 ThreadTimer\cf0 : \cf4 IDisposable\par ??\cf0 \{\par ?? \cf1 private\cf0 \cf1 const\cf0 \cf1 string\cf0 LOG_CATEGORY = \cf5 "General"\cf0 ;\par ??\par ?? [\cf4 ThreadStatic\cf0 ()]\par ?? \cf1 private\cf0 \cf4 ProcessThread\cf0 _processThread;\par ??\par ?? \cf1 private\cf0 \cf1 int\cf0 _threadId;\par ?? \cf1 private\cf0 \cf4 TimeSpan\cf0 _start;\par ??\par ?? [\cf4 DllImport\cf0 (\cf5 "Kernel32"\cf0 , EntryPoint = \cf5 "GetCurrentThreadId"\cf0 , ExactSpelling = \cf1 true\cf0 )]\par ?? \cf1 private\cf0 \cf1 static\cf0 \cf1 extern\cf0 \cf4 Int32\cf0 GetCurrentWin32ThreadId();\par ??\par ?? \cf1 public\cf0 ThreadTimer()\par ?? \{\par ?? _threadId = ProcessThread.Id;\par ?? _start = ProcessThread.UserProcessorTime;\par ??\par ?? \cf4 Logger\cf0 .Write(\cf5 "ThreadTimer started"\cf0 , LOG_CATEGORY);\par ?? \}\par ??\par ?? \cf1 public\cf0 \cf1 void\cf0 Dispose()\par ?? \{\par ?? \cf1 double\cf0 duration = ProcessThread.UserProcessorTime.Subtract(_start).TotalSeconds;\par ?? \cf4 Logger\cf0 .Write(\par ?? \cf4 String\cf0 .Format(\cf5 "ThreadTimer ended with elapsed time of \{0\} seconds processor user time."\cf0 ,\par ?? duration),\par ?? LOG_CATEGORY);\par ?? \}\par ?? \par ?? \cf1 private\cf0 \cf4 ProcessThread\cf0 ProcessThread\par ?? \{\par ?? \cf1 get\par ??\cf0 \{\par ?? \cf1 if\cf0 (_processThread == \cf1 null\cf0 )\par ?? \{\par ?? \cf1 int\cf0 id = GetCurrentWin32ThreadId();\par ?? \cf1 foreach\cf0 (\cf4 ProcessThread\cf0 thread \cf1 in\cf0 \cf4 Process\cf0 .GetCurrentProcess().Threads)\par ?? \{\par ?? \cf1 if\cf0 (thread.Id == id)\par ?? \{\par ?? _processThread = thread;\par ?? \cf1 return\cf0 _processThread;\par ?? \}\par ?? \}\par ?? \}\par ??\par ?? \cf1 return\cf0 _processThread;\par ?? \}\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">ThreadTimer</span> : <span style="color: #2b91af;">IDisposable</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> LOG_CATEGORY = <span style="color: #a31515;">"General"</span>;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">ThreadStatic</span>()]</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: #2b91af;">ProcessThread</span> _processThread;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">int</span> _threadId;</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: #2b91af;">TimeSpan</span> _start;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">DllImport</span>(<span style="color: #a31515;">"Kernel32"</span>, EntryPoint = <span style="color: #a31515;">"GetCurrentThreadId"</span>, ExactSpelling = <span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">static</span> <span style="color: blue;">extern</span> <span style="color: #2b91af;">Int32</span> GetCurrentWin32ThreadId();</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> ThreadTimer()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> _threadId = ProcessThread.Id;</p><p style="margin: 0px;"> _start = ProcessThread.UserProcessorTime;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: #2b91af;">Logger</span>.Write(<span style="color: #a31515;">"ThreadTimer started"</span>, LOG_CATEGORY);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">void</span> Dispose()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">double</span> duration = ProcessThread.UserProcessorTime.Subtract(_start).TotalSeconds;</p><p style="margin: 0px;"> <span style="color: #2b91af;">Logger</span>.Write(</p><p style="margin: 0px;"> <span style="color: #2b91af;">String</span>.Format(<span style="color: #a31515;">"ThreadTimer ended with elapsed time of {0} seconds processor user time."</span>,</p><p style="margin: 0px;"> duration),</p><p style="margin: 0px;"> LOG_CATEGORY);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: #2b91af;">ProcessThread</span> ProcessThread</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span></p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (_processThread == <span style="color: blue;">null</span>)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">int</span> id = GetCurrentWin32ThreadId();</p><p style="margin: 0px;"> <span style="color: blue;">foreach</span> (<span style="color: #2b91af;">ProcessThread</span> thread <span style="color: blue;">in</span> <span style="color: #2b91af;">Process</span>.GetCurrentProcess().Threads)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (thread.Id == id)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> _processThread = thread;</p><p style="margin: 0px;"> <span style="color: blue;">return</span> _processThread;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">return</span> _processThread;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br />Here is how we can instrument a method called SortOperation:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;\red0\green128\blue0;}??\fs20 \cf1 using\cf0 (ThreadTimer threadTimer = \cf1 new\cf0 ThreadTimer())\par ??\{\par ?? \cf4 // simulate an operation that needs to be instrumented\par ??\cf0 SortOperation(i);\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">using</span> (ThreadTimer threadTimer = <span style="color: blue;">new</span> ThreadTimer())</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: green;">// simulate an operation that needs to be instrumented</span></p><p style="margin: 0px;"> SortOperation(i);</p><p style="margin: 0px;">}</p></div><br /><br />To see how this works out in comparison to instrumentation of EntLib, I implemented the SortOperation() as in the following:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red0\green0\blue0;}??\fs20 \cf1 static\cf0 \cf1 void\cf0 SortOperation(\cf1 int\cf0 x)\par ??\{\par ?? SortedList<\cf1 int\cf0 , \cf1 int\cf0 > slist = \cf1 new\cf0 SortedList<\cf1 int\cf0 , \cf1 int\cf0 >();\par ?? \cf1 for\cf0 (\cf1 int\cf0 i = x * 10000; i >= 0; i--)\par ?? \{\par ?? slist.Add(i, i);\par ?? \}\par ??\}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;">static</span> <span style="color: blue;">void</span> SortOperation(<span style="color: blue;">int</span> x)</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> SortedList<<span style="color: blue;">int</span>, <span style="color: blue;">int</span>> slist = <span style="color: blue;">new</span> SortedList<<span style="color: blue;">int</span>, <span style="color: blue;">int</span>>();</p><p style="margin: 0px;"> <span style="color: blue;">for</span> (<span style="color: blue;">int</span> i = x * 10000; i >= 0; i--)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> slist.Add(i, i);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br />To make it interesting, I first instrumented this method using both Tracer and my ThreadTimer and then instrumented the same method plus another method that does just IO, e.g. read from a file on disk. The IO method takes about 200 milliseconds. The following table summarizes the results:<br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBIX8fw3QzMa7RQHfFhFQqpx2ni5seMdipZEuDdHlJxeEzcKmqYICyuSPVcNO4X70Jj-6lW_inv9Tz_o_WqDYzbKb9RA9qquU9L3G8znSKKtK4Yhts-0tPTxQR1kJyeL3A0wbjnC9e4BA/s1600-h/results.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBIX8fw3QzMa7RQHfFhFQqpx2ni5seMdipZEuDdHlJxeEzcKmqYICyuSPVcNO4X70Jj-6lW_inv9Tz_o_WqDYzbKb9RA9qquU9L3G8znSKKtK4Yhts-0tPTxQR1kJyeL3A0wbjnC9e4BA/s320/results.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5127602401143380178" /></a><br /><br />Without the IO method, both Tracer and ThreadTimer produce almost identical results. Based on the empirical results, it’s not hard to figure out that the algorithm of looping with SortedList <> is O(N<sup>2</sup>). When the IO method is included, EntLib reports the extra 200 milliseconds while ThreadTimer reports pretty much unchanged results. <br /><br />So you see this ThreadTimer can be a reasonable solution for instrumentation in the scenario where you want to just focus on measuring your algorithm. However, as EntLib captures the start to end performance, which is what end user will experience, this solution shouldn’t replace that. Another caveat is that since there is no fixed relationship between a Win32 native thread and a managed thread, there is no guarantee that this solution will work for any CLR host.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-9609068411133289782007-10-26T21:11:00.000-04:002007-10-26T23:41:07.296-04:00Debugging Without Source Code Using the Right ToolsWhen it comes to debugging without source code, there are two categories of debugging tools for this purpose:<br /><ul><br /><li> <strong>deassemblers/debuggers</strong> such as IDA Pro, windbg, etc. If the running application is written in .NET, using windbg with SOS is really handy. This kind of tools are helpful if you need to track and understand program flow and execution state, such as stack and memory for subtle issues. These tools also tend to be favorites of crackers who want to crack/bypass license schemes.</li><br /><br /><li> <strong>tracing tools</strong>. There are those that trace network traffic such as <a href="http://www.pocketsoap.com/tcpTrace/">tcpTrace</a>, <a href="http://www.ethereal.com/download.html">ethereal</a>, <a href="http://www.fiddlertool.com/fiddler/">Fiddler</a> and of course <a href="http://www.microsoft.com/downloads/details.aspx?familyid=18b1d59d-f4d8-4213-8d17-2f6dde7d7aac&displaylang=en">netmon</a>. For monitoring process, file and registry activities on the OS itself, nothing beats the tools from Sysinternals, now Microsoft. The top two I recommend are <a href="http://www.microsoft.com/technet/sysinternals/utilities/processmonitor.mspx">Process Monitor</a> and <a href="http://www.microsoft.com/technet/sysinternals/ProcessesAndThreads/ProcessExplorer.mspx">Process Explorer</a>. The tools in this category are your friends in many debugging scenarios, especially during the initial stage of understanding the issues at hand. </li><br /></ul><br />Here I will go over the debugging experience I just had this week with a legacy financial software, with a file-based database (no it's not Access or any of the Microsoft products). For anonymity reasons I will just not name this software.<br /><br />Anyway, the database file used by this software is installed on a network share so that the client on users desktops can all use it. You'd probably guess by now that locking has been a common issue but that's not what I plan to cover here. <br /><br />There is a custom built web application (classic ASP app hosted on IIS 5) in house that has to look up data from this database, which are files on the network share as I have mentioned. I was told that when the web app was being designed, the developer had found that although a VB Winform application could access the database through ODBC just fine, ASP application couldn't. Without investigating and further pursuit, a separate ETL program was developed to transfer the needed data to the SQL database used by the ASP application. The data fortunately doesn't change that much so this ETL program is run once every night. Suffice to say that such a moving piece has had its moments with IT operations although it is not the worst pet peeve.<br /><br />I came to know this application and the ETL program recently and when I asked why we can't query and cache the data from the file based database in the ASP app, the answer was that the ODBC connection didn't work in ASP although it worked in the ETL program written in VB. I was told that the developer hadn't really debugged the application but rather called the help desk of the software vendor, which was not at all helpful like most of the tier 1 support of those vendors. Intuition was telling me that this problem had to do with the security context, such as impersonation and logon sessions. Question I would ask right away when facing this type of issue: does the context in ASP have permission or other issue accessing this share? <br /><br />Determined to fix this problem and get rid of the ETL program, I started on the developer machine by configuring the IIS application with high isolation process model. This would cause a server COM+ application to be created - remember I had to deal with IIS 5! :-) Then I set the identity of the COM+ application to a domain service account that would have proper permission to the share, where the database files are located. With IIS directory security configured to do integrated windows authentication, the ASP app worked! But the production server has basic auth to allow users access this app from the Internet. Alas when basic auth was configured, it stopped working even when I typed in the same domain user name and password as the developer logged onto the desktop. As IIS 5 impersonates when either windows auth or basic auth is turned on, there must be a difference between the logged on session associated with the impersonation token in the two authentication types. Using a tool called TokenDump from Keith Brown, that I downloaded long time ago, I was able to confirm that 1) with windows auth, the ASP impersonation token is using the same logon session as the interactive user 2) with basic auth, the impersonation token, however, uses a different logon session than the desktop's although they are both for the same domain user account. This is probably by design as I conjecture that IIS may have done a LogonUser() call using the passed in user name and password, which would result in a different logon session. <br /><br />But why was a different logon session encountering the problem? I used the Process Monitor for two captures, one for each auth type. When I compared the two, I noticed that the basic auth one didn't even have any trace showing attempts to access the network share. Going further, I found out from the developer that during ODBC and client setup, the legacy financial software used a mapped drive to point to the network share, where the database files reside. This is where the answer to the puzzle lies. As you may know, mapped drive is tied to the logon session and therefore not global. Here is the <a href="http://msdn2.microsoft.com/en-us/library/ms685143.aspx">official documentation</a> on MSDN. We should avoid using it, especially for enterprise server applications.<br /><br />Finally I would need to change the drive letter to UNC. Alas the legacy software UI explicitly disallows it. Not wanting to give up at this point, I asked the developer to use Process Monitor to find out where (registry or configuration files) the software is keeping the data that contains the drive letter. Sure enough, we found a handful in the registry under the hives created by the software installation. I went ahead and manually changed all relevant registry settings. I then ran the ASP application with basic auth. Voila it worked! So the software must already be using the Win32 IO API that supports UNC, when its UI still disallows it. <br /><br />This experience shows you how handy the right tools can be in hairy situations such as this. I do caution you though that you need to be extra careful when changing registry settings like this as it may cause unexpected behavior and/or breach license agreement.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com3tag:blogger.com,1999:blog-362796945673239800.post-7940490944212183962007-10-05T23:13:00.000-04:002008-12-08T15:58:52.426-05:00EntLib 3.1 Configuration - Part IIAll EntLib versions come with source code, which is a great place to start digging around and understanding the framework. For configuration, since we are planning to write a custom configuration section, we will need to look at the Configuration and Configuration.Design sub namespaces as each block has them. For example, look for <i>Microsoft.Practices.EnterpriseLibrary.Logging.Configuration</i> and <i>Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.Design</i> in the Logging block. Given all the overwhelming number of types defined in those namespaces, it can seem daunting first to tackle this. <br /><br />It's actually not too difficult. I will illustrate the approach by just doing a custom configuration section with simple attributes, instead of a complicated configuration schema with nested hierarchies. <br /><br />Consider an example where we need to connect to an external web service, which needs a configuration section with URL, User ID and Password. <br /><br />1) First of all we will need to create a type that corresponds to the section in the configuration XML file. Note that this type extends the EntLib's SerializableConfigurationSection. All we need to do in this type is defining the section name and attribute names. And of course we need to define read/write properties for those attributes to read/persist from the configuration file.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;\red0\green128\blue0;\red163\green21\blue21;}??\fs20 \par ?? \cf3 public\cf0 \cf3 class\cf0 \cf4 FooSettings\cf0 : \cf4 SerializableConfigurationSection\par ??\cf0 \{\par ?? \cf5 // Defines the section name \par ??\cf0 \cf3 public\cf0 \cf3 const\cf0 \cf3 string\cf0 SectionName = \cf6 "FooConfiguration"\cf0 ;\par ??\par ?? \cf5 // Defines attributes for the section\par ??\cf0 \cf3 private\cf0 \cf3 const\cf0 \cf3 string\cf0 URL_ATTR = \cf6 "url"\cf0 ;\par ?? \cf3 private\cf0 \cf3 const\cf0 \cf3 string\cf0 UID_ATTR = \cf6 "userID"\cf0 ;\par ?? \cf3 private\cf0 \cf3 const\cf0 \cf3 string\cf0 PASSWORD_ATTR = \cf6 "password"\cf0 ;\par ??\par ?? [\cf4 ConfigurationProperty\cf0 (URL_ATTR, IsRequired = \cf3 true\cf0 )]\par ?? \cf3 public\cf0 \cf3 string\cf0 Url\par ?? \{\par ?? \cf3 get\cf0 \{ \cf3 return\cf0 (\cf3 string\cf0 )\cf3 base\cf0 [URL_ATTR]; \}\par ?? \cf3 set\cf0 \{ \cf3 base\cf0 [URL_ATTR] = \cf3 value\cf0 ; \}\par ?? \}\par ??\par ?? [\cf4 ConfigurationProperty\cf0 (UID_ATTR, IsRequired = \cf3 true\cf0 )]\par ?? \cf3 public\cf0 \cf3 string\cf0 UID\par ?? \{\par ?? \cf3 get\cf0 \{ \cf3 return\cf0 (\cf3 string\cf0 )\cf3 base\cf0 [UID_ATTR]; \}\par ?? \cf3 set\cf0 \{ \cf3 base\cf0 [UID_ATTR] = \cf3 value\cf0 ; \}\par ?? \}\par ??\par ?? [\cf4 ConfigurationProperty\cf0 (PASSWORD_ATTR, IsRequired = \cf3 true\cf0 )]\par ?? \cf3 public\cf0 \cf3 string\cf0 Password\par ?? \{\par ?? \cf3 get\cf0 \{ \cf3 return\cf0 (\cf3 string\cf0 )\cf3 base\cf0 [PASSWORD_ATTR]; \}\par ?? \cf3 set\cf0 \{ \cf3 base\cf0 [PASSWORD_ATTR] = \cf3 value\cf0 ; \}\par ?? \}\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">FooSettings</span> : <span style="color: #2b91af;">SerializableConfigurationSection</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: green;">// Defines the section name </span></p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> SectionName = <span style="color: #a31515;">"FooConfiguration"</span>;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// Defines attributes for the section</span></p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> URL_ATTR = <span style="color: #a31515;">"url"</span>;</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> UID_ATTR = <span style="color: #a31515;">"userID"</span>;</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">const</span> <span style="color: blue;">string</span> PASSWORD_ATTR = <span style="color: #a31515;">"password"</span>;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">ConfigurationProperty</span>(URL_ATTR, IsRequired = <span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> Url</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> (<span style="color: blue;">string</span>)<span style="color: blue;">base</span>[URL_ATTR]; }</p><p style="margin: 0px;"> <span style="color: blue;">set</span> { <span style="color: blue;">base</span>[URL_ATTR] = <span style="color: blue;">value</span>; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">ConfigurationProperty</span>(UID_ATTR, IsRequired = <span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> UID</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> (<span style="color: blue;">string</span>)<span style="color: blue;">base</span>[UID_ATTR]; }</p><p style="margin: 0px;"> <span style="color: blue;">set</span> { <span style="color: blue;">base</span>[UID_ATTR] = <span style="color: blue;">value</span>; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">ConfigurationProperty</span>(PASSWORD_ATTR, IsRequired = <span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> Password</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> (<span style="color: blue;">string</span>)<span style="color: blue;">base</span>[PASSWORD_ATTR]; }</p><p style="margin: 0px;"> <span style="color: blue;">set</span> { <span style="color: blue;">base</span>[PASSWORD_ATTR] = <span style="color: blue;">value</span>; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br /><br />2) Secondly, we will need to create a type that represents the node on the EntLib console. This type extends the EntLib's ConfigurationSectionNode. Note that It's very similar to the FooSettings type defined above, in that there are three read/write properties that correspond to the Url, UID and Password. <br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red163\green21\blue21;}??\fs20 \par ?? [\cf3 Image\cf0 (\cf4 typeof\cf0 (\cf3 FooNode\cf0 ), \cf5 "ConfigNode_d.bmp"\cf0 )]\par ?? [\cf3 SelectedImage\cf0 (\cf4 typeof\cf0 (\cf3 FooNode\cf0 ), \cf5 "ConfigNode_h.bmp"\cf0 )]\par ?? \cf4 public\cf0 \cf4 class\cf0 \cf3 FooNode\cf0 : \cf3 ConfigurationSectionNode\par ??\cf0 \{\par ?? \cf4 private\cf0 \cf4 string\cf0 _url;\par ?? \cf4 private\cf0 \cf4 string\cf0 _uid;\par ?? \cf4 private\cf0 \cf4 string\cf0 _password;\par ??\par ?? \cf4 public\cf0 FooNode()\par ?? : \cf4 base\cf0 (\cf5 "Foo Configuration"\cf0 )\par ?? \{\}\par ??\par ?? [\cf3 ReadOnly\cf0 (\cf4 true\cf0 )]\par ?? \cf4 public\cf0 \cf4 override\cf0 \cf4 string\cf0 Name\par ?? \{\par ?? \cf4 get\cf0 \{ \cf4 return\cf0 \cf4 base\cf0 .Name; \}\par ?? \}\par ??\par ?? [\cf3 Required\cf0 ()]\par ?? [\cf3 Browsable\cf0 (\cf4 true\cf0 )]\par ?? \cf4 public\cf0 \cf4 string\cf0 Url\par ?? \{\par ?? \cf4 get\cf0 \{ \cf4 return\cf0 _url; \}\par ?? \cf4 set\cf0 \{ _url = \cf4 value\cf0 ; \}\par ?? \}\par ??\par ?? [\cf3 Required\cf0 ()]\par ?? [\cf3 Browsable\cf0 (\cf4 true\cf0 )]\par ?? \cf4 public\cf0 \cf4 string\cf0 UID\par ?? \{\par ?? \cf4 get\cf0 \{ \cf4 return\cf0 _uid; \}\par ?? \cf4 set\cf0 \{ _uid = \cf4 value\cf0 ; \}\par ?? \}\par ??\par ?? [\cf3 Required\cf0 ()]\par ?? [\cf3 Browsable\cf0 (\cf4 true\cf0 )]\par ?? \cf4 public\cf0 \cf4 string\cf0 Password\par ?? \{\par ?? \cf4 get\cf0 \{ \cf4 return\cf0 _password; \}\par ?? \cf4 set\cf0 \{ _password = \cf4 value\cf0 ; \}\par ?? \}\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;">[<span style="color: #2b91af;">Image</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">FooNode</span>), <span style="color: #a31515;">"ConfigNode_d.bmp"</span>)]</p><p style="margin: 0px;">[<span style="color: #2b91af;">SelectedImage</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">FooNode</span>), <span style="color: #a31515;">"ConfigNode_h.bmp"</span>)]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">FooNode</span> : <span style="color: #2b91af;">ConfigurationSectionNode</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">string</span> _url;</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">string</span> _uid;</p><p style="margin: 0px;"> <span style="color: blue;">private</span> <span style="color: blue;">string</span> _password;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> FooNode()</p><p style="margin: 0px;"> : <span style="color: blue;">base</span>(<span style="color: #a31515;">"Foo Configuration"</span>)</p><p style="margin: 0px;"> {}</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">ReadOnly</span>(<span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">string</span> Name</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> <span style="color: blue;">base</span>.Name; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">Required</span>()]</p><p style="margin: 0px;"> [<span style="color: #2b91af;">Browsable</span>(<span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> Url</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> _url; }</p><p style="margin: 0px;"> <span style="color: blue;">set</span> { _url = <span style="color: blue;">value</span>; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">Required</span>()]</p><p style="margin: 0px;"> [<span style="color: #2b91af;">Browsable</span>(<span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> UID</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> _uid; }</p><p style="margin: 0px;"> <span style="color: blue;">set</span> { _uid = <span style="color: blue;">value</span>; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> [<span style="color: #2b91af;">Required</span>()]</p><p style="margin: 0px;"> [<span style="color: #2b91af;">Browsable</span>(<span style="color: blue;">true</span>)]</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">string</span> Password</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">get</span> { <span style="color: blue;">return</span> _password; }</p><p style="margin: 0px;"> <span style="color: blue;">set</span> { _password = <span style="color: blue;">value</span>; }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br />What is interesting here is that this class as well as its propeties have some attributes attached to them. The SelectedImage() and Image() attributes simply define the bitmap images shown when the node is in selected or deselected state. The two bitmap images are embedded resources within the same VS.NET project. The Required() and Browsable() attributes at the properties are telling the EntLib UI that those properties are required and browsable(visible) on the UI. If you don't put a value for the property, a UI validation error will occur when you try to save the configuration. <br /><br />3) So far so good. Now we need to have a way to let EntLib UI become aware of our custom configuration node. Here is where we need to extend EntLib's ConfigurationDesignManager to have our FooConifgurationDesignManager. <br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;\red163\green21\blue21;\red0\green128\blue0;}??\fs20 \par ?? \cf3 public\cf0 \cf3 class\cf0 \cf4 FooConfigurationDesignManager\cf0 : \cf4 ConfigurationDesignManager\par ??\cf0 \{\par ?? \cf3 public\cf0 \cf3 override\cf0 \cf3 void\cf0 Register(\cf4 IServiceProvider\cf0 serviceProvider)\par ?? \{\par ?? (\cf3 new\cf0 \cf4 FooCommandRegistrar\cf0 (serviceProvider)).Register();\par ?? \}\par ??\par ?? \cf3 protected\cf0 \cf3 override\cf0 \cf3 void\cf0 OpenCore(\cf4 IServiceProvider\cf0 serviceProvider, \par ?? \cf4 ConfigurationApplicationNode\cf0 rootNode, \par ?? \cf4 ConfigurationSection\cf0 section)\par ?? \{\par ?? \cf3 if\cf0 (section != \cf3 null\cf0 )\par ?? \{\par ?? \cf4 FooSettings\cf0 settings = section \cf3 as\cf0 \cf4 FooSettings\cf0 ;\par ?? \cf4 Debug\cf0 .Assert(settings != \cf3 null\cf0 , \par ?? \cf5 "Check config section - not of the FooSettings type"\cf0 );\par ??\par ?? \cf4 FooNode\cf0 node = \cf3 new\cf0 \cf4 FooNode\cf0 ();\par ?? \cf6 // sync data on UI and in config file\par ??\cf0 node.Url = settings.Url;\par ?? node.UID = settings.UID;\par ?? node.Password = settings.Password;\par ??\par ?? SetProtectionProvider(section, node);\par ??\par ?? rootNode.AddNode(node);\par ?? \}\par ?? \}\par ??\par ?? \cf3 protected\cf0 \cf3 override\cf0 \cf4 ConfigurationSectionInfo\cf0 GetConfigurationSectionInfo(\par ?? \cf4 IServiceProvider\cf0 serviceProvider)\par ?? \{\par ?? \cf4 ConfigurationNode\cf0 rootNode = \cf4 ServiceHelper\cf0 .GetCurrentRootNode(serviceProvider);\par ?? \cf4 FooNode\cf0 node = \cf3 null\cf0 ;\par ?? \cf3 if\cf0 (rootNode != \cf3 null\cf0 )\par ?? node = (\cf4 FooNode\cf0 )rootNode.Hierarchy.FindNodeByType(rootNode, \cf3 typeof\cf0 (\cf4 FooNode\cf0 ));\par ??\par ?? \cf4 FooSettings\cf0 settings = \cf3 null\cf0 ;\par ?? \cf3 if\cf0 (node == \cf3 null\cf0 )\par ?? \{\par ?? settings = \cf3 null\cf0 ;\par ?? \}\par ?? \cf3 else\par ??\cf0 \{\par ?? settings = \cf3 new\cf0 \cf4 FooSettings\cf0 ();\par ??\par ?? \cf6 // sync data on UI and in config file\par ??\cf0 settings.Url = node.Url;\par ?? settings.UID = node.UID;\par ?? settings.Password = node.Password;\par ?? \}\par ?? \cf3 string\cf0 protectionProviderName = GetProtectionProviderName(node);\par ??\par ?? \cf3 return\cf0 \cf3 new\cf0 \cf4 ConfigurationSectionInfo\cf0 (node, \par ?? settings, \cf4 FooSettings\cf0 .SectionName, protectionProviderName);\par ?? \}\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">FooConfigurationDesignManager</span> : <span style="color: #2b91af;">ConfigurationDesignManager</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">void</span> Register(<span style="color: #2b91af;">IServiceProvider</span> serviceProvider)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> (<span style="color: blue;">new</span> <span style="color: #2b91af;">FooCommandRegistrar</span>(serviceProvider)).Register();</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">protected</span> <span style="color: blue;">override</span> <span style="color: blue;">void</span> OpenCore(<span style="color: #2b91af;">IServiceProvider</span> serviceProvider, </p><p style="margin: 0px;"> <span style="color: #2b91af;">ConfigurationApplicationNode</span> rootNode, </p><p style="margin: 0px;"> <span style="color: #2b91af;">ConfigurationSection</span> section)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (section != <span style="color: blue;">null</span>)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">FooSettings</span> settings = section <span style="color: blue;">as</span> <span style="color: #2b91af;">FooSettings</span>;</p><p style="margin: 0px;"> <span style="color: #2b91af;">Debug</span>.Assert(settings != <span style="color: blue;">null</span>, </p><p style="margin: 0px;"> <span style="color: #a31515;">"Check config section - not of the FooSettings type"</span>);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: #2b91af;">FooNode</span> node = <span style="color: blue;">new</span> <span style="color: #2b91af;">FooNode</span>();</p><p style="margin: 0px;"> <span style="color: green;">// sync data on UI and in config file</span></p><p style="margin: 0px;"> node.Url = settings.Url;</p><p style="margin: 0px;"> node.UID = settings.UID;</p><p style="margin: 0px;"> node.Password = settings.Password;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> SetProtectionProvider(section, node);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> rootNode.AddNode(node);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">protected</span> <span style="color: blue;">override</span> <span style="color: #2b91af;">ConfigurationSectionInfo</span> GetConfigurationSectionInfo(</p><p style="margin: 0px;"> <span style="color: #2b91af;">IServiceProvider</span> serviceProvider)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">ConfigurationNode</span> rootNode = <span style="color: #2b91af;">ServiceHelper</span>.GetCurrentRootNode(serviceProvider);</p><p style="margin: 0px;"> <span style="color: #2b91af;">FooNode</span> node = <span style="color: blue;">null</span>;</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (rootNode != <span style="color: blue;">null</span>)</p><p style="margin: 0px;"> node = (<span style="color: #2b91af;">FooNode</span>)rootNode.Hierarchy.FindNodeByType(rootNode, <span style="color: blue;">typeof</span>(<span style="color: #2b91af;">FooNode</span>));</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: #2b91af;">FooSettings</span> settings = <span style="color: blue;">null</span>;</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (node == <span style="color: blue;">null</span>)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> settings = <span style="color: blue;">null</span>;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> <span style="color: blue;">else</span></p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> settings = <span style="color: blue;">new</span> <span style="color: #2b91af;">FooSettings</span>();</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// sync data on UI and in config file</span></p><p style="margin: 0px;"> settings.Url = node.Url;</p><p style="margin: 0px;"> settings.UID = node.UID;</p><p style="margin: 0px;"> settings.Password = node.Password;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> <span style="color: blue;">string</span> protectionProviderName = GetProtectionProviderName(node);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: blue;">new</span> <span style="color: #2b91af;">ConfigurationSectionInfo</span>(node, </p><p style="margin: 0px;"> settings, <span style="color: #2b91af;">FooSettings</span>.SectionName, protectionProviderName);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br />The two overridden methods OpenCore() and GetConfigurationSectionInfo() are the hooks for EntLib UI to load and save from/to configuration file. You will notice the code in the two methods to sync up data held by FooNode and FooSettings - just remember that FooNode represents the UI view and FooSettings represents the configuration file. The remaining function, Register(), is the hook for us to provide the command in the EntLib context menu. Here we will use the following CommandRegistrar class to add the command "Foo Configuration":<br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;\red163\green21\blue21;}??\fs20 \par ?? \cf3 public\cf0 \cf3 class\cf0 \cf4 FooCommandRegistrar\cf0 : \cf4 CommandRegistrar\par ??\cf0 \{\par ?? \cf3 public\cf0 FooCommandRegistrar(\cf4 IServiceProvider\cf0 serviceProvider)\par ?? : \cf3 base\cf0 (serviceProvider)\par ?? \{\par ?? \}\par ??\par ?? \cf3 public\cf0 \cf3 override\cf0 \cf3 void\cf0 Register()\par ?? \{\par ?? \cf4 ConfigurationUICommand\cf0 cmd = \cf4 ConfigurationUICommand\cf0 .CreateSingleUICommand(ServiceProvider,\par ?? \cf5 "Foo Configuration"\cf0 ,\par ?? \cf5 "Foo Configuration"\cf0 ,\par ?? \cf3 new\cf0 \cf4 AddChildNodeCommand\cf0 (ServiceProvider, \cf3 typeof\cf0 (\cf4 FooNode\cf0 )),\par ?? \cf3 typeof\cf0 (\cf4 FooNode\cf0 ));\par ?? AddUICommand(cmd, \cf3 typeof\cf0 (\cf4 ConfigurationApplicationNode\cf0 ));\par ?? \}\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">FooCommandRegistrar</span> : <span style="color: #2b91af;">CommandRegistrar</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">public</span> FooCommandRegistrar(<span style="color: #2b91af;">IServiceProvider</span> serviceProvider)</p><p style="margin: 0px;"> : <span style="color: blue;">base</span>(serviceProvider)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">void</span> Register()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">ConfigurationUICommand</span> cmd = <span style="color: #2b91af;">ConfigurationUICommand</span>.CreateSingleUICommand(ServiceProvider,</p><p style="margin: 0px;"> <span style="color: #a31515;">"Foo Configuration"</span>,</p><p style="margin: 0px;"> <span style="color: #a31515;">"Foo Configuration"</span>,</p><p style="margin: 0px;"> <span style="color: blue;">new</span> <span style="color: #2b91af;">AddChildNodeCommand</span>(ServiceProvider, <span style="color: blue;">typeof</span>(<span style="color: #2b91af;">FooNode</span>)),</p><p style="margin: 0px;"> <span style="color: blue;">typeof</span>(<span style="color: #2b91af;">FooNode</span>));</p><p style="margin: 0px;"> AddUICommand(cmd, <span style="color: blue;">typeof</span>(<span style="color: #2b91af;">ConfigurationApplicationNode</span>));</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br />With all these in place, we need to add the following to the AssemblyInfo.cs file:<br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;}??\fs20 [assembly: \cf3 ConfigurationDesignManager\cf0 (\cf4 typeof\cf0 (\cf3 FooConfigurationDesignManager\cf0 ))]}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;">[assembly: <span style="color: #2b91af;">ConfigurationDesignManager</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">FooConfigurationDesignManager</span>))]</p></div><br />EntLib UI, when being loaded, will use reflection API to query the assemblies in its folder ("c:\Program Files\Microsoft Enterprise Library 3.1 - May 2007\Bin\" in my case) and find out all the ConfigurationDesignManager types through this assembly attribute. So after the project is compiled, we need to drop the dll into the same folder as the EntLibConfig.exe file.<br /><br />Whew, now we can see how this works in the EntLib UI. The following figure shows that the "Foo Configuration" command is in the context menu.<br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAxhmRawiPcDdahRuSmD_WG4cqWRF9MwU689XJ6_A1B3E46qik8zrTDLg-1WZLpjFnlDVJoNaURvTse1NFELRi1HFbSJ1SBG7YoRuWm4m4_9AIjG0Am0YA_jtOFv0Twzbf2emy8Ol2quM/s1600-h/FooCommand.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAxhmRawiPcDdahRuSmD_WG4cqWRF9MwU689XJ6_A1B3E46qik8zrTDLg-1WZLpjFnlDVJoNaURvTse1NFELRi1HFbSJ1SBG7YoRuWm4m4_9AIjG0Am0YA_jtOFv0Twzbf2emy8Ol2quM/s320/FooCommand.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5118077920379885970" /></a><br /><br />One cool feature of EntLib 3.1 configuration is that it works integrated with VS.NET 2005 IDE. The following figure shows just that. <br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6LGieYbwNCj-wAreByhcbBnrz5z7zPD_Ak6rPAv8dKeqNkacRwjVQAjg28FFYrbFhsv5wqjEfAVF4EwEecZvwU-PIm3rAuPkjlb6VQxb-Q4wtKSw2h6bJ20w5MsygKXO5i4Jk_OpQEvI/s1600-h/override.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6LGieYbwNCj-wAreByhcbBnrz5z7zPD_Ak6rPAv8dKeqNkacRwjVQAjg28FFYrbFhsv5wqjEfAVF4EwEecZvwU-PIm3rAuPkjlb6VQxb-Q4wtKSw2h6bJ20w5MsygKXO5i4Jk_OpQEvI/s320/override.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5118078517380340130" /></a><br /><br />You see that a Foo Configuration is created besides the standard logging application block. In the properties window on the lower-right corner, the values for Url, UID and Password are set. I have also defined two environments, QA and Prod. For QA, override is chosen for the foo configuration and you can also see that it's a different set of values. Note that you can encrypt this section with the standard providers. If you have gone through the code I listed above, you will realize that these two functions are provided by EntLib without us writing any code!<br /><br />Here is what the app.config file looks like (I have ommitted the logging section for clarity):<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof65001\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red163\green21\blue21;\red255\green0\blue0;\red0\green0\blue0;}??\fs20 \cf1 <?\cf3 xml\cf1 \cf4 version\cf1 =\cf0 "\cf1 1.0\cf0 "\cf1 \cf4 encoding\cf1 =\cf0 "\cf1 utf-8\cf0 "\cf1 ?>\par ??<\cf3 configuration\cf1 >\par ?? <\cf3 configSections\cf1 >\par ?? <\cf3 section\cf1 \cf4 name\cf1 =\cf0 "\cf1 FooConfiguration\cf0 "\cf1 \cf4 type\cf1 =\cf0 "\cf1 CustomEntLibConfig.FooSettings, CustomEntLibConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\cf0 "\cf1 />\par ?? </\cf3 configSections\cf1 >\par ?? <\cf3 FooConfiguration\cf1 \cf4 url\cf1 =\cf0 "\cf1 http://foo.com/test.asmx\cf0 "\cf1 \cf4 userID\cf1 =\cf0 "\cf1 test\cf0 "\par ??\cf1 \cf4 password\cf1 =\cf0 "\cf1 test123\cf0 "\cf1 />\par ??</\cf3 configuration\cf1 >}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;"><?</span><span style="color: #a31515;">xml</span><span style="color: blue;"> </span><span style="color: red;">version</span><span style="color: blue;">=</span>"<span style="color: blue;">1.0</span>"<span style="color: blue;"> </span><span style="color: red;">encoding</span><span style="color: blue;">=</span>"<span style="color: blue;">utf-8</span>"<span style="color: blue;">?></span></p><p style="margin: 0px;"><span style="color: blue;"><</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">configSections</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">section</span><span style="color: blue;"> </span><span style="color: red;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">FooConfiguration</span>"<span style="color: blue;"> </span><span style="color: red;">type</span><span style="color: blue;">=</span>"<span style="color: blue;">CustomEntLibConfig.FooSettings, CustomEntLibConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</span>"<span style="color: blue;"> /></span></p><p style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">configSections</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">FooConfiguration</span><span style="color: blue;"> </span><span style="color: red;">url</span><span style="color: blue;">=</span>"<span style="color: blue;">http://foo.com/test.asmx</span>"<span style="color: blue;"> </span><span style="color: red;">userID</span><span style="color: blue;">=</span>"<span style="color: blue;">test</span>"</p><p style="margin: 0px;"><span style="color: blue;"> </span><span style="color: red;">password</span><span style="color: blue;">=</span>"<span style="color: blue;">test123</span>"<span style="color: blue;"> /></span></p><p style="margin: 0px;"><span style="color: blue;"></</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></p></div><br /><br />In case you're curious, here is what the QA delta file looks like.<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue255;\red255\green255\blue255;\red163\green21\blue21;\red255\green0\blue0;\red0\green0\blue0;}??\fs20 \cf1 <\cf3 configuration\cf1 >\par ?? <\cf3 configSections\cf1 >\par ?? <\cf3 section\cf1 \cf4 name\cf1 =\cf0 "\cf1 EnvironmentMergeData\cf0 "\cf1 \cf4 type\cf1 =\cf0 "\cf1 Microsoft.Practices.EnterpriseLibrary.Configuration.EnvironmentalOverrides.Configuration.EnvironmentMergeSection, Microsoft.Practices.EnterpriseLibrary.Configuration.EnvironmentalOverrides, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\cf0 "\cf1 />\par ?? </\cf3 configSections\cf1 >\par ?? <\cf3 EnvironmentMergeData\cf1 \cf4 environmentName\cf1 =\cf0 "\cf1 QA\cf0 "\cf1 \cf4 environmentDeltaFile\cf1 =\cf0 "\cf1 app.qa\cf0 "\cf1 >\par ?? <\cf3 mergeElements\cf1 >\par ?? <\cf3 override\cf1 \cf4 nodePath\cf1 =\cf0 "\cf1 /Foo Configuration\cf0 "\cf1 \cf4 overrideProperties\cf1 =\cf0 "\cf1 true\cf0 "\cf1 >\par ?? <\cf3 overridddenProperties\cf1 >\par ?? <\cf3 add\cf1 \cf4 name\cf1 =\cf0 "\cf1 Password\cf0 "\cf1 \cf4 value\cf1 =\cf0 "\cf1 qa123\cf0 "\cf1 />\par ?? <\cf3 add\cf1 \cf4 name\cf1 =\cf0 "\cf1 UID\cf0 "\cf1 \cf4 value\cf1 =\cf0 "\cf1 qa\cf0 "\cf1 />\par ?? <\cf3 add\cf1 \cf4 name\cf1 =\cf0 "\cf1 Url\cf0 "\cf1 \cf4 value\cf1 =\cf0 "\cf1 http://qa.foo.com/test.asmx\cf0 "\cf1 />\par ?? </\cf3 overridddenProperties\cf1 >\par ?? </\cf3 override\cf1 >\par ?? </\cf3 mergeElements\cf1 >\par ?? </\cf3 EnvironmentMergeData\cf1 >\par ??</\cf3 configuration\cf1 >}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;"><</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">configSections</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">section</span><span style="color: blue;"> </span><span style="color: red;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">EnvironmentMergeData</span>"<span style="color: blue;"> </span><span style="color: red;">type</span><span style="color: blue;">=</span>"<span style="color: blue;">Microsoft.Practices.EnterpriseLibrary.Configuration.EnvironmentalOverrides.Configuration.EnvironmentMergeSection, Microsoft.Practices.EnterpriseLibrary.Configuration.EnvironmentalOverrides, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</span>"<span style="color: blue;"> /></span></p><p style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">configSections</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">EnvironmentMergeData</span><span style="color: blue;"> </span><span style="color: red;">environmentName</span><span style="color: blue;">=</span>"<span style="color: blue;">QA</span>"<span style="color: blue;"> </span><span style="color: red;">environmentDeltaFile</span><span style="color: blue;">=</span>"<span style="color: blue;">app.qa</span>"<span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">mergeElements</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">override</span><span style="color: blue;"> </span><span style="color: red;">nodePath</span><span style="color: blue;">=</span>"<span style="color: blue;">/Foo Configuration</span>"<span style="color: blue;"> </span><span style="color: red;">overrideProperties</span><span style="color: blue;">=</span>"<span style="color: blue;">true</span>"<span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">overridddenProperties</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">add</span><span style="color: blue;"> </span><span style="color: red;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">Password</span>"<span style="color: blue;"> </span><span style="color: red;">value</span><span style="color: blue;">=</span>"<span style="color: blue;">qa123</span>"<span style="color: blue;"> /></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">add</span><span style="color: blue;"> </span><span style="color: red;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">UID</span>"<span style="color: blue;"> </span><span style="color: red;">value</span><span style="color: blue;">=</span>"<span style="color: blue;">qa</span>"<span style="color: blue;"> /></span></p><p style="margin: 0px;"><span style="color: blue;"> <</span><span style="color: #a31515;">add</span><span style="color: blue;"> </span><span style="color: red;">name</span><span style="color: blue;">=</span>"<span style="color: blue;">Url</span>"<span style="color: blue;"> </span><span style="color: red;">value</span><span style="color: blue;">=</span>"<span style="color: blue;">http://qa.foo.com/test.asmx</span>"<span style="color: blue;"> /></span></p><p style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">overridddenProperties</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">override</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">mergeElements</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"> </</span><span style="color: #a31515;">EnvironmentMergeData</span><span style="color: blue;">></span></p><p style="margin: 0px;"><span style="color: blue;"></</span><span style="color: #a31515;">configuration</span><span style="color: blue;">></span></p></div><br />4) To use the configuration programmatically, here is a code snippet:<br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red163\green21\blue21;}??\fs20 \par ?? \cf3 FooSettings\cf0 node = \cf3 ConfigurationManager\cf0 .GetSection(\cf3 FooSettings\cf0 .SectionName) \cf4 as\cf0 \cf3 FooSettings\cf0 ;\par ??\par ?? \cf4 if\cf0 (node != \cf4 null\cf0 )\par ?? \cf3 Console\cf0 .WriteLine(\cf5 "Url: \{0\}, UID: \{1\}, Password: \{2\}"\cf0 , \par ?? node.Url, node.UID, node.Password);\par ?? \cf4 else\par ??\cf0 \cf3 Console\cf0 .WriteLine(\cf5 "Custom section not found"\cf0 );\par ??\par ?? \cf3 Console\cf0 .ReadLine();}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: #2b91af;">FooSettings</span> node = <span style="color: #2b91af;">ConfigurationManager</span>.GetSection(<span style="color: #2b91af;">FooSettings</span>.SectionName) <span style="color: blue;">as</span> <span style="color: #2b91af;">FooSettings</span>;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">if</span> (node != <span style="color: blue;">null</span>)</p><p style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Url: {0}, UID: {1}, Password: {2}"</span>, </p><p style="margin: 0px;"> node.Url, node.UID, node.Password);</p><p style="margin: 0px;"><span style="color: blue;">else</span></p><p style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Custom section not found"</span>);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: #2b91af;">Console</span>.ReadLine();</p></div><br /><br />I hope you find this helpful. Email me at hugh dot ang @ gmail dot com if you need the source code for the sample I have used here.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com2tag:blogger.com,1999:blog-362796945673239800.post-55889029754527849312007-09-23T21:36:00.000-04:002007-09-23T22:51:37.762-04:00EntLib 3.1 Configuration - Part IRecently, when an application that my team had built was deployed to production, an external web service's URL was forgotten to have been set from its test site's to the production site's. So the transactions thought to have completed actually never happened. This caused some interruptions to the business.<br /><br />We had to rush this deployment one month ahead of schedule because of a business issue. While the rush was partly to blame, I think since the application uses Enterprise Library 3.1 for quite a few of its blocks, one feature could have helped us avoid this hiccup. <br /><br />Since 3.0, Enterprise Library introduced the environment overrides for configuration. <a href="http://bloggingabout.net/blogs/olaf/archive/2007/02/18/environmental-overrides-made-it-into-entlib-v3.aspx">This blog</a> had a good overview of this feature. In a nutshell, the configuration files for the most part are the same between different environments, DEV, Staging, QA and Prod, etc. There are only a few minor things that might be different, such as database connection string, logging threshold, etc. This new Enterprise Library configuration feature saves the differentials in delta config files. And the merge function overrides the original (usually DEV) application config file with the delta to produce the config file for the target environment. <br /><br />Of course, this feature works out of box for any configuration setting if it can be done through the Enterprise Library configuration console tool, which is also available from Visual Studio .NET 2005 IDE. The list includes the "Application Settings", "Caching", "Logging", etc. "Application Settings" is great for independent name/value pairs but for a set of values and more complicated configuration data schema, a separate configuration section is required. In the next blog, I will show how to create custom EntLib 3.1 configuration sections so that not only can we leverage the environment overrides, but also we can easily use the EntLib config UI to protect sensitive config data with cryptography.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-61198837972962128132007-08-21T22:22:00.000-04:002007-08-21T22:44:59.435-04:00Sync Active Item in VS.NET Solution ExplorerOpening a big VS.NET solution and having a lot of source files can be very disorienting. I've found it especially so when I am tracking deep in the types that are referred by other types, which are referred by others, and so on. And it's pretty difficult to find out which Visual Studio project the type is defined in. I was so desperate that I almost wrote an addin to help me synchronizing the active source file in the solution explorer window. Alas when I was almost done, I found that Visual Studio 2005 already offers such function. To use it, just follow these steps:<br /><br /><ul><br /><li>Go to "Tools" menu, click "Options"</li><br /><li>Under "Projects and Solutions", click "General"</li><br /><li>Check the "Track Active Item in Solution Explorer" checkbox on the right side</li><br /></ul><br /><br />That is it! Now no matter which source file you end up burying your head into, the solution explorer window always selects and scrolls to that file for you. I guess my lesson learned is that I should always look hard first because Visual Studio may just have that built in already. And it was also interesting to learn the VS.NET automation APIs that have to do with finding the solution item and manipulating the solution explorer window.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-47627729415001348592007-08-09T12:19:00.000-04:002007-08-20T13:18:12.165-04:00Commoditization of Business ApplicationsI have been quiet here for the last couple of months. The main reason is that I stepped into the position of vice president of app dev department and became heavily involved in organization management as well as project management at a client. I am also busy collaborating with a colleague on a WCF article that we hope to publish soon.<br /><br />Being in this particular management position has given me a different perspective on application development. I have been involved in a couple of projects where "buy vs. build" decision was made. Dollars and benefits (ROI) always took precedence over anything else. And it seems obvious that the industry trend is going towards the direction where building everything from scratch is less favored and we see needs for product knowledge and configuration management skills. You can see that with the slew of products like BizTalk, MOSS, Commerce Server 2007, Dynamics, etc. coming out of Microsoft assembly line in a pace like never before. <br /><br />The trade-off for the product knowledge will be fundamentals and technical skills. An unfortunate effect of this will be that for certain technical issues, the root problems don't get resolved but rather awkward workarounds are contrived at the product level, which could result in convoluted business processes for end users.<br /><br />For developers, this may not be good news as the creativity aspect (the fun part) of application development is being gradually taken away and it is a big challenge to keep on top of the ever growing list of products.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-66400133476912340462007-05-25T14:08:00.000-04:002007-05-25T14:14:06.024-04:00EntLib 3.1 Is ComingIt looks like that p&p is addressing the <a href="http://tigerang.blogspot.com/2007/02/policy-injection-app-block-of-entlib-30.html">implementation issue of interception for Policy Injection</a> by making the interception mechanism configurable. Here is the <a href="http://blogs.msdn.com/tomholl/archive/2007/05/21/enterprise-library-3-1-is-coming.aspx">blog</a> that talks about this.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com0tag:blogger.com,1999:blog-362796945673239800.post-79583752042273597442007-05-15T12:50:00.000-04:002007-05-15T13:54:49.824-04:00A Simple Design Pattern for Extensible Enumeration TypesIn .NET, custom enum types are of value type so they cannot be extended. This can create an issue if you want to include an enum in your framework API, where you wish that the enum could be extended by the applications that are using the framework. After examining the implementation of System.Enum, I came up with the following implementation. I haven't googled thoroughly yet so please no flames if someone has done something similar :-) Also note the code posted is POC quality and I am looking to improve it. <br /><br />First I will start off with a base class EnumEx<T>. I am using generics to allow different underlying enum types:<br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red0\green0\blue255;\red0\green128\blue0;}??\fs20 \par ?? [\cf3 Serializable\cf0 ()]\par ?? \cf4 public\cf0 \cf4 abstract\cf0 \cf4 class\cf0 \cf3 EnumEx\cf0 <T> \par ?? \cf4 where\cf0 T : \cf4 struct\par ??\cf0 \{\par ?? \cf5 // holds the actual value\par ??\cf0 \cf4 protected\cf0 T _t;\par ??\par ?? \cf5 // default constructor\par ??\cf0 \cf4 protected\cf0 EnumEx()\par ?? \{\par ?? _t = \cf4 default\cf0 (T);\par ?? \}\par ??\par ?? \cf5 // constructor that takes the enum value\par ??\cf0 \cf4 protected\cf0 EnumEx(T t)\par ?? \{\par ?? _t = t;\par ?? \}\par ??\par ?? \cf5 // performs an implicit conversion to the underlying value\par ??\cf0 \cf4 public\cf0 \cf4 static\cf0 \cf4 implicit\cf0 \cf4 operator\cf0 T(\cf3 EnumEx\cf0 <T> obj)\par ?? \{\par ?? \cf4 return\cf0 obj._t;\par ?? \}\par ??\par ?? \cf5 // parses a string to the specified type, returns null if no string match is defined\par ??\cf0 \cf5 // this is a case-sensitive version\par ??\cf0 \cf4 public\cf0 \cf4 static\cf0 \cf4 object\cf0 Parse(\cf4 string\cf0 s, \cf3 Type\cf0 type)\par ?? \{\par ?? \cf3 FieldInfo\cf0 [] fis = type.GetFields(\cf3 BindingFlags\cf0 .Static | \cf3 BindingFlags\cf0 .Public);\par ?? \cf4 foreach\cf0 (\cf3 FieldInfo\cf0 fi \cf4 in\cf0 fis)\par ?? \{\par ?? \cf4 if\cf0 (s.Equals(fi.Name))\par ?? \{\par ?? \cf4 object\cf0 obj = fi.GetValue(\cf4 null\cf0 );\par ?? \cf4 return\cf0 obj;\par ?? \}\par ?? \}\par ??\par ?? \cf4 return\cf0 \cf4 null\cf0 ;\par ?? \}\par ??\par ?? \cf5 // System.Object overrides\par ??\cf0 \cf4 public\cf0 \cf4 override\cf0 \cf4 int\cf0 GetHashCode()\par ?? \{\par ?? \cf4 return\cf0 _t.GetHashCode();\par ?? \}\par ??\par ?? \cf4 public\cf0 \cf4 override\cf0 \cf4 string\cf0 ToString()\par ?? \{\par ?? \cf3 FieldInfo\cf0 [] fis = \cf4 this\cf0 .GetType().GetFields(\cf3 BindingFlags\cf0 .Static | \cf3 BindingFlags\cf0 .Public);\par ?? \cf4 foreach\cf0 (\cf3 FieldInfo\cf0 fi \cf4 in\cf0 fis)\par ?? \{\par ?? \cf4 object\cf0 obj = fi.GetValue(\cf4 this\cf0 );\par ?? T t = ((\cf3 EnumEx\cf0 <T>)obj)._t;\par ?? \cf4 if\cf0 (_t.Equals(t))\par ?? \cf4 return\cf0 fi.Name;\par ?? \}\par ??\par ?? \cf4 return\cf0 \cf4 string\cf0 .Empty;\par ?? \}\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;">[<span style="color: #2b91af;">Serializable</span>()]</p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">abstract</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">EnumEx</span><T> </p><p style="margin: 0px;"> <span style="color: blue;">where</span> T : <span style="color: blue;">struct</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: green;">// holds the actual value</span></p><p style="margin: 0px;"> <span style="color: blue;">protected</span> T _t;</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// default constructor</span></p><p style="margin: 0px;"> <span style="color: blue;">protected</span> EnumEx()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> _t = <span style="color: blue;">default</span>(T);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// constructor that takes the enum value</span></p><p style="margin: 0px;"> <span style="color: blue;">protected</span> EnumEx(T t)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> _t = t;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// performs an implicit conversion to the underlying value</span></p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">implicit</span> <span style="color: blue;">operator</span> T(<span style="color: #2b91af;">EnumEx</span><T> obj)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">return</span> obj._t;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// parses a string to the specified type, returns null if no string match is defined</span></p><p style="margin: 0px;"> <span style="color: green;">// this is a case-sensitive version</span></p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">object</span> Parse(<span style="color: blue;">string</span> s, <span style="color: #2b91af;">Type</span> type)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">FieldInfo</span>[] fis = type.GetFields(<span style="color: #2b91af;">BindingFlags</span>.Static | <span style="color: #2b91af;">BindingFlags</span>.Public);</p><p style="margin: 0px;"> <span style="color: blue;">foreach</span> (<span style="color: #2b91af;">FieldInfo</span> fi <span style="color: blue;">in</span> fis)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (s.Equals(fi.Name))</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">object</span> obj = fi.GetValue(<span style="color: blue;">null</span>);</p><p style="margin: 0px;"> <span style="color: blue;">return</span> obj;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: blue;">null</span>;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: green;">// System.Object overrides</span></p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">int</span> GetHashCode()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">return</span> _t.GetHashCode();</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">override</span> <span style="color: blue;">string</span> ToString()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: #2b91af;">FieldInfo</span>[] fis = <span style="color: blue;">this</span>.GetType().GetFields(<span style="color: #2b91af;">BindingFlags</span>.Static | <span style="color: #2b91af;">BindingFlags</span>.Public);</p><p style="margin: 0px;"> <span style="color: blue;">foreach</span> (<span style="color: #2b91af;">FieldInfo</span> fi <span style="color: blue;">in</span> fis)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">object</span> obj = fi.GetValue(<span style="color: blue;">this</span>);</p><p style="margin: 0px;"> T t = ((<span style="color: #2b91af;">EnumEx</span><T>)obj)._t;</p><p style="margin: 0px;"> <span style="color: blue;">if</span> (_t.Equals(t))</p><p style="margin: 0px;"> <span style="color: blue;">return</span> fi.Name;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: blue;">string</span>.Empty;</p><p style="margin: 0px;"> }</p><p style="margin: 0px;">}</p></div><br />This can be how a framework enum is defined:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;}??\fs20 \par ?? \cf3 public\cf0 \cf3 class\cf0 \cf4 LogType\cf0 : \cf4 EnumEx\cf0 <\cf3 int\cf0 >\par ?? \{\par ?? \cf3 protected\cf0 LogType() \par ?? : \cf3 base\cf0 ()\par ?? \{\par ?? \}\par ??\par ?? \cf3 protected\cf0 LogType(\cf3 int\cf0 value)\par ?? : \cf3 base\cf0 (value)\par ?? \{\par ?? \}\par ??\par ?? \cf3 public\cf0 \cf3 static\cf0 \cf3 implicit\cf0 \cf3 operator\cf0 \cf4 LogType\cf0 (\cf3 int\cf0 i)\par ?? \{\par ?? \cf3 return\cf0 \cf3 new\cf0 \cf4 LogType\cf0 (i);\par ?? \}\par ??\par ?? \cf3 public\cf0 \cf3 static\cf0 \cf4 LogType\cf0 Info = 1;\par ?? \cf3 public\cf0 \cf3 static\cf0 \cf4 LogType\cf0 Warning = 2;\par ?? \cf3 public\cf0 \cf3 static\cf0 \cf4 LogType\cf0 Error = 3;\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">LogType</span> : <span style="color: #2b91af;">EnumEx</span><<span style="color: blue;">int</span>></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">protected</span> LogType() </p><p style="margin: 0px;"> : <span style="color: blue;">base</span>()</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">protected</span> LogType(<span style="color: blue;">int</span> value)</p><p style="margin: 0px;"> : <span style="color: blue;">base</span>(value)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">implicit</span> <span style="color: blue;">operator</span> <span style="color: #2b91af;">LogType</span>(<span style="color: blue;">int</span> i)</p><p style="margin: 0px;"> {</p><p style="margin: 0px;"> <span style="color: blue;">return</span> <span style="color: blue;">new</span> <span style="color: #2b91af;">LogType</span>(i);</p><p style="margin: 0px;"> }</p><p style="margin: 0px;"> </p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">LogType</span> Info = 1;</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">LogType</span> Warning = 2;</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">LogType</span> Error = 3;</p><p style="margin: 0px;">}</p></div><br /><br />And if the application wishes to extend the enum:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;}??\fs20 \par ?? \cf3 public\cf0 \cf3 class\cf0 \cf4 LogTypeEx\cf0 : \cf4 LogType\par ??\cf0 \{\par ?? \cf3 public\cf0 \cf3 static\cf0 \cf4 LogType\cf0 Verbose = 4;\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">LogTypeEx</span> : <span style="color: #2b91af;">LogType</span></p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">LogType</span> Verbose = 4;</p><p style="margin: 0px;">}</p></div><br /><br />If I write the following test code:<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red43\green145\blue175;\red163\green21\blue21;\red0\green0\blue255;}??\fs20 \par ?? \cf3 LogType\cf0 logType = \cf3 LogType\cf0 .Error;\par ?? \cf3 Console\cf0 .WriteLine(logType.ToString());\par ??\par ?? \cf3 LogType\cf0 logType2 = \cf3 LogType\cf0 .Parse(\cf4 "Warning"\cf0 , \cf5 typeof\cf0 (\cf3 LogType\cf0 )) \cf5 as\cf0 \cf3 LogType\cf0 ;\par ?? \cf5 if\cf0 (logType2.Equals(\cf3 LogType\cf0 .Warning))\par ?? \cf3 Console\cf0 .WriteLine(\cf4 "Parsing succeeds"\cf0 );\par ??\par ?? \cf5 int\cf0 i = logType2;\par ?? \cf5 if\cf0 (i == 2)\par ?? \cf3 Console\cf0 .WriteLine(\cf4 "Implicit conversion to underlying type succeeds"\cf0 );}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: #2b91af;">LogType</span> logType = <span style="color: #2b91af;">LogType</span>.Error;</p><p style="margin: 0px;"><span style="color: #2b91af;">Console</span>.WriteLine(logType.ToString());</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: #2b91af;">LogType</span> logType2 = <span style="color: #2b91af;">LogType</span>.Parse(<span style="color: #a31515;">"Warning"</span>, <span style="color: blue;">typeof</span>(<span style="color: #2b91af;">LogType</span>)) <span style="color: blue;">as</span> <span style="color: #2b91af;">LogType</span>;</p><p style="margin: 0px;"><span style="color: blue;">if</span> (logType2.Equals(<span style="color: #2b91af;">LogType</span>.Warning))</p><p style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Parsing succeeds"</span>);</p><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">int</span> i = logType2;</p><p style="margin: 0px;"><span style="color: blue;">if</span> (i == 2)</p><p style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Implicit conversion to underlying type succeeds"</span>);</p></div><br /><br />I will get the following result in the console:<br /><br /><i><br />Error<br />Parsing succeeds<br />Implicit conversion to underlying type succeeds<br /></i><br /><br />So you see that this extensible enum type is pretty straightforward to use, extending the enum is very easy and it achieves pretty much what the native enum type has to offer. One thing left to be desired though is an issue using switch statement with C# compiler. Only the following works:<br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;\red163\green21\blue21;}??\fs20 \par ?? \cf3 switch\cf0 (logType2)\par ?? \{\par ?? \cf3 case\cf0 2:\par ?? \cf4 Console\cf0 .WriteLine(\cf5 "Do logic based on LogType.Info"\cf0 );\par ?? \cf3 break\cf0 ;\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">switch</span> (logType2)</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">case</span> 2:</p><p style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Do logic based on LogType.Info"</span>);</p><p style="margin: 0px;"> <span style="color: blue;">break</span>;</p><p style="margin: 0px;">}</p></div><br />while this doesn't compile (it fails at the case statement):<br /><br /><!--<br />{\rtf1\ansi\ansicpg\lang1024\noproof1252\uc1 \deff0{\fonttbl{\f0\fnil\fcharset0\fprq1 Courier New;}}{\colortbl;??\red0\green0\blue0;\red255\green255\blue255;\red0\green0\blue255;\red43\green145\blue175;\red163\green21\blue21;}??\fs20 \par ?? \cf3 switch\cf0 (logType2)\par ?? \{\par ?? \cf3 case\cf0 \cf4 LogType\cf0 .Info:\par ?? \cf4 Console\cf0 .WriteLine(\cf5 "Do logic based on LogType.Info"\cf0 );\par ?? \cf3 break\cf0 ;\par ?? \}}<br />--><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"> </p><p style="margin: 0px;"><span style="color: blue;">switch</span> (logType2)</p><p style="margin: 0px;">{</p><p style="margin: 0px;"> <span style="color: blue;">case</span> <span style="color: #2b91af;">LogType</span>.Info:</p><p style="margin: 0px;"> <span style="color: #2b91af;">Console</span>.WriteLine(<span style="color: #a31515;">"Do logic based on LogType.Info"</span>);</p><p style="margin: 0px;"> <span style="color: blue;">break</span>;</p><p style="margin: 0px;">}</p></div><br /><br />The C# compiler requires a constant in the case statement. It's also picky about what's in the switch statement. Without the implicit operator, the first switch code snippet wouldn't even have compiled either.Hugh Anghttp://www.blogger.com/profile/09609747815666754891noreply@blogger.com2