C++でライブラリー(DLL)を作り、pythonなどから利用する方法について連載します。
1. C++でライブラリーを作る Linux編
2. pythonからC++のライブラリーを利用する Linux編
3. C++でライブラリー(DLL)を作る Windows編
4. pythonからC++のライブラリー(DLL)を利用する Windows編
5. C#からC++のライブラリー(DLL)を利用する
6. python・C#とC++間で複素数を受け渡す
ライブラリー作成時のポイント
C++で作ったライブラリーをC++で利用する場合はライブラリーをC++で書いても支障ないのですが、pythonなど他言語から利用する場合はエクスポート関数をCの書式にする必要があります。LinuxでC++コンパイラー(g++)でビルドする場合、ヘッダーファイルでエクスポート関数を定義する箇所を extern "C" { } で囲むとソースコードは追加なしで良いようです。
WindowsではDLLの開始時や終了時の処理をdllmain()内の DLL_PROCESS_ATTACH や DLL_PROCESS_DETACHで実施することが出来ます。Linuxでも同じことを LibAttach() と LibDetach()で実施できます。
ライブラリーのサンプルコード
今回作ったライブラリーはC++で記述しました。ライブラリー起動時と終了時にクラスのインスタンスをnew/deleteします。Cのエクスポート関数内でC++クラスのメンバー関数を実行します。
4つのエクスポート関数を用意しました。
int LLibSum(int nInA, int nInB);
整数変数を渡して受け取る例です。2つの引数の和を返します。
int LLibCalcX10(double *dpOut, double *dpIn, int nInLen);
配列を渡して受け取る例です。実数データを10倍にします。
int LLibTxRxMessage(char *bpOut, char *bpIn, int nInLen);
文字列を渡して受け取る例です。
ライブラリーが受け取ったメッセージに
ライブラリーのメッセージを加えて返します。
int LLibInOutStr(tagLibParameter *sp);
構造体の受け渡し例です。
利用側で入力した2つの値の和
および日付時間情報の文字列を返します。
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "LocalLib.h"
class CLocalClass {
public:
CLocalClass(void);
virtual ~CLocalClass(void);
int Sum(int nInA, int nInB);
int CalcX10(double *dpOut, double *dpIn, int nInLen);
int TxRxMessage(char *bpOut, char *bpIn, int nInLen);
int InOutStr(tagLibParameter *sp);
private:
};
CLocalClass *CP;
void __attribute__ ((constructor)) LibAttach(void)
{
CP = new CLocalClass;
}
void __attribute__ ((destructor)) LibDetach(void) {
delete CP;
}
int LLibSum(int nInA, int nInB)
{
int nRet = (CP->Sum)(nInA, nInB);
return(nRet);
}
int LLibCalcX10(double *dpOut, double *dpIn, int nInLen)
{
int nRet = (CP->CalcX10)(dpOut, dpIn, nInLen);
return(nRet);
}
int LLibTxRxMessage(char *bpOut, char *bpIn, int nInLen)
{
int nRet = (CP->TxRxMessage)(bpOut, bpIn, nInLen);
return(nRet);
}
int LLibInOutStr(tagLibParameter *sp)
{
int nRet = (CP->InOutStr)(sp);
return(nRet);
}
//================================================================================
// CLocalClass
//================================================================================
CLocalClass::CLocalClass(void)
{
printf("start DLL\n");
}
CLocalClass::~CLocalClass(void)
{
printf("end DLL\n");
}
int CLocalClass::Sum(int nInA, int nInB)
{
int nSum = nInA + nInB;
return(nSum);
}
int CLocalClass::CalcX10(double *dpOut, double *dpIn, int nInLen)
{
for(int nLoop=0; nLoop<nInLen; nLoop++) {
(dpOut[nLoop]) = (dpIn[nLoop]) * 10.0;
}
return(0);
}
int CLocalClass::TxRxMessage(char *bpOut, char *bpIn, int nInLen)
{
char sMsg[256];
int nMsgLen, nCpLen;
//--------
sprintf(sMsg, "DLL->App: Hi ! rcvd[%s]", bpIn);
nMsgLen = (int)strlen(sMsg);
nCpLen = nMsgLen;
if(nInLen<=nCpLen) {
nCpLen = nInLen - 1;
}
memcpy(bpOut, sMsg, nCpLen);
(bpOut[nCpLen]) = 0;
return(nCpLen);
}
int CLocalClass::InOutStr(tagLibParameter *sp)
{
struct timespec ts;
struct tm *pTm;
(sp->dOut) = (sp->dInA) + (sp->dInB);
int nRet = timespec_get(&ts, TIME_UTC);
pTm = localtime(&(ts.tv_sec));
sprintf((sp->sData), "%04d %02d/%02d %02d:%02d:%02d %03d",
((pTm->tm_year)+1900), ((pTm->tm_mon)+1), (pTm->tm_mday),
(pTm->tm_hour), (pTm->tm_min), (pTm->tm_sec), (int)((ts.tv_nsec)/(1000*1000)));
return(0);
}
ヘッダーファイルは上述の通りエクスポート関数をextern "C" { }で囲みます。
typedef struct {
double dInA;
double dInB;
double dOut;
char sData[64];
} tagLibParameter;
extern "C" {
int LLibSum(int nInA, int nInB);
int LLibCalcX10(double *dpOut, double *dpIn, int nInLen);
int LLibTxRxMessage(char *bpOut, char *bpIn, int nInLen);
int LLibInOutStr(tagLibParameter *sp);
}
次のようにビルドして下さい。
g++ -shared -fPIC -o libLocalLib.so LocalLib.cpp
呼び出し側のサンプルコード
ライブラリーの動作確認のため、とりあえずここでは呼び出し側のサンプルコードをCで書きました。
配列と文字列のテストでは、ライブラリーへ渡す配列(文字列)と共にライブラリーから処理結果を受け取る配列(文字列)も呼び出し側で用意して2つの配列(文字列)のアドレスをライブラリーへ伝えます。
2025_1120 追記 コードに誤りがあり修正しました。ご指摘ありがとうございました。
#include <stdio.h>
#include <string.h>
typedef struct {
double dInA;
double dInB;
double dOut;
char sData[64];
} tagLibParameter;
extern int LLibSum(int nInA, int nInB);
extern int LLibCalcX10(double *dpOut, double *dpIn, int nInLen);
extern int LLibTxRxMessage(char *bpOut, char *bpIn, int nInLen);
extern int LLibInOutStr(tagLibParameter *sp);
int main()
{
int nA, nB, nSum;
nA = 1;
nB = 2;
//---------------------
nSum = LLibSum(nA, nB);
//---------------------
printf("%d + %d = %d\n", nA, nB, nSum);
//================================================================
double dsOut[3], dsIn[3];
int nLen = (sizeof(dsOut)) / (sizeof(double));
dsIn[0] = 1.1;
dsIn[1] = 2.2;
dsIn[2] = 3.3;
//----------------------------------------------------------
LLibCalcX10(dsOut, dsIn, ((sizeof(dsIn))/(sizeof(double))));
//----------------------------------------------------------
printf("in[%.1lf %.1lf %.1lf] out[%.1lf %.1lf %.1lf]\n",
(dsIn[0]), (dsIn[1]), (dsIn[2]),
(dsOut[0]), (dsOut[1]), (dsOut[2]) );
//================================================================
char sTxBuf[64], sRxBuf[256];
sprintf(sTxBuf, "App->DLL: Hello !");
//----------------------------------------------
LLibTxRxMessage(sRxBuf, sTxBuf, sizeof(sRxBuf));
//----------------------------------------------
printf("%s\n", sRxBuf);
//================================================================
tagLibParameter LP;
memset(&LP, 0, sizeof(tagLibParameter));
(LP.dInA) = 1.1;
(LP.dInB) = 2.2;
//----------------
LLibInOutStr(&LP);
//----------------
printf("%.1lf + %.1lf = %.1lf [%s]\n",
(LP.dInA), (LP.dInB), (LP.dOut), (LP.sData));
}
Linuxではライブラリーを /usr/local/lib などへコピーして利用することが一般的ですが、ここでは呼び出し側の実行ファイルと同じ場所にあるライブラリーを利用する例を紹介します。
次のようにビルドして下さい。
gcc -o main.o -c main.c
gcc -L./ -Wl,-rpath=./ -o main main.o -lLocalLib
ライブラリーの名前が libLocalLib.so と変てこりんなのは、Linuxのライブラリー名はlibから始まるのが一般的で、リンク時は頭のlibを取ったライブラリー名を使用するからです。
実行するとこのようになります。呼び出し側とライブラリー間で、変数・配列・文字列・構造体の双方向のやりとりが出来ています。
$ ./main
start DLL
1 + 2 = 3
in[1.1 2.2 3.3] out[11.0 22.0 33.0]
DLL->App: Hi ! rcvd[App->DLL: Hello !]
1.1 + 2.2 = 3.3 [2025 11/18 19:43:02 398]
end DLL