Advertisement
3_2004-2005 Miscellaneous #143457

Don't Point That At Me!!! - Pointers in VB5/6 (Update 2)

Sick and tired of all those nay-saying C/C++ programmers who tell you that VB is weak because it doesn't have support for Pointers? Silence them by learning the dark art of pointers in VB. Ok, so that's a wee bit dramatised, but this article will help you work out how to use pointers in VB 5 and 6. -- Update 1 -- Modified the CSS styles to (hopefully) make it a little more readable on PSC -- Update 2 -- Changed the wording on one line a bit to make it easier to read (thanks go to José Pablo Ramírez Vargas for that suggestion)

AI

AI Summary: This codebase represents a historical implementation of the logic described in the metadata. Our preservation engine analyzes the structure to provide context for modern developers.

Source Code
original-source
<style type="text/css">.doc{font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;font-size:0.9em;}.doc_title1{font-weight:bold;font-size:2em;text-align:center;margin-top:10px;}.doc_title2{font-weight:bold;font-size:1.5em;text-align:center;}.doc_title3{font-weight:bold;font-size:1em;text-align:center;margin-bottom:20px;}.doc_h1{font-weight:bold;font-size:120%;border-bottom:solid2px;padding-top:20px;}.doc_h2{font-weight:bold;color:#008800;padding-top:10px;margin-left:0px;}.dblk{margin-left:5px;margin-right:0px;padding-top:10px;}.doc_term{color:#008800;font-weight:bold;}.dcod{border:1px inset;background-color:#DDDDDD;font-family:"Courier New",Courier,monospace;font-size:0.8em;margin-top:8px;margin-bottom:8px;padding:3px;}.dccm{color:#008000;}.dckw{color:#000080;}.dchr{border-bottom:solid 1px #808080;margin-bottom:10px;}.dcil{font-family:"Courier New",Courier,monospace;font-size:1em;}.doc_bignote{background-color:#000000;color:#FF0000;font-decoration:underline;font-size:3em;font-weight:900;padding:10px;border:solid 5px #FF0000;}</style><font size="1">Note: this document contains CSS formatting. Unfortunetly, the &lt;STYLE&gt; tag is outside the &lt;HEAD&gt; tags, so I can't guaruntee it'll work. If it doesn't just download the offline HTML version, k?</font><div class="doc"><div class="doc_title1">Don't Point That At Me!!!</div><div class="doc_title2">Pointers in VB5/VB6</div><div class="doc_title3">Written by Daniel Keep</div><div class="doc_h1">0. Introduction</div><div class="dblk"><div class="dblk">Welcome one and all to my first PSC tutorial. I have made a few code posts before, but this is my first tutorial.</div><div class="dblk">In this tutorial, I will be looking at that coveted technique that C/C++ users are always telling us VB'ers cannot be done in our beloved language: pointers.</div><div class="dblk">Just incase you don't <em>know</em> what a pointer is, I've included a brief introduction to the subject.</div><div class="dblk">Before we go on, this is, dead serious, for an <em>Advanced</em> VB programmer only. If you are a newbie, GO AWAY NOW! I'm not trying to be mean, but you can seriously screw your system up if you get this wrong.</div><div class="dblk">As such, I'll just warn you that the intro. to pointers is not quite the best of quality. Also, this article assumes you know how your computer's memory is layed out to some extent (ie: a Long is 4 bytes, User Defined Types are linear in memory, array elements come one after another, etc.)</div><div class="dblk">Also, this article applies to VB5 and VB6 (I haven't tested in VB5, although as far as I know it'll work), but <emstrong>not</emstrong> in VB.NET. Why? Because Microsoft saw fit to remove these wonderous features from VB.NET. Bastards. Oh well, if you're actually using VB.NET, A) This is the wrong part of Planet Source Code and B) You can always write some C++ code, and compile that into your project (lucky buggers ;) ).</div><div class="dblk">So, read through, and please vote and leave a comment. Not worth five globes? Then tell me why so I can improve it.</div><div class="dblk">Lastly, if anyone knows of a good VB -> HTML syntax colourer, could you let me know: I did this entire article BY HAND... :(</div><div class="dblk">And so, with that, on with the show!</div></div><div class="doc_h1">1. What? I don't see anything over there...</div><div class="dblk"><div class="dblk">So, just what <em>is</em> a pointer, anyway? Well, a pointer (funnily enough) <em>points</em> to something.</div><div class="dblk">More specifically, it points to a place in memory. C/C++ programmers have been using them for eons for all kinds of advanced tricks. They allow them to use <strong>linked lists</strong>, <strong>dynamic link libraries</strong>, and more.</div><div class="dblk">The problem is, pointers aren't something that can really be supported by the API: it has to be supported by the programming language.</div><div class="dblk">Now, before I go off on how pointers in VB work, let's take a look at how POINTERS work.</div></div><div class="doc_h2">&nbsp;That's easy: stretch out your index finger and <em>point</em>! Duh.</div><div class="dblk"><div class="dblk">Umm... not quite. You see, a pointer is a number that referrs to a place in memory. It's a bit like a street address (and here is where I borrow a very nice explanation from someone else's tutorial... I forget where, but it was a tutorial on C++... if I remember where, I'll let you know...)</div><div class="dblk">Imagine that pointers <em>are</em> street addresses. Each address is unique, and points to a different house. Now, some houses you know by name (a variable name), and some are a block of flats (an array... actually I made that bit up :P). But every house has an address.</div><div class="dblk">Let's take a totally contrived example, and say there are five houses on a street, 1 Contrived St., 2 Contrived St., etc. We can also refer to each house by a name (Amy's house, Bob's house, Caity's house, David's house and Eroll's house). </div><div class="doc_h2">But that's silly...</div><div class="dblk">Be quiet. Now, you can refer to each house by it's address (1 Contrived St. thru 5 Contrived St.), or by it's name (Amy's house thru Eroll's house :P).</div><div class="dblk">Now, it's fairly easy to refer to them by name: you know the people who live there, and sometimes the name can even tell you what <em>type</em> of house it is (Bob's House of Glue, Eroll's House of Propane and Propane Accessories for example).</div><div class="dblk"> However, there may come a time when you need to know the house's address: maybe someone who doesn't know David wants to get to his house, or perhaps you want to store Caity's address in an organizer where you know twelve Caity's (it could happen!).</div><div class="dblk">Now, while the name is much easier to remember, the address is much more versatile. You can give it to other people, pizza delivery places, and the cops when they're having a rowdy party.</div><div class="dblk">The same goes for pointers. You can refer to a variable by it's name (m_iCount, g_matView), or by it's address (or pointer). While the name is handy, the address is much more useful.</div><div class="doc_h2">Yes, but how does that help me?</div><div class="dblk">I'm getting to that. Pointers can be used for all sorts of neat things. The Windows API uses them a lot to look at arrays, DirectX uses them a bit, and you can put them to use for things such as <strong>linked lists</strong>, copying private UDTs around, and even writing huge arrays of User Defined Types to disk.</div><div class="dblk">Ok, as far as things go, that was a pretty shocking intro to pointers. If anyone didn't understand it, post here and I'll try to explain it better (or if you can suggest any improvements, that'd be great :D).</div><div class="dblk">Now onto the real meat: how to do it in VB...</div><div class="doc_h2">2. Didn't your mother ever tell you it's rude to point?</div><div class="dblk">Now, here's the bit you've been waiting for: how to do pointers in VB.</div><div class="dblk">First up, we're going to need an API declaration. Just plonk this into a code module (or, you can make it private and put it in a Form or private Class):</div><div class="dcod"><div><span class="dckw">Public Declare Sub</span> CopyMemory <span class="dckw">Lib</span> &quot;kernel32&quot; <span class="dckw">Alias</span> &quot;RtlMoveMemory&quot; ( _<br> &nbsp;Destination <span class="dckw">As Any</span>, Source <span class="dckw">As Any</span>, ByVal <span class="dckw">Length As Long</span>)</div></div><div class="dblk">That particular API call tells Windows to copy a chunk of memory from one place in memory to another. Since VB lacks any native support for pointers, that's how we'll shuffle our memory about.</div><div class="dblk">The next thing you need to know about are the following internal methods: </div><div class="dcod"><span class="dccm">' VarPtr() - Returns the address of a variable</span><br> l_pAddress = VarPtr(l_lMyLong)<br><br><span class="dccm"><font color="#008000">' VarPtrArray() - Returns the address of the array's SAFEARRAY structure</font></span><br> l_pAddress = VarPtrArray(l_dMyDoubles())<br><br><span class="dccm"><font color="#008000">' StrPtr() - Returns the address of a string's BSTR structure</font></span><br> l_pAddress = StrPtr(l_sMyString)<br><br><font color="#008000">' StrPtrArray() - Returns the address of the string array's SAFEARRAY structure</font><br> l_pAddress = StrPtrArray(l_sMyStrings())<br><br><span class="dccm"><font color="#008000">' ObjPtr() - Returns the address of an object's interface</font></span><br> l_pAddress = ObjPtr(l_oMyObject)</div><div class="dblk">One thing you should take note of: none of these will show up in the VB Object Browser, or in the IntelliSense list. And, for <span class="dcil">VarPtrArray()</span> and <span class="dcil">StrPtrArray()</span>, you will actually need to define these yourself (more on that later), but trust me, they're there :)</div><div class="dblk">Now, let's look at each of these in turn, how to use them, and what they do.</div><div class="doc_h2">3. VarPtr() - &quot;Look ma! A var!&quot; &quot;A var? Whar?&quot; &quot;Thar!&quot;</div><div class="dblk">Ok, worst title for a chapter, <em style="text-decoration:underline;">ever</em>.</div><div class="dblk">VarPtr() is the first of our pointer methods. This little puppy works by returning the address in memory of a named variable. In the above example, <span class="dcil">VarPtr(l_lMyLong)</span> would return the address of <span class="dcil">l_lMyLong</span>. This is by far the easiest to use. Now, what can we do with this? Below is an example that shows you how to copy from one Integer into another.</div><div class="dcod"><span class="dccm">' First, create our variables, and display their values</span><br><span class="dckw">Dim</span> l_iNumber1 <span class="dckw">As Integer</span>, l_iNumber2 <span class="dckw">As Integer</span><br><span class="dckw">Dim</span> l_pNumber1 <span class="dckw">As Long</span>, l_pNumber2 <span class="dckw">As Long</span><div><br> Randomize Timer<br> l_iNumber1 = Int(Rnd * 1000)<br> l_iNumber2 = Int(Rnd * 1000)</div><div><br> MsgBox &quot;Our numbers are &quot; &amp; l_iNumber1 &amp; &quot; and &quot; &amp; l_iNumber2</div><div><span class="dccm"><br> ' Here we will copy the contents of l_iNumber1 to l_iNumber2</span><br> l_pNumber1 = VarPtr(l_iNumber1)<br> l_pNumber2 = VarPtr(l_iNumber2)</div><div><br> CopyMemory <span class="dckw">ByVal</span> l_pNumber2, <span class="dckw">ByVal</span> l_pNumber1, 2&amp;</div><div><span class="dccm"><br> ' Display the results</span><br> MsgBox &quot;Our numbers are now &quot; &amp; l_iNumber1 &amp; &quot; and &quot; &amp; l_iNumber2</div></div><div class="dblk">Ok, so what does all that mean?</div><div class="dblk">The first chunk (before the second comment) just defines two Integers, and gives them random values. It then displays them to prove we're not being tricky in any way.</div><div class="dblk">Just take note of <span class="dcil">l_pNumber1</span> and <span class="dcil">l_pNumber2</span>: these are our pointers. You'll notice that integers are &quot;<span class="dcil">l_i*</span>&quot; and pointers are &quot;<span class="dcil">l_p*</span>&quot;. The &quot;<span class="dcil">l_</span>&quot; just means it's a local variable (declared inside our current method). The &quot;i&quot; means it's an integer, and the &quot;p&quot; means it's a pointer. It's <em>very</em> important to make sure we know these are pointers: VB doesn't have a pointer data type, and we have to use Longs (memory addresses are 32-bits -- the same length as a Long). We don't want to accidentally start using those Longs in arithmetic!</div><div class="dblk">This brings up an important note:<br></div><div class="doc_bignote">UNDER <em>NO</em> CIRCUMSTANCES USE <em>ANY</em> FORM OF ARITHMETIC ON <em>ANY</em> POINTER, UNLESS YOU KNOW WHAT YOU'RE DOING!</div><div class="dblk">Sorry to have to do that, but it's extremely important. If, for some unexplainable reason, you happened to <em>use</em> arithmetic on a pointer, that pointer would become completely useless, and most probably very dangerous, so be careful. ;) Now, onto the next bit of code...</div><div class="dcod"><span class="dccm">' Here we will copy the contents of l_iNumber1 to l_iNumber2</span><br> l_pNumber1 = VarPtr(l_iNumber1)<br> l_pNumber2 = VarPtr(l_iNumber2)</div><div class="dblk">This bit of code is what gets the pointers to our Integers. It's just a simple call to <span class="dcil">VarPtr()</span>. Again, remember that <span class="dcil">l_pNumber1</span> and <span class="dcil">l_pNumber2</span> are Longs.</div><div class="dcod">CopyMemory <span class="dckw">ByVal</span> l_pNumber2, <span class="dckw">ByVal</span> l_pNumber1, 2&amp;</div><div class="dblk">This is the real guts of the code. This call copies the contents of memory pointed to by <span class="dcil">l_pNumber1</span> to the location that <span class="dcil">l_pNumber2</span> points to. Here's how it works:</div><div class="dblk">The first argument to <span class="dcil">CopyMemory()</span> is the destination. In this case, we've given it the pointer (or address) of our second number, <span class="dcil">l_iNumber2</span>. Take special notice of how we do this: we need to use the <span class="dcil"><span class="dckw">ByVal</span></span> keyword, otherwise VB will pass <span class="dcil">CopyMemory()</span> the address of our pointer: and that's not what we want. We want the pointer passed &quot;By Value&quot;, not &quot;By Reference&quot; (the default).</div><div class="dblk">The second argument is the source. In this case, we're passing the value of <span class="dcil">l_pNumber1</span>: the address of <span class="dcil">l_iNumber1</span>.</div><div class="dblk">The final argument is very important: it tells <span class="dcil">CopyMemory()</span> how many bytes of memory to copy. Since we're copying Integers, we want this to be 2. If you're wondering what the &quot;<span class="dcil">&amp;</span>&quot; is doing on the end, it just tells VB to store the number as a Long (that's what we want). It's just a little habit of mine :)</div><div class="dcod"><span class="dccm">' Display the results</span><br> MsgBox &quot;Our numbers are now &quot; &amp; l_iNumber1 &amp; &quot; and &quot; &amp; l_iNumber2</div><div class="dblk">This final line of code just displays the two numbers again to prove they really have been copied.</div><div class="doc_h2">Ok, could you have done that any harder???</div><div class="dblk">Now, as anyone who's used <span class="dcil">CopyMemory()</span> before will have noticed, we've gone the long way about it: we could have accomplished the above copy by using the following code instead:</div><div class="dcod"><div><span class="dccm"><br> ' First, create our variables, and display their values</span><br><span class="dckw">Dim</span> l_iNumber1 <span class="dckw">As Integer</span>, l_iNumber2 <span class="dckw">As Integer</span></div><div><br> Randomize Timer<br> l_iNumber1 = Int(Rnd * 1000)<br> l_iNumber2 = Int(Rnd * 1000)</div><div><br> MsgBox &quot;Our numbers are &quot; &amp; l_iNumber1 &amp; &quot; and &quot; &amp; l_iNumber2</div><div><span class="dccm"><br> ' Here we will copy the contents of l_iNumber1 to l_iNumber2</span><br> CopyMemory l_iNumber2, l_iNumber1, 2&amp;</div><div><span class="dccm"><br> ' Display the results</span><br> MsgBox &quot;Our numbers are now &quot; &amp; l_iNumber1 &amp; &quot; and &quot; &amp; l_iNumber2</div></div><div class="dblk">So why didn't we? The truth is that using <span class="dcil">CopyMemory()</span> like that relies on us knowing the name of the variable. Remember the analogy above with the houses? We can tell it the name of the house because we know it personally, but if we asked someone else to go to that house, we'd have to tell them the address. This can be illustrated in the code below, which swaps four Integers three times:</div><div class="dcod"><div class="dchr"><span class="dckw">Private</span> m_pPointer1 <span class="dckw">As Long</span><span class="dccm">' Our first pointer</span><br><span class="dckw">Private</span> m_pPointer2 <span class="dckw">As Long</span><span class="dccm">' Our second pointer</span></div><div><span class="dckw">Private Sub</span> TestSwap()</div><div><span class="dccm"><br> ' Create some integers</span><br><span class="dckw">Dim</span> l_iInt1%, l_iInt2%, l_iInt3%, l_iInt4% <span class="dccm">' That '%' is short for ' As Integer'.</span></div><div><span class="dccm"><br> ' Now, assign some random values</span><br> l_iInt1 = Int(Rnd * 1000)<br> l_iInt2 = Int(Rnd * 1000)<br> l_iInt3 = Int(Rnd * 1000)<br> l_iInt4 = Int(Rnd * 1000)</div><div><span class="dccm"><br> ' Dump them to the Immediate Window</span><br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;Before swap: &quot;, l_iInt1, l_iInt2, l_iInt3, l_iInt4</div><div><span class="dccm"><br> ' Swap them around</span><br> m_pPointer1 = VarPtr(l_iInt1): m_pPointer2 = VarPtr(l_iInt2): SwapInt<br> m_pPointer1 = VarPtr(l_iInt3): m_pPointer2 = VarPtr(l_iInt4): SwapInt<br> m_pPointer1 = VarPtr(l_iInt2): m_pPointer2 = VarPtr(l_iInt3): SwapInt</div><div><span class="dccm"><br> ' Dump them to Immediate again</span><br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;After swap: &quot;, l_iInt1, l_iInt2, l_iInt3, l_iInt4</div><div class="dchr"><span class="dckw"><br> End Sub</span></div><div><span class="dckw">Private Sub</span> SwapInt()</div><div><span class="dccm"><br> ' Create a temporary Integer, and a pointer to it</span><br><span class="dckw">Dim</span> l_iTemp%, l_pTemp&amp;<br> l_pTemp = VarPtr(l_iTemp) <span class="dccm">' Get the pointer</span></div><div><span class="dccm"><br> ' Swap them around</span><br> CopyMemory <span class="dckw">ByVal</span> l_pTemp, <span class="dckw">ByVal</span> m_pPointer1, 2&amp;<br> CopyMemory <span class="dckw">ByVal</span> m_pPointer1, <span class="dckw">ByVal</span> m_pPointer2, 2&amp;<br> CopyMemory <span class="dckw">ByVal</span> m_pPointer2, <span class="dckw">ByVal</span> l_pTemp, 2&amp;</div><div class="dckw"><br> End Sub</div></div><div class="dblk">Wow, what a lot of code, eh? Let's go through it.</div><div class="dcod"><span class="dckw">Private</span> m_pPointer1 <span class="dckw">As Long</span><span class="dccm">' Our first pointer</span><br><span class="dckw">Private</span> m_pPointer2 <span class="dckw">As Long</span><span class="dccm">' Our second pointer</span></div><div class="dblk">This just defines two pointers at the module level: both <span class="dcil">TestSwap()</span> and <span class="dcil">SwapInt()</span> can see them.</div><div class="dcod"><div><span class="dckw">Private Sub</span> TestSwap()</div><div><span class="dccm"><br> ' Create some integers</span><br><span class="dckw">Dim</span> l_iInt1%, l_iInt2%, l_iInt3%, l_iInt4% <span class="dccm">' That '%' is short for ' As Integer'.</span></div><div><span class="dccm"><br> ' Now, assign some random values</span><br> l_iInt1 = Int(Rnd * 1000)<br> l_iInt2 = Int(Rnd * 1000)<br> l_iInt3 = Int(Rnd * 1000)<br> l_iInt4 = Int(Rnd * 1000)</div><div><span class="dccm"><br> ' Dump them to the Immediate Window</span><br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;Before swap: &quot;, l_iInt1, l_iInt2, l_iInt3, l_iInt4</div></div><div class="dblk">As you would expect, just creates some integers, and dumps their values to the Immediate window. Nothing too special.</div><div class="dcod"><span class="dccm">' Swap them around</span><br> m_pPointer1 = VarPtr(l_iInt1): m_pPointer2 = VarPtr(l_iInt2): SwapInt</div><div class="dblk">Now <em>this</em> is interesting. Here, we swap two Integers using <span class="dcil">SwapInt()</span>, and yet we don't actually pass it anything. How? We use those pointers we declared earlier. Now I know this is very contrived, but it illustrates an important point: <span class="dcil">SwapInt()</span> doesn't need to know, nor does it care what the two Integers we are swapping are called. All it cares about is what's in the two pointers <span class="dcil">m_pPointer1</span> and <span class="dcil">m_pPointer2</span>. In this way, we simply need to change which variables the pointers point to to swap different Integers.</div><div class="dblk">I'm sure you can guess what the rest of <span class="dcil">TestSwap()</span> does, so let's take a look at <span class="dcil">SwapInt()</span>.</div><div class="dcod"><div><span class="dckw">Private Sub</span> SwapInt()</div><div><span class="dccm"><br> ' Create a temporary Integer, and a pointer to it</span><br><span class="dckw">Dim</span> l_iTemp%, l_pTemp&amp;<br> l_pTemp = VarPtr(l_iTemp) <span class="dccm">' Get the pointer</span></div></div><div class="dblk">Ok, this is pretty simple: we create a temporary Integer to use during the swap, and get a pointer to it. We will use this to hold the value of the first Integer while we exchange them.</div><div class="dblk">An interesting point is that we can't simply create a pointer, and use that. That's known as a <span class="doc_term">null pointer</span>. Null pointers are very bad: they are pointers that don't actually point to anything. If you use them, you just get an access error, invalid data, or worst of all, the dreaded <span class="doc_term">General Protection Fault</span>. Just as bad is if you have a pointer that points to data that doesn't exist anymore (we'll look at this in just a second...)</div><div class="dcod"><div><span class="dccm">' Swap them around</span><br> CopyMemory <span class="dckw">ByVal</span> l_pTemp, <span class="dckw">ByVal</span> m_pPointer1, 2&amp;<br> CopyMemory <span class="dckw">ByVal</span> m_pPointer1, <span class="dckw">ByVal</span> m_pPointer2, 2&amp;<br> CopyMemory <span class="dckw">ByVal</span> m_pPointer2, <span class="dckw">ByVal</span> l_pTemp, 2&amp;</div><div class="dckw"><br> End Sub</div></div><div class="dblk">This is easy enough: all we're doing is copying the value from the first Integer into our temporary variable, copying the value of the second Integer into the first, and copying the temporary value (the old value of the first Integer) into the second Integer. Quite simply: we swap the values. Again, note the &quot;<span class="dcil">2&amp;</span>&quot; on the end: telling <span class="dcil">CopyMemory()</span> how big your copy is is most important.</div><div class="dblk">Now, for those of you who've picked up the glaring mistake, good on you. For those of you who haven't, this is it: we are left with two, big, ugly, nasty <span class="doc_term">dangling pointers</span>. These are pointers that point to data that doesn't exist anymore. Take a look at the code... We declare two pointers at module level. Then, we fill them... three times... and then... uh oh... we forgot to clear them...</div><div class="doc_h2">So what?</div><div class="dblk">So... this is bad. If someone were to call <span class="dcil">SwapInt()</span> now, it would try to swap data that simply didn't exist anymore. Who knows, it might actually succeed. Then again, it might not. That's why it's always good practice to clear your pointers like so:</div><div class="dcod">m_pPointer1 = 0&amp;<br> m_pPointer2 = 0&amp;</div><div class="dblk">Another addition you can make is to <span class="dcil">SwapInt()</span>, telling it to check that the two pointers aren't null/clear/equal to 0. Of course, this will slow it down, but it's a safe precaution none the less.</div><div class="dblk">Ok, so we've covered <span class="dcil">VarPtr()</span>... now we move onto <span class="dcil">VarPtrArray()</span>...</div><div class="doc_h2">4. VarPtrArray() - &quot;Look ma! A var()!&quot; &quot;Oh shut up.&quot;</div><div class="dblk">Ok, now onto something a bit trickier. In order to understand this, it's neccecary to understand how VB stores it's Arrays.</div><div class="dblk">VB uses a structure called a SAFEARRAY. C/C++ programmers might be familiar with it, if not, here it is for the rest of us:</div><div class="dcod"><span class="dccm">' SAFEARRAY declaration borrowed from <br> ' http://www.experts-exchange.com/Programming/ <br> ' &nbsp;Programming_Languages/Visual_Basic/Q_20230969.html</span><br><br><span class="dckw">Type</span> SAFEARRAYBOUND <br> &nbsp; cElements <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Number of elements in this dimension</span><br> &nbsp; lLbound <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Lower bound of the dimension</span><br><span class="dckw">End Type </span><br><br><span class="dckw">Public Type</span> SAFEARRAY <br> &nbsp; cDims <span class="dckw">As Integer</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Count of dimensions in this array. </span><br> &nbsp; fFeatures <span class="dckw">As Integer</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Flags used by the SafeArray routines. </span><br> &nbsp; cbElements <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Size of an individual element of the array. </span><br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Does not include size of pointed-to data </span><br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' (ie: Strings, Objects) </span><br> &nbsp; cLocks <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Number of times the array has been </span><br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' locked without corresponding unlock. </span><br> &nbsp;&nbsp;pvData <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Pointer to the data. </span><br> &nbsp;&nbsp;rgsabound() <span class="dckw">As</span> SAFEARRAYBOUND &nbsp;<span class="dccm">' One bound for each dimension. This should </span><br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' have the same number of elements as cDims </span><br><span class="dckw">End Type</span></div><div class="dblk">Now, I won't go into what all that means, but suffice to say, that's how VB keeps tracks of how many elements are in your arrays, where the data is stored, how many dimensions, etc.</div><div class="dblk">Now, what <span class="dcil">VarPtrArray()</span> does is point you to this structure. You see, if you called the following:</div><div class="dcod">l_pArrayBase = VarPtr(l_iMyIntegerArray())</div><div class="dblk">Or</div><div class="dcod">l_pArrayBase = VarPtr(l_iMyIntegerArray)</div><div class="dblk">It simply wouldn't work.</div><div class="dblk">While we're at it, I'd like to point out that if an element existed at 0 (or any other index you specified),</div><div class="dcod">l_pAnInteger = VarPtr(l_iMyIntegerArray(0))</div><div class="dblk">would work. It would return the address of the 0'th element in the <span class="dcil">l_iMyIntegerArray</span> array. If you've played with Direct3D8, this is how most of the <span class="dcil">Draw*()</span> methods work: you pass the first element in your array as, since arrays are stored linearly in memory (element 1 immediately proceeds element 0), and you give it the number of elements, it can read your entire array without needing to understand SAFEARRAYS (which is why you can't just pass the array itself).</div><div class="dblk">Now, where were we? Ah yes. <span class="dcil">VarPtrArray()</span> allows you to read an array's SAFEARRAY structure, allowing you direct access to the array's memory. This is put to good use over at <a href="http://www.ur.co.nz/">Unlimited Realities</a>, where you will find some excellent <a href="http://www.ur.co.nz/urcorp/default.asp?pageid=9">general programming tutorials</a> and <a href="http://www.ur.co.nz/urcorp/default.asp?pageid=10">game programming tutorials</a> in VB, many of which use this technique (one even shows you how to use it to create a <a href="http://www.ur.co.nz/urcorp/default.asp?pageid=10&data_article=132">cool progressive fire effect</a> :) ).</div><div class="dblk">As it stands, you probably won't use this much, and I'd as soon not recommend you do unless you're up to something especially clever: there are a whole slew of methods you need to use to access SAFEARRAYs. I won't explain them here as A) It's beyond the scope of this article (gotta love <em>that</em> excuse! :D), and B) I wouldn't know the first thing about manipulating SAFEARRAYs. ;)</div><div class="dblk">But, if you are interested, check out the <a href="http://www.ur.co.nz/urcorp/default.asp?pageid=10&data_article=132">VBFlamer</a> and <a href="http://www.ur.co.nz/urcorp/default.asp?pageid=1&data_article=131">VBRipple</a> articles on <a href="http://www.ur.co.nz/">Unlimited Realities</a> (hey to all the Kiwis out there!)</div><div class="dblk">Now, you may be wondering where <span class="dcil">VarPtrArray()</span> has gone... did VB misplace it? No, but as I said above, you need to declare it. Below is the API declarations for VB5 and VB6.</div><div class="dcod"><div><span class="dckw">Declare Function</span> VarPtrArray <span class="dckw">Lib</span> &quot;msvbvm50.dll&quot; <span class="dckw">Alias</span> &quot;VarPtr&quot; _<br> &nbsp;(Var() <span class="dckw">As Any</span>) <span class="dckw">As Long</span><span class="dccm">' VB5 Declaration</span></div><div><span class="dckw"><br> Declare Function</span> VarPtrArray <span class="dckw">Lib</span> &quot;msvbvm60.dll&quot; <span class="dckw">Alias</span> &quot;VarPtr&quot; _<br> &nbsp;(Var() <span class="dckw">As Any</span>) <span class="dckw">As Long</span><span class="dccm">' VB6 Declaration</span></div></div><div class="dblk">Obviously, use the VB5 declaration if you're using VB5, and the VB6 declaration if you're using VB6.</div><div class="dblk">With that out of the way, let's progress onto...</div><div class="doc_h2">5. StrPtr() - How would you use string as a pointer when it's so floppy?</div><div class="dblk">Terrible jokes aside, <span class="dcil">StrPtr()</span> is one of the most useful pointer methods in VB. As you should all know by now, VB's string methods are<span style="letter-spacing: 60px;"><strong>&nbsp;slow</strong></span> (gotta love CSS :P). As a result, many people have resorted to writing their own, heavilly optimized String routines, and this is how they've done them.</div><div class="dcod"><div><span class="dckw">Dim</span> l_sString <span class="dckw">As String</span><span class="dccm">' Our String</span><br><span class="dckw">Dim</span> l_pBSTRData <span class="dckw">As Long</span><span class="dccm">' Pointer to the BSTR structure</span><br><span class="dckw">Dim</span> l_pCharData <span class="dckw">As Long</span><span class="dccm">' Pointer to the character data</span><br><span class="dckw">Dim</span> l_cChars() <span class="dckw">As Byte&nbsp;&nbsp;</span><span class="dccm">' The string's character data itself</span><br><br> l_sString = &quot;I inherited 32,000 miles of string. Unfortunetly, it's in three-inch lengths.&quot;<br> l_pBSTRData = VarPtr(l_sString) <span class="dccm">' Get our BSTR pointer</span><br> l_pCharData = StrPtr(l_sString) <span class="dccm">' Get our character data pointer</span><br> CopyMemory l_cChars(0&amp;), <span class="dckw">ByVal</span> l_pCharData, <span class="dckw">CLng(</span>Len(l_sString)<span class="dckw">) </span><span class="dccm">' Copy out the character data</span></div></div><div class="dblk">Now, what this code does is extract the actual character data from a string, and dump it into an array. You may be wondering why I didn't just use a loop and the <span class="dcil">Mid$()</span> method, and the truth is, this is <em>faster</em>. Let's take a look:</div><div class="dcod"><span class="dckw">Dim</span> l_sString <span class="dckw">As String</span><span class="dccm">' Our String</span><br><span class="dckw">Dim</span> l_pBSTRData <span class="dckw">As Long</span><span class="dccm">' Pointer to the BSTR structure</span><br><span class="dckw">Dim</span> l_pCharData <span class="dckw">As Long</span><span class="dccm">' Pointer to the character data</span><br><span class="dckw">Dim</span> l_cChars() <span class="dckw">As Byte&nbsp;&nbsp;</span><span class="dccm">' The string's character data itself</span></div><div class="dblk">Ok, this is pretty self-explanitory. We create a string, two pointers (we'll get onto BSTR in a sec.), and a buffer to hold our character data.</div><div class="dcod">l_sString = &quot;I inherited 32,000 miles of string. Unfortunetly, it's in three-inch lengths.&quot;</div><div class="dblk">Here we put something into our string. Very simple ;)</div><div class="dcod">l_pBSTRData = VarPtr(l_sString) <span class="dccm">' Get our BSTR pointer</span><br> l_pCharData = StrPtr(l_sString) <span class="dccm">' Get our character data pointer</span></div><div class="dblk">These two lines demonstrate the difference between <span class="dcil">VarPtr()</span> and <span class="dcil">StrPtr()</span> when used on a string. I'll talk about <span class="dcil">VarPtr()</span> in a moment, but for now just take note that we're putting the return value from <span class="dcil">StrPtr()</span> into <span class="dcil">l_pCharData</span>.</div><div class="dcod">CopyMemory l_cChars(0&amp;), <span class="dckw">ByVal</span> l_pCharData, <span class="dckw">CLng(</span>Len(l_sString)<span class="dckw">)</span> * 2&amp; <span class="dccm">' Copy out the character data</span></div><div class="dblk">Not a hard one, this copies the character data that <span class="dcil">l_pCharData</span> is pointing to into an array we can access, using the length of the string * 2 as the number of bytes.</div><div class="dblk">Now that's interesting... why * 2?? The answer is that VB stores it's strings as Unicode: 2 bytes per character. We'll get onto what this means in a moment, but first let's deal with that loose thread, BSTR...</div><div class="dblk">Now, as you saw in the above code, VarPtr() returns a pointer to something called BSTR. Like most things in VB, Strings are actually wrapped up in something called BSTR.</div><div class="dblk">BSTR is actually just a fancy name for a pointer. You see, when you play with strings in VB, you tend to change their length quite a lot, and computer's don't take well to a certain variable expanding and contracting a lot, so every time you resize a string, VB has to allocate a new character buffer, dump the new string data into the buffer and deallocate the old one. With all this shuffling of memory (which is almost NEVER in the same spot) how does VB still know where all the character data is? By using a BSTR.</div><div class="dblk">The actual value of the string variable is a four-byte Long, which acts as a pointer to the start of the character buffer. To help illustrate how this works, let's write a little bit of code that shows how a VB String is structured:</div><div class="dcod"><div><span class="dccm">' First, create some variables</span><br><span class="dckw">Dim</span> l_sString <span class="dckw">As String</span> &nbsp;&nbsp;<span class="dccm">' Our string</span><br><span class="dckw">Dim</span> l_pBSTR <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Pointer to the BSTR pointer</span><br><span class="dckw">Dim</span> l_pCharData <span class="dckw">As Long</span> &nbsp;&nbsp;<span class="dccm">' Pointer to the character data</span><br><span class="dckw">Dim</span> l_pStrPtr <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Stores the value of StrPtr()</span><br><span class="dckw">Dim</span> l_lLength <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Length of the string</span><br><span class="dckw">Dim</span> l_cUnicode() <span class="dckw">As Byte</span> &nbsp;<span class="dccm">' Unicode representation of our string</span><br><span class="dckw">Dim</span> l_cANSI() <span class="dckw">As Byte</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' ANSI representation of our string</span><br><span class="dckw">Dim</span> i&amp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Oh go on... guess...</span></div><div><span class="dccm"><br> ' Fill our string with some stuff</span><br> l_sString = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;</div><div><span class="dccm"><br> ' Now, grab the pointer to the BSTR structure</span><br> l_pBSTR = VarPtr(l_sString)</div><div><span class="dccm"><br> ' Next, grab the pointer to the character data</span><br> CopyMemory l_pCharData, <span class="dckw">ByVal</span> l_pBSTR, 4&amp;</div><div><span class="dccm"><br> ' Righteo. Now, for the heck of it, store what StrPtr() gives us</span><br> l_pStrPtr = StrPtr(l_sString)</div><div><span class="dccm"><br> ' Now we want to get the length of the string</span><br> CopyMemory l_lLength, <span class="dckw">ByVal</span> (l_pCharData - 4&amp;), 4&amp;</div><div><span class="dccm"><br> ' Next up, redimension our character buffer, and copy the string out</span><br><span class="dckw">ReDim</span> l_cUnicode(l_lLength - 1&amp;)<br> CopyMemory l_cUnicode(0&amp;), <span class="dckw">ByVal</span> l_pCharData, l_lLength</div><div><span class="dccm"><br> ' Right, now let's convert that to ANSI...</span><br><span class="dckw">ReDim</span> l_cANSI((l_lLength / 2&amp;) - 1&amp;)<br><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> (l_lLength / 2&amp;) - 1&amp;<br> &nbsp; l_cANSI(i) = l_cUnicode(i * 2&amp;)<br><span class="dckw">Next</span> i</div><div><span class="dccm"><br> ' All done: let's display the results</span><br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_sString:&quot;, l_sString<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_pBSTR:&quot;, l_pBSTR<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_pCharData:&quot;, l_pCharData<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_pStrPtr:&quot;, l_pStrPtr<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_lLength:&quot;, l_lLength<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_lLength/2:&quot;, (l_lLength) / 2&amp;</div><div><span class="dckw"><br> Debug</span>.<span class="dckw">Print</span> &quot;l_cUnicode:&quot;, ;<br><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> l_lLength - 1&amp;<br> &nbsp; <span class="dckw">Debug</span>.<span class="dckw">Print</span> Chr$(l_cUnicode(i));<br><span class="dckw">Next</span> i<br><span class="dckw">Debug</span>.<span class="dckw">Print</span></div><div><span class="dckw"><br> Debug</span>.<span class="dckw">Print</span> &quot;l_cANSI:&quot;, ;<br><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> (l_lLength / 2&amp;) - 1&amp;<br> &nbsp; <span class="dckw">Debug</span>.<span class="dckw">Print</span> Chr$(l_cANSI(i));<br><span class="dckw">Next</span> i<br><span class="dckw">Debug</span>.<span class="dckw">Print</span></div></div><div class="dblk">Ok, let's look at how this works...</div><div class="dcod"><div><span class="dccm">' First, create some variables</span><br><span class="dckw">Dim</span> l_sString <span class="dckw">As String</span> &nbsp;&nbsp;<span class="dccm">' Our string</span><br><span class="dckw">Dim</span> l_pBSTR <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Pointer to the BSTR pointer</span><br><span class="dckw">Dim</span> l_pCharData <span class="dckw">As Long</span> &nbsp;&nbsp;<span class="dccm">' Pointer to the character data</span><br><span class="dckw">Dim</span> l_pStrPtr <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Stores the value of StrPtr()</span><br><span class="dckw">Dim</span> l_lLength <span class="dckw">As Long</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Length of the string</span><br><span class="dckw">Dim</span> l_cUnicode() <span class="dckw">As Byte</span> &nbsp;<span class="dccm">' Unicode representation of our string</span><br><span class="dckw">Dim</span> l_cANSI() <span class="dckw">As Byte</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' ANSI representation of our string</span><br><span class="dckw">Dim</span> i&amp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dccm">' Oh go on... guess...</span></div><div><span class="dccm"><br> ' Fill our string with some stuff</span><br> l_sString = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;</div></div><div class="dblk">Here we're just creating our variables and initializing the string. Now just make note of the fact that that string is now 26 characters long.</div><div class="dcod"><span class="dccm">' Now, grab the pointer to the BSTR structure</span><br> l_pBSTR = VarPtr(l_sString)</div><div class="dblk">Ok, easy enough: this code returns the pointer to the BSTR structure of the string.</div><div class="dcod"><span class="dccm">' Next, grab the pointer to the character data</span><br> CopyMemory l_pCharData, <span class="dckw">ByVal</span> l_pBSTR, 4&amp;</div><div class="dblk">This might require a little thought: the BSTR structure that the variable <span class="dcil">l_sString</span> contains points to a character buffer that contains the actual string itself. What this code does is copy out the 4-byte (Long) value that <span class="dcil">l_pBSTR</span> is pointing to, and puts it into <span class="dcil">l_pCharData</span>. <span class="dcil">l_pCharData</span>, then, points to the character buffer.</div><div class="dcod"><span class="dccm">' Righteo. Now, for the heck of it, store what StrPtr() gives us</span><br> l_pStrPtr = StrPtr(l_sString)</div><div class="dblk">This code just saves the value of <span class="dcil">StrPtr()</span>, so we can look at it later. Nothing too special.</div><div class="dcod"><span class="dccm">' Now we want to get the length of the string</span><br> CopyMemory l_lLength, <span class="dckw">ByVal</span> (l_pCharData - 4&amp;), 4&amp;</div><div class="dblk">Obviously, this code returns the length of the string. The reason why we do it like this is that the length of the string is stored as a 32-bit (Long) value <em>immediately before</em> the character data. Why they do it like this, I have no idea, they just do.</div><div class="dblk">You may also have noticed that for the first argument I used &quot;<span class="dcil">l_lLength</span>&quot; instead of &quot;<span class="dcil">ByVal VarPtr(l_lLength)</span>&quot;. Both these accomplish the same thing: it just saves us from having to work out the pointer to <span class="dcil">l_lLength</span>.</div><div class="dcod"><span class="dccm">' Next up, redimension our character buffer, and copy the string out</span><br><span class="dckw">ReDim</span> l_cUnicode(l_lLength - 1&amp;)<br> CopyMemory l_cUnicode(0&amp;), <span class="dckw">ByVal</span> l_pCharData, l_lLength</div><div class="dblk">This code resizes the <span class="dcil">l_cUnicode</span> array so that it's large enough to fit our character data, and then copies the data in. Notice the &quot;<span class="dcil">l_lLength - 1&amp;</span>&quot; bit: this is because if we have a string of length 5, our array indicies go: 0, 1, 2, 3, 4: that's five characters.</div><div class="dcod"><span class="dccm">' Right, now let's convert that to ANSI...</span><br><span class="dckw">ReDim</span> l_cANSI((l_lLength / 2&amp;) - 1&amp;)<br><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> (l_lLength / 2&amp;) - 1&amp;<br> &nbsp; l_cANSI(i) = l_cUnicode(i * 2&amp;)<br><span class="dckw">Next</span> i</div><div class="dblk">This is something most people don't immediately realize: while VB only allows you to use ANSI strings, it stores them internally as Unicode strings. Why on Earth Microsoft did this (except possibly to waste memory) I have no idea, but it's really irrelevant: if we want to deal with the string directly, we need to deal with it as ANSI.</div><div class="dblk">Before you go off at me, yes it's possible to simply use a &quot;<span class="dcil"><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> l_Length - 1&amp; <span class="dckw">Step</span> 2&amp;</span>&quot; to read through the ANSI part of the string, but that becomes annoying if you want to process it with another procedure. It's often easier to make an ANSI copy, process it, and copy it back later (unless you're after the best possible speed, in which case it may be faster to leave it as Unicode).</div><div class="dblk">Finally, just take note that you can't resize the string: to do so you would not only need to change the stored length of the string, but you would need to allocate a new buffer, store it, and deallocate the old one, which you can't do directly. You need to let VB handle resizing the string.</div><div class="dcod"><div><span class="dccm">' All done: let's display the results</span><br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_sString:&quot;, l_sString<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_pBSTR:&quot;, l_pBSTR<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_pCharData:&quot;, l_pCharData<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_pStrPtr:&quot;, l_pStrPtr<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_lLength:&quot;, l_lLength<br><span class="dckw">Debug</span>.<span class="dckw">Print</span> &quot;l_lLength/2:&quot;, (l_lLength) / 2&amp;</div><div><span class="dckw"><br> Debug</span>.<span class="dckw">Print</span> &quot;l_cUnicode:&quot;, ;<br><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> l_lLength - 1&amp;<br> &nbsp; <span class="dckw">Debug</span>.<span class="dckw">Print</span> Chr$(l_cUnicode(i));<br><span class="dckw">Next</span> i<br><span class="dckw">Debug</span>.<span class="dckw">Print</span></div><div><span class="dckw"><br> Debug</span>.<span class="dckw">Print</span> &quot;l_cANSI:&quot;, ;<br><span class="dckw">For</span> i = 0&amp; <span class="dckw">To</span> (l_lLength / 2&amp;) - 1&amp;<br> &nbsp; <span class="dckw">Debug</span>.<span class="dckw">Print</span> Chr$(l_cANSI(i));<br><span class="dckw">Next</span> i<br><span class="dckw">Debug</span>.<span class="dckw">Print</span></div></div><div class="dblk">Ok, we're all done collecting data, so this bit just dumps all that to the Immediate Window. Just so you can see, the output on my machine was:</div><div class="dcod">l_sString: &nbsp;&nbsp;&nbsp;ABCDEFGHIJKLMNOPQRSTUVWXYZ<br> l_pBSTR: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#F00;">1243384</span><br> l_pCharData: &nbsp;&nbsp;<span style="color:#F00;">2192820</span><br> l_pStrPtr:&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:#F00;">2192820</span><br> l_lLength:&nbsp;&nbsp;&nbsp;&nbsp; 52 <br> l_lLength/2:&nbsp; &nbsp;26 <br> l_cUnicode: &nbsp;&nbsp;A B C D E F G H I J K L M N O P Q R S T U V W X Y Z <br> l_cANSI: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ABCDEFGHIJKLMNOPQRSTUVWXYZ</div><div class="dblk">The red values are ones that most probably won't be the same on your machine (the others should be).</div><div class="dblk">Now, the first thing to notice is that <span class="dcil">l_pCharData</span> and <span class="dcil">l_pStrPtr</span> have the same value. This illustrates an important point: if you want the address of the character data, you can either get the BSTR, and read the value stored at that, or you can just use <span class="dcil">StrPtr()</span>: they do the same thing.</div><div class="dblk">Next, is the value of <span class="dcil">l_lLength</span>. It's 52. But that can't be right... there's only 26 characters, right? Well, yes, but <span class="dcil">l_lLength</span> actually stores the length of the <em>character data</em>, <strong>not</strong> the number of characters in the string. This is especially important when dealing with Unicode which uses two bytes for every character. This is why we have to divide by 2 to get 26.</div><div class="dblk">Lastly, look at the difference between l_cUnicode and l_cANSI. Here you can see for yourself that Unicode uses two bytes per character. To get the ANSI string, all you need to do is take the first of every two bytes. Also, just incase you are wondering, those aren't spaces (0x20/Chr$(32)), but nulls (0x00/Chr$(0)); this is important when you're re-assembling a Unicode string that the extra bytes are null. Otherwise, you might end up with a Japanese string ;)</div><div class="dblk">Well, that's about all you need to know for StrPtr(). If you're interested in learning more on low-level VB String manipulation, I highly recommend the <a href="http://www.vb2themax.com/">VB2TheMax</a> article <a href="http://www.vb2themax.com/HtmlDoc.asp?Table=Articles&ID=30">Play VB's Strings</a> which deals with several methods for manipulating VB strings and Byte arrays.</div><div class="dblk">Well, with <span class="dcil">StrPtr()</span> out of the way, let's move onto the (more complex to setup than use) <span class="dcil">StrPtrArray()</span>...</div><div class="doc_h2">6. StrPtrArray() - Wouldn't an array of strings just be a net?</div><div class="dblk">Hmm... is it just me or are the chapter titles getting worse? :P</div><div class="dblk">Ok, this one's pretty simple, actually. It works exactly like VarPtrArray(), with one critical difference:</div><div class="dblk">When you pass a VB String to an API procedure (in fact, <em>any</em> procedure in an external DLL that asks for it ByVal), VB makes a temporary copy of the string, converts it to ANSI, and passes that.</div><div class="doc_h2">Your point being...?</div><div class="dblk">My point being that if you tried the following:</div><div class="dblk">l_pSAFEARRAY = VarPtrArray(l_sMyStrings())</div><div class="dblk">You would actually return the address of the SAFEARRAY of a temporary copy of the string in ANSI: not what we want (well, at least not in most cases).</div><div class="dblk">So how to we stop this from happening? Simple, we need to declare a new method.</div><div class="dblk">Now, this method is present in both the VB5 and VB6 runtimes, so it needs a seperate declaration for each, but unlike VarPtrArray(), we can't get to it using a normal Declare statement. Instead, we need to make ourselves a Type Library.</div><div class="dblk">A Type Library is just a file that tells a programming language what methods are stored in what DLLs. Since VB treats a Type Library method like a regular, run of the mill method, it doesn't do that fancy-pants conversion to ANSI when you pass a Type Library method a string.</div><div class="dblk">To create this Type Library, we need to write a script that will compile with the MIDL Type Library compiler. I'm not going to put you through the hell of learning how to do this, so I've included the neccecary code below:</div><div class="dblk">(These ODL files are taken from KB article <a href="http://support.microsoft.com/default.aspx?scid=KB;en-us;q199824">Q199824 - HOWTO: Get the Address of Variables in Visual Basic</a>)</div><div class="dblk">For VB6, place the following into a text file called VB6PtrLib.odl:</div><div class="dcod"><div>#define RTCALL _stdcall<br> [<br> uuid(C6799410-4431-11d2-A7F1-00A0C91110C3),<br> lcid (0), version(6.0), helpstring(&quot;VarPtrStringArray Support for VB6&quot;)<br> ]<br> library PtrLib<br> {<br> importlib (&quot;stdole2.tlb&quot;);<br> [dllname(&quot;msvbvm60.dll&quot;)]<br> module ArrayPtr<br> &nbsp;&nbsp;&nbsp;&nbsp;{<br> &nbsp;&nbsp;&nbsp;&nbsp;[entry(&quot;VarPtr&quot;)]<br> &nbsp;&nbsp;&nbsp;&nbsp;long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);<br> &nbsp;&nbsp;&nbsp;&nbsp;}<br> }</div></div><div class="dblk">And for VB5, put this into a file called VB5PtrLib.odl</div><div class="dcod"><div>#define RTCALL _stdcall<br> [<br> uuid(6E814F00-7439-11D2-98D2-00C04FAD90E7),<br> lcid (0), version(5.0), helpstring(&quot;VarPtrStringArray Support for VB5&quot;)<br> ]<br> library PtrLib<br> {<br> importlib (&quot;stdole2.tlb&quot;);<br> [dllname(&quot;msvbvm50.dll&quot;)]<br> module ArrayPtr<br> &nbsp;&nbsp;&nbsp;&nbsp;{<br> &nbsp;&nbsp;&nbsp;&nbsp;[entry(&quot;VarPtr&quot;)]<br> &nbsp;&nbsp;&nbsp;&nbsp;long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);<br> &nbsp;&nbsp;&nbsp;&nbsp;}<br> }</div></div><div class="dblk">You then need to compile it using MIDL, with this command line:</div><div class="dcod">MIDL /t VB6ptrlib.odl <br> MIDL /t VB5ptrlib.odl</div><div class="dblk">Obviously, just use the one you want to compile :)</div><div class="dblk">But then again, some people may simply look at that and go &quot;huh?&quot;, so I've included the .odl and compiled .tlb files along with the tutorial. Aren't I good to you people? :P</div><div class="dblk">Now, to use these Type Libraries, go into your Project's Refrences dialog. If the Type Library isn't on the list, hit &quot;Browse&quot;, and locate the appropriate .tlb file. Then, make sure it's checked, and hit Ok.</div><div class="dblk">Viola! You now have a <span class="dcil">StrPtrArray()</span> method. It works the same way as VarPtrArray(), so I won't go into the specifics here.</div><div class="doc_h2">7. ObjPtr() - &quot;I don't know what the fuss over Laser Pointers are. I've never seen a Laser variable before, let alone a pointer to one!&quot;</div><div class="dblk">Yup, definetly getting worse ;)</div><div class="dblk">This is the last pointer method we're going to deal with. This one deals with (funnily enough) Objects.</div><div class="dblk">This method works a bit like <span class="dcil">StrPtr()</span> does. You see, when you create an object like so:</div><div class="dcod"><span class="dckw">Dim</span> l_oMyObj <span class="dckw">As</span> MyObj<br><span class="dckw">Set</span> l_oMyObj = <span class="dckw">New</span> MyObj</div><div class="dblk">You are creating an instance of the MyObj class. But what happens when you do this:</div><div class="dcod"><span class="dckw">Dim</span> l_oMyOtherObj <span class="dckw">As</span> MyObj<br><span class="dckw">Set</span> l_oMyOtherObj = l_oMyObj</div><div class="dblk">Does VB copy the object? Nope; it actually creates a reference to the existing object. And this is done with pointers (surprise, surprise).</div><div class="dblk">When you use &quot;<span class="dcil"><span class="dckw">Set</span> l_oMyObj = <span class="dckw">New</span> MyObj</span>&quot;, VB does two things:</div><div class="dblk">1) It creates a new instance of the MyObj class somewhere in memory.<br> 2) It tells <span class="dcil">l_oMyObj</span> to <em>point</em> to this position in memory.</div><div class="dblk">Then, when you use &quot;<span class="dcil"><span class="dckw">Set</span> l_oMyOtherObj = l_oMyObj</span>&quot;, VB simply tells <span class="dcil">l_oMyOtherObj</span> to point to that same place in memory.</div><div class="dblk">What this means is that you can end up with several variables pointing to the same object.</div><div class="dblk">So what, then, does <span class="dcil">ObjPtr()</span> do? <span class="dcil">ObjPtr()</span> returns the address of this instance (ie: the address that <span class="dcil">l_oMyObj</span> and <span class="dcil">l_oMyOtherObj</span> are pointing to).</div><div class="dblk">Here's an example:</div><div class="dcod"><div><span class="dccm">' Make some variables</span><br><span class="dckw">Dim</span> l_oMyFont <span class="dckw">As</span> StdFont<br><span class="dckw">Dim</span> l_oMyOtherFont <span class="dckw">As</span> StdFont<br><br><span class="dckw">Dim</span> l_pMyFont <span class="dckw">As</span> Long<br><span class="dckw">Dim</span> l_pMyOtherFont <span class="dckw">As</span> Long<br><br><span class="dckw">Dim</span> l_pMyFontObj <span class="dckw">As</span> Long<br><span class="dckw">Dim</span> l_pMyOtherFontObj <span class="dckw">As</span> Long<br><br><span class="dccm">' Now give them values</span><br><span class="dckw">Set</span> l_oMyFont = <span class="dckw">New</span> StdFont<br><span class="dckw">Set</span> l_oMyOtherFont = l_oMyFont<br><br> l_pMyFont = VarPtr(l_oMyFont)<br> l_pMyOtherFont = VarPtr(l_oMyOtherFont)<br><br> l_pMyFontObj = ObjPtr(l_oMyFont)<br> l_pMyOtherFontObj = ObjPtr(l_oMyOtherFont)</div></div><div class="dblk">So what does that do?</div><div class="dblk">Well, <span class="dcil">l_oMyFont</span> and <span class="dcil">l_oMyOtherFont</span> point to the same object. <span class="dcil">l_pMyFont</span> and <span class="dcil">l_pMyOtherFont</span> will have different values, as <span class="dcil">l_oMyFont</span> and <span class="dcil">l_oMyOtherFont</span> are completely different variables in different places in memory. However, <span class="dcil">l_pMyFontObj</span> and <span class="dcil">l_pMyOtherFontObj</span> will have the same value, as both <span class="dcil">l_oMyFont</span> and <span class="dcil">l_oMyOtherFont</span> are pointing to the same instance of the StdFont class.</div><div class="dblk">Hopefully someone out there understood all that :P</div><div class="dblk">Now you may be wondering what use this all is. Perhaps the best use is to use it in Collections to test and make sure that an object is unique. For example:</div><div class="dcod">MyCollection.Add SomeObject, ObjPtr(SomeObject)</div><div class="dblk">Would ensure that even if the <em>name</em> of the variable changed, it would still pick up duplicate entries.</div><div class="dblk">There is another application that is <em>very</em> tricky: using it to make your own copies of objects. However, I don't fully understand it, so I won't go into it. Another use is rewriting the vTable of objects, but that's way, <em>way</em> beyond the scope of this article. Just do a search on Google and have a poke around.</div><div class="doc_h2">8. Conclusion</div><div class="dblk">Well, that's it for my first article. I've tried to explain things as best I can, but there's bound to be a few omissions, mistakes, or even bits that no one can understand because it's just too confusing :P</div><div class="dblk">If you have any questions, please post them here so everyone can benefit, and I'll see to updating the article.</div><div class="dblk">With that, I'd like to wish everyone a very merry christmas, a happy new year, and best of luck with your programming.</div><div class="doc_h2">9. Credits</div><div class="dblk">I picked up the declaration for VarPtrArray() and the .ODL code for StrPtrArray() from<br> &nbsp;&nbsp;<a href="http://support.microsoft.com/default.aspx?scid=KB;en-us;q199824">MSKB Q199824 - HOWTO: Get the Address of Variables in Visual Basic</a></div><div class="dblk">I couldn't find the declaration for a SAFEARRAY in the API tool, so I borrowed it from<br> &nbsp;&nbsp;<a href="http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_20230969.html">Visual Basic: Completely Dynamic Array</a><br> &nbsp;&nbsp;Which incidentally has some interesting stuff on SAFEARRAYs for you to look at.</div><div class="doc_h2">10. Some Light Relief</div><div class="dblk">After that monster, I think you've deserved some fun. I've taken this idea from Just Java 1.2 from Sun Microsystems by van der Linden. At the end of most chapters he puts in a little &quot;Light Relief&quot;. In that book I found the most brilliant little programmer song ever. Here it is, complete with English &quot;translation&quot;.</div><div class="dblk"><span class="doc_h2">Hatless Atlas</span> (sung to the tune of 'Twinkle, Twinkle, Little Star') <table border="0" cellpadding="0" cellspacing="8"><tr><td class="dcil">^ &lt; @ &lt; . @ *<br> } &quot; _ # &nbsp;&nbsp;|<br> - @ $ &amp; / _ %<br> ! (&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ | = &gt;<br> ; ' + $ ? ^?<br> , # &quot; ~ | ) ^G</td><td>Hat less at less point at star,<br> backbrace double base pound space bar.<br> Dash at cash and slash base rate,<br> wow open tab at bar is great.<br> Semi backquote plus cash huh DEL,<br> comma pound double tilde bar close BEL.</td></tr></table></div></div></div></div>
Original Comments (3)
Recovered from Wayback Machine