{"id":9983,"date":"2011-08-03T07:00:00","date_gmt":"2011-08-03T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2011\/08\/03\/a-shell-extension-is-a-guest-in-someone-elses-house-dont-go-changing-the-code-page\/"},"modified":"2011-08-03T07:00:00","modified_gmt":"2011-08-03T07:00:00","slug":"a-shell-extension-is-a-guest-in-someone-elses-house-dont-go-changing-the-code-page","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110803-00\/?p=9983","title":{"rendered":"A shell extension is a guest in someone else&#039;s house; don&#039;t go changing the code page"},"content":{"rendered":"<p>A customer reported a problem with their shell extension:<\/p>\n<blockquote class=\"q\"><p> We want to format a floating point number according to the user&#8217;s default locale. We do this by calling <code>snprintf<\/code> to convert the value from floating point to text with a period (U+002E) as the decimal separator, then using <code>Get&shy;Number&shy;Format<\/code> to apply the user&#8217;s preferred <a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2006\/04\/17\/577483.aspx\"> grouping character<\/a>, decimal separator, etc. We found, however, that if the user is running in (say) German, we find that sometimes (but not always) the <code>snprintf<\/code> function follows the German locale and uses a comma (U+002C) as the decimal separator with no thousands separator. This format prevents the <code>Get&shy;Number&shy;Format<\/code> function from working, since it requires the decimal separator to be U+002E. What is the recommended way of formatting a floating point number according to the user&#8217;s locale? <\/p><\/blockquote>\n<p> <a href=\"http:\/\/msdn.microsoft.com\/bb688127.aspx#ene\"> The recommended way of formatting a floating point number according to the user&#8217;s locale<\/a> is indeed to use a function like <code>snprintf<\/code> to convert it to text with U+002E as the decimal separator (and other criteria), then use <code>Get&shy;Number&shy;Format<\/code> to apply the user&#8217;s locale preferences.\n The <code>snprintf<\/code> function follows the C\/C++ runtime locale to determine how the floating point number should be converted, and the default C runtime locale is the so-called <code>\"C\"<\/code> locale which indeed uses U+002E as the decimal separator. Since you&#8217;re getting U+002C as the decimal separator, somebody must have called <code>set&shy;locale<\/code> to change the locale from <code>\"C\"<\/code> to a German locale, most likely by passing <code>\"\"<\/code> as the locale, which means &#8220;follow the locale of the environment.&#8221;<\/p>\n<blockquote class=\"q\"><p> Our shell extension is running in Explorer. Under what conditions will Explorer call <code>set&shy;locale(LC_NUMERIC, \"\")<\/code>? What should we do if the locale is not <code>\"C\"<\/code>? <\/p><\/blockquote>\n<p> As it happens, Explorer never calls <code>set&shy;locale<\/code>. It leaves the locale set to the default value of <code>\"C\"<\/code>. Therefore, the call to <code>snprintf<\/code> should have generated a string with U+002E as the decimal separator. Determining who was calling <code>set&shy;locale<\/code> was tricky since the problem was intermittent, but after a lot of work, we found the culprit: some other shell extension loaded before the customer&#8217;s shell extension and <a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2009\/12\/02\/9931183.aspx\"> decided to change the carpet<\/a> by calling <code>set&shy;locale(LC_ALL, \"\")<\/code> in its <code>DLL_PROCESS_ATTACH<\/code>, presumably so that its calls to <code>snprintf<\/code> would follow the environment locale. What made catching the miscreant more difficult was that the rogue shell extension didn&#8217;t restore the locale when it was unloaded (not that that would have been the correct thing to do either), so by the time the bad locale was detected, the culprit was long gone!\n That other DLL <a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2008\/12\/11\/9193695.aspx\"> used a global setting to solve a local problem<\/a>. Given the problem &#8220;How do I get my calls to <code>snprintf<\/code> to use the German locale settings?&#8221; they decided to change <i>all<\/i> calls to <code>snprintf<\/code> to use the German locale settings, even the calls that didn&#8217;t originate from the DLL itself. What if the program hosting the shell extension had done a <code>set&shy;locale(LC_ALL, \"French\")<\/code>? Tough noogies; the rogue DLL just screwed up the host program, which wants to use French locale settings but is now being forced to use German ones. The program probably won&#8217;t notice that somebody <a href=\"https:\/\/www.youtube.com\/watch?v=6HGKJHpQkfI\"> secretly replaced its coffee with Folgers Crystals<\/a>. It&#8217;ll be a client who notices that the results are not formatted correctly. The developers of the host program, of course, won&#8217;t be able to reproduce the problem in their labs, since they don&#8217;t have the rogue shell extension, and the problem will be classified as &#8220;unsolved.&#8221;<\/p>\n<p> What both the rogue shell extension and the original customer&#8217;s shell extension should be using is the <code>_l<\/code> variety of string formatting functions (in this case <code>_snprintf_l<\/code>, although <code>_snprintf_s_l<\/code> is probably better). The <code>_l<\/code> variety lets you pass an explicit locale which will be used to format that particular string. (You create one of these <code>_locale_t<\/code> objects by calling <code>_create_locale<\/code> with the same parameters you would have passed to <code>set&shy;locale<\/code>.) Using the <code>_l<\/code> technique solves two problems: <\/p>\n<ol>\n<li>It lets you apply a local solution to a local problem.     The locale you specify applies only to the specific call;     the process&#8217;s default locale remains unchanged. <\/li>\n<li>It allows you to ensure that you get the locale you want     even if the host process has set a different locale. <\/li>\n<\/ol>\n<p> If either the customer&#8217;s DLL or the rogue DLL had followed this principle of not using a global setting to solve a local problem, the conflict would not have arisen. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>A customer reported a problem with their shell extension: We want to format a floating point number according to the user&#8217;s default locale. We do this by calling snprintf to convert the value from floating point to text with a period (U+002E) as the decimal separator, then using Get&shy;Number&shy;Format to apply the user&#8217;s preferred grouping [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-9983","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A customer reported a problem with their shell extension: We want to format a floating point number according to the user&#8217;s default locale. We do this by calling snprintf to convert the value from floating point to text with a period (U+002E) as the decimal separator, then using Get&shy;Number&shy;Format to apply the user&#8217;s preferred grouping [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/9983","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=9983"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/9983\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=9983"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=9983"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=9983"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}