Contents
はい、今日もニッチな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関連要素をまとめていますので、よければ一覧記事もご覧ください。