C#で作成したMicrosoft Access VBA用のライブラリDLLをMicrosoft Access VBAで使用するには、RegAsm.exeでレジストリに登録する必要があります。
その際に、署名がないアセンブリ(DLL)を登録しようとすると警告が表示されます。
登録はされますので、警告を無視すればいいのですが、アセンブリに署名を行って警告が表示されないようします。
署名は、厳密な名前ツール(Strong Name Tool : sn.exe)で作成したRSA公開鍵暗号キーペアファイルを使って行います。
今回はCore、Comプロジェクトによる階層型のソリューション構成でライブラリDLLを開発しますので、キーペアファイルだけではなく、公開キーも必要になります。
公開キーはsn.exe -tpで表示できるのですが、320桁の16進文字列が複数行に渡って表示されますので、.csprojにコピー&ペーストするとミスが発生しやすいため、その作業を自動的に行うツールSetPublicKeyを作成します。
ソリューション、プロジェクトを作成後、.csprojを編集します。
例では、Product.csprojになります。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>xx.xx.xx.xx</Version>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SetPublicKey</AssemblyName>
<PackAsTool>true</PackAsTool>
<PackageId>ProgressLLC.Tools.SetPublicKey</PackageId>
<ToolCommandName>setpublickey</ToolCommandName>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
ツール本体のProgram.csを編集します。
using System.Security.Cryptography;
using System.Xml.Linq;
namespace ProgressLLC.Tools.SetPublicKey
{
internal class Program
{
static void Main()
{
// ソリューションのディレクトリとソリューション名を取得します。
var solutionDir = Directory.GetCurrentDirectory();
var solutionName = new DirectoryInfo(solutionDir).Name;
// 厳密な名前ツール(sn.exe)で作成したRSA公開鍵暗号キーペアファイルのフルパス名を取得します。
var snkPath = Path.Combine(solutionDir, $"{solutionName}.snk");
if (!File.Exists(snkPath))
{
Console.WriteLine($"{solutionName}.snkが存在しません!");
return;
}
Console.WriteLine("公開キーを取り出しています...");
var snkBytes = File.ReadAllBytes(snkPath);
var parameters = ParseSnkToRsaParameters(snkBytes);
byte[] blob = CreateStrongNamePublicKeyBlob(parameters);
string publicKeyHex = BitConverter.ToString(blob)
.Replace("-", "")
.ToLowerInvariant();
if (publicKeyHex.Length != 320)
{
Console.WriteLine($"公開キーの長さが異常です: {publicKeyHex.Length}バイト");
return;
}
// Coreプロジェクトのcsprojファイルのフルパス名を取得します。
var csprojPath = Path.Combine(solutionDir, "Core", "Core.csproj");
if (!File.Exists(csprojPath))
{
Console.WriteLine($"Core.csprojが存在しません!: {csprojPath}");
return;
}
Console.WriteLine("Core.csprojに公開キーを埋め込みます...");
var doc = XDocument.Load(csprojPath);
var publicKeyElement = doc.Descendants("PublicKey").FirstOrDefault();
if (publicKeyElement == null)
{
Console.WriteLine("PublicKeyノードが見つかりません!");
return;
}
publicKeyElement.Value = publicKeyHex;
doc.Save(csprojPath);
Console.WriteLine("公開キーを埋め込みました。");
}
/// <summary>
/// sn.exe -kで出力した.snk(CAPI PRIVATEKEYBLOB)から
/// sn.exe -pで取り出した公開鍵用RSAParametersを取得します。
/// </summary>
static RSAParameters ParseSnkToRsaParameters(byte[] snkBytes)
{
var bitlen = (int)BitConverter.ToUInt32(snkBytes, 12);
var modulusByteLen = bitlen / 8;
var exponentLe = new byte[4];
Array.Copy(snkBytes, 16, exponentLe, 0, 4);
var exponentValue = BitConverter.ToUInt32(exponentLe, 0);
var exponentBe = BitConverter.GetBytes(exponentValue);
Array.Reverse(exponentBe);
var exponentTrimmed = exponentBe.SkipWhile(b => b == 0).ToArray();
if (exponentTrimmed.Length == 0)
exponentTrimmed = [0];
var modulusFromBlob = new byte[modulusByteLen];
Array.Copy(snkBytes, 20, modulusFromBlob, 0, modulusByteLen);
Array.Reverse(modulusFromBlob);
return new RSAParameters
{
Modulus = modulusFromBlob,
Exponent = exponentTrimmed
};
}
/// <summary>
/// sn.exe -tpで表示される形式(PublicKeyBlob: ヘッダ12バイト+CAPI公開鍵)のblobを生成します。
/// </summary>
static byte[] CreateStrongNamePublicKeyBlob(RSAParameters p)
{
if (p.Modulus == null)
throw new ArgumentNullException(nameof(p), $"{nameof(p.Modulus)} is null.");
if (p.Exponent == null)
throw new ArgumentNullException(nameof(p), $"{nameof(p.Exponent)} is null.");
var capiLength = 8 + 12 + p.Modulus.Length; // PUBLICKEYSTRUC + RSAPUBKEY + modulus
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
// PublicKeyBlob ヘッダ(12バイト)= sn.exe -pが出力する先頭
const uint CALG_RSA_SIGN = 0x00002400u;
const uint CALG_SHA1 = 0x00008004u;
bw.Write(CALG_RSA_SIGN); // SigAlgId
bw.Write(CALG_SHA1); // HashAlgId
bw.Write((uint)capiLength); // cbPublicKey(以下のCAPI部分の長さ)
// CAPI PUBLICKEYBLOB
bw.Write((byte)0x06); // PUBLICKEYBLOB
bw.Write((byte)0x02); // Version
bw.Write((ushort)0x0000); // Reserved
bw.Write((uint)0x00002400); // CALG_RSA_SIGN
bw.Write(0x31415352u); // "RSA1"
bw.Write((uint)(p.Modulus.Length * 8)); // bitlen
bw.Write(ToUInt32BigEndian(p.Exponent)); // exponent (little-endian)
var modulusLE = p.Modulus.Reverse().ToArray();
bw.Write(modulusLE);
return ms.ToArray();
}
/// <summary>
/// 4バイトのリトルエンディアン配列をビッグエンディアンのUInt32に変換します。
/// </summary>
static uint ToUInt32BigEndian(byte[] bytes)
{
var padded = new byte[4];
Array.Copy(bytes, 0, padded, 4 - bytes.Length, bytes.Length);
Array.Reverse(padded);
return BitConverter.ToUInt32(padded, 0);
}
}
}
.NET ToolのビルドはNuGetパッケージを作成します。
Releaseビルドで実行します。
パッケージのビルドVisual Studioのツールバーから『ビルド(B)』➡『パッケージProduct(P)』でNuGetパッケージを作成します。
開発者用PowerShellでビルドしたNuGetパッケージをグローバルインストールします。
開発者用PowerShellの起動Visual Studioのツールバーから『ツール(T)』➡『コマンドライン(L)』➡『開発者用PowerShell(P)』で開発者用PowerShellを起動します。
dotnet tool install --global --add-source .\Product\bin\Release ProgressLLC.Tools.SetPublicKey
アップグレードは、ビルド後、下記のコマンドで行います。
.csprojのVersionを必ず上げてからビルドします。
dotnet tool update --global ProgressLLC.Tools.SetPublicKey