皆さんごきげんよう。今日は、懺悔です。
予定していた偉い人へのレビューがリスケになって、ちょっと時間が空いたので、そうだ担当製品(※)をたまにはちゃんとさわらなきゃ!と思いついて、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 さんもお祝いしてくれたんですよ、かわいい。
🎂 🥳 🎈 Happy 20th Birthday @dotnet 🎈🥳 🎂
— GitHub (@github) February 13, 2022
Anyone ordered a Slice<Cake> for the charming Octocat in the red polo??#dotNETLovesMe pic.twitter.com/gIUkySDxYL
#簡単にできたはずだったもの(docs にある C# サンプル)
いや違う…これ .NET 6 なんだし、.NET Framework 2.0 時代じゃあるまいし、もっとこう、.NET 感を醸し出したいというか…楽にできる方法があるはずなんでは??いやまあ適当に構造体書けばいいからこれもこれでいいけど、なんたって量が多い。コード多い。こんなんでいいのか。なんか数行で何とかイイカンジになるもんなんじゃないのか?
担当の子なのに、その子の良さを引き出し切れてないなんて…!!!
そんなわけで、後輩君に泣きついてみました。わかんないときはオーソリティに聞くのができるならそれが一番!
ええ、彼は優秀な方です。数十秒もせずに教えてもらったのが以下。
なんだ、青い鳥は家に居たんだね!を体現する、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);
短っ!!!
おかげでやりたいことができましたけど、ほんとね、なんかやろう!って思ったらまず検索すべきでした。
もちろん全部を満たすサンプルはありませんが、組み合わせていけばなんかできるはず。一昔前は、あんまりサンプルとして使えるコードって少なかったけど、今はかなり充実したな、という実感がありました。
###追記 : .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 テクノロジーに依存しちゃってるからわけた、ということのようです。実際、以下のような警告が出ます。
そんなわけで、System.Drawing.Text を使って .NET 6 で実装しようとした場合、NuGet から System.Drawing.Common のパッケージを別途入手してインストールをしてあげる必要があります。[ツール] - [NuGet パッケージマネージャ] あるいは、[ソリューションの NuGet パッケージの管理] あたりから System.Drawing.Common を入れてあげてください。それにしても「破壊的変更」って字面がすごい。Breaking changes…
System.Drawing.Common の NuGet はこちら
・・・
まあそんなわけで、今度は、いきなり DllImport とかする前に、docs を検索するようにしようと思います。
あと最新の .NET ちゃんと上っ面じゃなくて、手を動かして勉強して良さを語れるように頑張ります…。
だいぶなまってるから大変ですけどね…。
以上、懺悔でした。
皆さん、ごきげんよう。