Blank?=False

「呉下の阿蒙にあらず」をモットーにしたITエンジニアの日々

VBAでRedisに接続 その3 [完結編]

f:id:stonebeach-dakar:20161204184825p:plain

何回目かのVBAでRedisを使うポストですが、ようやく今回で無事VBAからのRedisアクセスができるようになりました。

C#VBAのDLLを作成する方法

VBAから他の言語で書かれたDLLを作成する場合、2パターンあります。

  • COMインターフェースを持つDLLを環境設定で追加して使用する。
  • 通常のDLLをDllImportして使用する。


今回は勉強がてらCOMインターフェースを持つDLLを作成します。
COMインターフェースで作れば、インテリセンスも効くしヒントも表示されるので使いやすくなります。


COMインターフェースとは

COMインターフェースCOMインターフェースと言っていますがそもそもCOMインターフェースって何?
なところがあったので調べてみました。

Component Object Modelとは、マイクロソフトが提唱するソフトウェアの再利用を目的とした技術のことである。
アプリケーションソフトウェア間の通信や、OSとアプリケーションソフトウェアとのAPIに用いられる。
Component Object Model - Wikipedia


主にWindowsで使われるライブラリの基盤的なもの、という感じかな。
現在は殆どが.Net Frameworkに移行しているけど、C++では現役バリバリ、だそうだ。

VBAはそもそもが.Net以前のVBをベースにした言語なので、COMとは相性が良いんでしょう。

StackExchange.RedisにCOM設定を入れてビルドする

早速、StackExchange.Redisの設定でCOM参照を追加…等やってみてビルドします。
f:id:stonebeach-dakar:20161219210825p:plain



そうは問屋がおろしまへんで!とばかりにビルドエラーが発生しました。
f:id:stonebeach-dakar:20161219210740p:plain

ビルドエラーの原因

ビルドエラー内容を見ると、ジェネリック型はCOMインターフェースで使えないようです。
このジェネリック型、ですがC#Javaにある機能で、型が違うけど同じような処理を実装する時にに使えるものです。
C++には似たようなテンプレートと言う機能があります。
動的型付け言語のRubyでは特にこういった機能はなかった気がします。

別dllファイルとしてラッパーdllを作成する

ジェネリック型を含むコードをビルドしてdllを作成する場合、COMインターフェースの実装ができないので、ジェネリック型を使わない COMインターフェース用のラッパーDLLを作成してVBAからはそのラッパーDLLを経由したアクセスを行う方針で作成することにしました。

NugetでStackExchange.Redisをインストール

VisualStudioには、RubyのGemとかMacのHomeBrewのようなNugetというパッケージマネージャーがあり、それで開発環境にStackExchange.Redisを含めることが出来ます。
実はVisualStudio4年使っていて初めてVisualStudioにもNugetがあることを知りました
StackExchange.RedisのReadmeにある通りNugetのパッケージマネージャーコンソールを開いて、以下を入力します。

PM> Install-Package StackExchange.Redis



これで環境へStackExchange.Redisが準備できるので、プロジェクトのプロパティでCOM関係の設定をした後、COMインターフェースと空のクラスを作って、COM公開用の属性などを追加してビルドします。

よっしゃこれでよかろ!とおもったらビルドエラー。
なんでや!?と思ってエラーメッセージを見ると、レジストリにアクセス出来ませんでした…とあります。
f:id:stonebeach-dakar:20161219211319p:plain
COMインターフェースアセンブリを登録するためにはレジストリに登録する必要があるようで、 一般ユーザ権限でVisualStudioを起動しているときのビルドだと、レジストリに登録する権限がないので正常にCOMインターフェースが登録出来ない、ということみたいですね。


というわけで、VisualStudioを管理者モードとして起動してビルドすることで正常にビルドが通りました。

とりあえず基本的な読み書きだけやってみる

前回作成したBasicUsageを見ると、ConnectionMultiplexerクラスのConnect,GetDatabase、そしてIDatabaseインターフェースのStringGet,StringSetがあれば基本的な読み書きはできそうです。
というわけで、2つのクラスのCOMインターフェースとラッパークラスを作成したものをビルドします。

ConnectionMultiPlexer.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.ComponentModel;
using StackExchange.Redis;


namespace StackExchange.Redis_COM
{
    [ComVisible(true)]
    public interface IConnectionMultiplexer
    {
        [Description("Create a new ConnectionMultiplexer instance")]
        ConnectionMultiplexer Connect(string configuration);

        [Description("Obtain an interactive connection to a database inside redis")]
        Database GetDatabase(int db = -1);
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class ConnectionMultiplexer : IConnectionMultiplexer
    {
        private Redis.ConnectionMultiplexer ConnectionMultiplexerInstance;

        public ConnectionMultiplexer Connect(string configuration)
        {
            ConnectionMultiplexerInstance = Redis.ConnectionMultiplexer.Connect(configuration);
            return this;
         }

        public Database GetDatabase(int db = -1)
        {
            return new Database(ConnectionMultiplexerInstance.GetDatabase(db));
        }
    }
}


Database.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace StackExchange.Redis_COM
{
    [ComVisible(true)]
    public interface IDatabase
    {
        void StringSet(string key, string value);
        string StringGet(string key);
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class Database : IDatabase
    {
        private Redis.IDatabase IDatabaseInstance;

        public Database(Redis.IDatabase IDatabase)
        {
            IDatabaseInstance = IDatabase;
        }

        public void StringSet(string key, string value)
        {
            IDatabaseInstance.StringSet(key, value);
        }

        public string StringGet(string key)
        {
            return IDatabaseInstance.StringGet(key);
        }
    }
}

VBAで使ってみる

作成したCOMインターフェースをVBEの環境設定で使う用に設定して、BasicUsageをVBAで書いてみます。
f:id:stonebeach-dakar:20161219212027p:plainf:id:stonebeach-dakar:20161219212038p:plain

Option Explicit
Public Sub Redis_BasicUsage()
    Dim Connection_Dummy As New Redis_COM.ConnectionMultiplexer
    
    Dim Connection As Redis_COM.ConnectionMultiplexer
    Set Connection = Connection_Dummy.Connect("localhost")
    
    Dim Redis As Redis_COM.Database
    Set Redis = Connection.GetDatabase
    
    Redis.StringSet "mykey", "foo"
    Debug.Print Redis.StringGet("mykey")
End Sub

実行結果
f:id:stonebeach-dakar:20161219210709p:plain


結果、無事Redisにアクセスしてデータを設定、取得ができました。

あとがき

今回作成したRedisのCOMインターフェースはGitHubに上げました。
github.com
examplesディレクトリにVBAでの使用例を保存してあります。


せっかくなのでC#を勉強がてら、実用的なレベルまで作成した後、Redisのクライアント一覧に登録までやっちゃおうかなと思います。