1、概要
Excel VBA から FFTW の DLL 'libfftw3-3.dll'
を直接呼び出そうと色々やってみたが、ランタイムエラー'49' が出てどうしてもうまく行かない。
ならば Excel → DLL(今回作成) → 'libfftw3-3.dll' の様に仲介する DLL
を作成したら、問題解決出来たのでその方法を記しておきます。
Excel から FFTW(高速フーリエ変換)を使えるようにしてみたい方は、DLL の作成に是非挑戦してみてください。
Excel の分析ツールに付属するフーリエ解析(FFT) は、 データ数が 4096 までの制限があったり2の整数乗でないと解析できませんが、FFTW
はその制限が無く2の整数乗でなくても解析できて、しかも Cooley, Tukey の FFT アルゴリズムより高速に動作します。
FFTW がどんなものかは http://www.fftw.org を参照ください。
1-1、開発環境
・ OS : Windows7 Professional SP1、64bit版
・ Office : Office 2010、32bit版又は64bit版
・ Visual Studio : Visual Studio Community 2017
2、FFTW の DLL を入手
・ Windows 版のダウンロードページ http://www.fftw.org/install/windows.html から、使っている Excel が 32bit の場合は 32bit version の DLL、64bit の場合は 64bit version の DLL をダウンロードする。(自分の環境に合わせてダウンロードするファイルを選択する)
3、仲介 DLL を作成
3-1、LIB ファイルを作成
Visual Studio のリンカーに指示する LIB ファイルが必要なので作成する。
注)64bit 版をダウンロードした方は、フォルダ名を 'fftw-3.3.5-dll32' から 'fftw-3.3.5-dll64' に読み替えて作業して下さい。 また 32bit 版と違う部分は、文中の注意書きの指示に従ってください。
・ダウンロードした fftw-3.3.5-dll32.zip を解凍、'fftw-3.3.5-dll32' というフォルダが出来たら、作業しやすい場所へ移動する。
ここでは 'C:\' に移動した。
・ Windows のスタートメニューから、すべてのプログラム → Visual Studio 2017 → Visual Studio Tools → 開発者コマンド プロンプト for VS 2017 を起動する。
・ 'cd \fftw-3.3.5-dll32' と入力し Enter キーを押す、下図は Enter キーを押した後の画面。
・ 'lib /def:libfftw3-3.def' と入力し Enter キーを押す、ファイル 'libfftw3-3.lib' が出来たらOK。
注) 64bit 版の場合は、'lib /def:libfftw3-3.def /machine:x64' と入力し Enter キーを押す。
3-2、Visual Studio でDLL を作成する
新規プロジェクトの作成
・ Visual Studio を起動し、ファイル(F) → 新規作成(N) → プロジェクト(P) を選択する。
・ 'Visual C++' の 'Windows デスクトップ' を選び、'ダイナミックリンクライブラリ(DLL)' を選択する。
・ プロジェクトの '名前(N)' と '場所(L)' を入力し、OKボタンを押す。
→ ここでは 名前:fftw-vbif、場所:C:\work とした。
・下図はOKボタンを押した後の画面。
fftw-vbif.cpp のコード書き換え
・fftw-vbif.cpp のコードを以下のように書き換える。
// fftw-vbif.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。
//
#include "stdafx.h"
#include "..\fftw3.h"
#pragma comment(lib, "libfftw3-3.lib")
void __stdcall vb_fftw_dft_1d(int NV,double *in,double *out, int sign, unsigned flags){
fftw_plan p;
// FFTW プラン作成
p = fftw_plan_dft_1d(NV, (double(*)[2])in, (double(*)[2])out, sign, flags);
// FFTW 実行
fftw_execute(p);
// FFTW プラン解放
fftw_destroy_plan(p);
}
def ファイルの作成
・プロジェクト(P) → 新しい項目の追加(W) を選択する。
Visual C++ の 'コード'、モジュール定義ファイル(.def) を選択する。
'名前(N)' を入力し、OKボタンを押す。 → ここでは 名前:fftw-vbif とした。
・下図はOKボタンを押した後の画面。
・fftw-vbif.def のコードを以下のように書き換える。
EXPORTS
vb_fftw_dft_1d @1
FFTW ライブラリの取り込み
・FFTW のライブラリをプロジェクトへ取り込む。
先ほど解凍と作成した LIB ファイルを、 'c:\fftw-3.3.5-dll32' から、下記の2つのファイルを拾ってプロジェクトへコピーする。
→ ここでは、'c:\work\fftw-vbif' へコピーした。
fftw3.h
libfftw3-3.lib
・下図はプロジェクト(ソリューション)フォルダにファイルをコピーした後の画面。
lib ファイルの検索パス追加
注) 64bit 版の DLL を作成する場合、プラットフォーム(P):は 'x64' を選択してから lib ファイルの検索パスを追加してください。
・プロジェクトに 'libfftw3-3.lib ' を入れた場所を教えてあげる。
プロジェクト(P) → fftw-vbif のプロパティ(F) を選択する。
'VC++ ディレクトリ' から、'ライブラリディレクトリ' の編集を選択する。
下図はライブラリディレクトリの編集を選択する前の画面。
・ '$(SolutionDir)' を追加してOKボタンを押す。
下図はOKボタンを押した後、再確認したときの画面。
ビルドする
・ビルド(B) → ソリューションのビルド(B) を実行する。
出力に、========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ========== と出ればビルド成功。
'C:\work\fftw-vbif\Debug' に 'fftw-vbif.dll' が出来ているはずです。
注) 構成(C):は 'Debug' のままだと、Visual Stdio が入っていない環境では動作しません、最終的には 'Release' に直して Build してください。
4、Excel 側で使う準備
最初に Excel のサンプルプログラムや必要なファイルをここからダウンロード(file size:1,793kByte)してください。
注) 自分で DLL を作らず、まずはサンプルで試してみたい方は、Microsoft Visual C++2017 ランタイムが必要ですので、別の記事
'3-1、ランタイムライブラリの入手'を実施してから下記の設定をしてください。
4-1、DLL の環境設定
・Excel 側から DLL を呼び出せるようにパソコンの環境を整える。
先ほどダウンロードしたファイルを解凍すると、下記5つのファイルがあります。
1、fftw_sample.xlsm → Excel のサンプルプログラム
2、x86\fftw-vbif.dll → 32bit 版、3項で作った DLL
3、x86\libfftw3-3.dll → 32bit 版、fftw.org から入手したファイル、ここでは double 版の DLL を使用。
4、x64\fftw-vbif.dll → 64bit 版、3項で作った DLL
5、x64\libfftw3-3.dll → 64bit 版、fftw.org から入手したファイル、ここでは double 版の DLL を使用。
注) 32bit 版の DLL は 'x86'、64bit 版の DLL は 'x64' という名前のフォルダに入ってます。
・Windows OS が 32bit 版の場合:
x86\fftw-vbif.dll, x86\libfftw3-3.dll のファイルを、'C:\Windows\System32' へ入れる。
・Windows OS が 64bit 版の場合:
Excel が 64bit 版の場合は、x64\fftw-vbif.dll, x64\libfftw3-3.dll のファイルを 'C:\Windows\System32' へ入れる。
Excel が 32bit 版の場合は、x86\fftw-vbif.dll, x86\libfftw3-3.dll のファイルを 'C:\Windows\SysWOW64' へ入れる。
【上記と違うやり方】
・注) 上記の方法で DLL を登録した方は、ここの項目を飛ばして次へ行ってください。
Windows のシステムフォルダへ入れるのが嫌な人は、環境変数の path に上記2つの DLL を入れたフォルダパスを追加する方法もあります。
Windows7 の場合は、マイコンピュータのプロパティ → システムの詳細設定 → 詳細設定 → 環境変数(N) から path の環境設定を編集してください。
例えば c:\work に入れた場合は、変数値(V) の最後に ';c:\work' を足してOKする。
・Excel 側から 'vb_fftw_dft_1d' の DLL をコールしたとき、下記のエラーが出る場合は環境設定が正しく出来てないので、ここの内容を良く見直してください。 Microsoft Visual C++ 再頒布可能パッケージをインストールしていない場合も同じエラーが出ます。
5、Excel で FFTW を使う
5-1、DLL のコール方法
手続き(関数)宣言部分
・VBA 側は Declare ステートメントを使って Sub 又は Function を作成しますが、渡す引数の型に注意してください。
下記VBA コードでは、変数 n,sign は本来 C言語 の int 型なので、VBA では 4byte 長の long 型に書き換えている。
変数 flags は、本来 C言語 の undigned int 型だが、VBA にはその型無いので、とりあえず long 型と書いている。
→ FFTW 側はこの変数、符号ビット部分まで使ってなさそうだが、もし渡す場面がある場合 VBA 側はマイナスに変換して渡す必要がある。
変数 in,outは、本来 C言語 の double のポインタ型 だが、VBA にはその型無いので Any 型と書いておく。
#If VBA7 And Win64 Then
Declare PtrSafe Sub vb_fftw_dft_1d Lib "fftw-vbif.dll" (ByVal NV As Long, fc As Any, cfs As Any, ByVal sign As Long, ByVal flags As Long)
#Else
Declare Sub vb_fftw_dft_1d Lib "fftw-vbif.dll" (ByVal NV As Long, fc As Any, cfs As Any, ByVal sign As Long, ByVal flags As Long)
#End If
・
64bit 版の Excel で実行する場合は、Declare ステートメントの次に PtrSafe 属性を付けて宣言してください。
#If 文中の VBA7 は Office2010 以降か Office2007 以前か、Win64 は Excel が 64bit 版か 32bit
版か調べられる定数です。
上記の様に書いておくと、Excel のビット数やバージョンが何であれ、共通のコードで実行出来るようになります。
手続き(関数)コール部分
・VBA のサンプルコードを下記に示す。
手続きで宣言した n,sign,flags 部分は、 FFTW側、VBA 側とも受け渡しに問題が無いので普通に書けばよい。
手続きで宣言した in,out 部分は、double のポインタ型なので、変数の先頭アドレスを渡すという意味で in(0) のように書く。
→ ただし正確に言うと FFTW側は Complex 型で受けとるので、VBA 側の変数も Complex 型で宣言しておく必要がある。
Call vb_fftw_dft_1d(n, in(0), out(0), FFTW_FORWARD, FFTW_ESTIMATE)
Complex 型の宣言方法
・Type ステートメントでユーザー定義型を作成する。
下記サンプルでは、in,out とも 1000ポイント分 complex 型で領域確保。
また FFTW の DLL を使う場合、受け渡しする変数の型を気にするので、VBA のコードは Option Explicit 宣言をしておいた方が良いでしょう。
Option Explicit
Type complex
real As Double
image As Double
End Type
Dim in(1000) As complex
Dim out(1000) As complex
5-2、Excel サンプルコードの説明
・fftw_sample.xlsm は Sin 波を生成して、FFTW にかけられるサンプルコードです。
ピンクの網掛けのセル、サンプリング周波数(B6セル)と生成周波数(B7セル)を入力して波形生成ボタンを押すと、指定のサンプリング周期で波形が生成され結果を B,C列に格納します。
続けて FFTW 実行ボタンを押すと FFTW が実行され、結果を E,F 列に格納します。
サンプリング周波数と生成周波数の関係で、動的に波形ポイント数が変わりますが、各ボタンを押した後グラフ範囲が自動で変わるようにプログラムされてます。
これだと、ふうーんで終わってしまいますので、次回はもうちょっと使えるサンプルを作りたいと思ってます。
6、余談
下記は動かなかったサンプルです
・今回作成した fftw-vbif.dll では、fftw_plan_dft_1d → fftw_execute → fftw_destroy_plan を実行して DLL を抜けますので、毎回 fftw_plan を捨てていることになります。(それでも
FFTW の応答はとても早いですが....)
これではもったいないと思い、最初は下記のコードを書いて試してたのですが、VBA でステップ実行すると fftw_plan_dft_1d の呼出しはOK、次に fftw_execute を実行すると Excel が落ちてしまいます。
DLL 側で fftw_plan のメモリを確保して関数抜けてるのでそこが怪しいのと、VBA 側も LongPt
型を宣言して値を受け取ろうとしましたが、型が合っているかどうか怪しいです。
DLL 側コード(下記は動かなかったサンプルです)
// DLL 側コード
fftw_plan __stdcall vb_fftw_plan_dft_1d(int NV, double *in, double *out, int sign, unsigned flags) {
fftw_plan p;
p = fftw_plan_dft_1d(NV, (double(*)[2])in, (double(*)[2])out, sign, flags);
return(p);
}
void __stdcall vb_fftw_execute(fftw_plan p) {
fftw_execute(p);
}
void __stdcall vb_fftw_destroy_plan(fftw_plan p) {
fftw_destroy_plan(p);
}
VBA 側コード(下記は動かなかったサンプルです)
' VBA 側コード
' Complex 型定義
Type complex
real As Double
image As Double
End Type
' 関数宣言
Declare Function vb_fftw_plan_dft_1d Lib "fftw-vbif.dll" _
(ByVal n As Long, in As Any, out As Any, ByVal sign As Long, ByVal flags As Long) As LongPtr
Declare Sub vb_fftw_execute Lib "fftw-vbif" (p As LongPtr)
Declare Sub vb_fftw_destroy_plan Lib "fftw-vbif" (p As LongPtr)
' FFTW I/F 用変数宣言
Dim in(1000) As complex, out(1000) As complex
Dim p As LongPt
Dim n As Long
' FFTW プラン作成
p = vb_fftw_plan_dft_1d(n, in(0), out(0), FFTW_FORWARD, FFTW_ESTIMATE)
' FFTW 実行
Call vb_fftw_execute(p)
' FFTW プラン解放
Call vb_fftw_destroy_plan(p)
7、参考文献、参考 URL
1、中村尚五著:ビギナーズ デジタルフーリエ変換,東京電機大学出版局(1989)
2、坂巻佳壽美著:見てわかる デジタル信号処理,工業調査会(1998)
3、http://www.fftw.org(本家 FFTW のサイト)
4、http://skomo.o.oo7.jp/f46/hp46_1.htm(同じような DLL の作成方法について書かれてます)