Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trying to implement the REFPRP64.DLL in C# API #637

Open
mikevill1968 opened this issue Jan 9, 2025 · 20 comments
Open

Trying to implement the REFPRP64.DLL in C# API #637

mikevill1968 opened this issue Jan 9, 2025 · 20 comments

Comments

@mikevill1968
Copy link

mikevill1968 commented Jan 9, 2025

Hello,

REFPROP version: REFPRP64.dll (no version appears in the properties but it has a timestamp of 06/04/2018 22:24:01)
Operating System: Microsoft Windows 11 Enterprise, Version: 10.0.22631 Build 22631
Access Method: C# wrapper built in-house (code provided below)

I am building a C# .NET API to create endpoints that will access the REFPRP64 high-methods and make then consumable by web applications throughout the company. I have build a C# wrapper and referenced the wrapper in my API. The code for the wrapper is as follows:

public class RefpropWrapper
{
    //Get the path of the FLD files
    [DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern void SETPATHdll(string hpth, int hpth_length);

    //Set which fluids are being passed
    [DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void SETFLUIDSdll(string hFld, ref int ierr, StringBuilder herr, int hFld_length);

    // Import the REFPROPdll function
    [DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void REFPROPdll(
        string hFld,           // Fluid string (semicolon-separated)
        string hIn,            // Input type (e.g., "TP")
        string hOut,           // Output type (e.g., "D")
        ref double a,          // First input (e.g., temperature)
        ref double b,          // Second input (e.g., pressure)
        [In] double[] z,         // Composition array (20 elements)
        ref double output1,    // Output result
        ref int ierr,          // Error code
        StringBuilder herr,    // Error message
        int lerr               // Length of error message
    );

    [DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern void ABFLSHdll(string ab, double a, double b, double[] z, int iFlag, ref double t, ref double p, ref double d, ref double di, ref double dv,
                                        ref double[] x, ref double[] y, ref double q, ref double e, ref double h, ref double s, ref double cv, ref double cp, ref double w,
                                        ref int ierr, StringBuilder herr, int ab_length, int herr_length);
}
```

In my API, I have a controller with the following code:

```
public class RefpropController : ApiController
{
    public string GetRefpropDensity() //(double[] ret, string hFluid, string hInput, string hOutput, double a, double b)
    {
        RefpropViewModel refpropResultsViewModel = new RefpropViewModel();

        // Define inputs
        string fluid = "NITROGEN;METHANE;CO2;ETHANE;PROPANE;ISOBUTAN;BUTANE;IPENTANE;PENTANE"; // Valid fluid names
        string inputUnits = "TP";       // Inputs are temperature and pressure
        string outputProps = "D";       // Desired outputs: Density
        double temperature = 300.0;     // Temperature in K
        double pressure = 6894.76;      // Pressure in kPa
        double[] composition = new double[20]; // Ensure 20 elements
        composition[0] = 0.1;  // Nitrogen
        //for (int i = 1; i < 20; i++) composition[i] = 0.0; // Fill unused slots with 0.0
        composition[1] = 0.2;  // Methane
        composition[2] = 0.05; // CO2
        composition[3] = 0.1;  // Ethane
        composition[4] = 0.15; // Propane
        composition[5] = 0.05; // Isobutane
        composition[6] = 0.1;  // Butane
        composition[7] = 0.15; // Isopentane
        composition[8] = 0.1;  // Pentane
        for (int i = 9; i < 20; i++) composition[i] = 0.0; // Fill unused slots with 0.0

        // Validate mole fractions
        if (Math.Abs(composition.Sum() - 1.0) > 1e-6)
        {
            throw new Exception("Mole fractions in the composition array must sum to 1.");
        }

        // Outputs
        double density = 0.0;
        int ierr = 0;
        StringBuilder herr = new StringBuilder(255); // Pre-allocate error buffer
        int lerr = herr.Capacity; // Set length of error buffer

        // Set REFPROP path
        RefpropWrapper.SETPATHdll(@"C:/Users/mvillegas1/source/Repos/CIPHRAPI/CIPHRAPI/FLUIDS",
                                  @"C:/Users/mvillegas1/source/Repos/CIPHRAPI/CIPHRAPI/FLUIDS".Length);

        // Set fluids
        RefpropWrapper.SETFLUIDSdll(fluid, ref ierr, herr, lerr);
        if (ierr != 0)
        {
            throw new Exception($"SETFLUIDSdll Error {ierr}: {herr}");
        }

        double t = 0.0;
        double p = 0.0;
        double d = 0.0;
        double di = 0.0;
        double dv = 0.0;
        double[] x = new double[20];
        double[] y = new double[20];
        double q = 0.0;
        double e = 0.0;
        double h = 0.0;
        double s = 0.0;
        double cv = 0.0;
        double cp = 0.0;
        double w = 0.0;
        int ab_length = 2;
        int herr_length = 255;


        //Call ABFLSHdll
        RefpropWrapper.ABFLSHdll(inputUnits, temperature, pressure, composition, 200, ref t, ref p, ref d, ref di, ref dv, ref x, ref y, ref q, ref e, ref h, ref s, ref cv, ref cp, ref w,
                                ref ierr, herr, ab_length, herr_length);

        // Call REFPROPdll
        RefpropWrapper.REFPROPdll(fluid, inputUnits, outputProps, ref temperature, ref pressure, composition,
                                  ref density, ref ierr, herr, lerr);

        // Check for errors
        if (ierr != 0)
        {
            refpropResultsViewModel.Error = $"Error {ierr}: {herr}";
        }
        else
        {
            refpropResultsViewModel.Density = density;
        }

        return JsonConvert.SerializeObject(refpropResultsViewModel);
    }
}
```

As you can see, I am populating all input variables and making various calls to the wrapper methods.  When stepping through the code, the call to SETPATHdll runs successfully.  Also, the call to SETFLUIDSdll runs without issue, returning 0 in ierr which according to documentation means that no errors occurred.

However, when calling  ABFLSHdll or REFPROPdll, my application crashes before returning from the call and in my Visual Studio output I get the following message:

"Exception thrown: 'System.AccessViolationException' in CIPHRAPI.dll
The program '[3604] iisexpress.exe' has exited with code 3762504530 (0xe0434352)."

What I expect to happen (specifically with the call to REFPROPdll) is for the call to return and receive the density in the ref variable "density".

Please advise with any help you can provide.

Thank you.
@ianhbell
Copy link
Contributor

ianhbell commented Jan 9, 2025

You need to make sure that all string buffers are at minimum the lengths specified in https://refprop-docs.readthedocs.io/en/latest/DLL/high_level.html#f/_/REFPROPdll

@mikevill1968
Copy link
Author

Thank you for your quick response, Ian. I am not entirely sure of your meaning. Could you please provide a brief example?
Do you mean that all strings (such as hFld) need to be defined in a StringBuilder with a 255 buffer?

@ianhbell
Copy link
Contributor

ianhbell commented Jan 9, 2025

Yes, all string buffers must be at minimum the length indicated in the docs

@ianhbell
Copy link
Contributor

ianhbell commented Jan 9, 2025

For instance this string is way too short:

string outputProps = "D";       // Desired outputs: Density

@mikevill1968
Copy link
Author

mikevill1968 commented Jan 9, 2025

Thank you again Ian.

parameter (ncmax=20) !Maximum number of components in the mixture
parameter (iPropMax=200) !Number of output properties available in ALLPROPS.
character255 hFld,hIn,hOut,hUnits,herr !hFld, hIn, and hOut can actually be of any length.
integer iUnits,iMass,iFlag,ierr,iUCode !Note: as integer
4
double precision a,b,q,Output(iPropMax),z(ncmax),x(ncmax),y(ncmax),x3(ncmax)

Based on the specifications in the documentation as pasted above, I've made some changes to the above code and have the following:

/////////////////////////Wrapper///////////////////////////////////////

[DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void REFPROPdll(
    StringBuilder hFld,           // Fluid string (semicolon-separated)
    StringBuilder hIn,            // Input type (e.g., "TP")
    StringBuilder hOut,           // Output type (e.g., "D")
    ref double a,          // First input (e.g., temperature)
    ref double b,          // Second input (e.g., pressure)
    [In] double[] z,         // Composition array (20 elements)
    ref double[] Output,    // Output result
    ref int ierr,          // Error code
    StringBuilder herr,    // Error message
    int herr_length               // Length of error message
);

///////////////////////Controller//////////////////////////////////////////////

public string GetRefpropDensity() //(double[] ret, string hFluid, string hInput, string hOutput, double a, double b)
{
    RefpropViewModel refpropResultsViewModel = new RefpropViewModel();

    // Define inputs
    StringBuilder fluid = new StringBuilder("NITROGEN;METHANE;CO2;ETHANE;PROPANE;ISOBUTAN;BUTANE;IPENTANE;PENTANE", 255); // Valid fluid names
    StringBuilder inputUnits = new StringBuilder("TP", 255);       // Inputs are temperature and pressure
    StringBuilder outputProps = new StringBuilder("D", 200);       // Desired outputs: Density
    double temperature = 300.0;     // Temperature in K
    double pressure = 6894.76;      // Pressure in kPa
    double[] composition = new double[20]; // Ensure 20 elements
    composition[0] = 0.1;  // Nitrogen
    composition[1] = 0.2;  // Methane
    composition[2] = 0.05; // CO2
    composition[3] = 0.1;  // Ethane
    composition[4] = 0.15; // Propane
    composition[5] = 0.05; // Isobutane
    composition[6] = 0.1;  // Butane
    composition[7] = 0.15; // Isopentane
    composition[8] = 0.1;  // Pentane
    for (int i = 9; i < 20; i++) composition[i] = 0.0; // Fill unused slots with 0.0

    // Validate mole fractions
    if (Math.Abs(composition.Sum() - 1.0) > 1e-6)
    {
        throw new Exception("Mole fractions in the composition array must sum to 1.");
    }

    // Outputs
    double[] density = new double[200];
    int ierr = 0;
    StringBuilder herr = new StringBuilder(255); // Pre-allocate error buffer
    int lerr = herr.Capacity; // Set length of error buffer

    // Call REFPROPdll
    RefpropWrapper.REFPROPdll(fluid, inputUnits, outputProps, ref temperature, ref pressure, composition,
                              ref density, ref ierr, herr, lerr);

    // Check for errors
    if (ierr != 0)
    {
        refpropResultsViewModel.Error = $"Error {ierr}: {herr}";
    }
    else
    {
        var test = density[0];
        
        refpropResultsViewModel.Density = density[0];
    }

    return JsonConvert.SerializeObject(refpropResultsViewModel);
}

Still crashing and getting:

Exception thrown: 'System.AccessViolationException' in CIPHRAPI.dll
The program '[8864] iisexpress.exe' has exited with code 3762504530 (0xe0434352).

What am I missing here?

@ianhbell
Copy link
Contributor

Lengths are still wrong. See the docs:

hFld_length [int ] :: length of variable hFld (default: 10000)
hIn_length [int ] :: length of variable hIn (default: 255)
hOut_length [int ] :: length of variable hOut (default: 255)
hUnits_length [int ] :: length of variable hUnits (default: 255)
herr_length [int ] :: length of variable herr (default: 255)

There are user-contributed wrappers for C# you should use: https://github.com/usnistgov/REFPROP-wrappers/tree/master/wrappers/Csharp

@mikevill1968
Copy link
Author

mikevill1968 commented Jan 10, 2025

Hello Ian,

Thanks again for your quick response. I have modified my initial code to reflect the information you have given me and it is still crashing with the System.AccessViolationException.

I do have the C# example in the wrappers documentation and tried to implement it in my code. However, I did notice that the example uses the legacy dll which requires calling SETUPdll. As far as I understand, these legacy methods are deprecated and should not be used. So, I have a few questions:

  1. Do I need to call the legacy SETUPdll method to use the REFPROPdll in the high-level API?
  2. Are all parameters (including output ref parameters) required when calling the method. I ask this because I have left out hUnits as well as other output parameters I am not interested or have need for. I am only interested in calculating density. Also, there are mentions of default values for some if not specified.
  3. Is the Output always a double array of 200? Even when the only hOut value is "D"?

My modified code is below. Please point out what I am missing so I may correct it. Thank you!

///////////////////////WRAPPER//////////////////////////////

// Import the REFPROPdll function
[DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void REFPROPdll(
    string hFld,            // Fluid string (semicolon-separated)
    string hIn,             // Input type (e.g., "TP")
    string hOut,            // Output type (e.g., "D")
    ref double a,           // First input (e.g., temperature)
    ref double b,           // Second input (e.g., pressure)
    double[] z,             // Composition array (20 elements)
    ref double[] Output,    // Output result
    ref long ierr,          // Error code
    ref string herr,        // Error message
    long herr_length    // Length of error message
);

/////////////////////CONTROLLER/////////////////////////////

public string GetRefpropDensity() 
{
    RefpropViewModel refpropResultsViewModel = new RefpropViewModel();

    // Define inputs
    long iErr, kph = 1;

    double te = 0.0, p = 0.0, d = 0.0, Dl = 0.0, Dv = 0.0, q = 0.0, ee = 0.0, hh = 0.0, ss = 0.0, cp = 0.0, cv = 0.0, w = 0.0;
    double[] x = new double[20], xliq = new double[20], xvap = new double[20];
    double[] xlkg = new double[20], xvkg = new double[20];
    double[] output = new double[200];

    double tk = 0.0, wm = 0.0;

    string hpath = @"C:\Users\mvillegas1\source\Repos\CIPHRAPI\CIPHRAPI\FLUIDS";
    long size = hpath.Length;
    hpath += new String(' ', 255 - (int)size);
    
    RefpropWrapper.SETPATHdll(hpath, ref size);

    long numComps = 10;
    string hfld = "NITROGEN|METHANE|CO2|ETHANE|PROPANE|ISOBUTAN|BUTANE|IPENTANE|PENTANE|HEXANE"; // Valid fluid names
    size = hfld.Length;
    hfld += new String(' ', 10000 - (int)size);

    string herr = new String(' ', 255);

    //Composition
    double[] z = new double[20]; // Ensure 20 elements
    z[0] = 0.1;  // Nitrogen
    z[1] = 0.2;  // Methane
    z[2] = 0.05; // CO2
    z[3] = 0.1;  // Ethane
    z[4] = 0.15; // Propane
    z[5] = 0.05; // Isobutane
    z[6] = 0.1;  // Butane
    z[7] = 0.15; // Isopentane
    z[8] = 0.1;  // Pentane
    z[9] = 0.0;  // Hexane
    for (int i = 10; i < 20; i++) z[i] = 0.0; // Fill unused slots with 0.0

    // Validate mole fractions
    if (Math.Abs(z.Sum() - 1.0) > 1e-6)
    {
        throw new Exception("Mole fractions in the composition array must sum to 1.");
    }

    string hIn = "TP";
    size = hIn.Length;
    hIn += new String(' ', 255 - (int)size);

    string hOut = "D";
    size = hOut.Length;
    hOut += new String(' ', 255 - (int)size);

    iErr = 0;
    long hfldLen = hfld.Length, hOutLen = hOut.Length, herrLen = herr.Length;  //Confirmed as (10000, 255, 255, 255)

    double a = 300.0;     // Temperature in K
    double b = 6894.76;      // Pressure in kPa

    // Call REFPROPdll
    RefpropWrapper.REFPROPdll(hfld, hIn, hOut, ref a, ref b, z, ref output, ref iErr, ref herr, herrLen);

    // Check for errors
    if (iErr != 0)
    {
        refpropResultsViewModel.Error = $"Error {iErr}: {herr}";
    }
    else
    {
        refpropResultsViewModel.Density = output[0];
    }

    return JsonConvert.SerializeObject(refpropResultsViewModel);
}

@ianhbell
Copy link
Contributor

You are also not passing the right string length variables. Please see the examples in the Python wrapper: https://github.com/usnistgov/REFPROP-wrappers/blob/master/wrappers/python/ctypes/ctREFPROP/ctREFPROP.py

@mikevill1968
Copy link
Author

mikevill1968 commented Jan 13, 2025

Hello Ian,

Are you specifically speaking about hFld_length, hIn_length, hOut_length and hUnits_length? Also, could you please address my three questions above? I believe that question number 2 might be related somewhat to those 4 fields.

I apologize for my many inquiries. It is difficult for me to decipher single sentence answers and Python code. The documentation, though extensive, is not very clear in terms of what is a required parameter and which is not.

I am not asking you to write my code for me, I am asking specific questions whose answers will allow me to understand the documentation better and write my own code for the high-level API methods which do not have wrapper and implementation examples.

Thank you,

Michael Villegas

@marciahuber
Copy link

Hi, I am afraid I have zero knowledge of C#, but if you have not done so already, please take a look at the C# wrapper that another user has kindly posted here https://github.com/usnistgov/REFPROP-wrappers/tree/master/wrappers/Csharp If you continue to have problems perhaps contact the author of the wrapper.

@mikevill1968
Copy link
Author

Hello Marcia,

The author of the wrapper is not available to contact from GIT hub.

Is there anyone you can point me to who has knowledge of the high-level API dell (REFPRP64.DLL) and its implementation requirements to consume it through a C# API?

We purchased the license for this DLL and are unable to find anyone who can provide proper support.

Thank you very much,

Michael Villegas

@marciahuber
Copy link

marciahuber commented Jan 13, 2025 via email

@gallopinggoose6
Copy link

@mikevill1968

To answer your three questions, a little bit of explanation of how REFPROP works may be helpful. REFPROP is written in a language called FORTRAN 77. This low-level language does not allow for dynamic memory allocation, meaning that all inputs in and out of the program must be completely defined, and with fully fixed lengths, as new and recent language features like overloading do not exist.

Furthermore, because you're working with low level DLL interactions that leaves the safe C# memory managed bubble, your routines need to call the exact hooks that exist on the DLL, and this mean they need the precise number of arguments as well as array sizes.

So to answer your questions:

  1. I don't think so.
  2. Yes. This is why you're seeing access violations.
  3. Yes. This is how FORTRAN works.

Feel free to direct your further questions towards me. Sorry for the personal Github account.

@mikevill1968
Copy link
Author

Thank you so much for your reply Tyler. this makes things much clearer.

In my latest code to call the REPROPdll method I have the following call:

RefpropWrapper.REFPROPdll(hfld, hIn, hOut, iUnits, iMass, iFlag, a, b, z, ref output, ref hUnits, ref iUCode, ref x, ref y, ref x3, ref q, ref iErr,
                          ref herr, hfldLen, hInLen, hOutLen, hUnits_length, herrLen);

The input parameter values along with explanations of each are the following

hfld = "NITROGEN|METHANE|CO2|ETHANE|PROPANE|ISOBUTAN|BUTANE|IPENTANE|PENTANE|HEXANE" (with blank spaces at the end that add up to 10000 characters)

hIn = "TP" (with blank spaces at the end that add up to 255 characters)

hOut = "D" (with blank spaces at the ed that add up to 255 characters)

iUnits = 3 (SI with Celcius)

iMass = 0 (Molecular)

iFlag = 0 (since I am not looking for saturation and don't require SATSPLN to be called)

a = 26.9 (temperature in Celcius)

b = 6.89476 (pressure in kPa)

z = an array of type double and size 20 whose values for the first components in the order of hFld add up to 1)

hfldLen = 10000

hInLen = 255

hOutLen = 255

hUnits_length = 255

herrLen = 255

As far as I can surmise, this should run, based on what I see in the documentation for the method but it still crashes and returns the System.AccessViolationException

Is something missing or off in those input values?

@gallopinggoose6
Copy link

Hi Michael, I'm recreating your code and conducting a few experiments, expect to hear back from me later today.

@mikevill1968
Copy link
Author

mikevill1968 commented Jan 15, 2025

Hello again Tyler,

The good news is that it is no longer crashing when calling REFPROPdll. The bad news is that it returning an error "Length cannot be less than zero.\r\nParameter name: length". which appears to happen in VBByValStrMarshaller.cs (which I can see in decompiled code)

[SecurityCritical]
internal unsafe static string ConvertToManaged(IntPtr pNative, int cch)
{
if (IntPtr.Zero == pNative)
{
return null;
}
return new string((sbyte*)(void*)pNative, 0, cch);

I am adding my recent code below in full so you can see my explanations better: I know I am VERY CLOSE to getting this to work. :-)

////////////////////WRAPPER//////////////////////////////////////////////////

[DllImport("REFPRP64.dll", CharSet = CharSet.Ansi)]
public static extern void SETUPdll
         (
         ref int nComps,                                          // (INPUT) number of components (1 for pure fluid) [integer]
         [MarshalAs(UnmanagedType.VBByRefStr)] ref string hfld,    // (INPUT) list of file names specifying fluid/mixture components. Separated by pipes "|"
                                                                   //  e.g., METHANE|AMMONIA|ARGON
         [MarshalAs(UnmanagedType.VBByRefStr)] ref string hfmix,   // (INPUT) mixture coefficients. File name containing coefficients for mixture model, if applicable
         [MarshalAs(UnmanagedType.VBByRefStr)] ref string hrf,     // (INPUT) reference state for thermodynamic calculations [3 character string]
                                                                   //  'DEF' : default reference state as specified in fluid file is applied to each pure component
                                                                   //  'NBP' : h,s = 0 at pure component normal boiling point(s)
                                                                   //  'ASH' : h,s = 0 for sat liquid at -40 C (ASHRAE convention)
                                                                   //  'IIR' : h= 200, s = 1.0 for sat liq at 0 C (IIR convention)
                                                                   //  other choices are possible, but these require a separate call to SETREF
         ref int ierr,                                            // (OUTPUT) error flag: 
                                                                   //  0 = successful
                                                                   //  101 = error in opening file
                                                                   //  102 = error in file or premature end of file
                                                                   //  -103 = unknown model encountered in file
                                                                   //  104 = error in setup of model
                                                                   //  105 = specified model not found
                                                                   //  111 = error in opening mixture file
                                                                   //  112 = mixture file of wrong type
                                                                   //  114 = nc<>nc from setmod
                                                                   //  -117 = binary pair not found, all parameters will be estimated
                                                                   //  117 = no mixture data are available, mixture is outside the range of the model and calculations will not be made
         [MarshalAs(UnmanagedType.VBByRefStr)] ref string herr,    // (OUTPUT) error string
         ref int hfldLen,
         ref int hfmixLen,
         ref int hrfLen,
         ref int herrLen
    );

//Get the path of the FLD files
[DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void SETPATHdll(string hpth, int hpth_length);

//Set which fluids are being passed
[DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void SETFLUIDSdll(string hFld, ref int ierr, int hFld_length);

// Import the REFPROPdll function
[DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void REFPROPdll(
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hFld,              // Fluid string (semicolon-separated)
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hIn,               // Input type (e.g., "TP")
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hOut,              // Output type (e.g., "D")
    ref int iUnits,                                                         // The unit system to be used for the input and output properties (e.g., 3)
    ref int iMass,                                                          // 0 for mole
    ref int iFlag,                                                          // See Flags table (no flags required?)
    ref double a,                                                           // First input (e.g., temperature in Celcius)
    ref double b,                                                           // Second input (e.g., pressure in Mega Pascals)
    ref double[] z,                                                         // Composition array (20 elements)
    ref double[] Output,                                                // Output result array (200 elements)
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hUnits,            // Units for the fist property in the Output array
    ref int iUCode,                                                     // Unit code that represents the units of the first property in the Output array
    ref double[] x,                                                     // Composition of the liquid phase (20 elements)
    ref double[] y,                                                     // Composition of the vapor phase (20 elements)
    ref double[] x3,                                                    // Reserved for returning the composition of a second liquid phase for LLE or VLLE
    ref double q,                                                       // Vapor quality on a mole or mass basis depending on the value of iMass
    ref int ierr,                                                       // Error code
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string herr,              // Error message
    ref int hFld_length,                                                    // Length of hFld (10000)
    ref int hIn_length,                                                     // Length of hIn (255)
    ref int hOut_length,                                                    // Length of hOut (255)
    ref int hUnits_length,                                                  // Length of hUnits (255)
    ref int herr_length                                                     // Length of error message (255)
);

///////////////////CONTROLLER//////////////////////////////////////////////

public string GetRefpropDensity() //(double[] ret, string hFluid, string hInput, string hOutput, double a, double b)
{
    RefpropViewModel refpropResultsViewModel = new RefpropViewModel();

    // Define inputs
    int iErr = 0;

    double q = 0.0;
    double[] x = new double[20];
    double[] output = new double[200];

    string hpath = @"C:\Users\mvillegas1\source\Repos\CIPHRAPI\CIPHRAPI\FLUIDS";
    int size = hpath.Length;
    hpath += new String(' ', 255 - (int)size);
    int hPathLen = hpath.Length;

    int numComps = 10;
    string hfld = "NITROGEN;METHANE;CO2;ETHANE;PROPANE;ISOBUTAN;BUTANE;IPENTANE;PENTANE;HEXANE"; // Valid fluid names
    size = hfld.Length;
    hfld += new String(' ', 10000 - (int)size);
    int hFldLen = hfld.Length;

    string herr = new String(' ', 255);

    //Composition
    double[] z = new double[20]; // Ensure 20 elements
    z[0] = 0.1;  // Nitrogen
    z[1] = 0.2;  // Methane
    z[2] = 0.05; // CO2
    z[3] = 0.1;  // Ethane
    z[4] = 0.15; // Propane
    z[5] = 0.05; // Isobutane
    z[6] = 0.1;  // Butane
    z[7] = 0.15; // Isopentane
    z[8] = 0.1;  // Pentane
    z[9] = 0.0;  // Hexane
    for (int i = 10; i < 20; i++) z[i] = 0.0; // Fill unused slots with 0.0

    // Validate mole fractions
    if (Math.Abs(z.Sum() - 1.0) > 1e-6)
    {
        throw new Exception("Mole fractions in the composition array must sum to 1.");
    }

    string hIn = "TP";
    size = hIn.Length;
    hIn += new String(' ', 255 - (int)size);

    string hOut = "D";
    size = hOut.Length;
    hOut += new String(' ', 255 - (int)size);

    iErr = 0;
    int hfldLen = hfld.Length, hInLen = hIn.Length, hOutLen = hOut.Length, herrLen = herr.Length;  //Confirmed as (10000, 255, 255, 255)

    double a = 26.9;     // Temperature in Celcius
    double b = 6.89476;      // Pressure in kPa

    int iUnits = 3; //SI with Celcius
    int iMass = 0;
    int iFlag = 0;
    string hUnits = "";
    size = hUnits.Length;
    hUnits += new String(' ', 255 - (int)size);
    int hUnits_length = 255;
    int iUCode = 0;
    double[] y = new double[20];
    double[] x3 = new double[20];
    double c = 0.0;
    string hfmix = "";
    size = hfmix.Length;
    hfmix += new String(' ', 255 - (int)size);
    int hfmixlen = hfmix.Length;
    string hrf = "DEF";
    size = hrf.Length;
    hrf += new String(' ', 255 - (int)size);
    int hrflen = hfmix.Length;

    RefpropWrapper.SETPATHdll(hpath, hPathLen);

    //Set mixture
    RefpropWrapper.SETFLUIDSdll(hfld, ref iErr, hFldLen); //Runs with no errors

    RefpropWrapper.SETUPdll(ref numComps, ref hfld, ref hfmix, ref hrf, ref iErr, ref herr, ref hfldLen, ref hfmixlen, ref hrflen, ref herrLen);  //Runs with no errors

    // Call REFPROPdll
    RefpropWrapper.REFPROPdll(ref hfld, ref hIn, ref hOut, ref iUnits, ref iMass, ref iFlag, ref a, ref b, ref z, ref output, ref hUnits, ref iUCode, ref x, ref y, ref x3, ref q, ref iErr,
                              ref herr, ref hfldLen, ref hInLen, ref hOutLen, ref hUnits_length, ref herrLen);  //No longer crashing but getting error "Length cannot be less than zero.\r\nParameter name: length"

    // Check for errors
    if (iErr != 0)
    {
        refpropResultsViewModel.Error = $"Error {iErr}: {herr}";
    }
    else
    {
        refpropResultsViewModel.Density = output[0];
    }

    return JsonConvert.SerializeObject(refpropResultsViewModel);
}

@mikevill1968
Copy link
Author

Hello again Tyler.

I figured it out!!!

My issue was not with my controller call. It was with my wrapper. Your information on Fortran made me go back to 1989 in college and realize that I am dealing with unmanaged type arrays. I was already marshalling the string arrays but not the double type arrays. Adding the InteropServices.MarshalAs method to those arrays and passing them by value instead of by reference fixed the problem and now the REFPROPdll method is returning values without crashing or erroring out.

Thank you SO very much for your help!!!

My wrapper code below:

// Import the REFPROPdll function
[DllImport("REFPRP64.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void REFPROPdll(
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hFld,                  // Fluid string (semicolon-separated)
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hIn,                   // Input type (e.g., "TP")
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hOut,                  // Output type (e.g., "D")
    ref int iUnits,                                                         // The unit system to be used for the input and output properties (e.g., 3)
    ref int iMass,                                                          // 0 for mole
    ref int iFlag,                                                          // See Flags table (no flags required?)
    ref double a,                                                           // First input (e.g., temperature in Celcius)
    ref double b,                                                           // Second input (e.g., pressure in Mega Pascals)
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] z,      // Composition array (20 elements)
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] Output, // Output result array (200 elements)
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string hUnits,                // Units for the fist property in the Output array
    ref int iUCode,                                                         // Unit code that represents the units of the first property in the Output array
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x,      // Composition of the liquid phase (20 elements)
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] y,      // Composition of the vapor phase (20 elements)
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x3,     // Reserved for returning the composition of a second liquid phase for LLE or VLLE
    ref double q,                                                           // Vapor quality on a mole or mass basis depending on the value of iMass
    ref int ierr,                                                           // Error code
    [MarshalAs(UnmanagedType.VBByRefStr)] ref string herr,                  // Error message
    ref int hFld_length,                                                    // Length of hFld (10000)
    ref int hIn_length,                                                     // Length of hIn (255)
    ref int hOut_length,                                                    // Length of hOut (255)
    ref int hUnits_length,                                                  // Length of hUnits (255)
    ref int herr_length                                                     // Length of error message (255)
);

@gallopinggoose6
Copy link

Happy to be of assistance. I normally don't interact with NIST's customers (hence the personal github, getting that fixed), but you can also direct any further REFPROP and C# questions to me.

@gallopinggoose6
Copy link

I'm going to leave this issue open while I update our C# wrapper example to use the new API.

@mikevill1968
Copy link
Author

mikevill1968 commented Jan 15, 2025

Thank you again Tyler

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants