タグ別アーカイブ: Windows SDK

gdiplus save

Keyleigh をそろそろなんとかしたいと考えた。
拾ったデルヒャァコンポーネントを使っているだけのクソソフトだが。
せっかくなので画像コンバータ機能とかでも追加すれば面白くなるかも。

GDI+ を使うのが一番だよなぁ。
C# で使えば死ぬほど簡単だ。

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace iconpoli2
{
	class Program
	{
		static void Main(string[] args)
		{
			foreach (var s in args)
			{
				string t = Path.ChangeExtension(s, "jpg");
				Bitmap b = new Bitmap(s);
				b.Save(t, ImageFormat.Jpeg);
			}
		}
	}
}

コンバートならマジでこれだけだ。
いや、せっかくなので C++ でやりたいぞ。
と思ったけど

Gdiplus::Bitmap * img = new Gdiplus::Bitmap(__wargv[i]);
img->Save(outname, &Gdiplus::ImageFormatJPEG);

とやっても全然上手くいかない。

Transforming a JPEG Image Without Loss of Information (Windows)
Retrieving the Class Identifier for an Encoder (Windows)

どうやら自力で CLSID を取ってくるしかないようで。
つか ImageCodecInfo って class なのにサイズを得て malloc なの?
試しに普通な new で作ったら見事にヒープ領域不足、どういう設計だよ。

とにかく MSDN の方法で上記と同じになるよう C++ で作ってみる。
C++ じゃ ChangeExtension なんて便利なものは無いし GDI+ 初期化も必須。

stdafx.h

#pragma once

// 面倒なのでココで参照追加
#pragma comment(lib, "gdiplus.lib")

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600 // Vista
#endif

#include <stdio.h>
//#include <tchar.h>
#include <wchar.h>
#include <windows.h>
#include <gdiplus.h>

凄く無駄だと解っているけど tchar を利用しないで UNICODE 関数をまんま使う。
てか今となっては Ansi 関数を併用する必要は無いと思うんだが。
WCHAR や UINT のマクロも利用したくない。
だって Visual Studio は大文字マクロの型名では色が付かないんだよね。
というかなるべく小文字にしたいと思うのは多分 Python のやりすぎ。

iconpoli.cpp

#include "stdafx.h"

using namespace Gdiplus;

int GetEncoderClsid(const wchar_t* format, CLSID* pClsid)
{
	unsigned int  num = 0;
	unsigned int  size = 0;

	ImageCodecInfo * pImageCodecInfo = NULL;

	GetImageEncodersSize(&num, &size);
	if(size == 0)
		return -1;  // Failure

	// コレだとヒープ領域不足になる
	//ImageCodecInfo * pImageCodecInfo = new ImageCodecInfo();
	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if(pImageCodecInfo == NULL)
		return -1;  // Failure

	GetImageEncoders(num, size, pImageCodecInfo);

	for(unsigned int j = 0; j < num; ++j)
	{
		if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;  // Success
		}    
	}

	free(pImageCodecInfo);
	return -1;  // Failure
}

bool ChangeExtension(const wchar_t* src, wchar_t* dst, const wchar_t* ext)
{
	wchar_t path[4][256];
	_wsplitpath_s(src, path[0], 256, path[1], 256, path[2], 256, path[3], 256);
	_wmakepath_s(dst, 1024, path[0], path[1], path[2], ext);
	return TRUE;
}

int wmain(int argc, wchar_t* argv[])
{
	// GDI+ 初期化
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

	for (int i=1; i < __argc; i++)
	{
		// makepath
		wchar_t outname[1024];
		ChangeExtension(__wargv[i], outname, L".jpg");

		Gdiplus::Bitmap * img = new Gdiplus::Bitmap(__wargv[i]);

		CLSID encoderClsid;
		int r = GetEncoderClsid(L"image/jpeg", &encoderClsid);
		if (r == -1)
		{
			wprintf(L"no CLSID");
			return 0;
		}
		// コレは上手くいかない
		//int result = img->Save(outname, &Gdiplus::ImageFormatJPEG);
		int result = img->Save(outname, &encoderClsid);
		if (result == Gdiplus::Ok)
			wprintf(L"Encode Success");
		else
			wprintf(L"Encode Failure");
		delete img;
	}
	// 後片付け
	Gdiplus::GdiplusShutdown(gdiplusToken);
	return 0;
}

長い。。。。。

たったこれだけのモンにえらく時間が掛かった、やっぱり C# のほうがいいかなと。
それと Python や C# ばかりやっていると開放処理をつい忘れそうになるよね。

msi

Vista 状態の今はとりあえず minipoli を msi インストーラ化しようと考えた。
作るのは SeeMe で一度作ったので簡単だったけど「送る」がまったく反応しない…
Palepoli でリンク先を調べるとなんだこりゃ?

pale

「リンク先」がグレーアウトしているのはそういうことか。
そりゃ実体へのリンクじゃないならコマンドライン引数なんか無意味だわ。
なのにプロパティには引数の指定項目が…この msi を作った開発者ってバカなの?
検索すると、あぁ皆さんご苦労様。

アドバタイズショートカットではなく、普通のショートカットを作成する: .NET Tips: C#, VB.NET, Visual Studio

どっちをやっても私の環境では下記エラーダイアログが出るだけなんですけど…
と思ったけど Orca で開いていたのを閉じるだけだった、皆さん気をつけて。

err

どっちにしろメンドイ。
Visual Studio のプロパティだけでなんとかできないか模索しているが駄目っぽい。
Windows での開発ってマジでもう嫌!Linux ならシェルスクリプトだけでなんとかなるのに。

てか、コレをどうにかするより別のインストーラを探したほうがよさそうな雰囲気。
SeeMe は引数やドラッグ&ドロップが関係無いから別にイイと思うけど変更したいな。

OPENFILENAME は古い 2

SHCreateItemFromParsingName についてだが3番目の引数に

IShellItem *psi = NULL;
SHCreateItemFromParsingName(szDoc, NULL, IID_IShellItem, (LPVOID *)&psi);

なんてありがちな IID を付けたらすんなり通ってしまった。
しかしどこを探しても引数が3つなのは何故?
まあこれで IFileDialog 関連はなんとかなりそうだ。

自前関数とかそのままだけーがこんな感じで。

/*
 * (*.mpg|*.mpeg) を (*.mpg;*.mpeg) に変換
 * セミコロンって INI の仕様ではコメントなのよね
 */
BOOL SemicolonReplace(LPWSTR _Dst, LPWSTR _Src)
{
	for (int i=0; i<256; i++)
	{
		if (_Src[i] == '\0') {
			_Dst[i] = '\0';
			return TRUE;
		} else if (_Src[i] == '|') {
			_Dst[i] = ';';
		} else {
			_Dst[i] = _Src[i];
		}
	}
	return FALSE;
}

void CCinemaWindow::DoOpenDlg()
{
	IFileDialog *pDlg = NULL;
	IShellItem *psi = NULL;
	HRESULT hr;

	hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileDialog, (LPVOID *)&pDlg);
	if (FAILED(hr)) {
		DoMessage(hWnd, L"Errer IFileDialog Create");
		pDlg->Release();
		return;
	}
	//拡張子フィルタの登録
	wchar_t filter[3][256];
	SemicolonReplace(filter[0], IniSetting.szExt01);
	SemicolonReplace(filter[1], IniSetting.szExt02);
	SemicolonReplace(filter[2], IniSetting.szExt03);
	COMDLG_FILTERSPEC filterspec[] =
	{
		{IniSetting.szExtA, filter[0]},
		{IniSetting.szExtB, filter[1]},
		{IniSetting.szExtC, filter[2]}
	};
	hr = pDlg->SetFileTypes(3, filterspec);
	if (FAILED(hr)) {
		DoMessage(hWnd, L"Errer SetFileTypes1");
		pDlg->Release();
		return;
	}
	//フィルタの選択インデックス変更(先頭は 1 である)
	hr = pDlg->SetFileTypeIndex((UINT)IniSetting.nExtNo + 1);
	if (FAILED(hr)) {
		DoMessage(hWnd, L"Errer SetFileTypeIndex");
		pDlg->Release();
		return;
	}
	//初期選択フォルダ
	if (fname::FileExists(PlayFileInfo.FullPath))
	{
		hr = SHCreateItemFromParsingName(PlayFileInfo.FilePath, NULL, IID_IShellItem, (LPVOID *)&psi); //IID_PPV_ARGS(&si)
	} else if (fname::DirectryExists(IniSetting.szSFolder)) {
		hr = SHCreateItemFromParsingName(IniSetting.szSFolder, NULL, IID_IShellItem, (LPVOID *)&psi);
	} else {
		wchar_t szDoc[256];
		SysUtils::get_MyDocumentString(szDoc);
		hr = SHCreateItemFromParsingName(szDoc, NULL, IID_IShellItem, (LPVOID *)&psi);
	}
	if (FAILED(hr)) {
		DoMessage(hWnd, L"Errer CreateItemFromParsingName");
		psi->Release();
		pDlg->Release();
		return;
	}
	hr = pDlg->SetFolder(psi);
	if (FAILED(hr)) {
		DoMessage(hWnd, L"Errer SetFolder");
		psi->Release();
		pDlg->Release();
		return;
	}
	psi->Release();
	psi = NULL;
	//ダイアログの表示
	hr = pDlg->Show(hWnd);
	if (SUCCEEDED(hr))	// FAILED ではキャンセルを拾ってしまう
	{
		wchar_t *pFileName = NULL;
		hr = pDlg->GetResult(&psi);
		if (FAILED(hr)) {
			DoMessage(hWnd, L"Errer GetResult");
			psi->Release();
			pDlg->Release();
			return;
		}
		hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pFileName);
		if (FAILED(hr)) {
			DoMessage(hWnd, L"Errer GetFileName");
			psi->Release();
			pDlg->Release();
			CoTaskMemFree(pFileName);
			return;
		}
		UINT nIndex;
		hr = pDlg->GetFileTypeIndex(&nIndex);
		if (FAILED(hr)) {
			DoMessage(hWnd, L"Errer GetFileTypeIndex");
			psi->Release();
			pDlg->Release();
			CoTaskMemFree(pFileName);
			return;
		}
		hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pFileName);
		if (FAILED(hr)) {
			DoMessage(hWnd, L"Errer GetDisplayName");
			psi->Release();
			pDlg->Release();
			CoTaskMemFree(pFileName);
			return;
		}
		//完走したので処理
		int nExtNo = (int)nIndex - 1;
		if (IniSetting.nExtNo != nExtNo)
		{
			SetPlayFileInfo(L"c:\\a\\a"); //拡張子が無いのでクリアになる
			SetListupMenu(nExtNo, TRUE);  //本体のセレクトを変更
		}
		DirectA->SetURL(hWnd, pFileName);
		//end
		CoTaskMemFree(pFileName);
		psi->Release();
	}
	pDlg->Release();
}

長いよぉ!だから COM は…

を選択したら本体のリストアップ選択も変更したかっただけなのに。

さて、問題は XP 版をどうするかだ…又 #ifdef で分けるしかないか。
こっちに都合がイイように仕様変更しまくったのだどうしよう…

OPENFILENAME は古い

C# ばかりやっていたら頭が腐りそうなので今日は慣れた C++ を。

今回は Cinema にリストアップ拡張子選択機能を付けて更新しようと考えた。
ついでに開くダイアログを改造していたのであるが…

OPENFILENAME 構造体に lpfnHook を指定したら古いダイアログになっちまった。
なんでじゃい!

CFileDialog::m_ofn

なんてこった、GetOpenFileName 関数はこんなに変わっていたのか。
このサポートされなくなったメンバを使わなければコンパイル時に新しいのに置き換えられる。
ということみたい。

ExpLZH の保存ダイアログが古いのとかが気になっていたのだがそういうことだったか。
でも秀丸とかはカスタマイズしているのに Vista スタイルになっているよな?
何か方法があるのだろう、調べてみよう。

で。

どうやら Vista 型式のダイアログは COM の IFileDialog を使うようである。
何故 COM なんだ?と文句を書いてもしゃーないので作るしかない。

MSDN じゃ全然解らない!サンプルにも見あたらない!
日本語で探しても解りにくいので「ウエブ全体」でググる。

CodeProject: Vista Goodies in C++: Using the New Vista File Dialogs. Free source code and programming help

流石に老舗のココは解りやすい。
CComPtr なので ATL での解説だけどなんとかなるだろうと書いてみる。

何故だろう?
SHCreateItemFromParsingName の引数がどこを探しても3つなんですけど…
私の Visual Studio では4つあるんですけど、デフォルト引数でもないし…

あぁ更に解らない!ということで更新は又今度。

ウインド表示直後に処理

久々に SDK チップスネタでも書こう。

WM_SHOW を捕まえて処理するとウインド表示に時間が掛かる場合がある。
このイベント処理が終わった後でないと実際の描写が行われないからだ。

だから表示された直後に作成や描写を行えば見た目の起動速度が向上する。
しかしそんな都合のよいメッセージは飛んでこない。

が、実はデルヒャァにはある。

procedure CMShowingChanged(var Message:TMessage);message CM_SHOWINGCHANGED;

をフォームの private に定義してハンドラを書くだけ…
但し表示状態が変わる毎に飛んでくるので一番最初に捕まえた時以外は弾くこと。
Y901 はそのツマラン方法で見た目の起動時間を短縮するセコイことをやっていた。

はて、CM_SHOWINGCHANGED なんて無い VC++ ではどうすんだ?
で考えた、PostMessage を使ってまえ!

SendMessage は処理が終わるまで制御を戻してくれない。
PostMessage はキューにメッセージを放り込んで即制御を戻す。
だから WM_SHOW 処理の一番最後で適当なメッセージを投げればよい。

#define WM_SHOWEND (WM_APP + 3)

とか定義しておいて WM_SHOW 処理の一番最後に

PostMessage(hWnd, WM_SHOWEND, 0L, 0L);

とすればキューに自分宛の WM_SHOWEND がセットされる。
キューって何?な人は Windows をもっと勉強してください。
後は WndProc で普通に

case WM_SHOWEND:

を処理、これで見た目の起動時間が早いアプリのできあがり!
詳しくは Cinema のソースコードで、オープンソースは楽だ。

フォーム表示直後に実行されるメソッド OnShowed を作る – Sakura scope

他にも同じコトを考える人がいるのね。
ビルダァ(名古屋弁化できん…)には CM_SHOWINGCHANGED は無いのかな?
でも WM_USER を使うのは止めておいたほうがいいみたいよ

WM_APPの薦め。

それより Windows SuperFetch がある Vista ではあまり意味が無い…