C言語のプログラムにLuaインタプリタを組み込む

ここではWindows/VC++環境で、C言語のプログラムにLuaインタプリタを組み込んでみる。

Luaライブラリのダウンロード

  • Lua Binariesより、Windows用のライブラリをダウンロードする。
  • 32ビット版ならlua-x.x.x_Win32_dllw4_lib.zip
  • 64ビット版ならlua-x.x.x_Win64_dllw4_lib.zip

VC++のプロジェクトにファイルを配置

  • VC++でプロジェクトを作成する。
  • 上記zipファイルを解凍した中身をプロジェクトディレクトリに配置する。

プロジェクトのプロパティの設定

  • C/C++>全般>追加のインクルードディレクトリに $(ProjectDir)\include を追加
  • リンカー>全般>追加のライブラリディレクトリに $(ProjectDir) を追加
  • リンカー>入力>追加の依存ファイルに liblua53.a を追加

Luaスクリプト文字列を実行

#include <stdio.h>
#include "lua.hpp"

int main(void)
{
    // Luaの初期化
    lua_State* L = luaL_newstate();
    // Luaの標準ライブラリを読み込み
    luaL_openlibs(L);
    // Luaスクリプト文字列を実行
    luaL_dostring(L, "print('hello, world!')");
    // Luaを終了
    lua_close(L);
    return 0;
}

Luaスクリプトファイルを実行

下記Luaスクリプトファイルを作成してプロジェクトディレクトリに配置する。

-- 人口のテーブル ※配列も構造体もなくすべてテーブル
cities = {
    {city = "Osaka",    pop = 2725006 },
    {city = "Tokyo",    pop = 9396595 },
    {city = "Nagoya",   pop = 2320361 },
    {city = "Yokohama", pop = 3741765 }
}

-- 一覧表示
for i, v in ipairs(cities) do print(v.city) end

-- ソート ※無名関数を引数にできることに注目
table.sort(cities, function(a, b) return (a.pop > b.pop) end)

-- ふたたび一覧表示
print("---");
for i, v in ipairs(cities) do print(v.city) end

これを下記C言語プログラムから実行する。

#include <stdio.h>
#include "lua.hpp"

int main(void)
{
    // Luaの初期化
    lua_State* L = luaL_newstate();
    // Luaの標準ライブラリを読み込み
    luaL_openlibs(L);
    // Luaスクリプトファイルを実行
    luaL_dofile(L, "test.lua");
    // Luaを終了
    lua_close(L);
    
    return 0;
}

LuaからC言語の関数を呼ぶ出す

#include <stdio.h>
#include "lua.hpp"

// Cの関数: Luaからは add(x, y) と呼び出す
int add(lua_State *L)
{
	// 引数をLuaから取得
    int const x = (int)lua_tonumber(L, 1);
    int const y = (int)lua_tonumber(L, 2);
    // Luaのスタックのクリア
    lua_settop(L, 0);
    
    // 処理の中身
    int const ret = x + y;
    
    // 戻り値をLuaのスタックにプッシュ
    lua_pushnumber(L, ret);
    return 1;
}

int main(void)
{
    // Luaの初期化
    lua_State *L = luaL_newstate();
    // Luaの標準ライブラリを読み込み
    luaL_openlibs(L);
    // LuaにCの関数を登録
    lua_register(L, "add", add);
    // Luaスクリプトを実行
    luaL_dostring(L, "print(add(5, 3))");
    // Luaを終了
    lua_close(L);
    
    return 0;
}

C言語からLuaの関数を呼び出す

#include <stdio.h>
#include "lua.hpp"

int main(void)
{
    // Luaの初期化
    lua_State *L = luaL_newstate();
    // Luaスクリプトを実行 (Luaの関数を定義)
    luaL_dostring(L, "function add(x, y) return x + y end");
    // Luaのスタックに、定義した関数を積む。
    lua_getglobal(L, "add"); 
    // Luaのスタックに引数をプッシュ
    lua_pushinteger(L, 5);
    lua_pushinteger(L, 3);
    
    // Luaの関数を呼び出す (引数は2個、戻り値は1個)
    lua_call(L, 2, 1); 
    
    // 結果を表示
    printf("Result: %d\n", lua_tointeger(L, -1));
    // Luaを終了
    lua_close(L);
    
    return 0;
}

ARMインラインアセンブラ

IAR EWARMで動作確認。GCCインラインアセンブラ構文を採用しており、他でもだいたい同じっぽい。VC++でのx86のインラインアセンブラに比べるとちょっとC言語の変数にアクセスする記法が分かりにくいかも。

基本

VC++と同様に__asmキーワードを用いてインラインアセンブラのコードを書くことができる。ただし、記法は少し異なる。(括弧とか引用符とかセミコロンとか)

    __asm ("add r0, r1, r2");

volatile

volatile修飾子をつけると、コンパイラに最適化されずに書いた通りのコードになる。

    __asm volatile ("add r0, r1, r2");

C言語の変数へのアクセス

VC++では何気なく記述することができたが、GCCインラインアセンブラ構文の場合は出力リスト、入力リストで明示しなければならない。

__asm volatile(
    "add %[result], %[input1], %[input2]"
    : [result] "=r" (c)                   // 出力リスト
    : [input1] "r" (a), [input2] "r" (b)  // 入力リスト
    : // 破壊レジスタリスト
);

上の例で、

  • %[result], %[input1], %[input2] はテンプレート。その下の出力リスト、入力リストでC言語の変数が指定される。
  • 出力リスト、入力リストは、[シンボル名] "制約子" (C言語変数名) の形式で、コンマ区切り。
  • シンボル名は適当な名前を自由に付ける。
  • シンボル名を省略した場合は、%1,%2,%3...のようになる。
  • 制約子はアクセスの制約を指定する。
  • 制約子の"="は書き込み専用を示す。"+"なら読み書き両用。
  • 制約子の"r"は汎用レジスタを示す。
  • 破壊レジスタリストは、破壊してしまうレジスタ名を列挙する。
  • 破壊レジスタリストは、: "r0", "r1" のように、""囲み、コンマ区切りで書く。
  • 破壊レジスタリストで、”cc”は条件レジスタの破壊を示す。
  • 破壊レジスタリストで、”memory”は未知のメモリの破壊を示す。

足し算のコード

x86インラインアセンブラ - 滴了庵日録の記事で書いたサンプルと同等の内容のコード。

#include<stdio.h>

int main(void)
{
    int a, b, c;
    a = 10;
    b = 20;
    
    // c = a + b
    __asm volatile(
        "add %[result], %[input1], %[input2]"
        : [result] "=r" (c)
        : [input1] "r" (a), [input2] "r" (b)
    );
    
    printf("c = %d\n", c);
    
    while(1){;}
    
    return 0;
}

C#とC++のラムダ式

ラムダ式(lambda expression)とは

  • 要するに名前のない関数 (無名関数、匿名関数)、その場で書く関数 (関数リテラル)
  • 値と同じように変数に代入したり関数に渡したりできる。
  • C#C++では微妙に書き方が違う。
  • C#は (引数) => {処理}
  • C++は [キャプチャ](引数) -> 戻り値 {処理}
  • 正確に言うと、C#ラムダ式delegateを作る糖衣構文だけど、以下では雑な説明をする。

ラムダ式を変数に代入

// C#
Action hoge = () => { Console.WriteLine("Hello, world!"); };
hoge();
// C++
auto hoge = []{ cout << "Hello, world!" << endl; };
hoge();
  • C#では戻り値のないラムダ式の型はAction
  • C++では型推論でOK
  • C++では引数も戻り値もないとき () -> は省略できる。

ラムダ式を関数に渡す

// C#
static void piyo(Action func)
{
    func();
}
static void Main(string[] args)
{
    piyo(() => { Console.WriteLine("Hello, world!"); });
}
// C++
template <typename Func>
void piyo(Func func)
{
    func();
}
int main(void)
{
    piyo([]{ cout << "Hello, world!" << endl; });
    return 0;
}
  • C++では引数の型はテンプレートを用いる。

ラムダ式に引数を渡す

// C#
Action<string> hoge = (string str) => { Console.WriteLine(str); };
hoge("Hello, world!");
// C++
auto hoge = [](string str){ cout << str << endl; };
hoge("Hello, world!");

ラムダ式から戻り値を返す

// C#
Func<int, int> twice = (int x) => { return 2 * x; };
Console.WriteLine(twice(13));
// C++
// 戻り値の型を明示
auto twice1 = [](int x) -> int { return 2*x; };
cout << twice1(13) << endl;

// 戻り値の型を省略
auto twice2 = [](int x) { return 2*x; }; 
cout << twice2(13) << endl;

キャプチャ(ラムダ式が定義された関数のスコープの変数を取り込む)

// C#
static void piyo(Action func)
{
    func();
}
static void Main(string[] args)
{
    string hello = "Hello, world!";
    Action hoge = () => { Console.WriteLine(hello); };
    piyo(hoge);
}
// C++
template <typename Func>
void piyo(Func func)
{
    func();
}
int main(void)
{
    string hello = "Hello, world!";
    auto hoge1 = [&] { cout << hello << endl; }; //参照
    auto hoge2 = [=] { cout << hello << endl; }; //コピー
    auto hoge3 = [ ] { cout << hello << endl; }; //エラーになる
    piyo(hoge1);
    piyo(hoge2);
    return 0;
}
  • C++では参照かコピーかの区別がある。明示しなければキャプチャできない。
  • C++では変数ごとに参照かコピーかの指示もできる。

画像の回転

Processingって、ちょこっと画像処理の実験とかするのにちょうどよさげ。
ためしに簡単な画像の回転をやってみた。

回転行列

画像の回転の基本は高校の数学で習う回転行列。
え、今は高校で行列習わないの??? それはアカンやろ!

\left (\begin{array}{c}
    x' \\
    y' \\
\end{array} \right)
=
\left( \begin{array}{cc}
    \cos \theta & -\sin \theta \\
    \sin \theta & \cos \theta \\
\end{array} \right)

\left( \begin{array}{c}
    x \\
    y \\
\end{array} \right)

画像を回転するスケッチ(その1)

回転行列を使って画像を回転するスケッチ

PImage img;         // 画像
float x0,y0;        // 画像の中心座標
float X0,Y0;        // 画面の中心座標

void setup()
{
    size(512, 512);
    
    img = loadImage("Lena.png");
    img.loadPixels();
    
    x0 = img.width / 2;
    y0 = img.height / 2;
    X0 = width / 2;
    Y0 = height / 2;
}

void draw()
{
    background(0);
    
    // マウス座標を角度に
    float th = atan2(mouseY-Y0, mouseX-X0);
    
    // 画像を回転
    for(int y=0; y<img.height-1; y++)
    {
        for(int x=0; x<img.width-1; x++)
        {
            // 回転の計算 (x,y) → (X,Y)
            int X = int((x-x0)*cos(th) - (y-y0)*sin(th) + X0);
            int Y = int((x-x0)*sin(th) + (y-y0)*cos(th) + Y0);
            
            set(X, Y, img.pixels[y*img.width+x]);
        }
    }
}
実行結果

f:id:licheng:20181102191538p:plain
たしかに回転はできたけど、画像に細かい穴が開いて透け透けになってしまった。格子点を斜めに回転して格子点に写すとどうしてもこうなってしまう。レナたんが透け透け。これはアカン。

画像を回転するスケッチ(その2)

そこで発想を逆転して、回転後の座標(X,Y)から回転前の座標(x,y)を逆算することにする。

PImage img;         // 画像
float x0,y0;        // 画像の中心座標
float X0,Y0;        // 画面の中心座標

void setup()
{
    size(512, 512);
    
    img = loadImage("Lena.png");
    img.loadPixels();
    
    x0 = img.width / 2;
    y0 = img.height / 2;
    X0 = width / 2;
    Y0 = height / 2;
}

void draw()
{
    background(0);
    
    // マウス座標を角度に
    float th = atan2(mouseY-Y0, mouseX-X0);
    
    // 画像を回転
    for(int Y=0; Y<height-1; Y++)
    {
        for(int X=0; X<width-1; X++)
        {
            // 回転の計算の逆算(=逆回転) (X,Y) → (x,y)
            int x = int((X-X0)*cos(-th) - (Y-Y0)*sin(-th) + x0);
            int y = int((X-X0)*sin(-th) + (Y-Y0)*cos(-th) + y0);
            
            // 範囲チェック
            if((x>=0) && (x<img.width) && (y>=0) && (y<img.height))
            {
                set(X, Y, img.pixels[y*img.width+x]);
            }
        }
    }
}
実行結果

f:id:licheng:20181102191554p:plain
穴がなくなった。成功! ただし、ちょっとギザギザしている。実用的な画像回転処理ではフィルタを施して画像を滑らかにする必要がある。まあ、今回はここまで。

アニメ調のレナたんについて

サンプル画像に使ったアニメ調のレナたんは、下記サイトで画像処理用テスト素材として公開されているものを使わせていただきました。
(今回アニメ調の画像を使ったのは、穴やギザギザが分かりやすいからです。)
tyosm.blogspot.com

Processingやってみた

Processingとは

  • Javaベースのプログラミング環境
  • Javaベースだけどクラス原理主義じゃない簡単な言語
  • ArduinoにそっくりなシンプルなIDE (というか、Processingのほうが先)
  • グラフィックの描画が得意

Processingの良さ (※個人の感想です)

  • 余計なしきたりとか覚えることが少なくて、書きたいことだけ書ける。
  • キャンバスって何よ? コンテキストって何よ? みたいな本質的でない知識が不要。
  • 基本的な機能だけならimportも不要。ライブラリはメニューからimportできる。
  • 三角関数がMath.cos(theta)みたいにならず、ふつうにcos(theta)と書ける。

コッホ曲線のスケッチ

コッホ曲線を描画するスケッチを書いてみた。
Processingはじめて1時間くらいで書けた。楽しい。

void koch(float x1, float y1, float x2, float y2, int n)
{
    float x3 = (2*x1 + x2)/3.0;
    float y3 = (2*y1 + y2)/3.0;

    float x4 = (x1 + 2*x2)/3.0;
    float y4 = (y1 + 2*y2)/3.0;
    
    float x = (x2 - x1) / sqrt(3);
    float y = (y2 - y1) / sqrt(3);
    float theta = radians(-30);
    float x5 = x1 + x*cos(theta) - y*sin(theta);
    float y5 = y1 + x*sin(theta) + y*cos(theta);
    
    n--;
    if(n<=0){
        line(x1, y1, x3, y3);
        line(x3, y3, x5, y5);
        line(x5, y5, x4, y4);
        line(x4, y4, x2, y2);
    }else{
        koch(x1, y1, x3, y3, n);
        koch(x3, y3, x5, y5, n);
        koch(x5, y5, x4, y4, n);
        koch(x4, y4, x2, y2, n);
    }
}

void setup()
{
    size(640, 480);
    translate(320, 240);
    background(255);
    stroke(0,0,255);
    strokeWeight(1);
    smooth();
    
    float R = 200;
    float[] x = new float[3];
    float[] y = new float[3];
    for(int i=0;i<3;i++){
        x[i] = R*cos(radians(120*i));
        y[i] = R*sin(radians(120*i));  
    }
    
    koch(x[0], y[0], x[1], y[1], 5);
    koch(x[1], y[1], x[2], y[2], 5);
    koch(x[2], y[2], x[0], y[0], 5);
}

void draw()
{
}

実行結果

f:id:licheng:20181101212243p:plain

C++11のスマートポインタ

前提

  • C++のnewはJavaC#のnewのようなつもりで気安く書いてはいけない。
  • なぜならC++には言語レベルではガベージコレクタが無い。
  • newしたオブジェクトは自己責任でdeleteしなければならない。
  • 自動解放の仕組みをライブラリレベルで提供しているのがスマートポインタ。
  • スマートポインタにもいろいろある。

スマートポインタいろいろ

  • std::auto_ptr : C++98からあった。いろいろ欠点があり、C++11では非推奨。
  • std::unique_ptr : std::auto_ptrの改良版のようなやつ。C++11ではこちらを使うべし。
  • std::shared_ptr : 一つのオブジェクトを複数のスマートポインタから参照できる。
  • std::weak_ptr : 弱参照する。std::shared_ptrだと循環参照に陥る場合とかに用いる。
  • boost::scoped_ptr : std::unique_ptrの前身? しらんけど。
  • boost::shared_ptr : std::shared_ptrの前身? しらんけど。
  • boost::weak_ptr : std::weak_ptrの前身? しらんけど。

C++11使えること前提なら、std::unique_ptrstd::shared_ptr、おまけでstd::weak_ptrをおさえておけばよい。オブジェクトの所有権を独占する(std::unique_ptr)か共有する(std::shared_ptr)に明示的な区別があるのがC++のスマートポインタの特徴。JavaC#の参照にはそのような概念はない。

std::unique_ptr

  • いちばん基本的なスマートポインタ
  • オブジェクトの所有権を独占する。
  • つまり、あるスマートポインタのスコープから出るときにオブジェクトは必ず解放される。
  • そのため参照のコピーは出来ないが、所有権を譲渡することはできる。
サンプルコード
#include <iostream>
#include <memory> // スマートポインタを使うために必要

// テキトーなクラス
class Hoge
{
public:
    Hoge()
    {
        std::cout << "constructor" << std::endl;
    }
    ~Hoge()
    {
        std::cout << "destructor" << std::endl;
    }
    void message()
    {
        std::cout << "message" << std::endl;
    }
};

int main(void)
{
    // 生ポインタの場合
    std::cout << "Raw Pointer:" << std::endl << std::endl;
    for (int i = 0; i < 3; ++i)
    {
        Hoge* hoge = new Hoge();
        hoge->message();
    }

    // スマートポインタの場合
    std::cout << std::endl << "Smart Pointer:" << std::endl << std::endl;
    for (int i = 0; i < 3; ++i)
    {
        std::unique_ptr<Hoge> hoge(new Hoge()); // 定義の記述はちょっとウザいが
        hoge->message(); // 生ポインタと同様の記述で使える
    }

    return 0;
}
実行結果

生ポインタだとデストラクタは呼ばれないが、スマートポインタだと呼ばれる。

Raw Pointer:

constructor
message
constructor
message
constructor
message

Smart Pointer:

constructor
message
destructor
constructor
message
destructor
constructor
message
destructor


std::shared_ptr

  • 一つのオブジェクトを複数のポインタから参照したいときに使うスマートポインタ
  • オブジェクトの所有権を共有する。
  • そのために、オブジェクトを参照しているポインタの数が参照カウンタで監視されている。
  • 参照カウンタがゼロになったときにオブジェクトは解放される。
サンプルコード
#include <iostream>
#include <string>
#include <memory>

// テキトーなクラス
class Hoge
{
public:
    Hoge(std::string name)
    {
        m_name = name;
        std::cout << "constructor of " << m_name << std::endl;
    }
    ~Hoge()
    {
        std::cout << "destructor of " << m_name << std::endl;
    }
    void message()
    {
        std::cout << "I am " << m_name << std::endl;
    }
private:
    std::string m_name;
};

int main()
{
    // std::unique_ptrの場合
    std::cout << "std::unique_ptr:" << std::endl;
    {
        std::unique_ptr<Hoge> u1(new Hoge("u1"));
        u1->message();
//      std::unique_ptr<Hoge> u2 = u1; ←コンパイルエラーになる (参照のコピー不可)
    }

    // std::shared_ptrの場合
    std::cout << std::endl << "std::shared_ptr:" << std::endl;
    {
        std::shared_ptr<Hoge> s1(new Hoge("s1"));
        s1->message();
        {
            std::shared_ptr<Hoge> s2 = s1; // 参照のコピー可
            s2->message();
            std::cout << "s2's scope ends" << std::endl;
        }
        std::cout << "s1's scope ends" << std::endl;
    }
    return 0;
}
実行結果
std::unique_ptr:
constructor of u1
I am u1
destructor of u1

std::shared_ptr:
constructor of s1
I am s1
I am s1
s2's scope ends
s1's scope ends
destructor of s1



std::weak_ptr

  • 参照するだけで所有権には関与しない。
  • つまり参照カウンタの増減に影響しない。
  • したがって、このポインタが生存中でも、オブジェクトは既に解放されているかもしれない。
  • 生ポインタとちがって、オブジェクトが解放されたかをチェックすることができる。
  • std::shared_ptrだと循環参照に陥る場合とかに用いる。
サンプルコード
include <iostream>
#include <string>
#include <memory>

// テキトーなクラス
class Hoge
{
public:
    Hoge(std::string name)
    {
        m_name = name;
        std::cout << "constructor of " << m_name << std::endl;
    }
    ~Hoge()
    {
        std::cout << "destructor of " << m_name << std::endl;
    }

    std::shared_ptr<Hoge> s_ptr;
    std::weak_ptr<Hoge> w_ptr;

private:
    std::string m_name;
};

int main()
{
    // 循環参照の例
    std::cout << "Circular Reference" << std::endl;
    {
        std::shared_ptr<Hoge> s1(new Hoge("s1"));
        std::shared_ptr<Hoge> s2(new Hoge("s2"));
        s1->s_ptr = s2;
        s2->s_ptr = s1;
    }

    // 弱参照の例
    std::cout << std::endl << "Weak Reference" << std::endl;
    {
        std::shared_ptr<Hoge> s1(new Hoge("s1"));
        std::shared_ptr<Hoge> s2(new Hoge("s2"));
        s1->s_ptr = s2;
        s2->w_ptr = std::weak_ptr<Hoge>(s1); // 片方を弱参照にする
    }
    return 0;
}
実行結果

循環参照だとオブジェクトが解放されずメモリリークするが、片方が弱参照だと解放される。

Circular Reference
constructor of s1
constructor of s2

Weak Reference
constructor of s1
constructor of s2
destructor of s1
destructor of s2

いまさらC#でasync/await

いまいち理解が定着してなかったのでおさらい。

  • asyncなメソッドからTaskを起動してawaitする。
  • awaitするといったんそのメソッドからリターンする。
  • タスクが完了するとそのメソッドに戻り、awaitの続きから処理される。
  • つまり、途中で中断/後で再開するのがasyncなメソッド。
  • async/awaitと対のように言うけど、awaitはasyncなメソッドの中で使うもの。
  • async/await使うとThreadを自分で書かなくて済む。
  • つまり糖衣構文にすぎないが、記述がブツ切りにならないので、書きやすく読みやすい。
  • まあ、それだけ。

簡単なサンプルコード

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    // asyncなメソッド
    private async void button1_Click(object sender, EventArgs e)
    {
        Console.WriteLine("button1_Click() begins : @Thread "
            + Thread.CurrentThread.ManagedThreadId.ToString());

        // 重い処理をどっかの別スレッドで実行 (おまかせ)
        Task<int> task = Task.Run<int>(new Func<int>(HeavyTask));
        Console.WriteLine("before await");

        // ここでいったん中断 / 重い処理の終わった後で再開
        int result = await task;
        
        Console.WriteLine("after await : @Thread "
             + Thread.CurrentThread.ManagedThreadId.ToString());
        this.textBox1.Text = result.ToString();
        Console.WriteLine("button1_Click() ends");
    }
    
    // 重い処理
    private int HeavyTask()
    {
        Console.WriteLine("HeavyTask() begins : @Thread "
            + Thread.CurrentThread.ManagedThreadId.ToString());

        int sum = 0;
        for (int i = 0; i <= 10; ++i)
        {
            sum += i; // まあ、この計算は重くないけど
        }
        Thread.Sleep(3000); // ここで3秒かかる (重い)
        Console.WriteLine("HeavyTask() ends");
        return sum;
    }
}

実行結果例

button1_Click() begins : @Thread 9
before await
HeavyTask() begins : @Thread 10
HeavyTask() ends
after await : @Thread 9
button1_Click() ends

注意事項

  • ほんとうは、asyncなメソッドはTaskを戻り値にしないといけない。
  • 上のサンプルコードはUIのイベントハンドラなので、例外的にasync voidで大丈夫。
  • UIのイベントハンドラの場合には、たぶん裏で誰かがあんじょうやってくれてる。しらんけど。

参考にしたページ

qiita.com