ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その1
ExcelのVBAでもそこそこの事は出来るけれど、C++で処理したい事があって「DLLにしちゃえ」と思ったはいいが、いろいろと忘れて(いや、覚えてないから忘れられないだろうorz)作るのが大変だったので、まとめてみることにした。
とりあえず、今回は、何もしない、何も返さない関数を含むDLLをビルドして、VBAから呼び出して、エラーが出ない事を確認するまで。
何らかの値を渡して、何らかの処理をした結果を返してもらうのは、次回以降に・・・
開発環境
OS:Windows 10 Professional
DLL:Microsoft Visual Studio Community 2017(Microsoft Visual C++ 2017)
VBA:Microsoft Office Personal 2013(Microsoft Excel 2013)
DLLの作り方(基礎編)
プロジェクトの作成
1.Visual Studioを起動
2.「ファイル」-「新規作成」-「プロジェクト」をクリック
3.「Visual C++」-「Windows デスクトップ」-「ダイナミック リンク ライブラリ (DLL)」をクリック
4.「名前」「場所」「ソリューション名」を指定し、「OK」をクリック
「ソリューションのディレクトリを作成」にチェックが入っていないと、ソリューション名は指定できないので、作成する場合はチェックする。
ソリューションのディレクトリを作成するかどうかは任意。今回は作成した状態で説明しています。作成しない場合は、適宜読み替えて下さい。
サイトに寄っては、Visual Studioを「管理者として実行しないとDLLが作れない」と書かれている所もあるようですが、私の場合は通常の起動でも作成出来ました。
モジュール定義ファイル(defファイル)
VBAからアクセスしやすいように、モジュール定義ファイルを追加します。
1.「プロジェクト」-「新しい項目の追加」をクリック。
2.「インストール済み」-「Visual C++」-「コード」-「モジュール定義ファイル (.def)」をクリック
3.名前をプロジェクト名.def(AccessibleFromVBA.def)として、「追加」ボタンをクリック
プロジェクトの設定
プロジェクトのプロパティを変更します。
1.「プロジェクト」-「プロパティ」をクリック
2.構成を"アクティブ(Debug)"から"すべての構成"に変更します
3.「構成プロパティ」-「全般」-「プロジェクトの既定値」-「文字セット」を"マルチ バイト文字セット" "Unicode 文字セットを使用する"に変更します。(2019/9/25 訂正)
4.「構成プロパティ」-「全般」-「全般」-「出力ディレクトリ」を"$(SolutionDir)$(Configuration)\"から"$(ProjectDir)$(Configuration)\"に変更します。
(この操作は必須ではありません。)
5.「構成プロパティ」-「リンカー」-「モジュール定義ファイル」の値が、先程作成したモジュール定義ファイル名(AccessibleFromVBA.def)になっているか確認します。
6.「適用」ボタンをクリック
プリコンパイル済みヘッダーは、使わなくてもいいと思ったが、とりあえずそのまま使用することに。
関数を追加してみる
まずは、何もしない関数を追加してみる。
ヘッダーファイル
デフォルトでヘッダーファイルが無いので、追加する。
1.「プロジェクト」-「新しい項目の追加」をクリック。
2.「インストール済み」-「Visual C++」-「コード」-「ヘッダー ファイル (.h)」をクリック
3.名前をプロジェクト名.h(AccessibleFromVBA.h)として、「追加」ボタンをクリック
4.まずは、おまじない。
#pragma once
extern "C" {
#define ACCESSIBLEFROMVBA_API __declspec(dllexport)
}
#pragma onceについてはこちらを参照。
extern "C"については、ロベールのC++教室 - 第47章 C±± -あたりを見るか、自分でググって下さい。
"#define ACCESSIBLEFROMVBA_API __declspec(dllexport)"については、以下を参照。
DLL のビルド時には通常、エクスポートする関数のプロトタイプやクラスを含むヘッダー ファイルを作成し、そのヘッダー ファイル内の宣言に __declspec(dllexport) を追加します。 コードを読みやすくするために、次のように __declspec(dllexport) 用のマクロを定義して、そのマクロをエクスポートする各シンボルに使います。
__declspec(dllexport) を使った DLL からのエクスポート
#define・・・は、externの{}の外の方がいいかもしれないけど、今回は、C++ & VBA Onlyなのでこれでよしとする。
5.extern "C"の後の{}内に関数を宣言する。
今回は、引数を取らず、何も返さない"DoNothing"という関数を追加してみる。
----- 重 要 -----
戻り値の型と関数名の間にWINAPIを忘れないように。
#pragma once
extern "C" {
#define ACCESSIBLEFROMVBA_API __declspec(dllexport)
ACCESSIBLEFROMVBA_API void WINAPI DoNothing();
}
赤い波線が出て来るが気にしない。
ソースファイル
関数の宣言が出来たら、次は定義の作成。
1.ソリューションエクスプローラーで、AccessibleFromVBA.cppをクリック(or ダブルクリック)
2.前項で作ったヘッダーファイルのインクルードを確認。無ければ追記。
3.宣言に合わせて関数を作成。今回は何もしないので、関数の中身は"return;"のみ
#include "stdafx.h"
#include "AccessibleFromVBA.h"
ACCESSIBLEFROMVBA_API void WINAPI DoNothing()
{
return;
}
モジュール定義ファイル
1.ソリューションエクスプローラーで、AccessibleFromVBA.defをクリック(or ダブルクリック)
2."LIBRARY"の後ろに、DLL名を追記。
3."EXPORTS"と追記。
4.エクスポートする関数名を追記。
以前は、関数名の後ろに"@"と数字を付けていたけど、特定の場合を除き付けることはお勧めではないようです。
@ordinal を使用して、関数名ではなく番号が DLL のエクスポート テーブルに格納されるように指定できます。 多くの Windows DLL で、レガシ コードをサポートするために序数がエクスポートされます。 DLL のサイズを最小限に抑えるのに役立つため、16 ビットの Windows コードでは序数を使用することが一般的でした。 レガシ サポートのために DLL のクライアントで必要な場合を除き、関数を序数でエクスポートすることはお勧めしません。 .LIB ファイルには序数と関数のマッピングが含まれているため、DLL を使用するプロジェクトでは通常と同様に関数名を使用できます。
EXPORTS
ビルド
1.「ビルド」-「AccessibleFromVba のビルド」をクリック
2.出力ウィンドウに、"ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ"と表示されればOK
出力先のフォルダに、"AccessibleFromVba.dll"が出来ているはず。
使い方
Excel
Declare ステートメント
1.構文
[ Public|Private ] Declare Sub name Lib "libname" [ Alias "aliasname" ] [ ( [ arglist ] ) ]
[ Public|Private ] Declare Function name Lib "libname" [ Alias "aliasname" ] [ ( [ arglist ] ) ] [ As type ]
指定項目 | 説明 | 備考 |
name | 関数名 | 前述の例では、DoNothing |
libname | DLL名 | 必要であればパスも含めて指定 |
aliasname | DLL またはコード リソース内のプロシージャの名前 | 多分気にしなくてもOK |
arglist | プロシージャが呼び出されるときにプロシージャに渡される引数を表す変数のリスト | オプション |
type | Function プロシージャによって返される値のデータ型 | オプション。VBAの型を指定 |
DoNothingの例
標準モジュールに、以下の1行を追記。(パスは自分の環境に合わせて下さい。)
Private Declare Sub DoNothing Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\AccessibleFromVBA\Debug\AccessibleFromVBA.dll" ()
以下のように呼び出す。
Public Sub DllCallTest()
'DLLの関数呼び出し
Call DoNothing
Debug.Print "Done."
End Sub
エラーの発生もなく、無事終了。
次回予告
今回の内容では、投げっぱなしの処理しか出来ないので、次回はデータの受け渡しに関連する事をまとめる予定。
- 受け渡しするデータの型について
- 値渡しと参照渡しについて
- 処理する値を渡せるようにすること
- 処理した結果や値を返してもらえるようにすること
- データ型による渡し方の注意点
それ以降は、
- DLLからエクスポートされる関数の確認方法
- DLLのデバッグ方法
などをまとめる予定。
いつ頃までに、まとめられるかは、不明・・・
気分次第?
2018/6/27
やっと、その2を追加しました。
予告までしたのに、遅いし、予告した内容も一部次に先送りしてるし・・・orz
2019/10/22
その3 文字列の受け渡しを追加しました。