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

趣味プログラマーのメモ

【WSH】JScript で、多重起動判定

はじめに

WSHで多重起動判定する方法です。
できるだけ、スマートに実装したつもりです。

実装

サンプルは、shebang記法でバッチファイルにJScriptに埋め込みしていますが
普通に.jsでも動くはず。
Sleepで適当な時間待って、終了前に起動したものは多重起動判定されます。

判定方法は、WMIを使用して、実行ファイル名で検索をかけることで実現しています。
そのため、同名のバッチファイルが起動されていると誤検出し得るので、
ファイル名は、被らないようにしましょう。

あとがき

そこまで凝ったモノは要らない人向けです。
簡単な物であれば、5行程度で判定できるというネタでした。


【C++】コマンドライン引数をstd::vectorに展開する方法

ちょっとしたメモ

使わなくても良いテクニックですが、どこかで使いそうなのでメモっときます。
用途があるとしたら、
vectorコンテナとしてコマンドラインをやり取りしたいときとかでしょうか?

実装

あとがき

エントリーポイントを int main(std::vector<std::string> args)
みたいに書けたら楽ですが、そういう横着はできません。

元ネタは、下記を参照。
stackoverflow.com

【Windows】バッチファイルで管理者権限を判定する方法 3種

バッチファイルで管理者権限を判定する方法 3種

Windowsのバッチファイルで管理者権限が必要な場合に
標準のコマンドで確かめる方法を調査したときの断片です。
以下、巷で使われている手法の3種です。

whoami コマンド

いろいろな方法がありますが、自分が普段使用している
whoami コマンドを使用する方法です。
whoami コマンドのprivオプションは、
ユーザーやグループに割り当てられた権限を表示します。
権限に「SeLoadDriverPrivilege」が含まれるかどうかで、管理者権限を判定します。

openfiles コマンド

openfiles コマンドは、管理者権限が必要なコマンドです。

> nul > 2>&1 で、標準出力を出さないようにしています。

net コマンド

session や file オプションは、管理者権限が必要なため判定が行えます。

あとがき

ちなみに、管理者権限に昇格させる方法は、
Powershell の -Verb runas が手っ取り早いです。

いつの間にか、Markdownが使えるようになっていたので、Markdownで書いてみたり。 やっぱりMarkdownは、書きやすい。

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

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;
}

あとがき

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