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 文字列の受け渡しを追加しました。
Excelの列のアルファベット-数値変換
ThisWorkbook.Worksheets(1).Columns("AA").Column
===>27
ThisWorkbook.Worksheets(1).Columns(28).Address(ColumnAbsolute:=False)
===>AB:AB
ThisWorkbook.Worksheets(1).Cells(1, 28).Address(RowAbsolute:=False, ColumnAbsolute:=False)
===>AB1
数値→アルファベットへの変換の場合、余計な部分を削除する必要がある。
scpコマンドで秘密鍵を使ってファイルをコピーする
普段使用しているPCから、他のPCにファイルをコピーしようとする場合、scpコマンドを使用しますが、コピー先のPCと使用中のPCのユーザー名が異なる場合、明示的に接続先のユーザー名の指定が必要となる場合の指定方法です。
scp -i 秘密鍵ファイル コピーするファイル名 コピー先のユーザー名@コピー先のアドレス:コピー先のファイルを保存するディレクトリ
暗くなると点灯するLED回路を作ってみた Part2
暗くなると点灯するLED回路の、抵抗値を見直しました。
点灯、消灯するタイミングおよび、点灯時の明るさを調整した結果、最終的な回路は、以下のようになりました。
フル充電したエネループ(単4×4本)で、この回路で1ヶ月くらいは使えるようでした。
暗くなると点灯するLED回路を作ってみた
昨年12月の事、妻が外に置いていたプランターを、寒さ対策のために家の中(廊下の片隅)に持ち込んだ。
日頃から廊下の電灯を点けずに(暗い中を)歩く私にとって、本来何もないはずの所に物があると、それを蹴飛ばしてしまう可能性は非常に高い。
電灯を点けて歩けば何も問題がないのだが、昔からの習慣でなかなかそうできずにいた。
そこで、プランターが見えるように(というか、そこにプランターがあることを意識させるため)手持ちのLEDを点灯させようという結論に。
手持ちのエネループ4本と抵抗、LED、スイッチを繋げて完成。しばらくは、それで過ごしていたが、朝と夜にスイッチの操作が面倒くさくなってきた。
なんとかこの手間を省きたい。
で、「明るさに応じて、勝手に点灯、消灯するようにしてしまえ」ということになり、調べてみると、明るさはCDSで検知して、それを元にトランジスタでLEDに流す電流をオン、オフすればいいらしい。
回路は、こんな感じ。
抵抗は、手持ちの物を使ったので、ひとつでいいところが並列になっていたりする。
LEDと直列に接続している抵抗は、電源のエネループ4本がフル充電時5.8Vだったので、そこにLEDの定格20mAを流すとすると
5.8V / 20mA = 290Ω
手持ちの抵抗に近いのがないので、460Ωを並列にして230Ωで妥協。
5.8V / 230Ω = 25mA
許容範囲内でしょう。
(ベース側の抵抗が、いい加減で大きめにしてあるので、実際にはそんなに流れていないのは、点灯したLEDの明るさからみて明らか)
電池とベース間の抵抗(100kΩ)や、CDSと並列に接続した抵抗(100kΩ、46kΩ)は、結構いい加減ベースで。最終的には、実際にLEDを点灯させてみて、点灯消灯する明るさを見ながら調整して決定。時間があったらもう少し抵抗値を下げる方向で調整してみよう。
春になって暖かくなれば使わなくなるので、基盤に半田付けなどせず、ブレッドボードに部品を差し込んでおしまい。
電池は最終仕様前の抵抗値で使って、10日くらいで暗くなって来たので、1週間くらいは使えそうかな?
完成した全体は、こんな感じ
消灯状態
点灯状態
配線状態