動的な関数の追加

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
pocket
記事: 49
登録日時: 4年前

動的な関数の追加

#1

投稿記事 by pocket » 4年前

お世話になっております。

プログラムを作成していて思ったのですが、c++で動的に関数の追加をする方法はありませんか?
c++がコンパイル言語ということは承知しています。
ですが、コンパイル後に関数の追加をしたいです。

たとえば、hello,good bye という単語を表示する関数が定義されているとします。
hello.cpp

コード:

hello()({
   std::cout<<"hello"<<std::endl;
}
good_bye.cpp

コード:

good_bye(){
  std::cout<<"good bye"<<std::endl;
}
main.cpp

コード:

int main(){
   int count=0;
   //hello関数を読み込む
   //good_bye関数を読み込む
   std::cout<<count<<"個の関数が読み込まれました"<<std::endl<<"どの関数を使いますか?";
   int x;
   std::cin>>x;
   //選択された関数実行
}
コンパイル時にリンクするというわけではなく、実行時に動的に関数を読み込んで、その関数を実行できるようにしたいです。
DLLを使うと出来るかなと思いましたが、どうも読み込む関数を最初に定義しないといけないみたいです。(私の認識が違うかもしれません)
また、AOP指向プログラムを使うと出来るという情報も得ましたが、わかりませんでした。

スクリプト言語を使わず、c++で実装できる方法をご存知の方がいらっしゃいましたら、ご教授いただければ幸いです。

よろしくお願いいたします。

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: 動的な関数の追加

#2

投稿記事 by YuO » 4年前

関数シグネチャは固定にしないといけませんが,
  • 動的リンク (DLLやsoなど)
  • 実行時コンパイル (Java/.NETなどのJIT等が代表例)
で関数へのポインタを得ることができれば,あとは通常の関数ポインタと同じ取り扱いで呼び出すことが出来ます。

検索するならば,「プラグイン」あたりを検索のワードとして用いるのがよいかと思います。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 動的な関数の追加

#3

投稿記事 by softya(ソフト屋) » 4年前

ソースコードを吐き出して、コンパイラを呼び出して、動的にDLLを作成すれば出来ます。
ただ、コンパイラを添えたり、すごく脆弱性を伴うので公開するプログラムにするのはどうかと思います。
なんでも出来ますからね。

【補足】
YuOさんの言われる俗に言うプラグインの事であれば、世の中に参考例は沢山ありますよ。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

pocket
記事: 49
登録日時: 4年前

Re: 動的な関数の追加

#4

投稿記事 by pocket » 4年前

>>Yuoさん

返信ありがとうございます。
動的リンクと実行時コンパイル、プラグインについて調べてみました。
私の認識では、関数の中身を外部に置くことは出来るように思いました。
つまり、実行時に.soファイル内の関数を利用する感じです。

ですが、その場合、事前に関数名を知っておく必要があると思います。
私が調べたところ、継承を利用して関数名を事前に知っているというものがありました。

私の理想では、文字列から実行コードを生成したいです。

例えば、以下のようになります。

コード:

int main(){
   std::string str;
   std::cin>>str;//入力にhello
   //ここにhello();が動的に生成され実行する
}
例えば、system()+lsでフォルダ内の.soファイルを見つけて、
動的に関数を利用できるようにしたいです。

併せて回答いただければ幸いです。


>>ソフト屋さん

回答ありがとうございます。
ソースコードを吐き出すとは、具体的にはどういう動作でしょうか?
また、コンパイラを呼び出すとは、コードの中から呼び出すということでしょうか?

初歩的な質問で申し訳ないのですが、回答いただければ幸いです。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 動的な関数の追加

#5

投稿記事 by softya(ソフト屋) » 4年前

>ソースコードを吐き出すとは、具体的にはどういう動作でしょうか?

関数を作るとありますが、この関数のソースコード自体はどこから出てくるんでしょうか?
私はDLLのソースコードをDLLを必要とするプログラムがファイルとして書きだすと解釈しました。
なので、ソースコードを吐き出すと表現しています。

>また、コンパイラを呼び出すとは、コードの中から呼び出すということでしょうか?

あなたのプログラムからコンパイラとリンカを実行するという意味です。
つまり、統合開発環境のやっていることの簡易版です。

> 初歩的な質問で申し訳ないのですが、回答いただければ幸いです。

いえいえ、やろうとしている事は中級から上級者レベルの質問です。
こんな事をする必然が良く分かりません。
なので、技術的に可能な方法を提案しているだけです。
実はもっと楽に解決出来るかもしれませんが、真の意図がわからないのでこう応えるしか無いわけです。
世の中にあまり情報が無い方法というものは技術的には可能でも実はやらないほうが良いことも多いですから、そこは考えてみてください。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: 動的な関数の追加

#6

投稿記事 by YuO » 4年前

「何のためにやるのか」がわからないので,なんとも回答しにくいのですが……。

pocket さんが書きました:動的リンクと実行時コンパイル、プラグインについて調べてみました。
私の認識では、関数の中身を外部に置くことは出来るように思いました。
つまり、実行時に.soファイル内の関数を利用する感じです。

ですが、その場合、事前に関数名を知っておく必要があると思います。
プラグインの場合はホスト側が関数シグネチャを指定します。
関数名に関しては,別の方法で指定できれば指定内容に含める必要がなかったりしますが。
e.g.) WindowsでのGetProcAddress APIのように関数名から関数へのポインタを取得できれば呼び出せる

ただし,実際の関数の引数の型と数,戻り値の型が決定していないと標準C/C++のみで単純に書くことはできません。
一応,標準C++にasmがキーワードとして含まれているのですが,
ISO/IEC 14882:2011 さんが書きました:The asm declaration is conditionally-supported; its meaning is implementation-defined.
なので……。


動的な実行時リンクが必要な場合で,プラグインのレベル以上の柔軟性を必要とすることは通常のプログラムではありません。
プラグインでは機能が足りないのであれば,たいていの場合は設計ミスか,欲張りすぎなだけだと思います。
まず,何のためにそこまでの柔軟性が必要なのか,考え直してみてはどうでしょうか。

かずま

Re: 動的な関数の追加

#7

投稿記事 by かずま » 4年前

pocket さんが書きました: コンパイル時にリンクするというわけではなく、実行時に動的に関数を読み込んで、その関数を実行できるようにしたいです。
DLLを使うと出来るかなと思いましたが、どうも読み込む関数を最初に定義しないといけないみたいです。(私の認識が違うかもしれません)
sub.cpp

コード:

#include <iostream>
#include <windows.h>

extern "C" __declspec(dllexport) void hello()
{
    std::cout<<"hello"<<std::endl;
}

extern "C" __declspec(dllexport) void good_bye()
{
    std::cout<<"good bye"<<std::endl;
}
main.cpp

コード:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
    HMODULE h = LoadLibrary("sub");
    if (!h) return 1;
    int count;
    while (cout << "count: ", cin >> count) {
        FARPROC f = GetProcAddress(h, (char*)count);
        if (f) f();
        else cout << "can't get proc address\n";
    }
    FreeLibrary(h);
}
コンパイル

コード:

C:\tmp>cl -c -EHsc sub.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

sub.cpp

C:\tmp>link -dll sub.obj
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.

   ライブラリ sub.lib とオブジェクト sub.exp を作成中

C:\tmp>cl -EHsc main.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj
実行例

コード:

C:\tmp>main
count: 1
good bye
count: 2
hello
count: 3
can't get proc address
count: .
番号は関数のアルファベット順のようです。

pocket
記事: 49
登録日時: 4年前

Re: 動的な関数の追加

#8

投稿記事 by pocket » 4年前

>>ソフト屋さん

返信ありがとうございます。
関数のソースコード自体はすでに用意されている(.soファイルなどで)事を前提としています。

また、何のために必要なのかということですが、現在動的に関数が追加できるような仕組みを実装しています。

例えば、あるシステムは足し算しか出来ないとします。
そこで、掛け算をしたいとなった場合、ソースコートを書き換えて、コンパイルすれば掛け算が出来るようになります。
しかし、ソースコートを書き換えずに、ある特定のフォルダに掛け算用の.soファイルを入れるだけで、
掛け算が出来るようになるプログラムを作りたいと思っています。

具体的には、system関数とlsを用いて、あるフォルダ内の.soファイルの名前を配列に入れます。
フォルダ内には、hello.so good_bye.so see_you.soがあるとします。
そして、cinで数字を読み込んで、1が読み込まれたら、hello.so内に記述されている関数を実行する、といった動作を目指しています。
この仕組みを利用すれば、ソースコードを書き換えることをせずに、動的に機能の追加が出来るのではないかと考えています。

>>世の中にあまり情報が無い方法というものは技術的には可能でも実はやらないほうが良いことも多いですから、そこは考えてみてください。

アドバイスありがとうございます。
一長一短を考えて、実装していきたいと思います。

>>YuOさん

回答ありがとうございます。

>>まず,何のためにそこまでの柔軟性が必要なのか,考え直してみてはどうでしょうか。

こちらに関しては、上記で述べたとおりです。
設計ミスという可能性も大いにあるのですが、より簡単にシステムに機能が追加できる仕組みを目指しています。

>>かずまさん

回答ありがとうございます。

ソースコードの理解に少し手間取っていますので、
取り急ぎお礼を申し上げます。

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: 動的な関数の追加

#9

投稿記事 by YuO » 4年前

pocket さんが書きました:例えば、あるシステムは足し算しか出来ないとします。
そこで、掛け算をしたいとなった場合、ソースコートを書き換えて、コンパイルすれば掛け算が出来るようになります。
しかし、ソースコートを書き換えずに、ある特定のフォルダに掛け算用の.soファイルを入れるだけで、
掛け算が出来るようになるプログラムを作りたいと思っています。
追加する機能が可能な事項は,どの程度の範囲なのですか。
例えば,数値2つを演算して結果を数値で返す,というのであれば,メソッドのシグネチャ固定したプラグインシステムで十分処理できる話になります。
pocket さんが書きました:設計ミスという可能性も大いにあるのですが、より簡単にシステムに機能が追加できる仕組みを目指しています。
簡単に機能を追加できるようにしたいのであれば,追加して行えることを絞ることです。

とりあえず,次の点についてよく考えて説明してみることから始めることをお薦めします。
  • ホスト側は何をするソフトウェアなのですか
  • そのソフトウェアは,何を拡張できるのですか
  • 拡張する場合において,その拡張が元の処理のどこに割り込むのですか
  • 拡張するにあたっての,要求事項 (メソッドシグネチャ等) はどうなっていますか

sleep

Re: 動的な関数の追加

#10

投稿記事 by sleep » 4年前

pocket さんが書きました: 例えば、あるシステムは足し算しか出来ないとします。
そこで、掛け算をしたいとなった場合、ソースコートを書き換えて、コンパイルすれば掛け算が出来るようになります。
しかし、ソースコートを書き換えずに、ある特定のフォルダに掛け算用の.soファイルを入れるだけで、
掛け算が出来るようになるプログラムを作りたいと思っています。


add.c

コード:

//gcc -shared -fPIC -o add.so add.c
int add(int x, int y)
{
	return x + y;
}
mul.c

コード:

//gcc -shared -fPIC -o mul.so mul.c
int mul(int x, int y)
{
	return x * y;
}
test.cpp

コード:

//g++ -pthread -std=c++0x -o test test.cpp -ldl
#include <iostream>
#include <string>
#include <cstdio>   //asprintf
#include <cassert>  //assert
#include <dlfcn.h>
#include <sys/wait.h>
using namespace std;

int main(int argc, char **argv)
{
	string command;
	while (getline(cin, command), !(command.empty() || command == "quit"))
	{
		char *filename;
		asprintf(&filename, "./%s.so", command.c_str());

		void *handle;
		handle = dlopen(filename, RTLD_LAZY);
	    assert(handle != nullptr);
		using fptr = int (*)(int, int);
	    auto func = (fptr)dlsym(handle, command.c_str());
	    assert(func != nullptr);

	    cout << func(2, 3) << endl;

	    dlclose(handle);
	}
    return 0;
}
実行結果

コード:

$ ./test 56
add
5
mul
6

かずま

Re: 動的な関数の追加

#11

投稿記事 by かずま » 4年前

pocket さんが書きました: 具体的には、system関数とlsを用いて、あるフォルダ内の.soファイルの名前を配列に入れます。
フォルダ内には、hello.so good_bye.so see_you.soがあるとします。
そして、cinで数字を読み込んで、1が読み込まれたら、hello.so内に記述されている関数を実行する、といった動作を目指しています。
あるフォルダは、環境変数 SO_PATH で指定してください。
system の代わりに popen を使いました。

main.cpp

コード:

// g++ -o main main.cpp -ldl
#include <iostream>
#include <string>
#include <cstdio>   // popen, pclose, fgets
#include <cstdlib>  // getenv
#include <cstring>  // strstr
#include <vector>
#include <dlfcn.h>  // dlopen, dlsym, dlclose
using namespace std;
 
void help(vector<string>& v)
{
    vector<string>::iterator it;
    int i = 0;
    for (it = v.begin(); it != v.end(); ++it)
        cout << ++i << ": " << *it << endl;
}

int main()
{
    const char *d = getenv("SO_PATH");
    string path = d ? d : ".";
    string cmd = "cd " + path + "; ls";
    FILE *fp = popen(cmd.c_str(), "r");
    if (!fp) return 1;
    vector<string> v;
    char name[1024];
    while (fgets(name, sizeof name, fp)) {
        char *p = strstr(name, ".so");
        if (p) *p = '\0', v.push_back(name);
    }
    pclose(fp);
    help(v);
    int n;
    while (cout << "number> ", cin >> n) {
        if (n < 1 || n > v.size()) { help(v); continue; }
        string name = path + "/" + v[n-1] + ".so";
        void *h = dlopen(name.c_str(), RTLD_LAZY);
        if (!h) { cout << "can't load the function\n"; continue; }
        void (*f)() = (void (*)())dlsym(h, v[n-1].c_str());
        if (f) f();
        else cout << "can't get the function\n";
        dlclose(h);
    }
    return 0;
}
hello.cpp

コード:

// g++ -shared -fPID -o hello.so hello.cpp
#include <iostream>

extern "C" void hello() { std::cout << "hello\n"; }
good_bye.cpp

コード:

// g++ -shared -fPIC -o good_bye.so good_bye.cpp
#include <iostream>

extern "C" void good_bye() { std::cout << "good bye\n"; }

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 動的な関数の追加

#12

投稿記事 by softya(ソフト屋) » 4年前

Unit/Linux系の動的リンクで.soを利用するだけなら、それはプラグインじゃないかなと思うわけですが違うんでしょうか?
私の知っている.soはソースコードじゃなくコンパイル済みのライブラリな訳です。
違うなら違う点を教えて下さい。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

pocket
記事: 49
登録日時: 4年前

Re: 動的な関数の追加

#13

投稿記事 by pocket » 4年前

YuOさん

回答ありがとうございます。

>>追加する機能が可能な事項は,どの程度の範囲なのですか。

現在は、とりあえず機能の追加が出来るプログラムを作成しています。
今後、大規模に拡張していく場合、
YuOさんのおっしゃるとおり、機能を絞っていくこと、または明確に定義する必要があると思います。

また、多くの方が返信してくださったソースコードを精読して、関数シグネチャを固定しなければいけないという意味がようやく理解できました。
ホスト側で実行する場合、受け皿となる関数ポインタを用意しないといけないのですね。
その関数ポインタのシグネチャとdLL側の関数シグネチャが一致している必要がある、と理解しました。
ですが、現状はとりあえず、関数シグネチャを固定して(例えば、引数は必ず2つで戻り値はint型)、関数の追加が出来るようなプログラムの作成を目指します。


sleepさん

回答ありがとうございます。
私が実装したいと思っていたソースコートを記載して頂きありがとうございます。
非常に簡潔に作成していただき、すんなり理解することが出来ました。


かずまさん

複数ソースコードを提示していただきありがとうございます。
私が実現したい内容に合致したコードでした。

ですが、私の知識不足で、提示していただきましたソースコード内に理解できない部分がありましたので、
質問させていただきます。

コード:

    const char *d = getenv("SO_PATH");
    string path = d ? d : ".";
上記の三項演算子の部分です。
dの中身があれば、pathに代入、dの中身が空の場合は”.”をpathに代入するという意味かなと思いました。
それを確かめるために、以下のコードを作成し実行しました。

sample.cpp

コード:

#include <iostream>
#include <string>
using namespace std;

int main(){
  const char *d ;
  string str= d ? d : "sorry";
  cout<<str<<endl;
}
実行結果は以下です。
環境はVMWare Lubuntu g++です。

g++ sample.cpp
% ./a.out
AWAVA��AUATL�%V

文字化けしたものが出力されました。

string path = d ? d : "."; の部分について解説していただければ幸いです。

もう一点お願いします。

コード:

if (p) *p = '\0', v.push_back(name);
上記のif文の使い方は、私が知っているものと異なっており理解できませんでした。
カンマ演算子についていくつかサイトを調べましたが、結局理解できませんでした。

併せて回答いただければ幸いです。

よろしくお願いいたします。

アバター
みけCAT
記事: 6247
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: 動的な関数の追加

#14

投稿記事 by みけCAT » 4年前

pocket さんが書きました:以下のコードを作成し実行しました。

sample.cpp

コード:

#include <iostream>
#include <string>
using namespace std;

int main(){
  const char *d ;
  string str= d ? d : "sorry";
  cout<<str<<endl;
}
実行結果は以下です。
環境はVMWare Lubuntu g++です。

g++ sample.cpp
% ./a.out
AWAVA��AUATL�%V

文字化けしたものが出力されました。
これは未初期化で不定の値を計算に使用している、危険なコードです。
こんなものを実行するべきではありません。
pocket さんが書きました:string path = d ? d : "."; の部分について解説していただければ幸いです。
string型の変数pathを宣言し、dがNULLでなければdが指している文字列の内容、dがNULLであれば"."で初期化する、というコードですね。
pocket さんが書きました:

コード:

if (p) *p = '\0', v.push_back(name);
上記のif文の使い方は、私が知っているものと異なっており理解できませんでした。
カンマ演算子についていくつかサイトを調べましたが、結局理解できませんでした。

併せて回答いただければ幸いです。
「理解できませんでした」だけでは何を回答すればいいのかわからないですが…
この文は

コード:

if (p) {
    *p = '\0';
    v.push_back(name);
}
と(ほぼ)同じ意味です。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

pocket
記事: 49
登録日時: 4年前

Re: 動的な関数の追加

#15

投稿記事 by pocket » 4年前

みけCATさん
みけCAT さんが書きました: これは未初期化で不定の値を計算に使用している、危険なコードです。
こんなものを実行するべきではありません。
アドバイスありがとうございます。

適切に初期化していないことが原因だったのですね。
勉強になりました。

ソースコードは以下のように修正しました。

temp.cpp

コード:

//g++ temp.cpp -std=c++11
#include <iostream>
#include <string>
using namespace std;

int main(){
  const char *d =nullptr;
  string str= d ? d : "sorry";
  cout<<str<<endl;
}
% ./a.out
sorry

適切に動作しました。

また、if文についても解説してくださってありがとうございます。
コンマで処理をつなげることが出来たのですね。
知りませんでした。

今回たくさんの方にご返信いただき本当にありがとうございました。
皆さんのソースコードを精読して、関数ポインタやDLL、カンマ演算子、popenといったことを勉強することが出来ました。
そして、自分がまだまだ基礎的な部分の知識が不足しているということも分かりました。
また、本来の目的である、動的な関数の追加についても実装することが出来ました。

お忙しいところをお時間割いていただきました皆様に感謝を述べまして、
解決とさせていただきます。

閉鎖

“C言語何でも質問掲示板” へ戻る