外部関数を使ってかゆい背中を掻く

外部関数

こんにちは! エイタです。

PowerBuilder を使っていて「こんな関数が他の言語にはあるんだけどな」とか、「関数の動作が目的とちょっと違うな」とか思うことはありませんか? そういったモヤモヤを外部関数が解決してくれるかもしれません。

外部関数を利用することで、他の言語でつくられた PowerBuilder には実装されていない機能やサードパーティが提供する便利なライブラリを利用することができますよ。


外部関数の宣言

外部関数はアプリケーション全体で利用できる「グローバル外部関数」と、そのオブジェクト内で利用できる「ローカル外部関数」の 2 種類の宣言記述箇所があります。 スクリプトエディタで [インスタンス変数の宣言] を選択し、上部のドロップダウンで “Global External Function” (グローバル外部関数) または “Local External Function” (ローカル外部関数) を選択してください。

外部関数を宣言するには、次の構文を使います。

外部関数宣言の構文

{access} FUNCTION 戻り値のデータ型 関数名( {{REF} データ型 引数1, ..., {REF} データ型 引数n}) LIBRARY "ライブラリ名" ALIAS FOR "ライブラリで定義されている関数名{;ansi}"

また、次の構文を使って外部サブルーチンを宣言できます。 外部サブルーチンは外部関数と同じですが値を戻しません。

外部サブルーチン宣言の構文

{access} SUBROUTINE 関数名( {{REF} データ型 引数1, ..., {REF} データ型 引数n}) LIBRARY "ライブラリ名" ALIAS FOR "ライブラリで定義されている関数名{;ansi}"

なお、PowerBuilder で外部関数として利用できる API は、呼び出し規約が “__stdcall” 形式として定義されている必要があります。 なんでもかんでも呼び出せるというわけではないんですね。 “__stdcall” 形式については各言語のマニュアルを参照してください。

また、言語によってデータ型の精度が異なるものもあります。 C/C++ の int 型は 32 ビット符号付き整数ですが、PowerBuilder の int (integer) 型は 16 ビットの符号付き整数です。 このためライブラリで int の引数が定義されている関数は PowerBuilder では long 型で宣言しなければなりません。 それぞれの型についての対応表はマニュアルに記載されています。


Windows API の利用

さて、実際にどのような API が利用できるんでしょうか。 今回は PowerBuilder の組み込み関数ではできない、ちょっと便利な API を紹介します。

 
Sleep

Sleep は処理を止める関数です。 同名かつ同じような機能を持つ関数が PB にもありますが、PB の Sleep 関数は「秒単位」で処理を止めるため、一瞬だけ処理を止めたいという場合には残念ながら実用的ではありません。 Windows API の Sleep 関数は停止時間を「ミリ秒」で指定することができます。

関数の宣言

Subroutine Sleep(ulong dwMilliseconds) LIBRARY "kernel32.dll"

利用するライブラリは “kernel32.dll” です。 Sleep 関数は上記の通りミリ秒単位で処理を止めることができます。 例えばループ処理を駆使してオブジェクトを点滅させることもできます。

integer i
// ブレーキランプを 5 回点滅させる
FOR i = 1 TO 10
    // 表示/非表示を切り替え
    p_brakelight.visible = NOT p_brakelight.visible
    // 処理を 200ms 止める
    Sleep(200)
NEXT

Sleep 関数を使わないと処理が一瞬で終わり、点滅していることがまったくわかりません。 また PB の Sleep 関数を使った場合では 5 回点滅させるのに最短でも 10 秒かかりますので、ずいぶん間延びしたサインになってしまいますね。 サンプルでは 200 ミリ秒ですが、思いを伝えるのに最適な間隔はそれぞれで調整してください。

そうそう、宣言した外部関数が PowerBuilder の組み込み関数と同じ場合は外部関数が優先して呼び出されます。 PowerBuilder の組み込み関数も同時に利用したい場合は関数名が同じにならないように宣言しましょう。

 
ShellExecute

ShellExecute は指定したプログラムを実行するという関数ですが、同様のものは PowerBuilder にも Run 関数があります。 しかし ShellExecute ではプログラムの実行だけでなく、指定したファイルを既定のプログラムで実行することもできるんです。

関数の宣言

FUNCTION ulong ShellExecute(ulong hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, ulong nShowCmd) LIBRARY "shell32.dll" ALIAS FOR "ShellExecuteW"

ファイルだけでなく URL を指定することもできます。 アプリケーションで Web ページを開きたいけど、実行環境にどんな Web ブラウザがインストールされているかがわからないという場合もありますよね。 そんな時には ShellExecute で実行環境の「既定のブラウザ」を使って URL を開くことができます。

string ls_url, ls_null

ls_url = "https://japan.appeon.com/"
SetNull(ls_null)

// 既定のブラウザで開く
ShellExecute( 0, "open", ls_url, ls_null, ls_null, 0 )

このテクニックは Google Sheets APIを利用する記事でも使われていますね。

ちなみに ShellExecute は引数 “lpOperation” を変更することで、「ファイルをエディタで開く」、「フォルダーをエクスプローラで開く」などの操作も可能です。

 
GetUserName

GetUserName は Windows の現在のログインユーザー名を取得することができます。 引数はユーザー名を受け取る参照渡しの文字列と、そのバッファサイズです。

関数の宣言

FUNCTION ulong GetUserName(ref string lpBuffer, ref ulong nSize) LIBRARY "advapi32.dll" ALIAS FOR "GetUserNameW"

参照渡しの文字列型引数を取る関数は取得する値を格納するメモリ領域を予め確保しておく必要があります。 そのために、必要となる文字数分、何らかの文字を格納した状態で関数に渡します。 ここでは半角スペースを格納しています。

string ls_username
ulong ll_size = 256   // バッファサイズ

ls_username = Space(ll_size + 1)

// ログインユーザー名を取得
GetUserName(ls_username, ll_size)

OS のログインユーザー名を使ってアプリケーションにログインさせる場合などに使えますね。

 
GetSystemPowerStatus

Windows API をはじめとした外部の API には引数に構造体をとるものも多くあります。 GetSystemPowerStatus もそのひとつ。 この関数はノート PC のバッテリー情報を取得することができます。

関数の宣言

FUNCTION ulong GetSystemPowerStatus(ref SYSTEM_POWER_STATUS lpSystemPowerStatus) LIBRARY "kernel32.dll"

参照渡しの構造体、SYSTEM_POWER_STATUS は PowerBuilder で同じ構成の構造体を作成することで利用できます。 下記を参考に構造体を作ってみましょう。

SYSTEM_POWER_STATUS 構造体

名前備考
ByteACLineStatusAC電源のステータス
ByteBatteryFlag充電ステータス
ByteBatteryLifePercentバッテリー残量 (%)
LongSystemStatusFlagバッテリーセーバーのステータス
LongBatteryLifeTime残りのバッテリー寿命 (秒)
LongBatteryFullLifeTimeフル充電時のバッテリー寿命 (秒)

作成した構造体を宣言して関数を呼び出します。

SYSTEM_POWER_STATUS lsr_powerstat
string ls_msg

// バッテリー情報を取得
GetSystemPowerStatus(lsr_powerstat)

CHOOSE CASE lsr_powerstat.ACLineStatus
    CASE 0
        ls_msg = "残量: " + String(lsr_powerstat.BatteryLifePercent) + " %"
    CASE 1
        ls_msg = "AC 接続中"
END CHOOSE

MessageBox("バッテリー状態", ls_msg)

実用的かどうかはさておき、構造体の使い方の参考として紹介しました。


外部関数利用の注意点

ANSI 版、Unicode 版

Windows API には同じ動作をする関数でも文字を ANSI コードで扱うものと Unicode で扱うものとの 2 種類存在するものがあります。たとえば GetWindowTextA と GetWindowTextW などです。 GetWindowTextA は「ANSI 版」で文字列のやり取りに ANSI コードを使うのですが、PowerBuilder 10 以降は処理が Unicode 化されているためそのまま呼び出すと正しい結果が返ってきません。

そこで外部関数の定義で ALIAS FOR "xxxxA;ansi" と指定することにより ANSI コードで文字データのやり取りが行われ、この関数を利用することができます。 xxxxW の場合は Unicode 版ですので “ansi” を付与する必要はありません。  

32-bit と 64-bit

PowerBuilder 12.6 以降、64-bit のアプリケーションを配布できるようになりましたが、32-bit/64-bit のプロセスの違いによってポインタのサイズが異なります。 ポインタは 32-bit では PowerBuilder の long 型で代用できていましたが、64-bit になるとサイズが足りません。

ここで登場したのが longptr 型です。 longptr 型は 32-bit で配布すると 4 byte、64-bit では 8 byte で動作しますのでこの問題が解決できます。 32-bit、64-bit の両方でアプリケーションを配布する予定がある場合には、ポインタの引数には longptr 型を使っておいたほうがいいですね。


まとめ

外部関数は PowerBuilder では手の届かないところに手が届く「孫の手」のような存在です。 うまく使えれば外部プログラムとの連携や細かい要望の実装など思いのままです。 Windows API は MSDN にリファレンスがあるので、どんな関数があるか眺めるだけでも面白いかも。

ちなみに、孫の手という名称の由来って「おじいちゃんが孫に背中を掻いてもらうような・・・」ということではないんですって。

以上、エイタでした!

テクニカルブログ 一覧を見る
PowerBuilder マイグレーション
PowerBuilder学習、動画で始めちゃう?