📄 article1.htm
字号:
[in, out] LONG * lParam);
</FONT></PRE><P>Notice that the <i>entry </i>attribute is the same for all of these because they're actually the same function in the system DLL. The name and the parameter types are different in each declaration, and you'll see these different aliases in an object browser.
<P><h3>The Structure Problem</h3><P>Many Windows API functions receive parameters as blocks of related variables called structures. Basic has a syntax for representing these blocks of related variables as user-defined types (UDTs). For example, you can define the following type in Basic:<P><PRE><FONT FACE="COURIER" SIZE="2">Type TPoint
x As Long
y As Long
End Type
</FONT></PRE><P>Windows calls this structure a POINT, but I change that because I find all-uppercase names confusing in Basic. You can write a <b>Declare</b> statement to use this type in a function like this:<P><PRE><FONT FACE="COURIER" SIZE="2">Declare Function GetCurrentPositionEx Lib "GDI32" ( _
ByVal hDC As Long, pt As TPoint) As Long
</FONT></PRE><P>Object Description Language has a <b>typedef</b> <b>struct</b> syntax for blocks of related variables. You can define the type like this in ODL:<P><PRE><FONT FACE="COURIER" SIZE="2">typedef struct {
long x;
long y;
} TPoint;
</FONT></PRE><P>You can define an ODL entry to use this type in a function like this: <P><PRE><FONT FACE="COURIER" SIZE="2">[
usesgetlasterror,
entry("GetCurrentPositionEx"),
helpstring("Gets current position in a TPoint structure"),
]
BOOL WINAPI GetCurrentPositionEx ([in] HDC hDC, [out] TPoint * ppt);
</FONT></PRE><P>So what's the problem? You should be able to use the ODL TPoint in a function defined by a Basic <b>Declare</b> statement, or use the Basic TPoint in a function defined by an ODL entry, right? Wrong! Worse yet, you can't even use the ODL TPoint in the ODL function. An ODL structure may look like a Basic UDT to you and me, but it doesn't look the same to Basic. The Visual Basic development team looked at what it would take to make this work in version 4.0, but this particular enhancement didn't make the grade. I've heard rumors that the next version of Visual Basic will be able to handle structures (and void * for As Any). In the meantime, you'll have to use Basic <b>Declares</b> and UDTs for API functions that take structures--unless you want to hack. <P>I'm not going to get into all the details of what you have to hack to get ODL definitions to work in Visual Basic; it's discussed in Chapter 6 of <i>Hardcore Visual Basic</i>. Here's a compressed version. Let's say you have the following ODL entry:<P><PRE><FONT FACE="COURIER" SIZE="2"> [
usesgetlasterror,
entry("GetCurrentPositionEx"),
helpstring("Get current position in a POINT structure"),
]
BOOL WINAPI GetCurrentPositionEx([in] HDC hdc,
[in, out] int FAR * lpPoint);
</FONT></PRE><P>Notice that the helpstring says it's a POINT structure, but the prototype says it's a pointer to an int, which to 32-bit Visual Basic is an array of Longs. Now let's say you have the following code: <P><PRE><FONT FACE="COURIER" SIZE="2">Dim axy(0 To 1) As Long
f = GetCurrentPositionEx(hDC, axy(0))
</FONT></PRE><P>After this call, the <i>x</i> coordinate would be in axy(0) and the <i>y</i> coordinate would be in axy(1). Windows simply fills the first 8 bytes of memory at the address of axy(0) with the appropriate values. It doesn't care that you passed it a two-element array of Longs rather than a TPoint variable. I'll leave you to figure out how to expand on this technique to create and use a <b>CPoint</b> class (or a class representing any structure) in Visual Basic. <P>The point is, this technique has to be hacked on the Visual Basic side, and all the type library can do to help is to take a parameter that is a pointer to the target structure's first member. <P><h2>All About Constants</h2><P>Type libraries provide a convenient alternative to the Visual Basic <b>Const</b> statement. You can define all the thousands of Windows constants. Furthermore, you can define constants that can't be declared in Basic. The ODL language provides two different ways to declare constants, each with its advantages and disadvantages. <P><h3>Constants with the const Statement</h3><P>You can place all the module statements you want in an ODL module block with the <b>Const</b> statement. The ODL statement looks and works pretty much the same as the C++ <b>const</b> statement. <P>Most of the constants you'll want to define will be integers. Let's take a random constant from Windows, MF_SEPARATOR. In the Windows include files, this constant appears as a <b>#define</b> rather than a <b>const</b>. It looks like one of these statements: <P><PRE><FONT FACE="COURIER" SIZE="2">#define MF_SEPARATOR 0x00000800L // 32-bit WINUSER.H
#define MF_SEPARATOR 0x0800 // 16-bit WINDOWS.H
</FONT></PRE><P>That's what I'd call an unsigned integer, and if you look at functions such as <b>InsertMenu</b> that use this flag, you'll see that the type is indeed UINT. Type libraries can't expose external constants with <b>#define</b>, so you have to convert to <b>const</b>: <P><PRE><FONT FACE="COURIER" SIZE="2">[ helpstring("Flag for menu functions: Separator line") ]
const UINT MF_SEPARATOR = 0x0800;
</FONT></PRE><P>Always define a help string for your constants. You can start by writing them for all the constants I didn't get around to in the Windows API type library. <P>As you may remember, Visual Basic doesn't recognize unsigned integer types, so UINT is actually a typedef for the <i>int </i>type:<P><PRE><FONT FACE="COURIER" SIZE="2">typedef int UINT;
</FONT></PRE><P>This typedef resides in a conditional compilation block in WINTYPE.ODL so that you can use the correct unsigned type if you are targeting an OLE client that understands unsigned numbers. <P>Normally, decimal and hexadecimal constants work fine, but if you want to define a constant with the high bit set, you might have a little trouble:<P><PRE><FONT FACE="COURIER" SIZE="2">const short t1 = 0x8000; // Fails 16-bit and 32-bit
const int t2 = 0x8000; // Fails 16-bit
const long t3 = 0x80000000; // Never fails
</FONT></PRE><P>You can get around this by defining the number in decimal rather than hexadecimal form. <P><PRE><FONT FACE="COURIER" SIZE="2">const short t1 = -32768; // Never fails
</FONT></PRE><P>Of course, you're much less likely to hit this problem if you're 32-bit only. <P>You can also define floating point constants if your Basic programs need them. <P><PRE><FONT FACE="COURIER" SIZE="2">const DOUBLE pi = 3.14159265;
</FONT></PRE><P><h3>String Constants</h3><P>Some of the most useful constants are strings because ODL is more flexible than Basic in dealing with characters. One of the most frustrating minor annoyances of Visual Basic before type libraries was the inability to define a constant for that most common of strings, the carriage return/line feed sequence. You could define the string easily enough:<P><PRE><FONT FACE="COURIER" SIZE="2">sCrLf = Chr$(13) & Chr$(10)
</FONT></PRE><P>But you only need to define this variable once, and you want it to be globally available forever from anywhere. In other words, you want a constant. But you can't code<P><PRE><FONT FACE="COURIER" SIZE="2">Public Const sCrLf = Chr$(13) & Chr$(10)
</FONT></PRE><P>because Basic doesn't like the <b>Chr$</b> function or concatenation operators in constants. But in a type library it's easy:<P><PRE><FONT FACE="COURIER" SIZE="2">[ helpstring("Carriage return/line feed (ASCII 13,10)") ]
const LPSTR sCrLf = "\r\n";
</FONT></PRE><P>You can write constants for all the control characters that have C++ escape sequences:<P><PRE><FONT FACE="COURIER" SIZE="2">[ helpstring("Bell (ASCII 7)") ]
const LPSTR sBell = "\a";
</FONT></PRE><P>The more obscure control characters are more difficult. You might expect that ODL would recognize C++ hexadecimal or octal escape sequences, but no such luck:<P><PRE><FONT FACE="COURIER" SIZE="2">const LPSTR sEOT = "\x4"; // Hex doesn't work
const LPSTR sEOT = "\4"; // Neither does octal
</FONT></PRE><P>This limitation seems to be fixed in the MIDL compiler, but for now you have to find yourself an editor that allows you to enter control characters directly into text. If the Microsoft Developer Studio does this, I haven't figured out how. The old MSDOS DEBUG program works if all else fails. Fortunately, I've already done it for you.<P>Here are three other handy strings:<P><PRE><FONT FACE="COURIER" SIZE="2">[ helpstring("Empty string (\"\")") ]
const LPSTR sEmpty = "";
[ helpstring("Null character (ASCII 0)") ]
const LPSTR sNullChr = "\0";
[ helpstring("Null string pointer (address zero)") ]
const LPSTR sNullStr = 0;
</FONT></PRE><P>The sEmpty constant is equivalent to "" in Basic. The sNullChr constant is equivalent to Chr$(0), and sNullStr is a null pointer. The null pointer is particularly handy because it has the value zero, but is of String type. You can pass it to API functions (such as <b>FindWindow</b>) that expect either a string or a null pointer. The Visual Basic for Applications type library has an equivalent constant called vbNullString.<P><h3>Constants with the typedef enum Statement</h3><P>You can also define constants with the <b>typedef</b> and <b>enum</b> statements. In C++ a <b>typedef</b> is a <b>typedef</b> and an <b>enum</b> is an <b>enum</b> , but ODL has its own strange logic. There is no such thing as an <b>enum</b> without a <b>typedef</b>. A <b>typedef</b> block goes inside the main library block. You can't nest it within a module block. <P>Here's a typical <b>typedef</b> <b>enum</b>:<P><PRE><FONT FACE="COURIER" SIZE="2">[ helpstring("Error constants") ]
typedef enum {
[ helpstring("There really isn't any such file anywhere") ]
errNoSuchFile = 1,
[ helpstring("No directory either") ]
errNoSuchDirectory,
[ helpstring("What planet are you from, anyway?") ]
errNoSuchPlanet
} Errors;
</FONT></PRE>
<P>If your ODL documentation seems to indicate a different syntax for <b>enum</b>s or <b>typedef</b>s, ignore it. This is how they really work.
<P>Why use a <b>typedef enum</b> rather than individual <b>const</b> statements within a module? Not much reason, really. They come to the same thing. Generally, you use a <b>const</b> to define a constant with a specific value. You use an <b>enum</b> for a group of related constants when you don't really care what the value is. The compiler automatically increments each <b>enum</b> element. But you can also assign a separate value to each element. An <b>enum</b> is equivalent to an int--an Integer in 16-bit Basic or a Long in 32-bit Basic.
<P>I usually use <b>const</b> statements in type libraries that map system DLLs. I use <b>enum</b>s more often in type libraries for DLLs or OLE objects.
<P><h2>Type Library Help Files</h2>
<P>I haven't had much to say about help files for type libraries, but let me take this opportunity to lay down the law once and for all. You should never create a type library without a corresponding help file that explains every entry. So why didn't I create a help file for the Windows API type library?
<P>I wanted to, and at various times I had different schemes for doing it:
<P><UL><LI> At one time I had a grand idea of extracting all the C-specific help text from Windows API text files and converting it to a Basic-specific help file. Basic function syntax would replace C syntax. Basic examples would replace C examples. Functions that don't work with Basic would be noted. Hacks required to call certain functions from Basic would be explained. Each entry in the type library would have a help-context link to the corresponding entry in the help file. All I needed to complete this task was an army of word processing specialists, a few Basic gurus to help translate the text and write examples, and several months of overtime. You'd have to pay for this product (no way was I going to provide it free with a book CD), but to a lot of hardcore Visual Basic programmers, such a help file would be cheap at any price. It didn't happen for me, but don't let that stop you. If you create this product, I will be first in line with hard cash.
<LI> Plan B was to link to the type library help contexts to the existing Windows API help files. The text for each entry would still be C-specific, but at least you'd get there with a single click from the Visual Basic object browser. The only problem is that you'd have to figure out every single help context number from the API help files and insert them into the corresponding ODL entries. There are tools for dumping such information from help files, but this is not a task for human beings. A wizard program would have to be written to extract the context numbers from the help file and insert them in the type library source. I didn't have the time or the expertise to write this wizard. <LI> Plan C was to create a forwarding help file. I would write a wizard that would go through the type library source, numbering the help context of each entry consecutively. At the same time it would create a separate help file that associated the entry names with the context numbers. Each entry in the help file would be a macro that called the entry name in the real help file. I could go into more detail, but it doesn't matter because 32-bit Windows API help is no longer provided in a help file. Starting with Visual Basic 4.0, the Win32 API is documented in the Microsoft Developer Network (MSDN)/Visual Basic starter kit (MSDNVB). This is an InfoView file, not a help file. On to plan D.
<LI> Your forwarding help file must call a help macro to find a given keyword in the MSDNVB InfoView file. I'll show you how in a minute, but first let me say why I never did this. The MSDNVB help file supplied with Visual Basic 4.0 is fatally flawed for this particular use. The index of keywords is messed up so that if you look up <b>GetWindow</b>, you'll come out at a random topic. This isn't a great problem because most users find topics in MSDNVB with the query tool rather than the index. But if you're calling keywords automatically from a help file, you need the keyword index. The Win32 API is documented in several other InfoView help files, such as the ones in the MSDN Library or the one in Microsoft Visual C++. The indexes in these work fine, but I can't be sure all Visual Basic programmers have access to them. </UL>So I'm stymied at every turn in efforts to connect the Windows API type library to Win32 help. Nevertheless, I'll show you what I had in mind. Because the current version of MSDNVB doesn't work, we'll assume you and your customers all have the full MSDN Library for the following examples. In order to bring up a keyword in an InfoView help file, you have to run a program called IV2TOOL.EXE with a command line like this: <P><
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -