雑な k|m の生態について

その 18 - 携帯電子レンジ

2002-10-25

コマンドプロンプトを表示させない

掲示板にて「DOS プロンプトを表示させずに perl や java を実行させるには?」というお題が出たので,それを実現させるコードを書いてみました。

#define STRICT
#include <windows.h>
#include <stdio.h>

int PASCAL WinMain( HINSTANCE, HINSTANCE, LPSTR cmd, int ) {

  PROCESS_INFORMATION pi;
  STARTUPINFO si;

  ::ZeroMemory(&si,sizeof(si));
  si.cb=sizeof(si);

  ::CreateProcess(
    NULL, (LPTSTR)cmd, NULL, NULL, FALSE, DETACHED_PROCESS,
        NULL, NULL, &si, &pi );

  return 0;
}

非常に簡単ですね。このコードをコンパイルし,出来た実行ファイルへのショートカットを作成します。そのショートカットのプロパティの「リンク先」を "C:\(フォルダ名)\program.exe" から例えば "C:\(フォルダ名)\program.exe perl test.pl" とすると,プロンプト画面が表示されないまま perl インタプリタが test.pl を実行します。

::CreateProcess() 第 5 引数を DERACHED_PROCESS にするのがポイント。しかしこの ::CreateProcess() の使い方は不正確かも知れません。というのも,滅多に ::CreateProcess() など使わないくせに,MSDN などで正確な情報をロクに調べもしていなかったからです。

というわけで,明日の雑記は ::CreateProcess() ネタで決まりです。笑い

疲労度:12

[ 某掲示板 ]
決してプログラミングと密接に関係のある掲示板ではありません。
[ DOS プロンプト ]
「コマンドプロンプト」「コマンドライン」「DOS コンソール」など,あまり呼び名が一定していません。どないやねん・・・

2002-10-24

2万円 + 10千円 = ?

ょっと詳細がアレなのですが,もしかしたら ATM のバグに遭遇したかもわからんね (;´Д`)

私が大分でお世話になっていた ( 否,正確には現在進行形です ) 銀行は大分銀行さん。ATM でお金を引き出す時は必ず「10千円」としていました。決して「1 万円」ではありません。こうすることで 1 万円札ではなく,10 枚の千円札を手に入れる事ができます。

さて現在お世話になっている某金融機関さんの ATM で,今日も私は元気にお金を引き出します。家賃を払わなければならないのです。額にして 2 万 6 千円。東京中心部に限りなく近い郊外にしては破格ですが,やはりそれなりの住まいですよ?

そろそろ寒いので新しいズボンでも買おうかと,合計 3 万円を引き出すことにしました。そのうちの 2 万円以上は家賃として一気に払ってしまうので 1 万円札で引き出し,あとの 1 万円は「10千円」で引き出そうと思います。

2 万円 + 10 千円 ...

・・・んー,どうも千円札が 9 枚しか出てこなかったかも知れません。残念ながらしばらく後で気づいたため & 私は財布の中身について無頓着なのでお札の枚数との関係がハッキリと分からなくなりました。しかし既存のお札とレシートとの位置関係から,私の財布に新規に追加された千円札は 9 枚であると推測されます。

考えられる原因としては,千円札を引き出せる上限が暗黙のうちに 9 枚にされているとか? 1 万円を引き出すのに「10 千円」を要求する人はいない,という前提で ATM が設計されたのかも知れません。

来月,再試してみようと思います。

疲労度:58959

[ 現在進行形 ]
coara さんの使用料は大分銀行さんから引き落とされています。・・現在のお付き合いはそれだけ・・。
[ 某金融機関 ]
都市銀行なのか信用金庫なのか,それとも郵便貯金なのかさえも特定していません。hehehe...
[ 再試してみよう ]
アホか私は (;´Д`) でもこういう事で喜ぶから「バッカー」とか呼ばれるんですよね。。。

2002-10-23

::MulDiv() うぜぇ

もそも私は画像処理屋さんを目指したりしていました。だから画像関係の作業となると燃えます・・・が,そういう仕事ってかなーり少ないんですよね。。。

そんなわけで最近めっきり画像関連のコーディングから遠ざかっていたのですが,久しぶりに「半透明処理」でもやってみる事にしました。で,今日の失敗はこんなコード。

  *pel = (MulDiv(*pel & 0x00ff00ff, level, 255 ) & 0x00ff00ff)|
         (MulDiv(*pel & 0xff00ff00, level, 255 ) & 0xff00ff00);

好奇心に惹かれてつい ::MulDiv() API を使ってしまいました。::MulDiv(a, b, c) は a * b / c を計算しますが,a * b が 32 bit を超えてしまってもそれなりにうまく計算してくれるとの事なのです。

上のコードは半透明な黒いフィルタをかけるための処理で,画像処理屋にとっては最も基本的なアルゴリズム。それぞれ上段では 0〜7 ビット目と 16〜23 ビット目,下段は 8〜15 ビット目と 24〜31 ビット目を処理し,お互いを or 演算子で結びつけています。R,G,B それぞれ 1 色素ずつ計算するより,かなり高速に処理を行ってくれるようです。

半透明処理 失敗例

お,いいかんじの模様が出てますが,惑わされないで下さい。失敗してるのです。プログラマの勘は "::MulDiv() が怪しい" と主張しています。というわけでこんなコードを組んでみました。::MulDiv() の演算結果を 2 進数で見てみるのです。

#define STRICT
#include <windows.h>
#include <stdio.h>

void putBin(unsigned int i) {
  if ( !i ) return;
  putBin( i >> 1 );
  printf("%d", i & 1);
}

int main() {
  unsigned int i = 0x80000000;
  putBin( MulDiv(i, 2, 2) );
  return 0;
}

実行結果はこう:

11111111111111111111111111111111

見事に 1 が 32 個も並んでくれやがりました。0x80000000 は 2 進数では最上位に 1 つビットが立っているだけであり,且つこのコードで ::MulDiv() には " 2 を掛けて 2 で割る" 事しか行いません。だから期待すべき実行結果は 10000...( 0 が 31 個 ) のはず。

うー,なんかこう ( いつもの通り ) うまく考えがまとまらないんだけど,こういう場面では ::MulDiv() を使うべきではないというのがファイナルアンサー。10 進数に慣れている人間にとっては簡潔に表現できる数も,2 進数を扱うコンピュータにとってはまれに大変な思いをする事もあるようなのです。そういう意味ではコンピュータは決して万能な計算機ではあり得ないわけで,なかなか複雑な様相を呈してますよ?

疲労度:9854

[ ::MulDiv() ]
そもそも MulDiv が扱う型は符号付き int 型。だから「是非 unsigned で演算して行きたい行けば行くとき行くぜ!!!!1ぬふあ」と強く願っても,決して期待通りの値が手に入ると考えちゃダメですよね・・・。

2002-10-22

[PostgreSQL] なんで?(;´Д`)

わったかに見えた過去の仕事ですが,未だにダラダラと続いています2002-10-09 でも愚痴ってますが,私がノリ気でない原因は,個々としては小さな ── しかしアルゴリズムやデータ構造に大きく関係する要求を小出しにしてくる「似非インクリメンタル開発」。こうなってしまうとプロジェクトの行き先は大混乱,コーディング時によい発想が浮かびません。ソフトウェア開発って難しいなあ・・・。

さて本日つまづいたのは PostgreSQL の Perl 用モジュール "Pg.pm" での事。さらに Pg.pm を扱いやすくするためのちょっとしたパッケージを他の人が書いており,今回はそれを使う事になったのです。パッケージこそ他の人が作ったモノですが,なかなかどうして巧く Pg.pm がラップされたファイルに仕上がっています。

そのパッケージは DB への接続に Pg::connectdb() メソッドを使用していました。これに渡すべき文字列はパッケージのユーザが自分で生成するようです。

Pg::connectdb('dbnme=insDB user=km password=km');

結果はこう:

FATAL 1: Relation 'insdb' does not exist

なんでやねんっ・・・とツッコミを入れる前にエラーメッセージをよく見ると,DB 名 'insDB' の大文字部分が小文字化されています。ここでマニュアルを見ることにしました。

The database-name will be converted to lower-case, unless it is surrounded by double quotes.

なんでやねんっ・・・どうして ( ダブルクオートで囲まれていなければ ) わざわざ小文字にしちゃうかなあ。きっと歴史的に重要な意味のある仕様だと思うんですが,もはや私のような newbie には知る由もないのかな。。。とりあえずこの場合の正解例は次の通り:

Pg::connectdb('dbnme="insDB" user=km password=km');

こういうのは面倒なんで,なんとかパッケージを作った人を説得して Ps::setdbLogin() を使わせてもらえないかなと考えていたりします。

疲労度:125

2002-10-21

ゲームを作る前に / クライアント領域のサイズ

ームを作る際に必ず気にしなければならないのが,スクリーンサイズですかね。フルスクリーンじゃなくてウィンドウのままでゲームをするならば,ウィンドウの中で私たちプログラマがグラフィックを自由に描画できる「クライアント領域」のサイズが気になります。私たちプログラマはクライアント領域はウィンドウサイズより「若干小さい」事しか分かっていないも同然で,プレイヤの環境の設定によってウィンドウサイズとクライアント領域のサイズの差には違いがあります。例えば,クライアント領域に 640 x 480 の大きさが欲しい場合,::CreateWindowEx() にはどのような値を渡せばよいのでしょう?

まず一つ目のよい解決方法は次の通り。::AdjustWindowRectEx() を使います。

DWORD exstyle = 0;
DWORD style = WS_OVERLAPPEDWINDOW;
RECT  rc = { 100, 100, 740, 580 };

::AdjustWindowRectEx( &rc, style, FALSE, exstyle );

HWND form = ::CreateWindowEx (
        exstyle,
        "WinClass",
        "TestWindow",
        style,
        rc.left,
        rc.top,
        rc.right - rc.left,
        rc.bottom - rc.top,
        HWND_DESKTOP,
        0,
        ::GetModuleHandle(0),
        0 );

rc.rightrc.bottom の初期値に注目。本来ならばここはクライアント領域の右下隅座標を入力します。左上隅の座標を ( 100, 100 ) とした場合,右下隅の座標は (100 + 640 - 1, 100 + 480 - 1) つまり ( 739, 579 ) となるべきですが,上の例では ( 740, 580 ) などと指定しています。しかし大丈夫。この 1 ドットの辻褄は ::CreateWindowEx() に渡す width と height を計算している部分で吸収しています。left と right から横幅を計算するには本来は ( right - left + 1 ) という式が必要ですが,最後の + 1 を省略する事で初期値の 1 ドットの誤差は無かった事に出来るのです。ていうか,このあたりは基本っすね。。。

私の場合はこんなコードを書いたりもしています。すでにウィンドウを生成している場合,これも悪くない解だと思います。

RECT rcWin, rcCli;
::GetWindowRect( window_handle, &rcWin );
::GetClientRect( window_handle, &rcCli );

int width  = 640 + (rcWin.right  - rcWin.left) - (rcCli.right  - rcCli.left);
int height = 480 + (rcWin.bottom - rcWin.top ) - (rcCli.bottom - rcCli.top );

::SetWindowPos(
          window_handle, NULL,
          0, 0,
          width, height,
          SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);

::AdjsutWindowRectEx() API では,ウィンドウが持つメニューが 2 行以上になってしまった場合に正確な計算ができないんですよね。上のコードならばその問題も解決できると思うんだけど,どうでしょ? 実は私はまだメニューの扱い方についてほとんど勉強していないのですが,それはここだけの秘密です。笑い

疲労度:9

2002-10-20

CreateFiber() 見つけたっ

昨日の雑記はやはり大嘘でした。ヘッダファイルで宣言されていないのは CreateFiber() ではなく _WIN32_WINNT だったようです。これは VC++ の仕様で,ユーザが自分で #define を行う必要があります。というのも,例えば CreateFiber() は Win95 では使えないため,何も知らずにうっかり使ってコンパイルが通ってしまうのを防ぐためですね。その時の名残というわけです ( 既に Win95 のサポートは終了しています ) 。

という事で無事に ::CreateFiber() を使ったコードのコンパイルに成功しました。コードは次の通り:

#ifndef STRICT
#  define STRICT
#endif // !STRICT
#define _WIN32_WINNT 0x400
#include <windows.h>
#include <stdio.h>

LPVOID gFibers[4];

VOID WINAPI fiberMethod (LPVOID param) {
  int fiber_number = (int)param;

  printf("サブファイバ %d 実行してます\n", fiber_number);

  ::SwitchToFiber( gFibers[fiber_number+1] );

  printf("またサブファイバ %d 実行してます\n", fiber_number);

  ::SwitchToFiber( gFibers[fiber_number+1] );
}


int main() {
  int i;
  for (i=0; i<3; i++)
    gFibers[i] = ::CreateFiber(0, fiberMethod, (LPVOID)i);
  gFibers[3] = ::ConvertThreadToFiber(0);

  if (! gFibers[3]) {
    perror("だめっす.");
    return -1;
  }

  puts("メインスレッド(メインのファイバ)実行中です");
  puts("最初のサブファイバを起動します");
  ::SwitchToFiber(gFibers[0]);
  puts("メインスレッド(メインのファイバ)に戻りました");

  puts("また最初のサブファイバを起動");
  ::SwitchToFiber(gFibers[0]);
  puts("戻ってきました");

  return 0;
}

目論見通り,2 度目にサブファイバを呼び出した時,そのサブファイバは途中から実行されます。これはなかなか楽しいのでぜひ大量に使ってゆきたいと思いました。さて,もう少し詳しい API の解説を見てみたわけですが・・・

By default, every fiber has 1 megabyte of reserved stack space.

もしかしたら大量に使っちゃだめですか? 上記コードをデバッグモードでコンパイルするとおよそ 80KBytes の実行ファイルが出来ますが,スタックサイズがそれよりも激しく巨大なのには仰天。ノイマン博士,プログラムってこんなもんなんですか?

疲労度:59

[ CreateFiber() ]
->MSDN 解説

2002-10-19

【残り】SDK アップデート中【6 時間】

Windows では "fiber" とかいうモノがあるらしいのです。スレッドよりもさらに小さな実行単位で,スケジューリングは OS ではなくユーザが行うとか。あちこちのサンプルなどを見ている限り,こんな事も出来るのかな ( 実際にまだ試していないので,こんな事が出来るのかは知りません ) :

なるほど,これは面白そうだと言う事で早速 CreateFiber()ConvertThreadToFiber() API を使おうと思ったのですが,どうやらヘッダファイルには宣言されていないようでした。おそらく SDK が古いからですね。無論ライブラリファイルも古いはずなので独自に宣言したって無駄。kernel32.dll から直接引っ張って来るにしても名前マングリングの仕様を ( 調べればいいんだろうけど ) 知らないし,ヘタな小細工はやめてついに SDK をアップデートする事にしました。

Platform SDK Update

さて,適当に見繕ってダウンロードを開始。

ダウンロード所要時間 7 時間!!!111ぬふあ

・・・MS はもう少しハンパでもいいと思います・・・。

残り時間:6 時間 22分

[ Platform SDK Update ]
・・・とリンクしてみたものの,どうも MS サイトのリンクは信用できないというか何というか,すぐこの URL は無効になると思います。効率的なサイトの運営のために最適な構成を模索している最中なのかな? ( それにしても URL が無効になるサイクルが速いような印象が )
[ ハンパでもいい ]
時代に逆行する爆弾発言をサラリと放った気がしました。快感。

追記

大嘘こいてます。ヘッダファイルに宣言されていないわけではなくて・・・詳細は 2002-10-20 参照ね。

2002-10-18

[ネガティブ・コーディング] 1 - 関数オーバーロードの悪用

関数オーバーロードとは,関数名を同じくして引数の型や数を異ならせる事。関数に入力する型によって関数名を変える必要がなくなるのでコードの保守性が向上するという話でしたが,本当にこんな形で有効利用されている例を見かける事はなかなかありません。

int func (int a, int b) {
  return a - b;
}

double func (double a, double b) {
  return a * b;
}

その代わりによく見かけるのは,クラスの operator =() をオーバーロードしている例。コードの簡潔さが増しているのがよく分かります。

さて「ネガティブ・コーディング」シリーズでは,例えば会社の待遇があまりにもムカつくので明日にでも辞めてやろうと思った時に,置き土産としてコードの中に紛れ込ませるとよいと思われる極悪なコードを紹介してゆきます。で,本当にこんなコードを書くのはやめてください。そのコードを保守するのは ( 多分 ) あんまし罪のない,残された人たちなんです(涙)

int func (int n, const char *str) {
  return printf( "%s:%d\n", str, n );
}

int func (const char *str, int n) {
  return printf( "%s:%d\n", str, n );
}

これ,ちゃんとコンパイルが通ります。しかし実際には引数の順序が違うだけで,中身は全く同じ処理。このようなコードをいくつかのファイルにこっそりと点在させる事で,プロジェクトは間違いなく破綻にっ!

悪用だめゼッタイ!

ネガティブ度:3824

2002-10-17

そろそろ PowerBuilder かよっ・・・

わー,どうしよう。。。"PowerBuilder" の案件が来る予感らしいのですが

Function ulong GetDC( ulong hWnd ) Library "user32"

ウィンドウハンドルからデバイスコンテキストを得る GDI 関数 ::GetDC() を使用するには,このような宣言が必要です。ulong は 4 バイトの符号なし整数といった感じですか。ちなみに long は 4 バイトの符号つき整数。どうやら上のコードで long を使っても構わないようです。でもまあ,HDCHWND は C/C++ ではポインタ型であり,どちらかと言うと PowerBuilder ではこれを ulong 型で宣言する方がしっくりです。

また,PowerBuilder の integer 型は C/C++ では short に当たるとか。すなわち 2 バイトの整数ですか。それからそれから PowerBuilder はケース・インセンシティブ,つまり大文字と小文字を区別しないようです。そーれから,コールバック関数はどうやって書くんだろう? 不可能という情報をどこかで入手した気もしますが,そろそろ進展があってもいいような気もします。

以上,VisualBasic の紹介でした・・・違うっ・・・

VB との酷似度:8454903545

2002-10-16

そろそろ COBOL・・・!?

Windows API32 COBOL Programming

うわぁ・・・

昨日は C# という最新の言語ネタだったんですが,今日は "COBOL" という,うん,そーゆー言語ネタです。この格差はもはや快感です

このサイトではサンプルコードがダウンロードできるのでなかなか親切。私は高校生だった頃にほんのわずかに COBOL を扱った事がありますが,Win32 on COBOL はもうあの頃の"大文字だらけ"のコードとは異なります。さあ,ぜひダウンロードして見て下さい。うまく書けばきっと VisualBasic よりもオサレなコードが書けるに違いないのです。

ところで私が常に気にしているのは,C/C++ 以外の言語では「コールバック関数」をどうするのかという事。つまり C/C++ ならば関数のエントリポイントをポインタ変数に ( 事実上の制約はあるけども ) 自由に代入できるため,コーディング時にはその場でどんな関数が実行されるかを特定せず,リンク時までにその可能性を限定し,実行時に動作を決定する事ができます。

JAVA では「リスナ」という考え方を導入していますね。悪くない解決法だと思うのですが,私はあまりこの辺で遊んだ事がないので詳しく言及できません。しかしまれに「関数ポインタが使えないための苦肉の策だ」という意見も聞く気が。

COBOL では entry という予約語を用いて関数のエントリポイントのようなものを定義し,set という命令 ( move でなく ) を用いてポインタ変数に代入しています。これ,まんま C/C++ の関数ポインタのような雰囲気じゃありませんか。

おそるべし,COBOL の執念・・・

疲労度:55

[ リスナ ]
listener。デザインパターンの言葉じゃないですよね。Strategy パターンとか,TemplateMethod パターンあたりがオーバーラップしてると思うんスけど,Do-U-Yo ?

2002-10-15

そろそろ C#・・・

C#言語,ISO標準獲得へ

常に最新技術を追いかけてゆくのはプログラマーに限らず技術者・・に限らず,多分みんなに課せられた本質的な仕事だと思うんですよ。でも「最新技術」の中にはしばしば追いかけるべきでない最新技術もあったりするわけで,それをどう見分けるかが「センス」とやらを必要とするトコロ。結局「マルチメディア」て何だったんでしょうね。あと「CAPTAIN システム」,NTT コミュニケーションズは 2002 年 3 月 31 日をもってサービスを終了しました。

で,C# はどうなんでしょうか。/.J のみならず至る所でも言われているように「C++ で細かい部品を書いておいて,それら部品を C# 上で組み合わせる」という開発を行うのが最適解かな。つまり「勉強しておいて損はない」程度?

どうせ 2〜3 年もしないうちに ── C# の力を全て使いこなせないうちに「より優れた」言語が出てきて,気づいてみたらデファクトスタンダードに・・というシナリオを予言してみるテスト。

疲労度:0

[ CAPTAIN システム ]
ただし,なおも後続のちょっとしたサービスを専用線で行っているとかいう話も・・。Google などでは「キャプテンサービス」という単語で検索をかけると情報を見つけやすいようです。参考:日本最初のホームページ on ビデオテックス
[ 至る所で ]
わりと Microsoft の公式見解っぽい気もしますね。

2002-10-14

ウィンドウをアクティブにする関数

うやく「アプリケーション起動時にフォームが非アクティブになってしまう」という問題が解決しました。フォームの中には私製コントロールを ::CreateWindowEx() で生成しています。そのウィンドウプロシージャはこんなの。

// メインフォームのプロシージャ
LRESULT CALLBACK
mainFormMethods(
        HWND ctrl,
        unsigned int msg,
        WPARAM wp,
        LPARAM lp )
{
  // メインフォームはアプリケーションを終了させる権利を持ちます
  if ( msg == WM_DESTROY ) {
    ::PostQuitMessage(0);
    return 0;
  }

  if ( msg == WM_SIZE ) {
    // すいません,ここ,仮想コードです
    // 全ての子コントロールに WM_X_FormResized を送ります
    foreach ( child in children ) {
      child->sendMessage( WM_X_FormResized, 0, 0 );
    }
  }

  return ::DefWindowProc( ctrl, msg, wp, lp );
}

// 私製コントロールのプロシージャ
LRESULT CALLBACK
myControlMethods(
        HWND ctrl,
        unsigned int msg,
        WPARAM wp,
        LPARAM lp )
{
  // フォームのサイズが変われば・・・
  if ( msg == WM_X_FormResized ) {

    int left, top, width, height;

    // フォームのサイズに合わせてコントロールのサイズを
    // 計算して・・・
    calcControlSize( &left, &top, &width, &height );

    // コントロールのサイズを変更します
    ::SetWindowPos( ctrl, NULL,
                left,  top,
                width, height,
                SWP_NOZORDER );
  }

  return ::DefWindowProc( ctrl, msg, wp, lp );
}

フォームに WM_SIZE が送られると,子コントロールに拡張メッセージ WM_X_FormResized を送ります。そして子コントロールはフォームのサイズを参考にして自分の居るべき位置,あるべきサイズを再計算し,::SetWindosPos() でサイズを反映します。・・・以上のようなマネジメントが最適解なのかどうかが分かってない ( ≒ 自信がない ) んだけど,具合は良いようです。

まず重要だったのは,WM_SIZE::ShowWindow() の実行後にも送られるという事でした。生成したメインフォームを実際に表示するには ::ShowWindow() を用いますが,その時に WM_SIZE が自分自身に送られます。ウィンドウプロシージャ内で WM_SIZE を捕らえたメインフォームは子コントロールたちに WM_X_FormResized を送り,子コントロールたちは ::SetWindowPos() を実行。その後で晴れてウィンドウアプリケーションは開始されます。

そして最も重要だったのは ::SetWindowPos() の使い方。最終引数の SWP_NOZORDER は Z オーダーを変更しないためのフラグです。これはこれで必要ですが,さらに SWP_NOACTIVATE フラグも必要でした。これがなくては子コントロールが自分自身をアクティブ化し,相対的にメインフォームは非アクティブ化してしまうのです

デバッガで 1 ステップずつ実行して行く限り,メインフォームが WM_SIZE を捕らえて子コントロールに送り・・・て部分まではステップ実行がされなかったんですよね。今思えば,ブレークポイントをちょっと工夫すれば簡単に見つかるモノだったわけですが。。。

教訓:GUI ライブラリのデバッグはやりにくいっ

[ ウィンドウプロシージャ ]
WNDCLASSEX がいわゆる「クラス」なんだから,その挙動を定義する「ウィンドウメソッド」と呼びたいっ。呼んでゆきたいっ。是非っ。

ところで

世間ではあの有名な「3 連休」とかいうやしだったんですか? 私はぜんぜん気づかずにずーっと会社へ通っていましたよ?

Written by kuri|minima(tkuri@fat.coara.or.jp) - all rights reserved.(warai
このリソースの位置情報は http://www.coara.or.jp/%7etkuri/D/018.htm で安定しています。coara さん ああ coara さん coara さん