ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その2

次回予告までしておきながら、他にVBA関係の記事を書いていたこともあり、既に2ケ月以上が過ぎ、
「いつ頃までに、まとめられるかは、不明・・・」の記載通りになってしまった。

前回も書きましたけど、64bit版のVBAではどうなるかわかりませんので!!!

今回の内容は次の通り。

  1. 受け渡しするデータの型について
  2. 処理する値を渡せるようにすること
  3. 値渡しと参照渡しについて
  4. 処理した結果や値を返してもらえるようにすること
  5. 数値型の場合の受け渡しの例


受け渡しするデータの型について

とりあえず必要なのは、VBAC++のデータ型の対応。
VBA独自の型は、C++に渡せないし、逆にC++にしかない型は、VBAが受け取れない。
(条件付きであれば、例外もあるけど・・・)
VBAの主な型を中心に対応を見てみると次のような感じ。

VBAの型 C++の型 備考
Byte unsigned char
BYTE
Integer short
SHORT
Long int
long
INT
LONG
Single float
Double double
Boolean BOOL
C++の型 VBAの型 備考
HANDLE Long
char
CHAR
Byte 最上位ビットがOFFならばそのまま使用可
unsigned short
WORD
Integer 最上位ビットがOFFならばそのまま使用可
unsigned int
unsigned long
UINT
ULONG
DWORD
Long 最上位ビットがOFFならばそのまま使用可
VBAにあってC++にない型(抜粋)
Currency
Date
Decimal
String
Object
Variant
C++にあってVBAにない型(抜粋)
WCHAR
LONGLONG
ULONGLONG

VBAのString型に対応するC++の型はありません。
ちょっと特殊です。データの受け渡しにはchar型のポインタまたは、BSTR型を使用します。
これについては、次回(?)にでも・・・

処理する値を渡せるようにすること

DLLに何らかのデータ処理をしてもらうためには、必要なデータを渡してあげないと出来ません。
データはVBAの関数と同様にパラメータに引数を渡します。
ただ、C++VBAでは、構文が違うので予め覚えておく必要があります。

値渡しと参照渡しについて

値渡し
引数のアドレスをプロシージャに渡すのではなく引数の値を渡す方法。
VBAでは、ByValを指定する事により値渡しとなる。
参照渡し
引数の値をプロシージャに渡すのではなく引数のアドレスを渡す方法。
VBAでは、デフォルトで参照渡しである。明示的に指定する場合には、ByRefを指定する。

C++の場合

値渡し
データ型の後ろに変数を指定する。
void doAnything(int hoge); intデータ型で、hoge変数
値渡し
データ型の後ろに"*"を付けその後ろに変数を指定する。(ポインタですな。)
void doSomething(long* fuga); longデータ型で、fuga変数

処理した結果や値を返してもらえるようにすること

値を返してもらう方法は、大きく分けて2つ。
一つは、関数の復帰値による方法。
これは、VBAでいえば、ファンクションプロシージャによる復帰値で結果を得る方法と同じ。
C++の場合には、関数名の前に復帰値のデータ型を指定する。
復帰値の型 関数名(パラメータリスト)
もう一つは、渡したパラメータにデータを入れてもらい、返してもらう方法。
こちらは、VBAでいえば、パラメータにByRefを指定し、返してもらう方法と同じ。
どちらを使うかは、状況に応じて使い分ければよろしいかと。
場合によっては、両方というのもありです。

数値型の場合の受け渡しの例

VBAからLongの値を渡して、2倍した結果を返してもらう「GetNumberI」という関数と、
300倍した値を返してもらう「GetNumberI2」を作ってみます。
GetNumberIは、パラメータを値渡しして、復帰値で結果をもらいます。
GetNumberI2は、パラメータを参照渡しし、そのパラメータ値を変更して返してもらいます。

前回のファイルに、書き加えていきます。

まず、ヘッダファイル(AccessibleFromVBA.h)

#pragma once

extern "C"
{
#define ACCESSIBLEFROMVBA_API __declspec(dllexport) 

	ACCESSIBLEFROMVBA_API void WINAPI DoNothing();

	ACCESSIBLEFROMVBA_API int WINAPI GetNumberI(int i);

	ACCESSIBLEFROMVBA_API void WINAPI GetNumberI2(int* pi);
}

f:id:Z1000S:20180627100359j:plain

次に、ソースファイル(AccessibleFromVBA.cpp)

#include "stdafx.h"
#include "AccessibleFromVBA.h"


ACCESSIBLEFROMVBA_API void WINAPI DoNothing()
{
	return;
}

//int型の値を受け取って、int型の復帰値を返す(VBAのファンクションプロシージャ相当)
ACCESSIBLEFROMVBA_API int WINAPI GetNumberI(int i)
{
	return i * 2;
}

//int型の値を受け取って、内部で値を変更して返す(VBAでパラメータをByRef で受け渡しするイメージ)
ACCESSIBLEFROMVBA_API void WINAPI GetNumberI2(int* pi)
{
	*pi *= 300;

	return;
}

f:id:Z1000S:20180627100406j:plain

最後に、モジュール定義ファイル(AccessibleFromVBA.def)

LIBRARY AccessibleFromVba

EXPORTS
	DoNothing
	GetNumberI
	GetNumberI2

f:id:Z1000S:20180627100410j:plain
全て追加したら、プロジェクトをビルドします。

========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

と表示されればOK。
f:id:Z1000S:20180627100414j:plain

呼び出すExcel側は、

Private Declare Function GetNumberI Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\ForTest\AccessibleFromVBA.dll" (ByVal l As Long) As Long

Private Declare Sub GetNumberI2 Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\ForTest\AccessibleFromVBA.dll" (ByRef l As Long)

呼び出してみる

Public Sub DllCallTest2()

    Dim lValue  As Long
    Dim lResult As Long

    lValue = 1000

    lResult = GetNumberI(lValue)

    Debug.Print "GetNumberI:", lValue, lResult

    Debug.Print ""

    Debug.Print "GetNumberI2(Before):", lValue

    Call GetNumberI2(lValue)

    Debug.Print "GetNumberI2(After ):", lValue

End Sub

実行結果は、

call DllCallTest2
GetNumberI: 1000 2000


GetNumberI2(Before): 1000
GetNumberI2(After ): 300000

f:id:Z1000S:20180713081606j:plain

当てにならない次回予告

とりあえず、数値の受け渡しは出来たので、次は、

  • 文字列
  • 配列
  • 構造体

の受け渡しあたりをまとめられたらいいなぁ~