偏った言語信者の垂れ流し

2013-06-16

[]DelphiDLLとdylibを動的にロードする

DelphiからDLLを動的にロードする場合、WindowsではLoadLibraryとGetProcAddressを使っていましたが、MacOSXではどうすればいいのか調べてました。

MacOSX(Posix)だとdlopen, dlsymを使うことになるらしいです。

dlopen(3) Mac OS X Developer Tools Manual Page

dlsym(3) Mac OS X Developer Tools Manual Page

System.SysUtilsのコードを見てみたら、どうやら最近のDelphiのバージョンでは、MacOSXでもLoadLibraryとGetProcAddressからこれらを使うようラップしているようでした。

IFDEFがたくさん書かれていて読みづらいですが、SafeLoadLibraryという関数がSysUtilsにあり、これはWindows, MacOSX両方で使用可能のようです。

System.SysUtils.SafeLoadLibrary - RAD Studio API Documentation

ただし、GetProcAddressについてはXE4の段階ではWindowsだとSysUtilsのほうには定義がなく、WinApi.Windows経由を使うことになりそうです。

また、dylibを作成する際MacOSXの場合は、exportsはユニット側に書き、アンダースコアを先頭に付けた名前でエクスポートしないと認識されないそうです。

クロスプラットフォームの共有ライブラリ - RAD Studio

この情報を基に、WindowsではDLLを、MacOSXではdylibを動的にロードするようなコードを書いてみます。

試したバージョンはDelphi XE4です。

DLL/dylib側

mylib.dpr

LIBPREFIXに空文字列を指定しているのは、dccosxだとデフォルトが"lib"になっていたためです。

library mylib;

uses
  MyLibrary;

{$LIBPREFIX ''}

begin
end.
MyLibrary.pas

足し算の結果を返すAdd関数と、メッセージボックスでテキストを表示するSay手続きをエクスポートしています。

呼び出し規約は他の言語からも使いやすいように、とりえあえずcdeclを指定しています。

Say関数では引数文字列はUTF8エンコードされたバイト列のポインタを前提にしています。

unit MyLibrary;

interface

uses
  FMX.Dialogs;

function Add(X, Y: Integer): Integer; cdecl;
procedure Say(Text: PAnsiChar); cdecl;

exports
  {$IFDEF MACOS}
  Add name '_Add',
  Say name '_Say'
  {$ELSE}
  Add,
  Say
  {$ENDIF}
  ;

implementation

function Add(X, Y: Integer): Integer; cdecl;
begin
  Result := X + Y;
end;

procedure Say(Text: PAnsiChar); cdecl;
begin
  ShowMessage(UTF8ToString(Text));
end;

end.

動的にライブラリをロードするコード

dynamic_loading.dpr

コンソールアプリケーションで、SafeLoadLibraryを使ってライブラリをロードし、Add関数、Say手続きを呼び出しています。

MacOSの場合はmylib.dylib、Windowsの場合はmylib.dllを使います。

ファイルの文字コードはUTF8です(日本語を含むので)。

program dynamic_loading;

{$APPTYPE CONSOLE}

uses
  System.SysUtils
{$IFDEF MSWINDOWS}
  , WinApi.Windows
{$ENDIF}
  ;

const
{$IFDEF MacOS}
LIB_NAME = 'mylib.dylib';
{$ELSE}
LIB_NAME = 'mylib.dll';
{$ENDIF}

type
  TAdd = function(X, Y: Integer): Integer; cdecl;
  TSay = procedure(Text: PAnsiChar); cdecl;

var
  Handle: THandle;
  Add: TAdd;
  Say: TSay;

begin
  Handle := SafeLoadLibrary(LIB_NAME);
  try
    if Handle <> 0 then
    begin
      @Add := GetProcAddress(Handle, 'Add');
      if @Add <> nil then
        Writeln(Add(10, 20));
      @Say := GetProcAddress(Handle, 'Say');
      if @Say <> nil then
        Say('こんにちは');
    end;
  finally
    FreeLibrary(Handle);
  end;
end.

実行結果

Windows7(32bit)

f:id:nullpobug:20130616013856p:image

MacOSX 10.6

f:id:nullpobug:20130616013857p:image

ソースコード

ソースコードコンパイル済みのバイナリは以下のURLからダウンロードできます。

test_dynamic_loading.zip(3.9MB)

2013-05-23

[]Delphiレコードヘルパを試す

レコードヘルパはInteger型やString型などにメソッドを追加する機能。

試したのはDelphiXE4。2010以降の機能なのかな。

test_my_record_helper.dpr

program test_my_record_helper;

{$APPTYPE CONSOLE}

type
  TMyIntegerHelper = record helper for Integer
    function Add(X: Integer): Integer;
  end;

function TMyIntegerHelper.Add(X: Integer): Integer;
begin
  Result := Self + X;
end;

var
  Value: Integer;

begin
  Value := 10;
  Writeln(Value.Add(20));
end.

実行結果

>dcc32 test_my_record_helper.dpr
>test_my_record_helper.exe
30

参考

2013-05-15

[]Delphi XE4での変更点を試す

Delphi XE4が先日発売されましたね。私はiOSアドオンなしでXE3からアップグレードしました。

XE3を購入したときのポイントが余ってたおかげで、SEshopで1,200円程度で購入できました。

iOS対応を除くと、機能追加はほとんどないのですが、リリースノートに載っているインデックスが0から始まるの文字列と、追加されたTIntegerHelperを試してみました。

Delphi XE4 および C++Builder XE4 の新機能 - RAD Studio

test_zerobased_str.dpr

ZEROBASEDSTRINGSにONを指定するとString型のインデックスは0から始まります。

program test_zerobased_str;

{$APPTYPE CONSOLE}
{$ZEROBASEDSTRINGS ON}

var
  Foo: String;
  I: Integer;

begin
  Foo := 'ABCDE';
  for I := 0 to Length(Foo) - 1 do
    Writeln(Foo[I]);
end.
実行結果
>dcc32 test_zerobased_str.dpr
>test_zerobased_str.exe
A
B
C
D
E

ちなみに、OFFの場合はこうなります。

>test_zerobased_str.exe

A
B
C
D

test_integer_helper.dpr

program test_integer_helper;

{$APPTYPE CONSOLE}

(* System.SysUtilsをusesに追加することでヘルパークラスが有効になる *)
uses
  System.SysUtils;

var
  IntValue, IntValue2: Integer;
  StrValue: String;

begin
  IntValue := 12345;
  (* IntegerからStringへ変換 *)
  StrValue := IntValue.ToString;
  Writeln(StrValue);
  (* StringからIntegerへ変換 *)
  IntValue2 := Integer.Parse('54321');
  Writeln(IntValue2);
end.
実行結果
>dcc32 test_integer_helper.dpr
>test_integer_helper.exe
12345
54321

参考

2013-05-11

[]nginxのリバースプロキシキャッシュする

メモ。nginxのリバースプロキシでファイルをキャッシュする方法。

設定したページはこれ。バックエンドはGoogleAppEngine。

http://www.nullpobug.com/

試したnginxのバージョンは0.7。Ubuntu10.04のなので古い。

nginxの設定

nginx.confの中でconf.dやsites-enabledがincludeされるようになってることが前提。

/etc/nginx/conf.d/proxy_cache.conf
proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=my-key:16m max_size=100m inactive=120m;
proxy_temp_path /var/cache/nginx/tmp;

httpディレクティブにproxy_cache_pathを設定する。levelsは、キャッシュファイルをサブディレクトリで保存する設定。

この例の場合、my-keyというキーの領域で共有メモリは16MB使用、キャッシュ全体のサイズは100MB、120分間アクセスが無ければキャッシュは削除される。

proxy_temp_pathで一時ディレクトリを設定する。対象のディレクトリはあらかじめ作成し、nginxのworkerプロセスアクセスできるようにしておく。

/etc/nginx/sites-available/my-site
server {
        listen   80;
        server_name  www.example.com;
        proxy_set_header X-Real-IP $remote_addr;

        access_log  /var/log/nginx/my-site.access.log;
        error_log  /var/log/nginx/my-site.error.log;

        location / {
                proxy_pass http://127.0.0.1:8000;
                proxy_ignore_headers Cache-Control;
                proxy_cache my-key;
                proxy_cache_valid 200 302 60m;
                proxy_cache_valid 404 10m;
        }
}

サイトごとの設定では、proxy_cacheでkeys_zoneに指定した名前、proxy_cache_validでキャッシュ対象と有効な時間を指定する。

この例では、レスポンスのステータス番号が200と302の場合は60分、404の場合は10分としている。

リバースプロキシ先のレスポンスにCache-Controlヘッダがついていて、"no-cache"とされているとキャッシュされないので、その場合はproxy_ignore_headersで無効化する。

nginxの設定をリロードする

$ sudo /etc/init.d/nginx reload

参考

2013-05-10

[]bashのexecを試す

bashスクリプトでexecを使うと、シェルが実行したコマンドに置き換えられる。

Pythonスクリプトシェルスクリプトから実行することが結構あって、プロセスの監視云々とか考えるとexec使ったらいいんじゃないとかどっかで聞いた気がする。

execを使わない場合

以下のようなPythonスクリプトbashスクリプト経由で実行してみる。

main.py
# coding: utf-8
import time


def main():
    while True:
        time.sleep(1)


if __name__ == '__main__':
    main()
run
#!/bin/bash
python main.py
実行結果
$ ./run
^Z
[1]+  Stopped                 ./run
$ ps aux
(中略)
tokibito 30303  0.0  0.1  12312  1160 pts/1    T    23:42   0:00 /bin/bash ./run
tokibito 30304  0.5  0.5  32172  5304 pts/1    T    23:42   0:00 python main.py

execを使う場合

run
#!/bin/bash
exec python main.py
実行結果
$ ./run
^Z
[1]+  Stopped                 ./run
$ ps aux
(中略)
tokibito 30348  0.1  0.5  32172  5308 pts/1    T    23:47   0:00 python main.py

execを使わない場合は2プロセスになってしまうけど、execを使うと1プロセスで済む。

参考

Man page of BASH