[懺悔] すごい久しぶりにリハビリプログラム作ったら .NET の良さを台無しにした件（docs をちゃんと見よう…!）

皆さんごきげんよう。今日は、懺悔です。

予定していた偉い人へのレビューがリスケになって、ちょっと時間が空いたので、**そうだ担当製品(※)をたまにはちゃんとさわらなきゃ！**と思いついて、Visual Studio 2022 でちょっとリハビリがてら、フォントを列挙するプログラムを C++ と .NET で作ったんですが…。すごい久しぶりだったんで、**.NET の良さを台無しにするものを作ってしまいましたので懺悔します**。(そしてその後、後輩様に「これ見れば一発ですよ」とそのものずばりの [**docs のページ**](https://docs.microsoft.com/ja-jp/dotnet/desktop/winforms/advanced/how-to-enumerate-installed-fonts?view=netframeworkdesktop-4.8)を教えてもらい、反省。System.Drawing で何とかなるのね…）

(※) わたくし、エンジニアではございませんで、[マーケティング](https://www.linkedin.com/in/uikou/)の人間でございます…。

#やりたかったこと、教訓、そして言い訳

* **やりたかったこと** : 英語 OS にしたら、なんかインストールされてるフォント ファミリが違うけど、何が入ってるんだろう？というのをただ見たかっただけ。
* **教訓** : 
    * 忙しくても、自分の担当エリアはきちんと時間を使って**キャッチアップすることを怠っちゃダメ！！**
    * **作る前にまず検索だ**。だいたい Docs とかにサンプル コードはある...。（そのものずばりはなくてもカスタマイズは頑張ろう）
* **言い訳** : 
    * C++ から作って、何も考えずにそのままを再現しようとしてしまいました…
    * C++ と .NET で作ったのは、何となく。

いや、あとで見るとこんな簡単にできるものを、なんでわざわざ [P/Invoke](https://docs.microsoft.com/ja-jp/dotnet/standard/native-interop/pinvoke) してるんだろう？と思うんですが、今後もこういう変なことをやらないように戒めるために懺悔します！
※ コードは久しぶりに書いたから変なところあるかもしれません

#作ったもの (C++)

```cpp
#include <iostream>
#include <windows.h>
#include <wingdi.h>

int CALLBACK EnumFontFamExProc(ENUMLOGFONTEX FAR* lpelf, NEWTEXTMETRICEX FAR* lpntm, int FontType, LPARAM lParam)
{
	printf("%ls (%s)\n", lpelf->elfLogFont.lfFaceName, (lpelf->elfLogFont.lfPitchAndFamily & FIXED_PITCH) ? "FIXED" : "non-FIXED");
	return TRUE;
}

int main()
{
	LOGFONT logfont;
	ZeroMemory(&logfont, sizeof(LOGFONT));
	EnumFontFamiliesEx(GetDC(NULL), &logfont, (FONTENUMPROC)EnumFontFamExProc, 0,0);
}
```

おーうごくうごく～。ここまででよかったんですが…
そうだ、.NET でも**同じように**書いてみよう！久しぶりだし！って思っちゃったんですよね…。

#次に作ってみたもの（C# だけど…)
ほんと、なんでコードを **C++ と同じように C# で再現してみよう**とか思いついちゃったんでしょう。
再現すべきなのは**結果であって、つくりではない**のに…

```csharp
using System;
using System.Runtime.InteropServices;


namespace ConsoleApp2
{
	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
	internal struct EnumLogFontEx
	{
		public LogFont elfLogFont;
		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public char[] elfFullName;
		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] public char[] elfStyle;
		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] public char[] elfScript;
	}

	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
	internal class LogFont
	{
		public int lfHeight = 0;
		public int lfWidth = 0;
		public int lfEscapement = 0;
		public int lfOrientation = 0;
		public int lfWeight = 0;
		public byte lfItalic = 0;
		public byte lfUnderline = 0;
		public byte lfStrikeOut = 0;
		public byte lfCharSet = 0;
		public byte lfOutPrecision = 0;
		public byte lfClipPrecision = 0;
		public byte lfQuality = 0;
		public byte lfPitchAndFamily = 0;
		[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
		public string lfFaceName = String.Empty;
	}
	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
	internal struct NewTextMetric
	{
		public long tmHeight;
		public long tmAscent;
		public long tmDescent;
		public long tmInternalLeading;
		public long tmExternalLeading;
		public long tmAvecharWidth;
		public long tmMaxcharWidth;
		public long tmWeight;
		public long tmOverhang;
		public long tmDigitizedAspectX;
		public long tmDigitizedAspectY;
		public char tmFirstchar;
		public char tmLastchar;
		public char tmDefaultchar;
		public char tmBreakchar;
		public byte tmItalic;
		public byte tmUnderlined;
		public byte tmStruckOut;
		public byte tmPitchAndFamily;
		public byte tmcharSet;
		public ulong ntmFlags;
		public uint ntmSizeEM;
		public uint ntmCellHeight;
		public uint ntmAvgWidth;
	}

	internal delegate int EnumFontFamExProc(
		[MarshalAs(UnmanagedType.Struct)] ref EnumLogFontEx lpelf,
		[MarshalAs(UnmanagedType.Struct)] ref NewTextMetric lpntm,
		uint fontType,
		int lParam
	);
	internal class myProgram
	{
		[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
		internal static extern int EnumFontFamiliesEx(
							IntPtr hdc,
							[MarshalAs(UnmanagedType.LPTStr)] string lpszFamily,
							EnumFontFamExProc lpEnumFontFamProc,
							int lParam,
							int dwFlags				
					);

		[DllImport("User32.dll", CharSet = CharSet.Auto)]
		internal static extern IntPtr GetDC(
					IntPtr hWnd
			);

		static private int EnumFontFamExProc(
			ref EnumLogFontEx lpelf,
			ref NewTextMetric lpntm,
			uint FontType,
			int lParam)
		{

			Console.WriteLine(lpelf.elfLogFont.lfFaceName);
			return 1;

		}
		// See https://aka.ms/new-console-template for more information
		[STAThread]
		static void Main(string[] args)
		{
			EnumFontFamExProc fep = new EnumFontFamExProc(EnumFontFamExProc);
			IntPtr dc = GetDC(IntPtr.Zero);
			EnumFontFamiliesEx(dc, null, fep,0, 0);
			Console.ReadLine();
		}
	}		
}
```

うん、**長い**。**長いね**。
…なんででしょうね。昨日、「[**Happy Birthday！.NET、20 歳になりましたよ。君だけの dotnet-bot くんを作ろうぜ！＃dotNETLovesMe**](https://qiita.com/uikou/items/adf5980f409a316d3978)」とか**浮かれた**ポスト(※)しといてなんですけど、**.NET ってこんなだったっけ…**？

(※) 2022/02/14 は、.NET 20 歳のお誕生日でした。GitHub さんもお祝いしてくれたんですよ、かわいい。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">🎂 🥳 🎈 Happy 20th Birthday <a href="https://twitter.com/dotnet?ref_src=twsrc%5Etfw">@dotnet</a> 🎈🥳 🎂 <br><br>Anyone ordered a Slice&lt;Cake&gt; for the charming Octocat in the red polo??<a href="https://twitter.com/hashtag/dotNETLovesMe?src=hash&amp;ref_src=twsrc%5Etfw">#dotNETLovesMe</a> <a href="https://t.co/gIUkySDxYL">pic.twitter.com/gIUkySDxYL</a></p>&mdash; GitHub (@github) <a href="https://twitter.com/github/status/1492897690983804936?ref_src=twsrc%5Etfw">February 13, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#簡単にできたはずだったもの（docs にある C# サンプル)
いや違う…**これ .NET 6** なんだし、.NET Framework 2.0 時代じゃあるまいし、もっとこう、**.NET 感を醸し出したい**というか…楽にできる方法があるはずなんでは？？いやまあ適当に構造体書けばいいからこれもこれでいいけど、なんたって量が多い。コード多い。こんなんでいいのか。**なんか数行で何とかイイカンジになるもんなんじゃないのか？**

**担当の子なのに、その子の良さを引き出し切れてないなんて…！！！**

そんなわけで、後輩君に泣きついてみました。わかんないときはオーソリティに聞くのができるならそれが一番！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1529732/bac0e286-a945-cca2-004d-283f284c8a2e.png)

ええ、彼は優秀な方です。数十秒もせずに教えてもらったのが以下。
なんだ、青い鳥は家に居たんだね！を体現する、**docs が地味にサンプルコードが充実した**ことに気づきました。いやうすうす知ってましたけど、実際書かないと、おお！これは！というありがたさとかがいまいちわからないものですね。（反省ポイント）**わー！ダメだぁ！これは懺悔ものだ**。

https://docs.microsoft.com/ja-jp/dotnet/desktop/winforms/advanced/how-to-enumerate-installed-fonts?view=netframeworkdesktop-4.8

コードはこちら。（.NET6）
※ docs のサンプルをそのまま何も考えずにコピペして、最後を Console 出力するだけに変更しているので、いらないところもありますがご容赦ください。出力処理が docs のサンプルだと `Graphics.DrawString()` だったのですがコンソール アプリで作ったこともあってさくっと `Console.WriteLine()` に変更しています。なので、その前の処理のいくつかはいらないものになってますが、docsのサンプルそのままの部分も含めて載っけています。

>(2021.2.17 追記) もしかしたら、System.Drawing.Text を .NET 6 で利用する際に using 句使っても [CS0103](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs0103) でビルドができん！と思う人もいるかもと思って、後述 "追記" セクションを追加しました。

```csharp
// See https://aka.ms/new-console-template for more information
FontFamily fontFamily = new FontFamily("Arial");
Font font = new Font(
   fontFamily,
   8,
   FontStyle.Regular,
   GraphicsUnit.Point);
RectangleF rectF = new RectangleF(10, 10, 500, 500);
SolidBrush solidBrush = new SolidBrush(Color.Black);

string familyName;
string familyList = "";
FontFamily[] fontFamilies;

InstalledFontCollection installedFontCollection = new InstalledFontCollection();

// Get the array of FontFamily objects.
fontFamilies = installedFontCollection.Families;

// The loop below creates a large string that is a comma-separated
// list of all font family names.

int count = fontFamilies.Length;
for (int j = 0; j < count; ++j)
{
    familyName = fontFamilies[j].Name;
    familyList = familyList + familyName;
  //  familyList = familyList + ",  ";
    familyList = familyList + "\r\n";
}

// Draw the large string (list of all families) in a rectangle.
// e.Graphics.DrawString(familyList, font, solidBrush, rectF);
Console.WriteLine(familyList);
```
■ 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1529732/c962f44a-43a8-f055-1702-ac0130397ba4.png)

**短っ！！！**

おかげでやりたいことができましたけど、ほんとね、**なんかやろう！って思ったらまず検索すべき**でした。
もちろん全部を満たすサンプルはありませんが、組み合わせていけばなんかできるはず。一昔前は、あんまりサンプルとして使えるコードって少なかったけど、今はかなり充実したな、という実感がありました。

###追記 : .NET6 では、System.Drawing.Text は NuGet から別途ダウンロードしないとビルド時にエラー (CS0103) 起こします

下記の docs によると、.NET 6 になって、"Because System.Drawing.Common was designed to be a thin wrapper over Windows technologies, its cross-platform implementation is subpar." という判断となった模様です。要は、クロス プラットフォームの思想の観点でみると、Windows テクノロジーに依存しちゃってるからわけた、ということのようです。実際、以下のような警告が出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1529732/191b272c-176a-759d-f0d0-570a0715da7f.png)

https://docs.microsoft.com/ja-jp/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only

そんなわけで、**System.Drawing.Text を使って .NET 6 で実装しようとした場合、NuGet から [System.Drawing.Common のパッケージを別途入手してインストール](https://www.nuget.org/packages/System.Drawing.Common/)をしてあげる必要があります**。[ツール] - [NuGet パッケージマネージャ] あるいは、[ソリューションの NuGet パッケージの管理] あたりから [System.Drawing.Common](https://www.nuget.org/packages/System.Drawing.Common/) を入れてあげてください。それにしても「破壊的変更」って字面がすごい。Breaking changes…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1529732/94eb6e01-2102-bf71-18df-1ba9d20e2c13.png)

System.Drawing.Common の NuGet はこちら

https://www.nuget.org/packages/System.Drawing.Common/

-----------------------------------------------------------

・・・
まあそんなわけで、今度は、**いきなり DllImport とかする前に、docs を検索するようにしよう**と思います。

https://docs.microsoft.com/ja-jp/

あと最新の .NET ちゃんと上っ面じゃなくて、手を動かして勉強して良さを語れるように頑張ります…。
だいぶなまってるから大変ですけどね…。

以上、懺悔でした。
皆さん、ごきげんよう。