3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ExcelからDynamoDBのデータを取得するDLLを作ってみた話

Posted at

はじめに

ExcelからDynamoDBにアクセスしてデータを取得するツールを作ってみた。
QiitaなどWEB上に記事を投稿するのは初めてなので、練習兼備忘として色々書いてみる。

実現方法を考える

DynamoDBのデータを取得するには色々な手段があるが、今回はExcelを起点とするためVBAから実行できる処理が必要だった。
AWS系のサービスにプログラムからアクセスするとき真っ先に思いつくのはAWS SDKだが、VBA用のものは見当たらない。
他の手段としてはREST APIでデータを取得出来そうだったが、めんどくさそうな気配がしたのでやりたくない…
結果、AWS SDKが用意されているC#.NETを用いてVBAから呼び出せるDLLを作成し、それ経由でデータを取得することにした。

とりあえず作業開始

C#.NETで書くと言っても、実はJavaエンジニアな私はサラッと書けず、ネット上の記事を参考にしながらのコーディングだった。
Visual Studio 2022をインストールし、C#用のクラスライブラリ(.NET Framework)プロジェクトのテンプレートを使ってプロジェクトを作成する。
プロジェクトテンプレート選択画面

プロジェクトプロパティを変更する

次にDLLを使用するための設定を行う。(あまり理解できてないが、これを行わないとDLLとして使えないらしい)
画面最上部のメニューバーから、「プロジェクト」 > 「(プロジェクト名)のプロパティ」をクリックすると下記のような画面が開くので2カ所設定を変える。

まずは「アプリケーション」タブから「アセンブリ情報」を開き、「アセンブリをCOM参照可能にする」にチェックを入れる。
プロパティ_アプリケーション
プロパティ_アプリケーション_アセンブリ情報

次に、「ビルド」タブを開き「COM相互運用機能の登録」にチェックを入れる。
※これに関しては必須ではなく、別途コマンドを打てば代替可能らしい。
プロパティ_ビルド

設定し終えたらCtrl + Sで設定を保存して完了だ。

AWS SDKのインストール

続いて、DynamoDBにアクセスするためのAWS SDKをインストールする。
画面最上部のメニューバーから、「プロジェクト」 > 「NuGetパッケージの管理」をクリックすると下記のような画面が開く。
NuGetパッケージマネージャー

検索欄にdynamodbと入力して、AWSSDK.DynamoDBv2をインストールする。
依存している必須パッケージであるAWSSDK.Coreは同時にインストールされるので別途インストールする必要はない。
NuGetパッケージマネージャー_dynamodb

コーディングしてみる

必須のセットアップは完了したので、拙いながらもコーディングをしてみる。

GetData.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;

namespace DynamoDBAccessQiita
{
    [ComVisible(true)]
    public interface IDynamoDBAccessQiita
    {
        [Description("DynamoDBから設定値を取得する")]
        List<string[]> getDynamoDBData(string partitionKeyValue);
    }

    [ClassInterface(ClassInterfaceType.None)]
    public class GetData : IDynamoDBAccessQiita
    {

        private static readonly string[] attributeNames = new string[]
        {
            "PKey",
            "SKey",
            "Col1",
            "Col2"
        };

        public List<string[]> getDynamoDBData(string partitionKeyValue)
        {
            AmazonDynamoDBClient dynamoDBClient = new AmazonDynamoDBClient(
                new BasicAWSCredentials("AccessKeySample", "SecretKeySample"),
                new AmazonDynamoDBConfig
                {
                    RegionEndpoint = Amazon.RegionEndpoint.APNortheast1
                });

            // クエリの作成
            QueryRequest request = new QueryRequest
            {
                TableName = "SampleTable",
                KeyConditionExpression = "#pk = :pk",
                ExpressionAttributeNames = new Dictionary<string, string>
                {
                    { "#pk", "PKey" }
                },
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>
                {
                    { ":pk", new AttributeValue { S = partitionKeyValue } }
                }
            };

            // クエリの実行
            QueryResponse response = dynamoDBClient.Query(request);

            var result = new List<string[]>();

            // 取得したレコードを返却用に詰め替え
            foreach (var item in response.Items)
            {
                var record = new string[attributeNames.Length];

                for (int i = 0; i < attributeNames.Length; i++)
                {
                    record[i] = item.TryGetValue(attributeNames[i], out var value) ? value.S : null;
                }

                result.Add(record);
            }

            return result;
        }

    }
}

説明用のコードなので簡略化しているが、本来はもう少しまともなコードなのでお目汚しご容赦願いたい。
この状態でビルドすると以下のような警告が出る。

warning : タイプ ライブラリ エクスポーターで '○○○○' を処理中に警告が出されました。警告: タイプ ライブラリ エクスポーターでシグネチャのジェネリック型インスタンスが見つかりました。ジェネリック コードを COM にエクスポートすることはできません。

分からない単語だらけで解決に時間がかかったが、ジェネリック型というのが諸悪の根源のようだ…

ジェネリック型を使うことはできない

前述のコードは、取得したデータをListに格納して呼び出し元に返そうとするコードである。
このList、よくよく見てみると名前空間がSystem.Collections.Genericとなっている。
どうやらこれに属しているクラスがジェネリック型と呼ばれるものらしい。
List<string[]>のように要素の型を指定出来るものであり、普段List構造を使うならこの書き方だろう。
しかし、DLLとしてはListで返すことができないようなので、非ジェネリック型であるArrayListを使うことでこの問題を解消できる。
違いについては別途調べてもらいたいが、大して違いはなさそうなので単純に置き換えてみる。

GetData.cs
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;

namespace DynamoDBAccessQiita
{
    [ComVisible(true)]
    public interface IDynamoDBAccessQiita
    {
        [Description("DynamoDBから設定値を取得する")]
        ArrayList getDynamoDBData(string partitionKeyValue);
    }

    [ClassInterface(ClassInterfaceType.None)]
    public class GetData : IDynamoDBAccessQiita
    {

        private static readonly string[] attributeNames = new string[]
        {
            "PKey",
            "SKey",
            "Col1",
            "Col2"
        };

        public ArrayList getDynamoDBData(string partitionKeyValue)
        {
            AmazonDynamoDBClient dynamoDBClient = new AmazonDynamoDBClient(
                new BasicAWSCredentials("AccessKeySample", "SecretKeySample"),
                new AmazonDynamoDBConfig
                {
                    RegionEndpoint = Amazon.RegionEndpoint.APNortheast1
                });

            // クエリの作成
            QueryRequest request = new QueryRequest
            {
                TableName = "SampleTable",
                KeyConditionExpression = "#pk = :pk",
                ExpressionAttributeNames = new Dictionary<string, string>
                {
                    { "#pk", "PKey" }
                },
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>
                {
                    { ":pk", new AttributeValue { S = partitionKeyValue } }
                }
            };

            // クエリの実行
            QueryResponse response = dynamoDBClient.Query(request);

            var result = new ArrayList();

            // 取得したレコードを返却用に詰め替え
            foreach (var item in response.Items)
            {
                var record = new string[attributeNames.Length];

                for (int i = 0; i < attributeNames.Length; i++)
                {
                    record[i] = item.TryGetValue(attributeNames[i], out var value) ? value.S : null;
                }

                result.Add(record);
            }

            return result;
        }

    }
}

再度ビルドしてみると、今度はエラーも警告もなしで完了した。
一旦DLL側はこの状態で置いておいて、VBA側の対応を行う。

VBA側でやること

まずはDLLをVBAから呼び出せるようにする。
VBAのエディタを開き、画面上部のメニューバーから、「ツール」 > 「参照設定」をクリックすると下記のような画面が開くので、先ほど作成したDLLにチェックを入れてOKボタンを押下するだけで良い。
VBA_参照設定

次にDLL経由でDynamoDBのデータを取得し、シートに書き出すマクロを書く。

VBA
Option Explicit

Sub GetDynamoDB()

    Dim dll As DynamoDBAccess.GetData
    Set dll = New DynamoDBAccess.GetData
    
    Dim records As Object
    Dim record As Variant
    Dim rowIndex As Integer
    
    ' 外部DLL経由でDynamoDBの値を取得
    Set records = dll.getDynamoDBData("hoge")
    
    ' 取得したリストをシートに出力
    For Each record In records
        ActiveSheet.Range("A1").Offset(rowIndex).Resize(, 4) = record
        rowIndex = rowIndex + 1
        
    Next record

End Sub

試しに動かしてみたらちゃんと取得できたので、今回はここまで。

最後に

本来であれば、取得処理部分では対象のデータ型ごとに異なる処理をしなければならないのだが、今回は説明用に文字列だけを対象としたコードにしている。
また、使用しているExcelが32ビット用か64ビット用かでもビルドオプションを変えなければならないなど、説明を省略している部分が多々ある。
今後もDLLを作成する機会があれば、そのあたりも補足を入れて記事にしたい。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?