An Introduction to DLL Programming in C

This tutorial will demonstrate some basic methods used to create a 32-bit dynamic link library using the C programming language. The example files included with this tutorial will compile with PellesC, LccWin32 and DevC++.

The tutorial will assume that you are writing a dll that will be used with Liberty BASIC and that you are familiar with calling dlls from LB. It also assumes that you are at least a little familiar with the Windows API.

The DLL Entry Point

All dlls have an entry point. This is the code that is run when a program is opening the dll, and also when it is closing the dll. The following is the minimum for a dll entry point function.
BOOL WINAPI __declspec(dllexport) LibMain (HINSTANCE hInst, DWORD Reason, LPVOID Reserved)
{
  if(Reason==DLL_PROCESS_ATTACH)
	{
  	return   TRUE;
  	}

  if(Reason==DLL_PROCESS_DETACH)
  	{
  	return   TRUE;
  	}

  return   FALSE;
}
In some cases you may need to perform some sort of initialiazation for your dll when it is first loaded, perhaps setting some array elements to a certain value or setting aside some memory. The place to do this is in this section of the entry point function:
  if(Reason==DLL_PROCESS_ATTACH)
	{
	  // Perform your initialization here.
  	return   TRUE;
  	}
If your initializing code fails for any reason, like trying to allocate some memory and the computer doesn't have enough memory available, then you should return FALSE instead of TRUE. If FALSE is returned the dll will not be loaded.

This is the place to do any cleanup that may be required when your dll is closed:

  if(Reason==DLL_PROCESS_DETACH)
  	{
	  // Perform your cleanup here.
  	return   TRUE;
  	}

Writing Functions

Returning a numerical value

This first example is a simple function that can be called from LB. The function will be passed two numbers that will be added together, and the function will return their sum.
INT __declspec(dllexport) __stdcall Addit ( int A, int B)
  {
    int C;
    C = A + B;
    return C;
  }

Let's break down this function and examine it.

That's Pretty simple isn't it? Of course no one would use a dll for this but it does demonstrate how to declare the function, pass in numerical values and return a numerical value.

Pointers

Before we start working with strings and structures you need to understand what pointers are. To keep it as simple as possible you can think of a pointer as a variable or structure that is located someplace else. For example if you have a string variable named xyz$ in your LB program and you pass that string to a dll function that takes a pointer to xyz$ as a parameter, then changing the value of the pointer in the dll function directly changes the value of xyz$ in your LB program. So when you are working with a pointer in your dll function you are actually working with and affecting the variable or structure within your LB program.

Returning Strings

This next example accepts pointers to strings. A string pointer can be used in the dll function as a variable and it can also be used to return a string from the function to the calling program. This next function changes the text of a window.

The function takes 4 parameters.

If the pointer to the new text points to a null string, the function will return 0 and end. Otherwise it returns the text that was in the window before it was changed and >0 or 0 depending on success or failure of SetWindowText.

LONG __declspec(dllexport) __stdcall  ChangeWinTxt ( HWND hWin, CHAR *sztxt, LONG numChars, CHAR *szret )
{
	  // declare a local string array to hold up to 2048 characters of text
	char szWinTxt [2048];
	  // declare a local variable of type long
	long lRet;

	if(strcmp(sztxt,"")==0)
	{
	  return 0;  // and exit this function
	}

	  // retrieve up to numChars of text from the window
	GetWindowText ( hWin, (LPTSTR) szWinTxt, numChars );

	  // set the new window text
	lRet = SetWindowText ( hWin, sztxt );

	  // return the previous window text by copying it to 
	  // the string in LB that is pointed to by szret
	sprintf ( szret, "%s", szWinTxt );
	
	  // return will be 0 if SetWindowText failed or >0 if success.
	return lRet;
}
This function could also be written with only one pointer to a string and that string could be used as both a variable containing the new text and the variable that will have the old text returned to it.
LONG __declspec(dllexport) __stdcall  ChangeWinTxtA ( HWND hWin, CHAR *sztxt, LONG numChars )
{
	char szWinTxt [2048];
	long lRet;

	if(strcmp(sztxt,"")==0)
	{
	  return 0;  	
	}
	GetWindowText ( hWin, (LPTSTR) szWinTxt, numChars );
	lRet = SetWindowText ( hWin, sztxt );

	sprintf ( sztxt, "%s", szWinTxt );
	
	return lRet;
}

Using Structures to Return Multiple Values

Using a pointer to a structure takes slightly more work. You should define the structure and it's elements at the top of the dll code so the structure will be global in scope. Let's use a structure that has 3 elements of type long.
struct MyStruct
{
  long NumA;
  long NumB;
  long NumC;
};   // notice the semicolon here
Now we can write a function that takes a pointer - named strct - to a structure. The structure that strct points to must be defined in your Liberty BASIC program and must be constructed with the same type and number of elements as our dll structure that we named MyStruct.
VOID __declspec(dllexport) __stdcall FillStruct (struct MyStruct *strct )
{
    // copy some values to the structure that is in the LB program
  strct->NumA = 100;
  strct->NumB = 200;
  strct->NumC = 300;
}
The structure you define in your LB program does not have to be named "MyStruct" and its elements do not have to be named NumA etc. It only needs to have the same type and number of elements, in the same order, as the structure in your dll. You are not required to return values to all of the elements in the calling program's structure, or even any values. If you don't return a value to a structure element then that element simply retains the value it had before the dll function was called.

You also have the option of setting the values of the structure in your LB program, calling the dll function with a pointer to that structure, using some or all of the values of the structure as parameters and optionally returning new values to that same structure.

You may have noticed that the last function didn't have a "return" statement. That's because it was declared with VOID. It could have been declared as INT or LONG etc. and a return statement used to return some value. VOID was used just to illustrate that the dll function itself does not have to return a value.

Demo files for the above examples Download