ページ 1 / 1
動的な関数の追加
Posted: 2015年12月21日(月) 13:04
by pocket
お世話になっております。
プログラムを作成していて思ったのですが、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++で実装できる方法をご存知の方がいらっしゃいましたら、ご教授いただければ幸いです。
よろしくお願いいたします。
Re: 動的な関数の追加
Posted: 2015年12月21日(月) 13:47
by YuO
関数シグネチャは固定にしないといけませんが,
- 動的リンク (DLLやsoなど)
- 実行時コンパイル (Java/.NETなどのJIT等が代表例)
で関数へのポインタを得ることができれば,あとは通常の関数ポインタと同じ取り扱いで呼び出すことが出来ます。
検索するならば,「プラグイン」あたりを検索のワードとして用いるのがよいかと思います。
Re: 動的な関数の追加
Posted: 2015年12月21日(月) 13:51
by softya(ソフト屋)
ソースコードを吐き出して、コンパイラを呼び出して、動的にDLLを作成すれば出来ます。
ただ、コンパイラを添えたり、すごく脆弱性を伴うので公開するプログラムにするのはどうかと思います。
なんでも出来ますからね。
【補足】
YuOさんの言われる俗に言うプラグインの事であれば、世の中に参考例は沢山ありますよ。
Re: 動的な関数の追加
Posted: 2015年12月21日(月) 19:32
by pocket
>>Yuoさん
返信ありがとうございます。
動的リンクと実行時コンパイル、プラグインについて調べてみました。
私の認識では、関数の中身を外部に置くことは出来るように思いました。
つまり、実行時に.soファイル内の関数を利用する感じです。
ですが、その場合、事前に関数名を知っておく必要があると思います。
私が調べたところ、継承を利用して関数名を事前に知っているというものがありました。
私の理想では、文字列から実行コードを生成したいです。
例えば、以下のようになります。
コード:
int main(){
std::string str;
std::cin>>str;//入力にhello
//ここにhello();が動的に生成され実行する
}
例えば、system()+lsでフォルダ内の.soファイルを見つけて、
動的に関数を利用できるようにしたいです。
併せて回答いただければ幸いです。
>>ソフト屋さん
回答ありがとうございます。
ソースコードを吐き出すとは、具体的にはどういう動作でしょうか?
また、コンパイラを呼び出すとは、コードの中から呼び出すということでしょうか?
初歩的な質問で申し訳ないのですが、回答いただければ幸いです。
Re: 動的な関数の追加
Posted: 2015年12月21日(月) 20:30
by softya(ソフト屋)
>ソースコードを吐き出すとは、具体的にはどういう動作でしょうか?
関数を作るとありますが、この関数のソースコード自体はどこから出てくるんでしょうか?
私はDLLのソースコードをDLLを必要とするプログラムがファイルとして書きだすと解釈しました。
なので、ソースコードを吐き出すと表現しています。
>また、コンパイラを呼び出すとは、コードの中から呼び出すということでしょうか?
あなたのプログラムからコンパイラとリンカを実行するという意味です。
つまり、統合開発環境のやっていることの簡易版です。
> 初歩的な質問で申し訳ないのですが、回答いただければ幸いです。
いえいえ、やろうとしている事は中級から上級者レベルの質問です。
こんな事をする必然が良く分かりません。
なので、技術的に可能な方法を提案しているだけです。
実はもっと楽に解決出来るかもしれませんが、真の意図がわからないのでこう応えるしか無いわけです。
世の中にあまり情報が無い方法というものは技術的には可能でも実はやらないほうが良いことも多いですから、そこは考えてみてください。
Re: 動的な関数の追加
Posted: 2015年12月22日(火) 01:26
by YuO
「何のためにやるのか」がわからないので,なんとも回答しにくいのですが……。
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: 動的な関数の追加
Posted: 2015年12月22日(火) 13:54
by かずま
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: .
番号は関数のアルファベット順のようです。
Re: 動的な関数の追加
Posted: 2015年12月22日(火) 18:01
by pocket
>>ソフト屋さん
返信ありがとうございます。
関数のソースコード自体はすでに用意されている(.soファイルなどで)事を前提としています。
また、何のために必要なのかということですが、現在動的に関数が追加できるような仕組みを実装しています。
例えば、あるシステムは足し算しか出来ないとします。
そこで、掛け算をしたいとなった場合、ソースコートを書き換えて、コンパイルすれば掛け算が出来るようになります。
しかし、ソースコートを書き換えずに、ある特定のフォルダに掛け算用の.soファイルを入れるだけで、
掛け算が出来るようになるプログラムを作りたいと思っています。
具体的には、system関数とlsを用いて、あるフォルダ内の.soファイルの名前を配列に入れます。
フォルダ内には、hello.so good_bye.so see_you.soがあるとします。
そして、cinで数字を読み込んで、1が読み込まれたら、hello.so内に記述されている関数を実行する、といった動作を目指しています。
この仕組みを利用すれば、ソースコードを書き換えることをせずに、動的に機能の追加が出来るのではないかと考えています。
>>世の中にあまり情報が無い方法というものは技術的には可能でも実はやらないほうが良いことも多いですから、そこは考えてみてください。
アドバイスありがとうございます。
一長一短を考えて、実装していきたいと思います。
>>YuOさん
回答ありがとうございます。
>>まず,何のためにそこまでの柔軟性が必要なのか,考え直してみてはどうでしょうか。
こちらに関しては、上記で述べたとおりです。
設計ミスという可能性も大いにあるのですが、より簡単にシステムに機能が追加できる仕組みを目指しています。
>>かずまさん
回答ありがとうございます。
ソースコードの理解に少し手間取っていますので、
取り急ぎお礼を申し上げます。
Re: 動的な関数の追加
Posted: 2015年12月22日(火) 18:59
by YuO
pocket さんが書きました:例えば、あるシステムは足し算しか出来ないとします。
そこで、掛け算をしたいとなった場合、ソースコートを書き換えて、コンパイルすれば掛け算が出来るようになります。
しかし、ソースコートを書き換えずに、ある特定のフォルダに掛け算用の.soファイルを入れるだけで、
掛け算が出来るようになるプログラムを作りたいと思っています。
追加する機能が可能な事項は,どの程度の範囲なのですか。
例えば,数値2つを演算して結果を数値で返す,というのであれば,メソッドのシグネチャ固定したプラグインシステムで十分処理できる話になります。
pocket さんが書きました:設計ミスという可能性も大いにあるのですが、より簡単にシステムに機能が追加できる仕組みを目指しています。
簡単に機能を追加できるようにしたいのであれば,追加して行えることを絞ることです。
とりあえず,次の点についてよく考えて説明してみることから始めることをお薦めします。
- ホスト側は何をするソフトウェアなのですか
- そのソフトウェアは,何を拡張できるのですか
- 拡張する場合において,その拡張が元の処理のどこに割り込むのですか
- 拡張するにあたっての,要求事項 (メソッドシグネチャ等) はどうなっていますか
Re: 動的な関数の追加
Posted: 2015年12月23日(水) 00:51
by sleep
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: 動的な関数の追加
Posted: 2015年12月23日(水) 11:34
by かずま
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"; }
Re: 動的な関数の追加
Posted: 2015年12月23日(水) 13:56
by softya(ソフト屋)
Unit/Linux系の動的リンクで.soを利用するだけなら、それはプラグインじゃないかなと思うわけですが違うんでしょうか?
私の知っている.soはソースコードじゃなくコンパイル済みのライブラリな訳です。
違うなら違う点を教えて下さい。
Re: 動的な関数の追加
Posted: 2015年12月23日(水) 13:58
by pocket
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文の使い方は、私が知っているものと異なっており理解できませんでした。
カンマ演算子についていくつかサイトを調べましたが、結局理解できませんでした。
併せて回答いただければ幸いです。
よろしくお願いいたします。
Re: 動的な関数の追加
Posted: 2015年12月23日(水) 14:42
by みけCAT
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);
}
と(ほぼ)同じ意味です。
Re: 動的な関数の追加
Posted: 2015年12月23日(水) 15:20
by pocket
みけ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といったことを勉強することが出来ました。
そして、自分がまだまだ基礎的な部分の知識が不足しているということも分かりました。
また、本来の目的である、動的な関数の追加についても実装することが出来ました。
お忙しいところをお時間割いていただきました皆様に感謝を述べまして、
解決とさせていただきます。