28
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-02-16

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

予定していた偉い人へのレビューがリスケになって、ちょっと時間が空いたので、そうだ担当製品(※)をたまにはちゃんとさわらなきゃ!と思いついて、Visual Studio 2022 でちょっとリハビリがてら、フォントを列挙するプログラムを C++ と .NET で作ったんですが…。すごい久しぶりだったんで、.NET の良さを台無しにするものを作ってしまいましたので懺悔します。(そしてその後、後輩様に「これ見れば一発ですよ」とそのものずばりの docs のページを教えてもらい、反省。System.Drawing で何とかなるのね…)

(※) わたくし、エンジニアではございませんで、マーケティングの人間でございます…。

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

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

いや、あとで見るとこんな簡単にできるものを、なんでわざわざ P/Invoke してるんだろう?と思うんですが、今後もこういう変なことをやらないように戒めるために懺悔します!
※ コードは久しぶりに書いたから変なところあるかもしれません

#作ったもの (C++)

#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# で再現してみようとか思いついちゃったんでしょう。
再現すべきなのは結果であって、つくりではないのに…

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」とか浮かれたポスト(※)しといてなんですけど、.NET ってこんなだったっけ…

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

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

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

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

image.png

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

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

(2021.2.17 追記) もしかしたら、System.Drawing.Text を .NET 6 で利用する際に using 句使っても CS0103 でビルドができん!と思う人もいるかもと思って、後述 "追記" セクションを追加しました。

// 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

短っ!!!

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

###追記 : .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

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

image.png

System.Drawing.Common の NuGet はこちら


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

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

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

28
19
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?