空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

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

ExcelVBAでもそこそこの事は出来るけれど、C++で処理したい事があって「DLLにしちゃえ」と思ったはいいが、いろいろと忘れて(いや、覚えてないから忘れられないだろうorz)作るのが大変だったので、まとめてみることにした。

とりあえず、今回は、何もしない、何も返さない関数を含むDLLをビルドして、VBAから呼び出して、エラーが出ない事を確認するまで。

何らかの値を渡して、何らかの処理をした結果を返してもらうのは、次回以降に・・・

  1. 開発環境
  2. DLLの作り方(基礎編)
  3. 使い方(Excelからの呼び出し方)

開発環境

OS:Windows 10 Professional
DLL:Microsoft Visual Studio Community 2017(Microsoft Visual C++ 2017)
VBAMicrosoft Office Personal 2013(Microsoft Excel 2013)

DLLの作り方(基礎編)

プロジェクトの作成

1.Visual Studioを起動
2.「ファイル」-「新規作成」-「プロジェクト」をクリック

f:id:Z1000S:20180415145858j:plain


3.「Visual C++」-「Windows デスクトップ」-「ダイナミック リンク ライブラリ (DLL)」をクリック

f:id:Z1000S:20180415145927j:plain


4.「名前」「場所」「ソリューション名」を指定し、「OK」をクリック
「ソリューションのディレクトリを作成」にチェックが入っていないと、ソリューション名は指定できないので、作成する場合はチェックする。
ソリューションのディレクトリを作成するかどうかは任意。今回は作成した状態で説明しています。作成しない場合は、適宜読み替えて下さい。

サイトに寄っては、Visual Studioを「管理者として実行しないとDLLが作れない」と書かれている所もあるようですが、私の場合は通常の起動でも作成出来ました。

f:id:Z1000S:20180415150056j:plain

モジュール定義ファイル(defファイル)

VBAからアクセスしやすいように、モジュール定義ファイルを追加します。
1.「プロジェクト」-「新しい項目の追加」をクリック。

f:id:Z1000S:20180415150120j:plain


2.「インストール済み」-「Visual C++」-「コード」-「モジュール定義ファイル (.def)」をクリック

f:id:Z1000S:20180415150143j:plain


3.名前をプロジェクト名.def(AccessibleFromVBA.def)として、「追加」ボタンをクリック

f:id:Z1000S:20180415150201j:plain

f:id:Z1000S:20180415150232j:plain

プロジェクトの設定

プロジェクトのプロパティを変更します。

 1.「プロジェクト」-「プロパティ」をクリック

f:id:Z1000S:20180415150254j:plain

2.構成を"アクティブ(Debug)"から"すべての構成"に変更します

 

3.「構成プロパティ」-「全般」-「プロジェクトの既定値」-「文字セット」を"マルチ バイト文字セット" "Unicode 文字セットを使用する"に変更します。(2019/9/25 訂正)

f:id:Z1000S:20190925144310j:plain

4.「構成プロパティ」-「全般」-「全般」-「出力ディレクトリ」を"$(SolutionDir)$(Configuration)\"から"$(ProjectDir)$(Configuration)\"に変更します。
(この操作は必須ではありません。)

f:id:Z1000S:20180415164739j:plain

5.「構成プロパティ」-「リンカー」-「モジュール定義ファイル」の値が、先程作成したモジュール定義ファイル名(AccessibleFromVBA.def)になっているか確認します。

f:id:Z1000S:20180415164817j:plain

6.「適用」ボタンをクリック

 

プリコンパイル済みヘッダーは、使わなくてもいいと思ったが、とりあえずそのまま使用することに。

関数を追加してみる

まずは、何もしない関数を追加してみる。

ヘッダーファイル

デフォルトでヘッダーファイルが無いので、追加する。
1.「プロジェクト」-「新しい項目の追加」をクリック。

f:id:Z1000S:20180415165036j:plain


2.「インストール済み」-「Visual C++」-「コード」-「ヘッダー ファイル (.h)」をクリック


3.名前をプロジェクト名.h(AccessibleFromVBA.h)として、「追加」ボタンをクリック

f:id:Z1000S:20180415165051j:plain

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();
}

f:id:Z1000S:20180415165729j:plain

赤い波線が出て来るが気にしない。

ソースファイル

関数の宣言が出来たら、次は定義の作成。
1.ソリューションエクスプローラーで、AccessibleFromVBA.cppをクリック(or ダブルクリック)
2.前項で作ったヘッダーファイルのインクルードを確認。無ければ追記。

f:id:Z1000S:20180415170741j:plain


3.宣言に合わせて関数を作成。今回は何もしないので、関数の中身は"return;"のみ

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

ACCESSIBLEFROMVBA_API void WINAPI DoNothing()
{
    return;
}

f:id:Z1000S:20180415170804j:plain

モジュール定義ファイル

1.ソリューションエクスプローラーで、AccessibleFromVBA.defをクリック(or ダブルクリック)

f:id:Z1000S:20180415170825j:plain


2."LIBRARY"の後ろに、DLL名を追記。
3."EXPORTS"と追記。
4.エクスポートする関数名を追記。

f:id:Z1000S:20180415170901j:plain


以前は、関数名の後ろに"@"と数字を付けていたけど、特定の場合を除き付けることはお勧めではないようです。

@ordinal を使用して、関数名ではなく番号が DLL のエクスポート テーブルに格納されるように指定できます。 多くの Windows DLL で、レガシ コードをサポートするために序数がエクスポートされます。 DLL のサイズを最小限に抑えるのに役立つため、16 ビットの Windows コードでは序数を使用することが一般的でした。 レガシ サポートのために DLL のクライアントで必要な場合を除き、関数を序数でエクスポートすることはお勧めしません。 .LIB ファイルには序数と関数のマッピングが含まれているため、DLL を使用するプロジェクトでは通常と同様に関数名を使用できます。
EXPORTS
ビルド

1.「ビルド」-「AccessibleFromVba のビルド」をクリック

f:id:Z1000S:20180415171016j:plain


2.出力ウィンドウに、"ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ"と表示されればOK

f:id:Z1000S:20180415171039j:plain


出力先のフォルダに、"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

f:id:Z1000S:20180415201403j:plain

エラーの発生もなく、無事終了。

次回予告

今回の内容では、投げっぱなしの処理しか出来ないので、次回はデータの受け渡しに関連する事をまとめる予定。

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

それ以降は、

  • DLLからエクスポートされる関数の確認方法
  • DLLのデバッグ方法

などをまとめる予定。

いつ頃までに、まとめられるかは、不明・・・
気分次第?

 

2018/6/27

やっと、その2を追加しました。
予告までしたのに、遅いし、予告した内容も一部次に先送りしてるし・・・orz

z1000s.hatenablog.com

2019/10/22

その3 文字列の受け渡しを追加しました。

z1000s.hatenablog.com

z1000s.hatenablog.com

z1000s.hatenablog.com

z1000s.hatenablog.com

z1000s.hatenablog.com

z1000s.hatenablog.com