• No results found

WRITING DLL IN C/C++

There are mainly three types of DLLs that can be written in Visual C++.

1. Win32 console mode DLL and

2. Microsoft Foundation Class (MFC) DLL.

3. COM DLL using ATL (see chapter 19) First I’ll discuss about console mode DLL.

1. In Visual C++ (Visual Studio prior to .NET), open a new ‘Win32 Dynamic Link Library’ project.

2. Name it as ExDLL

3. Add a C++ source code file and write the following code in it.

Code 6-1

// an example of win32 console DLL

#include <math.h>

// define what to export from DLL

_declspec(dllexport) int SquareIt(int);

_declspec(dllexport) double pi = 3.141;

_declspec(dllexport)

int solve_quad(int,int,int,float*,float*);

// a typical function passed by value int SquareIt(int x)

{

return x * x;

}

// a typical function passed by reference

int solve_quad(int p, int q, int r, float *q1, float *q2) {

float determ;

determ=q*q-4*p*r;

if(determ<0) {

*q1=0;

*q2=0;

return 0;

}

else {

*q1=((-q)+sqrt(determ))/(2*p);

*q2=((-q)-sqrt(determ))/(2*p);

return 1;

} }

4. Build and compile ExDLL.dll

5. Now open a new ‘Win32 Console Application’ project and name it ExDLLUse.

6. Add a C++ source code file and write the following code in it.

Code 6-2

// this files calls a DLL in runtime

// the DLL must be in same folder as of EXE

// specify full DLL path in Project -> Setting -> Link

#include <iostream.h>

// define what to import from DLL

_declspec (dllimport) int SquareIt(int);

_declspec (dllimport) double pi;

_declspec (dllimport)

int solve_quad(int,int,int,float*,float*);

void main(void) {

int i;

int a,b,c;

float root1,root2;

int status;

// a function called from DLL cout << "Enter an integer " ; cin >> i ;

cout << "Square is " << SquareIt(i) << endl;

// value of a variable called from DLL cout << "Value of Pi is " << pi << endl;

// two values returned from DLL by reference cout << "Enter a,b,c for ax^2+bx+c=0" << endl;

cin >> a >> b >> c ;

status = solve_quad(a,b,c,&root1,&root2);

if(status==1) {

cout << "The solution is" << endl;

cout << root1 << " and " << root2 << endl;

} else

cout << "No real root" << endl;

}

7. Select Project – Setting – Link tab.

8. In the Object/Library module text box, specify the full path of the dll LIB file as shown for example (the path will be different in your computer):

"D:\C-PROGRAMMING\ExDLL\Debug\ExDLL.lib"

9. Now compile ExDLLUse.exe

10. Now copy the ExDLL.dll file in the same folder where ExDLLUse.exe exists.

11. Now execute ExDLLUse.exe. It should run as shown below.

D:\TEMP>exdlluse Enter an integer 5 Square is 25

Value of Pi is 3.141

Enter a,b,c for ax^2+bx+c=0 1 -5 6

The solution is 3 and 2

Hooray, you successfully made a DLL in C++!

Observe several points. First, in a DLL you must specify which function/variable to be exported using _declspec (dllexport) command. In the same way, you need to tell what to import from DLL to an EXE using

_declspec (dllimport) command. In the above example, I showed how to export a variable, a function by value and another function with some values passed by reference. Compare the function with shown in chapter 3.

Can I call a DLL written in C from Visual Basic?

Yes, you can! But in order to call it, you need to write something extra in C DLL source code.

If you just try to call the above from VB, you will get either 'Bad DLL calling convention' or 'Entry point not found' error message. In order to call a C DLL successfully from VB, you'll need to 'tell' the C DLL in that way. That’s why we need to use _stdcall command.

Modify the Code 6-1 as shown in Code 6-3 and save it as ExDLL.cpp (C/++

source code file).

Code 6-3

#include <math.h>

// to import in VB, you should use _stdcall int _stdcall SquareIt(int);

double _stdcall pi = 3.141;

int _stdcall solve_quad(int,int,int,float*,float*);

// a typical function passed by value int _stdcall SquareIt(int x)

{

return x * x;

}

// a typical function passed by reference

int _stdcall solve_quad(int p, int q, int r, float *q1, float

*q2) {

float determ;

determ=q*q-4*p*r;

if(determ<0) {

*q1=0;

*q2=0;

return 0;

}

else {

*q1=((-q)+sqrt(determ))/(2*p);

*q2=((-q)-sqrt(determ))/(2*p);

return 1;

} }

Since we shall call this C DLL from VB, we don't need any C EXE to test it.

But we must define a .DEF file to tell VB which functions/variables can be found from our DLL.

So, in the ExDLL project, add a text file named ExDLL.def and write following code in it.

Code 6-4

LIBRARY ExDLL DESCRIPTION My Test DLL

EXPORTS

SquareIt

solve_quad pi

The EXPORTS command provides ‘entry points of DLLs’. LIBRARY is the name of DLL without any extension. In DESCRIPTION you can write anything. To add any extra comment, use semicolon. There are several other commands you can write in a .def file. We shall discuss them later.

Now compile the DLL. You should not encounter any error message. (There may be some warning about double to float conversion etc. for this particular example problem, but you can ignore them)

DLL is now ready. Next we shall see how to call it from VB.

Open a standard EXE VB project. Add a standard module (BAS file). Write the following code in it. In your computer, the path of DLL may be different. If you place the compiled DLL file in \WINDOWS\SYSTEM folder, you can just specify the file name as “exdll” only.

Code 6-5

Declare Function SquareIt Lib "d:\c-programming\exdll\debug\exdll.dll" _ (ByVal x As Integer) As Integer

Declare Function solve_quad Lib "d:\c-programming\exdll\debug\exdll.dll" _

(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer, _ ByRef r1 As Single, ByRef r2 As Single) As Integer

Create a command button in the form. In its 'Click' event, write the following code.

Code 6-6

Private Sub Command1_Click() Dim z As Integer

z = SquareIt(5)

MsgBox "Square is " & z

Dim a As Integer Dim b As Integer Dim c As Integer Dim s1 As Single Dim s2 As Single

Dim status As Integer

status = solve_quad(1, -5, 6, s1, s2)

'try with status = solve_quad(1, 1, 6, s1, s2) If status = 1 Then

MsgBox "Root1 " & s1 MsgBox "Root2 " & s2 Else

MsgBox "No real root"

End If End Sub

Now run the VB project. Click on the command button. Everything should be fine and you will get the expected results! So, you have called a DLL written in C from a VB application.

More general way of writing C/++ DLL

You are probably wondering why you should write the C/++ DLL differently for calling from C/++ and VB EXE. In fact you should not! Now I am going to show you how to write a DLL in C/++ so that both C/++ and VB EXE will be able to call it without any modification!

To achieve this feat, instead of _dllexport or _stdcall, we need to write

BOOL APIENTRY. For example re-write code 6-3 (ExDLL.cpp) as follows.

Code 6-7

#include <math.h>

#include <windows.h>

BOOL APIENTRY SquareIt(int x,int *y) {

*y = x * x;

return 1;

}

BOOL APIENTRY solve_quad(int p, int q, int r, float *q1, float

*q2) {

float determ;

determ=q*q-4*p*r;

if(determ<0) {

*q1=0;

*q2=0;

return 0;

}

else {

*q1=((-q)+sqrt(determ))/(2*p);

*q2=((-q)-sqrt(determ))/(2*p);

return 1;

} }

Change code 6-4 (ExDLL.def) slightly as shown below.

Code 6-8

LIBRARY ExDLL DESCRIPTION My Test DLL

EXPORTS

SquareIt solve_quad

Now compile and build the ExDLL.dll file.

To use the new DLL in a C/++ executable, change code 6-2 as shown below.

Code 6-9

#include <iostream.h>

#include <windows.h>

// define what to import from DLL

_declspec (dllimport) BOOL APIENTRY SquareIt(int,int*);

_declspec (dllexport) BOOL APIENTRY solve_quad(int,int,int,float*,float*);

void main(void) {

int i;

int a,b,c,y,z;

float root1,root2;

int status;

cout << "Enter an integer " ; cin >> i ;

z = SquareIt(i,&y);

cout << "Square is " << y << endl;

cout << "Enter a,b,c for ax^2+bx+c=0" << endl;

cin >> a >> b >> c ;

status = solve_quad(a,b,c,&root1,&root2);

if(status==1) {

cout << "The solution is" << endl;

cout << root1 << " and " << root2 << endl;

} else

cout << "No real root" << endl;

}

Before running, remember to copy the DLL in C/++ exe’s path!

To call the DLL from VB, replace code 6-5 by this new code.

Code 6-10

Declare Function SquareIt Lib "d:\c-programming\exdll\debug\exdll.dll" _

(ByVal x As Integer, ByRef y As Integer) As Integer

Declare Function solve_quad Lib "d:\c-programming\exdll\debug\exdll.dll" _

(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer, _ ByRef r1 As Single, ByRef r2 As Single) As Integer

And finally, change code 6-6 as this.

Code 6-11

Private Sub Command1_Click() Dim z As Integer

Dim y As Integer z = SquareIt(5, y)

MsgBox "Square is " & y

Dim a As Integer Dim b As Integer Dim c As Integer Dim s1 As Single Dim s2 As Single

Dim status As Integer

status = solve_quad(1, -5, 6, s1, s2)

'try with status = solve_quad(1, 1, 6, s1, s2) If status = 1 Then

MsgBox "Root1 " & s1 MsgBox "Root2 " & s2 Else

MsgBox "No real root"

End If End Sub

This version of DLL is noticeably better than that we discussed earlier! The type

BOOL returns an integer – either 1 (true) or 0 (false). The calling convention in code 6-9 might have been written in another way using GetProcAddress. But I am leaving that stuff!

Why should I bother writing a DLL in C and call it from VB? I can write the DLL in VB itself.

Of course you can write a DLL in VB as well. But main advantage of writing a DLL in C is performance. A program (either EXE or DLL) written in C generally executes faster than those written in VB. (This sentence needs a little bit explanation. The speed of your applications mainly depends on the algorithm adopted rather than on the language you are using. A good VB program can easily outperform a poorly written C program. Then why it is said the C program executes faster? Well, take an example of string operations. Normally in C you use char* to perform string manipulation. But VB does not have explicit pointers. However, it internally handles strings in the same way as that of pointers in C. Definitely this internal operation takes some more time than direct handling of pointers. Similarly i++ executes faster than i = i + 1 because in former case the accumulator in memory directly gets incremented while in case of the later, variable’s value is first copied into the registers and then the addition is performed. Moreover VB programs often calls Windows API functions, which are mostly written in C. Using the same logic, programs written in assembly or machine languages run fastest because the processor directly executes them without any translation.) In many applications speed is significant. Apart from that, I already told you that C could interact with hardware in much more versatile manner compared to VB. So often, writing a DLL in C becomes a necessity.

Can I call any C/++ DLL in this way?

Unfortunately, you can’t. Why? Well, to call a DLL you must know how to call its functions. Unless the developer of the DLL provides you with the necessary documentation, you don’t know how to call it. For example, Microsoft tells you how to call its APIs. So, you can use them. Moreover, some DLLs can’t be used without registering/licensing. All these registration stuff can be described in a .def file. These things are quite scary and I shall come to this point later.

Next we’re going to build a MFC DLL!

In Visual C++, open a new MFCAppWizard(dll) project and name it as MyDll.

The VC will generate lots of code for you. All you need to add your own codes in between. I am showing your own code in blue color and some VC generated code in pink color as a guide where you need to insert your own code.

Add the following code in Mydll.h file.

Code 6-12

#ifndef __AFXWIN_H__

#error include 'stdafx.h' before including this file for PCH

#endif

#include "resource.h" // main symbols

// my code starts

_declspec(dllexport) void WINAPI ThickRectangle(CDC* pDC, int x1, int y1,int x2, int y2, int t);

_declspec(dllexport) void WINAPI ThickEllipse(CDC* pDC, int x1, int y1, int x2, int y2, int t);

_declspec(dllexport) void WINAPI ThickPixel(CDC* pDC, int x1, int y1);

// my code ends

//////////////////////////////////////////////////////////////

///////////////

// CMyDllApp

// See MyDll.cpp for the implementation of this class

Now add the following code in MyDll.cpp file.

Code 6-13

//////////////////////////////////////////////////////////////

///////////////

// The one and only CMyDllApp object CMyDllApp theApp;

// my code starts here

_declspec(dllexport) void WINAPI ThickRectangle(CDC* pDC, int x1, int y1,int x2, int y2, int t)

_declspec(dllexport) void WINAPI ThickEllipse(CDC* pDC, int x1, int y1, int x2, int y2, int t)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

CBrush newbrush;

CBrush* oldbrush;

pDC->Ellipse(x1,y1,x2,y2);

pDC->Ellipse(x1+t,y1+t,x2-t,y2-t);

newbrush.CreateSolidBrush(RGB(255,0,0));

oldbrush=pDC->SelectObject(&newbrush);

pDC->FloodFill(x1+(t/2),y1+((y2-y1)/2),RGB(0,0,0));

pDC->SelectObject(oldbrush);

newbrush.DeleteObject();

}

_declspec(dllexport) void WINAPI ThickPixel(CDC* pDC, int x1, int y1)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

CPen newpen;

CPen* oldpen;

pDC->SetPixel(x1,y1,0L);

newpen.CreatePen(PS_SOLID,2,RGB(255,255,0));

oldpen=pDC->SelectObject(&newpen);

pDC->MoveTo(x1-5,y1);

pDC->LineTo(x1-1,y1);

pDC->MoveTo(x1+1,y1);

pDC->LineTo(x1+5,y1);

pDC->MoveTo(x1,y1-5);

pDC->LineTo(x1,y1-1);

pDC->MoveTo(x1,y1+1);

pDC->LineTo(x1,y1+5);

pDC->SelectObject(oldpen);

newpen.DeleteObject();

}

// my code ends here

Now compile the dll. It should compile without any error message.

To test our generated dll, we need to make an MFC EXE to test it. Create a new MFCAppWizard(exe) named MyDllExe.

In MyDllExeView.h, add the following code.

Code 6-14

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

// my code starts here

extern void WINAPI ThickRectangle(CDC* pDC, int x1, int y1, int x2, int y2,int t);

extern void WINAPI ThickEllipse(CDC* pDC, int x1, int y1, int x2, int y2,int t);

extern void WINAPI ThickPixel(CDC* pDC, int x1, int y1);

// my code ends here

class CMyDllExeView : public CView {

protected: // create from serialization only

In MyDllExeView.cpp file, add the following code.

Code 6-15

void CMyDllExeView::OnDraw(CDC* pDC) {

CMyDllExeDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here // my code starts here

// call ThickPixel several times for(int i=50;i<900;i++)

ThickPixel(pDC,i,75);

// call ThickRectangle

ThickRectangle(pDC,50,300,75,350,20);

ThickRectangle(pDC,150,350,250,450,25);

ThickRectangle(pDC,400,200,700,600,25);

// call ThickEllipse

ThickEllipse(pDC,50,100,75,150,10);

ThickEllipse(pDC,150,150,250,250,15);

ThickEllipse(pDC,450,250,650,550,10);

pDC->TextOut(50,50,"Saikat's first DLL in Visual C++!");

pDC->MoveTo(200,200);

pDC->LineTo(300,300);

// my code ends here }

//////////////////////////////////////////////////////////////

///////////////

// CMyDllExeView diagnostics

As stated before, from Project – Settings – Link tab, specify the full path of Object/Library module (for the DLL’s LIB file). Now compile the EXE file. Be sure to copy the DLL file into same folder as of the EXE. Run the EXE and you should see some colorful boxes and ellipses! In this example, I used some graphics features of VC++ intentionally to demonstrate a true MFC example.

You can also incorporate the same functions as discussed in console mode DLL creation. Study the code carefully and you will realize what is the general procedure to write MFC DLLs! Of course, things may appear Hebrew to you if you are new to VC++. That’s why I recommend that you read a standard Visual C++ learning side by side. In fact, there are lots of more things need to be taken into consideration while developing MFC DLLs. A detail discussion of these topics is beyond the scope of this book. Honestly speaking, I myself don’t know all the features of VC++!

What is the meaning of _stdcall?

Functions using the standard calling convention remove the parameters from the stack before they return to the caller. In the normal C/++ calling convention, the caller cleans up the stack instead of the function. Most other language such as VB or Pascal use standard calling convention by default. The standard calling convention so named because all Win32 API functions, except few that take variable arguments, use it. Variable argument functions continue to use C calling convention of _cdecl. Virtually all functions offered by COM interfaces on Microsoft platforms use the standard calling convention.

Gee! So you learnt to create both console mode and MFC DLLs in C++! So far we only exported functions and variables from a DLL. There remains to learn another important task. How to export classes from a DLL?

Can I use DLL in Unix?

Yeah! In Unix, DLLs are known as ‘shared objects’ (*.so) – they work much like in the same way as that of DLLs in Windows.

Final notes on DLL – all’s not well that ends well

Don’t think that DLLs are solutions of all problems! Calling DLLs have traps of their own! As long as you are passing simple data types (e.g. number, string etc.) between C and VB you are fine. Trouble starts when you try to pass complex data types such as array, object etc.

Consider code 5-6 again. It showed how to find out matrix product in C/++.

Now, suppose you want to make it a DLL. You can easily do that in C/++ by

following the methods discussed (dllexport, stdcall or BOOL API etc.) in this chapter. Calling the DLL from C/++ won’t have any problem. But how do you call it from VB? Observe that the line in C/++ which calls the function p =

ProductMatrix(matrix1,matrix2,row1,col1,col2) does not have any direct equivalent in VB!

In C/++ when you state matrix1, you are actually passing the address of the first element of the matrix. Also the variable p after calling the function contains an address which stores address of an integer. How do you make VB understand this glitch? How do you return as user defined structure or object from C/++ to VB, which does not support that structure or object? So inter operability among several languages indeed a problem, a big problem. We’ll see in the following program how to pass a matrix through a C function that returns a structure and then calling it in VB.

Create a DLL named ArrayStructure using the methodology described already.

The code is given.

quad* _stdcall solve_quad(int *a);

quad* _stdcall solve_quad(int *a) {

if(determ<0) /* no real root */

{

x->r1=0;

x->r2=0;

x->status=(char*)malloc(13*sizeof(char));

strcpy(x->status,"No real root");

return x;

strcpy(x->status,"Two real roots");

return x;

} }

Create also the .def file as usual.

LIBRARY ArrayStructure EXPORTS solve_quad

Now use the following code to call the function in VB6.

Code 6-17

Private Declare Sub CopyMemory Lib "kernel32" Alias

"RtlMoveMemory" _

(Destination As Any, ByVal Source As Any, ByVal Length As Long)

Private Declare Function solve_quad Lib "arraystructure.dll" _ (ByVal m As Long) As Long

Private Sub Command1_Click() Dim adr As Long

Dim z(3) As Long Dim Root1 As Single Dim Root2 As Single Dim Solution As String

z(0) = 1 z(1) = -5 z(2) = 6

adr = solve_quad(VarPtr(z(0))) Call CopyMemory(Root1, adr, 4) Call CopyMemory(Root2, adr + 4, 4) Call CopyMemory(Solution, adr + 8, 4)

MsgBox "Root1 = " & Root1 & vbCrLf & "Root2 = " & Root2 _

& vbCrLf & "Solution = " & Solution End Sub

Execute the VB6 program and you’ll see two roots and status of solution.

I understand that code 6-17 requires some clarification. VarPtr is an undocumented V6B function that returns the address of a variable as long. Two similar functions are StrPtr and ObjPtr. In code 17-16, we pass the matrix as the address of its first element i.e. a(0). The quad* indicates that the function

solve_quad returns an address which holds a data of type quad. In C/++, an

int needs 4 bytes. In VB6, a long needs the same amount of bytes. That’s why we defined z(3) as Long in VB6 instead of Integer. (You may wonder why there is such a discrepancy between C and VB6 data types. Well, it is a logical question. However, this has been rectified in VB.NET where Integer takes 4 bytes as that of in C and Short takes 2 bytes as of earlier Integer!)

Anyway, back to our business. The adr stores the memory address of quad structure after calling the solve_quad function. Next the API function

CopyMemory(Root1, adr, 4) assigns the content of address adr into variable

Root1. It copies 4 bytes of data (since we defined Root1 as Single, which also takes 4 bytes) beginning from address adr.

By the way, these direct memory manipulation functions have been removed from VB.NET!

Exercise 6-1

Rewrite the code 6-16 using BOOL APIENTRY instead of _stdcall. (Hints:

Suppose you want to call this function inside a C main function. Then you can also use prototype as void solve_quad(int *a, quad **m). Inside function, write *m = x instead of return x in code 6-16. While calling the

Suppose you want to call this function inside a C main function. Then you can also use prototype as void solve_quad(int *a, quad **m). Inside function, write *m = x instead of return x in code 6-16. While calling the