Added Vanara.PInvoke.ODBC32 package and supporting unit test

nullableenabled
David Hall 2024-04-10 07:21:07 -06:00
parent 35a544b5c7
commit 94fdd98d31
7 changed files with 6701 additions and 0 deletions

7
PInvoke/Odbc32/Odbc32.cs Normal file
View File

@ -0,0 +1,7 @@
namespace Vanara.PInvoke;
/// <summary>Items from the Odbc32.dll.</summary>
public static partial class Odbc32
{
private const string Lib_Odbc32 = "Odbc32.dll";
}

6138
PInvoke/Odbc32/Sql.cs Normal file

File diff suppressed because it is too large Load Diff

269
PInvoke/Odbc32/Sqltypes.cs Normal file
View File

@ -0,0 +1,269 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
namespace Vanara.PInvoke;
public static partial class Odbc32
{
public const int SQL_NO_TOTAL = -4;
public const int SQL_NTS = -3;
public const nint SQL_NULL_DATA = -1;
private const int SQL_MAX_NUMERIC_LEN = 16;
[PInvokeData("sqltypes.h")]
public enum SQLINTERVAL
{
SQL_IS_YEAR = 1,
SQL_IS_MONTH = 2,
SQL_IS_DAY = 3,
SQL_IS_HOUR = 4,
SQL_IS_MINUTE = 5,
SQL_IS_SECOND = 6,
SQL_IS_YEAR_TO_MONTH = 7,
SQL_IS_DAY_TO_HOUR = 8,
SQL_IS_DAY_TO_MINUTE = 9,
SQL_IS_DAY_TO_SECOND = 10,
SQL_IS_HOUR_TO_MINUTE = 11,
SQL_IS_HOUR_TO_SECOND = 12,
SQL_IS_MINUTE_TO_SECOND = 13
}
/// <summary>
/// Each function in ODBC returns a code, known as its return code, which indicates the overall success or failure of the function.
/// Program logic is generally based on return codes.
/// </summary>
[PInvokeData("sqltypes.h")]
[Serializable]
public enum SQLRETURN : short
{
/// <summary>
/// Function completed successfully. The application calls SQLGetDiagField to retrieve additional information from the header record.
/// </summary>
SQL_SUCCESS = 0,
/// <summary>
/// Function completed successfully, possibly with a nonfatal error (warning). The application calls SQLGetDiagRec or SQLGetDiagField
/// to retrieve additional information.
/// </summary>
SQL_SUCCESS_WITH_INFO = 1,
/// <summary>
/// Function failed. The application calls SQLGetDiagRec or SQLGetDiagField to retrieve additional information. The contents of any
/// output arguments to the function are undefined.
/// </summary>
SQL_ERROR = -1,
/// <summary>
/// Function failed due to an invalid environment, connection, statement, or descriptor handle. This indicates a programming error.
/// No additional information is available from SQLGetDiagRec or SQLGetDiagField. This code is returned only when the handle is a
/// null pointer or is the wrong type, such as when a statement handle is passed for an argument that requires a connection handle.
/// </summary>
SQL_INVALID_HANDLE = -2,
/// <summary>
/// No more data was available. The application calls SQLGetDiagRec or SQLGetDiagField to retrieve additional information. One or
/// more driver-defined status records in class 02xxx may be returned. Note: In ODBC 2.x, this return code was named SQL_NO_DATA_FOUND.
/// </summary>
SQL_NO_DATA = 100,
/// <summary>
/// A function that was started asynchronously is still executing. The application calls SQLGetDiagRec or SQLGetDiagField to retrieve
/// additional information, if any.
/// </summary>
SQL_STILL_EXECUTING = 2,
/// <summary>
/// More data is needed, such as when parameter data is sent at execution time or additional connection information is required. The
/// application calls SQLGetDiagRec or SQLGetDiagField to retrieve additional information, if any.
/// </summary>
SQL_NEED_DATA = 99,
/// <summary>
/// More data is available. The application calls SQLParamData to retrieve the data. Note: In ODBC 2.x, this return code was named
/// </summary>
SQL_PARAM_DATA_AVAILABLE = 101,
}
public static Exception? GetException(this SQLRETURN ret, ISQLHANDLE? h = null) => ret switch
{
SQLRETURN.SQL_SUCCESS => null,
_ => Odbc32Exception.CreateException($"ODBC call failed with return code {ret}", ret, h),
};
public static bool SQL_SUCCEEDED(this SQLRETURN ret) => (((short)ret) & (~1)) == 0;
public static void ThrowIfFailed(this SQLRETURN ret, ISQLHANDLE? h = null)
{
if (!SQL_SUCCEEDED(ret))
throw ret.GetException(h)!;
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct DATE_STRUCT
{
public short year;
public ushort month;
public ushort day;
public static implicit operator DateTime(DATE_STRUCT d) => new(d.year, d.month, d.day);
public static implicit operator DATE_STRUCT(DateTime d) => new() { year = (short)d.Year, month = (ushort)d.Month, day = (ushort)d.Day };
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct SQL_DAY_SECOND
{
public uint day;
public uint hour;
public uint minute;
public uint second;
public uint fraction;
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct SQL_INTERVAL_STRUCT
{
public SQLINTERVAL interval_type;
public short interval_sign;
public INTVAL intval;
[StructLayout(LayoutKind.Sequential)]
public struct INTVAL
{
public SQL_YEAR_MONTH year_month;
public SQL_DAY_SECOND day_second;
}
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct SQL_NUMERIC_STRUCT
{
public byte precision;
public sbyte scale;
public byte sign;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SQL_MAX_NUMERIC_LEN)]
public byte[] val;
public static SQL_NUMERIC_STRUCT FromDecimal(decimal d, byte precision)
{
SafeCoTaskMemHandle ret = SafeCoTaskMemHandle.CreateFromStructure<SQL_NUMERIC_STRUCT>();
int[] parts = decimal.GetBits(d);
byte[] bits = BitConverter.GetBytes(parts[3]);
ret.Write(precision, false, 0); //Bits 0-7 precision
ret.Write(bits[2], false, 1); //Bits 16-23 scale
ret.Write((byte)(0 == bits[3] ? 1 : 0), false, 2); //Bit 31 - sign(isnegative)
ret.Write(parts[0], false, 3);
ret.Write(parts[1], false, 7);
ret.Write(parts[2], false, 11);
return ret.ToStructure<SQL_NUMERIC_STRUCT>();
}
public static implicit operator decimal(SQL_NUMERIC_STRUCT ns)
{
SafeCoTaskMemStruct<SQL_NUMERIC_STRUCT> mem = ns;
byte[] bits = mem.GetBytes();
int[] buffer = new int[4];
buffer[3] = bits[2] << 16; // scale
if (0 == bits[3])
buffer[3] |= unchecked((int)0x80000000); //sign
buffer[0] = BitConverter.ToInt32(bits, 4); // low
buffer[1] = BitConverter.ToInt32(bits, 8); // mid
buffer[2] = BitConverter.ToInt32(bits, 12); // high
return 0 != BitConverter.ToInt32(bits, 16) ? throw new OverflowException() : new decimal(buffer);
}
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct SQL_YEAR_MONTH
{
public uint year;
public uint month;
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct TIME_STRUCT
{
public ushort hour;
public ushort minute;
public ushort second;
public static implicit operator TimeSpan(TIME_STRUCT t) => new(t.hour, t.minute, t.second);
public static implicit operator TIME_STRUCT(TimeSpan t) => new() { hour = (ushort)t.Hours, minute = (ushort)t.Minutes, second = (ushort)t.Seconds };
}
[PInvokeData("sqltypes.h")]
[StructLayout(LayoutKind.Sequential)]
public struct TIMESTAMP_STRUCT
{
public short year;
public ushort month;
public ushort day;
public ushort hour;
public ushort minute;
public ushort second;
public uint fraction;
public static implicit operator DateTime(TIMESTAMP_STRUCT t) => new DateTime(t.year, t.month, t.day, t.hour, t.minute, t.second, DateTimeKind.Unspecified) + TimeSpan.FromTicks(t.fraction / 100);
public static implicit operator TIMESTAMP_STRUCT(DateTime t) => new() { year = (short)t.Year, month = (ushort)t.Month, day = (ushort)t.Day, hour = (ushort)t.Hour, minute = (ushort)t.Minute, second = (ushort)t.Second, fraction = (uint)(t.Ticks % TimeSpan.TicksPerSecond) * 100 };
}
public class Odbc32Exception(string message) : System.Data.Common.DbException(message)
{
public IReadOnlyCollection<Odbc32Error> Errors { get; private set; } = [];
public static Odbc32Exception CreateException(string message, SQLRETURN ret, ISQLHANDLE? h = null)
{
var errs = Odbc32Error.GetDiagErrors(h, ret);
StringBuilder sb = new(message);
if (sb.Length > 0) sb.AppendLine();
foreach (var e in errs)
sb.AppendLine($"State: {e.SqlState},\tErr: {e.hResult},\tMessage: {e.Message}");
return new(sb.ToString()) { Errors = errs };
}
public class Odbc32Error(string msg, string state, int err)
{
public int hResult { get; } = err;
public string Message { get; } = msg;
public string SqlState { get; } = state;
internal static List<Odbc32Error> GetDiagErrors(ISQLHANDLE? h, SQLRETURN ret)
{
List<Odbc32Error> errors = [];
if (ret != SQLRETURN.SQL_SUCCESS && h is not null)
{
short i = 0;
StringBuilder sbState = new(5), sbMsg = new(1024);
bool more = true;
while (more)
{
++i;
var rc = SQLGetDiagRec(h.GetHandleVal(), h.DangerousGetHandle(), i, sbState, out var err, sbMsg, (short)sbMsg.Capacity, out var req);
if (rc == SQLRETURN.SQL_SUCCESS_WITH_INFO && sbMsg.Capacity - 1 < req)
{
sbMsg.Capacity = req + 1;
rc = SQLGetDiagRec(h.GetHandleVal(), h.DangerousGetHandle(), i, sbState, out err, sbMsg, (short)sbMsg.Capacity, out _);
}
if (more = SQL_SUCCEEDED(rc))
errors.Add(new Odbc32Error(sbMsg.ToString(), sbState.ToString(), err));
}
}
return errors;
}
}
}
/* enumerations for DATETIME_INTERVAL_SUBCODE values for interval data ref types these values are from SQL-ref 92 */
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<ProjectExtensions>
<SupportedDlls>odbc32.dll</SupportedDlls>
</ProjectExtensions>
<PropertyGroup>
<Description>PInvoke API (methods, structures and constants) imported from Windows Odbc32.dll.</Description>
<AssemblyName>Vanara.PInvoke.Odbc32</AssemblyName>
<AssemblyTitle>$(AssemblyName)</AssemblyTitle>
<PackageId>$(AssemblyName)</PackageId>
<PackageTags>pinvoke;vanara;net-extensions;interop;odbc32;odbc</PackageTags>
<PackageReleaseNotes />
<PackageReadmeFile>pkgreadme.md</PackageReadmeFile>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Compile Remove="OleDB.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\Vanara.Core.csproj" />
<ProjectReference Include="..\Shared\Vanara.PInvoke.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>UnitTest.PInvoke.Odbc32</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\PInvoke\Kernel32\Vanara.PInvoke.Kernel32.csproj" />
<ProjectReference Include="..\..\..\PInvoke\Odbc32\Vanara.PInvoke.Odbc32.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,214 @@
using NUnit.Framework;
using NUnit.Framework.Internal;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static Vanara.PInvoke.Odbc32;
namespace Vanara.PInvoke.Tests;
[TestFixture]
public class Odbc32Tests
{
private const int DISPLAY_MAX = 50;
private const short NULL_SIZE = 6;
private const short DISPLAY_FORMAT_EXTRA = 3;
private const short gHeight = 80;
[OneTimeSetUp]
public void _Setup()
{
}
[OneTimeTearDown]
public void _TearDown()
{
}
[Test]
public void Test()
{
using var hEnv = SQLAllocHandle<SafeSQLHENV>();
SQLSetEnvAttr(hEnv, SQL_ATTR.SQL_ATTR_ODBC_VERSION, (IntPtr)SQL_OV.SQL_OV_ODBC3, 0).ThrowIfFailed(hEnv);
using var hDbc = SQLAllocHandle<SafeSQLHDBC>(hEnv);
SQLDriverConnect(hDbc, GetDesktopWindow(), "", SQL_NTS, default, 0, out _, SQL_DRIVER.SQL_DRIVER_COMPLETE).ThrowIfFailed(hDbc);
try
{
using var hStmt = SQLAllocHandle<SafeSQLHSTMT>(hDbc);
SQLExec(hStmt, "SELECT * FROM Filters");
}
finally
{
SQLDisconnect(hDbc);
}
}
static private void SQLExec(SQLHSTMT hStmt, string cmd)
{
SQLExecDirect(hStmt, cmd).ThrowIfFailed(hStmt);
try
{
SQLNumResultCols(hStmt, out var sNumResults).ThrowIfFailed(hStmt);
if (sNumResults > 0)
{
DisplayResults(hStmt, sNumResults);
}
else
{
SQLRowCount(hStmt, out var rows).ThrowIfFailed(hStmt);
TestContext.WriteLine($"{rows} row{(rows == 1 ? "" : "s")} affected");
}
}
finally
{
SQLFreeStmt(hStmt, SQL_STMT.SQL_CLOSE).ThrowIfFailed(hStmt);
}
}
/************************************************************************
/* DisplayResults: display results of a select query
/*
/* Parameters:
/* hStmt ODBC statement handle
/* cCols Count of columns
/************************************************************************/
static void DisplayResults(SQLHSTMT hStmt, short cCols)
{
// Allocate memory for each column
AllocateBindings(hStmt, cCols, out var pBindings, out var cDisplaySize);
// Set the display mode and write the titles
DisplayTitles(hStmt, (short)(cDisplaySize + 1), pBindings);
// Fetch and display the data
bool fNoData = false;
int iCount = 0;
do
{
// Fetch a row
if (iCount++ >= gHeight - 2)
{
iCount = 1;
DisplayTitles(hStmt, (short)(cDisplaySize + 1), pBindings);
}
SQLRETURN RetCode = SQLFetch(hStmt);
if (RetCode == SQLRETURN.SQL_NO_DATA)
{
fNoData = true;
}
else
{
// Display the data. Ignore truncations
foreach (var pThisBinding in pBindings)
{
TestContext.Write($"| {(pThisBinding.indPtr == SQL_NULL_DATA ? "<null>" : pThisBinding.wszBuffer!.ToString()!).PadRight(pThisBinding.cDisplaySize)}");
}
TestContext.WriteLine("|");
}
} while (!fNoData);
}
/************************************************************************
/* AllocateBindings: Get column information and allocate bindings
/* for each column.
/*
/* Parameters:
/* hStmt Statement handle
/* cCols Number of columns in the result set
/* *lppBinding Binding pointer (returned)
/* lpDisplay Display size of one line
/************************************************************************/
static void AllocateBindings(SQLHSTMT hStmt, short cCols, out IList<BINDING> ppBinding, out short pDisplay)
{
ppBinding = [];
pDisplay = 0;
for (ushort iCol = 1; iCol <= cCols; iCol++)
{
BINDING pThisBinding = new();
ppBinding.Add(pThisBinding);
// Figure out the display length of the column (we will
// bind to byte since we are only displaying data, in general
// you should bind to the appropriate C type if you are going
// to manipulate data since it is much faster...)
//SQLColAttribute(hStmt, iCol, SQL_DESC.SQL_DESC_DISPLAY_SIZE, default, 0, out _, out var cchDisplay).ThrowIfFailed(hStmt);
var cchDisplay = SQLColAttribute<int>(hStmt, iCol, SQL_DESC.SQL_DESC_DISPLAY_SIZE);
// Figure out if this is a character or numeric column; this is
// used to determine if we want to display the data left- or right-
// aligned.
// SQL_DESC_CONCISE_TYPE maps to the 1.x SQL_COLUMN_TYPE.
// This is what you must use if you want to work
// against a 2.x driver.
//SQLColAttribute(hStmt, iCol, SQL_DESC.SQL_DESC_CONCISE_TYPE, default, 0, out _, out var ssType).ThrowIfFailed(hStmt);
var ssType = SQLColAttribute<SQL_TYPE>(hStmt, iCol, SQL_DESC.SQL_DESC_CONCISE_TYPE);
pThisBinding.fChar = ssType is SQL_TYPE.SQL_CHAR or SQL_TYPE.SQL_VARCHAR or SQL_TYPE.SQL_LONGVARCHAR;
// Arbitrary limit on display size
if (cchDisplay > DISPLAY_MAX)
cchDisplay = DISPLAY_MAX;
// Allocate a buffer big enough to hold the text representation
// of the data. Add one character for the null terminator
pThisBinding.wszBuffer = new(cchDisplay + 1);
// Map this buffer to the driver's buffer. At Fetch time,
// the driver will fill in this data. Note that the size is
// count of bytes (for Unicode). All ODBC functions that take
// SQLPOINTER use count of bytes; all functions that take only
// strings use count of characters.
SQLBindCol(hStmt, iCol, SQL_C_TCHAR(), pThisBinding.wszBuffer, pThisBinding.wszBuffer.Size, ref pThisBinding.indPtr).ThrowIfFailed(hStmt);
// Now set the display size that we will use to display
// the data. Figure out the length of the column name
pThisBinding.colName = SQLColAttribute<string>(hStmt, iCol, SQL_DESC.SQL_DESC_NAME);
pThisBinding.cDisplaySize = (short)Math.Max(cchDisplay, pThisBinding.colName.Length);
if (pThisBinding.cDisplaySize < NULL_SIZE)
pThisBinding.cDisplaySize = NULL_SIZE;
pDisplay += (short)(pThisBinding.cDisplaySize + DISPLAY_FORMAT_EXTRA);
}
}
/************************************************************************
/* DisplayTitles: print the titles of all the columns and set the
/* shell window's width
/*
/* Parameters:
/* hStmt Statement handle
/* cDisplaySize Total display size
/* pBinding list of binding information
/************************************************************************/
static void DisplayTitles(SQLHSTMT hStmt, short cDisplaySize, IList<BINDING> pBindings)
{
foreach (var pBinding in pBindings)
{
TestContext.Write($"| {(pBinding.colName ?? "").PadRight(pBinding.cDisplaySize)}");
}
TestContext.WriteLine("|");
}
public class BINDING
{
public short cDisplaySize; /* size to display */
public string? colName; /* name of column */
public SafeLPTSTR? wszBuffer; /* display buffer */
public nint indPtr; /* size or null */
public bool fChar; /* character col? */
}
[DllImport("user32.dll", SetLastError = true)]
private static extern HWND GetDesktopWindow();
}

View File

@ -433,6 +433,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSocket", "UnitTests\PInv
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IMAPI", "UnitTests\PInvoke\IMAPI\IMAPI.csproj", "{55E8ADA4-D27A-42B9-8BFD-8313B2DEF6DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vanara.PInvoke.Odbc32", "PInvoke\Odbc32\Vanara.PInvoke.Odbc32.csproj", "{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Odbc32", "UnitTests\PInvoke\Odbc32\Odbc32.csproj", "{94724746-9D66-45E5-8EF1-419E29C6DF12}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -3790,6 +3794,41 @@ Global
{55E8ADA4-D27A-42B9-8BFD-8313B2DEF6DB}.Release|x64.Build.0 = Release|x64
{55E8ADA4-D27A-42B9-8BFD-8313B2DEF6DB}.Release|x86.ActiveCfg = Release|x86
{55E8ADA4-D27A-42B9-8BFD-8313B2DEF6DB}.Release|x86.Build.0 = Release|x86
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Debug|x64.ActiveCfg = Debug|x64
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Debug|x64.Build.0 = Debug|x64
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Debug|x86.ActiveCfg = Debug|x86
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Debug|x86.Build.0 = Debug|x86
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.DebugNoTests|Any CPU.ActiveCfg = Debug|Any CPU
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.DebugNoTests|Any CPU.Build.0 = Debug|Any CPU
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.DebugNoTests|x64.ActiveCfg = Debug|x64
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.DebugNoTests|x64.Build.0 = Debug|x64
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.DebugNoTests|x86.ActiveCfg = Debug|x86
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.DebugNoTests|x86.Build.0 = Debug|x86
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Release|Any CPU.Build.0 = Release|Any CPU
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Release|x64.ActiveCfg = Release|x64
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Release|x64.Build.0 = Release|x64
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Release|x86.ActiveCfg = Release|x86
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37}.Release|x86.Build.0 = Release|x86
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Debug|x64.ActiveCfg = Debug|x64
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Debug|x64.Build.0 = Debug|x64
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Debug|x86.ActiveCfg = Debug|x86
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Debug|x86.Build.0 = Debug|x86
{94724746-9D66-45E5-8EF1-419E29C6DF12}.DebugNoTests|Any CPU.ActiveCfg = DebugNoTests|Any CPU
{94724746-9D66-45E5-8EF1-419E29C6DF12}.DebugNoTests|Any CPU.Build.0 = DebugNoTests|Any CPU
{94724746-9D66-45E5-8EF1-419E29C6DF12}.DebugNoTests|x64.ActiveCfg = DebugNoTests|x64
{94724746-9D66-45E5-8EF1-419E29C6DF12}.DebugNoTests|x64.Build.0 = DebugNoTests|x64
{94724746-9D66-45E5-8EF1-419E29C6DF12}.DebugNoTests|x86.ActiveCfg = DebugNoTests|x86
{94724746-9D66-45E5-8EF1-419E29C6DF12}.DebugNoTests|x86.Build.0 = DebugNoTests|x86
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Release|x64.ActiveCfg = Release|x64
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Release|x64.Build.0 = Release|x64
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Release|x86.ActiveCfg = Release|x86
{94724746-9D66-45E5-8EF1-419E29C6DF12}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -3984,6 +4023,8 @@ Global
{2727B06D-1E73-4108-AFDE-39C1A6F99806} = {3EC6B40D-71D3-4E59-A0E0-544EC605FE11}
{AEA2913A-5E67-4C33-823F-CE06295DB35A} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
{55E8ADA4-D27A-42B9-8BFD-8313B2DEF6DB} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
{ED1BF632-4ACD-4B6B-9F27-C6DE86E00D37} = {212ABBD0-B724-4CFA-9D6D-E3891547FA90}
{94724746-9D66-45E5-8EF1-419E29C6DF12} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {543FAC75-2AF1-4EF1-9609-B242B63FEED4}