{"id":263,"date":"2014-08-12T07:00:00","date_gmt":"2014-08-12T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2014\/08\/12\/keep-your-eye-on-the-code-page-c-edition-warning-about-dllimport\/"},"modified":"2014-08-12T07:00:00","modified_gmt":"2014-08-12T07:00:00","slug":"keep-your-eye-on-the-code-page-c-edition-warning-about-dllimport","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20140812-00\/?p=263","title":{"rendered":"Keep your eye on the code page: C# edition (warning about DllImport)"},"content":{"rendered":"<p>\nOften, we receive problem reports from customers who failed to\nkeep their eye on the code page.\n<\/p>\n<blockquote CLASS=\"q\">\n<p>\nDoes the <code>SH&shy;Get&shy;File&shy;Info<\/code> function\nsupport files with non-ASCII characters in their names?\nWe find that the function either fails outright or\nreturns question marks when asked to provide information\nfor files with non-ASCII characters in their name.\n<\/p>\n<pre>\nusing System;\nusing System.Runtime.InteropServices;\nclass Program\n{\n static void Main(string[] args)\n {\n  string fileName = \"Bg&#x1e4d;R&#x1ed3;.txt\";\n  Console.WriteLine(\"File exists? {0}\", System.IO.File.Exists(fileName));\n  \/\/ assumes extensions are hidden\n  string expected = \"Bg&#x1e4d;R&#x1ed3;\";\n  Test(fileName, SHGFI_DISPLAYNAME, expected);\n  Test(fileName, SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES, expected);\n }\n static void Test(string fileName, uint flags, string expected)\n {\n  var actual = GetNameViaSHGFI(fileName, flags);\n  Console.WriteLine(\"{0} == {1} ? {2}\", actual, expected, actual == expected);\n }\n static string GetNameViaSHGFI(string fileName, uint flags)\n {\n  SHFILEINFO sfi = new SHFILEINFO();\n  if (SHGetFileInfo(fileName, 0, ref sfi, Marshal.SizeOf(sfi),\n                    flags) != IntPtr.Zero) {\n   return sfi.szDisplayName;\n  } else {\n   return null;\n  }\n }\n [StructLayout(LayoutKind.Sequential)]\n struct SHFILEINFO {\n  public IntPtr hIcon;\n  public int iIcon;\n  public uint dwAttributes;\n  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]\n  public string szDisplayName;\n  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]\n  public string szTypeName;\n }\n const uint SHGFI_USEFILEATTRIBUTES = 0x10;\n const uint SHGFI_DISPLAYNAME = 0x0200;\n [DllImport(\"shell32.dll\")]\n static extern IntPtr SHGetFileInfo(\n    string path, uint fileAttributes, ref SHFILEINFO info, int cbSize,\n    uint flags);\n}\n\/\/ Output:\n\/\/ File exists? True\n\/\/  == Bg?R? ? False\n\/\/ Bg?R? == Bg?R? ? False\n<\/pre>\n<p>\nIf we ask for the display name, the function fails\neven though the file does exist.\nIf we also pass the\n<code>SHGFI_USE&shy;FILE&shy;ATTRIBUTES<\/code> flag\nto force the system to act as if the file existed,\nthen it returns the file name but with question marks\nwhere the non-ASCII characters should be.\n<\/p>\n<\/blockquote>\n<p>\nThe <code>SH&shy;Get&shy;File&shy;Info<\/code> function\nsupports non-ASCII characters just fine,\nprovided you call the version that supports non-ASCII characters!\n<\/p>\n<p>\nThe customer here fell into the trap of not keeping their eye on the code\npage.\nIt goes back to an unfortunate choice of defaults in the\nSystem.Runtime.Interop&shy;Services namespace:\nAt the time the CLR was originally being developed,\nWindows operating systems derived from Windows&nbsp;95 were still\nin common use,\nso the CLR folks decided to default to\n<i>Char&shy;Set.Ansi<\/i>.\nThis made sense back in the day, since it meant that your program\nran the same on Windows&nbsp;98 as it did in Windows&nbsp;NT.\nIn the passage of time, the Windows&nbsp;95 series\nof operating systems became obsolete,\nso the need to be compatible with it gradually disappeared.\nBut too late.\nThe rules were already set, and the default of\n<i>Char&shy;Set.Ansi<\/i> could not be changed.\n<\/p>\n<p>\nThe solution is to specify\n<i>Char&shy;Set.Unicode<\/i> explicitly in the\n<i>Struct&shy;Layout<\/i>\nand\n<i>Dll&shy;Import<\/i> attributes.\n<\/p>\n<p>\nFxCop catches this error, flagging it as\n<i>Specify&shy;Marshaling&shy;For&shy;PInvoke&shy;String&shy;Arguments<\/i>.\nThe error explanation talks about the security risks of\nunmapped characters,\nwhich is all well and good,\nbut it is looking too much at the specific issue and not\nso much at the big picture.\nAs a result, people may ignore the issue because it is flagged\nas a complicated security issue, and they will think,\n&#8220;Eh, this is just my unit test, I&#8217;m not concerned about security here.&#8221;\nHowever, the big picture is\n<\/p>\n<blockquote CLASS=\"m\"><p>\nThis is almost certainly an oversight on your part.\nYou didn&#8217;t really mean to disable Unicode support here.\n<\/p><\/blockquote>\n<p>\nChange the lines\n<\/p>\n<pre>\n [StructLayout(LayoutKind.Sequential)]\n [DllImport(\"shell32.dll\")]\n<\/pre>\n<p>\nto\n<\/p>\n<pre>\n [StructLayout(LayoutKind.Sequential<font COLOR=\"blue\">, CharSet=CharSet.Unicode<\/font>)]\n [DllImport(\"shell32.dll\"<font COLOR=\"blue\">, CharSet=CharSet.Unicode<\/font>)]\n<\/pre>\n<p>\nand re-run the program. This time, it prints\n<\/p>\n<pre>\nFile exists? True\nBg?R? == Bg?R? ? True\nBg?R? == Bg?R? ? True\n<\/pre>\n<p>\nNote that you have to do the string comparison in the program\nbecause the console itself has a troubled history with Unicode.\nAt this point, I will simply cue a\n<a HREF=\"http:\/\/www.siao2.com\/\">\nMichael Kaplan<\/a>\nrant and link to\n<a HREF=\"http:\/\/www.siao2.com\/2010\/04\/07\/9989346.aspx\">\nan article explaining how to ask nicely<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Often, we receive problem reports from customers who failed to keep their eye on the code page. Does the SH&shy;Get&shy;File&shy;Info function support files with non-ASCII characters in their names? We find that the function either fails outright or returns question marks when asked to provide information for files with non-ASCII characters in their name. using [&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-263","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Often, we receive problem reports from customers who failed to keep their eye on the code page. Does the SH&shy;Get&shy;File&shy;Info function support files with non-ASCII characters in their names? We find that the function either fails outright or returns question marks when asked to provide information for files with non-ASCII characters in their name. using [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/263","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=263"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/263\/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=263"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=263"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=263"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}