イネマルのプログラミング備忘録

趣味プログラマーのメモ

フォントファイルからフォント名を取得する

f:id:inemaru:20170611112448p:plain

目的

C++で、フォントファイルからフォント名(フォントファミリ名)を取得します。
対応フォーマットは、スタンダードに ttf, otf とします。

対応

結局のところ、バイナリから直接、フォント名を取得するのが高速だと思うので、
フォーマットを解析しながらフォント名を取得します。

既知の方法

今回は使用しない既知の方法としてGDIを使用した方法があります。

  • GDI+の「PrivateFontCollection」

GDIのインスタンスを起動させる必要があり
対応フォーマットは多種ですが、フォント名を取得するだけで使用するには低速です。

昔、この方法で実装しましたが、
「GDIの起動が低速」(+自分の実装ミスでバグ持ち)という問題があり、
かなり微妙でした。

  • GDI+の非公開API「GetFontResourceInfo」

高速に動作しますが取得した結果が、
英名だったり「 & 」で結合された結果が戻ったり仕様に不明点が多いです。
非公開APIなので最悪、突然使えなくなるかもしれないという危なっかしさもあります。

注意

勉強のついでの感覚で実装しているので、実用性は考慮していません。
また、バグとかあっても自己責任でお願いします。

準備

ブログにコードを載せるには量が多いので、ソースはアプロダに上げます。
フォントファイルからフォント名を取得.zip(フォントファイルからフォント名を取得.zip) ダウンロード | イネマルの備忘倉庫 | uploader.jpをダウンロードして
VS2015以降のコンソールプロジェクトに追加した状態で動く想定です。

プログラム

下記のように使用します。

int main()
{
	// ロケール設定
	std::locale::global(std::locale("japanese"));
	setlocale(LC_ALL, std::locale().c_str());

	// フォント名取得
	FontFamilyMap nameMap;
	if (GetFontFamilyName(L"./GenEiLateGo.otf", nameMap)) {
		std::wcout << L"英:" << nameMap[LANGUAGE_ID::LID_ENGLISH] << std::endl;
		std::wcout << L"日:" << nameMap[LANGUAGE_ID::LID_JAPANESE] << std::endl;
	}

	// 一時停止
	system("pause");

	return 0;
}

出力結果

英:GenEi LateGo
日:源暎ラテゴ
続行するには何かキーを押してください . . .

使用させて頂いたもの

  • フォント

源暎フォント置き場 - 御琥祢屋

  • 参考サイト

d.hatena.ne.jp
www.codeguru.com

あとがき

経緯について
プログラム中にインストールしていないフォントを
使用したいタイミングがあったとして
Windows環境だと、自プロセスで使用するフォントを追加する場合に、
AddFontResourceExを使用すると思います。

追加には、フォントファイルのパスが分かれば問題ないですが、
いざ使用する時、フォント名を指定する必要があります。

フォント名を明示しないといけないという柔軟さのなさを回避するには、
フォント名がフォントファイルから分かった方が都合が良いということで、
対応した結果を備忘録行き。

バッチファイルの PowerShell 起動ワンライナーを 管理者権限 で実行する

バッチファイルのPowerShell起動ワンライナーを管理者権限で実行する

バッチファイルからPowerShellを起動するワンライナーは、
多種ありますが、管理者権限なしで、実行された場合、
ドライブ等のアクセスに制限がかかります。

ワンライナーを使用する場合、
管理者権限で再起動させると動作に若干、癖があるようです。
ProcessStartInfoの動作で無駄にハマったので、対応方法を考えてみました。
以下は、管理者権限で起動する方法の一例です。

ワンライナーは、reosabloさんの記事の物をベースに使用させて頂きます。
reosablo.hatenablog.jp

注意

今回は、C#コードをPowerShellから使用しています。
おそらく、PowerShellのみでも実装可能だと思います。

また、C#コードを経由して、権限を昇格させる方法なので、
初回起動時は、起動待ちが発生すると思われます。

確認用のプログラム

管理者権限で起動されなかった場合は、
UACによって権限を昇格させて再起動します。

バッチファイルを保存する際は、文字コードに注意が必要です。
(日本語環境では、ShiftJISにすれば問題ないと思われます。)

※動作確認は、Windows10で行いました。

template.bat

@setlocal enabledelayedexpansion&set a=%*&(if defined a set a=!a:"=\"!&set a=!a:'=''!)&@start /min powershell -windowstyle hidden -sta /c $i=$input;iex ('$i^|^&{$PSCommandPath=\"%~f0\";$PSScriptRoot=\"%~dp0";#'+(${%~f0}^|Out-String)+'} '+('!a!'-replace'[$(),;@`{}]','`$0'))&exit/b
<#
/// <summary>
/// 管理者権限で実行するサンプル
/// </summary>
#>

# アプリケーション名
set-variable -option readonly -name APP_NAME -value "テンプレート"

# フォームサイズ
set-variable -option readonly -name FORM_SIZE -value "640, 480"
set-variable -option readonly -name FORM_MIN_SIZE -value $FORM_SIZE

# 管理者権限
set-variable -option readonly -name ENABLE_ADMIN -value "true"

<#
	開発者向け定義
#>

# アプリケーションファイル名
set-variable -option readonly -name APP_FILE_NAME -value (Split-Path -Leaf $PSCommandPath)
set-variable -option readonly -name APP_FILE_BASE_NAME -value (Get-Item $APP_FILE_NAME).BaseName

#<# -------------------------------------------------------------------------------------------------------
Add-Type -IgnoreWarnings -Language CSharpVersion3 `
-ReferencedAssemblies (
#	"System",
	"System.Drawing",
	"System.Windows.Forms"
) `
-TypeDefinition @"
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Principal;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

public class MainForm: Form
{
	public MainForm()
	{
		if (Admin.IsAdmin) {
			this.Text = "$APP_NAME (管理者)";
		}
		else {
			this.Text = "$APP_NAME";
		}
		this.FormBorderStyle = FormBorderStyle.Sizable;
		this.Size = new Size($FORM_SIZE);
		this.MinimumSize = new Size($FORM_MIN_SIZE);
	}
}

public static class Admin
{
	public static bool RestartAdmin(Object[] Args)
	{
		if (IsAdmin) {
			return true;
		}
		var psi = new ProcessStartInfo("cmd", string.Format("/C \"\"{0}\" {1}\"",
			@"$PSCommandPath",
			string.Join(" ", Args
				.Select(x => string.Format("\"{0}\"", x))
				.ToArray())));
		psi.Verb = "runas";
		try {
			Process.Start(psi);
		}
		catch (System.ComponentModel.Win32Exception){}
		return false;
	}
	
	public static bool IsAdmin
	{ 
		get {
			var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
			return principal.IsInRole(WindowsBuiltInRole.Administrator);
		}
	}
}

public static class Entry
{
	[STAThread]
	public static void Run(Object[] Args)
	{
#if $ENABLE_ADMIN
		if (Admin.RestartAdmin(Args)) {
			MainProc(Args);
		}
#else
		MainProc(Args);
#endif
	}
	
	static void MainProc(Object[] Args)
	{
		MainForm f = new MainForm();
		f.ShowDialog();
	}
}
"@
# -------------------------------------------------------------------------------------------------------#>

# アセンブリ読み込み
Add-Type -Assembly System.Windows.Forms

try
{
	# 実行
	[Entry]::Run($args)
}
catch [Exception] {
	# 例外
	[System.Windows.Forms.MessageBox]::Show($error, "エラー", "OK", "Error")
}
finally {
	# プロセス解放
	[GC]::Collect()
}

その他

ENABLE_ADMIN の初期値を"false"にすれば、管理者起動を強制しないようになります。

あとがき

PowerShellコマンドプロンプトで起動すると、
使用するコマンドプロンプトPowerShellに乗っ取られるような動作をします。
バッチは、知識があまりないので最適な方法かわかりませんが、
対応策として、start コマンドを使用して別枠起動させています。

また、フォームを表示する際に、コマンドプロンプトが表示されるのが気になったので、
最低限の表示で済むように書いたつもりです。
(batをダブルクリックした時点で、一瞬は表示されますが、仕様です。)

string wstring 相互変換 (Windows用)

stringとwstringの相互変換

stlを使用していると、文字列にstd::stringを使用する事が多いですが、
std::wstringに変換したいタイミングがあるかもしれません。
そんな時の対応方法の一例です。

注意

Windows環境で使える変換手法を覚書きします。
今回は
「MultiByteToWideChar/WideCharToMultiByte」
を使用した例です。
マルチプラットフォームな方法では、

  • C++11の「std::wsting_convert」
  • C/C++の「mbsrtowcs/std::mbsrtowcs」

があるらしいです。

確認用のプログラム

stringとwstringの文字列を
それぞれwstringとstringに変換してコンソールに表示してみる。

※動作確認は、VC2013で行いました

StringConverter.hpp

// StringConverter.hpp
#pragma once

#include <string>
#include <vector>
#include <locale>
#include <winstring.h>

namespace StringConverter
{
	// コードページ定義
	enum CodePageID : unsigned int
	{
		ANSI	= CP_ACP,	// ANSI
		OEM		= CP_OEMCP,	// OEM(依存)
		MAC		= CP_MACCP,	// MAC
		UTF7	= CP_UTF7,	// UTF-7
		UTF8	= CP_UTF8	// UTF-8
	};

	// ロケール設定
	static void SetGlobalLocale(const std::string localeName = "")
	{
		std::locale::global(std::locale(localeName));
		setlocale(LC_ALL, std::locale().c_str());
	}

	// string から wstring 変換
	static std::wstring StringToWString(const std::string& refSrc, unsigned int codePage = CodePageID::ANSI)
	{
		std::vector<wchar_t> buffer(MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, nullptr, 0));
		MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size());
		return std::wstring(buffer.begin(), buffer.end());
	}

	// wstring から string 変換
	static std::string WStringToString(const std::wstring& refSrc, unsigned int codePage = CodePageID::OEM)
	{
		std::vector<char> buffer(WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, nullptr, 0, nullptr, nullptr));
		WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size(), nullptr, nullptr);
		return std::string(buffer.begin(), buffer.end());
	}
}

main.cpp

// main.cpp
#include <iostream>
#include <string>
#include "StringConverter.hpp"

int main()
{
	using namespace std;

	// 適当に文字用意
	std::string msgA = "ABCアイウエオ@日本語";
	std::wstring msgW = L"ABCアイウエオ@日本語";

	// ロケール設定
	StringConverter::SetGlobalLocale();

	// 表示
	cout << StringConverter::WStringToString(msgW) << endl;
	wcout << StringConverter::StringToWString(msgA) << endl;

	// 入力待ち
	rewind(stdin), getchar();
	return 0;
}

あとがき

関数の引数が対応していなくてやむなく変換ってパターンもあるけど
本来は、関数側が対応するべきだと思う。
Win32APIは大体、
(関数名)Aと(関数名)Wを用意しているから問題は起こりにくいと思うけど、
外部ライブラリが何故かwchar_tしか対応してない状況があって変換方法を備忘録行き。

C++でC#風な基本型を使う

f:id:inemaru:20160619195641p:plain

C#風に基本型が使えたら楽だと思う

int.ToString()みたいにかけるC#にあこがれて
勢いでC++の基本型をラップしてみた感じ。

目的

基本型を、C#ライクに記述できるようにする。

注意

実用性は考慮していません。
完全に興味本位で書いたので、実装に穴があるかもしれません。
特に問題なのは、速度面でコンストラクタ呼び出しの分非常に低速
(POD形式にすれば速度面は対応可能だけど目的が達成不可)

確認用のプログラム

ExtensionPrimitiveTypeは長すぎるのでGitHubに保存  ExtensionPrimitiveType.hpp
※動作確認は、VC2013

#include <iostream>
#include "ExtensionPrimitiveType.hpp"

int main()
{
	using namespace std;
	using namespace IneFramework;

	// pt_(型名)でいろいろ定義
	// ExtensionPrimitiveType<(型名)>でtypedefしてある
	pt_int iValue = 100;

	cout << "int型として使用可能:\n\t";
	cout << iValue << "\n\n";	// cout << でint型のオーバーロードが呼ばれる

	cout << "string型に変換:\n\t";
	cout << iValue.ToStringA() << "\n\n";

	cout << "文字列から数値を取得:\n\t";
	cout << pt_int::Parse("123") << "\n\n";

	cout << "型の名前:\n\t";
	cout << iValue.GetTypeInfo().name() << "\n\n";
	cout << "型の有効範囲(上限):\n\t";
	cout << iValue.GetTypeLimitInfo().max() << "\n\n";

	cout << "ハッシュ値:\n\t";
	cout << iValue.GetHash() << "\n\n";

	cout << "型推論で期待する型で計算:\n\t";
	auto autoValue = iValue + 0.5;
	cout << autoValue << "\n\t";
	cout << autoValue.GetTypeInfo().name() << "\n\n";

	rewind(stdin), getchar();
	return 0;
}

あとがき

実装に関してだいぶ無知なので手探り状態だけど
品質向上のタイミングがあればもう少し綺麗に書き直したいと思う。
むしろ、こういった類のものが既出してないのか気になる・・・。

C++で各種関数を同一のコンテナに格納する

関数をコンテナに格納する

テーブルに関数(std::function)を登録して関数名の文字列で呼び出す
みたいなことをやったときにクラスのメソッドの登録で
微妙にハマり、備忘録行き

注意

引数の数が同一でないと格納できない方法です。
引数が違うメソッドの格納は、ここでは扱いません。

目的

以下のタイプの関数を同一のコンテナに格納してみる
 ・メンバ関数(クラスのメソッド
 ・ラムダ式
 ・グローバル関数

確認用のプログラム

※動作確認は、VC2013

#include <iostream>
#include <array>
#include <string>
#include <functional>
#include <unordered_map>

// 登録関数の型定義
typedef std::function<void()>	RegistFuncType;

// メンバ関数用定義
struct MemberFunction
{
	MemberFunction()
		:	// メンバ関数はstd::bindでstd::functionに代入する
			m_memberFunc(std::bind(&MemberFunction::Method_01, this)),
			// 引数があれば(std::placeholders::_n)で追加する
			m_memberArgFunc(std::bind(&MemberFunction::Method_02, this, std::placeholders::_1, std::placeholders::_2))
	{}

	~MemberFunction()
	{}

	void Method_01(){
		std::cout << "メンバ関数" << std::endl;
	}

	void Method_02(int value1, int value2){
		std::cout << value1 + value2 << std::endl;
	}

	RegistFuncType m_memberFunc;
	std::function<void(int,int)> m_memberArgFunc;
};

// ラムダ式
const auto LambdaFunc = [](){
	std::cout << "ラムダ式" << std::endl;
};

// グローバル関数
void GlobalFunc(){
	std::cout << "グローバル関数" << std::endl;
}

int main()
{
	using namespace std;

	// 関数テーブル
	unordered_map<string, RegistFuncType>	funcTable;

	// キー配列
	array<string, 3> keyArray{{
		"メンバ関数",
		"ラムダ式",
		"グローバル関数"
	}};

	// 登録確認関数
	auto FailTableRegist = [&funcTable](const string key) ->bool{
		if (funcTable[key] == nullptr){
			std::cerr << key << " : "<< "登録失敗" << std::endl;
			return true;
		}
		return false;
	};

	// クラスメンバの登録
	MemberFunction cf;
	funcTable[keyArray[0]] = cf.m_memberFunc;
	if (FailTableRegist(keyArray[0])){
		return -1;
	}
	// 引数ありのメンバの呼び出し
	//cf.m_memberArgFunc(1, 2);

	// ラムダ式の登録
	funcTable[keyArray[1]] = LambdaFunc;
	if (FailTableRegist(keyArray[1])){
		return -1;
	}

	// グローバル関数の登録
	funcTable[keyArray[2]] = GlobalFunc;
	if (FailTableRegist(keyArray[2])){
		return -1;
	}

	// 呼び出し
	for (const auto& key : keyArray){
		funcTable[key]();
	}

	rewind(stdin), getchar();
	return 0;
}

あとがき

関数をコンテナに格納して使用する状況自体が
だいぶ特殊だと思ったりするけど、
プラグインから関数を呼び出すときに使用したので
使うときは使う。(正攻法とは言っていない)

C++で双方向リストをクイックソート

独自実装のリストクラスをクイックソートする

訳あって、STL(std)のlistを使用しない状況に遭遇
ソートを実装する必要が出たときに、使用した実装を備忘録

注意

stdが使用できる環境では無用な産物のはず

目的

シーケンシャルアクセスなコンテナ
(双方向リストクラス)をクイックソートする

確認用のプログラム

※動作確認は、VC2013

#include <iostream>
#include <random>
#include <list>
#include <functional>

// STLのイテレータが使用できない特殊な状況を想定
// 今回は代用としてstd::listを継承して定義
// 双方向リストを想定
template < typename t_Type >
class MyList : public std::list<t_Type>{
public:
	MyList(){}
	~MyList(){}
public:
	void QuickSort(
		// 本来は、staticなメンバでデフォルトの比較関数を用意する
		std::function<bool(t_Type, t_Type)> _compFunc = [](t_Type l, t_Type r)->bool{
			return l < r;
	}){
		if (size() > 1){
			_QuickSort(begin(), end(), size(), _compFunc);
		}
	}
private:
	// クイックソートの実装
	static void _QuickSort(
		typename MyList<t_Type>::iterator& beginIt,
		typename MyList<t_Type>::iterator& endIt,
		unsigned distance,
		std::function<bool(t_Type, t_Type)>& _compFunc)
	{
		if (distance < 2){
			return;
		}

		auto pivotIt = beginIt;
		t_Type pivot = *pivotIt;
		auto curIt = beginIt;
		auto partitionIt = curIt;
		unsigned leftSize = 0;
		auto rightIt = endIt;

		--rightIt;
		std::swap(*pivotIt, *rightIt);
		while (curIt != rightIt){
			if (_compFunc(*curIt, pivot)){
				std::swap(*curIt, *partitionIt);
				++partitionIt;
				++leftSize;
			}
			++curIt;
		}
		std::swap(*partitionIt, *rightIt);
		rightIt = partitionIt;
		++rightIt;

		_QuickSort(beginIt, partitionIt, leftSize, _compFunc);
		_QuickSort(rightIt, endIt, (distance - leftSize) - 1, _compFunc);
	}
};

int main(){
	using ListType = int;
	std::random_device randFunc;
	MyList<ListType> intList;

	for (int count = 0; count < 10; ++count){
		intList.emplace_back(static_cast<ListType>(randFunc()));
	}

	std::cout << std::endl << "【昇順】" << std::endl;
	intList.QuickSort();
	for (auto it = intList.begin(); it != intList.end(); ++it){
		std::cout << (*it) << std::endl;
	}

	std::cout << std::endl << "【降順】" << std::endl;
	intList.QuickSort([](ListType l, ListType r)->bool{
		return l >= r;
	}); 
	for (auto it = intList.begin(); it != intList.end(); ++it){
		std::cout << (*it) << std::endl;
	}

	rewind(stdin), getchar();
	return 0;
}

あとがき

ランダムアクセス可能なコンテナに対するサンプルはよく見かけますが、
リスト構造のようなシーケンシャルアクセスな実装の場合の対応策です。
イテレータの比較演算が定義されていないと
1対1で書き換えられなかったりするので、
とりあえず双方向リスト構造での実装例を備忘録行き。

D3DX民がDirectXMathを入門する ~ep.01 使い方を探る~

f:id:inemaru:20160328005638p:plain

まえがき(注意書き)

手探りで調べた内容を覚書程度に書き留めてあるものなので、
過度の信用は身を滅ぼします。

以上を踏まえたうえで閲覧してくださいませ。

DirectXMathを使うための前知識!

”保持と計算で型が違う!”

D3DXで計算していた勢は、3D座標の例で言うならD3DXVECTOR3がすべてでしたが、
DirectXMathでは、同じ例で言うと
値の保持にXMFLOAT3計算にXMVECTORを使います。

SIMDを使った最適化と高速化を考慮するために、こうなったらしい

確認用のプログラム

とりあえず、四則演算する。

※動作確認は、VC2013で行いました

#include	<iostream>
#include	<Windows.h>			// XMVerifyCPUSupport()が正常に動作するために必要
#include	<DirectXMath.h>

int main()
{
	using namespace std;
	using namespace DirectX;

	if (XMVerifyCPUSupport()){
		// 保持用の型
		XMFLOAT3 f3_l = { 0.5f, 1.0f, 1.5f };
		XMFLOAT3 f3_r = { 2.0f, 1.5f, 3.0f };
		XMFLOAT3 f3_ans = { 0, 0, 0 };
		// 計算用の型
		XMVECTOR vec_l;
		XMVECTOR vec_r;
		XMVECTOR vec_ans;

		auto DispPos = [&f3_ans](){
			cout << "x:" << f3_ans.x << endl;
			cout << "y:" << f3_ans.y << endl;
			cout << "z:" << f3_ans.z << endl;
		};

		// 加算
		cout << endl << "【加算】" << endl;
		vec_l = XMLoadFloat3(&f3_l);
		vec_r = XMLoadFloat3(&f3_r);
		vec_ans = XMVectorAdd(vec_l, vec_r);
		XMStoreFloat3(&f3_ans, vec_ans);
		DispPos();

		// 減算
		cout << endl << "【減算】" << endl;
		vec_l = XMLoadFloat3(&f3_l);
		vec_r = XMLoadFloat3(&f3_r);
		vec_ans = XMVectorSubtract(vec_l, vec_r);
		XMStoreFloat3(&f3_ans, vec_ans);
		DispPos();

		// 乗算
		cout << endl << "【乗算】" << endl;
		vec_l = XMLoadFloat3(&f3_l);
		vec_r = XMLoadFloat3(&f3_r);
		vec_ans = XMVectorMultiply(vec_l, vec_r);
		XMStoreFloat3(&f3_ans, vec_ans);
		DispPos();

		// 除算
		cout << endl << "【除算】" << endl;
		vec_l = XMLoadFloat3(&f3_l);
		vec_r = XMLoadFloat3(&f3_r);
		vec_ans = XMVectorDivide(vec_l, vec_r);
		XMStoreFloat3(&f3_ans, vec_ans);
		DispPos();
	}
	else{
		cout << "DirectXMathが使えない環境です。" << endl;
	}

	rewind(stdin), getchar();	// キー入力で終了
	return 0;
}

あとがき

いろいろあってD3DX系の関数が消えて・・・

(割と昔に)ベクトル計算用のDirectXSDKに付属していた
D3DX系の関数などのヘルパー関数がいろいろあって使えなくなりました。

WindowsSDKに統合されたあとは、DirectXMathが推奨されていたが、
大手の参考書ですら、D3DXを復活させる解説ばかりして、
DirectXMathを解説していない。

入門レベルでDirectXMathを解説してくれるサイトはたくさんあったけど
自分がわかるように備忘録。

それにしても、Windows.hがインクルード必須なのにはハマッた・・・