VBA

【VBA】C++を利用して処理の高速化

近年、VBA(Visual Basic for Applications)はエクセルなどのMicrosoft Office製品で広く使用されていますが、大量のデータや複雑な処理になると、処理が遅くなったり処理落ちすることがあります。このような場合、C++との連携を行うことで処理の高速化が期待できます。簡単に言うと、ボトルネックになる処理をC++側で処理させる事でVBAでは時間のかかる処理を驚くほど高速化できます。この記事では、VBAからC++を呼び出して大きな処理を実行する方法について詳しく解説します。

この記事では、C++の解説は行なっておりませんので、最低限の知識があるものとして進めます。

vbaからc++を使う方法

VBAからC++を呼び出すプロセスは、いくつかのステップで構成されています。以下に、具体的な手順と解説を記述します。

C++ DLLの作成

まず、C++でDLLを作成します。DLLは、VBAから呼び出される関数やプロシージャを提供します。以下は、単純な加算を行うDLLの例です。

複数のアプリケーションが共通して使用できる汎用性の高いプログラムをOSにファイルとして保存し、アプリケーションの実行時に必要に応じて利用する方法。 また、そのファイルのこと。 拡張子は「.dll」。

// addition.cpp

#include <iostream>

extern "C" __declspec(dllexport) double Add(double a, double b) {
    return a + b;
}

ポイントとして、extern "C"を使用してC言語の関数としてエクスポートし、__declspec(dllexport)を使ってDLLから利用可能にする事です。

このコードをビルドしてDLLを生成します。ビルド方法は開発環境により異なりますが、一般的な手順はコンパイルとリンクです。

g++ -shared -o addition.dll addition.cpp

Visual Studioを利用している場合、次の手順で簡単に作れます。

  1. プロジェクトのプロパティの設定: ソリューションエクスプローラーでプロジェクトを右クリックし、”プロパティ” を選択します。次に、以下の設定を行います。
    • 構成プロパティ全般: “構成の種類” を DLL に変更します。
    • 構成プロパティ出力ディレクトリ: DLLの出力先を設定します。
  2. ビルド: メニューの “ビルド” → “ソリューションのビルド” を選択してビルドします。

DLLの配置

生成されたDLLをVBAからアクセスできる場所に配置します。通常は、WindowsのSystem32フォルダやVBAプロジェクトの同じ場所に配置します。

VBAからDLLを呼び出す

VBAからC++ DLLを呼び出すには、Declareステートメントを使用します。

Declare Function Add Lib "path\to\your\dll\addition.dll" (ByVal a As Double, ByVal b As Double) As Double

Sub TestAddition()
    Dim result As Double
    result = Add(5, 10)
    MsgBox "Result: " & result
End Sub

Declare Function ステートメントでDLL内の関数を宣言し、その後で宣言した関数を呼び出すことができます。

注意事項

  1. ビット数の一致: VBAとC++ DLLのビット数(32ビットまたは64ビット)は一致している必要があります。
  2. 関数の型一致: 関数の引数と戻り値の型が一致していることを確認してください。
  3. エラーハンドリング: DLLの呼び出しでエラーが発生する可能性があるため、エラーハンドリングを適切に行いましょう。

以上が、VBAからC++ DLLを呼び出す基本的な手順です。

実用サンプル① 2次元配列の操作

多次元配列の操作は、VBA の処理の中では時間のかかる処理の一つで、VBA単体での高速化の方法は色々なサイトで紹介されています。

ただ、先述の通り近年のビッグデータを扱おうとすると、VBAでは遅くてまともに処理ができません。

そこでC++でボトルネックになる処理を行うことでExcelでもビッグデータの処理が可能になります。

C++ DLLの作成

まず、C++ DLLを作成します。以下は、2次元配列を受け取り、その要素を2倍にする関数の例です。

// ArrayProcessor.cpp
#include <vector>

extern "C" __declspec(dllexport) void Process2DArray(int rows, int cols, int** array) {
    // Processing the 2D array
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            array[i][j] = array[i][j] * 2;  // Example: Multiply each element by 2
        }
    }
}

Process2DArrayという関数を定義しました。この関数は、extern "C"を使用してC言語の関数としてエクスポートし、__declspec(dllexport)を使ってDLLから利用可能にします。これにより、この関数をVBAなどから呼び出すことができるようになります。

この関数は3つの引数を受け取ります。

  • rows: 2次元配列の行数
  • cols: 2次元配列の列数
  • array: 2次元配列へのポインタ

そして、2つのループを使用して2次元配列を処理します。各要素を2倍にする処理が行われていますが、これは単なる例であり、実際の用途によって処理内容を変更して下さい。。

VBAからC++ DLLを呼び出す

次に、VBAからC++ DLLを呼び出します。以下のVBAコードは、C++で作成したDLLを利用して2次元配列を処理し、その結果を取得する例です。

Declare Sub Process2DArray Lib "path\to\ArrayProcessor.dll" (ByVal rows As Long, ByVal cols As Long, ByRef array() As Long)

Sub ExampleUsage()
    Dim rows As Long
    Dim cols As Long
    Dim myArray() As Long
    Dim i As Long, j As Long

    ' Set the dimensions of the 2D array
    rows = 3
    cols = 4

    ' Redim the VBA array to hold the values
    ReDim myArray(1 To rows, 1 To cols) As Long

    ' Fill the VBA array with some initial values
    For i = 1 To rows
        For j = 1 To cols
            myArray(i, j) = i * j
        Next j
    Next i

    ' Call C++ function to process the array
    Call Process2DArray(rows, cols, myArray)

    ' Display the processed array
    Debug.Print "Processed Array:"
    For i = 1 To rows
        For j = 1 To cols
            Debug.Print myArray(i, j);
        Next j
        Debug.Print ""
    Next i
End Sub

VBAの2次元配列がC++で処理された後、処理結果が再びVBAに戻されています。 C++での処理は加算(各要素に2を加える)しているだけです。

効果的な使い方

この方法の効果は、主に以下の状況で発揮されます。

  • 大量のデータ処理: 数百行以上のデータがある場合、C++による高速処理が顕著に現れます。
  • 複雑なアルゴリズム: 複雑なアルゴリズムや数学的処理が必要な場合、C++の高度な計算機能を利用できます。

ただし、この方法は環境のセットアップが必要であり、誤った操作は予期せぬ問題を引き起こす可能性があるため注意が必要です。適切な利用場面でのみ導入するようにしましょう。

VBAの限界を感じた際にはC++との連携を検討して、処理の高速化を実現してみてください。

実用サンプル② C++ DLLを活用した高速なDB操作

続いてVBAからC++ DLLを呼び出してデータベースのCRUD(Create, Read, Update, Delete)操作を高速化する方法について解説します。DB操作も時間のかかる処理の代表です。

C++ DLLの作成

まず、C++ DLLを作成します。以下は、SQLiteデータベースに対して基本的なCRUD操作を行う例です。SQLiteを使用しますが、他のデータベースも同様の手法で扱えます。

// DatabaseProcessor.cpp
#include <sqlite3.h>
#include <string>

extern "C" __declspec(dllexport) int ExecuteQuery(const char* query, char* errorMessage, const char* dbName) {
    sqlite3* db;
    char* errMsg = nullptr;

    int result = sqlite3_open(dbName, &db);

    if (result != SQLITE_OK) {
        strcpy(errorMessage, sqlite3_errmsg(db));
        sqlite3_close(db);
        return result;
    }

    result = sqlite3_exec(db, query, nullptr, nullptr, &errMsg);
    if (result != SQLITE_OK) {
        strcpy(errorMessage, errMsg);
    }

    sqlite3_close(db);
    return result;
}

extern "C" __declspec(dllexport) int InsertData(const char* tableName, const char* values, char* errorMessage, const char* dbName) {
    std::string query = "INSERT INTO " + std::string(tableName) + " VALUES (" + std::string(values) + ");";
    return ExecuteQuery(query.c_str(), errorMessage, dbName);
}

extern "C" __declspec(dllexport) int UpdateData(const char* tableName, const char* setValues, const char* condition, char* errorMessage, const char* dbName) {
    std::string query = "UPDATE " + std::string(tableName) + " SET " + std::string(setValues) + " WHERE " + std::string(condition) + ";";
    return ExecuteQuery(query.c_str(), errorMessage, dbName);
}

extern "C" __declspec(dllexport) int DeleteData(const char* tableName, const char* condition, char* errorMessage, const char* dbName) {
    std::string query = "DELETE FROM " + std::string(tableName) + " WHERE " + std::string(condition) + ";";
    return ExecuteQuery(query.c_str(), errorMessage, dbName);
}

extern "C" __declspec(dllexport) int SelectData(const char* tableName, const char* columns, const char* condition, char* result, char* errorMessage, const char* dbName) {
    std::string query = "SELECT " + std::string(columns) + " FROM " + std::string(tableName) + " WHERE " + std::string(condition) + ";";
    
    sqlite3* db;
    int resultCount = 0;
    char* errMsg = nullptr;

    int resultDb = sqlite3_open(dbName, &db);

    if (resultDb != SQLITE_OK) {
        strcpy(errorMessage, sqlite3_errmsg(db));
        sqlite3_close(db);
        return resultDb;
    }

    sqlite3_stmt* stmt;
    int resultPrepare = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr);

    if (resultPrepare != SQLITE_OK) {
        strcpy(errorMessage, sqlite3_errmsg(db));
        sqlite3_close(db);
        return resultPrepare;
    }

    int cols = sqlite3_column_count(stmt);

    while (sqlite3_step(stmt) == SQLITE_ROW) {
        for (int col = 0; col < cols; col++) {
            strcat(result, reinterpret_cast<const char*>(sqlite3_column_text(stmt, col)));
            strcat(result, ",");
        }
        strcat(result, "\n");
        resultCount++;
    }

    sqlite3_finalize(stmt);
    sqlite3_close(db);

    return resultCount;
}

このC++コードはSQLite3を使用してデータベースの基本的な操作を行う関数を定義しました。各関数はDLLとしてエクスポートし、VBAなどから呼び出すことができるようにします。以下に各関数の解説をします。

  1. ExecuteQuery: 与えられたクエリを実行する関数です。データベースファイルのパス(dbName)とエラーメッセージを受け取り、実行結果を返します。実行結果やエラーメッセージは引数経由で返されます。
  2. InsertData: テーブルにデータを挿入する関数です。テーブル名、値(values)、データベースファイルのパス(dbName)を受け取り、ExecuteQuery関数を用いて実行します。
  3. UpdateData: テーブル内のデータを更新する関数です。テーブル名、更新する値(setValues)、条件(condition)、データベースファイルのパス(dbName)を受け取り、ExecuteQuery関数を用いて実行します。
  4. DeleteData: テーブルからデータを削除する関数です。テーブル名、条件(condition)、データベースファイルのパス(dbName)を受け取り、ExecuteQuery関数を用いて実行します。
  5. SelectData: テーブルからデータを選択する関数です。テーブル名、取得する列(columns)、条件(condition)、データベースファイルのパス(dbName)を受け取り、指定した条件に一致するデータを取得します。結果は文字列として返され、result引数を通じて受け取ります。また、結果の行数を整数として返します。

これらの関数は基本的なCRUD操作を定義していて、SQLite3の機能を利用してデータベースの操作を行います。また、エクスポートされた関数は__declspec(dllexport)によりDLLから直接呼び出すことができるように実装しました。

VBAからC++ DLLを呼び出す

次に、VBAからC++ DLLを呼び出します。以下のVBAコードは、C++で作成したDLLを利用してSQLiteデータベースに対してCRUD操作を行う例です。

Declare Function ExecuteQuery Lib "path\to\DatabaseProcessor.dll" (ByVal query As String, ByVal errorMessage As String, ByVal dbName As String) As Long
Declare Function InsertData Lib "path\to\DatabaseProcessor.dll" (ByVal tableName As String, ByVal values As String, ByVal errorMessage As String, ByVal dbName As String) As Long
Declare Function UpdateData Lib "path\to\DatabaseProcessor.dll" (ByVal tableName As String, ByVal setValues As String, ByVal condition As String, ByVal errorMessage As String, ByVal dbName As String) As Long
Declare Function DeleteData Lib "path\to\DatabaseProcessor.dll" (ByVal tableName As String, ByVal condition As String, ByVal errorMessage As String, ByVal dbName As String) As Long
Declare Function SelectData Lib "path\to\DatabaseProcessor.dll" (ByVal tableName As String, ByVal columns As String, ByVal condition As String, ByVal result As String, ByVal errorMessage As String, ByVal dbName As String) As Long

Sub DatabaseCRUDOperations()
    Dim result As Long
    Dim errorMessage As String
    Dim query As String
    Dim values As String
    Dim setValues As String
    Dim condition As String
    Dim tableName As String
    Dim columns As String
    Dim resultData As String

    ' SQLiteデータベースファイルのパス
    Dim dbName As String
    dbName = "C:\path\to\your\database.db"

    ' CREATE (INSERT)
    tableName = "example_table"
    values = "'Alice', 25, 'USA'"  ' Example values
    result = InsertData(tableName, values, errorMessage, dbName)

    ' READ (SELECT)
    tableName = "example_table"
    columns = "name, age, country"
    condition = "age > 20"
    result = SelectData(tableName, columns, condition, resultData, errorMessage, dbName)
    Debug.Print "Select Result: " & resultData

    ' UPDATE
    tableName = "example_table"
    setValues = "age = 30"
    condition = "name = 'Alice'"
    result = UpdateData(tableName, setValues, condition, errorMessage, dbName)

    ' DELETE
    tableName = "example_table"
    condition = "name = 'Alice'"
    result = DeleteData(tableName, condition, errorMessage, dbName)
End Sub

効果的な使い方

この手法は、主に以下の状況で効果を発揮します。

  • 大規模なデータベース処理: 複雑な検索やデータベース内の巨大なデータセットに対して、C++の高速性が有利です。
  • 高度な計算処理: 特に数学的な処理や統計処理が必要な場合、C++の計算機能が活かされます。

ただし、データベースのセキュリティや整合性には十分な注意が必要です。また、データベースの操作は慎重に行い、エラーハンドリングを適切に行うことが重要です。

この記事を参考にして、VBAでのデータベース操作を効率化し、高速なCRUD処理を実現してみてください。

まとめ

VBAからC++を呼び出すことで、VBAの柔軟性とC++の高性能な処理を組み合わせ、アプリケーション全体のパフォーマンスを向上させることができます。この手法を使用する際には、プロジェクトの要件やセキュリティに十分な注意を払いながら、最適な高速化手段を見つけることが重要です。

今回は簡単な例で示しましたが、C++を利用する事でVBAでは処理に時間がかかり過ぎたり実現が難しかった事柄も簡単に実現可能です。

特に近年、大きなデータ処理が必須になってきています。
PythonとVBAを連携させる記事は私も書いていますが、PythonよりC++の方が圧倒的に高速に処理可能です。
※Pythonは便利なライブラリがあり簡単に処理を実現できるので、使い分けが必要です。

是非、VBA と他のプログラミング言語の連携を覚えてみて下さい。