⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 article1.htm

📁 The code for this article was written for version 1.0 of the Active Template Library (ATL). The cu
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<LI>   Start commenting in the most important ZAPI functions. For each one, add the entry attribute list and attributes for each parameter. Compile often to make sure everything is OK. 
<LI>   Modify <b>#define</b> and <b>enum</b> statements to create ODL constants. </OL>I do many of the common editing tasks of converting from C++ to ODL with word processor macros or complex search and replace commands. Perhaps if I did this more frequently a tool would be worth the trouble. Don't let my limitations stop you from writing one. <P><h3>The Very Last Error</h3><P>The <b>GetLastError</b> function is the recommended way to determine what went wrong with a Win32 API call, but you can't use it in Visual Basic. That's because you don't really call API functions when you think you're calling them. The Visual Basic for Applications language engine intercepts your API calls and sends them on to the appropriate DLL. Sending them on involves making other API calls, such as <b>GetProcAddress</b> and possibly <b>LoadLibrary</b>. If you call <b>GetLastError</b>, the value it returns will usually be the one set by the last API call made by Visual Basic for Applications, not the one set by the API function you called. This problem isn't unique to Visual Basic for Applications. Any other OLE host that uses a type library is bound to have the same problem. <P>The <i>usesgetlasterror </i>attribute tells Visual Basic for Applications to call <b>GetLastError</b> and save the result immediately after the internal call to the requested API. You can then access this value through the <b>LastDllError</b> property of the Visual Basic for Applications Err object. Visual Basic for Applications does this automatically any time you make a call through a <b>Declare</b> statement, but the attribute is required to make it happen with type libraries. Other type library hosts can implement their own system of returning the last error. The attribute is ignored in 16-bit mode because there is no 16-bit <b>GetLastError</b> function. <P>Unfortunately, the <i>usesgetlasterror </i>attribute is required on every API call. It would be nice if you could just give it as an attribute for each module or for the entire library. I can't imagine where you would find a type library host that wouldn't require all entries to store the last error for system DLLs. Of course, when you write your own DLLs, you may wish to use some other system of error reporting rather than using <b>SetLastError</b> and <b>GetLastError</b>. <P><h3>Compiling with MKTYPLIB</h3><P>The MKTYPLIB command-line isn't difficult, but it can be long and tedious to retype repeatedly. My MKWIN batch file builds the 16- and 32-bit versions of the Windows API type library: <P><PRE><FONT FACE="COURIER" SIZE="2">@echo off
if &quot;%1&quot;==&quot;32&quot; Goto Win32
mktyplib /nologo /win16 /DWIN16 /tlb win16.tlb win.odl
:Win32
if &quot;%1&quot;==&quot;16&quot; Goto Exit
mktyplib /nologo /win32 /DWIN32 /tlb win32.tlb win.odl
:Exit
copy win*.tlb %windir%
</FONT></PRE>
<P>Although the current version of MKTYPLIB is a 32-bit program, it can build 16-bit type libraries when given the /win16 option. The /D option passes macro names, just as in the C++ compiler. There are other options--you can see them if you type <i>MKTYPLIB ? </i>at a command line. We'll deal with them as they come up. 
<P><blockquote><b>Note:</b>     The code in this article requires MKTYPLIB version 2.03. Early versions won't recognize the <i>usesgetlasterror </i>attribute. Although 16-bit versions of MKTYPLIB exist, they aren't compatible with the current version and can't be used on WIN.ODL. I've also been able to build the type library with a pre-release version of the MIDL compiler.</blockquote>
<P>The batch file works, and in fact it's how I developed the original type library. But the Microsoft Developer Studio makes development much easier. If you use a different compiler, you can probably set up a similar configuration. Essentially, you specify the same MKTYPLIB options for the IDE settings as you give with command-line options in MKWIN.BAT. 
<P><OL><LI>   Select New from the File menu and create a new Project Workspace named WINTLB. Select Dynamic-Link Library as the project type. You're not going to build a DLL, but you have to select some project type, and this one won't do any harm. 
<LI>   Add WIN.ODL as the only project source file. It will automatically add the included ODL files as dependencies. Although the project type is DLL, no DLL will be created because there are no DLL source files. 
<LI>   Make sure WINTYPE.ODL exists in the INCLUDE path known to the developer studio. I copy it to \MSDEV\INCLUDE. 
<LI>   Create two configurations--Win32 Release and Win32 Release 16--in the Configurations dialog (&quot;Configurations...&quot; on the Build menu). What you really want is a Win16 Release, but I couldn't figure out a way to get the Developer Studio to recognize this name. In theory, you could have separate Debug configurations, but I can't think of any options that I would set differently for debugging. 
<LI>   From the Project Settings dialog, select the OLE Types tab. You can think of OLE Types as a pseudonym for MKTYPLIB Options. <LI>   Add WIN32 to the Preprocessor Definitions field of the Win32 Release configuration. Leave this field unchanged for the Win16 Release configuration. <LI>   By default, you'll see the /win32 option in the Project Options field. Change this to /win16 for the Win32 Release 16 configuration. 
<LI>   Select the WIN.ODL files for Win32 Release in the Settings For list and set the Output file name field to the target TLB file. I usually build the Windows API type library in the Windows directory: C:\WINDOWS\WIN.TLB. If your Windows directory is different or if you want a different target directory, adjust accordingly. I wanted to use <i>%windir%</i> here, but unfortunately the Developer Studio doesn't recognize environment variables in dialog box fields. 
<LI>   Select the WIN.ODL files for Win32 Release 16 in the Settings For list, and set the Output filename field to an appropriate file such as C:\WINDOWS\WIN16.TLB. 
<LI>   Switch from the OLE Types pane to the Debug pane. In the General category, enter VB32.EXE as the executable for the debug session. Fill in the working directory and add /R and the Basic project name in the program arguments field. For example, enter <i>/R TestWin</i>. </OL>That's it. Click on the build button, and your type library will be built. If you hit a compile-time error, you'll see it in the Build pane of the Output window. <P>If you build successfully, you can press the Go button to load Visual Basic and run your test program. You'll get a message telling you that VB32.EXE does not contain debugging information. No kidding! There's no way to turn off this obnoxious message in Visual C++ 4.0 (or in earlier IDEs going all the way back to QuickC&#174; for Windows), but fortunately you won't see it in version 4.1 or higher. <P><h3>Debugging Type Libraries</h3><P>In most languages, bugs can occur at compile time or run time. With MKTYPLIB, however, you also frequently get bugs at preprocess time. The story isn't good on any of these. In fact, there's no way to put it nicely: MKTYPLIB can be the compiler from hell. It's not a very forgiving tool when you need to be forgiven, and it's too forgiving when you want immediate feedback. Furthermore, Visual Basic 4.0 can be the interpreter from hell when reporting errors in type libraries. <P>Compile-time errors are easy to deal with. Like any reasonable compiler, MKTYPLIB puts out error messages with the line number of the offending line. If you compile from within the Microsoft Developer Studio (or most other IDE environments), an error message will be displayed in the output window after an unsuccessful compile. You can give the Next Error command (toolbar button, hot key, or Goto menu item) to put the cursor on the error line. <P>Unfortunately, MKTYPLIB reports only one error at a time. The MIDL compiler reports multiple errors, but until it's released, you'll have to rebuild frequently. During early stages of development, you can speed compiles by commenting out all of the <b>#include</b> statements in WIN.ODL other than the one you're working on. Compile-time errors are frequently based on differences between the C++ way of evaluating expressions and the MKTYPLIB way. We'll see some specifics later.<P>Preprocessor errors are more of a problem. MKTYPLIB calls the C++ preprocessor (C1.EXE) to process<b> #include</b>, <b>#define</b>, and other statements. If the preprocessor fails, it will report an error, but MKTYPLIB will throw this information directly into the ozone. You'll see nothing but the following error message:<P><PRE><FONT FACE="COURIER" SIZE="2">fatal error M0006: unable to pre-process input file
</FONT></PRE><P>Which file couldn't be preprocessed? Which line caused the failure? Debugging such errors can require tedious experimenting by commenting out various parts of the type library until you narrow down the problem. I did it the hard way for many months, but I've finally found a way to find errors with a little less pain. Use the C++ command-line compiler to preprocess the file directly. <P><PRE><FONT FACE="COURIER" SIZE="2">Cl /DWIN32 /E /c win.odl &gt; win.out
</FONT></PRE>
<P>By examining the resulting WIN.OUT file, you can tell where the preprocessor stopped working. It's not a foolproof shortcut, but better than nothing. 
<P>Finally, you may have to deal with run-time errors. You might build your type library successfully; Visual Basic might load it successfully. But your Basic program might still fail when it uses type library function entries or constants. Worse yet, it will probably fail rudely. Suddenly a Visual Basic project that used to work fine will start giving an error message that says something like this: &quot;You screwed up something in your type library and I know what it is, but I'm not going to tell you.&quot; Well, maybe that won't be the exact wording, but that's what Visual Basic means when it says, &quot;Invalid ordinal entry&quot; or &quot;Error in loading DLL&quot;. You'll get no hint of which ordinal entry or DLL is invalid. 
<P>You'll be back to commenting out big chunks of code in the type library and in your Basic source file until the error goes away. Then you comment things back in until the error returns and you can figure out what caused it. To avoid getting caught in this cycle, it pays to change your type library in small increments, making backups frequently. This in itself won't always save you, however, because a type library might work with one Basic module but fail with another that calls different functions. Here are some of the errors I've encountered:
<P><UL><LI> Putting a type library function entry in the wrong module causes the message &quot;Specified DLL function not found.&quot; This is an easy mistake to make. It's hard to guess whether some functions go in KERNEL32, USER32, or GDI32. Once you get this error, removing the offending Basic statement won't get rid of the message because the wrong DLL is already loaded in Visual Basic at run time. You must quit Visual Basic to unload it. 
<LI> If you misspell the name of a DLL in a module statement, Visual Basic gives you the helpful message &quot;File not found.&quot; Which file? Guess. 
<LI> Giving a function an unsigned argument causes the error &quot;Function marked as restricted or uses an OLE type not supported in Visual Basic.&quot; Visual Basic thinks all numbers are signed (except Bytes, which are always unsigned). You would also get this error if you defined a type library function entry with a structure argument and then tried to pass a Basic UDT to the function. There are several other kinds of definitions that Basic doesn't like. MKTYPLIB is supposed to work for any language client, so it won't stop you from doing things Visual Basic doesn't like. </UL><h3>Standard Definitions</h3><P>The WINTYPE.ODL file contains standard definitions for common Windows types that you might need from any type library. Let's take a look:<P><PRE><FONT FACE="COURIER" SIZE="2">#ifdef SIGNAWARE
// For host languages that recognize unsigned numbers
typedef unsigned short  WORD;
typedef unsigned short  USHORT;
typedef unsigned long   DWORD;
typedef unsigned long   ULONG;
typedef unsigned int    UINT;
#else
// For host languages (such as VB) that only recognize signed numbers
typedef short           WORD;
typedef short           USHORT;
typedef long            DWORD;
typedef long            ULONG;
typedef int             UINT;
#endif
typedef unsigned char   BYTE;   // BYTE is unsigned under VB
typedef int             BOOL;
typedef long            LONG;
typedef int             HANDLE;
typedef int             HWND;
.
.
.
typedef DWORD           COLORREF;
typedef UINT            WPARAM;
typedef LONG            LPARAM;
typedef LONG            LRESULT;

#define VOID void
#define FAR far
#define LPCSTR LPSTR
#ifdef WIN32
#define WINAPI __stdcall
#else
#define WINAPI __pascal
#endif
</FONT></PRE><P>Notice that the definitions for some of the integer types are lies. In Windows, a DWORD is an unsigned long, but Visual Basic doesn't recognize unsigned numbers. The object definition language has no problem with unsigned numbers, but if you declare any unsigned parameters, return values, or constants in your type library, Visual Basic will fail. Signed and unsigned are just two different ways of looking at the same number. The bits are the same, and there's no harm done in receiving Windows unsigned numbers as Basic signed numbers. <P>Generally, you should write type libraries to be language-independent, and you'd be pretty rude to lie about the sign if your container supported unsigned numbers. It's a sad fact that you can't make a single type library that politely serves both kinds of containers, but at least you can use the SIGNAWARE macro to create signed and unsigned versions of the type library from the same source file. <P>The macros at the end of the file define some common Windows modifiers. FAR maps to <i>far, </i>which is ignored because there hasn't been any such concept since the 32-bit revolution. Unlike C++, ODL doesn't understand <i>const </i>as a modifier. The LPCSTR type (const char *) can't be defined, so you have to lie again to claim that LPCSTR is the same as LPSTR. The concept of <i>const </i>can be indicated by the <i>[in] </i>attribute in ODL, but there's no way to enforce this through a macro. <P><h2>Parameter Types</h2><P>When you're writing type libraries, every procedure parameter is an adventure. Just when you think you've seen them all, Windows throws out something new. Still, you'll encounter some familiar patterns. Let's look first at parameter attributes, and then at some common Windows types. <P><h3>Parameter Attributes</h3><P>When defining type library functions, you use attributes on each parameter to indicate how you want the parameter to be treated. The attribute is a hint to the client (caller) about how the parameter will be used. It can be <i>[in]</i>, <i>[out]</i>, or <i>[in, out]</i>. (I've never programmed in Ada, but I'm told this language has a similar feature.) MKTYPLIB compiles the attribute into a bitfield that can be read by the caller. (For those OLE experts who care, it's the wIDLFlags field of the IDLDESC type, which is part of the ELEMDESC type, which is part of . . . never mind. This isn't a book about how to write OLE containers.) In short, Visual Basic can read the attribute, and act accordingly. <P>But before you get too excited about this cool feature, let me warn you. Visual Basic only cares about attributes for the three OLE-specific types: BSTR, VARIANT, and SAFEARRAY. We won't be using those types in this article. They are rarely of any interest in type libraries that map existing DLLs. You'll use them when you create your own DLLs in later articles of this series. I'll have more to say about them then. <P>In this article, attributes are used primarily as documentation. They help you understand the purpose of the parameter. Values passed on the stack (<b>ByVal</b> in Basic) are always <i>[in] </i>parameters. You can't get anything back from the stack. Values can be passed through pointers (<b>ByRef</b> in Basic) for two reasons--because the value is too big to be efficiently passed by value, or because you want to get something back. In ODL you use the <i>[in] </i>attribute to indicate that a reference parameter is for input only. If you're passing by reference in order to get something back, you use the <i>[out] </i>or <i>[in,out] </i>attributes. <P>For example, the <b>SetWindowPlacement</b> function passes a WINDOWPLACEMENT structure. If it were possible to define this function for Basic in a type library (more on why you can't later), it would have an <i>[in] </i>parameter. The <b>GetWindowPlacement</b> function expects a blank WINDOWPLACEMENT structure, which it will fill. Therefore, it would be defined with an <i>[out] </i>parameter, if it could be defined. If there were a <b>ChangeWindowPlacement</b> function that expected a structure on input, but modified that structure on output, it would take an <i>[in, out] </i>parameter. <P><h3>String Parameters</h3><P>Here's the <b>Declare</b> statement for a typical API string function, <b>GetWindowsDirectory</b>. <P><PRE><FONT FACE="COURIER" SIZE="2">#If Win32
Declare Function GetWindowsDirectory Lib &quot;KERNEL32&quot; _
    Alias &quot;GetWindowsDirectoryA&quot; (ByVal lpBuffer As String, _
    ByVal nSize As Long) As Long
#Else
Declare Function GetWindowsDirectory Lib &quot;KERNEL&quot; ( _
    ByVal lpBuffer As String, ByVal nSize As Integer) As Integer
#EndIf
</FONT></PRE><P>Like most API functions that return information in strings, you pass in a buffer to receive the string and an integer with the maximum length of the string. The actual length of the string comes back in the return value. Here's the type library entry:<P><PRE><FONT FACE="COURIER" SIZE="2">    [
    #ifdef WIN32

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -