Using classes exported from a DLL using LoadLibrary
Introduction
I have seen quite a lot of code explaining how to use classes exported from a DLL in an application. However, all these describe the usage of the exported classes by linking implicitly to the DLL. Refreshing our DLL concepts, there are two ways for an application to use a function written in a DLL. The first way is to have your application's source code simply reference symbols contained in the DLL. This causes the loader to implicitly load (and link) the required DLL when the application is invoked. This is known as implicit linking.
The second way is for the application to explicitly load the required DLL (using a LoadLibrary()
call) and explicitly link to the desired exported symbol while the application is running. In other words, if the application decides that it wants to call a function in a DLL, it can explicitly load the DLL into the process' address space, get the virtual memory address of the function contained within the DLL, and then call the function using this memory address. The beauty of this technique is that everything is done while the application is running and the application can unload the DLL from its process address space when it has finished its work with the DLL. As you might have guessed, this technique is known as explicit linking.
Background
So far, I spoke of using functions, but hey, what about using classes exported from a DLL? Well, in the case of implicitly linked DLLs, there is no difference at all. But what about loading DLLs explicitly and using the exported classes? Well, under normal circumstances, it cannot be done, but I wrote this article not to explain to you why it cannot be done, but to give you an idea as to how you can do it. That's right!! Using exported classes by loading a DLL using a LoadLibrary()
call.
But before proceeding further, I warn you that the method given below is sort of a hack, and if for any reason you plan to use it in your project, please take the prior approval of your boss ... (if by any chance you do manage to get his/her approval on this technique!!). However, this column is basically for your understanding and also for extreme cases when you just cant do without this hack.
Using the code
If you look at the sample code, you can see that I have created a Calculator DLL called Calc.DLL and I am using the calculating powers present in the DLL in my console application called UserOfCalc (I couldn't think of a better name!).
// Calc.DLL contains an exported class
// called CCalc that contains 3 methods called Add,
Sub and GetLastFunc (). It is as follows:
// CALC.H - declares the CCalc class
// that is exported from the DLL
// and is imported in the EXE
#include <tchar.h>
#ifdef CALC_EXPORTS
#define CALC_API __declspec (dllexport)
#else
#define CALC_API __declspec (dllimport)
#endif
#define SOME_INSTN_BUF 260
class CALC_API CCalc
{
private:
TCHAR m_szLastUsedFunc[SOME_INSTN_BUF];
public:
CCalc ();
int Add (int i, int j);
int Sub (int i, int j);
TCHAR* GetLastUsedFunc ();
};
The implementation of this DLL is as shown in the file Calc.cpp:
#include "Calc.h"
#include <windows.h>
BOOL APIENTRY DllMain (HANDLE, DWORD, LPVOID)
{
return TRUE;
}
// Ctor, initializes the m_szLastFuncCalled array
CCalc::CCalc ()
{
memset (m_szLastUsedFunc, 0, sizeof (m_szLastUsedFunc));
strcpy (m_szLastUsedFunc, "No function used yet");
}
int CCalc::Add (int i, int j)
{
strcpy (m_szLastUsedFunc, "Add used");
return (i + j);
}
int CCalc::Sub (int i, int j)
{
strcpy (m_szLastUsedFunc, "Sub used");
return (i - j);
}
Now, how do we use the functions present in this Calc class by explicitly loading the DLL? The steps are as follows:
- The first step is to load the Calc.DLL library in your application using
LoadLibrary
.HMODULE hMod = LoadLibrary ("Calc.dll"); if (NULL == hMod) { printf ("LoadLibrary failed\n"); return 1; }
- Since you have the header file of Calc.DLL, the next step is to allocate a block of memory that matches the class layout, and call your constructor code.
CCalc *pCCalc = (CCalc *) malloc (sizeof (CCalc)); if (NULL == pCCalc) { printf ("memory allocation failed\n"); return 1; }
But why in the C++ world are we using
malloc
instead ofnew
!! Because thenew
operator callsCCalc
's constructor for which we don't have any access. Remember, we have to load the DLL dynamically and hence there is no definition ofCCalc
's constructor available to us at build time.Hence we just obtain an uninitialized block of memory whose size equals the
CCalc
class' size. - If you look up the exported functions in Dumpbin.exe (that's located under your Microsoft Visual Studio\VC98\Bin directory), and type dumpbin /exports, <path to="" calc.dll="">you will see a list of functions exported by the DLL. (By the way, I have used a DEF file to unmangle the mangled function names.) It is as shown in the figure.
The list contains the virtual memory address of the functions
Add
,Sub
,GetLastUsedFunc
and the constructor.Since we obtained the block of memory, we have to call the constructor to initialize the block of memory. So, we get the relative virtual address of the constructor in the DLL.
PCTOR pCtor = (PCTOR) GetProcAddress (hMod, "CCalc"); if (NULL == pCtor) { printf ("GetProcAddress failed\n"); return 1; }
PCTOR
is a function pointer and is present at the top of UserOfCalc.cpp. It is defined as follows:typedef void (WINAPI * PCTOR) ();
- Since we have the address of the constructor, we have to explicitly call it to initialize the block of memory obtained by
malloc
. Yes, but how do we associate an object for the constructor?If you remember, when any member function is called, including the constructor, the address of the object gets quietly passed to the called function and this address is stored in the stack. On an Intel based machine, this address of the object is pushed onto the stack via the
ECX
register. So, if you create a class and call its member function, theECX
register contains the 'this
' pointer. This screen shot should make things clearer.If you observe the disassembly window, just after the execution of the line:
ASMLEA ECX, [EBP-4]
you will see that the contents of
ECX
and '&bmw
' are the same. On a machine having a different processor architecture, it could be another register instead ofECX
. We just have to figure that out. - Coming back to our Calc.dll, since we already have the address of a block of memory (that will in the future be an object), we move this address into the
ECX
register by using the Visual C++ inline assembler syntax:ASM__asm { MOV ECX, pCCalc }
- Since we have already obtained the address of the constructor, we just say:
pCtor ();
- When your function pointer
pCtor()
returns from the DLL, it would have initialized the object of the class contained in the DLL. Voila!! - To call any other member function of the Calc class, once again move
pCalc
toECX
and obtain the proc address of the exported function and simply call the function. If you look at the disassembly for any simple class, you will observe that before any member function call, there is always an assembly instruction to move 'this
' intoECX
. This is basically what we have done.
// UserOfCalc.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #include <stdio.h> #include <stdlib.h> #include "Calc.h" typedef int (WINAPI * PADD) (int, int); typedef int (WINAPI * PSUB) (int, int); typedef void (WINAPI * PCTOR) (); typedef TCHAR* (WINAPI * PGETLASTUSEDFUNC) (); int main () { // 1. Load the library HMODULE hMod = LoadLibrary ("Calc.dll"); if (NULL == hMod) { printf ("LoadLibrary failed\n"); return 1; } // 1. // 2. Obtain a block of memory equal to the class layout CCalc *pCCalc = (CCalc *) malloc (sizeof (CCalc)); if (NULL == pCCalc) { printf ("memory allocation failed\n"); return 1; } // 2. // 3. Get the address of the ctor PCTOR pCtor = (PCTOR) GetProcAddress (hMod, "CCalc"); if (NULL == pCtor) { printf ("GetProcAddress Ctor failed\n"); return 1; } // 3. // 4. Move the ptr to the block of memory to ECX // and call the constructor. __asm { MOV ECX, pCCalc } pCtor (); // 4. // 5. Similarly do this for all the other member functions PGETLASTUSEDFUNC pGetLastUsedFunc = (PGETLASTUSEDFUNC) GetProcAddress (hMod, "GetLastUsedFunc"); if (NULL == pGetLastUsedFunc) { printf ("GetProcAddress GetLastUsedFunc failed\n"); return 1; } __asm { MOV ECX, pCCalc } printf ("Last used func in Calc.DLL is: %s\n", pGetLastUsedFunc ()); PADD pAdd = (PADD) GetProcAddress (hMod, "Add"); if (NULL == pAdd) { printf ("GetProcAddress Add failed\n"); return 1; } __asm { MOV ECX, pCCalc } int i = pAdd (15, 10); printf ("i = %d\n", i); __asm { MOV ECX, pCCalc } printf ("Last used func in Calc.DLL is: %s\n", pGetLastUsedFunc ()); PSUB pSub = (PSUB) GetProcAddress (hMod, "Sub"); if (NULL == pSub) { printf ("GetProcAddress Sub failed\n"); return 1; } __asm { MOV ECX, pCCalc } i = pSub (15, 10); printf ("i = %d\n", i); __asm { MOV ECX, pCCalc } printf ("Last used func in Calc.DLL is: %s\n", pGetLastUsedFunc ()); // 5. FreeLibrary (hMod); return 0; }
Points of Interest
If you single step through the source code, this concept will be much clearer to you. For understanding the working of DLLs, please refer 'Programming Applications for Microsoft Windows' or 'Advanced Windows NT', both written by Jeffrey Richter. They are awesome!!
Finally, this is my first shot at writing something (anything!!). So, I would be really happy if you enjoy reading this article and if you find this useful. If you have any suggestions or criticisms, please feel free to mail me at anupshubha@yahoo.com.
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.