Author Topic: Moving TCLib Into Mainstream  (Read 38845 times)

0 Members and 1 Guest are viewing this topic.

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #60 on: April 18, 2016, 05:19:44 PM »
For clarification, when I originally translated the grid from PowerBASIC to C++ I tested extensively both ways, that is, 32 bit and 64 bit.  I just hadn't gotten around to testing 32 bit with TCLib yet.  There is a define that needs to be set though, which I believe is in Server.cpp.  I believe the name is x64_GRID or something like that.  There was only one very small place that was used and it involved registering and unregistering the grid's type library in the typelib key of the Registry.  I believe there was a different equate used in on of the parameters of the call.  Other than that its one code base for 32/64 bit.

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #61 on: April 18, 2016, 07:35:47 PM »
Thought I'd check out a 32 bit compile of the grid and it doesn't work.  Here are the errors...

Code: [Select]
WinCode.obj : error LNK2019: unresolved external symbol __ftol2 referenced in function "long __cdecl fnGridProc_OnCreate(structWindowsEventArguments *)" (?fnGridProc_OnCreate@@YAJPAUWindowsEventArguments@@@Z)
WinCode.obj : error LNK2019: unresolved external symbol __ftol2_sse referenced in function "long __cdecl fnGridProc_OnCreate(struct WindowsEventArguments *)" (?fnGridProc_OnCreate@@YAJPAUWindowsEventArguments@@@Z)
FHGrid.dll : fatal error LNK1120: 2 unresolved externals

I've discussed that issue fairly extensively.  In summary, 32 bit C++ compilers use a bunch of asm code in the crt source subdirectory of the compiler's include directory.  Its for casting and other types of floating point transformations.  For TCLib its in that WinCode_crt_math.cpp file, which gets compiled for 32 bit builds (take a look at it and get ready to become appalled).  Apparently it was triggered by my High DPI Aware code I added to the grid sometime after I created it when I learned how to do that.  That code uses floating point math.  Not sure what I'm going to do about this.  While 64 bit is my priority, I had hoped this would work in 32 bit.  I might have to go to Martins Mozeiko for help on this.

What I had actually started working on today was my template code for multi-dimensional arrays, and particularly  that part you had turned me onto James about zeroing out memory using new and returning a NULL pointer on failed new calls.  Some issues (solvable) with regard to TCLib on that.
« Last Edit: April 18, 2016, 07:37:55 PM by Frederick J. Harris »

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #62 on: April 18, 2016, 09:31:45 PM »
I've diagnosed the cause of the linker errors and it is the High DPI Aware code.  Remming about 6 or 7 lines out allows the grid dll to compile.  I was amazed to see how much smaller it was than the 64 bit version, which came in 28 k and compacted to 18 k.  In 32 bit without the DPI code it compiled to 22016 bytes! and compacted to 15 k!

In the course of diagnosing this I compiled my Demo25.cpp (it scrolls the Demo25.cpp file in a GUI Window), and in 32 bit that compiled to 5 k!  Here is that...

Code: [Select]
// cl Demo25.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
// 7,680 Bytes x64 Wide Character
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#include "string.h"
#include "stdio.h"
#include "tchar.h"
#define  BUFFER_SIZE  256
#define  dim(x) (sizeof(x) / sizeof(x[0]))

struct WndEventArgs
{
 HWND                         hWnd;
 WPARAM                       wParam;
 LPARAM                       lParam;
 HINSTANCE                    hIns;
};

long fnWndProc_OnCreate       (WndEventArgs& Wea);
long fnWndProc_OnSize         (WndEventArgs& Wea);
long fnWndProc_OnVScroll      (WndEventArgs& Wea);
long fnWndProc_OnHScroll      (WndEventArgs& Wea);
long fnWndProc_OnMouseWheel   (WndEventArgs& Wea);
long fnWndProc_OnPaint        (WndEventArgs& Wea);
long fnWndProc_OnDestroy      (WndEventArgs& Wea);

struct EVENTHANDLER
{
 unsigned int                 iMsg;
 long                         (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
 {WM_CREATE,                  fnWndProc_OnCreate},
 {WM_SIZE,                    fnWndProc_OnSize},
 {WM_VSCROLL,                 fnWndProc_OnVScroll},
 {WM_HSCROLL,                 fnWndProc_OnHScroll},
 {WM_MOUSEWHEEL,              fnWndProc_OnMouseWheel},
 {WM_PAINT,                   fnWndProc_OnPaint},
 {WM_DESTROY,                 fnWndProc_OnDestroy}
};

struct ScrollData
{
 TCHAR**                      pPtrs;
 int                          iNumLines;
 int                          cxChar;
 int                          cxCaps;
 int                          cyChar;
 int                          cxClient;
 int                          cyClient;
 int                          iMaxWidth;
};


long fnWndProc_OnCreate(WndEventArgs& Wea)
{
 TCHAR szBuffer[BUFFER_SIZE];
 ScrollData* pScrDta=NULL;
 int iLen=0,iLof=0,i;
 HANDLE hHeap=NULL;
 TCHAR* cRet=NULL;
 HFONT hFont=NULL;
 FILE* fp=NULL;
 TEXTMETRIC tm;
 HDC hdc;

 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData));
 if(!pScrDta)
    return -1;
 SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);
 hdc = GetDC(Wea.hWnd);
 hFont=CreateFont
 (
   -1*(11*GetDeviceCaps(hdc,LOGPIXELSY))/72,
   0,
   0,
   0,
   FW_SEMIBOLD,
   0,
   0,
   0,
   ANSI_CHARSET,
   0,
   0,
   DEFAULT_QUALITY,
   0,
   (TCHAR*)_T("Lucida Console")
 );
 if(!hFont)
    return -1;
 HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
 GetTextMetrics(hdc, &tm);
 pScrDta->cxChar = tm.tmAveCharWidth;
 pScrDta->cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * pScrDta->cxChar / 2;
 pScrDta->cyChar = tm.tmHeight + tm.tmExternalLeading;
 DeleteObject(SelectObject(hdc,hTmp));
 ReleaseDC(Wea.hWnd, hdc);
 fp=_tfopen(_T("Demo25.cpp"),_T("r"));
 if(fp)
 {
    pScrDta->iNumLines=Lof(fp);
    if(pScrDta->iNumLines)
    {
       pScrDta->pPtrs=(TCHAR**)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(TCHAR*) * pScrDta->iNumLines);
       if(pScrDta->pPtrs)
       {
          for(i=0; i<pScrDta->iNumLines; i++)
          {
              _fgetts(szBuffer,BUFFER_SIZE,fp);
              iLen=_tcslen(szBuffer);
              if(iLen>pScrDta->iMaxWidth)
                 pScrDta->iMaxWidth=iLen;
              pScrDta->pPtrs[i]=(TCHAR*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY, sizeof(TCHAR)*iLen+1*sizeof(TCHAR));
              _tcscpy(pScrDta->pPtrs[i],szBuffer);
          }
          pScrDta->iMaxWidth=pScrDta->iMaxWidth*pScrDta->cxChar;
       }
    }
    fclose(fp);
 }

 return 0;
}


long fnWndProc_OnSize(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    pScrDta->cxClient = LOWORD(Wea.lParam);
    pScrDta->cyClient = HIWORD(Wea.lParam);
    si.cbSize = sizeof(si) ;
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iNumLines - 1;
    si.nPage  = pScrDta->cyClient / pScrDta->cyChar;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
    si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
 }

 return 0;
}


long fnWndProc_OnVScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof(si);    // Get all the vertial scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    int iVertPos = si.nPos;    // Save the position for comparison later on
    switch (LOWORD(Wea.wParam))
    {
      case SB_TOP:
           si.nPos = si.nMin;
           break;
      case SB_BOTTOM:
           si.nPos = si.nMax;
           break;
      case SB_LINEUP:
           si.nPos -= 1;
           break;
      case SB_LINEDOWN:
           si.nPos += 1;
           break;
      case SB_PAGEUP:
           si.nPos -= si.nPage;
           break;
      case SB_PAGEDOWN:
           si.nPos += si.nPage;
           break;
      case SB_THUMBTRACK:
           si.nPos = si.nTrackPos;
           break;
      default:
           break;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    if(si.nPos != iVertPos)
    {
       ScrollWindow(Wea.hWnd, 0, pScrDta->cyChar*(iVertPos-si.nPos), NULL, NULL);
       UpdateWindow(Wea.hWnd);
    }
 }

 return 0;
}


long fnWndProc_OnHScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof (si);                // Get all the horizontal scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);  // Save the position for comparison later on
    int iHorzPos = si.nPos;
    switch (LOWORD(Wea.wParam))
    {
      case SB_LINELEFT:
           si.nPos -= 1;
           break;
      case SB_LINERIGHT:
           si.nPos += 1;
           break;
      case SB_PAGELEFT:
           si.nPos -= si.nPage;
           break;
      case SB_PAGERIGHT:
           si.nPos += si.nPage;
           break;
      case SB_THUMBTRACK:              // case SB_THUMBPOSITION:
           si.nPos = si.nTrackPos;
           break;
      default:
           break;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);
    if(si.nPos != iHorzPos)
       ScrollWindow(Wea.hWnd, pScrDta->cxChar*(iHorzPos-si.nPos), 0, NULL, NULL);
 }

 return 0;
}


long fnWndProc_OnPaint(WndEventArgs& Wea)
{
 int x,y,iPaintBeg,iPaintEnd,iVertPos,iHorzPos;
 ScrollData* pScrDta=NULL;
 HFONT hFont=NULL;
 PAINTSTRUCT ps;
 SCROLLINFO si;
 HDC hdc;

 hdc = BeginPaint(Wea.hWnd, &ps);
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    hFont=CreateFont
    (
      -1*(11*GetDeviceCaps(hdc,LOGPIXELSY))/72,
      0,
      0,
      0,
      FW_SEMIBOLD,
      0,
      0,
      0,
      ANSI_CHARSET,
      0,
      0,
      DEFAULT_QUALITY,
      0,
      (TCHAR*)_T("Lucida Console")
    );
    HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
    si.cbSize = sizeof (si);  // Get vertical scroll bar position
    si.fMask  = SIF_POS;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si), iVertPos = si.nPos;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si), iHorzPos = si.nPos;
    if(iVertPos+ps.rcPaint.top/pScrDta->cyChar>0)
       iPaintBeg=iVertPos + ps.rcPaint.top / pScrDta->cyChar;
    else
       iPaintBeg=0;
    if(iVertPos + ps.rcPaint.bottom / pScrDta->cyChar < pScrDta->iNumLines - 1)
       iPaintEnd=iVertPos + ps.rcPaint.bottom / pScrDta->cyChar;
    else
       iPaintEnd=pScrDta->iNumLines-1;
    for(int i = iPaintBeg; i<= iPaintEnd; i++)
    {
        x = pScrDta->cxChar * (1 - iHorzPos);
        y = pScrDta->cyChar * (i - iVertPos);
        TabbedTextOut(hdc, x, y, pScrDta->pPtrs[i], _tcslen(pScrDta->pPtrs[i]),0,NULL,0);
    }
    DeleteObject(SelectObject(hdc,hTmp));
 }
 EndPaint(Wea.hWnd, &ps);

 return 0;
}


long fnWndProc_OnMouseWheel(WndEventArgs& Wea)
{
 int zdelta=GET_WHEEL_DELTA_WPARAM(Wea.wParam);
 if(zdelta>0)
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
 }
 else
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
 }

 return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 HANDLE hHeap=NULL;

 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta->pPtrs)
 {
    for(int i=0; i<pScrDta->iNumLines; i++)
        HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    HeapFree(hHeap,0,pScrDta->pPtrs);
 }
 PostQuitMessage(0);

 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[]=_T("Scroll Demo");
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName = szClassName,                          wc.lpfnWndProc = fnWndProc;
 wc.cbSize        = sizeof(WNDCLASSEX),                   wc.style       = CS_HREDRAW | CS_VREDRAW;
 wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION),       wc.hIconSm     = NULL;
 wc.hInstance     = hInstance,                            wc.hCursor     = LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra  = sizeof(void*);
 wc.lpszMenuName  = NULL,                                 wc.cbClsExtra  = 0;
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,25,25,1140,1035,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return (int)messages.wParam;
}

These are the changes I made in WinCode.cpp of the grid to allow it to compile...

at top...

Code: [Select]
#define SizX(x)  x //    x  rxRatio
#define SizY(y)  y //    x  ryRatio

...and in long fnGridProc_OnCreate(lpWndEventArgs Wea)...

Code: [Select]
int dpiY=96;
 
 // DPI Handling
 //double dpiX, dpiY;
 //double rxRatio, ryRatio;
 hDC = GetDC(NULL);
 //dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
 //dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
 //rxRatio=(dpiX/96);
 //ryRatio=(dpiY/96);
 #ifdef MYDEBUG
 //_ftprintf(fp,_T("    dpiX                       = %6.2f\n"),dpiX);
 //_ftprintf(fp,_T("    dpiY                       = %6.2f\n"),dpiY);
 //_ftprintf(fp,_T("    rxRatio                    = %6.2f\n"),rxRatio);
 //_ftprintf(fp,_T("    ryRatio                    = %6.2f\n"),ryRatio);
 #endif
 // DPI Handling End

I guess I really need to work on this and get it working because several of my work apps use this grid in 32 bit.

Offline James C. Fuller

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 595
  • User-Rate: +11/-8
Re: Moving TCLib Into Mainstream
« Reply #63 on: May 13, 2016, 01:10:46 PM »
Fred,
  What is the status of TCLib?
Having released UbxBasic I decided to explore minimalization (not a word:)) again.

James

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #64 on: May 13, 2016, 07:28:43 PM »
That was about all I worked on for like 3 months.  Haven't touched it for several weeks now as I had to finally move to other stuff I had to do.  I believe there were a few details I didn't post about though.  Was having some difficulties getting my 32 bit code for my grid to compile and work, but I found the solution to that at Martins Mozeiko's post over on www.handmadehero.com.  Its an addition that is needed. 

Where I finally stopped was my attempt to get one of my big projects at work going with TCLib.  What stopped me was C Runtime pow() from Math.h.  I knew in the back of my mind I'd need that eventually, but forgot I used it in that particular app.  Actually, I don't know how to code that.  So what I did was download the GCC C Standard Library Source code, and at some point I hope to develop my own versions of that in C or asm.  But that's my long term goal.  Its not going to happen for quite some time - maybe like 6 months or a year.  I'm not that accomplished at ASM and I've never gotten into all the floating point stuff with the FPU, SSE2, and everything else.  Then there's the 64 bit issue to deal with in all that.  Any of the recent Visual Studio downloads have ml64 in the \bin folder. 

Some of my other long term plans with it are to eventually go through it function by function and see if anything can be optimized by ASM or otherwise.  So I have long term plans for it.  It all turned out too good for me not to want to use it.  I'm amazed I got as far as I did with it, and I owe a lot of thanks to Martins Mozeiko for his postings about it and help he gave me.

I'll be retiring in under a year and I've got to finish up on some documentation I'm now working on.  That's kind of why I had to lay it aside for a bit.  Let me look for that final piece of code though that's absolutely needed.  Back in a bit...   

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #65 on: May 13, 2016, 08:33:50 PM »
Attached are Examples_x86.zip and Examples_x64.zip.  There is a file in both of them named Win32_crt_Float.cpp.  It needs to be added to the last TCLib.lib that you have Jim - of course changed to *.obj.

In it are _ftol2() and _ftol2_sse().  Its C inlined assembler code in the form of naked functions.  Its only compiled in x86 builds as you'll see in the conditional compilation statements in the code.

The lack of it was preventing my Grid from being built in x86 form.

I was astounded to see how small my grid built with TCLib.  Actually, I was astounded it built at all.  I'm a pessimist by nature I suppose (as well as a minimalist) and always assume that nothin 'ell work.  That way I'm not disappointed very much, and I get to be pleasantly surprised when something does work.  But it built to 22 k in x64 and compacted to 18 K.  Did even a lot better in x86 and compacts there to 16 k. 

In the x86 package are Client1.exe and the x86 version of FHGrid.dll.  They ought to run without registering the grid as Client1 uses Registry Free COM.  The registration code works very well though.  Its been thourougly tested on 2000, XP, Win7 and Win10 in both x86 and x64 where applicable.

In the x64 package are Client2.exe and the 64 bit version of the grid.  Everything I said above applies there too.

I believe if you simply provide that Win32_crt_Float.cpp file and update the TCLib.mak file with that listing you'll have the latest stuff I have.  Like I said I'd like to do more work on it in the future, but for now I'd best put it aside for awhile and work on other stuff that needs doing.

Offline James C. Fuller

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 595
  • User-Rate: +11/-8
Re: Moving TCLib Into Mainstream
« Reply #66 on: May 13, 2016, 09:00:40 PM »
Fred,
  Thanks for the update.
  I found the latest of my development of TCLib for use with bc9Basic and it seems pretty complete. I intend to stay with the old fprintf and use the WinApi File IO and the memory mapped file concept for line input.
I have the luxury with bc9Basic to take:
   Raw As fstring sFile("TEST1.TXT")
   Local As HANDLE hFile
   OpenFile sFile For Input As hFile
and translate it to
    fstring  sFile(_T("TEST.TXT"));
    HANDLE   hFile = {0};
    hFile = CreateFile( sFile.lpStr(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

I don't need the 32bit stuff as I don't write 32bit code any more.
While your grid is impressive, c++ COM is a bit more than my old brain can take at the moment :)

I will post some code in the bc9Basic section that uses TCLib.
I have one small gui that was reduced to 7k from 87k.

James

Offline James C. Fuller

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 595
  • User-Rate: +11/-8
Re: Moving TCLib Into Mainstream
« Reply #67 on: May 16, 2016, 03:06:26 PM »
Fred,
  I cannot seem to find either the Lof / LineCount function in any of my files.

Edit:
Never mind I found Lof and renamed it to LineCount

James
« Last Edit: May 16, 2016, 03:15:00 PM by James C. Fuller »

Offline James C. Fuller

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 595
  • User-Rate: +11/-8
Re: Moving TCLib Into Mainstream
« Reply #68 on: May 18, 2016, 12:07:43 PM »
Fred,
  The link: http://www.handmadehero.com/
appears to be gone?

James

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #69 on: May 18, 2016, 06:14:25 PM »
Wow!  I see.  Don't know what to make of it Jim.  It seemed to be mostly a gaming site.  I had first found it in trying to solve the horrendous difficulties I was having trying to get any floating point functionality without linking against the C Std. Lib.  That Martins Mozeiko is kind of like Jose is with PowerBASIC and COM/OLE - if it has anything to do with C, C++, or assembler, he knows about everything there is to know about it.  He's the man with the answers.  I don't know what us lesser mortals would do without folks like that. 

The site was to further a game that the owner of the site was developing.  He was developing it piecemeal and documenting everything online.  Casey M - something was his name - an advanced game developer I'm to understand.  But another thing I came to understand through this is that game developers seem to have a lot in common with a lot of PowerBASIC developers such as myself who strive to produce fast, small code with a minimum of dependencies. 

There was an interesting thread there about the pros/cons of OOP.  It seems a lot of game developers prefer C to C++, or at least C isms to C++ isms, for developing games.  What I found most interesting about that thread was the complete disorientation new C++ coders were having upon learning that the most advanced game programmers - including the head man of that site who was developing the demo game for learning purposes, used C instead of C++. 

Anyway, that doesn't look good.  I wish it would come back.

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris

Offline James C. Fuller

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 595
  • User-Rate: +11/-8
Re: Moving TCLib Into Mainstream
« Reply #71 on: May 19, 2016, 03:56:45 PM »
Fred,
  Here is an alternative line input.
I could not get your latest FILE based IO to work but decided the MMF approach was a bit weird and large.

File must be opened with CreateFile using GENERIC_READ.

The file must be asci. The lineinput routine converts to wide for your String (my fstring)

You need a:
typedef String fstring;
typedef char _char;


James

Code: [Select]
int LineInput (HANDLE hFile, fstring&  sData)
{
    _char*   ReadBuffer = {0};
    DWORD    dwBytesRead = {0};
    DWORD    dwPtr = {0};
    long     where = {0};
    int      j = {0};
    ReadBuffer = new _char[ 1024];
    dwPtr = SetFilePointer( hFile, NULL, NULL, FILE_CURRENT);
    if(FALSE == ReadFile(hFile, ReadBuffer, 1024, &dwBytesRead, NULL))
    {
        return -3;
    }
    if(dwBytesRead == 0 )
    {
        return -1;
    }
    {
        int      i;
        for(i = 0; i < dwBytesRead; i++)
        {
            if(ReadBuffer[i] == 10 )
            {
                where = i;
                j = where;
                if(ReadBuffer[i - 1] == 13 )
                {
                    where = i + 1;
                    j = i - 1;
                }
                break;
            }
        }
    }

    SetFilePointer(hFile, dwPtr + where, NULL, FILE_BEGIN);
    ReadBuffer[j]  = 0;
    wchar_t*  wszTo = new wchar_t[j + 1];
    wszTo[j] = L'\0';
    MultiByteToWideChar(CP_ACP, 0, ReadBuffer, -1, wszTo, (int)j);
    sData = wszTo;
    delete[] wszTo;
    delete []ReadBuffer;
    return 0;
}


Offline James C. Fuller

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 595
  • User-Rate: +11/-8
Re: Moving TCLib Into Mainstream
« Reply #72 on: May 20, 2016, 04:22:58 PM »
Fred,
  How do you work with binary file data?
Do you use your String class or just a void* buffer?

James

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #73 on: May 24, 2016, 04:49:21 AM »
Hello, Jim!

https://hero.handmade.network/

https://hero.handmade.network/forums/code-discussion/t/1083

https://hero.handmade.network/forums/code-discussion/t/209

This FILE business has been something of an important nuisance!  Originally I hadn’t paid it much mind, but then you got me thinking about it, and at that point I had everything else pretty much working, so I tried to work it out.

I’ve been at C a pretty long time, and have always used and liked the C Runtime functions for dealing with text files, and the functions involved there were fopen, fclose, fscanf, fgets, rewind, and feof.

Now the C Runtime has more general purpose functions for dealing with files of any type, i.e., binary, random access, and text files such as fread and fwrite, but I just never used them that much.

I simply don’t deal with files as binary streams that much, but I do deal with the special case of random access binary files a real lot.  As I mentioned above, the C Runtime has functions for that, i.e., fread and fwrite, but I’ve always tended to use the Windows functions for dealing with binary/random access files, i.e., ReadFile(), WriteFile(), SetFilePointer(), etc.

So I don’t have anything worked out whatsoever with regards to my String Class and binary/random access files.  I have wrappers around ReadFile/WriteFile/SetFilePointer which I believe you’ve already seen which I’ve named Get/Put after BASIC language versions of the same.

So when it came time to attempt to look at the issue with regard to my TCLib work, my real big realization was that in my general day to day programming work I use the C Runtime functions fopen, fprintf, and fclose on a daily basis for all my debugging work.  Unlike a lot of other coders I hardly use debuggers, and I couldn’t code and any real apps without my trusty debug output to log files.

In fooling around with it I discovered to my amazement that somehow a stdio FILE object and a Windows HANDLE were interchangeable, at least in the context of the fopen and CreateFile functions.  I’d really like to know more about that at some point.

So in my early efforts I was able to get fopen and fprintf working and I posted some code on that that I later changed.

Then you got me to thinking about the whole Line Input thing.  I’d always used C Runtime fgets for that.  Its not exactly like BASIC Line Input, but real close.  And I realized I really needed to have that too.  At first I was a bit confused about how to implement it, but it soon dawned on me that the only reasonable implementation would involve ReadFile() with the bytes to read parameter set to 1 for one byte at a time.  And the function would have to run in a loop testing for end of line or number of bytes to read.  So that’s what I worked out and posted I believe.  I didn’t want to name it Line Input but rather fgets and fgetws.  These are I believe reasonably good implementations of those (I’ve posted them before)…

Code: [Select]
//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, April 2016
//
//                   cl fgets.cpp /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
#include "stdlib.h"
#include "stdio.h"


char* __cdecl fgets(char* pBuffer, int iSize, FILE* fp)
{
 bool blnAddCharToBuffer=true;
 DWORD lpBytesRead=0;
 int iCtr=0;
 char c=0;

 if(fp!=INVALID_HANDLE_VALUE)
 {
    pBuffer[0]=0;
    do
    {
       ReadFile((HANDLE)fp,&c,1,&lpBytesRead,NULL);
       if(lpBytesRead==0)
       {
          if(iCtr)
          {
             pBuffer[iCtr]=10;
             pBuffer[iCtr+1]=0;
          }
          if(pBuffer[0])
             return pBuffer;
          else
             return NULL;
       }
       if(iCtr==iSize-1)
       {
          blnAddCharToBuffer=false;
          pBuffer[iCtr+0] = 10;
          pBuffer[iCtr+1] = 0;
       }
       if(c==13)
       {
          if(blnAddCharToBuffer)
          {
             pBuffer[iCtr]=10;
             iCtr++;
             pBuffer[iCtr]=0;
             blnAddCharToBuffer=false;
          }
       }
       if(blnAddCharToBuffer)
          pBuffer[iCtr]=c;
       if(c==10)
          break;
       iCtr++;

    }while(true);
 }
 
 return pBuffer;
}


wchar_t* __cdecl fgetws(wchar_t* pBuffer, int iSize, FILE* fp)
{
 char* pAsciBuffer=NULL;
 char* pRet=NULL;

 memset(pBuffer,0,iSize*2);
 pAsciBuffer=(char*)malloc(iSize);
 if(!pAsciBuffer)
    return NULL;
 memset(pAsciBuffer,0,iSize);
 pRet=fgets(pAsciBuffer,iSize,fp);
 if(pRet)
    MultiByteToWideChar(CP_ACP,0,pAsciBuffer,(int)strlen(pAsciBuffer),pBuffer,iSize);
 free(pAsciBuffer);
 if(pRet)
    return pBuffer;
 else
    return NULL;
}


size_t __cdecl Lof(FILE* fp)
{
 DWORD lpNumberOfBytesRead=0;
 size_t iReadSomething=0;
 size_t iCtr=0;
 char c=0;

 if(fp!=INVALID_HANDLE_VALUE)
 {
    do
    {
       ReadFile((HANDLE)fp,&c,1,&lpNumberOfBytesRead,NULL);
       if(lpNumberOfBytesRead)
       {
          if(c==10)
          {  iReadSomething=0;
             iCtr++;
          }
          else
             iReadSomething=1;
       }
       else
          break;
    }while(true);
 }
 rewind(fp);

 return iCtr+iReadSomething;
}


void __cdecl rewind(FILE* fp)
{
 SetFilePointer((HANDLE)fp,0,NULL,FILE_BEGIN);
}


The only other suspects left in the line up are fprintf and fopen…

Code: [Select]
//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, March 2016
//
//  cl fprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
// cl sprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdarg.h>
#include "stdio.h"
#define EOF (-1)
#pragma comment(linker, "/defaultlib:user32.lib")

int __cdecl fprintf(FILE* fp, const char* format, ...)
{
 char szBuff[1024];
 DWORD cbWritten;
 va_list argptr;
 int retValue;

 va_start(argptr, format);
 retValue = wvsprintfA(szBuff, format, argptr);
 va_end(argptr);
 WriteFile((HANDLE)fp, szBuff, retValue, &cbWritten, 0);

 return retValue;
}

int __cdecl fwprintf(FILE* fp, const wchar_t* format, ...)
{
 wchar_t szBuff[1024];
 DWORD cbWritten;
 va_list argptr;
 int retValue;

 va_start(argptr, format);
 retValue = wvsprintfW(szBuff, format, argptr);
 va_end(argptr);
 WriteFile((HANDLE)fp, szBuff, retValue*2, &cbWritten, 0);

 return retValue;
}

The above were changed from my initial postings of those functions.  And here is fopen…

Code: [Select]
//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, April 2016
//
//   cl fopen.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
#include <windows.h>
#include "stdio.h"


FILE* fopen(const char* pszFile, const char* pszMode)
{
 DWORD dwDesiredAccess;
 HANDLE hFile=NULL;

 if(*pszMode=='w')
    dwDesiredAccess=GENERIC_WRITE;
 if(*pszMode=='r')
    dwDesiredAccess=GENERIC_READ;
 hFile=CreateFileA(pszFile,dwDesiredAccess,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
 if(hFile==INVALID_HANDLE_VALUE)
    return NULL;

 return (FILE*)hFile;
}


FILE* _wfopen(const wchar_t* pszFile, const wchar_t* pszMode)
{
 DWORD dwDesiredAccess;
 HANDLE hFile=NULL;

 if(*pszMode==L'w')
    dwDesiredAccess=GENERIC_WRITE;
 if(*pszMode==L'r')
    dwDesiredAccess=GENERIC_READ;
 hFile=CreateFileW(pszFile,dwDesiredAccess,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
 if(hFile==INVALID_HANDLE_VALUE)
    return NULL;

 return (FILE*)hFile;
}


int fclose(FILE* fp)
{
 return !CloseHandle((HANDLE)fp);
}

And they need stdio.h and tchar.h…

Code: [Select]
// stdio.h
#ifndef stdio_h
#define stdio_h

struct FILE
{
 char*  _ptr;
 int    _cnt;
 char*  _base;
 int    _flag;
 int    _file;
 int    _charbuf;
 int    _bufsiz;
 char*  _tmpfname;
};

extern "C" char     __cdecl getchar();

extern "C" FILE*    __cdecl fopen(const char* pszFile, const char* pszMode);
extern "C" FILE*    __cdecl _wfopen(const wchar_t* pszFile, const wchar_t* pszMode);

extern "C" char*    __cdecl fgets(char* pBuffer, int iSize, FILE* fp);
extern "C" wchar_t* __cdecl fgetws(wchar_t* pBuffer, int iSize, FILE* fp);

extern "C" int      __cdecl printf(const char* format, ...);
extern "C" int      __cdecl wprintf(const wchar_t* format, ...);

extern "C" int      __cdecl fprintf(FILE* fp, const char* format, ...);
extern "C" int      __cdecl fwprintf(FILE* fp, const wchar_t* format, ...);

extern "C" int      __cdecl sprintf(char* buffer, const char* format, ...);
extern "C" int      __cdecl swprintf(wchar_t* buffer, const wchar_t* format, ...);

extern "C" size_t   __cdecl FltToCh(char* p, double x, size_t iFldWthTChrs, int iDecPlaces, char cDecimalSeperator, bool blnRightJustified);
extern "C" size_t   __cdecl FltToWch(wchar_t* p, double x, size_t iFldWthTChrs, int iDecPlaces, wchar_t cDecimalSeperator, bool blnRightJustified);

extern "C" size_t   __cdecl Lof(FILE* fp);
extern "C" void     __cdecl rewind(FILE* fp);
extern "C" int      __cdecl fclose(FILE* fp);
#endif
   

Code: [Select]
// tchar.h
#ifndef tchar_h
   #define tchar_h
   #ifdef  _UNICODE
      typedef wchar_t     TCHAR;
      #define _T(x)       L## x
      #define _tmain      wmain
      #define _tWinMain   wWinMain
      #define _tfopen     _wfopen
      #define _fgetts     fgetws
      #define _tprintf    wprintf
      #define _ftprintf   fwprintf
      #define _stprintf   swprintf
      #define _tcslen     wcslen
      #define _tcscpy     wcscpy
      #define _tcscat     wcscat
      #define _tcsncpy    wcsncpy
      #define _tcscmp     wcscmp
      #define _tcsicmp    _wcsicmp
      #define _tcsncmp    wcsncmp
      #define _tcsnicmp   _wcsnicmp
      #define _tcsrev     _wcsrev
      #define FltToTch    FltToWch
      #define _ttol       _wtol
      #define _ttoi64     _wtoi64
   #else
      typedef char        TCHAR;
      #define _T(x)       x
      #define _tmain      main
      #define _tWinMain   WinMain
      #define _tfopen     fopen
      #define _fgetts     fgets
      #define _tprintf    printf
      #define _ftprintf   fprintf
      #define _stprintf   sprintf
      #define _tcslen     strlen
      #define _tcscpy     strcpy
      #define _tcscat     strcat
      #define _tcsncpy    strncpy
      #define _tcscmp     strcmp
      #define _tcsicmp    _stricmp
      #define _tcsncmp    strncmp
      #define _tcsnicmp   _strnicmp
      #define _tcsrev     _strrev
      #define FltToTch    FltToCh
      #define _ttol       atol
      #define _ttoi64     _atoi64
   #endif
#endif

The below three programs should be able to be compiled with TCLib or the C Standard Library.  They are Test1.cpp, Test2.cpp and Test3.cpp.  They work with this text file…

TestData.txt
Code: [Select]
Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4

Below is Test1.cpp.  And in the above code I see I lapsed again into using Lof() for LineCount().  Try hard not to let that faul you up.  I’m sure its about as hard for BASIC programmers to deal with that name as it is for me to avoid using it because as far back as I can remember I’ve been writing Lof functions in C to count the number of lines in a text file, whereas in BASIC a function of that name counts the bytes in a file.  Anyway, in self-defense I put a rewind() call at the end of Lof() cuz I never learn and continue to waste piles of time by calling Lof to get the line count in a text file, then I forget to rewind the file cuz the file pointer is at EOF, then I try to read the file and I get NOTHING!  Then I spend an hour debugging fgets() trying to figure out why it won’t read the file, only to finally figure out I forgot to set the file pointer back to the beginning of the file to read it!!!!  I don’t know how many times I’ve done that.  Not in the same day or week of course, but months later one forgets details!  Anyway, this should read the file…

Code: [Select]
// cl Test1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
 TCHAR szBuffer[128];
 TCHAR pCh=NULL;
 FILE* fp=NULL;
 size_t iLof=0;

 fp=_tfopen(_T("TestData.txt"),_T("r"));
 if(fp)
 {
    _tprintf(_T("File Opened Successfully!\n"));
    iLof=Lof(fp);
    _tprintf(_T("iLof   = %Iu\n\n"),iLof);
    rewind(fp);
    for(size_t i=1; i<=iLof; i++)
    {
        _fgetts(szBuffer,128,fp);   
        _tprintf(_T("%s"),szBuffer);
    }
    fclose(fp);   
 }
 getchar();

 return 0;
}

/*
File Opened Successfully!
iLof   = 4

Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4
*/

Test2.cpp does the same thing but differently.  You can read and output the lines of text in the text file without knowing beforehand how many lines there are, because fgets returns NULL when it hits EOF…

Code: [Select]
// cl Test2.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
 TCHAR szBuffer[128];
 TCHAR* pCh=NULL;
 FILE* fp=NULL;
 
 fp=_tfopen(_T("TestData.txt"),_T("r"));
 if(fp)
 {
    _tprintf(_T("File Opened uccessfully!\n\n"));
    do
    {
      pCh=_fgetts(szBuffer,128,fp);
      if(!pCh)
         break;   
      _tprintf(_T("%s"),szBuffer);
    } while(true);
    fclose(fp);   
 }
 getchar();

 return 0;
}

The C Standard Library fgets behaves similiarly.  As I said, any of these programs are standard C programs that are not dependent on TCLib.  Another aspect of fgets is that the middle parameter takes the number of characters to read from each line.  In Test3.cpp I have it set to only read 12 characters from each line…

Code: [Select]
// cl Test3.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
 TCHAR szBuffer[128];
 FILE* fp=NULL;
 TCHAR pCh=NULL;
 size_t iLof=0;

 fp=_tfopen(_T("TestData.txt"),_T("r"));
 if(fp)
 {
    _tprintf(_T("File Opened uccessfully!\n"));
    iLof=Lof(fp);
    _tprintf(_T("iLof   = %Iu\n\n"),iLof);
    for(size_t i=1; i<=iLof; i++)
    {
        _fgetts(szBuffer,12,fp);   
        _tprintf(_T("%Iu\t%s"),_tcslen(szBuffer),szBuffer);
    }
    fclose(fp);   
 }
 getchar();

 return 0;
}

/*
C:\Code\VStudio\VC++9\LibCTiny\fgets>test3
File Opened uccessfully!
iLof   = 4

12      Here Is Lin
12      And Here Is
12      But Lets No
12      And Finally
*/

So anyway, I believe that’s the core of my TCLib interpretation of text file i/o.  I simply couldn’t use your MMF work for this Jim.  I felt strongly that a byte by byte low level read of a file stream with ReadFile in a loop was the best interpretation of fgets, and I’d be surprised if that wasn’t Bob Zale’s underlying implementation of Line Input.  I do respect your work on MMF though, as that’s something lacking in my background.  All I know about it is that MCM in the PowerBASIC forums loves it (MMF), and that its an important component of inter-process communication.  I don’t usually study up on something until I see a need for it I guess.  I did try your MMF examples though, and was surprised it didn’t add much overhead to the program.  And of course it did work.  Someday when I have time to tackle MMF I’ll get back to it.

The only other possible implementation of Line Input would be scanf / fscanf looking for a character string, and most C or C++ coders including myself hold those functions in fairly low regard.  It could possibly be managed though.  But it wouldn’t buy me anything with TCLib because I’d then have to implement scanf / fscanf, and that would bring me back to Win32 ReadFile().

After spending a few weeks on documentation I’m back at TCLib though.  At least temporarily.  I’m far from done with documentation.

What stopped me with TCLib though was, as I said, pow() from Math.h.  But I made a workaround for that.  I had a bunch of complicated regression equations for calculating the volume of pulpwood trees.  I was able to output a volume table though with the calculated volumes by diameter and height, and now I can read them into my program (using fgets(), by the way), and eliminate the need to raise doubles to exponents that are themselves doubles, thereby eliminating my need for pow().  In the fullness of time, as I said before, I want to figure out the code to do that, i.e., pow.  But for now I’ve gotten around the pow issue with my code.  After throwing the weekend plus today at it I’m down to these last remaing two unresolved externals, which are atof and wcstombs.

So I can deal with that.  I need to implement atof, and the 2nd one – wcstombs, can be removed with WideCharToMultiByte().  So I’m thinking in a couple days I might be able to get a major component of our Timber Sale System converted over to TCLib builds!

Sorry I took so long replying.  I didn’t see your posts ‘till today.  This past weekend I wasn’t on the internet at all.  All I did was alternate working on code and practicing my 5 string banjo picking.  I practice a pretty lot, as one of my lifelong interests has always been bluegrass music.  I have yard work to do but all it ever does around here is rain so I can’t seem to get that done.   I can’t get my hedge cut no how.

 

Offline Frederick J. Harris

  • Hero Member
  • *****
  • Posts: 914
  • User-Rate: +16/-0
    • Frederick J. Harris
Re: Moving TCLib Into Mainstream
« Reply #74 on: May 24, 2016, 05:23:41 PM »
If I wanted to implement Basic’s Line Input in C/C++ I’d use fgets to do it.  The major difference between fgets and basic’s Line Input is that basic’s Line Input will return a string without a CR or LF at the end.  For example, this PowerBASIC Console Compiler program will return a length of 15 for the first string of my previously posted TestData.txt file…

Code: [Select]
#Compile Exe
#Dim All

Function PBMain() As Long
  Local strLine As String
  Local fp As Long

  fp=Freefile
  Open "TestData.txt" For Input As #fp
  Line Input #fp, strLine
  Console.Print strLine
  Console.Print "Len(strLine) = " Len(strLine)
  Close #fp
  Waitkey$

  PBMain=0
End Function

#if 0

Here Is Line #1
Len(strLine) =  15

#EndIf

Here’s the same in C…

Code: [Select]
// cl CFGets.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"

int main()
{
 char szBuffer[128];
 FILE* fp=NULL;
 
 fp=fopen("TestData.txt","r");
 if(fp)
 {
    fgets(szBuffer,128,fp);   
    printf("%s",szBuffer);
    printf("strlen(szBuffer = %Iu\n",strlen(szBuffer));
    fclose(fp);   
 }
 getchar();

 return 0;
}

#if 0

Here Is Line #1
strlen(szBuffer) = 16

#endif

…and as you can see, the output is the same but there is a newline at the end which gets counted in the string length making strlen return 16 instead of 15 as with PowerBASIC.  So it would be most desirable to not hack up C code by changing the implementation of fgets – which would screw up already existing and working C code, but to rather simply implement in C a LineInput function that preserves basic language behaviour, which in my humble opinion is better anyway.  Here would be one possible implementation of that which pulls in my String Class…

Code: [Select]
// cl LineInput.cpp Strings.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
// 5120 bytes
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "Strings.h"

void LineInput(FILE* fp, String& strLn)
{
 char szBuffer[2048];                 // Allocate Buffer
 
 memset(szBuffer,0,2048);             // Zero Out Buffer
 fgets(szBuffer,2048,fp);             // Read Line From fp, Which Will Be Terminated With CR Or LF + NULL
 szBuffer[strlen(szBuffer)-1]=NULL;   // Replace CR Or LF With NULL To Shorten String By One Byte
 strLn=szBuffer;                      // Assign To String
}

int main()
{
 String strLine;
 FILE* fp=NULL;
 
 fp=fopen("TestData.txt","r");
 if(fp)
 {
    LineInput(fp,strLine);
    strLine.Print(true);
    printf("strLine.Len() = %Iu\n",strLine.Len());   
    fclose(fp);   
 }
 getchar();

 return 0;
}


#if 0

Here Is Line #1
strLine.Len() = 15

#endif
 

So now we have it working like in PowerBASIC where the last character of the string, not counting the NULL, is a printable character rather than a Cr or Lf (I forget which).