VC++によるDLLファイルの作り方

 投稿者:しばっち  投稿日:2019年12月 1日(日)15時49分44秒
  無料版のVisual Studio 2019 Communityを使用してWindows版十進BASIC上で動くDLLファイルを作成します。
(他のバージョンでも操作は同じです)

https://visualstudio.microsoft.com/ja/downloads/
https://qiita.com/Gaccho/items/1409c27216a67014a024

但し、VC++ 2019のIDEは使用せず、テキストエディタ(メモ帳等)でc/cppソースファイルを記述します。
十進BASICはwin32アプリなのでx86用のコマンドプロンプトを使用します。

スタートメニューからVC++2019のx86 native tools コマンドプロンプト又はx64_x86 cross tools コマンドプロンプト
を起動します。

起動直後はカレントフォルダがCドライブのプログラムフォルダになっています。
cl /?と打ち込むとclコマンドのヘルプが表示されます。
これでコンパイル可能ですが、プログラムフォルダは書き込み禁止のためコンパイルに失敗します。
pathが既に通っていますので別フォルダに移動してから作業始めてください。

c/c++で十進BASICから呼び出す関数には

cソースでは__declspec(dllexport) をつけて、
cppソースではextern "C"  __declspec(dllexport)をつけて記述します。
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) int test(int a,int b)
{
    int c;
    c=a+b;
    return c;
}

これをコンパイルしてDLLファイルを作成するには
cl /LD sample.cpp

と打ち込みます。/LDはdllファイルを作成するためのオプションです。
オプション /O2 や /Ox /Ot等で最適化を行います。
/arch:AVX 等のオプションをつけるとsimd命令のAVX等を利用できます。(※CPUが対応していること)

cl /LD /O2 /Ox /Ot /Qpar /EHsc /Gd /arch:AVX sample.cpp

BASIC側では次のようにします。

LET X=2147483647
LET Y=1
PRINT TEST(X,Y);MOD(X+Y+2^31,2^32)-2^31
END

EXTERNAL  FUNCTION TEST(X,Y)
ASSIGN "sample.dll","test"
END FUNCTION

int型の引数はBASIC側ではそのまま数値変数としてDLLへ渡します。
整数型をreturnで受け取るのでFUNCTIONで定義します。
unsignedをつけることもできます。
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) unsigned int test(unsigned int a,unsigned int b)
{
    int c;
    c=a*b;
    return c;
}

LET X=-3.2354885
LET Y=5.657321
LET X=INT(X+.5)
LET Y=INT(Y+.5)
PRINT TEST(X,Y);X*Y;
LET X=MOD(X+2^32,2^32)
LET Y=MOD(Y+2^32,2^32)
LET Z=MOD(X*Y+2^32,2^32)-2^32
PRINT Z
END

EXTERNAL  FUNCTION TEST(X,Y)
ASSIGN "sample.dll","test"
END FUNCTION

実数値を与えていますが、四捨五入されるようです。
unsigned int型のはずですが、-18と表示されました。

実数値を渡すときは
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) double test(double *a,double *b)
{
    double c;
    c=*a * *b;
    return c;
}
LET X=-3.2354885
LET Y=5.657321
LET X$=PACKDBL$(X)
LET Y$=PACKDBL$(Y)
PRINT TEST(X$,Y$);X*Y
END

EXTERNAL  FUNCTION TEST(X$,Y$)
ASSIGN "sample.dll","test",FPU
END FUNCTION

double型をdllへ渡すときはPACKDBL$使ってdouble型バイナリーを渡します。
double型を受け取るのでBASIC側ではFUNCTIONで定義してASSIGN文にFPUをつけます。
なお、return文で戻値をBASIC側で受け取れるのはint型とdouble型のみのようです。

double型の1次元配列を渡すときは
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) double sum(int n,double *a)
{
    double s=0.0;
    int i;
    for(i=0;i<n;i++) s+=a[i];
    return s;
}

OPTION CHARACTER BYTE
LET N=100
DIM A(N)
FOR I=1 TO N
   LET A(I)=RND*100
NEXT
LET X$=REPEAT$(" ",8*N)
FOR I=0 TO N-1
   LET X$(8*I+1:8*I+8)=PACKDBL$(A(I+1))
NEXT I
PRINT TEST(N,X$)
END

EXTERNAL  FUNCTION TEST(N,X$)
OPTION CHARACTER BYTE
ASSIGN "sample.dll","sum",FPU
END FUNCTION

extern "C"  __declspec(dllexport) double sum(int n,double a[])と書いても同じです。
OPTION CHARACTER BYTEを忘れずに書いてください。
REPEAT$でバファーを確保して部分文字列で書き込んでいます。
double型は8バイトなのでそれに個数分のバッファーを用意します。
変数Nで個数を与えています。

なお、2次元配列を渡したい時は、2次元配列を1次元配列化して渡します。
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) double sum(int n,int m,double *a)
{
    double s=0.0;
    int i,j;
    for(i=0; i<n; i++)
        for(j=0; j<m; j++)
            s+=a[i*m+j];
    return s;
}

OPTION CHARACTER BYTE
LET N=100
LET M=50
DIM A(N,M)
FOR I=1 TO N
   FOR J=1 TO M
      LET A(I,J)=RND*100
   NEXT J
NEXT I
LET X$=REPEAT$(" ",8*N*M)
FOR I=0 TO N-1
   FOR J=0 TO M-1
      LET X$(8*(I*M+J)+1:8*(I*M+J)+8)=PACKDBL$(A(I+1,J+1))
   NEXT  J
NEXT I
PRINT TEST(N,M,X$)
END

EXTERNAL  FUNCTION TEST(N,M,X$)
OPTION CHARACTER BYTE
ASSIGN "sample.dll","sum",FPU
END FUNCTION

DLLから2個以上のdouble型を受け取りたい時は
--------------------------------------------------------------------
                     sample.cpp


#include <cmath>

extern "C"  __declspec(dllexport) void csin(double *x,double *y,double *re,double *im)
{
    *re=sin(*x)*cosh(*y);
    *im=cos(*x)*sinh(*y);
}

LET X$=PACKDBL$(PI/4)
LET Y$=PACKDBL$(2)
LET R$=REPEAT$(" ",8)
LET I$=REPEAT$(" ",8)
CALL TEST(X$,Y$,R$,I$)
PRINT UNPACKDBL(R$);UNPACKDBL(I$)
END

EXTERNAL  SUB TEST(X$,Y$,R$,I$)
ASSIGN "sample.dll","csin"
END SUB

BASIC側ではR$,I$で受け取るのでREPEAT$でバファーを確保し
SUBとして定義します。

double型の1次元配列も受け渡しできます。
--------------------------------------------------------------------
                        dft.cpp


#include <cmath>
#include <omp.h>

#define PI 3.14159265358979323846
using namespace std;

extern "C" __declspec(dllexport) void dft(int m, double *xr, double *xi,double *yr,double *yi,int sw)
{
    double p,rr,ii;
    int i,j;
    p = 2.0*PI/(double)m;
    if(sw!=0) p=-p;

    #pragma omp parallel for private(i,j)
    for (j=0; j<m; j++) {
        rr=0.0;
        ii=0.0;
        for (i=0; i<m; i++) {
            rr += xr[i] * cos(p * j * i) - xi[i] * sin(p * j * i);
            ii += xr[i] * sin(p * j * i) + xi[i] * cos(p * j * i);
        }
        yr[j]=rr;
        yi[j]=ii;
    }
}

OPTION CHARACTER BYTE
RANDOMIZE
LET M=100
DIM XR(M),XI(M),YR(M),YI(M)
FOR I=1 TO M
   LET XR(I)=INT(RND*1000)
   LET XI(I)=0
NEXT   I
LET XR$=REPEAT$(" ",M*8)
LET XI$=REPEAT$(" ",M*8)
LET YR$=REPEAT$(" ",M*8)
LET YI$=REPEAT$(" ",M*8)
FOR I=0 TO M-1
   LET XR$(8*I+1:8*I+8)=PACKDBL$(XR(I+1))
   LET XI$(8*I+1:8*I+8)=PACKDBL$(XI(I+1))
NEXT I
CALL DFT(M,XR$,XI$,YR$,YI$,0) !'dft
CALL DFT(M,YR$,YI$,XR$,XI$,1) !'idft
FOR I=0 TO M-1
   PRINT INT(UNPACKDBL(XR$(8*I+1:8*I+8))/M+.5);XR(I+1)
NEXT I
END

EXTERNAL  SUB DFT(M,XR$,XI$,YR$,YI$,SW)
OPTION CHARACTER BYTE
ASSIGN "dft.dll","dft"
END SUB

double型で個数分のバッファーを用意します。

int型も受け渡しできます。
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) void test(int *a,int *b,int *c)
{
    *c=*a * *b;
}

LET X=123456
LET Y=456789
LET X$=DWORD$(123456)
LET Y$=DWORD$(456789)
LET Z$=REPEAT$(" ",4)
CALL TEST(X$,Y$,Z$)
PRINT INT32(Z$);X*Y;MOD(X*Y,2^32)
END

EXTERNAL  SUB TEST(X$,Y$,Z$)
ASSIGN "sample.dll","test"
END SUB

EXTERNAL FUNCTION INT32(S$)
OPTION CHARACTER BYTE
FOR I=1 TO 4
   LET N=N+256^(I-1)*ORD(S$(I:I))
NEXT I
LET INT32=MOD(N+2^31,2^32)-2^31
END FUNCTION

DWORD$でint型バイナリーを渡します。
int型は4バイトです。
受け取ったint型バイナリーはINT32関数で整数値にします。
32bit整数の範囲を超える部分は無視されます。


1000桁モードや有理数モードを使用して
64bit整数型を受け渡しできます。
unsignedもつけられます。
--------------------------------------------------------------------
                     sample.cpp


extern "C"  __declspec(dllexport) void test(int n,long long int *a,long long int *s)
{
    *s=0;
    for(int i=0; i<n; i++) *s+=a[i];
}

OPTION ARITHMETIC RATIONAL
OPTION CHARACTER BYTE
RANDOMIZE
LET N=100
DIM A(N)
LET N$=REPEAT$(" ",8*N)
LET ANS$=REPEAT$(" ",8)
FOR I=0 TO N-1
   LET A(I+1)=INT(RND*2^63)
   LET S=MOD(S+A(I+1),2^63)
   LET N$(8*I+1:8*I+8)=LWORD$(A(I+1))
NEXT I
CALL TEST(N,N$,ANS$)
PRINT INT64(ANS$)
PRINT S
END

EXTERNAL  SUB TEST(NUM,N$,ANS$)
OPTION ARITHMETIC RATIONAL
OPTION CHARACTER BYTE
ASSIGN "sample.dll","test"
END SUB

EXTERNAL FUNCTION LWORD$(N)
OPTION ARITHMETIC RATIONAL
OPTION CHARACTER BYTE
DIM A$(8)
IF N<0 THEN LET N=N+2^64
FOR I=1 TO 8
   LET A$(I)=CHR$(MOD(N,256))
   LET N=INT(N/256)
NEXT I
LET LWORD$=A$(1)&A$(2)&A$(3)&A$(4)&A$(5)&A$(6)&A$(7)&A$(8)
END FUNCTION

EXTERNAL FUNCTION INT64(S$)
OPTION ARITHMETIC RATIONAL
OPTION CHARACTER BYTE
FOR I=1 TO 8
   LET N=N+256^(I-1)*ORD(S$(I:I))
NEXT I
IF N>2^63 THEN LET N=N-2^63
LET INT64=N
END FUNCTION

long long int型は64bitの整数で8バイトです。
それに個数分のバッファーを用意して1次元配列を渡します。
受け取りはlong long int型1つなので8バイト分のバッファーを用意しておきます。
long long int型はBASIC側ではサポートされていないので自前で関数を用意します。


複素数型でも受け渡しできるようです。
--------------------------------------------------------------------
                          cexp.cpp


#include <complex>
using namespace std;

extern "C"  __declspec(dllexport) void cexp(complex<double>*x,complex<double>*y)
{
    *y=exp(*x);
}

OPTION ARITHMETIC COMPLEX
OPTION CHARACTER BYTE
LET X=COMPLEX(.5,.5)
LET XX$=REPEAT$(" ",16)
LET YY$=REPEAT$(" ",16)
LET XX$(1:8)=PACKDBL$(RE(X))
LET XX$(9:16)=PACKDBL$(IM(X))
CALL CEXP(XX$,YY$)
PRINT UNPACKDBL(YY$(1:8));UNPACKDBL(YY$(9:16))
PRINT EXP(X)
END

EXTERNAL  SUB CEXP(XX$,YY$)
OPTION ARITHMETIC COMPLEX
OPTION CHARACTER BYTE
ASSIGN "j:\src\cexp.dll","cexp"
END sub

complex型はdouble型2個使用するため、バッファーサイズは
倍の16バイトになります。


double型の引数で受け渡しをする時は
--------------------------------------------------------------------
                          csin.cpp


#include <complex>
using namespace std;

extern "C"  __declspec(dllexport) void csin(double *xr,double *xi,double *yr,double *yi)
{
    complex<double> x,y;
    x=complex<double>(*xr,*xi);
    y=sin(x);
    *yr=y.real();
    *yi=y.imag();
}


char型でも数値を渡せます。
--------------------------------------------------------------------
                       sample.cpp


#include <cstdlib>
using namespace std;

extern "C"  __declspec(dllexport) double test(char *a)
{
    double k;
    k=atof(a);
    // k=strtod(a,NULL);
    return k;
}

OPTION CHARACTER BYTE
LET A=SQR(3)
PRINT TEST(STR$(A));A
END

EXTERNAL  FUNCTION TEST(N$)
OPTION CHARACTER BYTE
ASSIGN "sample.dll","test",FPU
END FUNCTION

数値変数からはSTR$を使って数値文字列を渡します。
指数表現(1E+5など)も可能です。
有理数モードや複素数モードでは注意が必要です。

有理数モードでは
LET A=1/3
PRINT STR$(A)
END

複素数モードでは
LET A=COMPLEX(1,1)
PRINT STR$(A)
END

"/"や"("などの文字記号が入る場合があり、そのままDLLへ渡すと内部エラーを引き起こします。
int型ならatoi関数、long型ではatol関数で数値に変換できます。
 

Re: VC++によるDLLファイルの作り方

 投稿者:しばっち  投稿日:2019年12月 1日(日)15時50分20秒
  > No.4746[元記事へ]

char型を受け取るときは
--------------------------------------------------------------------
                       sample.cpp


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

extern "C"  __declspec(dllexport) int test(char *a,char *b)
{
    int i;
    string s;
    s=a;
    for(i=0;i<4;i++) s+=a;
    if(strlen(b)>=s.length()) strcpy(b,s.c_str());
    return (int)s.length();
}

OPTION CHARACTER BYTE
LET X$=REPEAT$(" ",1000)
!!!LET X$=REPEAT$(" ",20)
LET N$="0123456789"
LET L=TEST(N$,X$)
PRINT X$(1:L)
END

EXTERNAL  FUNCTION TEST(N$,X$)
OPTION CHARACTER BYTE
ASSIGN "sample.dll","test"
END FUNCTION

char型からはstring型へ代入できます。
string型からchar型へはメンバー関数を使用し、strcpy関数で代入します。又はstrcpy_s関数を使います。
バッファーサイズが不足していてもstrcpy関数は問答無用なので、バッファサイズを確認します。
又はBASIC側で十分余裕を持ってバッファを確保しておくことです。


C/C++のライブラリーを利用したい時、それがVC++用ならVisual Studio(IDE)を起動して
プロジェクト・ソリューションの読み込みからソリューションファイル(slnファイル)を読み込みます。
ソリューションエクスプローラからプロジェクトを選択し右クリックからビルドを選べばライブラリー
のビルド(コンパイル)ができます。
※slnファイルはVisual Studioのバージョン毎の種類がありますが、より上位版のVisual Studioなら読み込みできます。

ライブラリーを使用してコンパイルするにはインクルードファイルを/I オプションで、ライブラリーファイル(libファイル)も
必要なら指定します。さもないとコンパイルエラーになります。
--------------------------------------------------------------------
                      isprime.cpp


#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/miller_rabin.hpp>

using namespace boost::multiprecision;
using namespace std;

extern "C" __declspec(dllexport) int isprime(char *x,int num)
{
    cpp_int n;
    n.assign(x);
    if (n==2) return 1;
    bool is_prime = miller_rabin_test(n, num);
    return (is_prime ? 1:0);
}

OPTION ARITHMETIC RATIONAL
OPTION CHARACTER BYTE
FOR I=1 TO 1000
   LET N$=STR$(2^I-1)
   IF ISPRIME(N$,50)=1 THEN PRINT I;2^I-1
NEXT I
END

EXTERNAL FUNCTION ISPRIME(N$,NUM) !' num=試行回数
OPTION ARITHMETIC RATIONAL
OPTION CHARACTER BYTE
ASSIGN "isprime.dll","isprime"
END FUNCTION

メルセンヌ素数(2^n-1)
n=2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941...

boostライブラリー(ヘッダーライブラリー。ヘッダーをインクルードするだけで使用できます。libファイルはありません)を使用した例です。
https://www.boost.org

/I オプションでインクルードパスを指定します。
cl /LD /O2 /Ox /Ot /Qpar /EHsc isprime.cpp /I J:\boost_1_71_0

char型をDLLへ渡して、多倍長整数型 cpp_intに代入しています。
結果をint型で返すためreturn文を使用し、そのためFUNCTION文で定義します。


※なお、libファイルには2種類あります。(静的リンクlibと動的リンクlib)
静的リンクなら実行時、別途dllを必要としませんが、実行ファイルサイズが大きくなります。
動的リンクだと実行時、別途dllを必要としますが、実行ファイルサイズを小さくできます。

http://itdoc.hitachi.co.jp/manuals/3000/30003D0800/GD080284.HTM
https://ja.wikipedia.org/wiki/動的リンク


c/c++ソース整形ツール astyleが利用できます。
http://astyle.sourceforge.net
(コマンドプロンプトを起動して astyle *.cppのように打ち込みます)

生成されたdllファイルはupxで圧縮できます。(※exeファイルも圧縮できます)
https://upx.github.io
(コマンドプロンプトを起動して upx *.dllのように打ち込みます)

※Windows版十進BASIC.exeもupx.exeで圧縮済のようです。
 

戻る