Skip to content

Commit

Permalink
[.NET] Stub out foundation classes for .Net interface
Browse files Browse the repository at this point in the history
  • Loading branch information
burkenyo authored and speth committed Aug 17, 2022
1 parent b9148b0 commit 6d9ef17
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 0 deletions.
37 changes: 37 additions & 0 deletions interfaces/dotnet/.gitignore
@@ -0,0 +1,37 @@
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/

# Visual Studio Code
.vscode

# Rider
.idea

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn

# Visual Studio 2015
.vs/
22 changes: 22 additions & 0 deletions interfaces/dotnet/Cantera.sln
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cantera", "Cantera\Cantera.csproj", "{9FAB2B8A-5BAA-4D44-B0E4-1B00A45A7DAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9FAB2B8A-5BAA-4D44-B0E4-1B00A45A7DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FAB2B8A-5BAA-4D44-B0E4-1B00A45A7DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FAB2B8A-5BAA-4D44-B0E4-1B00A45A7DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FAB2B8A-5BAA-4D44-B0E4-1B00A45A7DAC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions interfaces/dotnet/Cantera/Cantera.csproj
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>10</LangVersion>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AssemblyName>CanteraDotNet</AssemblyName>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Nullable" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions interfaces/dotnet/Cantera/src/CanteraException.cs
@@ -0,0 +1,23 @@
using System.Runtime.InteropServices;

namespace Cantera;

/// <summary>
/// Represents a error that occurred in the native Cantera library.
/// </summary>
public class CanteraException : ExternalException
{
static class LibCantera
{
[DllImport(Interop.LIBCANTERA)]
unsafe public static extern int ct_getCanteraError(int buflen, byte* buf);
}

private CanteraException(string message) : base(message) { }

unsafe internal static void ThrowLatest()
{
var errorMessage = Interop.GetString(500, LibCantera.ct_getCanteraError);
throw new CanteraException(errorMessage);
}
}
29 changes: 29 additions & 0 deletions interfaces/dotnet/Cantera/src/CanteraHandle.cs
@@ -0,0 +1,29 @@
using System.Runtime.InteropServices;

namespace Cantera;

/// <summary>
/// The base class for a handle to a cantera object.
/// </summary>
/// </remarks>
/// Cantera uses signed 32-bit ints for handles, yet SafeHandle uses "native int" IntPtr.
/// The constuctor and the IsInvalid property are designed to account for this.
/// <remarks>
internal abstract class CanteraHandle : SafeHandle
{
static IntPtr INVALID = IntPtr.Size == 4
? new IntPtr(-1) // 32-bit IntPtr: 0xFFFFFFFF
: new IntPtr((long) unchecked((uint) -1)); // 64-bit IntPtr: 0x00000000FFFFFFFF

public CanteraHandle() : base(INVALID, true) { }

public override bool IsInvalid =>
(int)(long)handle < 0; // removes any leading bits

public void EnsureValidHandleReceived()
{
if (IsInvalid)
CanteraException.ThrowLatest();
}

}
28 changes: 28 additions & 0 deletions interfaces/dotnet/Cantera/src/CanteraInfo.cs
@@ -0,0 +1,28 @@
using System.Runtime.InteropServices;

namespace Cantera;

public static class CanteraInfo
{
static class LibCantera
{
[DllImport(Interop.LIBCANTERA)]
unsafe public static extern int ct_getCanteraVersion(int buflen, byte* buf);

[DllImport(Interop.LIBCANTERA)]
unsafe public static extern int ct_getGitCommit(int buflen, byte* buf);

}

unsafe static readonly Lazy<string> _version =
new Lazy<string>(() => Interop.GetString(10, LibCantera.ct_getCanteraVersion));

unsafe static readonly Lazy<string> _gitCommit =
new Lazy<string>(() => Interop.GetString(10, LibCantera.ct_getGitCommit));

public static string Version =>
_version.Value;

public static string GitCommit =>
_gitCommit.Value;
}
99 changes: 99 additions & 0 deletions interfaces/dotnet/Cantera/src/Interop.cs
@@ -0,0 +1,99 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;

namespace Cantera;

internal static class Interop
{
public unsafe delegate int GetStringFunc(int initialSize, byte* buffer);

static ArrayPool<byte> Pool = ArrayPool<byte>.Shared;

public const string LIBCANTERA = "cantera.2.6.0";

public static double CheckReturn(double code)
{
// Cantera returns this value when the function resulted in error
const double Error = -999.999;

if (code == Error)
CanteraException.ThrowLatest();

return code;
}

public static int CheckReturn(int code)
{
// Cantera returns this value when the function resulted in an error internal to Cantera
const int Error1 = -1;
// Cantera returns this value when the function resulted in an external error
// Some functions also return a negative value as the amount of space they need to
// fill a buffer with a string. There is no way to account for the ambiguity that arises
// when such a function returns -999!
const int Error999 = -999;
if (code == Error1 || code == Error999)
CanteraException.ThrowLatest();

// some functions return negative when they want more chars, others positive!
return Math.Abs(code);
}

[SuppressMessage("Reliability", "CA2014:NoStackallocInLoops", Justification = "Loop is executed at most twice.")]
public static string GetString(int initialSize, GetStringFunc func)
{
// take up to two tries
// 1) use the initial size
// if the initial size was large enough, return the string
// if the initial size was not large enough ...
// 2) try again with the needed size
// if the needed size was large enough, return the string
// otherwise, catastrophe, throw!
for(var i = 0; i < 2; i++)
{
int neededSize;

if (initialSize <= 120)
{
Span<byte> span = stackalloc byte[initialSize];
if (TryGetString(span, func, out var value, out neededSize))
return value;
}
else
{
var array = Pool.Rent(initialSize);
try
{
if (TryGetString(array, func, out var value, out neededSize))
return value;
}
finally
{
Pool.Return(array);
}
}

initialSize = neededSize;
}

throw new InvalidOperationException("Could not retrieve a string value from Cantera!");

unsafe static bool TryGetString(Span<byte> span, GetStringFunc func, [NotNullWhen(true)] out string? value, out int neededSize)
{
var initialSize = span.Length;

fixed(byte* buffer = span)
{
neededSize = CheckReturn(func(initialSize, buffer));

if(initialSize >= neededSize)
{
value = new String((sbyte*) buffer);
return true;
}
}

value = null;
return false;
}
}
}

0 comments on commit 6d9ef17

Please sign in to comment.