Excel 32bit VBA + C#製32bitDLLを64bitへ移行する

はい、今日もニッチなWindows開発の話をしていきます。
まず、32bit版の前提から確認していきましょう。

前提

  • 開発環境
    • Visual Studio 2019 Professional
    • Visual Studio Installer Projects
  • C#製DLL
    • 複数のDLLを32bit版で作成していた
    • うち1つがVBA側から使う窓口
      • プロジェクトプロパティの「COM相互運用機能の登録」にはチェックを入れている
      • 「アセンブリに署名する」にもチェックを入れている
  • 利用環境
    • Excel2007から脈々と2021まで、Microsoft365でも使い続けてきた

これを、64bit版Excelから使えるようにするのに、なかなか骨が折れた話。です。
まぁ、これまでも、InstallShieldからVisual Studio Installer projectに変更した余波で、ポキポキと骨が折れてますけどね!




検証端末はOSクリーンすべし

今までExcel 32bitを使っていた方、64bitにして検証しようとすることはオススメしません。
そもそも64bit版インストーラを実行しても32bit版がインストールされる罠
64bit版になっても、なんかエラーになる罠
罠がいっぱいです。

今まで32bit版のDLLをCOM登録していたのなら、なおさらです。
まずはクリーンなOSに、クリーンにExcel 64bitを入れて、ビルド環境とはわけて検証すべきです。

そして私は、OSをリセットしたにもかかわらずExcel 32bitがインストールされて泣きをみました。
必ず64bitがインストールされているか、目視確認しましょう。
過去にDLしておいたイントーラなどは利用せず、Webから64bitを指定してDLし直した方が成功率は高い気がする。

まずは仮想環境上で、キレイなOS+Excel環境ができたらチェックポイントを作成しておく、がオススメでしょうか。

対応していくぅ!

ターゲットプラットフォームをx64にします。
「アセンブリに署名する」にチェックを入れているプロジェクトのビルドが通らないけど、pfxファイルを作り直せばOK。

Visual Studio Installer Projectsのプロジェクトも対応していくぅ

  • プロジェクトのプロパティのTargetPlatformをx64へ
  • プロジェクトで右クリック→View→ファイルシステムを開き、Application FolderのプロパティのDefaultLocationが[ProgramFilesFolder]になっていたら、[ProgramFiles64Folder]に変更しましょう
  • プロジェクト出力やカスタム動作は勝手にx64にならないので、一度削除してx64で追加し直します
    • 追加したら各種プロパティを前の状態に合わせることを忘れずに(RegisterをvsdrpCOMすることとかね!)
    • 各カスタム動作のプロパティのRun64bitはtrueにします
    • その他、一度削除したことでショートカット等でエラーになってるかもなので、細々対応していきます

COM登録問題にとりかかるぅ

おそらくここまでで、全て一式揃ったWindowsアプリなら問題なく動くでしょう。
ところが、私はExcel 64bit版から動かせるようになりたいのです。
過去に避けた問題と向き合います。
インストールしても、Excelの参照設定から作成したDLLは見え……ることもあるけど、見えないこともあった。なんでや。
何度もOSリセットして試してみたけど、原因がよくわからない。
依存関係がキレイに揃ってないのかなぁ?

それでなくとも以前も問題があったので、なんとかしたい。
解決策はいくつかあります、が、その前に絶望から説明しておきましょう。

駄目な環境ならカスタム動作を追加して、RegAsmを叩いても無駄だった

ちっくしょう、なんでだ。
カスタム動作で「”RegAsm.exe dllのパス” /tlb /codebase “dllのパスコマンド”」を叩いても駄目。
コマンドを叩くバッチを作って、それを呼び出しても駄目。
インストール後に手動で管理者権限実行すれば解決するので、コマンドが間違えているわけでもないのに。

登録したいDLLのプロジェクトを、「プロジェクト出力」にせず「ファイル」にした上でRegisterプロパティをvsdraCOMにすると良いという方もいらっしゃいましたが、それも駄目でした。

ただ、COM登録自体はできているくさい。
/regfileオプションでレジストリ情報を列挙してみましたが、すべてレジストリ登録されていましたし。

さて、解決策です。

どうしてもVBAの参照設定から使えるようになりたい人向けは2通り?

すべてをインストーラで解決する策は見つけられませんでした…。
ので、カスタム動作でRegAsmを叩くバッチを生成して、あとはユーザに手動で管理者権限実行してもらう、という解決策が1つ。
または、GUIDはわかっているでしょうから、References.AddFromGUIDを起動時に実行するExcelファイルも添えて、インストーラの後に開いてもらう、という解決策。
どっちにしろユーザのひと手間が必要なスタイルです。

カスタム動作でRegAsmを叩くバッチを生成するパターン

カスタム動作を追加して、プロパティのCustomeActionDataに「/dir=”[TARGETDIR]\”」を指定しておきます。
dirは任意な名前でOKなので、好きに置き換えてください。
これで、this.Context.Parameters[“dir”]でインストールフォルダがもらえます。
ただ、末尾が\\で終わってます。
考慮しつつ、「”RegAsm.exe dllのパス” /tlb /codebase “dllのパスコマンド”」を叩くバッチを作ります。

public class Program : Installer
{
    private string InstallDirPath()
    {
        string targetdir = this.Context.Parameters["dir"];

        // 末尾の\を削除(2個連続して渡される対策)
        while (targetdir.Substring(targetdir.Length - 1, 1) == "\\")
        {
            targetdir = targetdir.Substring(0, targetdir.Length - 1);
        }

        return targetdir;
    }

    private string GetDLLPath(string dllName)
    {
        // ダブルコーテーションで囲む
        return "\"" + InstallDirPath() + @"\" + dllName + "\"";
    }

    public override void Install(IDictionary stateSaver)
    {
        base.Install(stateSaver);

        string path = System.IO.Path.Combine(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), "RegAsm.exe");
        string cmd = "\"" + path + "\" " + GetDLLPath("hoge.dll") 
                        + " /tlb /codebase " + GetDLLPath("hoge.dll");

        // 登録用batの作成
        string batPathForRegist = InstallDirPath() + @"\RegAsmCall.bat";
        if (!System.IO.File.Exists(batPathForRegist))
        {
            System.IO.File.Create(batPathForRegist).Close();
        }
        System.IO.File.WriteAllText(batPathForRegist, cmd);

        // 解除用も作っておく
        string batPathForUnregist = InstallDirPath() + @"\RegAsmUnregistCall.bat";
        if (!System.IO.File.Exists(batPathForUnregist))
        {
            System.IO.File.Create(batPathForUnregist).Close();
        }
        System.IO.File.WriteAllText(batPathForUnregist, "\"" + path + "\" " + GetDLLPath("MouseExcel.dll") + " /unregister");
    }
}

省略しましたが、Rollback、UnInstallを実装して作成したバッチファイルの削除も忘れずに。
これでインストール先にRegAsmCall.batと、RegAsmUnregistCall.batが作成されます。
あとはユーザに「管理者権限で実行してください、おなしゃす!」と土下座します。

References.AddFromGUIDを使うスタイル

Excelファイルを開いて、起動時ならThisWorkbookのWorkbook_Openなり、シートに「実行」ボタンを配置してイベントハンドラなりで、呼び出しましょう。
こちらにVBAといえばOffice TANAKAさんの解説がありますので、省略します。
AddFromFileで解説されていますが、AddFromGUIDに置き換えてください。

ActiveWorkbookを使っているけど、参照設定はExcelファイル別ではないよね?
私はこちらの手法は取らないので、動作確認はしていません。
やったけど駄目だったらごめん。

参照設定から使わないスタイル

参照設定から見えないだけで登録はされているようなので、Object型で利用するスタイルにする。
まぁ、コッチならインストール忘れてコンパイルエラーになるってことが避けられはする。
開発する人にとってはコーディングがしづらくなるけど。

Dim obj As Object
Set obj = CreateObject("DLL名.クラス名")
Call obj.独自関数名も呼び出せるはず()

元々VBAコードがある方は、NewしてるところをCreateObjectに書き換えつつ、型を独自のものからObjectに置き換える作業が必要になりますね。

実際は二重に対応しておくスタイルが良いかなー

カスタム動作でRegAsmを叩くバッチを生成して、さらに実行もしておく。
下記関数を追加して、

static private string RunAsCommand(string s)
{
    //Processオブジェクトを作成
    Process p = new Process();

    //ComSpec(cmd.exe)のパスを取得して、FileNameプロパティに指定
    p.StartInfo.FileName = System.Environment.GetEnvironmentVariable("ComSpec");
    //出力を読み取れるようにする
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardInput = false;

    //管理者権限で実行
    p.StartInfo.Verb = "RunAs";

    // 実行する対象の
    p.StartInfo.Arguments = s;

    //起動
    p.Start();

    //出力を読み取る
    string results = p.StandardOutput.ReadToEnd();

    //プロセス終了まで待機する
    //WaitForExitはReadToEndの後である必要がある
    //(親プロセス、子プロセスでブロック防止のため)
    p.WaitForExit();
    p.Close();

    //出力された結果を表示
    Console.WriteLine(results);
    System.Threading.Thread.Sleep(500);

    return results;
}

Install関数の最後で呼び出しておきます。

RunAsCommand("/c " + batPathForRegist);

これでVBAの方はObject型&CreateObject関数を使うよう実装しておくと確実。
「うまく動かない」って言われたら、「インストール先にRegAsmCall.batがあるから、管理者権限で実行してみて」って答える。
もちろん、VBAが動かない理由は他にあるかもしれないので、他人を疑うことも大事です。



と、いいつつ…

諸々の作業をしていたら、ひとまずRegAsmを叩かなくてもExcel 64bitからDLLが見えるようになったので、RegAsmのバッチを出すだけ(自動実行はしない)+VBAもObject型にしない方向で進めようかと思う。


他にもニッチなIT関連要素をまとめていますので、よければ一覧記事もご覧ください。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)