2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Win32 API をMindから使う(ワイド文字のケースも含め)

Posted at

はじめに

 Mindから Win32 API を呼び出す方法を一つのサンプルを例としてお見せします。
 通常のDLL内関数呼び出しのほか、ワイド文字版の関数呼び出しも行ってみます。後者では引数または戻値がUnicodeになるのでその為の注意点も書きます。

通常のDLL内関数呼び出し

 Win32 API と言うものの、実態はDLL(kernel32.dll)に含まれる関数の呼び出しなので、この記事は Win32 API に限らず、一般的なDLL呼び出しの方法としてお読みいただくこともできます。

 サンプルとして使ったのは GetModuleFileName という関数の呼び出しです。プログラム自身のプログラム名(プロセス名)を得ることができます。
 プログラム自身の名前なぞプログラマが分かっているはずなのに、こんな機能の何が嬉しいかと言うと、ほとんど同じソースプログラムなのにexeファイルをあえて別名にして起動するケースや、ほんのわずかソースコードを変更した複数のプログラムにおいて、起動直後にUsageを表示するなどの個所で、それらのソースコードを1つ1つ変更する手間が省けます。(開発途中で気が変わってプログラム名を変えてもソース変更が不要という利点もあります)

   記:実は GetModuleFileName の機能はMindの標準ライブラリで
     既に使っており、「私のプログラム名」という処理単語と
     して提供されています。

 ソースコードは次のようになります。

GetModuleFileName.src
自身のモジュール名は 文字列実体 長さ ファイル名長さ。   ※SJIS

自身のモジュール名を得るとは (・ → SJISモジュール名)
                結果は   変数
	自身のモジュール名を クリアしておく

	0をつみ
	 自身のモジュール名の アドレスを得て
	  自身のモジュール名の 長さをつみ、
	   "kernel32"と "GetModuleFileNameA"で
            API呼出し3し          ※APIの引数が3個なので
		 結果に 入れ           ※「API呼出し3」を使う
        エラー?
                ならば 終り
			※API呼び出しそのものが失敗した
			※エラー文字列は登録済み
                つぎに

        結果が 0より 大きい
                ならば 結果を
			自身のモジュール名に 文字列実体カウントを設定し
			自身のモジュール名を 返す
                さもなければ
			「取得失敗」を エラー登録し
                        空列を 返し
                つぎに。

メインとは
		モジュール名は 文字列

	自身のモジュール名を得て モジュール名に 入れ
	エラー?
		ならば 「エラー:」を 表示し エラー文字列を 一行表示し
		さもなければ
			「モジュール名=」を 表示し モジュール名を 一行表示し
		つぎに

 上記を説明します。
 まず「API呼出し3」という語を呼んでいます。MindからDLL内関数を呼ぶものです。
 MindではC言語などと違って関数名を直接にソースコード内に書いて呼び出すことはできず、DLL名と関数名を文字列として渡すことで間接的に実行する方法をとります。引数の数によって、

    API呼出し0
    API呼出し1
    API呼出し2
    ・・・・・・・

のように呼び出し用の処理単語を使い分けます。

    "kernel32"と "GetModuleFileNameA"で

の部分は、DLL名と関数名をスタックに積むものです。
 "kernel32.dll" と書いても良いのですが .dll は省略できます。
 関数名 "GetModuleFileNameA" ですが、シフトJISで文字列データをやりとりする通常関数の場合は末尾に"A"の付いたものが正式名となります。(末尾"W"のものもありますが後述します)

 このAPI関数は2番目の引数で得られた文字列データを受け取るためのバッファ領域を指し示す仕様になっています。Mind側は「自身のモジュール名」という文字列実体変数をそのための領域として用意しており、その変数アドレスを受け取るべき領域アドレスとしてスタックに積みます。
 ただこのとき注意するのは、Mindでの文字列実体変数はその先頭部にヘッダがあるため、ヘッダの先にある本来の文字列収容域のアドレスを指し示す必要がある点です。それは難しいものではなく、

    自身のモジュール名の アドレスを得て

で文字列収容域のアドレスを積むことができます。文字列実体変数をその少し前にクリアしているため、単に変数参照をおこなうと空の文字列が得られきますが、そのアドレス部こそが文字列収容域のアドレスとなります。「アドレスを得る」はMindの文字列データのうちアドレス部を得る機能です。

 さて、「API呼出し3」は戻ってきたとき、スタックに他の言語で言うところの関数の戻値を積んできます。上記例ではそれを「結果に 入れ」と書くことて局所変数に格納しています。
 このあとエラー検査をおこなっていますが、ここでのエラーは、DLL内関数を呼び出すこと自体に失敗したというケースになります。

 エラーでない場合は後続し、APIの戻値である「結果」を見ています。GetModuleFileNameA の仕様として、成功であれば取得した文字列の文字数値が、失敗であればゼロが戻値となる・・となっているので、その通りに分岐をおこなっています。
 成功したときのパスですが、「結果」は得られた文字列の文字数なので、その値を文字列実体変数のカウント部に強制設定しています。
 APIが返して来る文字列というものはC言語などで扱う末尾がNULL終端された文字列の先頭アドレスでしかありません。一方、Mindが言う文字列とは、アドレスとカウントのパック値であるためAPIが設定してくれた文字列データだけではまだ不足であり、カウントを設定する必要があります。
 文字列実体変数の先頭にはヘッダがあると言いましたが、そのヘッダのうち、カウント部を強制設定するのが「文字列実体カウントを設定」というやや高度な用途の処理単語です。これにより、この文字列実体変数は正規の形式を持つことになります。

 このプログラムの実行結果が以下のようなものです。わざと "にほんご" というフォルダ内に潜ってから実行しています。日本語を含むパスが正しく得られるか確認するためです。

にほんご>..\obj\GetModuleFileName
モジュール名=C:\Mind\pmindwin\test-m\にほんご\mrunt010.exe

 表示されたモジュール名が GetModuleFileName.exe ではなく mrunt010.exe なのはMindのアプリケーションの起動手順特有のものです。冒頭で少し触れましたがMind標準装備の「私のプログラム名」という機能ではこの部分を補正して正しいプログラム名を返すようになています。この違いは本稿の主題ではないため気にされないでください。

ワイド文字をやり取りするDLL内関数呼び出し

   記:ここから先はUnicodeライブラリを使います。
     Mindが提供するUnicodeライブラリについては別記事を
     参照願います。
     MindでUnicodeの文字列を扱う方法(2024.01.14改訂)
     https://qiita.com/killy/items/7a103e01561bf6a053d7

 Wind32 API の関数群の中にはUnicode形式で文字列をやりとりするものがあります。関数名の末尾に"W"が付くことが多いです。ここではMindからそのような関数を使う方法を解説します。
ここでは、先に紹介した GetModuleFileNameA のワイド文字版である GetModuleFileNameW を呼び出す例をお見せします。
 ソースコードは次のようになります。

GetModuleFileNameWideChar.src
"../srclib/uniencode.src"を コンパイル。
"../srclib/unidecode.src"を コンパイル。

	ローカル。

Unicodeテーブルをロード処理とは (・ → ・)
	Unicodeエンコードテーブルを読み出し 偽?
		ならば (ErrorMessageで) 重大エラー
		つぎに
	Unicodeデコードテーブルを読み出し 偽?
		ならば (ErrorMessageで) 重大エラー
		つぎに。

	グローバル。

自身のモジュール名は 文字列実体 長さ ファイル名長さ。   ※SJIS

自身のモジュール名を得るとは (・ → SJISモジュール名)
		Unicodeモジュール名は 文字列実体 長さ ファイル名長さ ※Unicode
                結果は   変数
	※------------------------------※
	※             解説               ※
	※                              ※
	※ APIで得たモジュール名は局所変数「Unicodeモジュール名」に ※
	※ 一旦格納し、それをシフトSJIに変換したものを        ※
	※ 「自身のモジュール名」という大域変数に格納しています。  ※
	※                              ※
	※ 「UCS2→SJIS変換」は変換先の文字列実体変数を明記 ※
	※ することができ、それを利用しています。          ※
	※                              ※
	※------------------------------※
	Unicodeモジュール名を クリアしておく    ※←これを忘れないこと
						  ※(局所変数は初期値不定)
	0をつみ
	 Unicodeモジュール名の アドレスを得て
	  Unicodeモジュール名の 長さをつみ、
	   "kernel32"と "GetModuleFileNameW"で
            API呼出し3し          ※APIの引数が3個なので
		 結果に 入れ           ※「API呼出し3」を使う
        エラー?
                ならば 終り
			※API呼び出しそのものが失敗した
			※エラー文字列は登録済み
                つぎに

        結果が 0より 大きい
                ならば 結果に 2を 掛け  ※←Wide文字数を2倍しByte単位に
			Unicodeモジュール名に 文字列実体カウントを設定し
			Unicodeモジュール名を、
			 自身のモジュール名をつかい UCS2→SJIS変換し
			  (→SJISのモジュール名)
                さもなければ
			「取得失敗」を エラー登録し
                        空列を 返し
                つぎに。

メインとは
		モジュール名は 文字列

	Unicodeテーブルをロード処理し

	自身のモジュール名を得て モジュール名に 入れ
	エラー?
		ならば 「エラー:」を 表示し エラー文字列を 一行表示し
		さもなければ
			「モジュール名=」を 表示し モジュール名を 一行表示し
		つぎに
	。

 上のソースコードを説明します。
 冒頭でUnicodeライブラリ(副ソース提供)のコンパイルをおこなっています。
 このライブラリを使うには前もってテーブルをロードしておく必要があり、それをおこなっているのが「Unicodeテーブルをロード処理」です。「メインとは」の冒頭でこの処理単語が呼び出されます。

 「自身のモジュール名を得るとは」の定義に入ります。
 APIから戻された文字列を格納するための文字列実体変数は今度は局所定義にしています。ここに格納したあと引き続きシフトJISへの変換でさらに別の文字列実体変数に格納することから一時的な利用と言うことで局所変数としています。

 注目するのはAPIからの戻値である「結果」の処理です。通常文字版のほうでは「結果」をそのまま「文字列実体カウントを設定」としていましたが、今度は「2を 掛け」をしてからカウント設定をおこなっています。この理由は、APIから返された「文字数」が16bit固定長の文字次元での文字数だからです。Mindで扱うカウントは常にバイト数であるため、バイト数に換算するため2倍しています。

    Unicodeモジュール名を、
     自身のモジュール名をつかい UCS2→SJIS変換し

の部分ですが、ここでAPIから返されたUnicode形式の文字列をシフトJISに変換しています。
 「○○をつかい」という表現は Mind 7 から導入された特殊送り仮名で、処理結果となる文字列の格納先変数を明示するものです。

 このプログラムの実行例は以下のようになります。

にほんご>..\obj\GetModuleFileNameWideChar
モジュール名=C:\Mind\pmindwin\test-m\にほんご\mrunt010.exe

 さて、このプログラム例ではUnicodeの文字列はAPI関数の戻値だけでしたが、APIによっては引数の側にUnicodeを指定するものもあります。そのときの注意を書いておきます。
 たとえばワイド文字を使う関数タイプで第1引数が文字列である場合、当然ながらUnicode化してから引数として積む必要があります。処理単語としては「SJIS→UCS2変換」を使います。

 複数の引数でUnicode化の必要がある場合、「SJIS→UCS2変換」は変換先の文字列実体を明記する方法で呼び出すよう注意してください。
 もし変換先の文字列実体変数を省略して単に、

    ○○を SJIS→UCS2変換し   ※第一引数
     △△を SJIS→UCS2変換し  ※第二引数

のように記述してしまうと、二度目の変換で最初の変換先変数を破壊してしまいます。正しくは、

        バッファ1は 文字列実体 長さ ・・
        バッファ2は 文字列実体 長さ ・・

    ○○を バッファ1をつかい SJIS→UCS2変換し
     △△を バッファ2をつかい SJIS→UCS2変換し

となります。

 その引数がたとえば単純なASCII文字の場合はどうでしょうか。ASCII文字だと分かっているのだからSJIS→UCS2変換をかける必要は無い・・と思ってしまいがちですが、そのような場合であっても変換をおこなってください。8bitストリームから16bitストリームに変換してあげないといけないからです。

 定数としての文字列を引数として与える場合も要注意です。たとえばファイル名として、

    "name1.txt"をつみ

のような記述でファイル名を積むことができそうに思いますが、これは二重の意味で誤りです。
 一つには、C型文字列とするためのNULL終端が無いことです。この文字列定数を一旦、文字列実体変数に入れてから「0を ○○に 一文字追加」でNULLを追加することもできますが、もっと簡単な方法は、

    "name1.txt&00&"をつみ

と書くことです。これでNULL終端されます(&xx& は文字の十六進表記です)。しかしこれでも間違いです。これは純粋にASCII文字であり、Unicode化されていません。

    "(uL+)name1.txt&00&"をつみ

上記が最終的に正しい表記になります。Mindコンパイラの特殊な機能ですが、文字列定数の先頭部として

    "(uL+)"
    "(uB+)"

という表現があり、これによって後続する文字列定数は16bit固定長のUnicodeとして生成されます(ただしASCII文字のみ)。Windows APIが扱うのは UTF-16 かつ Littleエンディアンであるため前者の (uL+) を文字列定数先頭に書いてください(LはLittleの略です)。

以上

2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?