cfilebindingfreepascal

Which type is the portable equivalent to the C type FILE in FreePascal


I have to write a wrapper for the C-API of the library GDAL to generate FreePascal bindings under Debian 12 and GDAL version 3.2.2.

One of the functions is:

      void CPL_DLL OGR_G_DumpReadable( OGRGeometryH, FILE *, const char * );

that uses a FILE pointer, which:

Question: What is the portable equivalent of this type for FreePascal?

Advise: I know we have the the built in type File in FreePascal, with the type related functions and procedures:

Procedure Assign(out f:File;const Name: RawByteString);
Procedure Rename(var f:File;const s : RawByteString);
Procedure Rewrite(var f:File);
Procedure Reset(var f:File);
Procedure Close(var f:File);
Procedure BlockWrite(var f:File;const Buf;Count:Int64;var Result:Int64);
Function  FilePos(var f:File):Int64;
Function  FileSize(var f:File):Int64;
Procedure Seek(var f:File;Pos:Int64);
Function  EOF(var f:File):Boolean;
Procedure Erase(var f:File);
Procedure Truncate (var f:File);         

located in the unit system anf looking similar to fopen(...), fclose(...), etc.;

Question: Are these both types equivalent?


Solution

  • There is no equivalence or easy conversion between a Free Pascal file object and a C FILE* object (or a file object of any language, for that matter, with the maybe slight exception for converting a C FILE* to a C++ stream using non-standard functions). You will have to make a decision for your wrapper.

    You can choose to make that parameter a string with the filename and then call fopen yourself. This would be the easy way out but also the ugliest, because not all FILE* streams are actual files (there are anonymous pipes, for example).

    Another way to treat this situation is to get the raw OS handle (file descriptor in Linux) of the particular file (it's inside the TTextRec record which you can get from a TextFile), duplicate it using dup and open a C FILE* (using fdopen) to the copy. Since the documentation of OGR_G_DumpReadable says it wants a text file, it would be appropriate to require that from Pascal callers.

    {H+}
    
    uses
        ctypes, SysUtils;
    
    type
        PFile = Pointer;
    
    function fdopen(fd: cint; const mode: PChar): PFile; cdecl; external 'c';
    function dup(fd: cint): cint; cdecl; external 'c';
    
    function GetPFile(var F: TextFile): PFile;
    var
        fd: THandle;
        newfd: THandle;
        pf: PFile;
    begin
        Flush(F);
        fd := TTextRec(F).Handle;
        newfd := dup(fd);
        if newfd = -1 then
        begin
            // handle error
        end;
        pf := fdopen(newfd, 'w');
        if pf = nil then
        begin
            // handle error
        end;
        GetPFile := pf;
    end;
    

    Note, however, this approach has some caveats, thus you need to be very careful about what the library is doing with this file. If the library closes the file itself, you may want to replicate that behaviour in Pascal and Close the Pascal file after the library function returns (but that's not required, you can leave it open).

    If the library just writes and nothing more (which I believe is the case with this particular function), you need to fflush the C file, call lseek on the C file's descriptor to get its new location, lseek the old file descriptor to that location and then fclose the C file (otherwise you will leak resources).

    The worst case is if the library keeps the FILE* beyond the function's end (that is, it keeps an internal state). The Pascal file won't be aware of writes made by the C file or vice-versa. If that is the case, your best bet is to expose the C FILE to the Pascal programmer and let him/her deal with fopen/fclose directly, as this will allow them to have a stream that's consistent with what the library has.

    Note for Windows: You said you wanted a portable equivalent, but it seems that on Windows, the handle inside the TTextRec record is actually a Win32 handle (at least according to Google, I haven't actually tested this). Thus, for Windows, you would want to call DuplicateHandle instead of dup, then call _open_osfhandle to get a file descriptor, and finally call _fdopen. You have to resort to conditional compilation, because the file implementation in Pascal is inherently non-portable.

    Make sure to link the wrapper to the same C library that GDAL is linked to! On Linux/Unix/macOS there is usually a single system library, but on Windows, each piece of software links to whatever C library it wishes (msvcrt.dll, one of the dozens of MSVC++ redistributables or the UCRT), and each has a different ABI.