Skip to main content

refprop/
sys.rs

1//! Low-level FFI bindings for NIST REFPROP.
2//!
3//! This module dynamically loads the REFPROP shared library (DLL/so)
4//! at runtime via [`libloading`] and pre-resolves all function pointers
5//! for zero-overhead calls.
6
7#![allow(non_snake_case)]
8
9use std::os::raw::{c_char, c_double, c_int, c_long};
10use std::path::Path;
11
12use libloading::Library;
13
14// ── REFPROP constants ───────────────────────────────────────────────
15pub const REFPROP_STRLEN: usize = 255;
16pub const REFPROP_FILESTR: usize = 10000;
17pub const REFPROP_NC_MAX: usize = 20;
18
19// ── Error type ──────────────────────────────────────────────────────
20#[derive(Debug)]
21pub enum RefpropSysError {
22    /// The DLL/so could not be found or loaded.
23    LibraryLoadFailed(String),
24    /// A required symbol was not found in the library.
25    SymbolNotFound(String),
26}
27
28impl std::fmt::Display for RefpropSysError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            Self::LibraryLoadFailed(msg) => write!(f, "REFPROP library load failed: {msg}"),
32            Self::SymbolNotFound(sym) => {
33                write!(f, "Symbol not found in REFPROP library: {sym}")
34            }
35        }
36    }
37}
38
39impl std::error::Error for RefpropSysError {}
40
41// ── Function pointer type aliases ───────────────────────────────────
42// These match the Fortran calling convention used by REFPROP.
43// Grouped by signature similarity for readability.
44
45/// SETPATHdll(hpath, length)
46type FnSetpath = unsafe extern "C" fn(*const c_char, c_long);
47
48/// SETUPdll(nc, hfld, hfmix, hrf, ierr, herr, len...)
49type FnSetup = unsafe extern "C" fn(
50    *const c_int,
51    *const c_char,
52    *const c_char,
53    *const c_char,
54    *mut c_int,
55    *mut c_char,
56    c_long,
57    c_long,
58    c_long,
59    c_long,
60);
61
62/// TPFLSHdll / PHFLSHdll / PSFLSHdll – all share the same signature:
63/// (in1, in2, z, out1..out12, ierr, herr, herr_length)
64type FnFlash = unsafe extern "C" fn(
65    *const c_double,
66    *const c_double,
67    *const c_double,
68    *mut c_double,
69    *mut c_double,
70    *mut c_double,
71    *mut c_double,
72    *mut c_double,
73    *mut c_double,
74    *mut c_double,
75    *mut c_double,
76    *mut c_double,
77    *mut c_double,
78    *mut c_double,
79    *mut c_double,
80    *mut c_int,
81    *mut c_char,
82    c_long,
83);
84
85/// THFLSHdll / TSFLSHdll / DHFLSHdll … – flash with extra `kr` root
86/// selector:
87/// (in1, in2, z, kr, p/out, d, dl, dv, x, y, q, e, out2, cv, cp, w, ierr, herr, herr_length)
88type FnFlashKr = unsafe extern "C" fn(
89    *const c_double,
90    *const c_double,
91    *const c_double,
92    *mut c_double,
93    *mut c_double,
94    *mut c_double,
95    *mut c_double,
96    *mut c_double,
97    *mut c_double,
98    *mut c_double,
99    *mut c_double,
100    *mut c_double,
101    *mut c_double,
102    *mut c_double,
103    *mut c_double,
104    *mut c_double,
105    *mut c_int,
106    *mut c_char,
107    c_long,
108);
109
110/// SATTdll / SATPdll – same signature:
111/// (in, z, kph, out1..out5, ierr, herr, herr_length)
112type FnSat = unsafe extern "C" fn(
113    *const c_double,
114    *const c_double,
115    *const c_int,
116    *mut c_double,
117    *mut c_double,
118    *mut c_double,
119    *mut c_double,
120    *mut c_double,
121    *mut c_int,
122    *mut c_char,
123    c_long,
124);
125
126/// CRITPdll(z, tc, pc, dc, ierr, herr, herr_length)
127type FnCritp = unsafe extern "C" fn(
128    *const c_double,
129    *mut c_double,
130    *mut c_double,
131    *mut c_double,
132    *mut c_int,
133    *mut c_char,
134    c_long,
135);
136
137/// TRNPRPdll(t, d, z, eta, tcx, ierr, herr, herr_length)
138type FnTrnprp = unsafe extern "C" fn(
139    *const c_double,
140    *const c_double,
141    *const c_double,
142    *mut c_double,
143    *mut c_double,
144    *mut c_int,
145    *mut c_char,
146    c_long,
147);
148
149/// SETMIXdll(hmxnme, hfmix, hrf, nc, hfld, z, ierr, herr, len...)
150type FnSetmix = unsafe extern "C" fn(
151    *const c_char,
152    *const c_char,
153    *const c_char,
154    *mut c_int,
155    *mut c_char,
156    *mut c_double,
157    *mut c_int,
158    *mut c_char,
159    c_long,
160    c_long,
161    c_long,
162    c_long,
163    c_long,
164);
165
166/// THERMdll(t, d, z, p, e, h, s, cv, cp, w, hjt)
167type FnTherm = unsafe extern "C" fn(
168    *const c_double,
169    *const c_double,
170    *const c_double,
171    *mut c_double,
172    *mut c_double,
173    *mut c_double,
174    *mut c_double,
175    *mut c_double,
176    *mut c_double,
177    *mut c_double,
178    *mut c_double,
179);
180
181/// INFOdll(icomp, wmm, ttrp, tnbpt, tc, pc, dc, zc, acf, dip, rgas)
182type FnInfo = unsafe extern "C" fn(
183    *const c_int,
184    *mut c_double,
185    *mut c_double,
186    *mut c_double,
187    *mut c_double,
188    *mut c_double,
189    *mut c_double,
190    *mut c_double,
191    *mut c_double,
192    *mut c_double,
193    *mut c_double,
194);
195
196// ── Dynamic library wrapper ─────────────────────────────────────────
197
198/// Holds a dynamically-loaded REFPROP shared library with **pre-resolved
199/// function pointers** for zero-overhead calls.
200///
201/// All function symbols are resolved once at construction time.  If any
202/// required symbol is missing the constructor returns an error instead
203/// of panicking later.
204///
205/// All methods are `unsafe` because they forward raw pointers to Fortran
206/// code that cannot be verified by the Rust compiler.
207pub struct RefpropLibrary {
208    /// The underlying library handle.  Must stay alive to keep the DLL
209    /// loaded and the function pointers valid.
210    _lib: Library,
211
212    // ── Cached function pointers ────────────────────────────────────
213    fn_setpath: FnSetpath,
214    fn_setup: FnSetup,
215    fn_tpflsh: FnFlash,
216    fn_phflsh: FnFlash,
217    fn_psflsh: FnFlash,
218    fn_satt: FnSat,
219    fn_satp: FnSat,
220    fn_critp: FnCritp,
221    fn_trnprp: FnTrnprp,
222    fn_setmix: FnSetmix,
223    fn_tdflsh: FnFlash,
224    fn_pdflsh: FnFlash,
225    fn_thflsh: FnFlashKr,
226    fn_tsflsh: FnFlashKr,
227    fn_dhflsh: FnFlash,
228    fn_dsflsh: FnFlash,
229    fn_hsflsh: FnFlash,
230    fn_therm: FnTherm,
231    fn_info: FnInfo,
232}
233
234impl RefpropLibrary {
235    // ── Symbol resolution ───────────────────────────────────────────
236
237    /// Resolve a single symbol from the library as a typed function
238    /// pointer.  Returns `Err(SymbolNotFound)` if the symbol is absent.
239    fn resolve<T: Copy>(lib: &Library, name: &[u8]) -> Result<T, RefpropSysError> {
240        // SAFETY: We are loading a known symbol name from a REFPROP DLL.
241        // The caller (resolve_all) ensures all type aliases match the
242        // actual Fortran calling convention.
243        let sym: libloading::Symbol<T> = unsafe { lib.get(name) }.map_err(|_| {
244            // Strip trailing \0 for display.
245            let display =
246                String::from_utf8_lossy(&name[..name.len().saturating_sub(1)]).to_string();
247            RefpropSysError::SymbolNotFound(display)
248        })?;
249        Ok(*sym)
250    }
251
252    /// Resolve **all** required REFPROP symbols from an already-loaded
253    /// library.  Fails on the first missing symbol.
254    fn resolve_all(lib: Library) -> Result<Self, RefpropSysError> {
255        Ok(Self {
256            fn_setpath: Self::resolve(&lib, b"SETPATHdll\0")?,
257            fn_setup: Self::resolve(&lib, b"SETUPdll\0")?,
258            fn_tpflsh: Self::resolve(&lib, b"TPFLSHdll\0")?,
259            fn_phflsh: Self::resolve(&lib, b"PHFLSHdll\0")?,
260            fn_psflsh: Self::resolve(&lib, b"PSFLSHdll\0")?,
261            fn_satt: Self::resolve(&lib, b"SATTdll\0")?,
262            fn_satp: Self::resolve(&lib, b"SATPdll\0")?,
263            fn_critp: Self::resolve(&lib, b"CRITPdll\0")?,
264            fn_trnprp: Self::resolve(&lib, b"TRNPRPdll\0")?,
265            fn_setmix: Self::resolve(&lib, b"SETMIXdll\0")?,
266            fn_tdflsh: Self::resolve(&lib, b"TDFLSHdll\0")?,
267            fn_pdflsh: Self::resolve(&lib, b"PDFLSHdll\0")?,
268            fn_thflsh: Self::resolve(&lib, b"THFLSHdll\0")?,
269            fn_tsflsh: Self::resolve(&lib, b"TSFLSHdll\0")?,
270            fn_dhflsh: Self::resolve(&lib, b"DHFLSHdll\0")?,
271            fn_dsflsh: Self::resolve(&lib, b"DSFLSHdll\0")?,
272            fn_hsflsh: Self::resolve(&lib, b"HSFLSHdll\0")?,
273            fn_therm: Self::resolve(&lib, b"THERMdll\0")?,
274            fn_info: Self::resolve(&lib, b"INFOdll\0")?,
275            _lib: lib,
276        })
277    }
278
279    // ── Constructors ────────────────────────────────────────────────
280
281    /// Try to load the REFPROP shared library from a **directory** that
282    /// contains the DLL / .so.  Common file names are tried automatically.
283    ///
284    /// On 64-bit Windows the 64-bit DLL (`REFPRP64.DLL`) is tried first.
285    /// If a candidate file exists but cannot be loaded (e.g. architecture
286    /// mismatch), the next candidate is tried.
287    ///
288    /// All required symbols are resolved eagerly.  If any symbol is
289    /// missing, an error is returned immediately.
290    pub fn load_from_dir(dir: &Path) -> Result<Self, RefpropSysError> {
291        // Order matters: prefer 64-bit DLL on 64-bit targets.
292        let candidates: &[&str] = if cfg!(target_os = "windows") {
293            if cfg!(target_pointer_width = "64") {
294                &["REFPRP64.DLL", "REFPROP.DLL", "refprop.dll"]
295            } else {
296                &["REFPROP.DLL", "refprop.dll", "REFPRP64.DLL"]
297            }
298        } else if cfg!(target_os = "macos") {
299            &["librefprop.dylib", "libREFPROP.dylib"]
300        } else {
301            &["librefprop.so", "libREFPROP.so"]
302        };
303
304        let mut errors = Vec::new();
305
306        // 1. Try full paths inside the directory.
307        //    If a file exists but fails to load, keep trying the rest.
308        for name in candidates {
309            let full = dir.join(name);
310            if full.exists() {
311                match unsafe { Library::new(&full) } {
312                    Ok(lib) => return Self::resolve_all(lib),
313                    Err(e) => {
314                        errors.push(format!("{}: {e}", full.display()));
315                    }
316                }
317            }
318        }
319
320        // 2. Fall back to system-wide search (PATH / LD_LIBRARY_PATH)
321        for name in candidates {
322            if let Ok(lib) = unsafe { Library::new(*name) } {
323                return Self::resolve_all(lib);
324            }
325        }
326
327        let detail = if errors.is_empty() {
328            format!(
329                "No REFPROP library found in {} (tried: {candidates:?})",
330                dir.display()
331            )
332        } else {
333            format!(
334                "REFPROP library found but could not be loaded:\n  - {}",
335                errors.join("\n  - ")
336            )
337        };
338        Err(RefpropSysError::LibraryLoadFailed(detail))
339    }
340
341    /// Load the REFPROP shared library from an **exact file path**.
342    pub fn load_from_file(path: &Path) -> Result<Self, RefpropSysError> {
343        let lib = unsafe { Library::new(path) }
344            .map_err(|e| RefpropSysError::LibraryLoadFailed(format!("{}: {e}", path.display())))?;
345        Self::resolve_all(lib)
346    }
347
348    // ── REFPROP function wrappers ───────────────────────────────────
349    //
350    // Each method calls the pre-resolved function pointer directly.
351    // No symbol lookup occurs at call time – this is the key
352    // performance improvement over the previous design.
353
354    /// Set the path where REFPROP will look for fluid files, mixture
355    /// files, etc.
356    pub unsafe fn SETPATHdll(&self, hpath: *const c_char, length: c_long) {
357        unsafe { (self.fn_setpath)(hpath, length) };
358    }
359
360    /// Set up a fluid or mixture for subsequent calculations.
361    pub unsafe fn SETUPdll(
362        &self,
363        nc: *const c_int,
364        hfld: *const c_char,
365        hfmix: *const c_char,
366        hrf: *const c_char,
367        ierr: *mut c_int,
368        herr: *mut c_char,
369        hfld_length: c_long,
370        hfmix_length: c_long,
371        hrf_length: c_long,
372        herr_length: c_long,
373    ) {
374        unsafe {
375            (self.fn_setup)(
376                nc,
377                hfld,
378                hfmix,
379                hrf,
380                ierr,
381                herr,
382                hfld_length,
383                hfmix_length,
384                hrf_length,
385                herr_length,
386            );
387        }
388    }
389
390    /// Temperature-pressure flash calculation.
391    pub unsafe fn TPFLSHdll(
392        &self,
393        t: *const c_double,
394        p: *const c_double,
395        z: *const c_double,
396        d: *mut c_double,
397        dl: *mut c_double,
398        dv: *mut c_double,
399        x: *mut c_double,
400        y: *mut c_double,
401        q: *mut c_double,
402        e: *mut c_double,
403        h: *mut c_double,
404        s: *mut c_double,
405        cv: *mut c_double,
406        cp: *mut c_double,
407        w: *mut c_double,
408        ierr: *mut c_int,
409        herr: *mut c_char,
410        herr_length: c_long,
411    ) {
412        unsafe {
413            (self.fn_tpflsh)(
414                t,
415                p,
416                z,
417                d,
418                dl,
419                dv,
420                x,
421                y,
422                q,
423                e,
424                h,
425                s,
426                cv,
427                cp,
428                w,
429                ierr,
430                herr,
431                herr_length,
432            );
433        }
434    }
435
436    /// Pressure-enthalpy flash calculation.
437    pub unsafe fn PHFLSHdll(
438        &self,
439        p: *const c_double,
440        h: *const c_double,
441        z: *const c_double,
442        t: *mut c_double,
443        d: *mut c_double,
444        dl: *mut c_double,
445        dv: *mut c_double,
446        x: *mut c_double,
447        y: *mut c_double,
448        q: *mut c_double,
449        e: *mut c_double,
450        s: *mut c_double,
451        cv: *mut c_double,
452        cp: *mut c_double,
453        w: *mut c_double,
454        ierr: *mut c_int,
455        herr: *mut c_char,
456        herr_length: c_long,
457    ) {
458        unsafe {
459            (self.fn_phflsh)(
460                p,
461                h,
462                z,
463                t,
464                d,
465                dl,
466                dv,
467                x,
468                y,
469                q,
470                e,
471                s,
472                cv,
473                cp,
474                w,
475                ierr,
476                herr,
477                herr_length,
478            );
479        }
480    }
481
482    /// Pressure-entropy flash calculation.
483    pub unsafe fn PSFLSHdll(
484        &self,
485        p: *const c_double,
486        s: *const c_double,
487        z: *const c_double,
488        t: *mut c_double,
489        d: *mut c_double,
490        dl: *mut c_double,
491        dv: *mut c_double,
492        x: *mut c_double,
493        y: *mut c_double,
494        q: *mut c_double,
495        e: *mut c_double,
496        h: *mut c_double,
497        cv: *mut c_double,
498        cp: *mut c_double,
499        w: *mut c_double,
500        ierr: *mut c_int,
501        herr: *mut c_char,
502        herr_length: c_long,
503    ) {
504        unsafe {
505            (self.fn_psflsh)(
506                p,
507                s,
508                z,
509                t,
510                d,
511                dl,
512                dv,
513                x,
514                y,
515                q,
516                e,
517                h,
518                cv,
519                cp,
520                w,
521                ierr,
522                herr,
523                herr_length,
524            );
525        }
526    }
527
528    /// Saturation properties at a given temperature.
529    pub unsafe fn SATTdll(
530        &self,
531        t: *const c_double,
532        z: *const c_double,
533        kph: *const c_int,
534        p: *mut c_double,
535        dl: *mut c_double,
536        dv: *mut c_double,
537        x: *mut c_double,
538        y: *mut c_double,
539        ierr: *mut c_int,
540        herr: *mut c_char,
541        herr_length: c_long,
542    ) {
543        unsafe { (self.fn_satt)(t, z, kph, p, dl, dv, x, y, ierr, herr, herr_length) };
544    }
545
546    /// Saturation properties at a given pressure.
547    pub unsafe fn SATPdll(
548        &self,
549        p: *const c_double,
550        z: *const c_double,
551        kph: *const c_int,
552        t: *mut c_double,
553        dl: *mut c_double,
554        dv: *mut c_double,
555        x: *mut c_double,
556        y: *mut c_double,
557        ierr: *mut c_int,
558        herr: *mut c_char,
559        herr_length: c_long,
560    ) {
561        unsafe { (self.fn_satp)(p, z, kph, t, dl, dv, x, y, ierr, herr, herr_length) };
562    }
563
564    /// Critical-point properties.
565    pub unsafe fn CRITPdll(
566        &self,
567        z: *const c_double,
568        tcrit: *mut c_double,
569        pcrit: *mut c_double,
570        dcrit: *mut c_double,
571        ierr: *mut c_int,
572        herr: *mut c_char,
573        herr_length: c_long,
574    ) {
575        unsafe { (self.fn_critp)(z, tcrit, pcrit, dcrit, ierr, herr, herr_length) };
576    }
577
578    /// Transport properties (viscosity, thermal conductivity).
579    pub unsafe fn TRNPRPdll(
580        &self,
581        t: *const c_double,
582        d: *const c_double,
583        z: *const c_double,
584        eta: *mut c_double,
585        tcx: *mut c_double,
586        ierr: *mut c_int,
587        herr: *mut c_char,
588        herr_length: c_long,
589    ) {
590        unsafe { (self.fn_trnprp)(t, d, z, eta, tcx, ierr, herr, herr_length) };
591    }
592
593    /// Load a predefined mixture from a `.MIX` file.
594    ///
595    /// Returns the number of components (`nc`), the fluid file string
596    /// (`hfld`), and the molar composition array (`z`).
597    pub unsafe fn SETMIXdll(
598        &self,
599        hmxnme: *const c_char,
600        hfmix: *const c_char,
601        hrf: *const c_char,
602        nc: *mut c_int,
603        hfld: *mut c_char,
604        z: *mut c_double,
605        ierr: *mut c_int,
606        herr: *mut c_char,
607        hmxnme_length: c_long,
608        hfmix_length: c_long,
609        hrf_length: c_long,
610        hfld_length: c_long,
611        herr_length: c_long,
612    ) {
613        unsafe {
614            (self.fn_setmix)(
615                hmxnme,
616                hfmix,
617                hrf,
618                nc,
619                hfld,
620                z,
621                ierr,
622                herr,
623                hmxnme_length,
624                hfmix_length,
625                hrf_length,
626                hfld_length,
627                herr_length,
628            );
629        }
630    }
631
632    /// Temperature-density flash calculation.
633    pub unsafe fn TDFLSHdll(
634        &self,
635        t: *const c_double,
636        d: *const c_double,
637        z: *const c_double,
638        p: *mut c_double,
639        dl: *mut c_double,
640        dv: *mut c_double,
641        x: *mut c_double,
642        y: *mut c_double,
643        q: *mut c_double,
644        e: *mut c_double,
645        h: *mut c_double,
646        s: *mut c_double,
647        cv: *mut c_double,
648        cp: *mut c_double,
649        w: *mut c_double,
650        ierr: *mut c_int,
651        herr: *mut c_char,
652        herr_length: c_long,
653    ) {
654        unsafe {
655            (self.fn_tdflsh)(
656                t,
657                d,
658                z,
659                p,
660                dl,
661                dv,
662                x,
663                y,
664                q,
665                e,
666                h,
667                s,
668                cv,
669                cp,
670                w,
671                ierr,
672                herr,
673                herr_length,
674            );
675        }
676    }
677
678    /// Pressure-density flash calculation.
679    pub unsafe fn PDFLSHdll(
680        &self,
681        p: *const c_double,
682        d: *const c_double,
683        z: *const c_double,
684        t: *mut c_double,
685        dl: *mut c_double,
686        dv: *mut c_double,
687        x: *mut c_double,
688        y: *mut c_double,
689        q: *mut c_double,
690        e: *mut c_double,
691        h: *mut c_double,
692        s: *mut c_double,
693        cv: *mut c_double,
694        cp: *mut c_double,
695        w: *mut c_double,
696        ierr: *mut c_int,
697        herr: *mut c_char,
698        herr_length: c_long,
699    ) {
700        unsafe {
701            (self.fn_pdflsh)(
702                p,
703                d,
704                z,
705                t,
706                dl,
707                dv,
708                x,
709                y,
710                q,
711                e,
712                h,
713                s,
714                cv,
715                cp,
716                w,
717                ierr,
718                herr,
719                herr_length,
720            );
721        }
722    }
723
724    /// Temperature-enthalpy flash calculation.
725    pub unsafe fn THFLSHdll(
726        &self,
727        t: *const c_double,
728        h: *const c_double,
729        z: *const c_double,
730        kr: *mut c_double,
731        p: *mut c_double,
732        d: *mut c_double,
733        dl: *mut c_double,
734        dv: *mut c_double,
735        x: *mut c_double,
736        y: *mut c_double,
737        q: *mut c_double,
738        e: *mut c_double,
739        s: *mut c_double,
740        cv: *mut c_double,
741        cp: *mut c_double,
742        w: *mut c_double,
743        ierr: *mut c_int,
744        herr: *mut c_char,
745        herr_length: c_long,
746    ) {
747        unsafe {
748            (self.fn_thflsh)(
749                t,
750                h,
751                z,
752                kr,
753                p,
754                d,
755                dl,
756                dv,
757                x,
758                y,
759                q,
760                e,
761                s,
762                cv,
763                cp,
764                w,
765                ierr,
766                herr,
767                herr_length,
768            );
769        }
770    }
771
772    /// Temperature-entropy flash calculation.
773    pub unsafe fn TSFLSHdll(
774        &self,
775        t: *const c_double,
776        s: *const c_double,
777        z: *const c_double,
778        kr: *mut c_double,
779        p: *mut c_double,
780        d: *mut c_double,
781        dl: *mut c_double,
782        dv: *mut c_double,
783        x: *mut c_double,
784        y: *mut c_double,
785        q: *mut c_double,
786        e: *mut c_double,
787        h: *mut c_double,
788        cv: *mut c_double,
789        cp: *mut c_double,
790        w: *mut c_double,
791        ierr: *mut c_int,
792        herr: *mut c_char,
793        herr_length: c_long,
794    ) {
795        unsafe {
796            (self.fn_tsflsh)(
797                t,
798                s,
799                z,
800                kr,
801                p,
802                d,
803                dl,
804                dv,
805                x,
806                y,
807                q,
808                e,
809                h,
810                cv,
811                cp,
812                w,
813                ierr,
814                herr,
815                herr_length,
816            );
817        }
818    }
819
820    /// Density-enthalpy flash calculation.
821    pub unsafe fn DHFLSHdll(
822        &self,
823        d: *const c_double,
824        h: *const c_double,
825        z: *const c_double,
826        t: *mut c_double,
827        p: *mut c_double,
828        dl: *mut c_double,
829        dv: *mut c_double,
830        x: *mut c_double,
831        y: *mut c_double,
832        q: *mut c_double,
833        e: *mut c_double,
834        s: *mut c_double,
835        cv: *mut c_double,
836        cp: *mut c_double,
837        w: *mut c_double,
838        ierr: *mut c_int,
839        herr: *mut c_char,
840        herr_length: c_long,
841    ) {
842        unsafe {
843            (self.fn_dhflsh)(
844                d,
845                h,
846                z,
847                t,
848                p,
849                dl,
850                dv,
851                x,
852                y,
853                q,
854                e,
855                s,
856                cv,
857                cp,
858                w,
859                ierr,
860                herr,
861                herr_length,
862            );
863        }
864    }
865
866    /// Density-entropy flash calculation.
867    pub unsafe fn DSFLSHdll(
868        &self,
869        d: *const c_double,
870        s: *const c_double,
871        z: *const c_double,
872        t: *mut c_double,
873        p: *mut c_double,
874        dl: *mut c_double,
875        dv: *mut c_double,
876        x: *mut c_double,
877        y: *mut c_double,
878        q: *mut c_double,
879        e: *mut c_double,
880        h: *mut c_double,
881        cv: *mut c_double,
882        cp: *mut c_double,
883        w: *mut c_double,
884        ierr: *mut c_int,
885        herr: *mut c_char,
886        herr_length: c_long,
887    ) {
888        unsafe {
889            (self.fn_dsflsh)(
890                d,
891                s,
892                z,
893                t,
894                p,
895                dl,
896                dv,
897                x,
898                y,
899                q,
900                e,
901                h,
902                cv,
903                cp,
904                w,
905                ierr,
906                herr,
907                herr_length,
908            );
909        }
910    }
911
912    /// Enthalpy-entropy flash calculation.
913    pub unsafe fn HSFLSHdll(
914        &self,
915        h: *const c_double,
916        s: *const c_double,
917        z: *const c_double,
918        t: *mut c_double,
919        p: *mut c_double,
920        d: *mut c_double,
921        dl: *mut c_double,
922        dv: *mut c_double,
923        x: *mut c_double,
924        y: *mut c_double,
925        q: *mut c_double,
926        e: *mut c_double,
927        cv: *mut c_double,
928        cp: *mut c_double,
929        w: *mut c_double,
930        ierr: *mut c_int,
931        herr: *mut c_char,
932        herr_length: c_long,
933    ) {
934        unsafe {
935            (self.fn_hsflsh)(
936                h,
937                s,
938                z,
939                t,
940                p,
941                d,
942                dl,
943                dv,
944                x,
945                y,
946                q,
947                e,
948                cv,
949                cp,
950                w,
951                ierr,
952                herr,
953                herr_length,
954            );
955        }
956    }
957
958    /// Compute thermodynamic properties from temperature and density.
959    ///
960    /// No error return – REFPROP always produces a result.
961    pub unsafe fn THERMdll(
962        &self,
963        t: *const c_double,
964        d: *const c_double,
965        z: *const c_double,
966        p: *mut c_double,
967        e: *mut c_double,
968        h: *mut c_double,
969        s: *mut c_double,
970        cv: *mut c_double,
971        cp: *mut c_double,
972        w: *mut c_double,
973        hjt: *mut c_double,
974    ) {
975        unsafe { (self.fn_therm)(t, d, z, p, e, h, s, cv, cp, w, hjt) };
976    }
977
978    /// Fluid information (molar mass, triple point, etc.).
979    pub unsafe fn INFOdll(
980        &self,
981        icomp: *const c_int,
982        wmm: *mut c_double,
983        ttrp: *mut c_double,
984        tnbpt: *mut c_double,
985        tc: *mut c_double,
986        pc: *mut c_double,
987        dc: *mut c_double,
988        zc: *mut c_double,
989        acf: *mut c_double,
990        dip: *mut c_double,
991        rgas: *mut c_double,
992    ) {
993        unsafe { (self.fn_info)(icomp, wmm, ttrp, tnbpt, tc, pc, dc, zc, acf, dip, rgas) };
994    }
995}
996
997// ── String helpers ──────────────────────────────────────────────────
998
999// ── DLL discovery (no loading) ──────────────────────────────────────
1000
1001/// Locate the REFPROP shared library file inside a directory **without
1002/// loading it**.  Returns the first matching candidate path, or `None`.
1003///
1004/// Candidate names are platform-specific:
1005/// - **Windows 64-bit**: `REFPRP64.DLL`, `REFPROP.DLL`, `refprop.dll`
1006/// - **Windows 32-bit**: `REFPROP.DLL`, `refprop.dll`, `REFPRP64.DLL`
1007/// - **macOS**: `librefprop.dylib`, `libREFPROP.dylib`
1008/// - **Linux**: `librefprop.so`, `libREFPROP.so`
1009pub fn find_dll_in_dir(dir: &Path) -> Option<std::path::PathBuf> {
1010    let candidates: &[&str] = if cfg!(target_os = "windows") {
1011        if cfg!(target_pointer_width = "64") {
1012            &["REFPRP64.DLL", "REFPROP.DLL", "refprop.dll"]
1013        } else {
1014            &["REFPROP.DLL", "refprop.dll", "REFPRP64.DLL"]
1015        }
1016    } else if cfg!(target_os = "macos") {
1017        &["librefprop.dylib", "libREFPROP.dylib"]
1018    } else {
1019        &["librefprop.so", "libREFPROP.so"]
1020    };
1021
1022    for name in candidates {
1023        let full = dir.join(name);
1024        if full.exists() {
1025            return Some(full);
1026        }
1027    }
1028    None
1029}
1030
1031// ── String helpers ──────────────────────────────────────────────────
1032
1033/// Convert a Rust `&str` into a zero-padded `Vec<c_char>` of length
1034/// `max_len`, suitable for passing to a Fortran routine.
1035pub fn to_c_string(s: &str, max_len: usize) -> Vec<c_char> {
1036    let mut buffer = vec![0 as c_char; max_len];
1037    let bytes = s.as_bytes();
1038    let copy_len = bytes.len().min(max_len - 1);
1039    for i in 0..copy_len {
1040        buffer[i] = bytes[i] as c_char;
1041    }
1042    buffer
1043}
1044
1045/// Convert a null-terminated (or fully-filled) Fortran `c_char` buffer
1046/// back into a trimmed Rust `String`.
1047pub fn from_c_string(buffer: &[c_char]) -> String {
1048    let bytes: Vec<u8> = buffer
1049        .iter()
1050        .take_while(|&&c| c != 0)
1051        .map(|&c| c as u8)
1052        .collect();
1053    String::from_utf8_lossy(&bytes).trim().to_string()
1054}