Excelから自作DLLを呼び出す(64ビット版)

# はじめに

自作したライブラリ関数をExcelでも使いたい場合を考えてみました。
ExcelからDLLを呼び出すことができるので、自作したライブラリ関数をDLLにするとよさそうです。
以下は、実際に作ってみたものの紹介です。

# 環境

* Windows 10
* Microsoft Excel for Office 365 MSO (16.0.12527.21230) 64bit
* DMD v2.094.0

# DLLのソースコード
使用する`Excel`が64ビット版のため、DLLモジュールも対応する64ビット版を作成します。
[32ビット版のDLL作成方法](https://wiki.dlang.org/Win32_DLLs_in_D)を参考にソースコードを書きました。

```d:incometax.d
import core.sys.windows.windows;
import core.sys.windows.dll;
import std.algorithm.searching;

__gshared HINSTANCE g_hInst;

extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
	switch (ulReason){
		case DLL_PROCESS_ATTACH:
			g_hInst = hInstance;
			dll_process_attach(hInstance, true);
			break;
		case DLL_PROCESS_DETACH:
			dll_process_detach(hInstance, true);
			break;
		case DLL_THREAD_ATTACH:
			dll_thread_attach(true, true);
			break;
		case DLL_THREAD_DETACH:
			dll_thread_detach(true, true);
			break;
		default:
	}
	return ( true );
}

export extern (Windows)
int calcIncomeTax(int income)
{
	income = income / 1000 * 1000;	// 千円未満の端数金額を切り捨て
	int[] boundary  = [1_950_000, 3_300_000, 6_950_000, 9_000_000, 18_000_000, 40_000_000];
	int[] taxrate   = [5, 10, 20, 23, 33, 40, 45];
	int[] deduction = [0, 97_500, 427_500, 636_000, 1_536_000, 2_796_000, 4_796_000];
	auto index = count!("a < b")(boundary, income);
	return ( income / 100 * taxrate[index] - deduction[index] );
}
```

# ソースコードの説明
[DLL の初期化](https://docs.microsoft.com/ja-jp/cpp/build/run-time-library-behavior?view=msvc-160#initialize-a-dll)にある通り、 DLL の初期化や終了に必要な`DllMain`を記述します。
`calcIncomeTax`関数は、課税所得から所得税を計算する関数です。
Excelから呼び出すことができるよう、`export extern (Windows)`とします。
[count関数](https://dlang.org/phobos/std_algorithm_searching.html#.count)を使うことで`if`分岐を多用せずにスッキリさせてみました。ちなみに`count`関数を使わなければ、以下のような実装例になると思います。

```d:
int calcIncomeTax(int income)
{
	income = income / 1000 * 1000;	// 千円未満の端数金額を切り捨て
	int itax;						// 所得税
	if        ( income < 1_950_000 ){
		itax = income / 100 * 5;
	} else if ( income < 3_300_000 ){
		itax = income / 100 * 10 - 97_500;
	} else if ( income < 6_950_000 ){
		itax = income / 100 * 20 - 427_500;
	} else if ( income < 9_000_000 ){
		itax = income / 100 * 23 - 636_000;
	} else if ( income < 18_000_000 ){
		itax = income / 100 * 33 - 1_536_000;
	} else if ( income < 40_000_000 ){
		itax = income / 100 * 40 - 2_796_000;
	} else {
		itax = income / 100 * 45 - 4_796_000;
	}
	return ( itax );
}
```

所得税の計算方法については、[こちら](https://www.nta.go.jp/taxes/shiraberu/taxanswer/shotoku/2260.htm)を参照してください。

# DLL作成のコマンド実行例
```text:コマンドプロンプト
D:\Dev> dmd -m64 -shared -L/SUBSYSTEM:WINDOWS -ofincometax.dll incometax.d
   ライブラリ incometax.lib とオブジェクト incometax.exp を作成中
```
コンパイルの結果、`incometax.dll`が作成されます。
Excelから呼び出せるように、環境変数`PATH`に記述があるフォルダに`incometax.dll`を配置します。（例えば、`C:\WINDOWS\system32`）

# Excel側VBAのコード例
```vb:Module1
Declare PtrSafe Function calcIncomeTax Lib "incometax.dll" (ByVal income As Long) As Long

Function incometax(income as Long) As Long
    incometax = calcIncomeTax(income)
End Function
```

[Declareステートメント](https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/declare-statement)を参考にDLLで`export`した`calcIncomeTax`関数を`Declare`します。
64ビット版の`Excel`で実行する場合は、`Declare`ステートメントに`PtrSafe`キーワードが必要です。
`calcIncomeTax`は、D言語で引数、戻り値ともに`int`型で定義しています。
D言語での`int`型は32ビットなので、VBAでは32ビットに該当する`Long`型としています。
※つまり、課税所得が20億円(正確には 2,147,483,647円)を超える方は、この関数使えないですね。:wink:

# 実行結果
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/3f44d0cf-881a-2bd4-48e6-5bee82fcf6d1.png)

B3セルに計算式`=IF(B2<0,0,INCOMETAX(B2))`をセットすることで、B2セルに入力した課税所得金額から所得税を算出できるようになりました。

# おまけ１
64ビット版のDLLモジュールをC++でも作成してみました。

```cpp:incometax.cpp
// cl /clr /LD incometax.cpp

#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)

VC_DLL_EXPORTS int __stdcall calcIncomeTax1(int income)
{
	income = income / 1000 * 1000;	// 千円未満の端数金額を切り捨て
	int itax;						// 所得税
	if        ( income < 1950000 ){
		itax = income / 100 * 5;
	} else if ( income < 3300000 ){
		itax = income / 100 * 10 - 97500;
	} else if ( income < 6950000 ){
		itax = income / 100 * 20 - 427500;
	} else if ( income < 9000000 ){
		itax = income / 100 * 23 - 636000;
	} else if ( income < 18000000 ){
		itax = income / 100 * 33 - 1536000;
	} else if ( income < 40000000 ){
		itax = income / 100 * 40 - 2796000;
	} else {
		itax = income / 100 * 45 - 496000;
	}
	return ( itax );
}
```

# おまけ２
`Microsoft Excel`の代わりに`LibreOffice Calc`(64ビット)版のVBAからもDLLを呼び出せました。
`Excel`版VBAとのソースコードの違いは、以下の２点です。

* 先頭に`Option VBASupport 1`を追加。
* `Declare`宣言に`PtrSafe`は不要。

```vb:Module1_calc
Option VBASupport 1

Declare Function calcIncomeTax Lib "incometax.dll" (ByVal income As Long) As Long

Function incometax(income As Long) As Long
    incometax = calcIncomeTax(income)
End Function
```

# 参考情報
[D言語での32ビット版のDLL作成方法](https://wiki.dlang.org/Win32_DLLs_in_D)
[所得税の税率、計算方法](https://www.nta.go.jp/taxes/shiraberu/taxanswer/shotoku/2260.htm)
[count関数(phobos)](https://dlang.org/phobos/std_algorithm_searching.html#.count)
[ExcelでDLLにアクセスする](https://docs.microsoft.com/ja-jp/office/client-developer/excel/how-to-access-dlls-in-excel)