2016年2月26日金曜日
C++の共有ライブラリをdlsymで呼び出す
C++で共有ライブラリを作って、dlsym()でクラスメソッドを動的に呼び出す場合、どうすればよいのか調べてみました。
ネットでよく出回っている方法は、MicrosoftがComponentObjectModelで昔よく使っていたクラスメソッドにすべてvirtualをつけて宣言し、クラスを作ったり破棄したりするC言語形式のファクトリー関数を用意する方法です。
------------------------------------
#ifdef _WIN32
#ifdef CPPDLL_EXPORTS
#define CPPDLL_API __declspec(dllexport)
#else
#define CPPDLL_API __declspec(dllimport)
#endif
#else
#define CPPDLL_API
#endif
class Ccppdll {
public:
Ccppdll();
virtual ~Ccppdll();
virtual void test1(void);
};
#ifdef __cplusplus
extern "C"{
#endif
//factory
CPPDLL_API Ccppdll* create_cppdll();
CPPDLL_API void destroy_cppdll(Ccppdll*);
#ifdef __cplusplus
}
#endif
Ccppdll::Ccppdll()
{
printf("Ccppdll::Ccppdll()\n");
}
Ccppdll::~Ccppdll()
{
printf("Ccppdll::~Ccppdll()\n");
}
void Ccppdll::test1()
{
printf("Ccppdll::test1\n");
}
Ccppdll* create_cppdll()
{
return new Ccppdll();
}
void destroy_cppdll(Ccppdll* a)
{
delete a;
}
------------------------------------
上記の例で説明すると
dlsym()でcreate_cppdll()のアドレスを取得して関数を呼び出せば、あとはCcppdllのポインタ経由でメンバー変数と仮想関数の呼び出しができます。
virtualで宣言したメンバ関数はコンストラクタで作られる仮想関数テーブル経由で関数が呼び出されるので、共有ライブラリ内でのシンボル名が解決していなくてもよいんです。
でもC++のクラスって、オペレーター演算子の関数が定義されてたり、テンプレートが使われていたりします。長年の疑問だったのですが、そういう場合、動的ロードできるのか調べてみました。
まず、オペレータ演算子のメソッド関数。
この関数そもそもvirtualにできるんですかね?
調べてみた結果、なんとできます。
すごいねぇ、オペレーターメソッド関数って仮想関数にできるのね。
次にテンプレート関数。
この関数はvirtualにできません。
そりゃーそうでしょう。だってテンプレートだから、C++のクラスの仮想関数テーブルの関数の順番が特定できないからね。
でも、テンプレート関数はstaticな無名外部関数に展開されるはずなので、テンプレート関数内部で使っている関数がすべて仮想関数ならばコンパイルが通り動的にロードできます。
実験してみました。
------------------------------------
#ifdef _WIN32
#ifdef CPPDLL_EXPORTS
#define CPPDLL_API __declspec(dllexport)
#else
#define CPPDLL_API __declspec(dllimport)
#endif
#else
#define CPPDLL_API
#endif
class Ccppdll {
public:
//ファクトリーで作る
Ccppdll();
virtual ~Ccppdll();
//呼び出せる
virtual void test1(void);
virtual int operator+(Ccppdll &a);
//呼び出せない
void test2(void);
int operator-(Ccppdll &a);
//呼び出せない
static void test3(void);
//コンパイルエラー
//template<typename t> virtual void test4(t);
//template<typename t> virtual int operator*(t);
//呼び出せる
template<typename t> void test5(t);
//呼び出せない
template<typename t> void test6(t);
};
template<typename a> void Ccppdll::test5(a){
test1();
}
template<typename a> void Ccppdll::test6(a){
test2();
}
#ifdef __cplusplus
extern "C"{
#endif
//factory
CPPDLL_API Ccppdll* create_cppdll();
CPPDLL_API void destroy_cppdll(Ccppdll*);
#ifdef __cplusplus
}
#endif
Ccppdll::Ccppdll()
{
printf("Ccppdll::Ccppdll()\n");
}
Ccppdll::~Ccppdll()
{
printf("Ccppdll::~Ccppdll()\n");
}
void Ccppdll::test1()
{
printf("Ccppdll::test1\n");
}
void Ccppdll::test2()
{
printf("Ccppdll::test2\n");
}
void Ccppdll::test3()
{
printf("Ccppdll::test2\n");
}
int Ccppdll::operator+(Ccppdll& a)
{
return 123;
}
int Ccppdll::operator-(Ccppdll& a)
{
return 456;
}
Ccppdll* create_cppdll()
{
return new Ccppdll();
}
void destroy_cppdll(Ccppdll* a)
{
delete a;
}
------------------------------------
2016年2月25日木曜日
MySQLとSQLiteを切り替える
今週はデータベースの勉強期間なのでデータベースネタをもう一つ。たぶんこれでデータベース系は最後です。
僕、SQLiteって軽くて高速なので、大好きでよく使います。
だけれど、スマートフォンなどのクライアント側はSQLiteが主に使われているのに、サーバ側はMySQLがメインで、使用するデータベースが違っていてサーバとクライアントで共通化できなくて困ります。
SQLiteでさくっとプロトタイプを作って、あとからMySQLに移行してリプリケーションさせてスケールさせることもよくあります。
MySQLって商用のサーバ運用するときにはとっても便利なんですが、ローカルでWebAPIとかを開発するときは自分のPCにインストールするのがめんどくさい。
また、どの言語のどのインタフェースで書くかにもよるのですが、MySQLとSQLiteの両方で動くようにするには関数のインタフェースって微妙に違っていて困ります。JavaだとJDBCがあるのですが、JDOとかPDOとかODBCとか共通化規格がそもそも共通化されてないし。データベースってSQL文投げて複数の行が返ってくるだけなのに、なんでこう関数が違うんでしょうね。簡単なラッパーでよいんでない?
ということで、MySQLとSQLiteを直接切り替えて呼び出せるラッパー関数を作ってみました。僕は高速に動作するC/C++が大好きなので、実装はいつものようにC/C++ですが、ほかの言語でも同じようにすれば複数のデータベースを併用することができます。
前回のブログで書いたMySQLの関数の動的呼び出しを行っているので、ビルド時にMySQLのライブラリはいりません。
これで簡単にデータベースを切り替えられる!
ローカルで開発とかデバッグするときはSQLiteで行い、最終運用はMySQLで行うというようにデータベースを使い分けることや、サーバとクライアントでコードを共通化することもできます。
C言語なのでAndroid、iPhone、Linuxサーバ、Windowsサーバどれでも動くしとても便利。
mysql_sqlite.h
------------------------------------------------------------
#ifndef MY_SQL_SQLITE_H_
#define MY_SQL_SQLITE_H_
#ifdef __cplusplus
extern "C"
{
#endif
typedef void* HDBSQL;
typedef void* HDBCOL;
HDBSQL mydb_connect(const char* server,const char* user,const char* pass,const char* dbname);
void mydb_close(HDBSQL hdb);
int mydb_exec(HDBSQL hdb,const char* str);
HDBCOL mydb_prepare(HDBSQL hdb, const char* str);
int mydb_step(HDBCOL col);
void mydb_free_result(HDBCOL col);
int mydb_column_int(HDBCOL col,int n);
const char* mydb_column_char(HDBCOL col,int n);
#ifdef __cplusplus
}
#endif
#endif /* MY_SQL_SQLITE_H_ */
------------------------------------------------------------
mysql_sqlite.c
------------------------------------------------------------
//#define I_USE_MYSQL
#define I_USE_MYSQL_WRAPPER
#define I_USE_SQLITE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef I_USE_MYSQL
#ifdef I_USE_MYSQL_WRAPPER
#include "mysql_wrapper.h"
#else
#include <mysql/mysql.h>
#endif
#endif
#ifdef I_USE_SQLITE
#include "sqlite3.h"
#endif
#include "mysql_sqlite.h"
HDBSQL mydb_connect(const char* server, const char* user, const char* pass, const char* dbname)
{
HDBSQL ret = NULL;
if (server == NULL || user == NULL || pass == NULL || dbname == NULL)goto end;
#ifdef I_USE_MYSQL
MYSQL *conn = NULL;
conn = mysql_init(NULL);
if (!mysql_real_connect(conn, server, user, pass, dbname, 0, NULL, 0)) {
goto end;
}
ret = (HDBSQL)conn;
#endif
#ifdef I_USE_SQLITE
sqlite3 *conn = NULL;
if(SQLITE_OK != sqlite3_open("db_test", &conn)){
goto end;
}
ret = (HDBSQL)conn;
#endif
end:
return ret;
}
void mydb_close(HDBSQL hdb)
{
if (hdb == NULL)return;
#ifdef I_USE_MYSQL
mysql_close((MYSQL*)hdb);
#endif
#ifdef I_USE_SQLITE
sqlite3_close((sqlite3*)hdb);
#endif
}
int mydb_exec(HDBSQL hdb, const char* str)
{
int ret = -1;
if (hdb == NULL || str==NULL)return ret;
#ifdef I_USE_MYSQL
if (mysql_query((MYSQL*)hdb, str)){
//error
ret = -1;
}else {
//ok
ret = 0;
}
#endif
#ifdef I_USE_SQLITE
if (SQLITE_OK != sqlite3_exec((sqlite3*)hdb, str, NULL, NULL, NULL))
{
ret = -1;
}else {
ret = 0;
}
#endif
return ret;
}
struct hdb {
void* resp;
void* row;
};
HDBCOL mydb_prepare(HDBSQL hdb, const char* str)
{
HDBCOL ret = NULL;
if (hdb == NULL || str == NULL)goto end;
#ifdef I_USE_MYSQL
ret = (HDBCOL)malloc(sizeof(struct hdb));
if (ret == NULL)goto end;
memset(ret, 0, sizeof(struct hdb));
if (mysql_query((MYSQL*)hdb, str)) {
//error
free(ret);
goto end;
}
((struct hdb*)ret)->resp=(HDBCOL)mysql_use_result((MYSQL*)hdb);
#endif
#ifdef I_USE_SQLITE
if (SQLITE_OK != sqlite3_prepare((sqlite3*)hdb, str, strlen(str), (sqlite3_stmt**)(&ret), NULL))
{
ret = NULL;
}
#endif
end:
return ret;
}
void mydb_free_result(HDBCOL col)
{
if (col == NULL)return;
#ifdef I_USE_MYSQL
mysql_free_result((MYSQL_RES*)(((struct hdb*)col)->resp));
free(col);
#endif
#ifdef I_USE_SQLITE
sqlite3_finalize((sqlite3_stmt*)col);
#endif
}
int mydb_step(HDBCOL col)
{
int ret = 0;
if (col == NULL)return ret;
#ifdef I_USE_MYSQL
((struct hdb*)col)->row= mysql_fetch_row((MYSQL_RES*)(((struct hdb*)col)->resp));
if (((struct hdb*)col)->row == NULL)return ret;
ret = 1;
#endif
#ifdef I_USE_SQLITE
if (SQLITE_ROW == sqlite3_step((sqlite3_stmt*)col))ret = 1;
#endif
return ret;
}
int mydb_column_int(HDBCOL col,int n)
{
int ret = 0;
const char* p = NULL;
if (col == NULL)return ret;
#ifdef I_USE_MYSQL
if (((struct hdb*)col)->row == NULL)return ret;
p=((MYSQL_ROW)(((struct hdb*)col)->row))[n];
if (p == NULL)p = "0";
ret = atoi(p);
#endif
#ifdef I_USE_SQLITE
ret= sqlite3_column_int((sqlite3_stmt*)col, n);
#endif
return ret;
}
const char* mydb_column_char(HDBCOL col,int n)
{
const char* ret = NULL;
#ifdef I_USE_MYSQL
if (((struct hdb*)col)->row == NULL)return ret;
if (((struct hdb*)col)->row == NULL)return ret;
ret = ((MYSQL_ROW)(((struct hdb*)col)->row))[n];
#endif
#ifdef I_USE_SQLITE
ret = sqlite3_column_text((sqlite3_stmt*)col, n);
#endif
return ret;
}
#if 0
int main(void) {
HDBSQL conn = NULL;
HDBCOL resp = NULL;
char sql_str[255];
char *sql_serv = "localhost";
char *user = "root";
char *passwd = "wanted";
char *db_name = "db_test";
conn = mydb_connect(sql_serv,user,passwd,db_name);
if (conn == NULL) {
printf("mydb_connect error\n");
exit(-1);
}
memset(&sql_str[0], 0x00, sizeof(sql_str));
snprintf(&sql_str[0], sizeof(sql_str) - 1, "select * from tb_test");
resp = mydb_prepare(conn, sql_str);
if (resp==NULL) {
mydb_close(conn);
exit(-1);
}
while (mydb_step(resp)){
printf("%d : %s\n", mydb_column_int(resp,0),mydb_column_char(resp,1));
}
mydb_free_result(resp);
mydb_close(conn);
return 0;
}
#endif
------------------------------------------------------------
僕、SQLiteって軽くて高速なので、大好きでよく使います。
だけれど、スマートフォンなどのクライアント側はSQLiteが主に使われているのに、サーバ側はMySQLがメインで、使用するデータベースが違っていてサーバとクライアントで共通化できなくて困ります。
SQLiteでさくっとプロトタイプを作って、あとからMySQLに移行してリプリケーションさせてスケールさせることもよくあります。
MySQLって商用のサーバ運用するときにはとっても便利なんですが、ローカルでWebAPIとかを開発するときは自分のPCにインストールするのがめんどくさい。
また、どの言語のどのインタフェースで書くかにもよるのですが、MySQLとSQLiteの両方で動くようにするには関数のインタフェースって微妙に違っていて困ります。JavaだとJDBCがあるのですが、JDOとかPDOとかODBCとか共通化規格がそもそも共通化されてないし。データベースってSQL文投げて複数の行が返ってくるだけなのに、なんでこう関数が違うんでしょうね。簡単なラッパーでよいんでない?
ということで、MySQLとSQLiteを直接切り替えて呼び出せるラッパー関数を作ってみました。僕は高速に動作するC/C++が大好きなので、実装はいつものようにC/C++ですが、ほかの言語でも同じようにすれば複数のデータベースを併用することができます。
前回のブログで書いたMySQLの関数の動的呼び出しを行っているので、ビルド時にMySQLのライブラリはいりません。
これで簡単にデータベースを切り替えられる!
ローカルで開発とかデバッグするときはSQLiteで行い、最終運用はMySQLで行うというようにデータベースを使い分けることや、サーバとクライアントでコードを共通化することもできます。
C言語なのでAndroid、iPhone、Linuxサーバ、Windowsサーバどれでも動くしとても便利。
mysql_sqlite.h
------------------------------------------------------------
#ifndef MY_SQL_SQLITE_H_
#define MY_SQL_SQLITE_H_
#ifdef __cplusplus
extern "C"
{
#endif
typedef void* HDBSQL;
typedef void* HDBCOL;
HDBSQL mydb_connect(const char* server,const char* user,const char* pass,const char* dbname);
void mydb_close(HDBSQL hdb);
int mydb_exec(HDBSQL hdb,const char* str);
HDBCOL mydb_prepare(HDBSQL hdb, const char* str);
int mydb_step(HDBCOL col);
void mydb_free_result(HDBCOL col);
int mydb_column_int(HDBCOL col,int n);
const char* mydb_column_char(HDBCOL col,int n);
#ifdef __cplusplus
}
#endif
#endif /* MY_SQL_SQLITE_H_ */
------------------------------------------------------------
mysql_sqlite.c
------------------------------------------------------------
//#define I_USE_MYSQL
#define I_USE_MYSQL_WRAPPER
#define I_USE_SQLITE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef I_USE_MYSQL
#ifdef I_USE_MYSQL_WRAPPER
#include "mysql_wrapper.h"
#else
#include <mysql/mysql.h>
#endif
#endif
#ifdef I_USE_SQLITE
#include "sqlite3.h"
#endif
#include "mysql_sqlite.h"
HDBSQL mydb_connect(const char* server, const char* user, const char* pass, const char* dbname)
{
HDBSQL ret = NULL;
if (server == NULL || user == NULL || pass == NULL || dbname == NULL)goto end;
#ifdef I_USE_MYSQL
MYSQL *conn = NULL;
conn = mysql_init(NULL);
if (!mysql_real_connect(conn, server, user, pass, dbname, 0, NULL, 0)) {
goto end;
}
ret = (HDBSQL)conn;
#endif
#ifdef I_USE_SQLITE
sqlite3 *conn = NULL;
if(SQLITE_OK != sqlite3_open("db_test", &conn)){
goto end;
}
ret = (HDBSQL)conn;
#endif
end:
return ret;
}
void mydb_close(HDBSQL hdb)
{
if (hdb == NULL)return;
#ifdef I_USE_MYSQL
mysql_close((MYSQL*)hdb);
#endif
#ifdef I_USE_SQLITE
sqlite3_close((sqlite3*)hdb);
#endif
}
int mydb_exec(HDBSQL hdb, const char* str)
{
int ret = -1;
if (hdb == NULL || str==NULL)return ret;
#ifdef I_USE_MYSQL
if (mysql_query((MYSQL*)hdb, str)){
//error
ret = -1;
}else {
//ok
ret = 0;
}
#endif
#ifdef I_USE_SQLITE
if (SQLITE_OK != sqlite3_exec((sqlite3*)hdb, str, NULL, NULL, NULL))
{
ret = -1;
}else {
ret = 0;
}
#endif
return ret;
}
struct hdb {
void* resp;
void* row;
};
HDBCOL mydb_prepare(HDBSQL hdb, const char* str)
{
HDBCOL ret = NULL;
if (hdb == NULL || str == NULL)goto end;
#ifdef I_USE_MYSQL
ret = (HDBCOL)malloc(sizeof(struct hdb));
if (ret == NULL)goto end;
memset(ret, 0, sizeof(struct hdb));
if (mysql_query((MYSQL*)hdb, str)) {
//error
free(ret);
goto end;
}
((struct hdb*)ret)->resp=(HDBCOL)mysql_use_result((MYSQL*)hdb);
#endif
#ifdef I_USE_SQLITE
if (SQLITE_OK != sqlite3_prepare((sqlite3*)hdb, str, strlen(str), (sqlite3_stmt**)(&ret), NULL))
{
ret = NULL;
}
#endif
end:
return ret;
}
void mydb_free_result(HDBCOL col)
{
if (col == NULL)return;
#ifdef I_USE_MYSQL
mysql_free_result((MYSQL_RES*)(((struct hdb*)col)->resp));
free(col);
#endif
#ifdef I_USE_SQLITE
sqlite3_finalize((sqlite3_stmt*)col);
#endif
}
int mydb_step(HDBCOL col)
{
int ret = 0;
if (col == NULL)return ret;
#ifdef I_USE_MYSQL
((struct hdb*)col)->row= mysql_fetch_row((MYSQL_RES*)(((struct hdb*)col)->resp));
if (((struct hdb*)col)->row == NULL)return ret;
ret = 1;
#endif
#ifdef I_USE_SQLITE
if (SQLITE_ROW == sqlite3_step((sqlite3_stmt*)col))ret = 1;
#endif
return ret;
}
int mydb_column_int(HDBCOL col,int n)
{
int ret = 0;
const char* p = NULL;
if (col == NULL)return ret;
#ifdef I_USE_MYSQL
if (((struct hdb*)col)->row == NULL)return ret;
p=((MYSQL_ROW)(((struct hdb*)col)->row))[n];
if (p == NULL)p = "0";
ret = atoi(p);
#endif
#ifdef I_USE_SQLITE
ret= sqlite3_column_int((sqlite3_stmt*)col, n);
#endif
return ret;
}
const char* mydb_column_char(HDBCOL col,int n)
{
const char* ret = NULL;
#ifdef I_USE_MYSQL
if (((struct hdb*)col)->row == NULL)return ret;
if (((struct hdb*)col)->row == NULL)return ret;
ret = ((MYSQL_ROW)(((struct hdb*)col)->row))[n];
#endif
#ifdef I_USE_SQLITE
ret = sqlite3_column_text((sqlite3_stmt*)col, n);
#endif
return ret;
}
#if 0
int main(void) {
HDBSQL conn = NULL;
HDBCOL resp = NULL;
char sql_str[255];
char *sql_serv = "localhost";
char *user = "root";
char *passwd = "wanted";
char *db_name = "db_test";
conn = mydb_connect(sql_serv,user,passwd,db_name);
if (conn == NULL) {
printf("mydb_connect error\n");
exit(-1);
}
memset(&sql_str[0], 0x00, sizeof(sql_str));
snprintf(&sql_str[0], sizeof(sql_str) - 1, "select * from tb_test");
resp = mydb_prepare(conn, sql_str);
if (resp==NULL) {
mydb_close(conn);
exit(-1);
}
while (mydb_step(resp)){
printf("%d : %s\n", mydb_column_int(resp,0),mydb_column_char(resp,1));
}
mydb_free_result(resp);
mydb_close(conn);
return 0;
}
#endif
------------------------------------------------------------
2016年2月22日月曜日
MySQLを動的にリンクする
久しぶりにデータベースの勉強をしようと思い僕のWindowsマシンにMySQLを入れてみました。
ところがMySQLをC言語から使おうとするとVisualStudio2013までしかサポートしていません。
せっかく最新のVisualStudio2015をいれたのに。これでは最新の開発でMySQL使えないじゃん。
このようなVisualStudioやGCCのバージョン依存問題って本当によく起こるのでこれらを解決する方法を考えてみました。
考えること一時間、MySQLの関数を動的にリンクして呼び出せばよいのではないかと思い、MySQLの動的呼び出しラッパーをつくってみました。
余談ですが、MySQLのコード書いている人、とても正しくきれいにコーディングできていてすごいですね。びっくりです。OpenSSLなんて汚くて汚くて。
mysql_wrapper.h
----------------------------------------
#ifndef MY_SQL_WRAPPER_H_
#define MY_SQL_WRAPPER_H_
#include <mysql/mysql.h>
#ifdef __cplusplus
extern "C"
{
#endif
extern MYSQL * (STDCALL *pf_mysql_init)(MYSQL *mysql);
extern void (STDCALL *pf_mysql_close)(MYSQL *sock);
extern MYSQL * (STDCALL *pf_mysql_real_connect)(MYSQL *mysql, const char *host,
const char *user,const char *passwd,const char *db,
unsigned int port,const char *unix_socket,unsigned long clientflag);
extern int (STDCALL *pf_mysql_query)(MYSQL *mysql, const char *q);
extern MYSQL_RES * (STDCALL *pf_mysql_use_result)(MYSQL *mysql);
extern void (STDCALL *pf_mysql_free_result)(MYSQL_RES *result);
extern MYSQL_ROW(STDCALL *pf_mysql_fetch_row)(MYSQL_RES *result);
#ifdef __cplusplus
}
#endif
#ifndef MY_SQL_WRAOPPER_C
#define mysql_init pf_mysql_init
#define mysql_close pf_mysql_close
#define mysql_real_connect pf_mysql_real_connect
#define mysql_query pf_mysql_query
#define mysql_use_result pf_mysql_use_result
#define mysql_free_result pf_mysql_free_result
#define mysql_fetch_row pf_mysql_fetch_row
#endif /* MY_SQL_WRAOPPER_C */
#endif /* MY_SQL_WRAPPER_H_ */
----------------------------------------
mysql_wrapper.cpp
----------------------------------------
#define MY_SQL_WRAOPPER_C
#include "mysql_wrapper.h"
#include "dll_client.h"
MYSQL * (STDCALL *pf_mysql_init)(MYSQL *mysql)=NULL;
void (STDCALL *pf_mysql_close)(MYSQL *sock)=NULL;
MYSQL * (STDCALL *pf_mysql_real_connect)(MYSQL *mysql, const char *host,
const char *user,const char *passwd,const char *db,unsigned int port,
const char *unix_socket,unsigned long clientflag) = NULL;
int (STDCALL *pf_mysql_query)(MYSQL *mysql, const char *q)=NULL;
MYSQL_RES * (STDCALL *pf_mysql_use_result)(MYSQL *mysql)=NULL;
void (STDCALL *pf_mysql_free_result)(MYSQL_RES *result)=NULL;
MYSQL_ROW(STDCALL *pf_mysql_fetch_row)(MYSQL_RES *result) = NULL;
static void *h_mysql = NULL;
class mysql_wrapper {
public :
mysql_wrapper();
virtual ~mysql_wrapper();
};
mysql_wrapper::mysql_wrapper()
{
h_mysql = dll_load(DLL_NAME("libmysql"));
if (h_mysql == NULL)return;
pf_mysql_init = (MYSQL * (STDCALL *)(MYSQL *))dll_access(h_mysql, "mysql_init");
pf_mysql_close = (void (STDCALL *)(MYSQL *))dll_access(h_mysql, "mysql_close");
pf_mysql_real_connect = (MYSQL * (STDCALL *)(MYSQL *, const char *,
const char *, const char *, const char *, unsigned int ,
const char *, unsigned long))dll_access(h_mysql, "mysql_real_connect");
pf_mysql_query = (int (STDCALL *)(MYSQL *mysql, const char *))dll_access(h_mysql, "mysql_query");
pf_mysql_use_result = (MYSQL_RES * (STDCALL *)(MYSQL *))dll_access(h_mysql, "mysql_use_result");
pf_mysql_free_result = (void (STDCALL *)(MYSQL_RES *))dll_access(h_mysql, "mysql_free_result");
pf_mysql_fetch_row = (MYSQL_ROW(STDCALL*)(MYSQL_RES *))dll_access(h_mysql, "mysql_fetch_row");
}
mysql_wrapper::~mysql_wrapper()
{
if (h_mysql)dll_close(h_mysql);
h_mysql = NULL;
}
static mysql_wrapper s_mysql_wrapper;
----------------------------------------
test.c
----------------------------------------
#define I_USE_MYSQL_WRAPPER
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef I_USE_MYSQL_WRAPPER
#include "mysql_wrapper.h"
#else
#include <mysql/mysql.h>
#endif
int main(void) {
MYSQL *conn = NULL;
MYSQL_RES *resp = NULL;
MYSQL_ROW row;
char sql_str[255];
char *sql_serv = "localhost";
char *user = "root";
char *passwd = "hoge";
char *db_name = "db_test";
memset(&sql_str[0], 0x00, sizeof(sql_str));
// mysql接続
conn = mysql_init(NULL);
if (!mysql_real_connect(conn, sql_serv, user, passwd, db_name, 0, NULL, 0)) {
// error
exit(-1);
}
// クエリ実行
snprintf(&sql_str[0], sizeof(sql_str) - 1, "select * from tb_test");
if (mysql_query(conn, &sql_str[0])) {
// error
mysql_close(conn);
exit(-1);
}
// レスポンス
resp = mysql_use_result(conn);
while ((row = mysql_fetch_row(resp)) != NULL) {
printf("%d : %s\n", atoi(row[0]), row[1]);
}
// 後片づけ
mysql_free_result(resp);
mysql_close(conn);
return 0;
}
----------------------------------------
やったー、動いた。
テストプログラムもちゃんと動きました。
この方法、OpenCVなどC++で書かれているライブラリだと使えませんが、
OpenSSLなどC言語で書かれているほかのライブラリでも使えます。
ところがMySQLをC言語から使おうとするとVisualStudio2013までしかサポートしていません。
せっかく最新のVisualStudio2015をいれたのに。これでは最新の開発でMySQL使えないじゃん。
このようなVisualStudioやGCCのバージョン依存問題って本当によく起こるのでこれらを解決する方法を考えてみました。
考えること一時間、MySQLの関数を動的にリンクして呼び出せばよいのではないかと思い、MySQLの動的呼び出しラッパーをつくってみました。
余談ですが、MySQLのコード書いている人、とても正しくきれいにコーディングできていてすごいですね。びっくりです。OpenSSLなんて汚くて汚くて。
mysql_wrapper.h
----------------------------------------
#ifndef MY_SQL_WRAPPER_H_
#define MY_SQL_WRAPPER_H_
#include <mysql/mysql.h>
#ifdef __cplusplus
extern "C"
{
#endif
extern MYSQL * (STDCALL *pf_mysql_init)(MYSQL *mysql);
extern void (STDCALL *pf_mysql_close)(MYSQL *sock);
extern MYSQL * (STDCALL *pf_mysql_real_connect)(MYSQL *mysql, const char *host,
const char *user,const char *passwd,const char *db,
unsigned int port,const char *unix_socket,unsigned long clientflag);
extern int (STDCALL *pf_mysql_query)(MYSQL *mysql, const char *q);
extern MYSQL_RES * (STDCALL *pf_mysql_use_result)(MYSQL *mysql);
extern void (STDCALL *pf_mysql_free_result)(MYSQL_RES *result);
extern MYSQL_ROW(STDCALL *pf_mysql_fetch_row)(MYSQL_RES *result);
#ifdef __cplusplus
}
#endif
#ifndef MY_SQL_WRAOPPER_C
#define mysql_init pf_mysql_init
#define mysql_close pf_mysql_close
#define mysql_real_connect pf_mysql_real_connect
#define mysql_query pf_mysql_query
#define mysql_use_result pf_mysql_use_result
#define mysql_free_result pf_mysql_free_result
#define mysql_fetch_row pf_mysql_fetch_row
#endif /* MY_SQL_WRAOPPER_C */
#endif /* MY_SQL_WRAPPER_H_ */
----------------------------------------
mysql_wrapper.cpp
----------------------------------------
#define MY_SQL_WRAOPPER_C
#include "mysql_wrapper.h"
#include "dll_client.h"
MYSQL * (STDCALL *pf_mysql_init)(MYSQL *mysql)=NULL;
void (STDCALL *pf_mysql_close)(MYSQL *sock)=NULL;
MYSQL * (STDCALL *pf_mysql_real_connect)(MYSQL *mysql, const char *host,
const char *user,const char *passwd,const char *db,unsigned int port,
const char *unix_socket,unsigned long clientflag) = NULL;
int (STDCALL *pf_mysql_query)(MYSQL *mysql, const char *q)=NULL;
MYSQL_RES * (STDCALL *pf_mysql_use_result)(MYSQL *mysql)=NULL;
void (STDCALL *pf_mysql_free_result)(MYSQL_RES *result)=NULL;
MYSQL_ROW(STDCALL *pf_mysql_fetch_row)(MYSQL_RES *result) = NULL;
static void *h_mysql = NULL;
class mysql_wrapper {
public :
mysql_wrapper();
virtual ~mysql_wrapper();
};
mysql_wrapper::mysql_wrapper()
{
h_mysql = dll_load(DLL_NAME("libmysql"));
if (h_mysql == NULL)return;
pf_mysql_init = (MYSQL * (STDCALL *)(MYSQL *))dll_access(h_mysql, "mysql_init");
pf_mysql_close = (void (STDCALL *)(MYSQL *))dll_access(h_mysql, "mysql_close");
pf_mysql_real_connect = (MYSQL * (STDCALL *)(MYSQL *, const char *,
const char *, const char *, const char *, unsigned int ,
const char *, unsigned long))dll_access(h_mysql, "mysql_real_connect");
pf_mysql_query = (int (STDCALL *)(MYSQL *mysql, const char *))dll_access(h_mysql, "mysql_query");
pf_mysql_use_result = (MYSQL_RES * (STDCALL *)(MYSQL *))dll_access(h_mysql, "mysql_use_result");
pf_mysql_free_result = (void (STDCALL *)(MYSQL_RES *))dll_access(h_mysql, "mysql_free_result");
pf_mysql_fetch_row = (MYSQL_ROW(STDCALL*)(MYSQL_RES *))dll_access(h_mysql, "mysql_fetch_row");
}
mysql_wrapper::~mysql_wrapper()
{
if (h_mysql)dll_close(h_mysql);
h_mysql = NULL;
}
static mysql_wrapper s_mysql_wrapper;
----------------------------------------
test.c
----------------------------------------
#define I_USE_MYSQL_WRAPPER
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef I_USE_MYSQL_WRAPPER
#include "mysql_wrapper.h"
#else
#include <mysql/mysql.h>
#endif
int main(void) {
MYSQL *conn = NULL;
MYSQL_RES *resp = NULL;
MYSQL_ROW row;
char sql_str[255];
char *sql_serv = "localhost";
char *user = "root";
char *passwd = "hoge";
char *db_name = "db_test";
memset(&sql_str[0], 0x00, sizeof(sql_str));
// mysql接続
conn = mysql_init(NULL);
if (!mysql_real_connect(conn, sql_serv, user, passwd, db_name, 0, NULL, 0)) {
// error
exit(-1);
}
// クエリ実行
snprintf(&sql_str[0], sizeof(sql_str) - 1, "select * from tb_test");
if (mysql_query(conn, &sql_str[0])) {
// error
mysql_close(conn);
exit(-1);
}
// レスポンス
resp = mysql_use_result(conn);
while ((row = mysql_fetch_row(resp)) != NULL) {
printf("%d : %s\n", atoi(row[0]), row[1]);
}
// 後片づけ
mysql_free_result(resp);
mysql_close(conn);
return 0;
}
----------------------------------------
やったー、動いた。
テストプログラムもちゃんと動きました。
この方法、OpenCVなどC++で書かれているライブラリだと使えませんが、
OpenSSLなどC言語で書かれているほかのライブラリでも使えます。
2016年2月16日火曜日
セキュアでないセキュアプログラミング
お仕事でプログラムをするときは、たとえば長い文字列を渡されてプログラムが攻撃されても大丈夫なようにセキュアプログラミングをしないといけません。
言語ごとにセキュアプログラミングのポイントが異なるのですが、とくにC言語はセキュアにプログラムをするのが本当に難しいです。
例えばアップルとかは以下のガイドラインを出してます。
セキュアコーディングガイド - Apple Developer
これにもC言語でセキュアプログラミングをする場合、「sprintf()、vsprint()でなくてsnprintf()、vsnprint()を使いましょう」って書いてあります。
でもこれ嘘です。
たとえば、Visual Studio 2013のC++のsnprintf()、vsnprint()は文字列が指定したサイズをオーバーしそうになった場合、文字列をnullで終端しません。
なのでsnprintf()を使って文字列を切り捨てているLinuxアプリをWindowsに移植するとへんな文字列が表示されたりプログラムが落ちます。
ANSI規格でnull終端する決まりになっていますが、決まりができるより前からあるVisualStudioではそうなっていないのです。
MSDNにも以下のように書かれています。
----------------------
Visual Studio 2015 その他のバージョン
解説
これらの関数は、引数リストへのポインターを使用し、データを書式指定して count 文字数までの文字を buffer が指すメモリに書き込みます。終端に空きがある場合 (つまり、書き込む文字数が count 文字数未満の場合)、バッファーは null で終わります。
----------------------
なんでしょうね。このへたくそな解説と翻訳。
一見nullで終わりますって書いてあるけど、よく読むと条件付きでnullで終わります。
つまり、nullでおわるとは限らないのです。
英語文化圏の人にはわかりやすいんでしょうがこれが日本語の壁というかなんというか。
さらにひどい会社とかのルールになるとstrcpy()、strcat()の代わりにstrncpy()、strncat()を使いましょうっていうのもあります。
もうこのルール作ったの誰だよ!これもそうとう怪しいです。
だってstrncpy()もVisualStudioのsnprintf()と同様にnullで終端しない可能性があるし、strncat()にいたっては連結する最大文字列であってバッファの最大サイズではないんです。
strcpy()、strcat()を使ってはいけないまではあっているのに・・・・。
もう、何が安全なのかさっぱりわかりません。
C言語のセキュアプログラミングについてどれも信用できないし、strcpy()、strcat()、sprintf()、snprintf()の関数って結構便利で使いたいですよね。
C言語の場合どの解説もよい代替案がないので、それが問題なんですよね。
解決策として、マイクロソフトはマイクロソフトがセキュリティー開発ライフサイクル(SDL)プログラミングっていうのを推奨しています。
これによると、マイクロソフトはstrcpy_s()、strcat_s()、sprintf_s()、snprintf_s()という関数を使用することを推奨していますが、これってLinuxにないので困ります。
また、アップルは上記にあげた先ほどのガイドラインで、strlcpy()とstrlcat()を使うことを推奨しています。これもMacOSXとかiPhoneでしか動かない。
なので、「僕だったらこう作ります。」というstrcpy_y()、strcat_y()、sprintf_y()を作ってみました。
基本的にはアップルと同じものをアップル以外にも動くようにした感じで、マイクロソフトの良いところも取り入れてみました。
こう書けばどのOSでも動くし、#include"safestring.h"すれば出力バッファが関数内で定義されているという条件付きですがstrcat()とかsprintf()とかそのままでも結構安全に動きます。
一流企業でもセキュアプログラミングの方針が全く違うので、C言語での標準化なんて出来そうにないですね。やはりC言語でのセキュアプログラミングのやり方って難しいんですね。
safestring.h
-------------------------------
#ifndef df_SAFESTRING_H_
#define df_SAFESTRING_H_
#define strcpy(a,b) strcpy_y(a,sizeof(a),b)
#define strcat(a,b) strcat_y(a,sizeof(a),b)
#define sprintf(a,b,...) sprintf_y(a,sizeof(a),b,__VA_ARGS__)
#define snprintf(a,b,fmt,...) sprintf_y(a,b,fmt,__VA_ARGS__)
#ifdef __cplusplus
extern "C"
{
#endif
char* strcpy_y(char* a,int sz,const char* b);
char* strcat_y(char* a, int sz, const char* b);
int sprintf_y(char* a,int sz,const char* fmt,...);
#ifdef __cplusplus
}
#endif
#endif /* df_SAFESTRING_H_ */
-------------------------------
safestring.c
-------------------------------
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "safestring.h"
#ifdef _MSC_VER
#if _MSC_VER >= 1400
#pragma warning( disable : 4996 )
#pragma warning( disable : 4819 )
#endif
#endif
char* strcpy_y(char* a, int sz, const char* b)
{
if (a == NULL || b == NULL || sz<1)return a;
*a = 0;
strncpy(a, b, sz);
a[sz - 1] = 0;
return a;
}
char* strcat_y(char* a, int sz, const char* b)
{
int l;
if (a == NULL || b == NULL || sz<1)return a;
l=sz-1-strlen(a);
if (l< 1)return a;
strncat(a, b, l);
a[sz - 1] = 0;
return a;
}
int sprintf_y(char* a, int sz, const char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret=vsnprintf(a, sz, fmt, ap);
a[sz - 1] = 0;
//va_end(ap);
return ret;
}
static int snprintf_bug(char* a, int sz, const char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = vsnprintf(a, sz, fmt, ap);
//a[sz - 1] = 0;
//va_end(ap);
return ret;
}
#if 1
int main(int argc, char* argv[])
{
char a[8];
char* p;
p = a;
char b[16];
strcpy(a, "1234567890");
printf("a=>>%s<<\n",a);
strcpy(a, "a");
strcat(a, "bcdefgihj");
printf("a=>>%s<<\n", a);
strcpy(p, "a");
strcat(p, "bcdefgihj");
printf("a=>>%s<<\n", a);
sprintf(a, "--%d--", 123456);
printf("a=>>%s<<\n", a);
snprintf(a, sizeof(a), "++%d++", 123456);
printf("a=>>%s<<\n", a);
strcpy(b, "###############");
snprintf_bug(b, 8, "--%d--", 123456);
printf("b=>>%s<<\n", b);
return 0;
}
#endif
-------------------------------
言語ごとにセキュアプログラミングのポイントが異なるのですが、とくにC言語はセキュアにプログラムをするのが本当に難しいです。
例えばアップルとかは以下のガイドラインを出してます。
セキュアコーディングガイド - Apple Developer
これにもC言語でセキュアプログラミングをする場合、「sprintf()、vsprint()でなくてsnprintf()、vsnprint()を使いましょう」って書いてあります。
でもこれ嘘です。
たとえば、Visual Studio 2013のC++のsnprintf()、vsnprint()は文字列が指定したサイズをオーバーしそうになった場合、文字列をnullで終端しません。
なのでsnprintf()を使って文字列を切り捨てているLinuxアプリをWindowsに移植するとへんな文字列が表示されたりプログラムが落ちます。
ANSI規格でnull終端する決まりになっていますが、決まりができるより前からあるVisualStudioではそうなっていないのです。
MSDNにも以下のように書かれています。
----------------------
Visual Studio 2015 その他のバージョン
解説
これらの関数は、引数リストへのポインターを使用し、データを書式指定して count 文字数までの文字を buffer が指すメモリに書き込みます。終端に空きがある場合 (つまり、書き込む文字数が count 文字数未満の場合)、バッファーは null で終わります。
----------------------
なんでしょうね。このへたくそな解説と翻訳。
一見nullで終わりますって書いてあるけど、よく読むと条件付きでnullで終わります。
つまり、nullでおわるとは限らないのです。
英語文化圏の人にはわかりやすいんでしょうがこれが日本語の壁というかなんというか。
さらにひどい会社とかのルールになるとstrcpy()、strcat()の代わりにstrncpy()、strncat()を使いましょうっていうのもあります。
もうこのルール作ったの誰だよ!これもそうとう怪しいです。
だってstrncpy()もVisualStudioのsnprintf()と同様にnullで終端しない可能性があるし、strncat()にいたっては連結する最大文字列であってバッファの最大サイズではないんです。
strcpy()、strcat()を使ってはいけないまではあっているのに・・・・。
もう、何が安全なのかさっぱりわかりません。
C言語のセキュアプログラミングについてどれも信用できないし、strcpy()、strcat()、sprintf()、snprintf()の関数って結構便利で使いたいですよね。
C言語の場合どの解説もよい代替案がないので、それが問題なんですよね。
解決策として、マイクロソフトはマイクロソフトがセキュリティー開発ライフサイクル(SDL)プログラミングっていうのを推奨しています。
これによると、マイクロソフトはstrcpy_s()、strcat_s()、sprintf_s()、snprintf_s()という関数を使用することを推奨していますが、これってLinuxにないので困ります。
また、アップルは上記にあげた先ほどのガイドラインで、strlcpy()とstrlcat()を使うことを推奨しています。これもMacOSXとかiPhoneでしか動かない。
なので、「僕だったらこう作ります。」というstrcpy_y()、strcat_y()、sprintf_y()を作ってみました。
基本的にはアップルと同じものをアップル以外にも動くようにした感じで、マイクロソフトの良いところも取り入れてみました。
こう書けばどのOSでも動くし、#include"safestring.h"すれば出力バッファが関数内で定義されているという条件付きですがstrcat()とかsprintf()とかそのままでも結構安全に動きます。
一流企業でもセキュアプログラミングの方針が全く違うので、C言語での標準化なんて出来そうにないですね。やはりC言語でのセキュアプログラミングのやり方って難しいんですね。
safestring.h
-------------------------------
#ifndef df_SAFESTRING_H_
#define df_SAFESTRING_H_
#define strcpy(a,b) strcpy_y(a,sizeof(a),b)
#define strcat(a,b) strcat_y(a,sizeof(a),b)
#define sprintf(a,b,...) sprintf_y(a,sizeof(a),b,__VA_ARGS__)
#define snprintf(a,b,fmt,...) sprintf_y(a,b,fmt,__VA_ARGS__)
#ifdef __cplusplus
extern "C"
{
#endif
char* strcpy_y(char* a,int sz,const char* b);
char* strcat_y(char* a, int sz, const char* b);
int sprintf_y(char* a,int sz,const char* fmt,...);
#ifdef __cplusplus
}
#endif
#endif /* df_SAFESTRING_H_ */
-------------------------------
safestring.c
-------------------------------
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "safestring.h"
#ifdef _MSC_VER
#if _MSC_VER >= 1400
#pragma warning( disable : 4996 )
#pragma warning( disable : 4819 )
#endif
#endif
char* strcpy_y(char* a, int sz, const char* b)
{
if (a == NULL || b == NULL || sz<1)return a;
*a = 0;
strncpy(a, b, sz);
a[sz - 1] = 0;
return a;
}
char* strcat_y(char* a, int sz, const char* b)
{
int l;
if (a == NULL || b == NULL || sz<1)return a;
l=sz-1-strlen(a);
if (l< 1)return a;
strncat(a, b, l);
a[sz - 1] = 0;
return a;
}
int sprintf_y(char* a, int sz, const char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret=vsnprintf(a, sz, fmt, ap);
a[sz - 1] = 0;
//va_end(ap);
return ret;
}
static int snprintf_bug(char* a, int sz, const char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = vsnprintf(a, sz, fmt, ap);
//a[sz - 1] = 0;
//va_end(ap);
return ret;
}
#if 1
int main(int argc, char* argv[])
{
char a[8];
char* p;
p = a;
char b[16];
strcpy(a, "1234567890");
printf("a=>>%s<<\n",a);
strcpy(a, "a");
strcat(a, "bcdefgihj");
printf("a=>>%s<<\n", a);
strcpy(p, "a");
strcat(p, "bcdefgihj");
printf("a=>>%s<<\n", a);
sprintf(a, "--%d--", 123456);
printf("a=>>%s<<\n", a);
snprintf(a, sizeof(a), "++%d++", 123456);
printf("a=>>%s<<\n", a);
strcpy(b, "###############");
snprintf_bug(b, 8, "--%d--", 123456);
printf("b=>>%s<<\n", b);
return 0;
}
#endif
-------------------------------
登録:
投稿 (Atom)