mingw_stdio example

General FreeBASIC programming questions.
Post Reply
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

mingw_stdio example

Post by srvaldez »

Windows CRT sscanf and sprintf don't support 80-bit reals but mingw does, here's an example

Code: Select all

#ifdef __FB_WIN32__
	#ifdef __FB_64BIT__
		'64-bit
		Type ext_real
			As Ulongint hi, lo
		end type

		Sub sprint_ Naked Cdecl(Byval buf As Zstring Ptr, Byval frmt As Zstring Ptr, Byref x As ext_real)
		   Asm
			  Sub      rsp, 56
			  fld      tbyte Ptr [r8]
			  fstp      tbyte Ptr [rsp+32]
			  lea      r8,[rsp+32]
			  Call      __mingw_sprintf
			  nop
			  Add   rsp, 56
			  ret
		   End Asm
		End Sub

		Sub sscan_ Naked Cdecl(Byref x As ext_real, Byval buf As Zstring Ptr, Byval frmt As Zstring Ptr)
		   Asm
			  mov      r9, rcx
			  Xor      eax, eax
			  mov      rcx, rdx
			  mov      rdx, r8
			  mov      r8, r9
			  jmp      __mingw_sscanf
		   End Asm
		End Sub
	#else
		'32-bit
		Type ext_real
			As Ulong ldfp(0 To 2)
		end type
		
		Sub sprint_ Naked Cdecl(Byval buf As Zstring Ptr, Byval frmt As Zstring Ptr, Byref x As ext_real)
		   Asm
			  Sub     esp, 24
			  mov     eax, dword Ptr [esp+36]
			  Push    dword Ptr [eax+8]
			  Push    dword Ptr [eax+4]
			  Push    dword Ptr [eax]
			  Push    dword Ptr [esp+44]
			  Push    dword Ptr [esp+44]
			  Call    ___mingw_sprintf
			  Add     esp, 44
			  ret
		   End Asm
		End Sub

		Sub sscan_ Naked Cdecl(Byref x As ext_real, Byval buf As Zstring Ptr, Byval frmt As Zstring Ptr)
		   Asm
			  mov     eax, dword Ptr [esp+8]
			  mov     edx, dword Ptr [esp+12]
			  mov     ecx, dword Ptr [esp+4]
			  mov     dword Ptr [esp+8], edx
			  mov     dword Ptr [esp+12], ecx
			  mov     dword Ptr [esp+4], eax
			  'jmp     __isoc99_sscanf
			  jmp     ___mingw_sscanf
		   End Asm
		End Sub	
	#endif
#endif


Function ldbl_val(Byref s As String) As ext_real
	Dim As ext_real ret
	Dim As String frmt = "%Lf"
	sscan_(ret, s, frmt)
	Function = ret
End Function

Function ldbl_str(Byref y As ext_real, Byval prec As Long = 19, Byref f As String="g") As String
	Dim As Zstring Ptr buf=Callocate(256)
	Dim As Zstring Ptr frmt=Callocate(16)
	*frmt= "%."+Trim(Str(prec))+"L"
	If Lcase(f)="e" Or Lcase(f)="f" Or Lcase(f)="a" Then
		*frmt=*frmt+f
	Else
		*frmt=*frmt+"g"
	End If
	Dim As String ret
	sprint_ (buf, frmt, y)
	ret=*buf
	If Left(ret,1)<>"-" Then
		ret=" "+ret
	End If
	Deallocate(buf)
	Deallocate(frmt)
	Function = ret
End Function

dim as ext_real x
dim as string e, s="2.718281828459045235360287"

x=ldbl_val(s)
print "extended float to string using defaults          ";ldbl_str(x)
print "extended float to string, 10 digits              ";ldbl_str(x, 10)
print "extended float to string, 12 decimals f notation ";ldbl_str(x, 12, "f")
print "extended float to string, 14 decimals e notation ";ldbl_str(x, 14, "e")
print "extended float to string, 16 digits   g notation ";ldbl_str(x, 16, "g")
print "extended float to string, 15 heximals a notation ";ldbl_str(x, 15, "A")
print "e=""0XA.DF85458A2BB4A9BP-2""" : e="0XA.DF85458A2BB4A9BP-2"
print "x=ldbl_val(e)" : x=ldbl_val(e)
print "print ldbl_str(x) -->";ldbl_str(x)

Code: Select all

extended float to string using defaults           2.718281828459045235
extended float to string, 10 digits               2.718281828
extended float to string, 12 decimals f notation  2.718281828459
extended float to string, 14 decimals e notation  2.71828182845905e+00
extended float to string, 16 digits   g notation  2.718281828459045
extended float to string, 15 heximals a notation  0XA.DF85458A2BB4A9BP-2
e="0XA.DF85458A2BB4A9BP-2"
x=ldbl_val(e)
print ldbl_str(x) --> 2.718281828459045235
caseih
Posts: 2157
Joined: Feb 26, 2007 5:32

Re: mingw_stdio example

Post by caseih »

Why do you need assembly wrappers? Can't you just call the mingw functions directly?
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: mingw_stdio example

Post by srvaldez »

hello caseih
I don't see how you could call these functions from FB other than asm or a C wrapper library, do you have any ideas?
caseih
Posts: 2157
Joined: Feb 26, 2007 5:32

Re: mingw_stdio example

Post by caseih »

Why not? They are just C functions after all. FB can call them directly, just like any other function in the C runtime.

Code: Select all

declare function mingw_sprintf CDECL alias "__mingw_sprintf" (byval as zstring ptr, byval as zstring ptr, ...) as long

dim a as zstring*256

mingw_sprintf (a, "%d", 5)
print a
I borrowed the declaration from the stdio.bi file, and modified it to refer to the non-standard __mingw_sprintf function. You can do something similar for all the mingw functions you want to use that aren't part of the standard C runtime bi files. Look in stdio.bi and see how the normal C runtime functions are declared, and then adapt them to the hidden functions you want to access. Hope that makes sense. Or am I completely misunderstanding what you are trying to do?
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: mingw_stdio example

Post by srvaldez »

yes, you are right, here's the version without the asm
note that in the function sscanf x must (obviously) be passed byref whereas in sprintf it must be passed byval.

Code: Select all

#include "crt/longdouble.bi"
extern "c"
	declare function __mingw_sscanf(byval as zstring ptr, byval as zstring ptr, Byref x As clongdouble) as long
	declare function __mingw_sprintf (byval as zstring ptr, byval as zstring ptr, Byval x As clongdouble) as long
end extern

Function ldbl_val(Byref s As String) As clongdouble
	Dim As clongdouble ret
	Dim As String frmt = "%Lf"
	__mingw_sscanf(s, frmt, ret)
	Function = ret
End Function

Function ldbl_str(Byref y As clongdouble, Byval prec As Long = 19, Byref f As String="g") As String
	Dim As Zstring Ptr buf=Callocate(256)
	Dim As Zstring Ptr frmt=Callocate(16)
	*frmt= "%."+Trim(Str(prec))+"L"
	If Lcase(f)="e" Or Lcase(f)="f" Or Lcase(f)="a" Then
		*frmt=*frmt+f
	Else
		*frmt=*frmt+"g"
	End If
	Dim As String ret
	__mingw_sprintf(buf, frmt, y)
	ret=*buf
	If Left(ret,1)<>"-" Then
		ret=" "+ret
	End If
	Deallocate(buf)
	Deallocate(frmt)
	Function = ret
End Function

dim as clongdouble x
dim as string e, s="2.718281828459045235360287"

x=ldbl_val(s)
print "extended float to string using defaults          ";ldbl_str(x)
print "extended float to string, 10 digits              ";ldbl_str(x, 10)
print "extended float to string, 12 decimals f notation ";ldbl_str(x, 12, "f")
print "extended float to string, 14 decimals e notation ";ldbl_str(x, 14, "e")
print "extended float to string, 16 digits   g notation ";ldbl_str(x, 16, "g")
print "extended float to string, 15 heximals a notation ";ldbl_str(x, 15, "A")
print "e=""0XA.DF85458A2BB4A9BP-2""" : e="0XA.DF85458A2BB4A9BP-2"
print "x=ldbl_val(e)" : x=ldbl_val(e)
print "print ldbl_str(x) -->";ldbl_str(x)
Last edited by srvaldez on Jun 25, 2019 16:51, edited 1 time in total.
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: mingw_stdio example

Post by srvaldez »

having discovered how the clongdouble variable is to be passed to sscanf and sprintf, there's much simplification, a naked sub with a simple jump, I like the simplicity of it.

Code: Select all

#include "crt/longdouble.bi"

Sub sprint_ Naked Cdecl(Byval buf As Zstring Ptr, Byval frmt As Zstring Ptr, Byval x As clongdouble)
	Asm
	#ifdef __FB_64BIT__
		jmp	__mingw_sprintf
	#else
		jmp	___mingw_sprintf
	#endif
	End Asm
End Sub

Sub sscan_ Naked Cdecl( Byval buf As Zstring Ptr, Byval frmt As Zstring Ptr, Byref x As clongdouble)
	Asm
	#ifdef __FB_64BIT__
		jmp	__mingw_sscanf
	#else
		jmp	___mingw_sscanf
	#endif
	End Asm
End Sub

Function ldbl_val(Byref s As String) As clongdouble
	Dim As clongdouble ret
	Dim As String frmt = "%Lf"
	sscan_(s, frmt, ret)
	Function = ret
End Function

Function ldbl_str(Byref y As clongdouble, Byval prec As Long = 19, Byref f As String="g") As String
	Dim As Zstring Ptr buf=Callocate(256)
	Dim As Zstring Ptr frmt=Callocate(16)
	*frmt= "%."+Trim(Str(prec))+"L"
	If Lcase(f)="e" Or Lcase(f)="f" Or Lcase(f)="a" Then
		*frmt=*frmt+f
	Else
		*frmt=*frmt+"g"
	End If
	Dim As String ret
	sprint_ (buf, frmt, y)
	ret=*buf
	If Left(ret,1)<>"-" Then
		ret=" "+ret
	End If
	Deallocate(buf)
	Deallocate(frmt)
	Function = ret
End Function

dim as clongdouble x
dim as string e, s="2.718281828459045235360287"

x=ldbl_val(s)
print "extended float to string using defaults          ";ldbl_str(x)
print "extended float to string, 10 digits              ";ldbl_str(x, 10)
print "extended float to string, 12 decimals f notation ";ldbl_str(x, 12, "f")
print "extended float to string, 14 decimals e notation ";ldbl_str(x, 14, "e")
print "extended float to string, 16 digits   g notation ";ldbl_str(x, 16, "g")
print "extended float to string, 15 heximals a notation ";ldbl_str(x, 15, "A")
print "e=""0XA.DF85458A2BB4A9BP-2""" : e="0XA.DF85458A2BB4A9BP-2"
print "x=ldbl_val(e)" : x=ldbl_val(e)
print "print ldbl_str(x) -->";ldbl_str(x)
SARG
Posts: 1766
Joined: May 27, 2005 7:15
Location: FRANCE

Re: mingw_stdio example

Post by SARG »

Hi srvaldez,

No need naked subs : just use declare with alias, simplier....
32bit tested but maybe declares should be added for 64 bit.

Code: Select all

#include "crt/longdouble.bi"

declare sub sprint_ cdecl Alias "__mingw_sprintf" (byval s As zstring ptr,byval frmt as zstring ptr,byval c as clongdouble)
declare sub sscan_ cdecl Alias  "__mingw_sscanf"  (byval s As zstring ptr,byval frmt as zstring ptr,byref c as clongdouble)

Function ldbl_val(Byref s As String) As clongdouble
   Dim As clongdouble ret
   Dim As String frmt = "%Lf"
   sscan_(s, frmt, ret)
   Function = ret
End Function

Function ldbl_str(Byref y As clongdouble, Byval prec As Long = 19, Byref f As String="g") As String
   Dim As Zstring Ptr buf=Callocate(256)
   Dim As Zstring Ptr frmt=Callocate(16)
   *frmt= "%."+Trim(Str(prec))+"L"
   If Lcase(f)="e" Or Lcase(f)="f" Or Lcase(f)="a" Then
      *frmt=*frmt+f
   Else
      *frmt=*frmt+"g"
   End If
   Dim As String ret
   sprint_ (buf, frmt, y)
   ret=*buf
   If Left(ret,1)<>"-" Then
      ret=" "+ret
   End If
   Deallocate(buf)
   Deallocate(frmt)
   Function = ret
End Function

dim as clongdouble x
dim as string e, s="2.718281828459045235360287"

x=ldbl_val(s)
print "extended float to string using defaults          ";ldbl_str(x)
print "extended float to string, 10 digits              ";ldbl_str(x, 10)
print "extended float to string, 12 decimals f notation ";ldbl_str(x, 12, "f")
print "extended float to string, 14 decimals e notation ";ldbl_str(x, 14, "e")
print "extended float to string, 16 digits   g notation ";ldbl_str(x, 16, "g")
print "extended float to string, 15 heximals a notation ";ldbl_str(x, 15, "A")
print "e=""0XA.DF85458A2BB4A9BP-2""" : e="0XA.DF85458A2BB4A9BP-2"
print "x=ldbl_val(e)" : x=ldbl_val(e)
print "print ldbl_str(x) -->";ldbl_str(x)
sleep
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: mingw_stdio example

Post by srvaldez »

hello SARG
your declarations are very similar to my next to last post, tested ok both 32 and 64 bit
sadly, the same approach won't work for functions like sinl, it works ok in 64-bit but not in 32-bit, apparently, there are some differences between linux cdecl and windows cdecl, or perhaps FB is generating different code than C, not sure.
caseih
Posts: 2157
Joined: Feb 26, 2007 5:32

Re: mingw_stdio example

Post by caseih »

When you say it's not working, what are you seeing?

Feel free to check the intermediate output (C or assembler) to see what the compiler is doing. All CDECL means is that the parameters are passed left to right on the stack, instead of right to left, which is the default for FB and the Windows API (stdcall). So there's no difference between how FB handles CDECL and how C does. The only problems could be how the parameter is passed, or whether FB is coercing it without you knowing.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: mingw_stdio example

Post by dodicat »

True caseih, but I can also use cdecl to substitute extern "c", which I don't see mentioned in the help file.
If I call a function from a c dll (not necessarily msvcrt.dll) I find it a bit hit and miss.
I start with
#inclib "something"
I try extern "c", and cdecl.
or
skip #inclib "something"
Then I try lib "SOMETHING" or lib"something" alias "SOMEFUNCTION" or alias "somefunction"
Or if it fails I try dylibload (as a last resort)

Code: Select all


'extern "c"
declare function sin_ cdecl alias "sin" (byval as double) as double
'end extern
print sin_(1)

'remove cdecl, try, then use extern "c" and try.
sleep

  
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: mingw_stdio example

Post by srvaldez »

my question is, why does the following example work in 64-bit but not in 32-bit?

Code: Select all

#Include "crt/longdouble.bi"

Extern "c"
	Declare Sub __mingw_sprintf (Byval s As Zstring Ptr,Byval frmt As Zstring Ptr,Byval c As clongdouble)
	Declare Sub __mingw_sscanf  (Byval s As Zstring Ptr,Byval frmt As Zstring Ptr,Byref c As clongdouble)
	Declare Function sinl (Byval x As clongdouble) As clongdouble
End Extern

Dim As clongdouble x, y
Dim As String f="%Lf", s="1"
Dim As Zstring Ptr buf=Callocate(256), frmt=Callocate(16)
*frmt="%.19Lg"
__mingw_sscanf(s, f, x)
y=sinl(x)
__mingw_sprintf(buf, frmt, y)
Print "sinl(1) = ";*buf
Deallocate(buf)
Deallocate(frmt)
Sleep
whereas the following will work with FB-32

Code: Select all

#Include "crt/longdouble.bi"

Extern "c"
	Declare Sub __mingw_sprintf (Byval s As Zstring Ptr,Byval frmt As Zstring Ptr,Byval c As clongdouble)
	Declare Sub __mingw_sscanf  (Byval s As Zstring Ptr,Byval frmt As Zstring Ptr,Byref c As clongdouble)
End Extern

Function sinl_ Naked Cdecl(Byval x As clongdouble) As clongdouble
	Asm
		lea     eax, [esp+8] 'address of x data, esp+4 = address of return value
		Push    DWORD Ptr [eax+8]
		Push    DWORD Ptr [eax+4]
		Push    DWORD Ptr [eax]
		Call    _sinl
		mov     eax, DWORD Ptr [esp+16] 'esp+4 + 12 = address of ret val
		fstp    TBYTE Ptr [eax]
		Add     esp, 12
		ret
	End Asm
End Function

Dim As clongdouble x, y
Dim As String f="%Lf", s="1"
Dim As Zstring Ptr buf=Callocate(256), frmt=Callocate(16)
*frmt="%.19Lg"
__mingw_sscanf(s, f, x)
y=sinl_(x)
__mingw_sprintf(buf, frmt, y)
Print "sinl(1) = ";*buf
Deallocate(buf)
Deallocate(frmt)
Sleep
SARG
Posts: 1766
Joined: May 27, 2005 7:15
Location: FRANCE

Re: mingw_stdio example

Post by SARG »

srvaldez wrote:your declarations are very similar to my next to last post, tested ok both 32 and 64 bit
Sorry I missed your post and caseih's one.
srvaldez wrote:sadly, the same approach won't work for functions like sinl, it works ok in 64-bit but not in 32-bit, apparently, there are some differences between linux cdecl and windows cdecl, or perhaps FB is generating different code than C, not sure.
To be forgiven I have searched with an asm debugger why it's not working on 32bit.

Code: Select all

##xdouble=sinl(xdouble)
	push dword ptr [ebp-8]    <------ pushing the long double argument
	push dword ptr [ebp-12]
	push dword ptr [ebp-16]
	mov dword ptr [ebp-108], 0  <----- resetting the space for the returned value
	mov dword ptr [ebp-104], 0
	mov dword ptr [ebp-100], 0
	lea eax, [ebp-108]
	push eax     <------ extra hidden parameter, address for the returned long double (udt)
	call _sinl
The long double value is put on the stack but also an hidden parameter (the famous for UDT).
So when sinl function is executed it takes the hidden parameter and only a part of the long double giving a wrong result.
However worst point, nothing is done to store the result , it stays in ST(0)..... letting the returned value empty
But in your naked function you do the job.

Defining sinl as a sub allows to take entirely the right value however impossible to get the result, unless using extra asm code.

Hope it's clear.
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: mingw_stdio example

Post by srvaldez »

@SARG
thanks a million for your fine detective work :-)
a take it that if the return type were one of FB's built-in types it would work.
SARG
Posts: 1766
Joined: May 27, 2005 7:15
Location: FRANCE

Re: mingw_stdio example

Post by SARG »

srvaldez wrote:@SARG
thanks a million for your fine detective work :-)
One thanks was enough :-)

srvaldez wrote:a take it that if the return type were one of FB's built-in types it would work.
Sure I tested this and it works fine.

Code: Select all

    declare function sin2 cdecl alias "sin" (byval c as double) as double
    dim as double sdouble=3.14/2
    sdouble=sin2(sdouble)
    print sdouble

Code: Select all

##sdouble=sin2(sdouble)
	push dword ptr [ebp-20]
	push dword ptr [ebp-24]
	call _sin
	add esp, 8
	fstp qword ptr [ebp-24]
Edit :
a funny way

Code: Select all

declare sub sinl cdecl alias "sinl" (byval c as clongdouble)
...
then after the call retrieving the value with just one asm line. It works also fine.

Code: Select all

sinl(xdouble)
asm
    fstp    TBYTE Ptr [xdouble]
end asm
Post Reply