Pelles C forum

C language => Work in progress => Topic started by: czerny on March 02, 2013, 10:40:00 AM

Title: reanimate a perished fish
Post by: czerny on March 02, 2013, 10:40:00 AM
In my opinion there are two shortages in Pelles C, the lack of a good library (such as STL) and the problems using the COM interface.

To the first point read the following article http://www.drdobbs.com/cpp/general-purpose-c-libraries-limited-opti/231000967# (http://www.drdobbs.com/cpp/general-purpose-c-libraries-limited-opti/231000967#),
though there must be more than the three mentioned libraries, I think.

To the second point I will do some research. I am not an expert in COM-programming, so I need your help.

Lets start with a first question: Assuming that I want to use ADO, what is the canonical way to get the corresponding header file?

czerny
Title: Re: reanimate a perished fish
Post by: Bitbeisser on March 02, 2013, 06:43:02 PM
In my opinion there are two shortages in Pelles C, the lack of a good library (such as STL)
Well, STL is a C++ library and as stated a lot of times in here and in that article you linked yourself to below, C and C++ are two distinct programming languages...
Quote
To the first point read the following article http://www.drdobbs.com/cpp/general-purpose-c-libraries-limited-opti/231000967# (http://www.drdobbs.com/cpp/general-purpose-c-libraries-limited-opti/231000967#),
though there must be more than the three mentioned libraries, I think.
Those libraries mentioned are also nothing (directly) Windows related, they are rather Linux related libraries, with little relevance to Windows programming, and that is the only target environment of Pelle's C.

Ralf

Title: Re: reanimate a perished fish
Post by: czerny on March 03, 2013, 10:46:17 AM
Thank you tater!

There is now a 7 page discussion about the question whether Pelles C is or is not a dead fish.
This does not help at all.

Is there anybody who can contribute?
Who is able to answer my question above?

czerny
Title: Re: reanimate a perished fish
Post by: JohnF on March 03, 2013, 12:31:33 PM

Is there anybody who can contribute?
Who is able to answer my question above?

czerny

To get this header one would normally download the latest M.S. SDK if PellsC does not already have it. Using it however will require a lot of work as it is intended for use with C++.

I have a copy of it if you would like to see it.

John
Title: Re: reanimate a perished fish
Post by: CommonTater on March 03, 2013, 04:36:41 PM

Is there anybody who can contribute?
Who is able to answer my question above?

czerny

To get this header one would normally download the latest M.S. SDK if PellsC does not already have it. Using it however will require a lot of work as it is intended for use with C++.

I have a copy of it if you would like to see it.

John

Actually John, I believe ADO would be part of the MS Office SDK...
Please correct me if I'm wrong...
 
 
Title: Re: reanimate a perished fish
Post by: CommonTater on March 03, 2013, 04:38:32 PM
There is now a 7 page discussion about the question whether Pelles C is or is not a dead fish.
This does not help at all.

Ok... message deleted....
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 03, 2013, 05:21:22 PM
msado15.h is in MSDACSDK too.
http://en.wikipedia.org/wiki/Microsoft_Data_Access_Components

If you try to create msado15.h from msado15.dll, it may be broken.
In Connection15 this is missing from it:
     /* Connection15 methods */
     STDMETHOD(get_Properties)(THIS,Properties **);
Title: Re: reanimate a perished fish
Post by: JohnF on March 03, 2013, 05:52:12 PM
Tater,

Actually John, I believe ADO would be part of the MS Office SDK...
Please correct me if I'm wrong...

I saw no reference to MS Office - but I don't know either way.

John
Title: Re: reanimate a perished fish
Post by: CommonTater on March 03, 2013, 06:23:43 PM
Tater,

Actually John, I believe ADO would be part of the MS Office SDK...
Please correct me if I'm wrong...

I saw no reference to MS Office - but I don't know either way.

John

I wasn't sure either ... Looks like Timo has it....
 
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 03, 2013, 06:59:06 PM
msado15.dll can be found from location:
C:\Program Files\Common Files\System\ado\msado15.dll
C:\Program Files (x86)\Common Files\System\ado\msado15.dll
It comes with Windows from Win2K and newer.

Title: Re: reanimate a perished fish
Post by: czerny on March 03, 2013, 08:50:53 PM
timovjl: Your msado15.h is 97 KB big and dated from today. How do you have build it?
Your example is running with nState = 1. But I can not compile it.
POLINK: error: Unresolved external symbol '_IID_Connection15'.
POLINK: error: Unresolved external symbol '_CLSID_Connection'.

---

I have here PSDK_2003R2.
msado15.h is there unly a stub including a header file adoint.h 471 KB and dated from 3.3.2006.

There is also a msado15.idl

Running midl msado15.idl produces :

msado15.h (445 KB)
msado15_i.c (7 KB)
msado15_p.c (436 KB)
dlldata.c (1 KB)

What are the differences between msado15.h(adoint.h), msado15.h(midl), msado15.h(timovjl)?
What are the other files for?

czerny
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 03, 2013, 09:51:33 PM
timovjl: Your msado15.h is 97 KB big and dated from today. How do you have build it?
Your example is running with nState = 1. But I can not compile it.
POLINK: error: Unresolved external symbol '_IID_Connection15'.
POLINK: error: Unresolved external symbol '_CLSID_Connection'.
I just use 'Load type Library..' (AddIn) and open msado15.dll to create that file and then modified it.
Title: Re: reanimate a perished fish
Post by: czerny on March 03, 2013, 10:25:32 PM
Now it compiles! What have you changed? Uups! It doesn't compile anymore.
Title: Re: reanimate a perished fish
Post by: Stefan Pendl on March 03, 2013, 11:19:27 PM
Now it compiles! What have you changed?

WinMerge, WinDiff or SVNdiff are tools to compare two files or folders, which will enable you to see for yourself what has changed ;)
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 05, 2013, 01:18:07 PM
What are the differences between msado15.h(adoint.h), msado15.h(midl), msado15.h(timovjl)?
What are the other files for?

czerny
Use that MIDL msado15.h and msado15_i.c for GUID/IID.
Some version may need to #define __RPC__deref_out
My msado15.h is just too broken.

BTW: In John's site there is ADO with C example:
http://www.johnfindlay.plus.com/lcc-win32/DataBase/c_ado.zip
Title: Re: reanimate a perished fish
Post by: czerny on March 05, 2013, 09:56:22 PM
Quote
Use that MIDL msado15.h and msado15_i.c for GUID/IID.

Yes, this works ok!

There is only one

#endif !_MIDL_USE_GUIDDEF_

which has to be changed to

#endif // !_MIDL_USE_GUIDDEF_

So, what can we say? Is the midl way the canonical way?
What are the two other files (msado15_p.c and dlldata.c) for?

czerny
Title: Re: reanimate a perished fish
Post by: czerny on March 06, 2013, 12:04:01 AM
Can someone please explane? What is the difference between
Connection, _Connection and Connection15 (maybe there are more variants)?
There is a 'struct ADOConnection' too.
I guess there is 'typedef struct ADOConnection Connection;'?

czerny
Title: Re: reanimate a perished fish
Post by: czerny on March 06, 2013, 01:06:13 AM
BTW: In John's site there is ADO with C example:
http://www.johnfindlay.plus.com/lcc-win32/DataBase/c_ado.zip

Thank you! That is a good example.

I haven't jet understand the meaning of INITGUID.
In the above example I found:

/* We define INITGUID so that guids are obtained from the header files
 * instead of a library. Note adoid.h should be included before msado15.h */
/*

What are the pros and cons of using a lib over header files?
Title: Re: reanimate a perished fish
Post by: czerny on March 24, 2013, 10:25:04 PM
Hi,

Quote
In my opinion there are two shortages in Pelles C, the lack of a good library (such as STL)

in the last weeks I have tried to find a good library. In the moment I am exploring 'c2lib'. It is a unix library and propably not trivial to port. But beside that, it uses an own memory mangement, a pool, which is meant for long living objects. The pool is allocated before main
and freed after exit. I am a little bit confused about that. I thought that under modern operating systems like windows or unix a process has its own memory space which is freed by the os. So such an idea like a pool is needless.

What do you thinck about this?

czerny
Title: Re: reanimate a perished fish
Post by: jj2007 on March 24, 2013, 11:06:23 PM
A pool (or better: a circular buffer) makes sense for getting quickly small amounts of memory, e.g. for a conversion routine with a tight innermost loop. HeapAlloc is the official recommendation for that, but it's pretty slow (albeit faster than VirtualAlloc).

And it is correct that the OS is supposed to release all memory on ExitProcess, but I have often seen extremely sluggish behaviour well after ExitProcess. Google is not helpful, my best guess is that the OS takes time to zero the released physical memory so that it becomes available to other processes.
Title: Re: reanimate a perished fish
Post by: czerny on March 27, 2013, 12:12:32 PM
A pool (or better: a circular buffer) makes sense for getting quickly small amounts of memory, e.g. for a conversion routine with a tight innermost loop. HeapAlloc is the official recommendation for that, but it's pretty slow (albeit faster than VirtualAlloc).
How can a circular buffer be used to do that? As I understand it, allocs and frees have to arrive in corresponding order, what can not guaranteed.

I have made a little excerpt from the pool implementation of c2lib, just to get the idea. In the original there is much more.
Title: Re: reanimate a perished fish
Post by: jj2007 on March 27, 2013, 03:27:30 PM
A pool (or better: a circular buffer) makes sense for getting quickly small amounts of memory, e.g. for a conversion routine with a tight innermost loop. HeapAlloc is the official recommendation for that, but it's pretty slow (albeit faster than VirtualAlloc).
How can a circular buffer be used to do that? As I understand it, allocs and frees have to arrive in corresponding order, what can not guaranteed.

You simply don't free them. A circular buffer is meant to get a temporary storage, e.g. a MAX_PATH buffer used in a string concatenation, a conversion, etc. The sequence is roughly:

0. Allocate a fat buffer (fat=somewhere below your data cache size)

repeat
  1. ask the slot manager for a pointer to the next free slot
  2. write to that buffer (up to some maxbytes setting)
  3. store ptr and bytes written to your private variables
  4. inform the slot manager about the end of your slot
  5. slot manager adds a few bytes, aligns the pointer and waits for the next request
until end of program

x. Free the fat buffer
Title: Re: reanimate a perished fish
Post by: czerny on March 27, 2013, 06:25:04 PM
You simply don't free them. A circular buffer is meant to get a temporary storage, e.g. a MAX_PATH buffer used in a string concatenation, a conversion, etc. The sequence is roughly:

0. Allocate a fat buffer (fat=somewhere below your data cache size)

repeat
  1. ask the slot manager for a pointer to the next free slot
  2. write to that buffer (up to some maxbytes setting)
  3. store ptr and bytes written to your private variables
  4. inform the slot manager about the end of your slot
  5. slot manager adds a few bytes, aligns the pointer and waits for the next request
until end of program

x. Free the fat buffer

What do you mean with: 3. store ptr

A pointer to this location is useless. It gets overwritten by further requests.

What do you mean with: 3. store bytes

If you copy your data, the location is not longer needed and a job manager is jobless.

Title: Re: reanimate a perished fish
Post by: jj2007 on March 27, 2013, 07:48:50 PM

What do you mean with: 3. store ptr

A pointer to this location is useless. It gets overwritten by further requests.

What do you mean with: 3. store bytes

If you copy your data, the location is not longer needed and a job manager is jobless.

store pointer means assign it to some variable.
store bytes means use another variable to keep the length of memory used.

There are many instances where you need storage only temporarily. Example string concatenation (sorry for the Basic syntax):

Print "Today is the ", Left$(Date$,6), ", and it is ", Left$(Time$, 5), CrLf$, "and the name of this file is ", CL$(0), CrLf$

Output:
Today is the 27.03., and it is 19:37
and the name of this file is C:\Masm32\test.exe

Date$, Time$ and CL$() use all a different fraction of the fat buffer, because the slot pointer advances.
Below the Print line, the memory is no longer needed. Still, it does not get overwritten in this moment, but rather after another 1000+ lines of such code, when the slot pointer restarts from the beginning of the fat buffer.

This is not for permanent global memory, but within the scope of a procedure it is perfectly feasible and safe, and about a factor 1000 faster than HeapAlloc/HeapFree pairs. I use it a lot, actually.

But it's not the same as a full-fledged memory manager, of course.
Title: Re: reanimate a perished fish
Post by: czerny on March 28, 2013, 12:29:00 PM
Ok, I want to describe one way to use a COM library (ADO in this example).

This way uses midl.exe, so the PSDK has to be installed.

1. create an empty directory, say 'test'
2. in the directory 'test' invoke 'midl msado15.idl'
3. copy the files 'msado15_i.c' and 'msado15.h' to your project
4. browse the header file for the available interfaces and use that

modifyed example from timovjl:
Code: [Select]
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wchar.h>
#include <ole2.h>
#include <stdio.h>
#include "msado15.h"

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")

WCHAR *szAppName = L"MSADOTest";

int main(int argc, char **argv)
{
HRESULT hr;
//_Connection *pConn = NULL;
Connection15 *pConn = NULL;

hr = CoInitialize(NULL);
//hr = CoCreateInstance(&CLSID_Connection, NULL, CLSCTX_ALL, &IID__Connection, (void **)&pConn);
hr = CoCreateInstance(&CLSID_Connection, NULL, CLSCTX_ALL, &IID_Connection15, (void **)&pConn);
if (hr)
MessageBox(0, L"Error CoGetClassObject()", szAppName, 0);
else
{
// STDMETHOD(Open)(THIS,BSTR,BSTR,BSTR,LONG);
//hr = pConn->lpVtbl->Open(pConn, NULL, NULL, NULL, -1);
//hr = pConn->lpVtbl->Open(pConn, L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb;", NULL, NULL, -1);
hr = pConn->lpVtbl->Open(pConn, L"Driver={Microsoft Access Driver (*.mdb)};Dbq=test.mdb;", NULL, NULL, -1);
if (!hr)
{
LONG nState;
pConn->lpVtbl->get_State(pConn, &nState);
pConn->lpVtbl->Close(pConn);
wprintf(L"nState = %d\n", nState);
}
else wprintf(L"Error in Open() : %Xh\n", hr);

pConn->lpVtbl->Release(pConn);
pConn = NULL;
}
CoUninitialize();
return 0;
}

You have to create an empty database 'test.mdb' to use the example.
As you can see: you have not to deal with UUIDs and you have not to define INITGUID nor to include <guiddef.h>.
That's all handeled by 'msado15_i.c'.

In the above example both interfaces '_Connection' and 'Connection15' are working. I don't know the difference.
There are also two different connection strings which also both are working.

The header file 'msado15.h' is to big for the ide.

It would be nice, if some people could try these steps and report.

czerny
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 28, 2013, 01:36:25 PM
Here is empty test.mdb for testing.
Quote
In the above example both interfaces '_Connection' and 'Connection15' are working. I don't know the difference
Look at msado15.h, Connection15 is for older version 1.5 level.
Title: Re: reanimate a perished fish
Post by: czerny on March 29, 2013, 08:13:30 PM
timovjl! Here is the testproject.
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 30, 2013, 08:19:21 AM
In msado15.h Connection15 first interface method after Invoke should be
STDMETHOD(get_Properties)(THIS,Properties **);
It is inherited from _ADO
_Connection is inherited from _ADO and Connection15 (use OleView to look at)
and last method is
STDMETHOD(Cancel)(THIS);

After you copy corrected Connection15 to _Connection you can use it too.
Any experts to confirm this ???

New example in ADOTest4.zip using msado20.h generated from msado20.tlb
Title: Re: reanimate a perished fish
Post by: czerny on March 30, 2013, 01:28:42 PM
Timovjl,

ComTypeLibList produces an empty declaration with 'msado15.dll':
Code: [Select]
#undef INTERFACE
#define INTERFACE ADOConnectionConstruction
DECLARE_INTERFACE(ADOConnectionConstruction) {
/* IUnknown methods */
};

#ifdef COBJMACROS
#endif /* COBJMACROS */
now the header file is much longer. It's very difficult to understand, what you have changed (and way).
Title: Re: reanimate a perished fish
Post by: TimoVJL on March 30, 2013, 01:43:07 PM
now the header file is much longer.
Convert that file to ANSI, that program generate UNICODE file.
Quote
It's very difficult to understand, what you have changed (and way).
Modified ComTypeLibWorker code generate that header.

Quote
ComTypeLibList produces an empty declaration with 'msado15.dll':
Just comment that out.

This is missing from that generated header msado15.h
Code: [Select]
#ifdef _WIN64
typedef LONGLONG ADO_LONGPTR;
#else
typedef LONG ADO_LONGPTR;
#endif
Error code list: http://support.microsoft.com/kb/209050
Title: Re: reanimate a perished fish
Post by: czerny on March 30, 2013, 01:56:25 PM
now the header file is much longer.
Convert that file to ANSI, that program generate UNICODE file.
I have done it before. But even so the difference is 91K to 137K.
Quote
It's very difficult to understand, what you have changed (and way).
Modified ComTypeLibWorker code generate that header.
I will have a look.

btw. ComTypeLibList produces the same empty struct with 'msado20.tlb'.
AdoText4 works for me.
Where do you find the error list?

czerny
Title: Re: reanimate a perished fish
Post by: czerny on May 18, 2013, 12:27:38 PM
I have made a little excerpt from the pool implementation of c2lib, just to get the idea. In the original there is much more.
jj2007: I want to get back to our discussion about a circular buffer.

Have you inspected the pool example (pool.zip) above?
Title: Re: reanimate a perished fish
Post by: czerny on May 18, 2013, 02:53:24 PM
This is a MS example in using WMI translated to Pelles C.
Title: Re: reanimate a perished fish
Post by: jj2007 on May 18, 2013, 06:05:44 PM
jj2007: I want to get back to our discussion about a circular buffer.
Have you inspected the pool example (pool.zip) above?

I've had a look. From what I see (and apologies if my eyes are not very good at understanding C ;))...
void *PoolMalloc(POOL P, size_t n)
..
   ptr = malloc(n);

... you are using malloc for every slot. This is different from my MasmBasic pool, which allocates one 640000 bytes buffer from which every user picks the next available slot until the end is reached, and at this point the next available one is slot #1 again. And previous content gets merciless discarded.

Your version, if I understand it correctly, provides permanent slots, but it uses malloc for each slot. That is more flexible but malloc is slow, of course.

My version is blazing fast for many small allocations (say: 10-20 cycles to get a pointer to an empty buffer) but it is naturally limited to 1. small buffers up to 640/4kB, 2. to allocations that are temporary, e.g. the buffer for a
Print Str$(n), " allocations", CrLf$, "are the limit"
Once it's printed, you don't need it any more.
Similar, you can use
Let MyString=Str$(n)+" allocations"+CrLf$+"are the limit"
and again, Str$() would use a temporary slot in the circular buffer. But MyString is a permanent heapalloc'ed slot.

But I don't want to advertise anything here. What are your intentions, what is your philosophy?
Title: Re: reanimate a perished fish
Post by: czerny on May 18, 2013, 07:05:52 PM
As I wrote, 'pool' is an essential part of c2lib. At first a wanted to discuss alternative memory managment techniques, with the c2lib 'pool' as an example.

In the meantime, due to your understanding of that what a pool is, i was thinking about circular buffers. This was a little excursus.

Now I would like to come back to the original theme.
Title: Re: reanimate a perished fish
Post by: jj2007 on May 18, 2013, 09:35:51 PM
At first a wanted to discuss alternative memory managment techniques, with the c2lib 'pool' as an example.

There is some documentation of the 'pool' here (http://doc.dvgu.ru/devel/c2lib/):
Quote
Advanced used of pools

So far we have only touched upon pools, and it may not be clear in the examples above why they don't in fact leak memory. There appears to be no deallocation being done, which is quite counter-intuitive to most C programmers!

Pools are collections of related objects (where an "object" is some sort of memory allocation).

I may be wrong, but this sounds as if pools are simply arrays of malloc handles that can "deleted in one go just by calling delete_pool(3)". That makes sense for multi-threaded apps which must clean memory at the end of a thread (although a simple loop freeing all entries in the array of handles could do the job, too).

Re memory managers: Ultrano has invested a lot into this question, google for Ultrano SmallAlloc to see some refs.

P.S.: I've made some simple benchmarks HeapAlloc vs circular buffer, the latter is about a factor 40 faster. Whether that matters in real life apps is another question - one Million MAX_PATH allocations take less than a second on my trusty notebook, HeapFree included.
Title: Re: reanimate a perished fish
Post by: czerny on May 19, 2013, 02:11:56 PM
I may be wrong, but this sounds as if pools are simply arrays of malloc handles ...
No, there is only one malloc (with further reallocs if needed).
Title: Re: reanimate a perished fish
Post by: DMac on May 20, 2013, 07:17:06 PM
Ever since I discovered the simple circular buffer employed by BCX I've used it for any application where I needed to manipulate a lot of strings of arbitrary length.  (One such project is the script generator for POINST wizard which can be downloaded from the Smorgasbordet site.)

Code: [Select]
static LPTSTR BCX_TmpStr(size_t Bites)
{
    static INT StrCnt;
    static LPTSTR StrFunc[2048];
    StrCnt = (StrCnt + 1) & 2047;
    if (StrFunc[StrCnt])
        free(StrFunc[StrCnt]);
    return StrFunc[StrCnt] = (LPTSTR)calloc(Bites + 128, sizeof(TCHAR));
}

Here is the BCX Join() method for easily concatinating any number of substrings that uses the circular buffer.

Code: [Select]
static LPTSTR Join(UINT nStrings, ...)
{
    register int i = nStrings, tmplen = 0;
    register LPTSTR str;
    register LPTSTR strtmp;
    va_list marker;
    va_start(marker, nStrings); // Initialize variable arguments
    while (i-- > 0)
    {
        str = va_arg(marker, LPTSTR);
        tmplen += _tcslen(str);
    }
    strtmp = BCX_TmpStr(tmplen);
    va_end(marker); // Reset variable arguments
    i = nStrings;
    va_start(marker, nStrings); // Initialize variable arguments
    while (i-- > 0)
    {
        str = va_arg(marker, LPTSTR);
        _tcscat(strtmp, str);
    }
    va_end(marker); // Reset variable arguments
    return strtmp;
}

The only thing that one has to remember when using this kind of thing is that all returned strings are temporary.  Any that you want to keep you need to copy to an allocated buffer before they are garbage collected.


Title: Re: reanimate a perished fish
Post by: jj2007 on May 20, 2013, 10:47:02 PM
Nice example, DMac. Can the inputs also come from the circular buffer, as in the example below? How would you code that message box in BCX?

Code: [Select]
include \masm32\MasmBasic\MasmBasic.inc
Init
MsgBox 0, Cat$(String$(11, "*")+CrLf$+Hex$(123h)+"="+Tb$+\
Str$(123h)+CrLf$+Hex$(123)+"="+Tb$+Str$(123)+CrLf$+\
String$(11, "*")), "Hi", MB_OK
Exit
end start

Output:
***********
0123=   291
7B=     123
***********
Title: Re: reanimate a perished fish
Post by: czerny on May 20, 2013, 11:09:31 PM
Ever since I discovered the simple circular buffer employed by BCX I've used it for any application where I needed to manipulate a lot of strings of arbitrary length.
Sorry, I do not see the point.
The circular buffer consist only of pointers. You have one calloc per call.In your example you could replace your BCX_TmpStr with calloc and free after the last loop.
Title: Re: reanimate a perished fish
Post by: DMac on May 21, 2013, 03:32:47 AM
The circular buffer consist only of pointers. You have one calloc per call.In your example you could replace your BCX_TmpStr with calloc and free after the last loop.
True, that wasn't the point.  BCX uses this for many basic style string manipulations so for instance I might do the following:
Code: [Select]
strSCRIPT = Join(3, strSCRIPT, "Append sub strings:",
Left(strPath, i), Right(strPath2, j));
In this case the call to Right will yield a temp string, Left will yield the next, and Join will yield a third comprised of the original plus three appended strings.  Eventually all of these will be freed but I can assemble an entire script easily without worrying about memory leaks and such, even if I am nesting allocations.
Title: Re: reanimate a perished fish
Post by: DMac on May 21, 2013, 03:42:29 AM
Nice example, DMac. Can the inputs also come from the circular buffer, as in the example below? How would you code that message box in BCX?

Code: [Select]
include \masm32\MasmBasic\MasmBasic.inc
Init
MsgBox 0, Cat$(String$(11, "*")+CrLf$+Hex$(123h)+"="+Tb$+\
Str$(123h)+CrLf$+Hex$(123)+"="+Tb$+Str$(123)+CrLf$+\
String$(11, "*")), "Hi", MB_OK
Exit
end start

Output:
***********
0123=   291
7B=     123
***********


BCX - the basic to C compiler by Kevin Diggins (Mr. BCX) is a basic syntax that gets translated to the C constructs.  So it's not all that differen't than what you have here.

I found it very helpful when I was learning C.  I could type in Basic for something I wanted to do and see how it translated.

Some things, like the circular buffer, I borrowed for a project or two when it made sense.  Mostly for parsers and script generators.