May Seventh 2002 From: Bob Zale, President PowerBASIC, Inc. PowerBASIC Gazette #29 ====================== Subject: Use LoadLibrary API to avoid "MISSING EXPORT" Errors ============================================================= Have you visited our web site lately? Strange as it may seem, there's a PowerSoapOpera unfolding, right on Page One! It involves a wizard, a prophet, a funny man in a white suit, and it may even foretell the PowerFuture! Stop by today to see if you'll be the one to unravel the subtle clues... Visit us now at www.powerbasic.com -- and be sure to leave a comment in the PowerForums! Once again, I'm happy we're able to offer you another informative article from Lance Edmonds, a PowerBASIC Staff Member. He's helped many of us, both directly and via the PowerBASIC BBS. As always, we hope you find this Gazette to offer interesting reading... Subject: Use LoadLibrary API to avoid "MISSING EXPORT" Errors ============================================================= Implicit linking ---------------- By default, implicit (Load-time) linking is used for every API Sub/Function declared in WIN32API.INC and the associated INC files supplied with PowerBASIC. Implicit linking is used whenever a DECLARE statement includes a LIB clause, and the named Sub/Function is referenced or used in the application. When an application is initially loaded, and before it starts being executed, Windows loads all of the DLLs that the application requires, as specified in the applications Import Table. However, if any of the DLLs are not present, Windows produces a missing DLL error, and the application is terminated before it is even started. This is the primary purpose of the LIB clause in the DECLARE statement. Also, Windows will match up the actual Sub/Function names in the Import Table with those in the Export Table of the associated DLL. The name specified in the Import Table is derived from the ALIAS clause of the DECLARE statement, or if an alias is not specified, the capitalized Sub/Function name is used. Windows requires that every single entry in the applications Import Table must be successfully matched to an exported Sub/Function in the named DLL. If not, the application is immediately terminated, and the user presented with a "Missing Export" error message. While implicit linking is by far the easiest way to call API functions, it presents a problem when an application calls API functions that are only present in some versions of Windows. Restricting the choice of platforms ----------------------------------- For applications using features specific to Win2000 and WinXP, the #OPTION VERSION5 metastatement can be used to limit the application to use on just those platforms. An attempt to run it on earlier platforms will produce a "new version of Windows required" error, rather than a cryptic "export" error message. However, sometimes applications are required to run on all versions of Windows, and could be required to take advantage of features on newer versions of Windows, but only if such features are available. Explicit Linking joins the fray ------------------------------- Since a Missing Export error is generated by Windows before the application even starts running, it is not possible to write a program, using implicit linking, which programmatically checks the Windows version to decide what it can call and what it must avoid. Therefore, to get the application to start running on a platform that may have either a missing DLL or a missing export function, we must abandon implicit linking of platform-specific Subs/Functions, and use explicit (run-time) linking instead. LIB-less DECLARE statements --------------------------- The first step in achieving explicit linking is to remove the LIB and ALIAS clauses from the DECLARE statement of the target API Subs and Functions. Next, we replace all direct calls to those Subs and Functions with a small piece of code that dynamically checks the availability of the API before calling it. To achieve the first part, editing the WIN32API.INC file may seem the easiest route, but that comes with a small cost -- any changes made to that file will be lost if you install an updated version of the file in the future. As an aside, .INC files are updated quite regularly, and the latest version can always be downloaded from http://www.powerbasic.com/files/pub/pbwin/win32api.zip The best solution is to copy the DECLARE statement from the include file, and paste it directly into your application code. Then the LIB clause can be removed. Finally, to avoid conflicts with the DECLARE statement in the INC file, the function name is modified slightly to make it unique. This can be done since this new DECLARE statement acts purely as a template for the code we'll be discussing below. For example, looking at the GetDiskFreeSpaceEx() API (which only became available in the OSR2 release of Windows 95), the original DECLARE looks like this: DECLARE FUNCTION GetDiskFreeSpaceEx LIB "KERNEL32.DLL" ALIAS _ "GetDiskFreeSpaceExA" (lpPathName AS ASCIIZ, _ lpFreeBytesAvailableToCaller AS QUAD, _ lpTotalNumberOfBytes AS QUAD, _ lpTotalNumberOfFreeBytes AS QUAD) AS LONG Using the above guidelines, our modified template DECLARE will look like this: DECLARE FUNCTION t_GetDiskFreeSpaceEx (lpPathName AS ASCIIZ, _ lpFreeBytesAvailableToCaller AS QUAD, _ lpTotalNumberOfBytes AS QUAD, _ lpTotalNumberOfFreeBytes AS QUAD) AS LONG We used the prefix "t_" here to indicate that it is intended as a template function declaration only. Dynamically checking the API exists ----------------------------------- Since we removed the LIB clause from the new DECLARE statement, we have to instruct Windows to try to open the DLL that we know may contain the target API. In the above case, this is KERNEL32.DLL, and we load it like this: LOCAL hLib AS DWORD hLib = LoadLibrary("KERNEL32.DLL") IF ISFALSE hLib THEN MSGBOX "KERNEL32.DLL cannot be found or loaded!" ELSE ' We have a handle to the DLL! END IF At this point, we will know if the DLL was loaded successfully or not. Naturally, if the DLL could not be loaded, we warn the user and bail out. A real application should handle this situation with more finesse, but this is suitable for our concept explanation. The next step is to query whether the name of the Sub/Function is present in the DLL. For this, we use the precise name given in the ALIAS clause, thus: LOCAL pFunc AS DWORD ' Capitalization is very important! pFunc = GetProcAddress(hLib, "GetDiskFreeSpaceExA") IF ISFALSE pFunc MSGBOX "KERNEL32.DLL does not contain ""GetDiskFreeSpaceExA""!" ELSE ' We have the address of the target Sub/Function END IF At this point, assuming the target Sub/Function is present (GetProcAddress returns a pointer to the Sub/Function, or zero if the function is not in the DLL), thus: LOCAL szPath AS ASCIIZ * %MAX_PATH, FreeBytesTo App&& LOCAL TotalBytes&&, TotalFreeBytes&&, Result& szPath = "C:\" CALL DWORD pFunc USING t_GetDiskFreeSpaceEx(szPath, _ FreeBytesToApp&&, TotalBytes&&, _ TotalFreeBytes&&) TO Result& And there we have it, one API function whose availability has been dynamically determined. Now that a pointer to the API has been obtained, it can be called again and again without going through the whole process again. However, this also means that we should clean up after ourselves, so as soon as the API is no longer required (or we terminate our application), we should release the library, thus: CALL FreeLibrary(hLib) A practical example ------------------- Of course, there are a few things that can be done to stream line this approach in a regular application where the API may need to be called multiple times. For example, loading the DLL and getting the address of the API could be wrapped into a Function that does the LoadLibrary and GetProcAddress calls only once for the duration of the application, thus reducing overhead. Another approach could be to create a general purpose function to deal with LoadLibrary and GetProcAddress procedures. For example: '------------------------------------------------------------------------- ' Explicit (Dynamic run-time) linking example for 32-bit PB/DLL and PB/CC ' By Lance C Edmonds, 2001. This code is released into the public domain. '------------------------------------------------------------------------- #COMPILE EXE #DIM ALL #INCLUDE "WIN32API.INC" '------------------------------------------------------------------------- ' The original DECLARE looks like this: ' DECLARE FUNCTION GetDiskFreeSpaceEx LIB "KERNEL32.DLL" ALIAS _ ' "GetDiskFreeSpaceExA" (lpPathName AS ASCIIZ, _ ' lpFreeBytesAvailableToCaller AS QUAD, _ ' lpTotalNumberOfBytes AS QUAD, lpTotalNumberOfFreeBytes AS QUAD) AS LONG ' Specify a modified "template DECLARE" for the CALL..DWORD statement: DECLARE FUNCTION t_GetDiskFreeSpaceEx (lpPathName AS ASCIIZ, _ lpFreeBytesAvailableToCaller AS QUAD, lpTotalNumberOfBytes AS QUAD, _ lpTotalNumberOfFreeBytes AS QUAD) AS LONG ' Declare our helper function DECLARE FUNCTION GetApiAddress(sDll AS ASCIIZ, _ sAPI AS ASCIIZ, BYVAL hLib AS LONG) AS DWORD '------------------------------------------------------------------------- FUNCTION PBMAIN LOCAL x AS LONG LOCAL pFunc AS DWORD LOCAL hLib AS DWORD LOCAL szPath AS ASCIIZ * %MAX_PATH LOCAL FreeBytesToApp AS QUAD LOCAL TotalBytes AS QUAD LOCAL TotalFreeBytes AS QUAD LOCAL Result AS LONG LOCAL Msg AS STRING LOCAL TmpStr AS STRING ' Load the library and get the address pFunc = GetApiAddress("KERNEL32.DLL", "GetDiskFreeSpaceExA", hLib) ' The API is available, lets use it IF pFunc THEN ' Loop though all drive letters FOR x = 1 TO 26 ' Call the API address directly, using our modified declare szPath = CHR$(x + 64) & ":" CALL DWORD pFunc USING t_GetDiskFreeSpaceEx(szPath, _ FreeBytesToApp, TotalBytes, TotalFreeBytes) TO Result ' If Result is non-zero, the API call produced useful results IF Result THEN TmpStr = SPACE$(20) RSET tmpStr = FORMAT$(FreeBytesToApp, "#,") Msg = Msg + TmpStr & " bytes available on Drive " _ & szPath & $CRLF END IF NEXT ' Display our results $IF %DEF(%PB_CC32) STDOUT Msg WAITKEY$ $ELSE MSGBOX Msg $ENDIF ELSE ' Display our results $IF %DEF(%PB_CC32) STDOUT "The API is not supported on this platform" WAITKEY$ $ELSE MSGBOX "The API is not supported on this platform" $ENDIF END IF ' At the app end, release the library, and clear pFunc & hLib ' Also the syntax: pFunc = GetApiAddress(BYVAL 0&, BYVAL 0&, hLib) pFunc = GetApiAddress("", "", hLib) END FUNCTION '------------------------------------------------------------------------- FUNCTION GetApiAddress(sDll AS ASCIIZ, sAPI AS ASCIIZ, _ BYVAL hLib AS LONG) AS DWORD ' If hLib is zero then DLL is loaded, otherwise the existing ' handle is used ' If sDLL is passed BYVAL 0& or simply "" (and hLib is non-zero), ' then the DLL is unloaded IF ISFALSE hLib THEN ' No library handle passed, so we'll try to load the DLL hLib = LoadLibrary(sDLL) ' Hmmm, the DLL is missing IF ISFALSE hLib THEN EXIT FUNCTION ELSEIF VARPTR(sDLL) = 0 OR LEN(sDLL) = 0 THEN ' Free the DLL CALL FreeLibrary(hLib) hLib = 0 EXIT FUNCTION END IF IF sAPI <> "" THEN FUNCTION = GetProcAddress(hLib, sAPI) END FUNCTION =================================================================== If you haven't yet acquired all three PowerBASIC Compilers, now might be a good time to look a bit further. Pricing remains unchanged at the most competitive levels known in our industry. PB/CC, our Console Compiler, creates text mode programs connected right to the heart of Win32. No talking paper clips, no popups to intrude, just raw programming power. For all the latest info on PB/CC ($159), go to http://www.powerbasic.com/products/pbcc/ If your interest leans towards graphical modes, we can help you there, as well. With DDT, the latest versions of PB/DLL can easily create complete graphical applications (GUI, GUI, GUI) with the best in Windows "Look and Feel". To learn about PB/DLL ($189), check it all out at http://www.powerbasic.com/products/pbdll32/ Or, if DOS is your interest, see everything about the latest PowerBASIC/Dos at http://www.powerbasic.com/products/pbdos/ Some other interesting features and price reductions... PowerTree BTree Manager 1.1 is a brand new release of this time-tested product. The original "Faster/Smaller" BTree manager, PowerTree manages indexed data with ease. You design data files... PowerTree uses them. Find one word, or sequence them, at lightning speed. With auto-locking, multi-user and multi-threading is a breeze. Originally more than double the price, it's now just $99 total for all versions combined -- Win32, Win16, Dos -- and there's never a royalty for when you distribute with your code. Buy it once, use and distribute it forever. Console Tools will give your PB/CC apps some serious sizzle. Add message boxes, progress bars, list boxes, input boxes, and other GUI features. Fast Peek and Poke, Bsave and Bload, even GUI pulldown menus and right-click popup context menus, in the Pro version. You can Maximize, Minimize, Un-Minimize, Restore, Hide, Show, Position, Center, and Size the console window. You can detect and control the console's FullScreen/Windowed mode, change the console window title and icon, and control your program's place in the Windows Z-order. For more information, visit http://www.powerbasic.com/products/contools/ Console Tools Standard is priced at $49.95, while the Pro version is $99.95. Graphics Tools offers Pizzazz! Must graphics for Windows be so darned complicated? Of course not. With Graphics Tools, you'll need no APIs, you'll need no device contexts, you won't even need to deal with wm_paint messages! Just call any of the easy functions like DrawCircle, DrawLine, DrawButton, DrawFlood, and DisplayBitmap. And for Console Apps, how about a sophisticated background image, using pictures, buttons, checkboxes, more... For full details, see http://www.powerbasic.com/products/graftool/ and remember the price is only $49.95. Graphics Tools requires Console Tools when used with PB/CC. SQL Tools presents a total database solution for Win32. Use the power of SQL to access relational databases from Microsoft Access, SQL Server, Oracle, FoxPro, dBase, Btrieve, and 50 other popular formats. Open a database with a single line of code, and use standard SQL statements to build powerful, multi-user database programs. SQL Tools is 100% procedural -- you perform database operations with simple functions. No handles, No objects, No Windows API calls. SQL Tools Standard provides all of the functions you'll need for small and medium sized database applications. SQL Tools Pro offers all of the Standard Version functionality, plus a large number of advanced features like multithreaded operation, access to 256 concurrent databases, batched SQL statements, and more. For more specific details as well as coding samples, visit the PowerBASIC web site at http://www.powerbasic.com/products/sqltools/ SQL Tools Standard is priced at $99.95, while the Pro version is $199.95. To order any PowerBASIC products, you can call us today at (888) 659-8000 or (941) 473-7300, fax an order to (941) 681-3100. Place an electronic order on our web site at www.powerbasic.com, or even mail it to us. But no matter the method, you can order today and do so with confidence. PowerBASIC products, other than downloads, are offered with a money-back guarantee for a full 30 days from the transaction date. =================================================================== Order online at https://www.powerbasic.com/shop/ or just send an email with all pertinent information to sales@powerbasic.com We'll take it from there! ------------------------------------------------------------------- Most PowerBASIC products (those without printed books) can now be delivered by electronic mail. No wait for a package to arrive... No high shipping costs... For just $6 per order, no matter how many products, we'll deliver directly to your computer. If you're outside the U.S., savings might be greater. You won't pay taxes or duties to a freight company or postal service, because they aren't involved in the delivery. Check your tax code to be sure, but some countries charge no tax at all on transactions of this type. It could just be your lucky day! ==================================================================== Is your PowerBASIC Gazette Electronic Edition subscription coming to you at home or work? If you don't want to miss a single issue, why not subscribe from both email addresses? Send your subscription request to email@powerbasic.com and please include your name and all email addresses you'd like to add as well as your Zip or Postal Code. If you know someone else who would enjoy this newsletter please forward a copy to them so they can subscribe. ==================================================================== All contents Copyright (c) 2002-2009 PowerBASIC Inc All Rights Reserved. PowerBASIC is a registered trademark of PowerBASIC, Inc. PB/CC, PB/DLL, and PowerTREE are trademarks of PowerBASIC Inc. Other names are trademarks or registered trademarks of their owners. ==================================================================== PowerBASIC Gazette - Electronic Edition Volume 1 - Issue 29 PowerBASIC, Inc. (888) 659-8000 Sales 2061 Englewood Road (941) 473-7300 Voice Englewood, FL 34223 (941) 681-3100 Fax Visit us on the World Wide Web at www.powerbasic.com Email Sales: sales@powerbasic.com This newsletter is only sent to email addresses in our subscription list. If you have received this newsletter by mistake or no longer wish to receive it, please send a simple unsubscribe request to support@powerbasic.com with your name and customer number. This newsletter is best viewed with a fixed-width font. ====================================================================