📄 article2.htm
字号:
long Hi;
};
LONGLONG int64; // __int64 in Microsoft Visual C++
};
</FONT></PRE><P>This allows you to address the data all in one chunk with the <i>int64 </i>field, or in 32-bit pieces with the <i>Lo</i> and <i>Hi</i> fields. In Microsoft Visual C++, LONGLONG is a <b>typedef</b> to the nonstandard intrinsic type <b>__int64</b>. Other compiler vendors may not provide this type or may call it something different. A Borland representative told me their compiler doesn't support a 64-bit integer type. I don't know about other vendors. If your compiler has the same type with a different name, you can typedef LONGLONG to your compiler's intrinsic type and everything will be fine. <P>Visual Basic already provides good support for the currency type. I doubt that many people will want to write C++ code to enhance it, so this series isn't going to say much more about Currency. I could be wrong. If you really want to work with Currency in C++, take a look at MFC's <b>COleVariant</b> class. Unlike my Variant type (see Article 4 in this series), theirs encapsulates currency through the <b>COleCurrency</b> class and provides a lot of support for handling currency variables inside or outside of variants. <P><h3>Date</h3><P>Visual Basic calls it Date (introduced in version 4.0). OLE calls it DATE. MFC calls it <b>COleDateTime</b>. But it's really just a double. As with other typedefed types, C++ can't tell the difference between a DATE and double. I don't have much to say about this type for the same reasons I don't say much about Currency. I think relatively few programmers want to enhance Visual Basic by writing new Date functions. If I'm wrong, go ahead and enhance my Variant type. This is another area where the MFC <b>COleVariant</b> class does a lot more than my Variant type.
<P>By the way, OLE provides the <b>DosDateTimeToVariantTime</b> and <b>VariantTimeToDosDateTime</b> functions to convert dates. Unfortunately, the MS-DOS® date format these functions work with is accurate to two-second intervals--far less precision than provided by the Date format. If I were going to add full Date functionality, I'd skip the OLE conversion functions and write my own. That's what the <b>COleDateTime</b> class does. <P><h3>Errors</h3><P>OLE calls this type HRESULT. To C++, it's a long. Basic doesn't have an intrinsic type that maps directly to an HRESULT, but it does have an <b>Err</b> object that represents the information contained in an HRESULT and more. Again, you'll have some difficulties with this type because C++ can't tell the difference between an HRESULT and a long. Fortunately, you'll rarely need to use this type.
<P>You can think of an HRESULT as an error code, but it actually goes beyond that. An HRESULT indicates the results of a function. Generally we think of a function result in terms of success or failure. Some functions return 0 to indicate failure and non-zero to indicate success. This corresponds with how people think of Booleans, but it is limiting because functions often have only one kind of success, but many kinds of failure. Therefore, some functions return 0 to indicate success and an error code to indicate failure. An HRESULT carries this further, allowing you to return different kinds of failure and different kinds of success. Negative numbers indicate failure, and they contain several specific bits indicating the kind and severity of the error. Positive numbers (including zero) indicate success. Usually functions return the <b>S_OK</b> macro, which represents zero, but if a function can succeed in various ways, it might return some other positive success code.
<P><h3>String</h3><P>Basic calls it String. OLE calls it BSTR. C++ doesn't know anything about this concept, but the <b>String</b> class that we'll introduce in Article 3 of this series will make it easy. OleType.H includes BString.H, which defines the <b>String</b> class. <P><h3>Variant</h3><P>Basic calls it Variant. OLE calls it VARIANT. MFC calls it <b>COleVariant</b>. It's basically just a structure containing a union. The whole thing takes up 64 bits. I'll wrap the native structure up in <b>Variant</b> class in Article 4 of this series. OleType.H includes Variant.H, which defines the <b>Variant</b> class. <P><h3>IDispatch and IUnknown Pointers</h3><P>Basic calls them Object, Control, Form, and Collection, or it may recognize them through an object-specific name. Whatever you call your objects, Visual Basic sees them as <b>IDispatch</b> pointers. <b>IDispatch</b> pointers are a specific type of <b>IUnknown</b> pointers. Unfortunately, I'm not going to get around to them in this series. Creating objects is a subject well beyond the scope of these articles, but using objects created by others--such as Visual Basic programs calling our C++ DLL functions--should be covered. I'm sorry I didn't have time to discuss this subject.
<P><h3>Arrays</h3><P>A Visual Basic array is actually what OLE calls a SAFEARRAY structure. We'll be encapsulating this concept in Article 5 of this series. For now, let's just say that an array is a subtype that can contain multiple instances of any other subtype. You can have an array of Integers or of Strings or of Objects or of any of the other standard OLE types. You can even have a Variant containing an array of Variants. OleType.H includes SafeArray.H, which defines the <b>SafeArray</b> template class. <P><h2>Using Simple Types</h2><P>When it comes to simple types such as integers and floating point numbers, the Windows application programming interface (API) way is good enough for everyone. You can do it the same way the Window system DLLs do it. The C++ side is as easy as the Basic side. <P><h3>Numbers by Value</h3><P>When you pass a number as input to a function, there's not much reason to pass it any way other than by value. The Basic default is to pass by reference, but the C++ default is by value. Inasmuch as you're writing your DLL in C++, you might as well pass by value. Visual Basic won't know the difference if you provide a type library. Here's a typical function passing parameters by value:
<P><PRE><FONT FACE="COURIER" SIZE="2">Long DLLAPI MakeDWord(Long wHi, Long wLo)
{
return (wHi << 16) | (wLo & 0x0000FFFFL);
}
</FONT></PRE>
<P>The type library entry for this function looks like this: <P><PRE><FONT FACE="COURIER" SIZE="2"> [
entry("MakeDWord"),
helpstring("Returns a DWord from two passed words"),
]
DWORD WINAPI MakeDWord([in] DWORD dwHi, [in] DWORD dwLo);
</FONT></PRE>
<P>The code should be clear, but why am I using Windows types, such as DWORD, rather than C types? That's a tricky question. I'm not a big fan of DWORD and WORD rather than unsigned long and unsigned short, but a lot of code you write for DLLs will call the Windows API, which expects Window type names. Using <b>typedef</b>s lets you use the same type names in your C++ and ODL source files. For example, DWORD in the type library actually means signed long, while DWORD in the C++ source means unsigned long. This issue was discussed in Article 1.<P>You can see the source for <b>MakeDWord</b> and other similar functions in TOOLS.CPP and TOOLS.H. The Bits button in the Cpp4VB sample program tests these functions. <P>Floating point parameters work the same as integer arguments: <P><PRE><FONT FACE="COURIER" SIZE="2">Double DLLAPI DoubleDouble(Double rInput)
{
return (rInput * 2);
}
</FONT></PRE><P><h3>Numbers by Reference</h3><P>A number passed by reference in Visual Basic is a pointer to a numeric variable in C++. Normally, you use <i>ByRef</i> parameters to receive multiple return values. For example, let's say you wanted a Basic-style wrapper for the Windows API <b>GetCurrentPositionEx</b> function. Every device context (hDC) has a current X/Y coordinate that can be returned by reference in a POINT structure. (The original <b>GetCurrentPosition</b> returned a POINT structure directly, but that doesn't work well in 32-bit, so <b>GetCurrentPositionEx</b> took over.) The C++ prototype looks like this: <P><PRE><FONT FACE="COURIER" SIZE="2">BOOL GetCurrentPositionEx(HDC hdc, LPPOINT pPoint);
</FONT></PRE><P><blockquote><b>Note:</b> For reasons unknown, the Windows API uses the abbreviation LP or lp to refer to pointers. I believe LP stands for long pointer, which is what everyone else in the world used to call a far pointer. Almost all Windows pointers were far even in the 16-bit world, so there was never any need to qualify them. In the 32-bit world, far (or long) pointers are just a bad dream, but we still see the bizarre LP notation in Windows include files and Windows documentation. Enough is enough. These articles use the old names for types because that's what they are in the include files (LPSTR, LPPOINT, and so on). But we'll just use <i>p </i>rather than <i>lp </i>for the variable names we control (<i>pPoint</i>, <i>pch</i>, and so on). </blockquote><P>You can certainly call <b>GetCurrentPositionEx</b> from Basic, but it's a little awkward. Basic doesn't recognize structures in type libraries, so you have to write both the <b>Declare</b> statement and the UDT on the Basic side. It would be a lot simpler if the original name were reused for a function such as this:<P><PRE><FONT FACE="COURIER" SIZE="2">BOOL GetCurrentPosition(HDC hDC, int * pX, int * pY);
</FONT></PRE><P>You could easily write a Basic wrapper for this function, but it's easier (and slightly more efficient) in C++.
<P><PRE><FONT FACE="COURIER" SIZE="2">BOOL GetCurrentPosition(HDC hDC, int * pX, int * pY
{
POINT pt;
f = GetCurrentPositionEx(hdc, &pt);
*pX = pt.x;
*pY = pt.y;
return (f);
}
</FONT></PRE><P>The ODL version looks like this:
<P><PRE><FONT FACE="COURIER" SIZE="2">[
entry("GetCurrentPosition"),
helpstring("Returns the X and Y coordinates of a given device context"),
]
BOOL GetCurrentPosition([in] HDC hDC, [out] int * pX, [out] int * pY);
</FONT></PRE>
<P>The same technique works for floating point numbers, integers of every size, and Boolean variables.
<P><h3>Structures</h3><P>The first question to ask yourself about structures is, do you really want to use them in your DLL functions? If your target client is Visual Basic, you should certainly think twice. If you're using small API structures such as POINT and RECT, it's probably better to pass coordinates in and receive coordinate results as separate parameters. If you're using large structures in order to pass in or receive chunks of mixed-type data, an object may be a better choice than a structure. <P>Remember, you can't use type libraries for your structures or functions that receive them. Everything has to be on the Basic side. The complications increase if you want your structures to contain strings, arrays, or other structures. Although you can use structures in DLLs like the ones in this chapter, you can't use structures reliably in the methods and properties of OLE objects. You have no choice when writing type libraries that map API functions, but if you're writing your own functions, you can design them any way you want to. If you decide, despite my advice, to use structures, here are a few things to remember.
<P>Basic user-defined types have the same default 4-byte alignment as C++ structures. For example, the following UDT takes 20 bytes:
<P><PRE><FONT FACE="COURIER" SIZE="2">Type BadBunchOStuff
bLittle As Byte ' One byte data, three bytes padding
lBig As Long ' Four bytes data
iMedium As Integer ' Two bytes data, two bytes padding
sText As String ' Four bytes data (pointer)
bMoreLittle As Byte ' One byte data, three bytes padding
End Type
</FONT></PRE>
<P>When designing your structures, you'll save a little memory space if you put <b>Integer</b> and <b>Byte</b> fields next to each other. Here's a better way to organize the same data: <P><PRE><FONT FACE="COURIER" SIZE="2">Type BunchOStuff
bLittle As Byte ' One byte data
bMoreLittle As Byte ' One byte data
iMedium As Integer ' Two bytes data
lBig As Long ' Four bytes data
sText As String ' Four bytes data (pointer)
End Type
</FONT></PRE><P>This one uses only 12 bytes.
<P>On the C++ side, the data looks like this:
<P><PRE><FONT FACE="COURIER" SIZE="2">struct BunchOStuff {
BYTE bLittle; // One byte data
BYTE bMoreLittle; // One byte data
SHORT iMedium; // Two bytes data
LONG lBig; // Four bytes data
BSTR sText; // Four bytes data (pointer)
};
</FONT></PRE><P>A function that uses this data might look like this:<P><PRE><FONT FACE="COURIER" SIZE="2">void GetBunchOStuff(BunchOStuff * pStuff)
{
pStuff->bLittle = GetLittle();
pStuff->bMoreLittle = GetLittle();
pStuff->lBig = GetBig();
...
}
</FONT></PRE><P>Because you can't define this function in a type library, you'll need a Basic <b>Declare</b> statement like the following: <P><PRE><FONT FACE="COURIER" SIZE="2">Declare Sub GetBunchOStuff Lib "MyDll" (stuff As BunchOStuff)
</FONT></PRE><P><h3>Strings the Windows Way</h3><P>You can write DLL functions to take Windows <i>lpstr</i> parameters like API functions, but this isn't politically correct. I've been known to pass input parameters as LPSTR type as a shortcut, but your Basic clients will consider you extremely rude if you define output parameters this way. Your C++ DLLs are supposed to serve your Basic clients, making everything easy and hiding the messy details. We'll get to the Basic way of doing this with <b>Strings</b> soon, but first let's take a short look at the shortcut for input parameters.<P>Assume you want a function that looks like this in Basic:<P><PRE><FONT FACE="COURIER" SIZE="2">f = ExistFile("FILE.TMP")
</FONT></PRE><P>The politically correct way to define it in ODL is this:<P><PRE><FONT FACE="COURIER" SIZE="2">Boolean ExistFile([in] BSTR sFile);
</FONT></PRE><P>This says Basic will pass in a normal Basic string (a BSTR) and the DLL will accept and handle it as a BSTR, as we'll discuss in Article 3. Basic won't do any Unicode translation, but your DLL will have to. It's kind of a messy business, but we'll provide a <b>String</b> class to clean things up. <P>The other alternative is to define the function like this in ODL:<P><PRE><FONT FACE="COURIER" SIZE="2"> Boolean WINAPI ExistFile([in] LPSTR lpFileName);
</FONT></PRE><P>In this case, Basic will do the same Unicode conversion it does for Windows API functions. The string is originally Unicode, but Basic makes an ANSI copy of it for the C++ side. Because this function doesn't need to pass anything back, the copy is perfectly adequate for the DLL. The function can be implemented like this: <P><PRE><FONT FACE="COURIER" SIZE="2">Boolean DLLAPI ExistFile(LPCSTR szFile)
{
return ((szFile == NULL) ? FALSE : !_access(szFile, 0));
}
</FONT></PRE><P>This is one of those rare functions that is actually easier to implement in C++ than in Basic. Notice that it uses the constant version LPCSTR to enforce that the string will be used only for input and never modified. <P>The <b>ExistFile</b> function is tested by the Exist File button in the sample program. <P><h3>Arrays, Typeless Variables, and Pointers</h3><P>Don't do it. We have to learn to deal with such things (arrays, that is) in the Windows API way when mapping system DLLs to type libraries. The system DLLs are what they are. But when you write your own DLLs, you shouldn't impose this inconvenience on your clients. <P>Instead of API-style arrays, use SafeArrays, as described in Article 5. Your clients can pass arrays naturally:<P><PRE><FONT FACE="COURIER" SIZE="2">c = CountArray(ai())
</FONT></PRE><P>This is much better than the unnatural way you have to pass API arrays by passing the first element: <P><PRE><FONT FACE="COURIER" SIZE="2">c = CountApiArray(ai(0))
</FONT></PRE>
<P>Besides, you get the better index protection of the SafeArray type.
<P>Typeless variables are usually represented in C++ as void pointers (<b>void *</b>). You have to do a lot of type-casting to get such functions to compile without warnings. OLE offers Variants as a safer (although less efficient) way to do typeless variables. Article 4 describes Variants.
<P>Basically, you destroy Basic's type safety net when you write C++ functions that take void pointers or any other kind of pointer. You can usually figure out a hack to write a <b>Declare</b> or type library entry for such functions, but you'll have to do it without my help.
<P>
<P><A HREF="intro.htm">Introduction</A>
<P><A HREF="article1.htm">Article 1. Stealing Code with Type Libraries</A>
<P><A HREF="article3.htm">Article 3. Strings the OLE Way</A>
<P><A HREF="article4.htm">Article 4. The Ultimate Data Type</A>
<P><A HREF="article5.htm">Article 5. The Safe OLE Way of Handling Arrays</a><P>
</BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -