Windows 32-bit DLLs with Harbour Code - Any More Progress

Post Reply
angelo.c
Posts: 36
Joined: Thu Mar 30, 2006 11:19 am

Windows 32-bit DLLs with Harbour Code - Any More Progress

Post by angelo.c »

Hello,

FWH8.05/BCC5.5 is now my development environment.

I have read the posts on creating a self contained DLL in Harbour that can be called from other programming platforms.

It seems that the one called level 1 (maindll.c Windows self-contained DLL entry point) would be the easiest to implement ... but may be limited to one parameter being passed.

Has any one successfully implemented a DLL function call from another programming language to a function defined in Harbour within the Windows environment. If so, would you be kind enough to share your approach.

I am new to Harbour (I've got a FiveWin++ background) and so would like a little "kick in the right direction".

My application that I would like to port over is written in FW++/Alaska XBase++/c/c++. I use c/c++ to increase performance when calculating crc's, string manipulations and querying hardware for status.

Any guidance would be appreciated.

Thank you
Angelo C
User avatar
Roger Seiler
Posts: 223
Joined: Thu Dec 01, 2005 3:34 pm
Location: Nyack, New York, USA
Contact:

Post by Roger Seiler »

Yes, it can be done using the FWH command DLL32. Go to ..\fwh\manual\fwcmd.chm and go to DLL to see the help topic explaining this.

You can see an example of how to do this in a set of DLL32 calls that I put together in order to use the Protection PLUS 3rd part tool that is implemented with a DLL programmed in C. Just click below to download this example (though it does not include the PLUS dll)...

http://www.leadersoft.com/software/ppp4xhb.zip

Then after you download and unzip the ppp4xhb.zip file, take a look at the ppp4fwh.prg file to see the example for using the DLL32 command.

- Roger
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Angelo,

There are three types of DLLs that you can create. From the Harbour docs:

Windows 32-bit DLLs with Harbour code
=====================================

Programs created with Clipper or Harbour are traditionally a
monolithic EXE containing all executable code. This includes
the Virtual Machine (VM) and the RunTime Library (RTL) as well as
your own code. Running under Windows (Win32) with Harbour, you
can now also create and use Windows DLLs that contain PRG code.

Harbour supports Win32 DLLs in 3 ways.

1) Self-contained DLLs containing functions from any platform.
(These are not what we call a "Harbour.dll", although they may
be named that. The DLL entry points are different.)
These have the VM/RTL inside them and can be used by any other
Windows program. You can create a .lib for static linking,
or use GetProcAddress as in any standard Win32 DLL.
Calling Harbour/Prg functions directly is limited to
those that take no parameters unless you include C functions
in the DLL that take parameters and then call the PRG-level
code.

To do static linking, do this to create the .lib:
implib harbour.lib harbour.dll
For the Borland C platform, use that library and import32.lib
and cw32.lib from Borland, and you are ready to go.

See contrib\delphi\hbdll for an example of a Delphi program that can
use all of Harbour's functionality by accessing a self-contained DLL.
BLD_SDLL.BAT is used there to create the DLL.


2) PCode EXEs using a Harbour.dll

A Harbour.dll is designed to be called from a Harbour app.
A pcode EXE is a small Harbour executable that does not contain the
VM/RTL. To execute its functions, it must load and access a
Harbour.dll.
If you want dynamic linking, then use this to execute a Harbour
dynamically loaded pcode DLL function or procedure:
HB_DllDo( <cFuncName> [,<params...>] ) --> [<uResult>]

This lets you have all your common code in a DLL, and have lots
of small EXEs that use it. Realize however that, even though this
may be a nice way to manage your code, each EXE may
load its own image of the Harbour.dll into memory at runtime.
In terms of Windows memory, there may not be a benefit to using pcode
EXEs over monolithic EXEs. But it may be a worthwhile maintenance
benefit to have lots of replaceable small exes.

3) PCode DLLs used from traditional EXEs
A pcode DLL does not contain the VM/RTL.
It is a library of Harbour-compiled PRG code that uses the VM/RTL
of the EXE that calls it. This has the benefit of having
replaceable modules in DLLs that don't necessarily require updating
your EXE.


The following is clipped from a msg by Antonio Linares to the Harbour
developer list explaining some of the details:

Please notice that there are three different Windows DLL entry points:
+ source/vm/
* maindll.c Windows self-contained DLL entry point
* maindllh.c Windows Harbour DLL entry point (harbour.dll)
* maindllp.c Windows pcode DLL entry point and VM/RTL routing functions

> * maindll.c Windows self-contained DLL entry point
To produce Harbour code, as DLLs, that may be used
from other programming languages applications (as VB,
Delphi, C++, etc...)

> * maindllh.c Windows Harbour DLL entry point (harbour.dll)
To produce Harbour.dll, to be just used from small pcode Harbour EXEs

> * maindllp.c Windows pcode DLL entry point and VM/RTL routing
To produce small pcode DLLs, to be used just from Harbour EXE apps.
maindllp.c is the entry point for the Harbour pcode DLLs. pcode DLLs
are quite small DLLs, that just contain pcode and/or C (using extend
api) functions.

mainwin.c is the entry point for Windows EXEs, not for DLLs.

You may use maindll.c, maindllh.c or maindllp.c based on
your needs.

If you are looking to build a Harbour.dll, then you must use
maindllh.c
regards, saludos

Antonio Linares
www.fivetechsoft.com
angelo.c
Posts: 36
Joined: Thu Mar 30, 2006 11:19 am

Post by angelo.c »

Hello

Thank you for your replies. It's made me think a little more.

As a example. I used the Tutor01.prg in the Sample directory as such:

Build as .DLL by executing Buildhd tutor01

I get the Tutor01.dll file OK.

To get a *.lib file so that I can statically link the DLL with another programme, I perform:

Implib tutor01.lib tutor01.dll

I get the Tutor01.lib file OK.

When I check the Tutor01.lib file, I have not got any DLL Import Symbol references to Main (or AppSys) which are defined in Tutor01.

How can I force Harbour to export these function names from the *.prg source?


Best Regards,
Angelo C
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Angelo,

We had not provided you maindll.obj properly compiled (because HB_EXPORT required -D__EXPORT__ flag).

Here you have the right one:
http://rapidshare.com/files/123510556/maindll.obj.html

Now the DLL has three entry points as declared in maindll.c:

Code: Select all

HB_EXPORT LONG PASCAL HBDLLENTRY( char * cProcName )
{
   hb_itemDoC( cProcName, 0, 0 );

   return 0;
}

HB_EXPORT LONG PASCAL HBDLLENTRY1( char * cProcName, LONG pItem )
{
   hb_itemDoC( cProcName, 1, ( PHB_ITEM ) pItem, 0 );

   return 0;
}

HB_EXPORT LONG PASCAL HBDLLENTRY2( char * cProcName, LONG pItem1, LONG pItem2 )
{
   hb_itemDoC( cProcName, 2, ( PHB_ITEM ) pItem1, ( PHB_ITEM ) pItem2, 0 );

   return 0;
}
As you see you have three choices: To invoke a procedure by its name without parameters, or to supply one or two parameters to it.

In case that you are not calling the DLL from Harbour (so you don't have Harbour items) you can easily modify those C functions to create the items for you.

If you need some more explanations or examples, just tell me :-)
regards, saludos

Antonio Linares
www.fivetechsoft.com
angelo.c
Posts: 36
Joined: Thu Mar 30, 2006 11:19 am

Post by angelo.c »

Thanks Antonio,

I compiled and linked with the updated maindll.obj file and it worked OK.

I now am trying to pass one parameter from testdll to tutor01 program. I modified the source of the programmes to the following:

TUTOR01.PRG:
// Typical Welcome message, from Windows!
// FWVERSION, FWCOPYRIGHT and FWDESCRIPTION are just
// some defines placed at FiveWin.ch to support four versions:
// Clipper, Xbase++, Harbour/xHarbour and C3!

#include "FiveWin.ch"
//----------------------------------------------------------------------------//
function Main()
MsgInfo( FWVERSION + Chr( 13 ) + FWCOPYRIGHT, FWDESCRIPTION )
return nil

function OneParam(Param1)
MsgInfo(Param1)
return nil
//----------------------------------------------------------------------------//
procedure AppSys // XBase++ requirement
return
//----------------------------------------------------------------------------//





TESTDLL.PRG:
// Using Harbour DLLs
// To build Tutor01.dll do: buildhd.bat tutor01

#include "FiveWin.ch"

function Main()
local Test_String

HbDLLEntry( "MAIN" )

Test_String := "Hello world from AC!" //String passed
HbDLLEntry1( "ONEPARAM", Test_String) //Passing a String to DLL function "OneParam"

MsgInfo( "ok from EXE" )
return nil

DLL FUNCTION HBDLLENTRY( cProc AS LPSTR ) AS LONG PASCAL LIB "Tutor01.dll"
DLL FUNCTION HBDLLENTRY1( cProc AS LPSTR , pItem AS LONG) AS LONG PASCAL LIB "Tutor01.dll"

I compile and link OK. But when I run it, I get the first message box with FWVERSION, FWCOPYRIGHT and FWDESCRIPTION text OK.

However the second passed parameter results in an
Harbour Exception window
with the folowing three lines of text:
CALLED FROM CALLDLL(0)
CALLED FROM HBDLLENTRY1( 18 )
CALLED FROM MAIN(12)
Can you help me on this one as well?

Best Regards, Angelo C
angelo.c
Posts: 36
Joined: Thu Mar 30, 2006 11:19 am

Post by angelo.c »

Hello again.

I am experimenting some more with DLL calls in FWH8.06

Can you advise me on how to compile the maindll.c source to produce the
maindll.obj properly compiled (you mentioned before that it requies the -D__EXPORT__ flag). I have downloaded the compiler/linker tools you suggested.

Also, can you supply me with a C/C++ code example of the pItem Structure so that I can pass parameters from Applications written in C/C++ to FWH using DLLs.

Thanks for your time in considering and actioning this request.

Best Regards,
Angelo.c
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Angelo,

You have to compile it this way:

c:\bcc55\bin\bcc32 -c -D__EXPORT__ -Ic:\harbour\include maindll.c
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Angelo,

Instead of trying to build Harbour items externally, it is much simpler if you let Harbour build them :-)

Here you have a working example:
MYDLL.PRG - build it using fwh\samples\buildhd.bat

Code: Select all

//----------------------------------------------------------------------------//

function Main()

   MsgInfo( "Inside DLL main()" )
   
return nil

//----------------------------------------------------------------------------//

function OneParam( x )

   MsgInfo( x )
   
return nil   

//----------------------------------------------------------------------------//

#pragma BEGINDUMP 

#include <windows.h>
#include <hbapi.h> 
#include <hbapiitm.h> 

__declspec( dllexport ) LONG pascal DOPROC( char * cProcName, char * cParam ) 
{ 
   PHB_ITEM pItem = hb_itemPutC( NULL, cParam ); // Harbour builds the item !!!

   if( cProcName )
   {
      hb_itemDoC( cProcName, 1, ( PHB_ITEM ) pItem, 0 ); 
      hb_itemRelease( pItem ); 
   }
   else   
      MessageBox( 0, "inside the DLL", "DOPROC", 0 );

   return 0; 
} 

#pragma ENDDUMP 

//----------------------------------------------------------------------------//
TestMyDL.prg - EXE that will use the DLL

Code: Select all

#include "FiveWin.ch" 

function Main() 

   local cString := "Hello world!"

   DOPROC( "ONEPARAM", cString )

   MsgInfo( "ok from EXE" ) 
   
return nil 

DLL FUNCTION DOPROC( cProc AS LPSTR, cText AS LPSTR ) AS LONG PASCAL LIB "MYDLL.dll" 
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

And now, accessing the same DLL from a C code EXE:

testc.c:

Code: Select all

#include <windows.h>

typedef long pascal ( * DOPROC ) ( char * cProcName, char * cString );

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
   HINSTANCE hDLL = LoadLibrary( "MyDLL.DLL" );
   DOPROC pDoProc = ( DOPROC ) GetProcAddress( hDLL, "DOPROC" );
   	
   pDoProc( "ONEPARAM", "Hello world!" );
   MessageBox( 0, "back in C EXE", "ok", 0 );
 
   FreeLibrary( hDLL );
   
   return 0;
}   
To build it do:
c:\bcc55\bin\bcc32 -c testc.c
c:\bcc55\bin\ilink32 -aa -Tpe c:\bcc55\lib\c0w32.obj testc.obj, testc.exe,,c:\bcc55\lib\cw32.lib c:\bcc55\lib\import32.lib
regards, saludos

Antonio Linares
www.fivetechsoft.com
angelo.c
Posts: 36
Joined: Thu Mar 30, 2006 11:19 am

Post by angelo.c »

Thank you Antonio for that information. I can now call a FWH DLL from FWH compiled code and C/C++ compiled code and pass appropiate parameters (Experimented with strings and integers to date).

I extended your examples to cover simultaneous string and integer parameter passing into the DLL written in FWH. Here is the updated code for people's reference. It compiles and runs OK on my system/environment setup for FWH8.06


MyDLL.prg: (Compile as you advised on the previous post.)

Code: Select all

//5th July 2008.  Testing User Defined DLL entry points into FWH  

//----------------------------------------------------------------------------// 
function Main() 

   MsgInfo( "Inside DLL main()" ) 
    
return nil 

//----------------------------------------------------------------------------// 

function OneParam( x ) 

   MsgInfo( x ) 
    
return nil    

function TwoParam( x , y) 

   MsgInfo( x ) 
   MsgInfo( y ) 
    
return nil    

function ThreeParam( cx, cy, nz) 

   MsgInfo( cx ) 
   MsgInfo( cy ) 
   MsgInfo( nz + 6) 
   MsgInfo( valtype(nz)) 
    
return nil    

//----------------------------------------------------------------------------// 

#pragma BEGINDUMP 

#include <windows.h> 
#include <hbapi.h> 
#include <hbapiitm.h> 

__declspec( dllexport ) LONG pascal DOPROC( char * cProcName, char * cParam ) 
{ 
   PHB_ITEM pItem = hb_itemPutC( NULL, cParam ); // Harbour builds the item !!! 

   if( cProcName ) 
   { 
      hb_itemDoC( cProcName, 1, ( PHB_ITEM ) pItem, 0 ); 
      hb_itemRelease( pItem ); 
   } 
   else    
      MessageBox( 0, "inside the DLL ... One Parameter", "DOPROC", 0 ); 

   return 0; 
} 

__declspec( dllexport ) LONG pascal DOPROC_2( char * cProcName, char * cParam,  char * cParam_2 ) 
{ 
   PHB_ITEM pItem   = hb_itemPutC( NULL, cParam ); // Harbour builds the item !!!   1st Parameter ... String
   PHB_ITEM pItem_2 = hb_itemPutC( NULL, cParam_2 ); // Harbour builds the item !!! 2nd parameter ... String

   if( cProcName ) 
   { 
      hb_itemDoC( cProcName, 2, ( PHB_ITEM ) pItem, ( PHB_ITEM ) pItem_2, 0 ); 
      hb_itemRelease( pItem ); 
      hb_itemRelease( pItem_2); 
   } 
   else    
      MessageBox( 0, "inside the DLL ... Two Parameters", "DOPROC_2", 0 ); 

   return 0; 
} 


__declspec( dllexport ) LONG pascal DOPROC_3( char * cProcName, char * cParam,  char * cParam_2, int nParam_3 ) 
{ 
   PHB_ITEM pItem   = hb_itemPutC( NULL, cParam );    // Harbour builds the item !!!   1st Parameter ... String
   PHB_ITEM pItem_2 = hb_itemPutC( NULL, cParam_2 );  // Harbour builds the item !!! 2nd parameter ... String
   PHB_ITEM pItem_3 = hb_itemPutNI( NULL, nParam_3 ); // Harbour builds the item !!! 3rd parameter ... Integer

   if( cProcName ) 
   { 
      hb_itemDoC( cProcName, 3, ( PHB_ITEM ) pItem, ( PHB_ITEM ) pItem_2, ( PHB_ITEM ) pItem_3, 0 ); 
      hb_itemRelease( pItem ); 
      hb_itemRelease( pItem_2 ); 
      hb_itemRelease( pItem_3 ); 
   } 
   else    
      MessageBox( 0, "inside the DLL ... Three Parameters (2 strings, 1 integer)", "DOPROC_3", 0 ); 

   return 0; 
} 

#pragma ENDDUMP 

//----------------------------------------------------------------------------// 

TestMyDLL.prg: (Build as you advised on the previous post.)

Code: Select all

//5th July 2008.  Testing User Defined DLL entry points into FWH  

// This Module tests the *.Exe generated by FWH that will use the DLL


//----------------------------------------------------------------------------// 
#include "FiveWin.ch" 

function Main() 

   local cString   := "Hello world from AC!" 
   local cString_2 := "Hello world from AC ... String 2!" 
   local nVal_3    := 4		//Integer value 

   DOPROC( "ONEPARAM", cString ) 
   MsgInfo( "ok from EXE for One Parameter (one String)" ) 

   DOPROC_2( "TWOPARAM", cString, cString_2 ) 
   MsgInfo( "ok from EXE for Two Parameters (Two Strings)" ) 

   DOPROC_3( "THREEPARAM", cString, cString_2, nVal_3 ) 
   MsgInfo( "ok from EXE for Two Parameters (Two Strings) and a Numeric value" ) 
    
return nil 

DLL FUNCTION DOPROC( cProc AS LPSTR, cText AS LPSTR ) AS LONG PASCAL LIB "MYDLL.dll" 
DLL FUNCTION DOPROC_2( cProc AS LPSTR, cText AS LPSTR, cText_2 AS LPSTR ) AS LONG PASCAL LIB "MYDLL.dll" 
DLL FUNCTION DOPROC_3( cProc AS LPSTR, cText AS LPSTR, cText_2 AS LPSTR, nVal AS WORD ) AS LONG PASCAL LIB "MYDLL.dll" 

//----------------------------------------------------------------------------// 


And now, accessing the same DLL from a C code EXE:
(Compile and Link as you advised on the previous post.)
Testc.c

Code: Select all

//5th July 2008.  Testing C Code exe to call DLL written in FWH

#include <windows.h> 

typedef long pascal ( * DOPROC ) ( char * cProcName, char * cString ); 
typedef long pascal ( * DOPROC_2 ) ( char * cProcName, char * cString, char * cString_2 ); 
typedef long pascal ( * DOPROC_3 ) ( char * cProcName, char * cString, char * cString_2, int nx ); 

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) 
{ 
   int nx;
   HINSTANCE hDLL = LoadLibrary( "MyDLL.DLL" ); 
   DOPROC pDoProc = ( DOPROC ) GetProcAddress( hDLL, "DOPROC" );        
   DOPROC_2 pDoProc_2 = ( DOPROC_2 ) GetProcAddress( hDLL, "DOPROC_2" );        
   DOPROC_3 pDoProc_3 = ( DOPROC_3 ) GetProcAddress( hDLL, "DOPROC_3" );        

   pDoProc( "ONEPARAM", "Hello world!" ); 
   MessageBox( 0, "back in C EXE from Passing One String Parameter to DLL", "ok", 0 ); 
  
   pDoProc_2( "TWOPARAM", "Hello world  .. No 1!",  "Hello world  .. No 2!" ); 
   MessageBox( 0, "back in C EXE from Passing Two String Parameter to DLL", "ok", 0 ); 

   nx = 5;
   pDoProc_3( "THREEPARAM", "Hello world  .. No 1!",  "Hello world  .. No 2!", nx ); 
   MessageBox( 0, "back in C EXE from Passing Two String Parameter and a Numeric to DLL", "ok", 0 ); 

   FreeLibrary( hDLL ); 
    
   return 0; 
}    

//----------------------------------------------------------------------------// 

That's it. Please notice that for passed strings, I use hb_itemPutC( NULL, cParam_2 ) and for integers I use hb_itemPutNI( NULL, nParam_3 )

I referenced the Harbour C Source module vm\hbapi.h to get an idea of what hb_itemPutxxxx routine to use. Now I think it's just a matter of experimenting with other parameters types such as dates, doubles, long etc to ensure I can use FWH routines from other languages.

Thanks for your help.

Best regards,
Angelo C
angelo.c
Posts: 36
Joined: Thu Mar 30, 2006 11:19 am

Post by angelo.c »

Hello


Is it possible to pass instantiated objects from a FW++ exe application to FWH *.dll code?

What I would like to do is to have my current application (which is mainly written in FW++) call and pass objects to a FWH DLL code. My current application extensively uses C/C++ routines called from my FW++ code to speed up execution.

My aim to to transition to FWH totally but want to do this in small incremental stages so that I can continue to use my FW++ application with minimal disruption and also getting the extra capabilites of FWH.

Best Regards,
Angelo C
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Angelo,

A Xbase++ object will not be recognized by the Harbour virtual machine, as they are internally different from Harbour ones.
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Ari
Posts: 128
Joined: Fri Feb 03, 2006 4:21 pm
Location: Brazil
Contact:

Post by Ari »

Como retornar resultados da funcoes da DLL, como se faz ?

Code: Select all

__declspec( dllexport ) LONG pascal DOPROC( char * cProcName, char * cParam ) 
{ 
   PHB_ITEM pItem = hb_itemPutC( NULL, cParam ); // Harbour builds the item !!! 

   if( cProcName ) 
   { 
--->   hb_itemDoC( cProcName, 1, ( PHB_ITEM ) pItem, 0 ); 
   hb_itemRelease( pItem ); 
   } 
   else    
      MessageBox( 0, "inside the DLL ... One Parameter", "DOPROC", 0 ); 

--->   return 0; } 
como pegar o retorno da minha DLL e retornar para o EXE ?
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Ari,

In this example we are returning a LONG value from the DLL, so you can do it this way:

Code: Select all

__declspec( dllexport ) LONG pascal DOPROC( char * cProcName, char * cParam ) 
{ 
   PHB_ITEM pItem = hb_itemPutC( NULL, cParam ); // Harbour builds the item !!! 

   if( cProcName ) 
   { 
      hb_itemDoC( cProcName, 1, ( PHB_ITEM ) pItem, 0 ); 
      hb_itemRelease( pItem );
      return hb_parnl( -1 ); // virtual machine returned numeric value ! 
   } 
   else    
      MessageBox( 0, "inside the DLL ... One Parameter", "DOPROC", 0 ); 

   return 123;  // here you simply return the value 123 
}  
If you need to return other types of values (logical -BOOL-, string -char*-) then you have to change the function to return those values instead of a LONG.
regards, saludos

Antonio Linares
www.fivetechsoft.com
Post Reply