📄 article2.htm
字号:
<P>.<P><h3>Build Settings</h3>
<P>Let me summarize a few of the settings I use in the Microsoft Developer Studio. The same issues will come up with other compilers, though the solutions may be slightly different.
<P><UL><LI> In the Precompiled Headers category of the C/C++ tab, set "Automatic use of precompiled headers through VBUTIL.H."
<LI> Normally you should leave the <b>_MBCS</b> macro defined in the Preprocessor category of the C/C++ tab. This default setting builds your DLL for ANSI strings. You can change the setting to <B>_UNICODE</B> if you want faster string processing under Windows NT®, but you should be aware that your DLL will no longer work under Windows 95, which doesn't handle Unicode. I'll discuss this in more detail in Article 3.
<LI> In the General category of the Link tab, set the output file to your DLL name and desired destination. I prefer to create the DLL in my \Windows directory (C:\WINDOWS on all my machines) because Visual Basic can find it there. Note that during debugging, you'll have to unload any Visual Basic test program that uses the DLL. You'll get a sharing access error if you try to re-link the DLL while Visual Basic is using it. I put the DLL in the same location whether I'm doing a debug or release build. Some readers may prefer to put copies of their debug and release DLLs in separate directories following the Visual C++ defaults. I don't use this method, but it can work if Visual Basic can find the DLL and the type library.
<LI> On the OLE Types tab, set the target type library (.TLB) file to the same destination directory as the DLL. You must expand the Debug and Release folders, select VBUTIL.ODL, and make the settings in the Output filename field. This field is disabled if the .ODL file isn't expanded. The development environment will automatically build the .TLB file, but again you must unload any Basic test program to avoid sharing errors.
<LI> You can embed the type library in the DLL file by making it a resource. I don't embed the resources in function DLLs such as those described here. I do embed them in OLE server DLLs (not discussed in this series). The reason is that I don't want the type library information distributed to end users. It's extra overhead that customers don't need, and I don't want to give away valuable information to my customers' customers, who haven't bought my product. I distribute the .TLB file to my Visual Basic customers, but they don't need to send it to their customers. </UL>
<p><h3>Module Initialization and Standard Definitions</h3><P>I put all the standard stuff required by any DLL in the VBUTIL.CPP and VBUTIL.H files. Normally, these will rarely change during the life of a project. If you put the standard definitions and include files required by all projects in VBUTIL.H and set this file to become part of your precompiled header, you can get much faster builds for changes in the rest of the project.
<P>Here are the standard initialization routines in VBUTIL.CPP:
<P><PRE><FONT FACE="COURIER" SIZE="2">#include "vbutil.h"
HINSTANCE hInst;
// This function is the library entry point. It's technically
// optional for 32-bit programs, but you'll have more options later
// if you define it here.
BOOL WINAPI DllMain(HINSTANCE hInstA, DWORD dwReason, LPVOID lpvReserved)
{
switch (dwReason) {
case DLL_PROCESS_ATTACH:
// The DLL is being mapped into the process's address space.
// Do any additional initialization here.
hInst = hInstA;
break;
case DLL_THREAD_ATTACH:
// A thread is being created.
break;
case DLL_THREAD_DETACH:
// A thread is exiting cleanly.
break;
case DLL_PROCESS_DETACH:
// The DLL is being unmapped from the process's address space.
// Do any additional cleanup here.
hInst = 0;
break;
}
return TRUE;
}
</FONT></PRE>
<P>Some of you may be old enough to remember the ancient times when 16-bit DLLs had a <b>LibMain</b> function for initialization and a <b>WEP</b> (Windows Exit Procedure) function for cleanup. Nowadays, there's just one function and it handles not only program initialization and cleanup, but also the same operations for separate threads started by the DLL. This series isn't going to get into separate threads, but you get the idea.
<P>The <i>hInst </i>variable is saved globally, based on the passed value of the <i>hInstA </i>parameter. This may prove handy in your DLL functions if you ever need an instance handle for loading resources.
<P>VBUTIL.H contains mostly standard include files and a few standard definitions. <P><PRE><FONT FACE="COURIER" SIZE="2">#include <windows.h>
#include <io.h>
#include <iostream.h>
#include <strstrea.h>
#include "oletype.h"
// Temporary buffer size
const TEMP_MAX = 512;
#define DLLAPI WINAPI // Currently evaluates to __stdcall.
// Make ASSERT statement (fails in expressions where it shouldn't be used).
#if defined(DEBUG)
#define ASSERT(f) \
if (f) \
NULL; \
else \
assert(f)
#else
#define ASSERT(f) NULL
#endif
void ErrorHandler(Long e);
DWORD HResultToErr(Long e);
</FONT></PRE>
<P>The contents of all those include files will go into the precompiled header the first time you compile and you won't have to compile them again. You might need to add more include files if you use more run-time functions in your DLL. You'll have to recompile when you add them. We'll be talking about the <b>DLLAPI</b> macro in the next section. The <b>ASSERT</b> macro is a standard one that I use in all my coding. It's purpose is to make asserting within an expression--a dangerous technique--illegal so that it will generate a compile-time error. I borrowed this technique from Steve Maguire's book, <i>Writing Solid Code</i> (Microsoft Press, 1993).
<P><h3>Calling Convention and Linkage</h3><P>Calling conventions are simple: Use <b>stdcall</b>. OLE supports three different calling conventions: <b>stdcall</b>, <b>pascal</b>, and <b>cdecl</b>. Most compilers support only two conventions: <b>pascal</b> and <b>cdecl</b> for 16-bit compilers or <b>stdcall</b> and <b>cdecl</b> for 32-bit compilers. Because this article focuses solely on 32-bit code, we can eliminate <b>pascal</b>. The only reason to use the <b>cdecl</b> convention is so that you can have a variable number of arguments in the C style. The only kind of host that could use such arguments is a C or C++ client. Because we're building a DLL targeted at Visual Basic, we can eliminate <b>cdecl</b>. That leaves <b>stdcall</b>, which is in fact the only calling convention recognized by 32-bit Visual Basic.
<P>You don't need to understand that the <b>stdcall</b> convention means that the callee cleans the stack or that arguments are pushed right to left. These details generally only matter to compiler writers. But you do need to understand something about the <b>stdcall</b> linking conventions.
<P>Almost everyone (myself included) who starts writing DLLs for Visual Basic makes the same mistake. In the old 16-bit days, you specified that a function should be exported to a DLL like this:
<P><PRE><FONT FACE="COURIER" SIZE="2">void __export __pascal DoNothing(void);
</FONT></PRE>
<P>Neither <b>__pascal</b> nor <b>__export</b> work in 32-bit compilers. Their approximate equivalents are <b>__stdcall</b> and <b>__declspec(dllexport)</b>. So the natural thing is to write the function like this:
<P><PRE><FONT FACE="COURIER" SIZE="2">void __declspec(dllexport) __stdcall DoNothing(void);
</FONT></PRE>
<P>In Microsoft Visual C++, this produced the following function name in the DLL:
<P><PRE><FONT FACE="COURIER" SIZE="2">?DoNothing@@YGXXZ</FONT></PRE>
<P>(You can verify this by building a DLL and checking the exported functions with the <b>DUMPBIN</b> program provided with Visual C++.) By default, C++ mangles names in order to support functions with the same names but different parameter lists. All C++ compilers mangle names, although different compilers use different mangling schemes.
<P>You can turn off C++ name mangling like this:
<P><PRE><FONT FACE="COURIER" SIZE="2">extern "C" void __declspec(dllexport) __stdcall DoNothing(void);
</FONT></PRE>
<P>This produces the following name in the DLL:
<P><PRE><FONT FACE="COURIER" SIZE="2">_DoNothing@0</FONT></PRE>
<P>Here the mangling follows the Win32 <b>stdcall</b> mangling convention. The names always have an underscore prefix and a suffix of the "at" sign (@) followed by the number of bytes of parameters in hexadecimal.<P>This isn't what you want for Visual Basic. Of course, your clients could get around this limitation by aliasing all their <b>Declare</b> statements:<P><PRE><FONT FACE="COURIER" SIZE="2">Declare Sub DoNothing Lib "MyLib.DLL" Alias "_DoNothing@0" ()
</FONT></PRE><P>However, it would be extremely rude to impose this extra burden on your users. In fact, it's not very polite to force your users to write any <b>Declare</b> statements. You should provide a type library. And if you do, you can get around this problem by aliasing the functions there. The ODL code would look like this:<P><PRE><FONT FACE="COURIER" SIZE="2">[
entry("_DoNothing@0"),
helpstring("Do absolutely nothing")
]
void WINAPI DoNothing();
</FONT></PRE><P>This works OK, but you have to figure out <b>stdcall</b> mangling for every function. It's not really difficult--usually just multiply the number of parameters by four and append this number in hexadecimal after the @ sign. But the easier solution is to specify the export name in the .DEF file. <P>A .DEF file is optional for Win32 DLLs. Most of the options you set in a .DEF file have acceptable defaults. You can get by without one on most C++ projects, but unfortunately you'll probably want to use one when targeting Visual Basic. You can define your functions like this:
<P><PRE><FONT FACE="COURIER" SIZE="2">void __stdcall DoNothing(void);
</FONT></PRE>
<P>Add a .DEF file entry in the EXPORTS section for each function:
<P><PRE><FONT FACE="COURIER" SIZE="2">EXPORTS
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -