diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs
new file mode 100644
index 00000000000000..930cb017814d17
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+/// <summary>
+/// A registry of all the contracts that may be provided by a target.
+/// </summary>
+internal abstract class ContractRegistry
+{
+    /// <summary>
+    /// Gets an instance of the Exception contract for the target.
+    /// </summary>
+    public abstract IException Exception { get;}
+    /// <summary>
+    /// Gets an instance of the Loader contract for the target.
+    /// </summary>
+    public abstract ILoader Loader { get; }
+    /// <summary>
+    /// Gets an instance of the EcmaMetadata contract for the target.
+    /// </summary>
+    public abstract IEcmaMetadata EcmaMetadata { get; }
+    /// <summary>
+    /// Gets an instance of the Object contract for the target.
+    /// </summary>
+    public abstract IObject Object { get; }
+    /// <summary>
+    /// Gets an instance of the Thread contract for the target.
+    /// </summary>
+    public abstract IThread Thread { get; }
+    /// <summary>
+    /// Gets an instance of the RuntimeTypeSystem contract for the target.
+    /// </summary>
+    public abstract IRuntimeTypeSystem RuntimeTypeSystem { get; }
+    /// <summary>
+    /// Gets an instance of the DacStreams contract for the target.
+    /// </summary>
+    public abstract IDacStreams DacStreams { get; }
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/IContract.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs
similarity index 75%
rename from src/native/managed/cdacreader/src/Contracts/IContract.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs
index 1a78ce2a429209..43dadfb923b3cc 100644
--- a/src/native/managed/cdacreader/src/Contracts/IContract.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs
@@ -8,5 +8,4 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
 internal interface IContract
 {
     static virtual string Name => throw new NotImplementedException();
-    static virtual IContract Create(Target target, int version) => throw new NotImplementedException();
 }
diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDacStreams.cs
similarity index 55%
rename from src/native/managed/cdacreader/src/Contracts/DacStreams.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDacStreams.cs
index 167c7f89ad2bfd..76c33b469aee77 100644
--- a/src/native/managed/cdacreader/src/Contracts/DacStreams.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDacStreams.cs
@@ -1,26 +1,13 @@
-﻿// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Microsoft.Diagnostics.DataContractReader.Contracts;
 
 internal interface IDacStreams : IContract
 {
     static string IContract.Name { get; } = nameof(DacStreams);
-    static IContract IContract.Create(Target target, int version)
-    {
-        return version switch
-        {
-            1 => new DacStreams_1(target),
-            _ => default(DacStreams),
-        };
-    }
-
     public virtual string? StringFromEEAddress(TargetPointer address) => throw new NotImplementedException();
 }
 
diff --git a/src/native/managed/cdacreader/src/Contracts/EcmaMetadata.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs
similarity index 76%
rename from src/native/managed/cdacreader/src/Contracts/EcmaMetadata.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs
index c7d8e5412b864e..d2fe21ff33823f 100644
--- a/src/native/managed/cdacreader/src/Contracts/EcmaMetadata.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs
@@ -9,17 +9,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
 internal interface IEcmaMetadata : IContract
 {
     static string IContract.Name { get; } = nameof(EcmaMetadata);
-    static IContract IContract.Create(Target target, int version)
-    {
-        return version switch
-        {
-            1 => new EcmaMetadata_1(target),
-            _ => default(EcmaMetadata),
-        };
-    }
-
     public virtual TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) => throw new NotImplementedException();
-
     public virtual MetadataReader? GetMetadata(ModuleHandle module) => throw new NotImplementedException();
 }
 
diff --git a/src/native/managed/cdacreader/src/Contracts/Exception.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IException.cs
similarity index 82%
rename from src/native/managed/cdacreader/src/Contracts/Exception.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IException.cs
index 2ad3d10faeb655..66c2af846cd8c3 100644
--- a/src/native/managed/cdacreader/src/Contracts/Exception.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IException.cs
@@ -18,14 +18,6 @@ internal record struct ExceptionData(
 internal interface IException : IContract
 {
     static string IContract.Name { get; } = nameof(Exception);
-    static IContract IContract.Create(Target target, int version)
-    {
-        return version switch
-        {
-            1 => new Exception_1(target),
-            _ => default(Exception),
-        };
-    }
 
     public virtual TargetPointer GetNestedExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) => throw new NotImplementedException();
     public virtual ExceptionData GetExceptionData(TargetPointer managedException) => throw new NotImplementedException();
diff --git a/src/native/managed/cdacreader/src/Contracts/Loader.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs
similarity index 90%
rename from src/native/managed/cdacreader/src/Contracts/Loader.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs
index a71bdd4d129515..4c3be1e2f0a8a5 100644
--- a/src/native/managed/cdacreader/src/Contracts/Loader.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs
@@ -35,14 +35,6 @@ internal record struct ModuleLookupTables(
 internal interface ILoader : IContract
 {
     static string IContract.Name => nameof(Loader);
-    static IContract IContract.Create(Target target, int version)
-    {
-        return version switch
-        {
-            1 => new Loader_1(target),
-            _ => default(Loader),
-        };
-    }
 
     public virtual ModuleHandle GetModuleHandle(TargetPointer modulePointer) => throw new NotImplementedException();
 
diff --git a/src/native/managed/cdacreader/src/Contracts/Object.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs
similarity index 55%
rename from src/native/managed/cdacreader/src/Contracts/Object.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs
index 944b97388b69ad..780ce9b4f1bdaa 100644
--- a/src/native/managed/cdacreader/src/Contracts/Object.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IObject.cs
@@ -8,23 +8,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
 internal interface IObject : IContract
 {
     static string IContract.Name { get; } = nameof(Object);
-    static IContract IContract.Create(Target target, int version)
-    {
-        ulong methodTableOffset = (ulong)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset;
-        byte objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask);
-        TargetPointer stringMethodTable = target.ReadPointer(
-            target.ReadGlobalPointer(Constants.Globals.StringMethodTable));
-        TargetPointer syncTableEntries = target.ReadPointer(
-            target.ReadGlobalPointer(Constants.Globals.SyncTableEntries));
-        return version switch
-        {
-            1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable, syncTableEntries),
-            _ => default(Object),
-        };
-    }
-
     public virtual TargetPointer GetMethodTableAddress(TargetPointer address) => throw new NotImplementedException();
-
     public virtual string GetStringValue(TargetPointer address) => throw new NotImplementedException();
     public virtual TargetPointer GetArrayData(TargetPointer address, out uint count, out TargetPointer boundsStart, out TargetPointer lowerBounds) => throw new NotImplementedException();
     public virtual bool GetBuiltInComData(TargetPointer address, out TargetPointer rcw, out TargetPointer ccw) => throw new NotImplementedException();
diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs
similarity index 93%
rename from src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs
index 67fd6db279604e..ddc2744d1d307b 100644
--- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs
@@ -76,17 +76,6 @@ public enum ArrayFunctionType
 internal interface IRuntimeTypeSystem : IContract
 {
     static string IContract.Name => nameof(RuntimeTypeSystem);
-    static IContract IContract.Create(Target target, int version)
-    {
-        TargetPointer targetPointer = target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable);
-        TargetPointer freeObjectMethodTable = target.ReadPointer(targetPointer);
-        ulong methodDescAlignment = target.ReadGlobal<ulong>(Constants.Globals.MethodDescAlignment);
-        return version switch
-        {
-            1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable, methodDescAlignment),
-            _ => default(RuntimeTypeSystem),
-        };
-    }
 
     #region TypeHandle inspection APIs
     public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException();
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs
new file mode 100644
index 00000000000000..cca1c522f10174
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal record struct ThreadStoreData(
+    int ThreadCount,
+    TargetPointer FirstThread,
+    TargetPointer FinalizerThread,
+    TargetPointer GCThread);
+
+internal record struct ThreadStoreCounts(
+    int UnstartedThreadCount,
+    int BackgroundThreadCount,
+    int PendingThreadCount,
+    int DeadThreadCount);
+
+[Flags]
+internal enum ThreadState
+{
+    Unknown             = 0x00000000,
+    Hijacked            = 0x00000080,   // Return address has been hijacked
+    Background          = 0x00000200,   // Thread is a background thread
+    Unstarted           = 0x00000400,   // Thread has never been started
+    Dead                = 0x00000800,   // Thread is dead
+    ThreadPoolWorker    = 0x01000000,   // Thread is a thread pool worker thread
+}
+
+internal record struct ThreadData(
+    uint Id,
+    TargetNUInt OSId,
+    ThreadState State,
+    bool PreemptiveGCDisabled,
+    TargetPointer AllocContextPointer,
+    TargetPointer AllocContextLimit,
+    TargetPointer Frame,
+    TargetPointer FirstNestedException,
+    TargetPointer TEB,
+    TargetPointer LastThrownObjectHandle,
+    TargetPointer NextThread);
+
+internal interface IThread : IContract
+{
+    static string IContract.Name { get; } = nameof(Thread);
+
+    public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException();
+    public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException();
+    public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException();
+}
+
+internal readonly struct Thread : IThread
+{
+    // Everything throws NotImplementedException
+}
diff --git a/src/native/managed/cdacreader/src/Data/IData.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Data/IData.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/IData.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Data/IData.cs
diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/DataType.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/IContractFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/IContractFactory.cs
new file mode 100644
index 00000000000000..4f1600ab0e7a73
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/IContractFactory.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+internal interface IContractFactory<out TContract> where TContract : Contracts.IContract
+{
+    TContract CreateContract(Target target, int version);
+}
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Microsoft.Diagnostics.DataContractReader.Abstractions.csproj b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Microsoft.Diagnostics.DataContractReader.Abstractions.csproj
new file mode 100644
index 00000000000000..142d604c464f84
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Microsoft.Diagnostics.DataContractReader.Abstractions.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
+    <RootNamespace>Microsoft.Diagnostics.DataContractReader</RootNamespace>
+    <Nullable>enable</Nullable>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <!-- Do not produce a public package. This ships as part of the runtime -->
+    <IsShippingPackage>false</IsShippingPackage>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader.Tests" />
+    <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader.Contracts" />
+    <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader" />
+    <InternalsVisibleTo Include="cdacreader" Condition="'$(TargetsWindows)' == 'true'"/>
+    <InternalsVisibleTo Include="libcdacreader" Condition="'$(TargetsWindows)' != 'true'"/>
+  </ItemGroup>
+</Project>
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs
new file mode 100644
index 00000000000000..46128d9dc59e0d
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs
@@ -0,0 +1,186 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+/// <summary>
+/// Representation of the target under inspection
+/// </summary>
+/// <remarks>
+/// This class provides APIs used by contracts for reading from the target and getting type and globals
+/// information. Like the contracts themselves in cdacreader, these are throwing APIs. Any callers at the boundaries
+/// (for example, unmanaged entry points, COM) should handle any exceptions.
+/// </remarks>
+internal abstract class Target
+{
+    /// <summary>
+    /// Pointer size of the target
+    /// </summary>
+    public abstract int PointerSize { get; }
+    /// <summary>
+    ///  Endianness of the target
+    /// </summary>
+    public abstract bool IsLittleEndian { get; }
+
+    /// <summary>
+    /// Reads a well-known global pointer value from the target process
+    /// </summary>
+    /// <param name="global">The name of the global</param>
+    /// <returns>The value of the global</returns>
+    public abstract TargetPointer ReadGlobalPointer(string global);
+
+    /// <summary>
+    /// Read a pointer from the target in target endianness
+    /// </summary>
+    /// <param name="address">Address to start reading from</param>
+    /// <returns>Pointer read from the target</returns>}
+    public abstract TargetPointer ReadPointer(ulong address);
+
+    /// <summary>
+    /// Read some bytes from the target
+    /// </summary>
+    /// <param name="address">The address where to start reading</param>
+    /// <param name="buffer">Destination to copy the bytes, the number of bytes to read is the span length</param>
+    public abstract void ReadBuffer(ulong address, Span<byte> buffer);
+
+    /// <summary>
+    /// Read a null-terminated UTF-8 string from the target
+    /// </summary>
+    /// <param name="address">Address to start reading from</param>
+    /// <returns>String read from the target</returns>}
+    public abstract string ReadUtf8String(ulong address);
+
+    /// <summary>
+    /// Read a null-terminated UTF-16 string from the target in target endianness
+    /// </summary>
+    /// <param name="address">Address to start reading from</param>
+    /// <returns>String read from the target</returns>}
+    public abstract string ReadUtf16String(ulong address);
+
+    /// <summary>
+    /// Read a native unsigned integer from the target in target endianness
+    /// </summary>
+    /// <param name="address">Address to start reading from</param>
+    /// <returns>Value read from the target</returns>
+    public abstract TargetNUInt ReadNUInt(ulong address);
+
+    /// <summary>
+    /// Read a well known global from the target process as a number in the target endianness
+    /// </summary>
+    /// <typeparam name="T">The numeric type to be read</typeparam>
+    /// <param name="name">The name of the global</param>
+    /// <returns>A numeric value</returns>
+    public abstract T ReadGlobal<T>(string name) where T : struct, INumber<T>;
+
+    /// <summary>
+    /// Read a value from the target in target endianness
+    /// </summary>
+    /// <typeparam name="T">Type of value to read</typeparam>
+    /// <param name="address">Address to start reading from</param>
+    /// <returns>Value read from the target</returns>
+    public abstract T Read<T>(ulong address) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>;
+
+    /// <summary>
+    /// Read a target pointer from a span of bytes
+    /// </summary>
+    /// <param name="bytes">The span of bytes to read from</param>
+    /// <returns>The target pointer read from the span</returns>
+    public abstract TargetPointer ReadPointerFromSpan(ReadOnlySpan<byte> bytes);
+
+
+    /// <summary>
+    /// Returns true if the given pointer is aligned to the pointer size of the target
+    /// </summary>
+    /// <param name="pointer">A target pointer value</param>
+    /// <returns></returns>
+    public abstract bool IsAlignedToPointerSize(TargetPointer pointer);
+
+    /// <summary>
+    /// Returns the information about the given well-known data type in the target process
+    /// </summary>
+    /// <param name="type">The name of the well known type</param>
+    /// <returns>The information about the given type in the target process</returns>
+    public abstract TypeInfo GetTypeInfo(DataType type);
+
+    /// <summary>
+    /// Get the data cache for the target
+    /// </summary>
+    public abstract IDataCache ProcessedData { get; }
+
+    /// <summary>
+    /// Holds a snapshot of the target's structured data
+    /// </summary>
+    public interface IDataCache
+    {
+        /// <summary>
+        /// Read a value from the target and cache it, or return the currently cached value
+        /// </summary>
+        /// <typeparam name="T">The type  of data to be read</typeparam>
+        /// <param name="address">The address in the target where the data resides</param>
+        /// <returns>The value that has been read from the target</returns>
+        T GetOrAdd<T>(TargetPointer address) where T : Data.IData<T>;
+        /// <summary>
+        /// Return the cached value for the given address, or null if no value is cached
+        /// </summary>
+        /// <typeparam name="T">The type of the data to be read</typeparam>
+        /// <param name="address">The address in the target where the data resides</param>
+        /// <param name="data">On return, set to the cached data value, or null if the data hasn't been cached yet.</param>
+        /// <returns>True if a copy of the data is cached, or false otherwise</returns>
+        bool TryGet<T>(ulong address, [NotNullWhen(true)] out T? data);
+    }
+
+    /// <summary>
+    /// Information about a well-known data type in the target process
+    /// </summary>
+    public readonly record struct TypeInfo
+    {
+        /// <summary>
+        /// The stride of the type in the target process.
+        /// This is a value that can be used to calculate the byte offset of an element in an array of this type.
+        /// </summary>
+        public uint? Size { get; init; }
+        /// <summary>
+        /// Information about the well-known fields of the type in the target process
+        /// </summary>
+        public readonly IReadOnlyDictionary<string, FieldInfo> Fields
+        {
+            get;
+            init;
+        }
+
+        public TypeInfo()
+        {
+            Fields = new Dictionary<string, FieldInfo>();
+        }
+    }
+
+    /// <summary>
+    /// Information about a well-known field of a well-known data type in the target process
+    /// </summary>
+    public readonly record struct FieldInfo
+    {
+        /// <summary>
+        /// The byte offset of the field in an instance of the type in the target process
+        /// </summary>
+        public int Offset {get; init; }
+        /// <summary>
+        /// The well known data type of the field in the target process
+        /// </summary>
+        public readonly DataType Type {get; init;}
+        /// <summary>
+        /// The name of the well known data type of the field in the target process, or null
+        /// if the target data descriptor did not record a name
+        /// </summary>
+        public readonly string? TypeName {get; init; }
+    }
+
+    /// <summary>
+    /// A cache of the contracts for the target process
+    /// </summary>
+    public abstract ContractRegistry Contracts { get; }
+}
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs
new file mode 100644
index 00000000000000..bbacb9a50b94fd
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetNUInt.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+public readonly struct TargetNUInt
+{
+    public readonly ulong Value;
+    public TargetNUInt(ulong value) => Value = value;
+}
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetPointer.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetPointer.cs
new file mode 100644
index 00000000000000..9aef236554a38b
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetPointer.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+public readonly struct TargetPointer : IEquatable<TargetPointer>
+{
+    public static TargetPointer Null = new(0);
+    public static TargetPointer Max32Bit = new(uint.MaxValue);
+    public static TargetPointer Max64Bit = new(ulong.MaxValue);
+
+    public readonly ulong Value;
+    public TargetPointer(ulong value) => Value = value;
+
+    public static implicit operator ulong(TargetPointer p) => p.Value;
+    public static implicit operator TargetPointer(ulong v) => new TargetPointer(v);
+
+    public static bool operator ==(TargetPointer left, TargetPointer right) => left.Value == right.Value;
+    public static bool operator !=(TargetPointer left, TargetPointer right) => left.Value != right.Value;
+
+    public override bool Equals(object? obj) => obj is TargetPointer pointer && Equals(pointer);
+    public bool Equals(TargetPointer other) => Value == other.Value;
+
+    public override int GetHashCode() => Value.GetHashCode();
+}
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetSpan.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetSpan.cs
new file mode 100644
index 00000000000000..8ecb093b49cb09
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/TargetSpan.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+public readonly struct TargetSpan
+{
+    public TargetSpan(TargetPointer address, ulong size)
+    {
+        Address = address;
+        Size = size;
+    }
+
+    public TargetPointer Address { get; }
+    public ulong Size { get; }
+}
diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Constants.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DacStreamsFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DacStreamsFactory.cs
new file mode 100644
index 00000000000000..e93fe712f00586
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DacStreamsFactory.cs
@@ -0,0 +1,18 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal sealed class DacStreamsFactory : IContractFactory<IDacStreams>
+{
+    IDacStreams IContractFactory<IDacStreams>.CreateContract(Target target, int version)
+    {
+        return version switch
+        {
+            1 => new DacStreams_1(target),
+            _ => default(DacStreams),
+        };
+    }
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DacStreams_1.cs
similarity index 99%
rename from src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DacStreams_1.cs
index 688ec1d5b89844..0a6eb4d34de394 100644
--- a/src/native/managed/cdacreader/src/Contracts/DacStreams_1.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DacStreams_1.cs
@@ -3,9 +3,7 @@
 
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Text;
-using System.Threading.Tasks;
 using Microsoft.Diagnostics.DataContractReader.Data;
 
 namespace Microsoft.Diagnostics.DataContractReader.Contracts;
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadataFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadataFactory.cs
new file mode 100644
index 00000000000000..029335787f3410
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadataFactory.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection.Metadata;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal sealed class EcmaMetadataFactory : IContractFactory<IEcmaMetadata>
+{
+    IEcmaMetadata IContractFactory<IEcmaMetadata>.CreateContract(Target target, int version)
+    {
+        return version switch
+        {
+            1 => new EcmaMetadata_1(target),
+            _ => default(EcmaMetadata),
+        };
+    }
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/EcmaMetadata_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/EcmaMetadata_1.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExceptionFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExceptionFactory.cs
new file mode 100644
index 00000000000000..209edd339d6a55
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExceptionFactory.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal sealed class ExceptionFactory : IContractFactory<IException>
+{
+    IException IContractFactory<IException>.CreateContract(Target target, int version)
+    {
+        return version switch
+        {
+            1 => new Exception_1(target),
+            _ => default(Exception),
+        };
+    }
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/Exception_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Exception_1.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/Exception_1.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Exception_1.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/LoaderFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/LoaderFactory.cs
new file mode 100644
index 00000000000000..5c13186b4dce93
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/LoaderFactory.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal sealed class LoaderFactory : IContractFactory<ILoader>
+{
+    ILoader IContractFactory<ILoader>.CreateContract(Target target, int version)
+    {
+        return version switch
+        {
+            1 => new Loader_1(target),
+            _ => default(Loader),
+        };
+    }
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/Loader_1.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs
new file mode 100644
index 00000000000000..4949a9f52fd729
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectFactory.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal sealed class ObjectFactory : IContractFactory<IObject>
+{
+    IObject IContractFactory<IObject>.CreateContract(Target target, int version)
+    {
+        ulong methodTableOffset = (ulong)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset;
+        byte objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask);
+        TargetPointer stringMethodTable = target.ReadPointer(
+            target.ReadGlobalPointer(Constants.Globals.StringMethodTable));
+        TargetPointer syncTableEntries = target.ReadPointer(
+            target.ReadGlobalPointer(Constants.Globals.SyncTableEntries));
+        return version switch
+        {
+            1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable, syncTableEntries),
+            _ => default(Object),
+        };
+    }
+
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/Object_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/Object_1.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs
new file mode 100644
index 00000000000000..8590f3e11a7cf8
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts;
+
+internal sealed class RuntimeTypeSystemFactory : IContractFactory<IRuntimeTypeSystem>
+{
+    IRuntimeTypeSystem IContractFactory<IRuntimeTypeSystem>.CreateContract(Target target, int version)
+    {
+        TargetPointer targetPointer = target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable);
+        TargetPointer freeObjectMethodTable = target.ReadPointer(targetPointer);
+        ulong methodDescAlignment = target.ReadGlobal<ulong>(Constants.Globals.MethodDescAlignment);
+        return version switch
+        {
+            1 => new RuntimeTypeSystem_1(target, freeObjectMethodTable, methodDescAlignment),
+            _ => default(RuntimeTypeSystem),
+        };
+    }
+}
diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.MethodTableFlags.cs
diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs
diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs
diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1_Helpers.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1_Helpers.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1_Helpers.cs
diff --git a/src/native/managed/cdacreader/src/Contracts/Thread.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread.cs
similarity index 68%
rename from src/native/managed/cdacreader/src/Contracts/Thread.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread.cs
index 88308b23c1ee4e..312c300e5b74fa 100644
--- a/src/native/managed/cdacreader/src/Contracts/Thread.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread.cs
@@ -5,46 +5,9 @@
 
 namespace Microsoft.Diagnostics.DataContractReader.Contracts;
 
-internal record struct ThreadStoreData(
-    int ThreadCount,
-    TargetPointer FirstThread,
-    TargetPointer FinalizerThread,
-    TargetPointer GCThread);
-
-internal record struct ThreadStoreCounts(
-    int UnstartedThreadCount,
-    int BackgroundThreadCount,
-    int PendingThreadCount,
-    int DeadThreadCount);
-
-[Flags]
-internal enum ThreadState
-{
-    Unknown             = 0x00000000,
-    Hijacked            = 0x00000080,   // Return address has been hijacked
-    Background          = 0x00000200,   // Thread is a background thread
-    Unstarted           = 0x00000400,   // Thread has never been started
-    Dead                = 0x00000800,   // Thread is dead
-    ThreadPoolWorker    = 0x01000000,   // Thread is a thread pool worker thread
-}
-
-internal record struct ThreadData(
-    uint Id,
-    TargetNUInt OSId,
-    ThreadState State,
-    bool PreemptiveGCDisabled,
-    TargetPointer AllocContextPointer,
-    TargetPointer AllocContextLimit,
-    TargetPointer Frame,
-    TargetPointer FirstNestedException,
-    TargetPointer TEB,
-    TargetPointer LastThrownObjectHandle,
-    TargetPointer NextThread);
-
-internal interface IThread : IContract
+internal sealed class ThreadFactory : IContractFactory<IThread>
 {
-    static string IContract.Name { get; } = nameof(Thread);
-    static IContract IContract.Create(Target target, int version)
+    IThread IContractFactory<IThread>.CreateContract(Target target, int version)
     {
         TargetPointer threadStorePointer = target.ReadGlobalPointer(Constants.Globals.ThreadStore);
         TargetPointer threadStore = target.ReadPointer(threadStorePointer);
@@ -54,15 +17,6 @@ static IContract IContract.Create(Target target, int version)
             _ => default(Thread),
         };
     }
-
-    public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException();
-    public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException();
-    public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException();
-}
-
-internal readonly struct Thread : IThread
-{
-    // Everything throws NotImplementedException
 }
 
 internal readonly struct Thread_1 : IThread
diff --git a/src/native/managed/cdacreader/src/Data/Array.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Array.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/Array.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Array.cs
diff --git a/src/native/managed/cdacreader/src/Data/DynamicMetadata.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DynamicMetadata.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/DynamicMetadata.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DynamicMetadata.cs
diff --git a/src/native/managed/cdacreader/src/Data/EEClass.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs
similarity index 95%
rename from src/native/managed/cdacreader/src/Data/EEClass.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs
index fd0ad35adbf9d6..c9e62eb39e85ef 100644
--- a/src/native/managed/cdacreader/src/Data/EEClass.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs
@@ -3,7 +3,7 @@
 
 namespace Microsoft.Diagnostics.DataContractReader.Data;
 
-public sealed class EEClass : IData<EEClass>
+internal sealed class EEClass : IData<EEClass>
 {
     static EEClass IData<EEClass>.Create(Target target, TargetPointer address) => new EEClass(target, address);
     public EEClass(Target target, TargetPointer address)
@@ -33,7 +33,7 @@ public EEClass(Target target, TargetPointer address)
     public ushort NumNonVirtualSlots { get; init; }
 }
 
-public sealed class ArrayClass : IData<ArrayClass>
+internal sealed class ArrayClass : IData<ArrayClass>
 {
     static ArrayClass IData<ArrayClass>.Create(Target target, TargetPointer address) => new ArrayClass(target, address);
     public ArrayClass(Target target, TargetPointer address)
diff --git a/src/native/managed/cdacreader/src/Data/Exception.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Exception.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/Exception.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Exception.cs
diff --git a/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/ExceptionInfo.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.cs
diff --git a/src/native/managed/cdacreader/src/Data/GCAllocContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GCAllocContext.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/GCAllocContext.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GCAllocContext.cs
diff --git a/src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GenericsDictInfo.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/GenericsDictInfo.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GenericsDictInfo.cs
diff --git a/src/native/managed/cdacreader/src/Data/InteropSyncBlockInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/InteropSyncBlockInfo.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs
diff --git a/src/native/managed/cdacreader/src/Data/MethodDesc.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/MethodDesc.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs
diff --git a/src/native/managed/cdacreader/src/Data/MethodDescChunk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/MethodDescChunk.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs
diff --git a/src/native/managed/cdacreader/src/Data/MethodTable.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTable.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/MethodTable.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTable.cs
diff --git a/src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTableAuxiliaryData.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/MethodTableAuxiliaryData.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTableAuxiliaryData.cs
diff --git a/src/native/managed/cdacreader/src/Data/Module.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/Module.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs
diff --git a/src/native/managed/cdacreader/src/Data/ModuleLookupMap.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ModuleLookupMap.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/ModuleLookupMap.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ModuleLookupMap.cs
diff --git a/src/native/managed/cdacreader/src/Data/ObjectHandle.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjectHandle.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/ObjectHandle.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjectHandle.cs
diff --git a/src/native/managed/cdacreader/src/Data/RuntimeThreadLocals.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeThreadLocals.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/RuntimeThreadLocals.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeThreadLocals.cs
diff --git a/src/native/managed/cdacreader/src/Data/String.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/String.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/String.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/String.cs
diff --git a/src/native/managed/cdacreader/src/Data/SyncBlock.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlock.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/SyncBlock.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncBlock.cs
diff --git a/src/native/managed/cdacreader/src/Data/SyncTableEntry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncTableEntry.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/SyncTableEntry.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SyncTableEntry.cs
diff --git a/src/native/managed/cdacreader/src/Data/Thread.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/Thread.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs
diff --git a/src/native/managed/cdacreader/src/Data/ThreadStore.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ThreadStore.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/ThreadStore.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ThreadStore.cs
diff --git a/src/native/managed/cdacreader/src/Data/TypeDesc.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TypeDesc.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Data/TypeDesc.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TypeDesc.cs
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj
new file mode 100644
index 00000000000000..70748b48d2bc35
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
+    <RootNamespace>Microsoft.Diagnostics.DataContractReader</RootNamespace>
+    <Nullable>enable</Nullable>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <!-- Do not produce a public package. This ships as part of the runtime -->
+    <IsShippingPackage>false</IsShippingPackage>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader.Tests" />
+    <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader" />
+    <InternalsVisibleTo Include="cdacreader" Condition="'$(TargetsWindows)' == 'true'"/>
+    <InternalsVisibleTo Include="libcdacreader" Condition="'$(TargetsWindows)' != 'true'"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.Diagnostics.DataContractReader.Abstractions\Microsoft.Diagnostics.DataContractReader.Abstractions.csproj" />
+  </ItemGroup>
+</Project>
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs
new file mode 100644
index 00000000000000..0ff37597326214
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+
+namespace Microsoft.Diagnostics.DataContractReader;
+
+/// <summary>
+/// Contract registry that caches contracts for a target
+/// </summary>
+internal sealed class CachingContractRegistry : ContractRegistry
+{
+    public delegate bool TryGetContractVersionDelegate(string contractName, out int version);
+    // Contracts that have already been created for a target.
+    // Items should not be removed from this, only added.
+    private readonly Dictionary<Type, IContract> _contracts = [];
+    private readonly Dictionary<Type, IContractFactory<IContract>> _factories;
+    private readonly Target _target;
+    private readonly TryGetContractVersionDelegate _tryGetContractVersion;
+
+    public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryGetContractVersion, Action<Dictionary<Type, IContractFactory<IContract>>>? configureFactories = null)
+    {
+        _target = target;
+        _tryGetContractVersion = tryGetContractVersion;
+        _factories = new () {
+            [typeof(IException)] = new ExceptionFactory(),
+            [typeof(ILoader)] = new LoaderFactory(),
+            [typeof(IEcmaMetadata)] = new EcmaMetadataFactory(),
+            [typeof(IObject)] = new ObjectFactory(),
+            [typeof(IThread)] = new ThreadFactory(),
+            [typeof(IRuntimeTypeSystem)] = new RuntimeTypeSystemFactory(),
+            [typeof(IDacStreams)] = new DacStreamsFactory(),
+        };
+        configureFactories?.Invoke(_factories);
+    }
+
+    public override IException Exception => GetContract<IException>();
+    public override ILoader Loader => GetContract<ILoader>();
+    public override IEcmaMetadata EcmaMetadata => GetContract<IEcmaMetadata>();
+    public override IObject Object => GetContract<IObject>();
+    public override IThread Thread => GetContract<IThread>();
+    public override IRuntimeTypeSystem RuntimeTypeSystem => GetContract<IRuntimeTypeSystem>();
+    public override IDacStreams DacStreams => GetContract<IDacStreams>();
+
+    private TContract GetContract<TContract>() where TContract : IContract
+    {
+        if (_contracts.TryGetValue(typeof(TContract), out IContract? contractMaybe))
+            return (TContract)contractMaybe;
+
+        if (!_tryGetContractVersion(TContract.Name, out int version))
+            throw new NotImplementedException();
+
+        if (!_factories.TryGetValue(typeof(TContract), out IContractFactory<IContract>? factory))
+            throw new NotImplementedException();
+        // Create and register the contract
+        TContract contract = (TContract)factory.CreateContract(_target, version);
+        if (_contracts.TryAdd(typeof(TContract), contract))
+            return contract;
+
+        // Contract was already registered by someone else
+        return (TContract)_contracts[typeof(TContract)];
+    }
+}
diff --git a/src/native/managed/cdacreader/src/ContractDescriptorParser.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs
similarity index 99%
rename from src/native/managed/cdacreader/src/ContractDescriptorParser.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs
index a82235731e4a07..9d351328daafe2 100644
--- a/src/native/managed/cdacreader/src/ContractDescriptorParser.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs
@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics.Contracts;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs
similarity index 82%
rename from src/native/managed/cdacreader/src/Target.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs
index afa989a66f4b00..3c3b4c67118393 100644
--- a/src/native/managed/cdacreader/src/Target.cs
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs
@@ -12,45 +12,6 @@
 
 namespace Microsoft.Diagnostics.DataContractReader;
 
-public readonly struct TargetPointer : IEquatable<TargetPointer>
-{
-    public static TargetPointer Null = new(0);
-    public static TargetPointer Max32Bit = new(uint.MaxValue);
-    public static TargetPointer Max64Bit = new(ulong.MaxValue);
-
-    public readonly ulong Value;
-    public TargetPointer(ulong value) => Value = value;
-
-    public static implicit operator ulong(TargetPointer p) => p.Value;
-    public static implicit operator TargetPointer(ulong v) => new TargetPointer(v);
-
-    public static bool operator ==(TargetPointer left, TargetPointer right) => left.Value == right.Value;
-    public static bool operator !=(TargetPointer left, TargetPointer right) => left.Value != right.Value;
-
-    public override bool Equals(object? obj) => obj is TargetPointer pointer && Equals(pointer);
-    public bool Equals(TargetPointer other) => Value == other.Value;
-
-    public override int GetHashCode() => Value.GetHashCode();
-}
-
-public readonly struct TargetNUInt
-{
-    public readonly ulong Value;
-    public TargetNUInt(ulong value) => Value = value;
-}
-
-public readonly struct TargetSpan
-{
-    public TargetSpan(TargetPointer address, ulong size)
-    {
-        Address = address;
-        Size = size;
-    }
-
-    public TargetPointer Address { get; }
-    public ulong Size { get; }
-}
-
 /// <summary>
 /// Representation of the target under inspection
 /// </summary>
@@ -60,23 +21,8 @@ public TargetSpan(TargetPointer address, ulong size)
 /// these are throwing APIs. Any callers at the boundaries (for example, unmanaged entry points, COM)
 /// should handle any exceptions.
 /// </remarks>
-public sealed unsafe class Target
+internal sealed unsafe class ContractDescriptorTarget : Target
 {
-    public record struct TypeInfo
-    {
-        public uint? Size;
-        public Dictionary<string, FieldInfo> Fields = [];
-
-        public TypeInfo() { }
-    }
-
-    public record struct FieldInfo
-    {
-        public int Offset;
-        public DataType Type;
-        public string? TypeName;
-    }
-
     private const int StackAllocByteThreshold = 1024;
 
     private readonly struct Configuration
@@ -90,20 +36,20 @@ private readonly struct Configuration
 
     private readonly Dictionary<string, int> _contracts = [];
     private readonly IReadOnlyDictionary<string, (ulong Value, string? Type)> _globals = new Dictionary<string, (ulong, string?)>();
-    private readonly Dictionary<DataType, TypeInfo> _knownTypes = [];
-    private readonly Dictionary<string, TypeInfo> _types = [];
+    private readonly Dictionary<DataType, Target.TypeInfo> _knownTypes = [];
+    private readonly Dictionary<string, Target.TypeInfo> _types = [];
 
-    internal Contracts.Registry Contracts { get; }
-    internal DataCache ProcessedData { get; }
+    public override ContractRegistry Contracts { get; }
+    public override DataCache ProcessedData { get; }
 
     public delegate int ReadFromTargetDelegate(ulong address, Span<byte> bufferToFill);
 
-    public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, out Target? target)
+    public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, out ContractDescriptorTarget? target)
     {
         Reader reader = new Reader(readFromTarget);
         if (TryReadContractDescriptor(contractDescriptor, reader, out Configuration config, out ContractDescriptorParser.ContractDescriptor? descriptor, out TargetPointer[] pointerData))
         {
-            target = new Target(config, descriptor!, pointerData, reader);
+            target = new ContractDescriptorTarget(config, descriptor!, pointerData, reader);
             return true;
         }
 
@@ -111,9 +57,9 @@ public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate re
         return false;
     }
 
-    private Target(Configuration config, ContractDescriptorParser.ContractDescriptor descriptor, TargetPointer[] pointerData, Reader reader)
+    private ContractDescriptorTarget(Configuration config, ContractDescriptorParser.ContractDescriptor descriptor, TargetPointer[] pointerData, Reader reader)
     {
-        Contracts = new Contracts.Registry(this);
+        Contracts = new CachingContractRegistry(this, this.TryGetContractVersion);
         ProcessedData = new DataCache(this);
         _config = config;
         _reader = reader;
@@ -125,12 +71,12 @@ private Target(Configuration config, ContractDescriptorParser.ContractDescriptor
         {
             foreach ((string name, ContractDescriptorParser.TypeDescriptor type) in descriptor.Types)
             {
-                TypeInfo typeInfo = new() { Size = type.Size };
+                Dictionary<string, Target.FieldInfo> fieldInfos = [];
                 if (type.Fields is not null)
                 {
                     foreach ((string fieldName, ContractDescriptorParser.FieldDescriptor field) in type.Fields)
                     {
-                        typeInfo.Fields[fieldName] = new FieldInfo()
+                        fieldInfos[fieldName] = new Target.FieldInfo()
                         {
                             Offset = field.Offset,
                             Type = field.Type is null ? DataType.Unknown : GetDataType(field.Type),
@@ -138,6 +84,7 @@ private Target(Configuration config, ContractDescriptorParser.ContractDescriptor
                         };
                     }
                 }
+                Target.TypeInfo typeInfo = new() { Size = type.Size, Fields = fieldInfos };
 
                 DataType dataType = GetDataType(name);
                 if (dataType is not DataType.Unknown)
@@ -263,7 +210,8 @@ private static DataType GetDataType(string type)
         return DataType.Unknown;
     }
 
-    public int PointerSize => _config.PointerSize;
+    public override int PointerSize => _config.PointerSize;
+    public override bool IsLittleEndian => _config.IsLittleEndian;
 
     /// <summary>
     /// Read a value from the target in target endianness
@@ -271,7 +219,7 @@ private static DataType GetDataType(string type)
     /// <typeparam name="T">Type of value to read</typeparam>
     /// <param name="address">Address to start reading from</param>
     /// <returns>Value read from the target</returns>
-    public T Read<T>(ulong address) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
+    public override T Read<T>(ulong address)
     {
         if (!TryRead(address, _config.IsLittleEndian, _reader, out T value))
             throw new InvalidOperationException($"Failed to read {typeof(T)} at 0x{address:x8}.");
@@ -308,7 +256,7 @@ private static T Read<T>(ReadOnlySpan<byte> bytes, bool isLittleEndian) where T
         return value;
     }
 
-    public void ReadBuffer(ulong address, Span<byte> buffer)
+    public override void ReadBuffer(ulong address, Span<byte> buffer)
     {
         if (!TryReadBuffer(address, buffer))
             throw new InvalidOperationException($"Failed to read {buffer.Length} bytes at 0x{address:x8}.");
@@ -330,7 +278,7 @@ private static bool IsSigned<T>() where T : struct, INumberBase<T>, IMinMaxValue
     /// </summary>
     /// <param name="address">Address to start reading from</param>
     /// <returns>Pointer read from the target</returns>}
-    public TargetPointer ReadPointer(ulong address)
+    public override TargetPointer ReadPointer(ulong address)
     {
         if (!TryReadPointer(address, _config, _reader, out TargetPointer pointer))
             throw new InvalidOperationException($"Failed to read pointer at 0x{address:x8}.");
@@ -338,7 +286,7 @@ public TargetPointer ReadPointer(ulong address)
         return pointer;
     }
 
-    public TargetPointer ReadPointerFromSpan(ReadOnlySpan<byte> bytes)
+    public override TargetPointer ReadPointerFromSpan(ReadOnlySpan<byte> bytes)
     {
         if (_config.PointerSize == sizeof(uint))
         {
@@ -368,7 +316,7 @@ public void ReadPointers(ulong address, Span<TargetPointer> buffer)
     /// </summary>
     /// <param name="address">Address to start reading from</param>
     /// <returns>String read from the target</returns>}
-    public string ReadUtf8String(ulong address)
+    public override string ReadUtf8String(ulong address)
     {
         // Read characters until we find the null terminator
         ulong end = address;
@@ -393,7 +341,7 @@ public string ReadUtf8String(ulong address)
     /// </summary>
     /// <param name="address">Address to start reading from</param>
     /// <returns>String read from the target</returns>}
-    public string ReadUtf16String(ulong address)
+    public override string ReadUtf16String(ulong address)
     {
         // Read characters until we find the null terminator
         ulong end = address;
@@ -421,7 +369,7 @@ public string ReadUtf16String(ulong address)
     /// </summary>
     /// <param name="address">Address to start reading from</param>
     /// <returns>Value read from the target</returns>
-    public TargetNUInt ReadNUInt(ulong address)
+    public override TargetNUInt ReadNUInt(ulong address)
     {
         if (!TryReadNUInt(address, _config, _reader, out ulong value))
             throw new InvalidOperationException($"Failed to read nuint at 0x{address:x8}.");
@@ -465,12 +413,10 @@ public bool IsAlignedToPointerSize(uint value)
         => IsAligned(value, _config.PointerSize);
     public bool IsAlignedToPointerSize(ulong value)
         => IsAligned(value, _config.PointerSize);
-    public bool IsAlignedToPointerSize(TargetPointer pointer)
+    public override bool IsAlignedToPointerSize(TargetPointer pointer)
         => IsAligned(pointer.Value, _config.PointerSize);
 
-    public char DirectorySeparator => (char)ReadGlobal<byte>(Constants.Globals.DirectorySeparator);
-
-    public T ReadGlobal<T>(string name) where T : struct, INumber<T>
+    public override T ReadGlobal<T>(string name)
         => ReadGlobal<T>(name, out _);
 
     public T ReadGlobal<T>(string name, out string? type) where T : struct, INumber<T>
@@ -482,7 +428,7 @@ public T ReadGlobal<T>(string name, out string? type) where T : struct, INumber<
         return T.CreateChecked(global.Value);
     }
 
-    public TargetPointer ReadGlobalPointer(string name)
+    public override TargetPointer ReadGlobalPointer(string name)
         => ReadGlobalPointer(name, out _);
 
     public TargetPointer ReadGlobalPointer(string name, out string? type)
@@ -494,17 +440,17 @@ public TargetPointer ReadGlobalPointer(string name, out string? type)
         return new TargetPointer(global.Value);
     }
 
-    public TypeInfo GetTypeInfo(DataType type)
+    public override TypeInfo GetTypeInfo(DataType type)
     {
-        if (!_knownTypes.TryGetValue(type, out TypeInfo typeInfo))
+        if (!_knownTypes.TryGetValue(type, out Target.TypeInfo typeInfo))
             throw new InvalidOperationException($"Failed to get type info for '{type}'");
 
         return typeInfo;
     }
 
-    public TypeInfo GetTypeInfo(string type)
+    public Target.TypeInfo GetTypeInfo(string type)
     {
-        if (_types.TryGetValue(type, out TypeInfo typeInfo))
+        if (_types.TryGetValue(type, out Target.TypeInfo typeInfo))
         return typeInfo;
 
         DataType dataType = GetDataType(type);
@@ -521,12 +467,12 @@ internal bool TryGetContractVersion(string contractName, out int version)
     /// Store of addresses that have already been read into corresponding data models.
     /// This is simply used to avoid re-processing data on every request.
     /// </summary>
-    internal sealed class DataCache
+    internal sealed class DataCache : Target.IDataCache
     {
-        private readonly Target _target;
+        private readonly ContractDescriptorTarget _target;
         private readonly Dictionary<(ulong, Type), object?> _readDataByAddress = [];
 
-        public DataCache(Target target)
+        public DataCache(ContractDescriptorTarget target)
         {
             _target = target;
         }
diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/Microsoft.Diagnostics.DataContractReader.csproj b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/Microsoft.Diagnostics.DataContractReader.csproj
new file mode 100644
index 00000000000000..b0868e188bb61a
--- /dev/null
+++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/Microsoft.Diagnostics.DataContractReader.csproj
@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
+    <RootNamespace>Microsoft.Diagnostics.DataContractReader</RootNamespace>
+    <Nullable>enable</Nullable>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <!-- Do not produce a public package. This ships as part of the runtime -->
+    <IsShippingPackage>false</IsShippingPackage>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="$(LibrariesProjectRoot)Common\src\System\HResults.cs" Link="Common\System\HResults.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader.Tests" />
+    <InternalsVisibleTo Include="cdacreader" Condition="'$(TargetsWindows)' == 'true'"/>
+    <InternalsVisibleTo Include="libcdacreader" Condition="'$(TargetsWindows)' != 'true'"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.Diagnostics.DataContractReader.Abstractions\Microsoft.Diagnostics.DataContractReader.Abstractions.csproj" />
+    <ProjectReference Include="..\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj" />
+  </ItemGroup>
+</Project>
diff --git a/src/native/managed/cdacreader/src/Root.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/Root.cs
similarity index 100%
rename from src/native/managed/cdacreader/src/Root.cs
rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/Root.cs
diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs
deleted file mode 100644
index a5d3721f06f885..00000000000000
--- a/src/native/managed/cdacreader/src/Contracts/Registry.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-
-namespace Microsoft.Diagnostics.DataContractReader.Contracts;
-
-internal sealed class Registry
-{
-    // Contracts that have already been created for a target.
-    // Items should not be removed from this, only added.
-    private readonly Dictionary<Type, IContract> _contracts = [];
-    private readonly Target _target;
-
-    public Registry(Target target)
-    {
-        _target = target;
-    }
-
-    public IException Exception => GetContract<IException>();
-    public ILoader Loader => GetContract<ILoader>();
-    public IEcmaMetadata EcmaMetadata => GetContract<IEcmaMetadata>();
-    public IObject Object => GetContract<IObject>();
-    public IThread Thread => GetContract<IThread>();
-    public IRuntimeTypeSystem RuntimeTypeSystem => GetContract<IRuntimeTypeSystem>();
-    public IDacStreams DacStreams => GetContract<IDacStreams>();
-
-    private T GetContract<T>() where T : IContract
-    {
-        if (_contracts.TryGetValue(typeof(T), out IContract? contractMaybe))
-            return (T)contractMaybe;
-
-        if (!_target.TryGetContractVersion(T.Name, out int version))
-            throw new NotImplementedException();
-
-        // Create and register the contract
-        IContract contract = T.Create(_target, version);
-        if (_contracts.TryAdd(typeof(T), contract))
-            return (T)contract;
-
-        // Contract was already registered by someone else
-        return (T)_contracts[typeof(T)];
-    }
-}
diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs
index d58f4750099522..17983bb68ed205 100644
--- a/src/native/managed/cdacreader/src/Entrypoints.cs
+++ b/src/native/managed/cdacreader/src/Entrypoints.cs
@@ -15,13 +15,13 @@ internal static class Entrypoints
     private static unsafe int Init(ulong descriptor, delegate* unmanaged<ulong, byte*, uint, void*, int> readFromTarget, void* readContext, IntPtr* handle)
     {
         // TODO: [cdac] Better error code/details
-        if (!Target.TryCreate(descriptor, (address, buffer) =>
+        if (!ContractDescriptorTarget.TryCreate(descriptor, (address, buffer) =>
             {
                 fixed (byte* bufferPtr = buffer)
                 {
                     return readFromTarget(address, bufferPtr, (uint)buffer.Length, readContext);
                 }
-            }, out Target? target))
+            }, out ContractDescriptorTarget? target))
             return -1;
 
         GCHandle gcHandle = GCHandle.Alloc(target);
diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
index 37e513995767b6..8f4a13500429e0 100644
--- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
+++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
@@ -3,9 +3,6 @@
 
 using Microsoft.Diagnostics.DataContractReader.Contracts;
 using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.Contracts;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices.Marshalling;
 using System.Text;
@@ -159,7 +156,9 @@ public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, ui
                         Contracts.ModuleHandle module = _target.Contracts.Loader.GetModuleHandle(modulePtr);
                         string modulePath = _target.Contracts.Loader.GetPath(module);
                         ReadOnlySpan<char> moduleSpan = modulePath.AsSpan();
-                        int pathNameSpanIndex = moduleSpan.LastIndexOf(_target.DirectorySeparator);
+                        char directorySeparator = (char)_target.ReadGlobal<byte>(Constants.Globals.DirectorySeparator);
+
+                        int pathNameSpanIndex = moduleSpan.LastIndexOf(directorySeparator);
                         if (pathNameSpanIndex != -1)
                         {
                             moduleSpan = moduleSpan.Slice(pathNameSpanIndex + 1);
diff --git a/src/native/managed/cdacreader/src/Legacy/SigFormat.cs b/src/native/managed/cdacreader/src/Legacy/SigFormat.cs
index f2928393eed400..69246f6b1e85f0 100644
--- a/src/native/managed/cdacreader/src/Legacy/SigFormat.cs
+++ b/src/native/managed/cdacreader/src/Legacy/SigFormat.cs
@@ -2,14 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
 using System.Reflection.Metadata;
 using System.Reflection.Metadata.Ecma335;
 using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Linq;
 using Microsoft.Diagnostics.DataContractReader.Contracts;
 
 namespace Microsoft.Diagnostics.DataContractReader.Legacy
diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj
index 1c973cac51a571..a8fd13e4992348 100644
--- a/src/native/managed/cdacreader/src/cdacreader.csproj
+++ b/src/native/managed/cdacreader/src/cdacreader.csproj
@@ -12,9 +12,6 @@
     <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
   </PropertyGroup>
 
-  <ItemGroup>
-    <Compile Include="$(LibrariesProjectRoot)Common\src\System\HResults.cs" Link="Common\System\HResults.cs" />
-  </ItemGroup>
   <ItemGroup>
     <InternalsVisibleTo Include="Microsoft.Diagnostics.DataContractReader.Tests" />
   </ItemGroup>
@@ -24,4 +21,8 @@
     <!-- <InstallRuntimeComponentDestination Include="sharedFramework" Condition="'$(RuntimeFlavor)' == 'coreclr'"/> -->
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.Diagnostics.DataContractReader\Microsoft.Diagnostics.DataContractReader.csproj" />
+  </ItemGroup>
+
 </Project>
diff --git a/src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs
index 6f93b1225670e4..04b750dd6063b7 100644
--- a/src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs
+++ b/src/native/managed/cdacreader/tests/ContractDescriptorParserTests.cs
@@ -3,7 +3,6 @@
 
 using System;
 using System.Text.Json;
-using System.Text.Unicode;
 using Xunit;
 
 namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
diff --git a/src/native/managed/cdacreader/tests/DacStreamsTests.cs b/src/native/managed/cdacreader/tests/DacStreamsTests.cs
index b3fbcae943f7a0..83eef79181a460 100644
--- a/src/native/managed/cdacreader/tests/DacStreamsTests.cs
+++ b/src/native/managed/cdacreader/tests/DacStreamsTests.cs
@@ -47,7 +47,7 @@ private static unsafe void DacStreamsContractHelper(MockTarget.Architecture arch
             builder = configure(builder);
         }
 
-        bool success = builder.TryCreateTarget(out Target? target);
+        bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target);
         Assert.True(success);
 
         testCase(target);
diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs
index 822e1d6a26af70..d86c2dd2e8eb2f 100644
--- a/src/native/managed/cdacreader/tests/MethodTableTests.cs
+++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs
@@ -32,7 +32,7 @@ private static void RTSContractHelper(MockTarget.Architecture arch, ConfigureCon
             builder = configure(builder);
         }
 
-        bool success = builder.TryCreateTarget(out Target? target);
+        bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target);
         Assert.True(success);
 
         testCase(target);
diff --git a/src/native/managed/cdacreader/tests/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors.cs
index 3a173813714841..8ffcd23556afdd 100644
--- a/src/native/managed/cdacreader/tests/MockDescriptors.cs
+++ b/src/native/managed/cdacreader/tests/MockDescriptors.cs
@@ -13,7 +13,7 @@ public class MockDescriptors
 {
     private static readonly Target.TypeInfo MethodTableTypeInfo = new()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { nameof(Data.MethodTable.MTFlags), new() { Offset = 4, Type = DataType.uint32}},
             { nameof(Data.MethodTable.BaseSize), new() { Offset = 8, Type = DataType.uint32}},
             { nameof(Data.MethodTable.MTFlags2), new() { Offset = 12, Type = DataType.uint32}},
@@ -28,7 +28,7 @@ public class MockDescriptors
 
     private static readonly Target.TypeInfo EEClassTypeInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { nameof (Data.EEClass.MethodTable), new () { Offset = 8, Type = DataType.pointer}},
             { nameof (Data.EEClass.CorTypeAttr), new () { Offset = 16, Type = DataType.uint32}},
             { nameof (Data.EEClass.NumMethods), new () { Offset = 20, Type = DataType.uint16}},
@@ -39,21 +39,21 @@ public class MockDescriptors
 
     private static readonly Target.TypeInfo ArrayClassTypeInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { nameof (Data.ArrayClass.Rank), new () { Offset = 0x70, Type = DataType.uint8}},
         }
     };
 
     private static readonly Target.TypeInfo ObjectTypeInfo = new()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { "m_pMethTab", new() { Offset = 0, Type = DataType.pointer} },
         }
     };
 
     private static readonly Target.TypeInfo StringTypeInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { "m_StringLength", new() { Offset = 0x8, Type = DataType.uint32} },
             { "m_FirstChar", new() { Offset = 0xc, Type = DataType.uint16} },
         }
@@ -61,28 +61,28 @@ public class MockDescriptors
 
     private static readonly Target.TypeInfo ArrayTypeInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { "m_NumComponents", new() { Offset = 0x8, Type = DataType.uint32} },
         },
     };
 
     private static readonly Target.TypeInfo SyncTableEntryInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { nameof(Data.SyncTableEntry.SyncBlock), new() { Offset = 0, Type = DataType.pointer} },
         },
     };
 
     private static readonly Target.TypeInfo SyncBlockTypeInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { nameof(Data.SyncBlock.InteropInfo), new() { Offset = 0, Type = DataType.pointer} },
         },
     };
 
     private static readonly Target.TypeInfo InteropSyncBlockTypeInfo = new Target.TypeInfo()
     {
-        Fields = {
+        Fields = new Dictionary<string, Target.FieldInfo> {
             { nameof(Data.InteropSyncBlockInfo.RCW), new() { Offset = 0, Type = DataType.pointer} },
             { nameof(Data.InteropSyncBlockInfo.CCW), new() { Offset = 0x8, Type = DataType.pointer} },
         },
diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs
index 0aa007a7791a9e..645d251dbbea99 100644
--- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs
+++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs
@@ -226,10 +226,10 @@ private bool FragmentFits(HeapFragment f)
             return true;
         }
 
-        public bool TryCreateTarget([NotNullWhen(true)] out Target? target)
+        public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target)
         {
             ReadContext context = CreateContext();
-            return Target.TryCreate(context.ContractDescriptor.Address, context.ReadFromTarget, out target);
+            return ContractDescriptorTarget.TryCreate(context.ContractDescriptor.Address, context.ReadFromTarget, out target);
         }
     }
 
diff --git a/src/native/managed/cdacreader/tests/ObjectTests.cs b/src/native/managed/cdacreader/tests/ObjectTests.cs
index 4f39edf44b9f1d..8a3c05dd79ea15 100644
--- a/src/native/managed/cdacreader/tests/ObjectTests.cs
+++ b/src/native/managed/cdacreader/tests/ObjectTests.cs
@@ -30,7 +30,7 @@ private static void ObjectContractHelper(MockTarget.Architecture arch, Configure
             builder = configure(builder);
         }
 
-        bool success = builder.TryCreateTarget(out Target? target);
+        bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target);
         Assert.True(success);
         testCase(target);
     }
diff --git a/src/native/managed/cdacreader/tests/TargetTests.cs b/src/native/managed/cdacreader/tests/TargetTests.cs
index 1ce64afceca0d2..50638e1f8c817e 100644
--- a/src/native/managed/cdacreader/tests/TargetTests.cs
+++ b/src/native/managed/cdacreader/tests/TargetTests.cs
@@ -17,14 +17,14 @@ public unsafe class TargetTests
         // Size and fields
         [DataType.Thread] = new(){
             Size = 56,
-            Fields = {
+            Fields = new Dictionary<string, Target.FieldInfo> {
                 { "Field1", new(){ Offset = 8, Type = DataType.uint16, TypeName = DataType.uint16.ToString() }},
                 { "Field2", new(){ Offset = 16, Type = DataType.GCHandle, TypeName = DataType.GCHandle.ToString() }},
                 { "Field3", new(){ Offset = 32 }}
             }},
         // Fields only
         [DataType.ThreadStore] = new(){
-            Fields = {
+            Fields = new Dictionary<string, Target.FieldInfo> {
                 { "Field1", new(){ Offset = 0, TypeName = "FieldType" }},
                 { "Field2", new(){ Offset = 8 }}
             }},
@@ -44,7 +44,7 @@ public void GetTypeInfo(MockTarget.Architecture arch)
             .SetGlobals(Array.Empty<(string, ulong, string?)>())
             .SetContracts(Array.Empty<string>());
 
-        bool success = builder.TryCreateTarget(out Target? target);
+        bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target);
         Assert.True(success);
 
         foreach ((DataType type, Target.TypeInfo info) in TestTypes)
@@ -90,7 +90,7 @@ public void ReadGlobalValue(MockTarget.Architecture arch)
             .SetGlobals(TestGlobals)
             .SetContracts([]);
 
-        bool success = builder.TryCreateTarget(out Target? target);
+        bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target);
         Assert.True(success);
 
         ValidateGlobals(target, TestGlobals);
@@ -107,7 +107,7 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch)
             .SetGlobals(TestGlobals.Select(MakeGlobalToIndirect).ToArray(),
                         TestGlobals.Select((g) => g.Value).ToArray());
 
-        bool success = builder.TryCreateTarget(out Target? target);
+        bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target);
         Assert.True(success);
 
         // Indirect values are pointer-sized, so max 32-bits for a 32-bit target
@@ -124,7 +124,7 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch)
     }
 
     private static void ValidateGlobals(
-        Target target,
+        ContractDescriptorTarget target,
         (string Name, ulong Value, string? Type)[] globals,
         [CallerMemberName] string caller = "",
         [CallerFilePath] string filePath = "",
diff --git a/src/native/managed/compile-native.proj b/src/native/managed/compile-native.proj
index bcda8c5d6b57b5..dea5d2c6380da5 100644
--- a/src/native/managed/compile-native.proj
+++ b/src/native/managed/compile-native.proj
@@ -50,7 +50,7 @@
         <ProjectReference Include="@(NativeLibsProjectsToBuild)"
                           ReferenceOutputAssembly="false"
                           AdditionalProperties="%(AdditionalProperties);$(SplitSubprojectProps)"
-                          Targets="LinkNative"
+                          Targets="Publish"
                           Condition="$(SupportsNativeAotComponents)"/>
     </ItemGroup>
 </Project>
