📄 article2.htm
字号:
DoNothing
</FONT></PRE><P>Now the function name comes out the way you expect in the DLL: <b>DoNothing</b>. You don't have to use aliases in <b>Declare</b> statements or type library entries. You <i>do</i> have to remember to modify the .DEF file every time you create a new function.
<P>So why does VBUtil use DLLAPI instead of <b>__stdcall</b> in its function prototypes and definitions? <P><PRE><FONT FACE="COURIER" SIZE="2">void DLLAPI DoNothing(void);
</FONT></PRE>
<P>I'm not just trying to get rid of underscores, although that's a pleasant side effect. (I'm going to resist the urge to flame about the ANSI C++ requirement for double underscores on compiler-specific keywords.) If you check back through the chain of definitions (the browser in the Microsoft Developer Studio makes this easy), you'll find the following declaration in VBUTIL.H:
<P><PRE><FONT FACE="COURIER" SIZE="2">#define DLLAPI WINAPI
</FONT></PRE>
<P>Going back one more level to the Win32 WINDEF.H include file, you'll find this:
<P><PRE><FONT FACE="COURIER" SIZE="2">#define WINAPI __stdcall
</FONT></PRE>
<P>I keep this somewhat confusing system to make it easy to port to new environments (such as Macintosh®) or to different compilers. For example, instead of <b>__declspec(dllexport)</b>, your compiler might have a 32-bit attribute that has the same effect as the old 16-bit <b>__export</b> attribute. Let's assume this attribute is called (surprise!) <b>__export</b>. You change the <b>define</b> statement to this: <P><PRE><FONT FACE="COURIER" SIZE="2">#define DLLAPI WINAPI __export
</FONT></PRE>
<P>Now you don't need a .DEF file.
<P><blockquote><b>Note:</b> Borland representatives assured me that their implementation of extern "C" and <b>__export</b> or <b>__declspec(dllexport)</b> will generate unmangled function names and that you won't need a .DEF file. I didn't have a chance to test this for Borland® C++ or other compilers. </blockquote>
<P><h3>Debugging</h3>
<P>I'm going to describe some of the debugging issues for the Microsoft development environment. Other IDEs will no doubt have similar issues.
<P>I make the Visual Basic environment (VB32.EXE) the program to be debugged. I set the working directory to the directory containing my Basic test programs. You'll have to adjust the directories for your own configuration. I set the program arguments to /r with the name of my Basic test program. When I press the Go toolbar button (or use the Start hotkey or menu item), the IDE runs Visual Basic, which runs my Basic test program, which loads my DLL. When I execute Basic code that uses DLL functions, the IDE will stop at any breakpoints in my C++ code. The load time is a little slow, but I can debug normally.
<P>Before any of this happens, you'll see the same obnoxious error message described in Article 1, telling you that VB32.EXE does not contain debugging information. Be calm. Do not curse. As my dad says, "It'll get better, or you'll get used to it." Or you'll get your hands on Visual C++ 4.1, which fixes the problem.
<P>To do serious debugging of the DLLs in this chapter, you'll need to enable Unicode debugging by modifying the AUTOEXP.DAT file. I'll explain this in detail in Article 3.
<P>Another thing I usually do is to enable the startup banner for debug builds in the Customize mode of the C/C++ tab in the Settings dialog. This causes the environment to output a lot more information during compiles, including the complete compile command lines with all the options used. Once you've got a project working properly, you may prefer to turn off this extra noise, but it can be handy for debugging build problems. The Linker and OLE Types tabs have similar setting, although I find these less useful.
<P><h2>The OLE Types</h2>
<P>C++ has types and more types--signed types, unsigned types, pointer types, intrinsic types, implementation-specific types, standard typedefed types . . . . And there are more coming when the ANSI C++ committee lays down the law "real soon now."
<P>Basic takes a different approach. It has only a few types, but they're solid ones. No pointer types. No unsigned types. No types that act like types but aren't quite types (such as char* in C++). Because this article is about extending Visual Basic with C++, we're going to trim the C++ type list down to fit the Basic type list. Or to be more exact, we're going to trim C++ types to OLE size.
<P>One goal of OLE automation is to be language-independent, but because the people who wrote the technology also work on Visual Basic for Applications, the OLE types look strangely familiar to Basic programmers. Here is a table of OLE types with comparisons of Basic and C++ types.
<P>
<TABLE WIDTH=87% BORDER=1 CELLPADDING=5 CELLSPACING=0>
<TR VALIGN=TOP BGCOLOR="#DDDDDD">
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2><b>OLE Type</b></FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2><b>Basic Type</b></FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2><b>C++ Type</b></FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>BYTE</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Byte</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>unsigned char</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>SHORT</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Integer</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>short</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>LONG</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Long</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>long</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>FLOAT</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Single</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>float</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>double</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Double</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>double</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>CURRENCY</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Currency</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>__int64 </FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>HRESULT</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>none</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>long</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>VARIANT</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Variant</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>none</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>IDispatch*</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>Object</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>none</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>IUnknown*</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>none</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>none</FONT></TD></TR>
<TR VALIGN=TOP>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>SAFEARRAY</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>array syntax</FONT></TD>
<TD><font face="Verdana, Arial, Helvetica, Sans-Serif" SIZE=2>none</FONT></TD></TR>
</TABLE>
<p>The OLETYPE.H include file contains classes or <b>typedef</b>s that create Basic type names out of the corresponding C++ and OLE types. OLETYPE.ODL has similar declarations for Object Description Language. If you add new modules to VBUTIL (and that's what it's for), you should include OLETYPE.H at the top of each new module file. OLETYPE.ODL is already included in VBUTIL.ODL. <P>Because the type definitions work out differently for each type, we'll have to look at them on a case-by-case basis. <P><h3>Numeric Types--Byte, Integer, Long, Single, and Double</h3><P>Basic calls them Byte, Integer, Long, Single, and Double. C++ calls them unsigned char, short, long, float, and double. Windows calls them BYTE, SHORT, LONG, FLOAT, and double (really), but rarely uses the floating point types at all. OLE usually calls them by their Windows names. If you've seen one (and you will), you've seen them all. There is one inconsistency to note: Byte is an unsigned type, whereas Integer and Long are signed. Here are the typedefs to implement our Basic names.
<P><PRE><FONT FACE="COURIER" SIZE="2">typedef unsigned char Byte;
typedef short Integer;
typedef long Long;
typedef float Single;
typedef double Double;
</FONT></PRE><P>Do you notice a conspicuous absence here? There is no type for the system word size (what C++ calls int). Basic has no such concept--to the chagrin of Visual Basic 4.0 programmers who wanted to write portable Basic code easily. Probably the reason Basic didn't provide this feature is that OLE doesn't provide it. The OLE automation model assumes that all types are size-specific. This annoyance doesn't affect these articles because we're 32-bit only, but the same problem will come up again when the world starts moving from 32-bit to 64-bit.
<P><h3>Boolean</h3><P>Basic calls it Boolean. OLE calls it VARIANT_BOOL. I call it Boolean in my C++ code. But it's really just a C++ short type. In other words, C++ can't tell the difference between a Basic Boolean and a Basic Integer. This will create some difficulties later (in Article 4) when we try to overload constructors and other member functions for the C++ Variant type. <P>You might think that a type with only two possible values would be the one area where two languages couldn't possibly find anything significant to disagree about. Wrong! Basic and C++ have different ideas on the subject, and you can get caught with annoying bugs if you don't understand the differences. <P>The Windows BOOL type is an int (32 bits in 32-bit mode), defined like this in WINDEF.H:
<P><PRE><FONT FACE="COURIER" SIZE="2">typedef int BOOL;
</FONT></PRE><P>The Basic Boolean type is a short (always 16 bits). You might argue that it would make more sense to make a Boolean type a byte (for more efficient storage) or an int (for more efficient run-time processing). But for whatever reason, OLE defines VARIANT_BOOL like this in OAIDL.H:
<P><PRE><FONT FACE="COURIER" SIZE="2">typedef short VARIANT_BOOL;
</FONT></PRE><P>There's more to this difference than size. Windows defines the following values for TRUE and FALSE:<P><PRE><FONT FACE="COURIER" SIZE="2">#define TRUE 1
#define FALSE 0
</FONT></PRE><P>OLE defines its own Boolean constants: <P><PRE><FONT FACE="COURIER" SIZE="2">#define VARIANT_TRUE ((VARIANT_BOOL)0xffff)
#define VARIANT_FALSE ((VARIANT_BOOL)0)
</FONT></PRE><P>Basic uses the OLE versions, although it spells them differently. For your convenience, I provide versions with Basic spelling in VARIANT.H:
<P><PRE><FONT FACE="COURIER" SIZE="2">typedef VARIANT_BOOL Boolean;
const Boolean True = VARIANT_TRUE;
const Boolean False = VARIANT_FALSE;
</FONT></PRE><P>I haven't checked every possible language, but C-based languages are the only ones I know of that use 1 as the constant value of truth. Most languages use -1. This doesn't make much difference on the C++ side, and most Basic programmers already know how to handle the differences on the Basic side.
<P>For now, Boolean, True, and False are more language-independent than BOOL, TRUE, and FALSE. C++ is supposed to add an intrinsic Boolean type called "bool," but it hasn't appeared in the compiler I use, and when it does, I'm not sure it will be compatible with OLE's Boolean type anyway. Most C and C++ representations of Boolean map to the int or to unsigned char rather than to short. <P><blockquote><b>Note: </b> The MIDL compiler that will eventually replace MKTYPLIB (as described in Article 1) has an intrinsic <b>boolean</b> type that is an unsigned char. WINTYPE.ODL conditionally defines Boolean (note the initial capital) to VARIANT_BOOL so that when MIDL becomes available, Boolean will have the size used by OLE and Visual Basic. </blockquote>
<P><h3>Currency</h3><P>Basic calls it Currency. OLE calls it CURRENCY. The Microsoft Foundation Classes call it <b>COleCurrency</b>. But what it really is is a 64-bit integer. OLE represents it something like this (with extra <b>typedef</b>s and macros cleaned out):<P><PRE><FONT FACE="COURIER" SIZE="2">union CURRENCY {
struct {
unsigned long Lo;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -