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++間で複素数を受け渡す
このページでは、1.でLinux用に作ったライブラリーのWindows版(DLL)を紹介します。
使用環境はVisualStudio2022です。新しいプロジェクトの作成にて、「ダイナミックリンクライブラリー」(DLL)と「コンソールアプリ」(呼び出し側)で作成したプロジェクトを使用しました。
ライブラリー作成時のポイント
WindowsでDLL側も呼び出し側もC++の場合、DLL側は__declspec(dllexport)を、呼び出し側は__declspec(dllimport)をそれぞれ関数名の先頭に書いてインポートすれば動作します。
一方、Linux版と同様に、呼び出し側がpythonやC#の場合、エクスポート関数はCの書式である必要があり、DLL側は
extern "C" __declspec(dllexport)
を関数名の先頭に書いてエクスポートします。
このおまじないは長ったらしいので、
#define DLLAPI extern "C" __declspec(dllexport)
などと定義して一単語で済ませることが多いようです。
WindowsではDLLの開始時や終了時の処理をDllMain()内の DLL_PROCESS_ATTACH や DLL_PROCESS_DETACH で実施することが出来ます。
ライブラリーのサンプルコード
Linux版との相違は #include と上述のDLLAPIおよびDllMain()くらいです。pragmaはおせっかいなwarningを抑止するためのもので本題とは無関係です。コードの詳細につきましてはLinux版の説明をご覧下さい。
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define DLLAPI extern "C" __declspec(dllexport)
#include "LocalLib.h"
#pragma warning(disable:4996)
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;
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
switch(ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
CP = new CLocalClass;
break;
case DLL_PROCESS_DETACH:
delete CP;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
default:
break;
}
return TRUE;
}
DLLAPI int LLibSum(int nInA, int nInB)
{
int nRet = (CP->Sum)(nInA, nInB);
return(nRet);
}
DLLAPI int LLibCalcX10(double *dpOut, double *dpIn, int nInLen)
{
int nRet = (CP->CalcX10)(dpOut, dpIn, nInLen);
return(nRet);
}
DLLAPI int LLibTxRxMessage(char *bpOut, char *bpIn, int nInLen)
{
int nRet = (CP->TxRxMessage)(bpOut, bpIn, nInLen);
return(nRet);
}
DLLAPI 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);
}
ヘッダーファイルはDLL側でも呼び出し側でも両方使えるようなひと工夫(#ifndef〜#endif)を加えています。DLL側ではincludeする前に
#define DLLAPI extern "C" __declspec(dllexport)
の一行を追加します。
#ifndef DLLAPI
#define DLLAPI extern "C" __declspec(dllimport)
#endif
typedef struct {
double dInA;
double dInB;
double dOut;
char sData[64];
} tagLibParameter;
DLLAPI int LLibSum(int nInA, int nInB);
DLLAPI int LLibCalcX10(double *dpOut, double *dpIn, int nInLen);
DLLAPI int LLibTxRxMessage(char *bpOut, char *bpIn, int nInLen);
DLLAPI int LLibInOutStr(tagLibParameter *sp);
呼び出し側のサンプルコード
Linux版との相違点は、文法自体はCなのですがファイル名は.cppでC++としてビルドしています。
ヘッダーファイルはDLLで作成したものと同じファイルです。上述のひと工夫により、こちらでは DLLAPI が
extern "C" __declspec(dllimport)
になります。
DLLの使い方は、LoadLibrary()を使用する方法もあるのですが、本例ではインポートライブラリーを使用しています。
DLLのビルド時にDLLと共に生成される.libファイル(=インポートライブラリー)を呼び出し側のプロジェクトのソースファイルのディレクトリーへコピーして、ソースファイル内に
#pragma comment(lib, "LocalLib.lib")
の一行を追加します。
コードの詳細につきましてはLinux版の説明をご覧下さい。
includeとpragma以外は同一内容です。
#include <stdio.h>
#include <string.h>
#include "LocalLib.h"
#pragma comment(lib, "LocalLib.lib")
#pragma warning(disable:4996)
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));
}
実行例
呼び出し側のexeファイル(main.exe)とDLL(LocalLib.dll)を同じディレクトリーに置いて実行して下さい。
呼び出し側とDLLの間で、変数・配列・文字列・構造体のやりとりが双方向で出来ています。 Linux版と同じ結果です。
D:\>main.exe
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 10:53:02 120]
end DLL