using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace P4API
{
///
/// P4.NET initialization (helper) methods.
///
public static class Bootstrapper
{
/**************************************************
/* Public interface
/**************************************************/
//-------------------------------------------------
///
/// Make sure 'p4dn' assembly can be resolved successfully
/// even when the underlying DLL is not discoverable
/// by Fusion assembly resolver.
///
///
/// Initializes current AppDomain so that failed attempts
/// to resolve assemblies will be given one last chance
/// by calling CustomResolve private method; this method
/// will recognize 'p4dn' assembly name and attempt to
/// satisfy the request by extracting the embedded p4dn
/// for the appropriate CPU architecture and loading
/// this extracted copy.
///
public static void Initialize()
{
if (!Bootstrapper._initialized) // CLR guarantees proper access to volatile bool by multiple threads
{
lock (Bootstrapper._sync)
{
if (!Bootstrapper._initialized) // just to be sure, recheck the flag value after the memory barrier issued by the lock above
{
AppDomain.CurrentDomain.AssemblyResolve += CustomResolve;
_initialized = true;
}
}
}
}
/**************************************************
/* Private
/**************************************************/
//-------------------------------------------------
///
/// Custom assembly resolve implementation that
/// recognizes requests for 'p4dn' assemblies.
///
private static Assembly CustomResolve(object sender, System.ResolveEventArgs args)
{
const string p4dnName = "p4dn";
const string categoryError = "P4.NET resolver error";
if (null != args && null != args.Name && args.Name.StartsWith(p4dnName, StringComparison.OrdinalIgnoreCase))
{
if (null == Bootstrapper._p4dnAssembly) // CLR guarantees proper access to volatile reference by multiple threads
{
lock (Bootstrapper._sync)
{
if (null == Bootstrapper._p4dnAssembly) // just to be sure, recheck the assembly reference after the memory barrier issued by the lock above
{
// directory for embedded assembly extraction must exist
var targetDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
Path.Combine(
"Perforce",
"P4.NET"));
if (!Directory.Exists(targetDirectory))
{
Directory.CreateDirectory(targetDirectory);
}
// is extraction needed? (or is a fresh copy in the target directory already?)
var architecture = (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? "x86").ToLowerInvariant();
var thisAssembly = typeof(Bootstrapper).Assembly;
var thisAssemblyName = thisAssembly.GetName();
var targetFileName = Path.Combine(
targetDirectory,
string.Concat(
p4dnName,
"_",
thisAssemblyName.Version.ToString(4),
"_",
architecture,
".dll"));
bool extractionNeeded;
var targetFileInfo = new FileInfo(targetFileName);
if (targetFileInfo.Exists) // if file exists, extract if target file is older than this assembly; if no file found, extract always
{
var thisAssemblyFileInfo = new FileInfo(new Uri(thisAssemblyName.CodeBase).AbsolutePath);
extractionNeeded = (thisAssemblyFileInfo.LastWriteTimeUtc > targetFileInfo.LastWriteTimeUtc);
}
else
{
extractionNeeded = true;
}
// extract now (unless the target file is fresh)
if (extractionNeeded)
{
var resourceName = string.Concat(
typeof(Bootstrapper).FullName,
".",
architecture,
".",
p4dnName,
".dll");
const int bufferSize = 0x10000; // 64k
var buffer = new byte[bufferSize];
using (var readStream = thisAssembly.GetManifestResourceStream(resourceName))
{
if (null == readStream)
{
Trace.WriteLine(string.Concat("No embedded resource \"", resourceName, "\" found, your architecture is probably not supported"), categoryError);
return null;
}
using (var writeStream = File.Create(targetFileName, bufferSize))
{
int read;
while (0 != (read = readStream.Read(buffer, 0, bufferSize)))
{
writeStream.Write(buffer, 0, read);
}
}
}
}
// and load the extracted file as assembly
Bootstrapper._p4dnAssembly = Assembly.LoadFile(targetFileName);
}
}
}
// make sure we can satisfy the requested reference with the embedded assembly (now extracted)
var reference = new AssemblyName(args.Name);
if (AssemblyName.ReferenceMatchesDefinition(reference, Bootstrapper._p4dnAssembly.GetName()))
{
return Bootstrapper._p4dnAssembly;
}
}
// we don't recognize the requested reference
return null;
}
private static volatile bool _initialized; // flag indicating whether we've already subscribed to this AppDomain's AssemblyResolve event
private static volatile Assembly _p4dnAssembly; // reference to the p4dn assembly for this process' architecture (set in the first resolve attempt; all subsequent attempts will use the cached reference)
private static readonly object _sync = new object(); // used to serialize access to critical sections of code for multiple threads trying to resolve p4dn at the same time
}
}