Windows Subclassing

Pages: 12
Thanks Ganado. Using "ss" rather than "&ss[0]" was the way I always used to create pointers to arrays. I didn't know if maybe that had changed.

Freddie1, if I recall, that awkward construct with pointers was once the only way to pass an array to a function. I'm guessing that the problem has been fixed, in the years since. I'll have to play with it a bit, to be sure.

I also recall, in the days of DOS, large arrays could only be created and accessed using far pointers. Arrays created using the standard notation, always had near pointers. Any array approaching 64k in size, caused "stack overflow". Often, the limit was far less than 64k, depending on the size and complexity of the program. That meant using far pointers, which also entailed the kind of messy pointer math that I mentioned. I am assuming that now, with 32-bit operating systems, that problem has been alleviated. Somewhere, I read that now, the limit is more like 2gig.

The Numerical Analysis class that I took, was more geared toward writing number-crunching software routines. It was part of the Engineering curriculum. In my old project, I had a function for calculating points along a cubic spline (four known data points). If you're interested, I could try to find that code, and post it here. The code is fairly simple.
About all I can say regarding the ss verses &ss{0] is that they are equivalent. To me, while the &ss[0] is more complicated and wordy, it is more clear, somehow. So I guess its a matter or personal opinion.

To the best of my knowledge, the whole 'near' verses 'far' issue is completely historical. Nonetheless, there are some issues. I'm glad to see there have been several other contributators to this posting, so that if I say something wrong, someone can correct me.

Here goes. Let's say in main() you dimension an arrray like so....

1
2
3
4
5
6
main()
{
  int iArray[1000000];
  .....
  .....
}


That array will use stack memory which will be far less than the dynamic memory a 32 bit operating system can provide. So if you are going to use large arrays, it would be preferable to make memory allocation calls, i.e., malloc() with C, or new[] with C++, or the way I do it, Win32 memory allocation functions such as the HeapAlloc() family. With Windows, these C or C++ built in functions will be implemented in any case with Win32 memory allocation functions. And in C or C++, there isn't much difference notationally with arrays between 'ordinary' arrays and pointers to arrays.

As I said in an earlier post, I don't use global variables much, so I'm not sure where the memory comes from if allocations are made at global scope. The symbols will go in the global/static program segment, but I'm not sure if it'll provide more memory than stack memory will. Maybe someone else can jump in here.

Memory just isn't a problem anymore. I do remember very well the situation with DOS. I really stayed with DOS too long; that's why I pretty much moved to 64 bit as soon as it came out. I learned my lesson. While I don't usually keep up with the latest Api functions, application frameworks, versions of Visual Studio, etc., I do believe in keeping up with the processor sizes I guess. As an aside, my holding onto DOS as long as I did really helped my employer a lot. When handheld data collectors first hit the forestry scene, they were all DOS based. With my knowledge of DOS I was able to provide data entry programs which folks really loved and used big time. They pretty much revolutionized data collection activities where I worked. When graphical OSs came out, i.e., Windows CE in the late 90s and early 2000s, I had one miserable time creating programs with equivalent functionality. It really took me years, and I attribute my knowledge of the Win32 Api to the years I spent 'in the trenches' trying to duplicate in Windows what I had done in low level DOS. I guess that's why I'm 'all over' the subclassing topic.

Don't go digging into your old code yet. You've given me likely the hint that I needed. Now I know there is a body of knowledge out there named 'numerical analysis' with terms such as 'spline'. I could likely do an internet search on it to get started. It was around 1995 when I was working on it; I had just got connected to the internet for the first time, and didn't know much about searching for information. I guess its amazing I got as far as I did with just a basic background in calculus and statistics. It worked me hard - I'll say that. So what is there basically - formulas that you plug points or coordinates into, and you end up with the 3rd or 4th order equation you need? Like I said, I used matrices to come up with the equation, then I had to code the programatic solution, then integrate it over four foot sections after rotating the thing around the x axis.

I'll post you some simple code to get you started with dynamic arrays. I'm sure it'll be helpful to you. Like I said, I needed some fairly complex array functionality for the work I did before I retired. The PowerBASIC programming language provided that until the creator of the language passed away while working on a 64 bit version of the compiler. I was in a 'lurch' for awhile, then decided to move all my coding over to C++. Had been with it for decades anyway in the embedded coding world. So I've pretty much been in the trenches with this stuff for years.

Last edited on
Thanks Freddie1. I did a little research on the current memory allocation functions. From what I read, "new[]" creates a global array. The only advantage I see to this, over straight-out creating a global array, is that the memory allocation is only temporary, as opposed to being allocated throughout the running of the program. Still, from what I have seen of these memory allocations, it gets back to the ugly pointer math that we discussed.

Though I don't have my notes handy, here is an approximation of what my arrays might look like:

1
2
3
struct V {double x, double y, double z};

struct V  A[160][16];


At at all times during the running of the program, at least two such arrays would be active, with the possibility of as many as four or five. Still, if my math is correct, that only adds-up to around 300K. The old DOS system used to choke on an eighth that much, even when allocating far (heap) memory.

Perhaps, to answer our questions on this, we should take it to a new thread? I would like to see the code you have on dynamic arrays

You're like me. I stayed with DOS well into the late 90's. I knew that, eventually, I would have to switch-over to Windows. But, I just couldn't find a good text on learning Windows. Petzold's Fourth Edition was out by then. Yet, I don't recall ever seeing it in the bookstores. It must have been in such a demand, that it was perpetually sold-out.

Good luck finding info on how to create spline functions. The info on "The Net" as all long, drawn-out explanations of theory and mechanics. They never seem to get to the actual, practical functions.
I'll post some stuff tomorrow. I was thinking of making a tutorial on it.

Its been a real 'trip' for me learning Windows coding after coming out of the DOS world. My first Windows coding was with Visual Basic 4, 5, and 6, circa 1995 - 2000. These were awesome creations. They created executables, which, however, were large - generally 1 to 2 megabytes. Then I discovered Petzold with his Windows 95 book. I started learning that using C, as I had learned C earlier with Borland's DOS C products. But Petzold never really covered COM/OLE (Microsoft's Component Object Model, also, Object Linking & Embedding). Petzold's C programs never used the really high powered and fancy Active X Controls that Visual Basic used so well - I'm talking really fancy visual interfaces. During that time in the middle 90s I also discovered PowerBASIC. Amazingly, Petzold's programs transferred amazingly over to PowerBASIC, and in fact, some PowerBASIC coders had translated all his programs from his Win 95 book. So in the late 90s I was burning the midnight oil learning Win32 SDK coding in both PowerBASIC and C. What I thought was so cool were the small executable files created by PowerBASIC and C using straight Win32 techniques. I had assumed any Windows program had to be large as a result of my formative Visual Basic experience. And actually, the PowerBASIC programs were only about a third of the size of C ones, and ran just as fast.

The problem with C and PowerBASIC though was that there was no way to use the high powered and fancy Active X / OLE technology which Visual Basic used. Actually, there was a way, but required such advanced knowledge that I doubt that more than several dozen coders in the world knew how to do it. The C/C++ way of using these advanced visual interfaces was through C++ - not C, and the use of C++ Class Frameworks such as Borland's 'Object Windows Library' -OWL, or Microsoft's MFC - Microsoft Foundation Classes. At that time in the 90s I tried to learn these, but I failed. You know, I don't think I failed so much as a result of being too dumb to grasp it; I believe I failed because I hated it. Having seen how elegant the Win32 Api was, I found C++ Class Framework code truly ugly, awkward, and simply appalling. At that time I threw everything out. I threw C++ out, I threw C++ Class Frameworks out, and reverted to C and PowerBASIC. I used C for my data recorder programming with Windows CE, and I used PowerBASIC (using Petzold's exact techniques) for my desktop coding.

And then something amazing happened. A genius showed up in the PowerBASIC Community of coders named Jose Roca, and he showed everyone how to implement the whole COM/OLE technology in PowerBASIC using all very low level techniques! I'd say it took me like a decade to figure everything out to my satisfaction, and eventually I picked up C++ again and figured out how to do everything related to COM/OLE in C++ using all low level code with no help from C++ template libraries, Classs Frameworks, or anything like that that C++ coders use. Good thing too! I needed to move my main coding endeavors over to C++ due to the passing away of the creator of PowerBASIC before a 64 bit compiler was created. Next step for me was to figure out how to build my code in C++ without all the bloat typical of C++ executables in Windows. PowerBASIC executables were usually a half to a third the size of C++'s. I just couldn't live with that bloat. To fix it I had to build my own C Runtime, which I call TCLib (Tiny C Lib). That took me years. To get small executables I couldn't use anything in the C++ section of the C++ Standard Library, so whatever functionality I needed from there (String Classes, Multi-Dimensional Array Capability, etc., I had to build myself. So yea, its been a haul. Sorry if I'm burdening you with my personal stuff. I'll post a useful Dynamic Multi-Dimensional Templated Array Class tomorrow, along with some starter programs showing how to use it.
Last edited on
I can't seem to start a new topic here, but I do seem to be able to reply. So I'll post here...

An Easy Way To Solve Problem Of Dynamic Multi-Dimensional Arrays In C++

To solve a problem one must first identify the problem. Here is the problem - MultiDim1.cpp....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// cl MultiDim01.cpp /O1 /Os /MT
// g++ MultiDim01.cpp -oMultiDim01.exe -mconsole -m64 -Os -s
// 16,384 bytes x64 GCC (TDM GCC 4.8 Series)
#include <cstdio>

int main()
{
 int iDim_1,iDim_2,iDim_3,iDim_4;

 iDim_1=3, iDim_2=4, iDim_3=5, iDim_4=6;  // 3 Rows, 4 Cols, 5 Deep, 6 ??? (don't know what to call 4th Dim!)
 int iArray[iD1][iD2][iD3][iD4];          // Create Array
 iArray[2][3][4][5]=123456789;            // Set some random element
 printf("iArray[2][3][4][5] = %d\n",iArray[2][3][4][5]); // Output Value
 getchar(); 
 
 return 0;   
}

#if 0
MultiDim01.cpp
MultiDim01.cpp(12) : error C2057: expected constant expression
#endif 


The above code tries to create a four dimensional int array with 3 rows, 4 columns, 5 deep (high), and 6 as the 4th dimension - however one wants to visualize or name it. It attempts to be dynamic, i.e., variables rather than constants are used for the four dimensions. This comports with many coding situations where the array sizes needed are only known at run time. This program won't build with any version of Microsoft's compiler tool chain with which I'm familiar. If one attempts to simplify it by using just one simple dimension....

1
2
3
4
5
6
7
8
9
10
11
// cl BadArray.cpp
#include <cstdio>

int main()
{
 int iNumber=5;
 int iArray[iNumber];
 return 0;
}

// BadArray.cpp(7) : error C2057: expected constant expression 


...the result is the same as seen by the compiler's error message. Interestingly, both these programs build without any problems with GCC's build system, at least with the somewhat dated TDM GCC 4.8 series on this old laptop I'm using. Not only that, MultiDim01.cpp runs correctly. Ignoring Microsoft's compiler (we don't have much choice), lets try to make some progress with this program, and see if we can pass our four dimensional dynamic int array iArray() to a function with GCC's C++ compiler - g++.exe.....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ MultiDim01A.cpp -oMultiDim01A.exe -mconsole -m64 -Os -s
#include <cstdio>

void Test(int Ar[][][][])
{
 printf("Ar[2][3][4][5] = %d\n",Ar[2][3][4][5]); 
}

int main()
{
 int iDim_1,iDim_2,iDim_3,iDim_4;

 iDim_1=3, iDim_2=4, iDim_3=5, iDim_4=6;  // 3 Rows, 4 Cols, 5 Deep, 6 ??? (don't know what to call 4th Dim!)
 int iArray[iD1][iD2][iD3][iD4];          // Create Array
 iArray[2][3][4][5]=123456789;            // Set some random element
 printf("iArray[2][3][4][5] = %d\n",iArray[2][3][4][5]); // Output Value
 Test(iArray[][][][]);
 getchar(); 
 
 return 0;   
}


MultiDim01A.cpp:4:24: error: declaration of 'Ar' as multidimensional array must have bounds for all dimensions except the first

So lets try that....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// g++ MultiDim01A.cpp -oMultiDim01A.exe -mconsole -m64 -Os -s
#include <cstdio>

void Test(int Ar[][4][5][6])
{
 printf("Ar[2][3][4][5] = %d\n",Ar[2][3][4][5]); 
} 

int main()
{
 int iDim_1,iDim_2,iDim_3,iDim_4;

 iDim_1=3, iDim_2=4, iDim_3=5, iDim_4=6;      // 3 Rows, 4 Cols, 5 Deep, 6 ??? (don't know what to call 4th Dim!)
 int iArray[iDim_1][iDim_2][iDim_3][iDim_4];  // Create Array
 iArray[2][3][4][5]=123456789;                // Set some random element
 printf("iArray[2][3][4][5] = %d\n",iArray[2][3][4][5]); 
 Test(iArray[][4][5][6]);
 getchar(); 
 
 return 0;   
}


// MultiDim01A.cpp:17:14: error: expected primary-expression before ']' token Test(iArray[][4][5][6]); 


I've tried a lot more convolutions of this in my attempts to get a C++ function to accept and understand a multi-dimensional dynamic array, but to save space, my reader's time, and a lot of hide and torn hair I'll refrain from posting any more attempts. Note however, that I'm aware of the C/C++ language technique of considering multi-dimensional arrays as arrays of arrays of a smaller dimension. Clearly though, there's not only a syntax problem here. There's a real problem here for the C/C++ coder.

It's a little hard to understand this, especially considering the fact that C and C++ have always been considered some of the most versatile and powerful programming languages. These are the languages professionals use, and they are used to build operating systems. Basic family languages, on the other hand, are largely viewed poorly by C/C++ practitioners, but in the specific case of PowerBASIC - a Windows programming language with which I'm most familiar, there are no problems whatsoever in dealing elegantly with dynamic multi-dimensional arrays. In the PowerBASIC code below I attempt to utilize an int (Long using PowerBASIC variable naming conventions) dynamic array named iArray() of four dimensions with the array sizing subscripts being 100 for 1st dimension, 100 for 2nd dimension, 100 for 3rd dimension, and 100 for 4th dimension. A C or C++ coder might be inclined to believe this will create a 100 x 100 x 100 x 100 = 100,000,000 element array, but in PowerBASIC the array creating subscript represents the upper bound of the array - not the number of elements of that specific dimension of the array - and like in C or C++ the lower bound is zero. So the array below actually has 101 x 101 x 101 x 101 = 104,060,401 elements, and since each element is four bytes, the dynamic memory allocation from the heap will be 416,241,604 bytes.

Let's take a brief look at the code. Like in MultiDim01A.cpp above, I have a function ArrayOutput(...) with an array parameter specified like so....

ByRef iAr() As Long

The ByRef keyword is optional. By default, PowerBASIC pushes the address of a variable on the stack at the point of a function call, whereas C/C++ family languages push the value at that address on the stack. While passing an array as a function parameter the language does not require any information be provided on the number of dimensions of the array. I believe the language allows up to 8 dimensions. As seen above, the array symbol simply requires an empty set of parentheses after the symbol, i.e.,...

iAr() As Long

I'll have more to say about the significance of this in a bit. Anyway, here is the full code, followed by the compiler output, and then the output from the running program....

continued....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#Compile Exe
#Dim     All

Function ArrayOutput(ByRef iAr() As Long) As Long
  Function = iAr(1,1,1,1)
End Function

Function PBMain() As Long
  Local iArray() As Long
  Local iDim_1 As Long, iDim_2 As Long, iDim_3 As Long, iDim_4 As Long
  Local iArrayAttr As DWord

  ' 100,000,000 Longs = 400,000,000 Bytes (approximately)
  iDim_1 = 100 : iDim_2 = 100 : iDim_3 = 100 : iDim_4 = 100
  Redim iArray(iDim_1, iDim_2, iDim_3, iDim_4) As Long
  iArray(1,1,1,1)=1
  iArrayAttr = ArrayAttr(iArray(),4) ' 4 signifies to return number of elements of array
  Con.Print "Number of Elements of iArray() = " iArrayAttr
  iArrayAttr = ArrayAttr(iArray(),5) ' 5 signifies to return element size of array
  Print "Element Size of iArray         = " iArrayAttr
  Print
  Print "LBound(iArray(1))              = " LBound(iArray(1))
  Print "LBound(iArray(2))              = " LBound(iArray(2))
  Print "LBound(iArray(3))              = " LBound(iArray(3))
  Print "LBound(iArray(4))              = " LBound(iArray(4))
  Print
  Print "UBound(iArray(1))              = " UBound(iArray(1))
  Print "UBound(iArray(2))              = " UBound(iArray(2))
  Print "UBound(iArray(3))              = " UBound(iArray(3))
  Print "UBound(iArray(4))              = " UBound(iArray(4))
  Print
  Print "Varptr(iArray(0,0,0,0)         = " Varptr(iArray())
  Print "iArray(1,1,1,1)                = " iArray(1,1,1,1)
  Print "ArrayOutput(iArray())          = " ArrayOutput(iArray())
  Console.Print
  Erase iArray()
  Waitkey$

  PBMain=0
End Function

#If 0

PowerBASIC 6 Console Compiler
Copyright (c) 1998-2011 PowerBasic Inc.
Englewood, Florida USA
All Rights Reserved

Primary source:  C:\Code\PwrBasic\PBCC6\Anachron\Test002.bas   {63 total lines}
Target compilation:  Test002.EXE
Compile time:  0.1 seconds, at 75600 lines/minute

1664 bytes compiled code, 8296 bytes RTLibrary,
520 bytes string literals, and 3188 bytes dgroup.
Executable stack size:  1048576 bytes.
Disk image: 14336 bytes   Memory image: 12004 bytes.

Component Files:
----------------
C:\Code\PwrBasic\PBCC6\Anachron\Test002.bas

Code Extracted:
---------------
<NONE>

Number of Elements of iArray() =  104060401
Element Size of iArray         =  4

LBound(iArray(1))              =  0
LBound(iArray(2))              =  0
LBound(iArray(3))              =  0
LBound(iArray(4))              =  0

UBound(iArray(1))              =  100
UBound(iArray(2))              =  100
UBound(iArray(3))              =  100
UBound(iArray(4))              =  100

Varptr(iArray(0,0,0,0)         =  1637800
iArray(1,1,1,1)                =  1
ArrayOutput(iArray())          =  1

#EndIf 


How about if I 'Let the cat out of the bag' and explain how this program does what the C++ programs above couldn't do? The answer can be summed up with the term 'metadata'. Near the top of the PBMain() function are these lines....

1
2
3
4
iArrayAttr = ArrayAttr(iArray(),4) ' 4 signifies to return number of elements of array
Con.Print "Number of Elements of iArray() = " iArrayAttr
iArrayAttr = ArrayAttr(iArray(),5) ' 5 signifies to return element size of array
Print "Element Size of iArray         = " iArrayAttr


...and in there you'll see the symbols twice like so....

ArrayAttr(.....)

This is a built in 'internal' PowerBASIC function in the same way sizeof() is in C or C++ (PowerBASIC also has a sizeof() built in function). What it does is return 'metadata' type information stored by the runtime system on the specific attributes of the dynamic array in question. The parameters of the ArrayAttr() function are the name of the array, and an equate/define specifying the particular attribute to be retrieved by the runtime system. The console output above from these function calls indicates the number of elements of the four dimensional array is 104,060,401 and the size of each element is four bytes. Further examining the output above reveals some other built in PowerBASIC functions, e.g., LBound() and UBound(). These functions return from the runtime system the lower and upper boundaries of the dynamic array by specific dimension, i.e., 1st, 2nd, 3rd, and 4th. Let's think about that.

continued....

What's going on here (that isn't going on in the failed C++ programs first shown) can be summed up with the generic term 'array descriptor'. The PowerBASIC language, and I believe most other implementations of Basic, utilize this concept of an 'array descriptor' to provide the runtime system with information on arrays being utilized by the program. That's why my ...

Function ArrayOutput(ByRef iAr() As Long) As Long

...does not specify that a four dimensional array is the single parameter of this function. Had the parameter been for a single dimension array or an eight dimension array it would have been specified the same. In concluding, the program then successfully outputs through the function the random value set in PBMain() for element iArray(1,1,1,1).

At this point many readers here in this forum will think I'm picking on C family languages for not being able to do something that basic family languages can easily do. That's not at all the case. C++ is my preferred programming language. Let me explain.

In both C and C++ this can be easily done. I'll not cover the C solution here (contact me directly if interested), but the C++ solution is relatively easy. PowerBASIC is a Windows only programming language. C and C++ are systems programming languages and the designers of the languages decided to allow solutions to this issue to be part of the Standard Libraries - not part of the core language. Its my understanding that the C++ specific part of the Standard Libraries has a solution to this problem. Also, 3rd party libraries containing high quality and free code exist. I personally do not use these libraries, and here I'll provide and explain my own specific solution. Some readers might wonder why I don't use the language's provided solutions in the Standard Library. I'll say a few words on that. I've coded my own C Runtime which allows me to create 3.5 K stand alone x64 console executables and 4 K GUIs. I build with the /nodefaultlib compiler switch which prevents the linker from using LIBC, LIBCMT, or whatever Microsoft provided library Microsoft is presently using. Its just an idiosyncrasy of mine which I can't defend. I fully realize that with today's systems with near infinite memory it serves no purpose. But it makes me feel good and that's my only justification. I'll say this though. I'm not the only one who appreciates small, tight code. I first learned of this technique in the 1990s from none other than Microsoft's Matt Pietrek in a Microsoft Systems Journal article where Matt had an 'Under The Hood' column where he covered esoteric advanced topics. His article was entitled....

Reduce EXE and DLL Size with LIBCTINY.LIB

My TCLib, which I worked on for years, likely still has a few lines of Matt's code. I had to make many modifications to allow for additional functionality, wide character strings, and x64. However, I can't use anything C++ specific from the C/C++ Standard Library with my TCLib so that's why I created my own 'homemade' templated C++ Class based solution to the multi-dimensional array problem. So that's the vein in which I'm providing this code. Some may find it useful. Others may wish to use functionality within the C++ Standard Library or other libraries.

So, returning to the issue of dynamic multi-dimensional arrays, the C++ solution is going to have to involve something similar to PowerBASIC's 'array descriptor' functionality. One simply can't write code such as my first presented MultiDim01A.cpp file where I have a function taking an array parameter where there is no way for the function to have any idea how large the array is, how many dimensions it has, etc. So, the C++ solution is a C++ Array Class. I said above that there is a solution for C, but that I wasn't going to talk about it. I changed my mind a bit. The reason is that perhaps the reader will understand this stuff better if he or she understands my evolution of thought on the topic.

I first had to deal with this topic in C doing Windows CE SDK style coding, i.e., Win32 Api. I had always taken basic language dynamic arrays as a given without thinking much about how the functionality was implemented. But I needed this functionality in C so I had to give it serious thought and develop it myself.

In terms of low level operating system functionality there is no such thing as a multi-dimension array. The operating system will give an application memory in terms of the base address of the allocation and that allocation will be good and run on linearly until it is no longer good and ends. In other words, its something like a single dimension array of bytes. Multi-dimension arrays are a concocted abstract construct which allows a coder to think more clearly about solving a problem where perhaps a tabular row/column, three or higher geometric dimensional scheme come into play. Its all fabricated by using row/column type indexes to index into the linear address space of a memory allocation. For example, suppose we have this two dimensional tabular construct of three rows and five columns....

Columns
----------------
0 1 2 3 4
----------------
Row 0 0 1 2 3 4
Row 1 5 6 7 8 9
Row 2 10 11 12 13 14

This data would be stored linearly in computer memory, and one possible representation could be like so....

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

For that representation a formula could be easily constructed to retrieve out of the linear address space the row/col index of the piece of data desired. For example, if one wanted the data at zero based Row #2 and zero based Column #1 the formula would be...

Row * Number_Of_Columns + Column.

So for this case Row #2 and Column #1 it would be,,,

2 x 5 + 1 = 11

So our data would be at the 11th zero based cell in the linear address space. An addressing scheme such as this can be created for an array of any number of higher dimensions from three on up. Using this technique in C is how one can create multi-dimensional abstractions with the help of C macros, i.e. the #define preprocessor directive. This is indeed how I solved the problem in C.

In C++ one can create a C++ Class having as a private member variable a pointer to a memory allocation for some specified variable type. Then one can create the abstraction needed by our little algebraic equation such as described above to index into the linear address space provided by a memory allocation. Further, one can overload the () parentheses symbols to provide functionality as seen in the PowerBASIC program allowing for constructs such as this....

iArray(1,2,3,4) ...

...as opposed to this....

iArray[1][2][3][4].

continued...
Last edited on
Here is a little C++ program with a Class which will handle int arrays of one or two dimensions. The program uses our 3 X 5 two dimensional array as presented just above....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// cl IntArray_1.cpp
#include <cstdio>

class CArray
{
 public:   
 CArray(size_t i) : pInts(0)              // One Dimensional Array Constructor
 {
  this->iNumInts=i;
  this->pInts=new int[this->iNumInts];
  d1=i, d2=0;
 }
 
 CArray(size_t i, size_t j) : pInts(0)    // Two Dimensional Array Constructor
 {
  this->iNumInts=i*j;
  this->pInts=new int[this->iNumInts];
  d1=i, d2=j;
 }
 
 int& operator()(size_t i)                // One Dimensional Accessor (Overloads '()' Symbols)
 {
  return pInts[i];
 }
 
 int& operator()(size_t i, size_t j)      // Two Dimensional Accessor (Overloads '()' Symbols)

 {
  return pInts[i*d2 + j];
 }
 
 ~CArray()
 {
  if(this->pInts)
     delete [] this->pInts;
 }

 public:
 int*   pInts;       // pointer to the base memory allocation for array
 size_t iNumInts;    // We'll need this to zero memory for non-class types
 size_t d1;          // Dimension #1
 size_t d2;          // Dimension #2
};

void Output(CArray& iArray)
{
 for(size_t i=0; i<iArray.d1; i++)
 {
     for(size_t j=0; j<iArray.d2; j++)
         printf("%d\t",iArray(i,j));
     printf("\n");
 }
}

int main()
{
 int iDim_1, iDim_2, iCtr;
  
 iCtr   = 0;
 iDim_1 = 3;
 iDim_2 = 5;
 CArray iAr(iDim_1,iDim_2);
 for(size_t i=0; i<iDim_1; i++)
     for(size_t j=0; j<iDim_2; j++)
         iAr(i,j)=iCtr++;
 Output(iAr);
 getchar();
 
 return 0;
}

#if 0
Output
======

0       1       2       3       4
5       6       7       8       9
10      11      12      13      14
#endif 


Pretty simple and not much to it, right? Note my function above....

 
void Output(CArray& iArray);


It takes a reference parameter to a CArray object, and like in my PowerBASIC app presented above, from looking at the function signature one would not be able to glean how many dimensions there are to this CArray object. However, the CArray class has the metadata contained within it describing the underlying memory allocation, and the overloaded () operator members translate this linear address space to a two dimensional configuration for the user.

But we're not home yet. There's more to it. Good as it is, it can be made much, much better. The problem with the above is that one would need to create such a class for every variable type intrinsic to the C++ language, and for every structure or class object which one decides to use as an array. Observe that the above class only works for ints. If one were to go about doing that one would soon find that each class creation would be very repetitive in that it would be the same symbols over and over again the only difference being the variable declarations, e.g., unsigned ints, short ints, doubles, etc. This is exactly the situation for which C++ templates were created. Here is my first example program using the template, CArray_01.cpp. But first CArray.h.....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// CArray.cpp
#ifndef CArray_h
#define CArray_h
#define NEW new


template <class datatype> class CArray                // This entity is a templated class for creating dynamic
{                                                     // multi-dimensional arrays of from one to four dimensions.
 public:                                              // It allows for a basic language type syntax for doing
 CArray() : pObjs(0)   // Uninitialized Constructor   // the above.  GNU C++ compilers implement dynamic array
 {                                                    // allocation but MS VC++ compilers don't.  Since I wanted
  this->iNumObjects=0;                                // to test compile with both I developed this class.
  d1=d2=d3=d4=0;
 }

 
 CArray(size_t i) : pObjs(0)                                  // One Dimensional Array Constructor
 {
  d1=i+1, d2=0, d3=0, d4=0;   
  this->iNumObjects=d1;
  this->pObjs = NEW datatype[this->iNumObjects]();
 }

 
 CArray(size_t i, size_t j) : pObjs(0)                        // Two Dimensional Array Constructor
 {
  d1=i+1, d2=j+1, d3=0, d4=0;
  this->iNumObjects=d1*d2;
  this->pObjs = NEW datatype[this->iNumObjects]();
 }

 
 CArray(size_t i, size_t j, size_t k) : pObjs(0)              // Three Dimensional Array Constructor
 {
  d1=i+1, d2=j+1, d3=k+1, d4=0;  
  this->iNumObjects=d1*d2*d3;
  this->pObjs = NEW datatype[this->iNumObjects]();
 }

 
 CArray(size_t i, size_t j, size_t k, size_t l) : pObjs(0)    // Four Dimensional Array Constructor
 {
  d1=i+1, d2=j+1, d3=k+1, d4=l+1;  
  this->iNumObjects=d1*d2*d3*d4;
  this->pObjs = NEW datatype[this->iNumObjects]();
 }
 
 
 bool Dim(size_t i)
 {
  if(this->pObjs)
     delete [] this->pObjs;
  d1=i+1, d2=0, d3=0, d4=0;
  this->iNumObjects=d1;
  this->pObjs=NEW datatype[this->iNumObjects]();
  if(this->pObjs)
     return true;
  else
     return false;
 }

 
 bool Dim(size_t i, size_t j)
 {
  if(this->pObjs)
     delete [] this->pObjs;
  d1=i+1, d2=j+1, d3=0, d4=0;;
  this->iNumObjects=d1*d2;
  this->pObjs=NEW datatype[this->iNumObjects]();
  if(this->pObjs)
     return true;
  else
     return false;
 }

 
 bool Dim(size_t i, size_t j, size_t k)
 {
  printf("Are We Getting In Here???\n");   
  if(this->pObjs)
     delete [] this->pObjs;
  d1=i+1, d2=j+1, d3=k+1, d4=0;  
  this->iNumObjects=d1*d2*d3;
  this->pObjs=NEW datatype[this->iNumObjects]();
  printf("j  = %Iu\n",j);
  printf("d2 = %Iu\n",d2);
  if(this->pObjs)
     return true;
  else
     return false;
 }
 
  
 bool Dim(size_t i, size_t j, size_t k, size_t l)
 {
  if(this->pObjs)
     delete [] this->pObjs;
  d1=i+1, d2=j+1, d3=k+1, d4=l+1;
  this->iNumObjects=d1*d2*d3*d4;
  this->pObjs=NEW datatype[this->iNumObjects]();
  if(this->pObjs)
     return true;
  else
     return false;
 }
 
  
 bool Redim(size_t i, bool blnPreserve)
 {
  datatype* pObj=NULL;
  
  d1=++i; 
  pObj=NEW datatype[i]();
  if(pObj)
  {
     if(blnPreserve)
     {   
        size_t iObs=0;
        if(i<this->iNumObjects)
           iObs=i;
        else          
           iObs=this->iNumObjects;
        for(size_t i=0; i<iObs; i++)
        {    
            pObj[i]=this->pObjs[i];
        }    
     }
     if(this->pObjs)
        delete [] this->pObjs;
     this->pObjs=pObj;
     this->iNumObjects=i;
     return true;
  }
  else
     return false; 
    
  return false;  
 }

 
 datatype& operator()(size_t i)                               // One Dimensional Accessor
 {
  return pObjs[i];
 }

 
 datatype& operator()(size_t i, size_t j)                     // Two Dimensional Accessor
 {
  return pObjs[i*d2 + j];
 }

 
 datatype& operator()(size_t i, size_t j, size_t k)           // Three Dimensional Accessor
 {
  return pObjs[i*d2 + j + k*d1*d2];
 }

 
 datatype& operator()(size_t i, size_t j, size_t k, size_t l) // Four Dimensional Accessor
 {
  return pObjs[i*d2 + j + k*d1*d2 + l*d1*d2*d3];
 }

 
 bool blnMemoryIsGood()
 {
  return !!pObjs;
 }

 
 int UBound(int iDim)
 {
  if(iDim==1)
     return d1-1;
  if(iDim==2)
     return d2-1;
  if(iDim==3)
     return d3-1;
  if(iDim==4)
     return d4-1;
  else
     return 0;
 }

 datatype* getptr()
 {
  return pObjs;
 }

 
 ~CArray()
 {
  if(this->pObjs)
     delete [] this->pObjs;
 }

 
 private:
 datatype*    pObjs;       // pointer to the base memory allocation for array
 size_t       iNumObjects; // We'll need this to zero memory for non-class types
 size_t       d1;          // Dimension #1
 size_t       d2;          // Dimension #2
 size_t       d3;          // Dimension #3
 size_t       d4;          // Dimension #4
};
#endif 


continued....
A few words about managing templates and files within your programs. They're pretty strange. I first started learning templates around 2000 with VC6 I believe. I don't know if anything has changed since then, but one couldn't use typical C/C++ file organization with them. In CArray.h above I have the whole class implementation within the CArray.h file. I don't typically do that, but I haven't been successful organizing my template files differently. I've been told to put everything in *.cpp files. But that just seems to weird to me to #include *.cpp files.

Let's try it out with CArray_01.cpp....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// CArray_01.cpp
// cl CArray_01.cpp /O1 /Os /MT
// cl CArray_01.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
//  3,584 Bytes x64 TCLib ansi
// 60,416 Bytes x64 MS LIBCMT ansi
//#define TCLib
#ifdef TCLib
   #include <windows.h>
   #include "stdio.h"
#else
   #include <cstdio>
#endif 
#include "CArray.h"

int main()
{
 CArray<int> iArray(5);        // Create A 32 Bit Single Dimension Dynamic Integer Array With 6 Elements - Zero Through Five
 
 printf("iArray.UBound(1)=%d\n\n",iArray.UBound(1));   // Print Upper Bound Of Array, i.e., Five
 for(int i=0; i<=iArray.UBound(1); i++)                
     iArray(i)=i;                                      // Use () - Not []
 for(int i=0; i<=iArray.UBound(1); i++)
     printf("iArray(%d)=%d\n",i,iArray(i)); 
 getchar();
 
 return 0;
}

#if 0
C:\Code\VStudio\VC19\Test1>cl CArray_01.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

CArray_01.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:CArray_01.exe
CArray_01.obj
TCLib.lib
kernel32.lib
user32.lib

C:\Code\VStudio\VC19\Test1>CArray_01
iArray.UBound(1)=5

iArray(0)=0
iArray(1)=1
iArray(2)=2
iArray(3)=3
iArray(4)=4
iArray(5)=5

C:\Code\VStudio\VC19\Test1>
#endif 


Pretty simple, really. The only strange part is this expression '<int>' in....

CArray<int> iArray(5);

As you might expect, that's template syntax for specifying that the instantiation will be for ints. And the array will contain 5 elements - 0 through 4.

Note I included a CArray::UBound() member function which gives similiar functionality to what we saw in the PowerBASIC program. Moving right along, let's move on to CArray_02.cpp....


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// CArray_02.cpp
// cl CArray_02.cpp /O1 /Os /MT /EHsc
// cl CArray_02.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
//  3,584 Bytes x64 TCLib ansi, Visual Studio 2008 
// 60,416 Bytes x64 MS LIBCMT ansi
// #define TCLib
#ifdef TCLib
   #include <windows.h>
   #include "stdio.h"
   #include "string.h"
#else
   #include <cstdio>
  #include  <string>
#endif 
#include "CArray.h"

struct SomeStruct
{
 int  iNumber;
 char szSomeText[32];
};

int main()
{
 CArray<SomeStruct> StructArray(5);                // C++ Template Notation - Templates Have weird Notation 
 char szBuffer[16];                                // Work Buffer, More Or Less, For Low Level C String Work
 
 for(size_t i=0; i<=StructArray.UBound(1); i++)    // Iterate Through SomeStruct Array
 {
     StructArray(i).iNumber=i*5;                   // Set Some Random Data
     sprintf(szBuffer,"Element #%d",i);            // I'll Use Low Level C Based String Buffer Manipulation
     strcpy(StructArray(i).szSomeText,szBuffer);   // ditto
     printf                                        // Output The Data
     (
      "%d\tStructArray(%d).iNumber = %d\tStructArray(i).szSomeText = %s\n",
      i,
      i,
      StructArray(i).iNumber,
      StructArray(i).szSomeText
     );
 }
 printf("\n\nHit Any Key To Continue....");
 getchar();
 
 return 0;
}

#if 0

C:\Code\VStudio\VC19\Test1>cl CArray_02.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

CArray_02.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:CArray_02.exe
CArray_02.obj
TCLib.lib
kernel32.lib
user32.lib

C:\Code\VStudio\VC19\Test1>CArray_02
0       StructArray(0).iNumber = 0      StructArray(i).szSomeText = Element #0
1       StructArray(1).iNumber = 5      StructArray(i).szSomeText = Element #1
2       StructArray(2).iNumber = 10     StructArray(i).szSomeText = Element #2
3       StructArray(3).iNumber = 15     StructArray(i).szSomeText = Element #3
4       StructArray(4).iNumber = 20     StructArray(i).szSomeText = Element #4
5       StructArray(5).iNumber = 25     StructArray(i).szSomeText = Element #5


Hit Any Key To Continue....

#endif 


Here the instantiation was for a struct named SomeStruct, and as one can see it works just fine. Now lets look at something that for me at least is quite confusing, and that is the technique of passing a template created object to a function. I'll use the same program as above, but for the console output we'll pass the CArray object by reference to an output function. Here is CArray_03.cpp....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// CArray_03.cpp
// cl CArray_03.cpp /O1 /Os /MT /EHsc
// cl CArray_03.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
//  3,584 Bytes x64 TCLib ansi, Visual Studio 2008 
// 60,928 Bytes x64 MS LIBCMT ansi
// #define TCLib
#ifdef TCLib
   #include <windows.h>
   #include "stdio.h"
   #include "string.h"
#else
   #include <cstdio>
  #include  <string>
#endif 
#include "CArray.h"

struct SomeStruct
{
 int  iNumber;
 char szSomeText[32];
};


template <typename t1> void DoWork(CArray<t1>& ss)
{
 char szBuffer[16];                                // Work Buffer, More Or Less, For Low Level C String Work
 
 for(size_t i=0; i<=ss.UBound(1); i++)             // Iterate Through SomeStruct Array
 {
     ss(i).iNumber=i*5;                            // Set Some Random Data
     sprintf(szBuffer,"Element #%d",i);            // I'll Use Low Level C Based String Buffer Manipulation
     strcpy(ss(i).szSomeText,szBuffer);            // ditto
     printf                                        // Output The Data
     (
      "%d\tss(%d).iNumber = %d\tss(i).szSomeText = %s\n",
      i,
      i,
      ss(i).iNumber,
      ss(i).szSomeText
     );
 } 
}

 
int main()
{
 CArray<SomeStruct> StructArray(5);                // C++ Template Notation - Templates Have weird Notation 
 
 DoWork(StructArray);
 printf("\n\nHit Any Key To Continue....");
 getchar();
 
 return 0;
}

/*
0       ss(0).iNumber = 0       ss(i).szSomeText = Element #0
1       ss(1).iNumber = 5       ss(i).szSomeText = Element #1
2       ss(2).iNumber = 10      ss(i).szSomeText = Element #2
3       ss(3).iNumber = 15      ss(i).szSomeText = Element #3
4       ss(4).iNumber = 20      ss(i).szSomeText = Element #4
5       ss(5).iNumber = 25      ss(i).szSomeText = Element #5


Hit Any Key To Continue....
*/


To be continued.....
Topic archived. No new replies allowed.
Pages: 12