26 February 2006

How to Build A DLL and Call It From Another C Program, using MS VC++ 6.0

My professional software development experience is mainly on server side Java/J2EE development. Recently, I had to build a DLL for –let me say – integration with a third party product. Although I was very proficient in the C programming language in collage days, it’s been years ago and it’s been based on vi&gcc&xxgdb development on Unix (mainly Sun Solaris and Digital Alpha workstations) and Linux.

I had trouble in finding a complete and explainatory document for all steps so I decided to write one. I decided to build this short tutorial to guide people who needs to build a DLL and call that DLL from some other program. This tutorial is consisted of two parts, the first section shows the details for building a DLL using MS VC++ 6.0 and the second section shows the way to call it from another C/C++ program. My function takes to char pointers and returns a byte pointer.
I have Microsoft Visual C++ 6.0 Enterprise Edition installed on my Windows XP SP2 computer.

Before starting the tutorial I strongly recommend you to download the free utility program InspectEXE which helps us to explore DLL symbols. I also want to note that I only have a few experience with DLLs and MSVC++ so the way I show here may not be the only and supported /clearer way but this is one way I figured it out and it works. Use it at your own risk :)

1. Building the DLL

Create a new DLL Project (File->New->Projects->Win32 Dynamic-Link Library)

We choose BuildDLL as the project name.



Press the OK button. On the next dialog choose “A DLL that exports some symbols” and press Finish.




Now the IDE builds the source files for you to explore.
BuildDLL.h and BuildDLL.cpp

If you want your function residing in the DLL to be called from outside your function must be preceded with __declspec(dllexport). BuildDLL.h contains a macro for this as a shortcut.

#define BUILDDLL_API __declspec(dllexport)

In order to define my trial function that takes two char*s and returns a byte*

I add the lines

typedef unsigned char byte;

BUILDDLL_API byte* myTrialFunction(char*,char*);
to BuildDLL.h

and the following code listing to BuildDLL.cpp

extern "C" BUILDDLL_API byte* myTrialFunction(char* param1,char* param2)
{
byte* p = (byte*) malloc(sizeof(byte));
*p='a';
return p;
}


Build->BuildDLL.dll(F7) resulted in my dll to be build. Since I have installed InspectExe before, I do right click on the dll open the properties window and choose Exports to see what my dll has exported.




Well., sort of strange names… I check the C++ Style names checkbox and see



Here comes the tip of the first section of the tutorial:

My function’s name is myTrialFunction and I will use this function name when I want to load the dll and use the functionality from another program. In order to avoid the mangled name “?myTrialFunction@@YAPAEPAD0@Z” you should add ‘extern “C”’ to your function decleration.If you fail to do this, the program calling your dll’s function with the name “myTrialFunction” will not be able to locale the method with GetProcAddress().

So the line in BuildDLL.dll becomes
extern "C" BUILDDLL_API byte* myTrialFunction(char*,char*);

and BuildDLL.cpp becomes

extern "C" BUILDDLL_API byte* myTrialFunction(char* param1,char* param2)
{

byte* p = (byte*) malloc(sizeof(byte));
*p='a';
return p;
}


Note: You should add #include stdlib.h to use malloc()

So the DLL export symbols look like




So, this is the end of first section, we have seen how to build a DLL that contains a function having two char pointers as arguments and returning a byte pointer. In the next section we will build another C++ program to load the BuildDLL.dll and call that myTrialFunction function.

2. Calling the function from the DLL

Create a new project by clicking File->New->Project->Win32 Console Application


We chose DLLCaller as the project name.



Choose “A Simple Hello World!” application from the next window and click finish.

There are three API calls that you should know at this point to load a DLL and call a function in it.

LoadLibrary() : Locates and loads the DLL into memory
GetProcAddress(): Gives you a function pointer to your function
FreeLibrary() : To release resources when you are done with the DLL.


Also BuildDLL.dll should be in the DLL lookup path of your new program so you must copy the BuildDLL next to DLLCaller.exe (you can also place it in system32 which is a very ugly way)


First we add the following two lines to define a shortcut to the function pointer.

typedef unsigned char byte;
typedef byte* (*ptr_myTrialFunction)(char*,char*);


and #include for DLL calling API functions.


And the main() method in DLLCaller.c becomes

// DLLCaller.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

int main(int argc, char* argv[])
{
HINSTANCE hDLL = NULL; //A DLL instace
Ptr_myTrialFunction func; //Pointer to my function
byte* function_retval; //return value of the function

hDLL = LoadLibrary("BuildDLL"); // Load the DLL
if(hDLL==NULL)
{
//If the LoadLibrary() call can not locate and load the DLL
//it returns NULL
fprintf(stderr,"%s\n","Unable to load DLL");
return -1;
}
else //DLL is loaded
{
func = (Ptr_myTrialFunction)GetProcAddress(hDLL, "myTrialFunction");
//first parameter to the GetProcAddress is the DLL instance containing the function
//second parameter is the name of function in the DLL
if(func==NULL)
{
//If the GetProcAddress() call can not find the function
//it returns NULL
fprintf(stderr,"%s\n","Unable to locate function");
return -1;
}
else // function is found and a pointer to it is located
{
function_retval=func("a","b");
if(function_retval==NULL)
{
fprintf(stderr,"%s\n","Function can not return null?");
return -1;
}
else // function returned successfully
{
fprintf(stdout,"%d\n",*function_retval);
}
}
}
fprintf(stderr,"%s\n","Finished!");
return 0;
}


Executing the program yields the following result


Where 97 is the ASCII code of ‘a’

Final note for the curious: If we do not put the 'extern "C"' in the decleration of myTrialFunction in the DLL side we can still call the function with its manged name ?myTrialFunction@@YAPAEPAD0@Z" so the line
func = (Ptr_myTrialFunction)GetProcAddress(hDLL, "myTrialFunction");

will become
func = (Ptr_myTrialFunction)GetProcAddress(hDLL, "?myTrialFunction@@YAPAEPAD0@Z");

and it also works.

Good luck!

No comments: