M3 R3.1 capture: scaffold net481 x64 harness + load-check (PASS)
First increment of the native-2023R2-gRPC capture (docs/plans/revision-write-path.md §"R3.1 capture plan"). Loads the mixed-mode aahClientManaged.dll by path (sibling resolver over bin/ + msi-extract/.../Bin/x64) and reflects the connection API — no live contact. load-check result on this box (net481 x64): - aahClientManaged.dll loads cleanly (no missing VC++ runtime / no BadImageFormat) — confirms the self-contained mixed-mode assembly runs without an AVEVA install. - HistorianConnectionMode.Historian = 2 (the 2023 R2 gRPC mode; ClassicHistorian = 1 = legacy) — the value the live-connect step sets on HistorianConnectionArgs.ConnectionMode. - GrpcHistoryClient resolves with RegisterTags + AddNonStreamValues present — the IL-rewrite capture targets are reachable. Standalone net481 project (not in Histsdk.slnx, like NativeTraceHarness). Next: read-only gRPC connect + tag read (first live step, per-action auth), then IL-rewrite + write capture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--
|
||||
Drives the 2023 R2 native (mixed-mode) aahClientManaged.dll over gRPC to capture the two
|
||||
remaining M3 non-streamed-write buffers (regular-tag HistoryService.RegisterTags btTagInfos +
|
||||
TransactionService.AddNonStreamValues btInput) — see docs/plans/revision-write-path.md
|
||||
§"R3.1 capture plan". net481 + x64 because the 2023 R2 aahClientManaged.dll is x64 mixed-mode.
|
||||
The 2023 R2 binaries live OUTSIDE the repo (histsdk-2023r2-analysis) and are loaded by path via
|
||||
Assembly.LoadFrom + a sibling resolver, exactly like AVEVA.Historian.NativeTraceHarness loads
|
||||
current/aahClientManaged.dll. Nothing from the analysis tree is referenced/compiled-against.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net481</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AssemblyName>Grpc2023CaptureHarness</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace AVEVA.Historian.Grpc2023CaptureHarness
|
||||
{
|
||||
/// <summary>
|
||||
/// Capture harness for the M3 R3.1 follow-up. Loads the 2023 R2 mixed-mode
|
||||
/// <c>aahClientManaged.dll</c> by path and drives it over gRPC to emit the two uncaptured
|
||||
/// non-streamed-write buffers (regular-tag <c>RegisterTags</c> <c>btTagInfos</c> +
|
||||
/// <c>AddNonStreamValues</c> <c>btInput</c>) — see <c>docs/plans/revision-write-path.md</c>
|
||||
/// §"R3.1 capture plan". The byte[] payloads are captured by IL-rewriting
|
||||
/// <c>Archestra.Historian.GrpcClient.dll</c>'s <c>GrpcHistoryClient.RegisterTags</c> /
|
||||
/// <c>AddNonStreamValues</c> (separate dnlib step).
|
||||
///
|
||||
/// This file currently implements only the <c>load-check</c> scenario: a local, no-network
|
||||
/// feasibility probe that confirms the mixed-mode assembly loads in this net481 x64 process and
|
||||
/// that the connection API is reflectable (notably the <c>HistorianConnectionMode</c> enum, whose
|
||||
/// gRPC value the live-connect step will need). Live scenarios (open/read/write) are added once
|
||||
/// load-check passes.
|
||||
/// </summary>
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
string scenario = args.FirstOrDefault(a => !a.StartsWith("--", StringComparison.Ordinal)) ?? "load-check";
|
||||
|
||||
// Default to the sibling analysis tree; overridable with --bin <dir>.
|
||||
string repoRoot = FindRepoRoot();
|
||||
string defaultBin = Path.GetFullPath(Path.Combine(repoRoot, "..", "histsdk-2023r2-analysis", "bin"));
|
||||
string binDir = GetOption(args, "--bin") ?? defaultBin;
|
||||
string msiX64 = Path.GetFullPath(Path.Combine(binDir, "..", "msi-extract", "ArchestrA", "Toolkits", "Bin", "x64"));
|
||||
|
||||
string managedDll = Path.Combine(binDir, "aahClientManaged.dll");
|
||||
if (!File.Exists(managedDll))
|
||||
{
|
||||
Console.Error.WriteLine($"aahClientManaged.dll not found at: {managedDll}");
|
||||
Console.Error.WriteLine("Pass --bin <dir> pointing at histsdk-2023r2-analysis/bin.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve siblings from both the core bin dir and the gRPC-runtime msi-extract dir.
|
||||
string[] probeDirs = Directory.Exists(msiX64) ? new[] { binDir, msiX64 } : new[] { binDir };
|
||||
AppDomain.CurrentDomain.AssemblyResolve += (_, e) =>
|
||||
{
|
||||
string simpleName = new AssemblyName(e.Name).Name + ".dll";
|
||||
foreach (string dir in probeDirs)
|
||||
{
|
||||
string candidate = Path.Combine(dir, simpleName);
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
return Assembly.LoadFrom(candidate);
|
||||
}
|
||||
}
|
||||
return null!;
|
||||
};
|
||||
|
||||
switch (scenario)
|
||||
{
|
||||
case "load-check":
|
||||
return LoadCheck(managedDll, probeDirs);
|
||||
default:
|
||||
Console.Error.WriteLine($"Unknown scenario '{scenario}'. Supported: load-check.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int LoadCheck(string managedDll, string[] probeDirs)
|
||||
{
|
||||
Console.WriteLine($"Process: {(Environment.Is64BitProcess ? "x64" : "x86")}, CLR {Environment.Version}");
|
||||
Console.WriteLine($"Probe dirs:");
|
||||
foreach (string d in probeDirs)
|
||||
{
|
||||
Console.WriteLine($" {d} (exists={Directory.Exists(d)})");
|
||||
}
|
||||
|
||||
Assembly asm;
|
||||
try
|
||||
{
|
||||
asm = Assembly.LoadFrom(managedDll);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"LoadFrom FAILED: {ex.GetType().Name}: {ex.Message}");
|
||||
if (ex is BadImageFormatException)
|
||||
{
|
||||
Console.Error.WriteLine(" -> likely an x86/x64 mismatch or missing VC++ runtime (MSVCP140/VCRUNTIME140_1).");
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Loaded: {asm.FullName}");
|
||||
|
||||
Type? access = asm.GetType("ArchestrA.HistorianAccess");
|
||||
Type? connArgs = asm.GetType("ArchestrA.HistorianConnectionArgs");
|
||||
Type? connMode = asm.GetType("ArchestrA.HistorianConnectionMode");
|
||||
Console.WriteLine($"HistorianAccess resolved: {access != null}");
|
||||
Console.WriteLine($"HistorianConnectionArgs resolved:{connArgs != null}");
|
||||
Console.WriteLine($"HistorianConnectionMode resolved:{connMode != null}");
|
||||
|
||||
if (connMode != null && connMode.IsEnum)
|
||||
{
|
||||
Console.WriteLine("HistorianConnectionMode values (the gRPC vs legacy selector):");
|
||||
foreach (object v in Enum.GetValues(connMode))
|
||||
{
|
||||
Console.WriteLine($" {v} = {Convert.ToInt64(v)}");
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm the managed gRPC client (IL-rewrite capture target) is reachable too.
|
||||
try
|
||||
{
|
||||
Assembly grpc = Assembly.Load("Archestra.Historian.GrpcClient");
|
||||
Type? historyClient = grpc.GetType("Archestra.Historian.GrpcClient.GrpcHistoryClient");
|
||||
bool hasRegister = historyClient?.GetMethod("RegisterTags") != null;
|
||||
bool hasAddNonStream = historyClient?.GetMethod("AddNonStreamValues") != null;
|
||||
Console.WriteLine($"GrpcHistoryClient resolved: {historyClient != null} (RegisterTags={hasRegister}, AddNonStreamValues={hasAddNonStream})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"GrpcHistoryClient load note: {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
bool ok = access != null && connArgs != null && connMode != null;
|
||||
Console.WriteLine(ok ? "LOAD-CHECK: PASS" : "LOAD-CHECK: PARTIAL (some types unresolved)");
|
||||
return ok ? 0 : 3;
|
||||
}
|
||||
|
||||
private static string? GetOption(string[] args, string name)
|
||||
{
|
||||
int i = Array.IndexOf(args, name);
|
||||
return i >= 0 && i + 1 < args.Length ? args[i + 1] : null;
|
||||
}
|
||||
|
||||
private static string FindRepoRoot()
|
||||
{
|
||||
string dir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
for (int i = 0; i < 8 && dir != null; i++)
|
||||
{
|
||||
if (File.Exists(Path.Combine(dir, "Histsdk.slnx")))
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
dir = Path.GetDirectoryName(dir.TrimEnd(Path.DirectorySeparatorChar))!;
|
||||
}
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user