Hi Charles,
I tried this little program:
$filename "test_sprintf.exe"
'uses rtl32
uses rtl64
uses corewin
uses console
SetConsoleTitle "sprintf / sprintf_s Examples"
double num
char buff[64]
num = 333 * 1.5
sprintf (buff, "%.2f", num)
printl "333*1.5 = " buff
num = 100 / 13
sprintf_s (buff,64, "%.4f" , num)
printl "100/13 = "buff
printl
printl
sprintf (buff, "Value of pi = %.2f", pi)
printl buff
int arg1 = 27, arg2 = 9, result
result = arg1 * arg2
sprintf(buff, "When %d is multiplied with %d the result is: %d", arg1, arg2, result)
printl buff
int age = 30
double height = 1.75 'float / single does not work
string name = "John"
sprintf(buff, "%s is %d years old and %.2f meters tall.", name, age, height)
'Result: "John is 30 years old and 1.75 meters tall."
printl buff
printl
printl "Enter ..."
waitkey
If I use msvcrt.inc as is, then the app only works correctly in 32-bit..
Therefore, I changed in msvcrt.inc:
...
//! sprintf
int sprintf(char *buffer, const char *format, ...)
//! sprintf_s
int sprintf_s(char *buffer, int size, const char *format, ...)
...
now the app does also work in 64-bit mode.
I know that many functions of msvcrt.dll work if applied unprototyped with Oxygen, but it seems to me that functions with variable arguments need prototyping. Is there another solution?
Hi Roland,
Interesting anomaly!
In 64bit mode, The variadic sprintf looks for the number in register rdx even though it is a double, normally passed in the simd register xmmm2.
The solution (hack) is to cast the number as a quad integer so it goes into the rdi register, where it will be picked up by sprintf.
sprintf (buff, "%.2f", (quad) num)
Hi Charles,
this is really curious. If I use (quad) num, I can use sprintf unprototyped. But (quad)pi does not work. I have to define: double my_pi = pi and (quad)my_pi to get the correct result.
I tried these statements:
int diameter = 10
sprintf (buff, "Circumferece: %d * %.3f = %.2f", diameter, pi, diameter * pi)
printl buff
the output is:
Circumference: 10 * 0.000 = 31.42
But if all else fails, I can use prototyped functions in 64-bit mode for these cases
Maybe I can use this strategy, which works in 32-bit and 64-bit mode and it does not interfere with the original Msvcrt.inc file:
$filename "test_sprintf.exe"
'uses rtl32
uses rtl64
uses corewin
uses console
#ifdef mode64bit
! sprintf lib "msvcrt.dll" (char *buffer, const char *format, ...) as sys
! sprintf_s lib "msvcrt.dll" (char *buffer, int size, const char *format, ...) as sys
#endif
SetConsoleTitle "sprintf / sprintf_s Examples"
double num
char buff[64]
num = 333 * 1.5
sprintf (buff, "%.2f", num)
printl "333*1.5 = " buff
num = 100 / 13
sprintf_s (buff,64, "%.4f" , num)
printl "100/13 = "buff
printl
printl
double my_pi = pi
sprintf (buff, "Value of pi = %.2f", pi)
printl buff
sprintf (buff, "Value of my_pi = %.2f", my_pi)
printl buff
int arg1 = 27, arg2 = 9, result
result = arg1 * arg2
sprintf(buff, "When %d is multiplied with %d the result is: %d", arg1, arg2, result)
printl buff
int age = 30
double height = 1.75
string name = "John"
sprintf(buff, "%s is %d years old and %.2f meters tall.", name, age, height)
'Result: "John is 30 years old and 1.75 meters tall."
printl buff
int diameter = 10
sprintf (buff, "Circumference: %d * %.3f = %.2f", diameter, pi, diameter * pi)
printl buff
printl
printl "Enter ..."
waitkey
To match the %f sprintf specification, your diameter variable must be a double, and it must be cast (quad) as previously.
This is certainly an edge-case for unprototyped calls :)
Hi Charles,
It took me some time to create a small demo that should show the advantages of sprintf over str(). Line 233 - 238 use sprintf. If try to use str() I will not achieve the desired result,. Therefore sprintf is a very useful formating feature and I think it is used in several programming languages, among other functions of msvcrt.dll. But perhaps you can adapt str()?
I am still hesitating to apply the quad approach. In 32-bit I do not need this, it even works without prototyping. Therefore I apply prototyping only in 64-bit. Is there a side effect that speaks against it? The best solution would actually be to carry out prototyping directly from time to time with the variadic functions in msvcrt.dll, as this is obviously very possible in Oxygenbasic. But I would of course like to use the original .inc files, so I will not change them.
Amort-Demo.o2bas:
' ************************************************
' Adapted from: Amort.bas by Kevin Diggins(MrBCX)
' ported to Oxygenbasic (simplified)
' ************************************************
' AMORT is a small loan amortization program
' ************************************************
$ filename "Amort-Demo.exe"
'uses rtl32
'uses rtl64
uses winutil
#ifdef mode64bit
! sprintf lib "msvcrt.dll" (char *buffer, const char *format, ...) as sys
#endif
typedef sys control
% DS_MODALFRAME=128
% SS_NOTIFY = 256
% SS_LEFT = 0
% LVS_EX_GRIDLINES 1
% LVS_EX_FULLROWSELECT 0x0020
% LVM_SETEXTENDEDLISTVIEWSTYLE 0x1036
% LVCF_TEXT=4
% LVM_SETCOLUMN = 4122
% LVM_SETCOLUMNWIDTH=4126
% LVSCW_AUTOSIZE -1
% LVSCW_AUTOSIZE_USEHEADER = -2
% LVM_DELETEALLITEMS = 4105
% LVIF_TEXT=1
% LVM_INSERTITEM=4103
% LVCF_FMT 1
% LVCF_WIDTH 2
% LVCF_SUBITEM 8
% LVCFMT_LEFT = 0
% LVM_INSERTCOLUMN = 4123
% LVS_REPORT = 1
% LVS_SHAREIMAGELISTS = 64
% LVS_EDITLABELS = 512
% LVM_SETITEMTEXT = 4142
% HDI_FORMAT = 4
% HDF_STRING = 16384
% HDM_SETITEM = 4612
% HDF_LEFT = 0
% HDF_RIGHT = 1
type LVCOLUMN
uint mask
int fmt
int cx
char* pszText
int cchTextMax
int iSubItem
int iImage
int iOrder
int cxMin
int cxDefault
int cxIdeal
end type
typedef LVCOLUMN LV_COLUMN
type LVITEM
uint mask
int iItem
int iSubItem
uint state
uint stateMask
char* pszText
int cchTextMax
int iImage // index of the list view item's icon
sys lParam // 32-bit value to associate with item
int iIndent
int iGroupId
uint cColumns
uint *puColumns
int *piColFmt
int iGroup
end type
typedef LVITEM LV_ITEM
type HDITEM
uint mask
int cxy
char* pszText
sys hbm
int cchTextMax
int fmt
sys lParam
int iImage
int iOrder
'#if (_WIN32_IE >= 0x0500)
uint type_ 'type
sys pvFilter 'LPVOID
'#endif /* _WIN32_IE >= 0x0500 */
'#if (_WIN32_WINNT >= 0x0600)
uint state
'#endif /* _WIN32_WINNT >= 0x0600 */
end type
typedef HDITEM HD_ITEM
macro ListView_DeleteAllItems(hwnd) (SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0))
macro ListView_InsertItem(hwnd,pitem) (SendMessage(hwnd, LVM_INSERTITEM,0, pitem))
macro ListView_InsertColumn(hwnd,iCol,pcol) (SendMessage(hwnd, LVM_INSERTCOLUMN, iCol, pcol))
macro ListView_SetItemText(hwnd,i,iSubItem_,pszText_)
LV_ITEM _ms_lvi
_ms_lvi.iSubItem = iSubItem_
_ms_lvi.pszText = pszText_
SendMessage((hwnd),LVM_SETITEMTEXT, i, &_ms_lvi)
end macro
' create a structure of INITCOMMONCONTROLSEX
INITCOMMONCONTROLSEXt iccex
iccex.dwSize=sizeof(iccex)
'Register Common Controls
iccex.dwICC= 0xffff
InitCommonControlsEx(&iccex)
GLOBAL Input1 AS CONTROL
GLOBAL Input2 AS CONTROL
GLOBAL Input3 AS CONTROL
GLOBAL Label1 AS CONTROL
GLOBAL Label2 AS CONTROL
GLOBAL Label3 AS CONTROL
GLOBAL Label4 AS CONTROL
GLOBAL Button1 AS CONTROL
GLOBAL LView1 AS CONTROL
SUB Set_ColumnText(ByVal hWnd AS sys, ByVal Column as int, ByVal Text$ as string)
LOCAL lvc AS LV_COLUMN
lvc.mask = LVCF_TEXT
lvc.pszText = Text$
SendMessage(hWnd, LVM_SETCOLUMN, Column, &lvc)
SendMessage(LView1, LVM_SETCOLUMNWIDTH, Column, LVSCW_AUTOSIZE_USEHEADER)
END SUB
SUB LV_Reset(ByVal LView AS CONTROL, ByVal Columns as int, ByVal Rows as int)
LOCAL lvItem AS LV_ITEM
LOCAL i as int
ListView_DeleteAllItems(LView)
for i = 1 to Rows
lvItem.mask = LVIF_TEXT
lvItem.pszText = " "
lvItem.iSubItem = 0
ListView_InsertItem(LView1, &lvItem)
next
END SUB
SUB LV_Justify(ByVal LV AS sys, ByVal Column as int , ByVal JustifyType as int)
LOCAL hHeader AS LONG
LOCAL hdi AS HD_ITEM
'*******************************************
% HDF_LEFT = 0 'JustifyType
% HDF_RIGHT = 1 'JustifyType
% HDF_CENTER = 2 'JustifyType
'*******************************************
% LVM_FIRST = 4096
% LVM_GETHEADER = LVM_FIRST + 31
'*******************************************
hHeader = SendMessage(LV, LVM_GETHEADER, 0, 0)
hdi.mask = HDI_FORMAT
hdi.pszText = " "
hdi.fmt = HDF_STRING OR JustifyType
SendMessage(hHeader, HDM_SETITEM, Column, &hdi)
END SUB
FUNCTION INTEREST_PAYMENT(ByVal i AS DOUBLE, ByVal Balance AS DOUBLE) AS DOUBLE
FUNCTION =(i/12/100) * Balance
END FUNCTION
FUNCTION Payment(ByVal i AS DOUBLE, ByVal np AS DOUBLE, ByVal pv AS DOUBLE, ByVal fv AS DOUBLE) AS DOUBLE
DIM q1 AS DOUBLE
DIM ir AS DOUBLE
ir = i / 12 / 100
q1 = POW(1 + ir, np)
FUNCTION =((ir *(fv + q1 * pv))/(-1 + q1))
END FUNCTION
function Set_Text(sys hWnd, string Text) as int
return SetWindowText(hWnd,Text)
end function
function Get_Text(sys hWnd) as string
int tmpint
tmpint = 1 + GetWindowTextLength(hWnd)
static string strtmp = nuls * 1024
GetWindowText(hWnd, strtmp, tmpint)
return strtmp
end function
SUB FillListView()
'==========================
DIM Amount AS DOUBLE
DIM Interest AS DOUBLE
DIM Years AS DOUBLE
DIM Pmt AS DOUBLE
DIM Int_Payment AS DOUBLE
DIM Prin_Payment AS DOUBLE
DIM lvItem AS LV_ITEM
DIM SumPrinc AS DOUBLE
DIM SumInt AS DOUBLE
DIM z AS int
char buff[32]
'==========================
Amount = VAL(GET_TEXT(Input1))
Interest = VAL(GET_TEXT(Input2))
Years = VAL(GET_TEXT(Input3))
Pmt = Payment(Interest, Years * 12, Amount, 0)
LV_Reset(LView1, 4, Years * 12)
Set_ColumnText(LView1, 0, "Payment")
Set_ColumnText(LView1, 1, "Interest")
Set_ColumnText(LView1, 2, "Principal")
Set_ColumnText(LView1, 3, "Balance")
FOR z = 1 TO Years * 12
Int_Payment = INTEREST_PAYMENT(Interest, Amount)
Prin_Payment = Pmt - INTEREST_PAYMENT(Interest, Amount)
SumPrinc = SumPrinc + Prin_Payment
SumInt = SumInt + Int_Payment
string z1=str(z)
ListView_SetItemText(LView1, z - 1, 0, z1)
sprintf(buff, "%0.2lf", Int_Payment)
ListView_SetItemText(LView1, z - 1, 1, buff)
sprintf(buff, "%.2f", Prin_Payment)
ListView_SetItemText(LView1, z - 1, 2, buff)
Amount = Amount - Prin_Payment
sprintf(buff, "%.2f", Amount)
ListView_SetItemText(LView1, z - 1, 3, buff)
NEXT
ListView_SetItemText(LView1, z, 3, " ") ' add a line at the bottom
FOR z = 0 TO 3
SendMessage(LView1, LVM_SETCOLUMNWIDTH, z, LVSCW_AUTOSIZE_USEHEADER)
NEXT
LV_Justify(LView1, 0, HDF_LEFT)
LV_Justify(LView1, 1, HDF_RIGHT)
LV_Justify(LView1, 2, HDF_RIGHT)
LV_Justify(LView1, 3, HDF_RIGHT)
SET_TEXT(Label3, str(SumPrinc,2))
SET_TEXT(Label4, str(SumInt,2))
END SUB
******************************************************************************
sys hInstance=inst
MainWindow 400,460, DS_MODALFRAME OR WS_POPUP OR WS_CAPTION OR WS_SYSMENU
function WndProc(sys hwnd, uMsg, wParam, lParam) as sys callback
select uMsg
case WM_CREATE
SetWindowText(hwnd, "Amortization Schedule in Oxygenbasic")
Input1=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","8000",WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 272, 6, 92, 28, hwnd,0,hinstance,0)
Input2=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","9.25",WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 272, 36, 92, 28, hwnd,0,hinstance,0)
Input3=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","3",WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 272, 66, 92, 28, hwnd,0,hinstance,0)
Label1=CreateWindowEx(0,"static","Principal",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 104, 80, 28, hwnd,0,hinstance,0)
Label2=CreateWindowEx(0,"static","Interest",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 130, 80, 28, hwnd,0,hinstance,0)
Label3=CreateWindowEx(0,"static","",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 100, 104, 80, 28, hwnd,0,hinstance,0)
Label4=CreateWindowEx(0,"static","",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 100,130, 80, 28, hwnd,0,hinstance,0)
Button1=CreateWindowEx(WS_EX_STATICEDGE,"button","Calc",WS_CHILD | WS_VISIBLE | BS_MULTILINE | BS_PUSHBUTTON | WS_TABSTOP, 280,104, 80, 28, hwnd, 101, hinstance, 0)
LV_COLUMN lvCol =
{ LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM, LVCFMT_LEFT, 65, "", 0, 0 }
LView1=CreateWindowEx(WS_EX_CLIENTEDGE,"SysListView32",0,WS_CHILD | WS_TABSTOP |
WS_VISIBLE | LVS_REPORT |
LVS_SHAREIMAGELISTS | LVS_EDITLABELS | WS_BORDER, 10, 160, 300, 246, hwnd, 0, hinstance, 0)
int Style=LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT
SendMessage(LView1, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, Style)
lvCol.iSubItem=0
while lvCol.iSubItem<15
ListView_InsertColumn(LView1, 0, lvCol)
lvCol.iSubItem+=1
wend
CreateWindowEx(0,"static","Loan Amount", WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 14, 126, 22, hwnd,0,hinstance,0)
CreateWindowEx(0,"static","Interest Rate",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 44, 126, 22, hwnd,0,hinstance,0)
CreateWindowEx(0,"static","No. of Years", WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 72, 192, 22, hwnd,0,hinstance,0)
Set_ColumnText(LView1, 0, "Payment")
Set_ColumnText(LView1, 1, "Interest")
Set_ColumnText(LView1, 2, "Principal")
Set_ColumnText(LView1, 3, "Balance")
SetFocus(Input1)
RedrawWindow(hwnd)
ShowWindow(hwnd)
case WM_COMMAND
if LOWORD(wParam) = 101 then 'Calc
FillListView()
end if
case WM_CLOSE
DestroyWindow(hwnd)
case WM_DESTROY
PostQuitMessage(0)
case else
return DefWindowProc(hwnd, uMsg, wParam, lParam)
end select
return 0
end function
Is it not possible any more to edit a reply? There are two lines in the code which I forgot to delete:
it should read:
...
function WndProc(sys hwnd, uMsg, wParam, lParam) as sys callback
select uMsg
...
Many thanks Roland, I will include it in demos\WinGui
It should be possible to edit using 'Quick Edit' or 'More'--> Modify' if these are visible to you. I have made the change for you.
PS:
a format function is available in parseutil.inc
uses parseutil
...
print format(num,"###.00") cr