Aquele velho sonho de conseguir executar código xBase contido em uma .DLL compilada com Harbour se concretizou para mim.
Noites sem dormir e muita pesquisa, testes, compilações, chingamentos e sem sapeca-iá-iá, mas consegui.
Meus primeiros testes foram com exemplos existentes em todo lugar, Harbour, xHarbour, Fivewin, etc.
Busquei informações de como o RunDLL32 do Windows trabalhava e fiz minhas tentativas.
Num primeiro momento consegui fazer um EXE executar uma função em uma DLL mas só executava a primeira função que encontrava. Bom já era um começo, mas ao retornar ao EXE paulava.
Tentei com RunDLL32 chamado do EXE e não enfrentava mais este problema, mas tinha uma demora de uns segundos e não era o que eu queria.
Fiz meu próprio RunDLL32 mas ainda tinha de executá-lo indiretamente.
Bom enfim, cheguei onde queria e o primeiro passo para isto foi compilando o código de minha DLL:
rochadll.prg
/*
* Jose Carlos da Rocha
* Trabalho com DLL de codigo xBase para uso com Harbour
* Sao Paulo - 09/09/2014
* Baseados nos exemplos BabuDLL e outros
*/
#include "fivewin.ch"
#pragma BEGINDUMP
#include <windows.h>
#include <hbvm.h>
#include <hbapiitm.h>
BOOL WINAPI DllEntryPoint( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
HB_SYMBOL_UNUSED( hinstDLL );
HB_SYMBOL_UNUSED( fdwReason );
HB_SYMBOL_UNUSED( lpvReserved );
switch( fdwReason )
{
case DLL_PROCESS_ATTACH:
hb_vmInit( FALSE );
break;
case DLL_PROCESS_DETACH:
hb_vmQuit();
break;
}
return TRUE;
}
void pascal __export HBDLLENTRY( char * cProcName )
{
hb_itemDoC( cProcName, 0, 0 );
return 0;
}
void pascal __export CusTstBrw()
{
hb_itemDoC( "CusTstBrw", 0 );
}
void pascal __export HBDLLENTRY2( char * cProcName, PHB_ITEM pParam1, PHB_ITEM pParam2 )
{
hb_itemDoC( cProcName, 2, pParam1, pParam2 );
}
#pragma ENDDUMP
/*
* MENUITEM "&Clientes..." ACTION HbDllEntry( "Customer" ) MESSAGE "Manutencao de Clientes"
* Esta funcao foi chamada atraves de parametro da funcao exportavel hbDLLEntry()
* Chamada indireta, pois a funcao abaixo passa por outra funcao para agir
* Aqui o exemplo CUSTOMER foi imputado na DLL para demonstrar que trechos grandes de codigo
* podem residir dentro de uma DLL e serem chamados a partir de um EXE externo.
*/
#include "Customer.ch"
function Customer()
local oWnd, oBar
local oClients, oClient
//local oName, cName
SET _3DLOOK ON
USE Customer SHARED NEW ALIAS Clients
USE Sales SHARED NEW
SELECT Clients
DEFINE WINDOW oWnd TITLE "Reporting tools" MDI ;
MENU BuildMenu(oClients) COLOR "N/W"
DEFINE BUTTONBAR oBar OF oWnd SIZE 60, 60 2007
DEFINE BUTTON OF oBar ACTION MsgInfo( "Click" ) ;
FILENAME "..\bitmaps\attach.bmp" PROMPT "Attach"
DEFINE BUTTON OF oBar ACTION MsgInfo( "Click" ) ;
FILENAME "..\bitmaps\calendar.bmp" PROMPT "Calendar"
DEFINE BUTTON OF oBar ACTION MsgInfo( "Click" ) ;
FILENAME "..\bitmaps\people2.bmp" PROMPT "Clients"
DEFINE BUTTON OF oBar ACTION MsgInfo( "Click" )
SET MESSAGE OF oWnd TO "Testing the FiveWin Report Class" CENTERED
ACTIVATE WINDOW oWnd
CLOSE DATABASES
return nil
function BuildMenu(oClients)
local oMenu
MENU oMenu
MENUITEM "&DataBases"
MENU
MENUITEM "&Clients..." ACTION BrwClients(oClients) ;
MESSAGE "Clients management"
MENUITEM "&Report..." ACTION GenReport()
SEPARATOR
MENUITEM "&End" ACTION oWnd:End() ;
MESSAGE "End this test"
ENDMENU
oMenu:AddMdi() // Add standard MDI menu options
ENDMENU
return oMenu
function BrwClients(oClients)
local oBrw, oIco, oBarBrw
if oClients != nil
return nil
endif
DEFINE ICON oIco FILENAME "..\icons\customer.ico"
DEFINE WINDOW oClients TITLE "Clients management" ;
MDICHILD ICON oIco
DEFINE BUTTONBAR oBarBrw OF oClients
DEFINE BUTTON OF oBarBrw ACTION ShowClient(oClients)
@ 2, 0 LISTBOX oBrw FIELDS OF oClients ;
SIZE 500, 500 // ON CHANGE ChangeClient(oClients)
oClients:SetControl( oBrw )
ACTIVATE WINDOW oClients ;
VALID( oClients := nil, .t. ) // We destroy the object
return nil
function GenReport()
local oWnd, oIco
DEFINE ICON oIco FILENAME "..\icons\print.ico"
DEFINE WINDOW oWnd MDICHILD TITLE "Clients report" ;
VSCROLL HSCROLL ICON oIco
ACTIVATE WINDOW oWnd
return nil
function ShowClient(oClients)
local oIco, oClient
local oName, cName
if oClient != nil
return nil
endif
DEFINE ICON oIco FILENAME "..\icons\Person.ico"
DEFINE DIALOG oClient RESOURCE "Client" ;
ICON oIco TITLE "Detalhes"
REDEFINE SAY ID 3 OF oClient // To get the proper color
REDEFINE SAY ID 4 OF oClient
REDEFINE SAY ID 5 OF oClient
REDEFINE GET oName VAR cName ID ID_NAME OF oClient
REDEFINE BUTTON ID ID_NEXT OF oClient ACTION GoNext(oClients,oName)
SELECT Sales // We select Sales to properly initialize the Browse
REDEFINE LISTBOX FIELDS ID ID_SALES OF oClient
ACTIVATE DIALOG oClient CENTERED NOWAIT ;
VALID ( oClient := nil, .t. ) // Destroy the object
SELECT Clients
return nil
function ChangeClient(oClients,oName)
if oClients != nil
cName = AllTrim( Clients->Last ) + ", " + Clients->First
oName:Refresh()
endif
return nil
function GoNext(oClients,oName)
if oClients != nil
oClients:oControl:GoDown()
else
SKIP
if EoF()
GO BOTTOM
endif
endif
ChangeClient(oClients,oName)
return nil
/*
* MENUITEM "&Browse..." ACTION HbDllEntry( "CusTeste" ) MESSAGE "Browse de Clientes"
* Esta funcao foi chamada atraves de parametro da funcao exportavel hbDLLEntry()
* Chamada indireta, pois a funcao abaixo passa por outra funcao para agir
*/
function CusTeste()
USE Customer SHARED NEW ALIAS Clients
Browse()
CLOSE DATABASES
return .t.
/*
* MENUITEM "&Customer..." ACTION CusTstBrw() MESSAGE "Browse de Clientes"
* Esta funcao foi chamada dentro da DLL usando o proprio nome ao inves de
* usar hbDLLEntry(), desta forma ficou mais legal
*/
function CusTstBrw()
USE Sales SHARED NEW
Browse()
CLOSE DATABASES
return .t.
Dentro do código da DLL existem aplicativos que serão chamados pela aplicação principal, Customer(), CusTeste() e CusTstBrw().
Atentem para o seguinte trecho:
#pragma BEGINDUMP
...
void pascal __export HBDLLENTRY( char * cProcName )
...
void pascal __export CusTstBrw()
...
#pragma ENDDUMP
Vejam que HBDLLENTRY e CusTstBrw são exportadas, ou seja, são visÃveis as chamadas à DLL.
Então como compilar este .PRG e transformá-lo em uma .DLL?
Eu usei o bom-e-velho BUILDH.BAT com algumas alterações, vejam os trechos que foram modificados:
%hdir%\bin\harbour %1 /n /i%fwh%\include;%hdir%\include /w0 /p %3 /d__HARBOUR__ > comp.log
IF ERRORLEVEL 1 GOTO COMPILEERRORS
@type comp.log
echo -O2 -e%1.exe -I%hdir%\include;%bcdir%\include %1.c > b32.bc
%bcdir%\bin\bcc32 -M -c @b32.bc
copy %bcdir%\lib\uuid.lib
:ENDCOMPILE
IF EXIST %1.rc %bcdir%\bin\brc32 -r -I%bcdir%\include %1
rem IF EXIST %1.rc %vcdir%\bin\rc -r -d__FLAT__ %1
echo %bcdir%\lib\c0d32.obj + > b32.bc
Quem usa este .BAT dirá, mas não tem nada de diferente neste trecho, mas tem sim e está na última linha, onde se vê echo %bcdir%\lib\c0d32.obj + > b32.bc
Quando compilamos nossos programas para gerar executáveis o arquivo c0w32.obj é o usado, mas neste caso usaremos o c0d32.obj.
Outra linha alterada no nosso BUILDH.BAT é a linha de chamada do iLink32.exe
if %GT% == gtgui %bcdir%\bin\ilink32 -Gn -aa -s -Tpd @b32.bc
No lugar de -Gn -aa -s -Tpe eu alterei para -Gn -aa -s -Tpd, onde e é para EXE e d é para DLL.
Bom, depois de gerada a DLL é possivel testá-la sem precisar do uso do aplicativo principal, bastando usar para isto o RunDLL32.exe do Windows, executando um comando simples:
%windir%\System32\RunDLL32 rochadll.dll,CusTstBrw
Leve em consideração que os testes foram feitos dentro da pasta SAMPLES do Fivewin, portanto as tabelas CUSTOMER.DBF e SALES.DBF serao usadas.
Se a DLL foi bem gerada um browse aparecerá mostrando os registros da tabela.
Agora vamos a parte do aplicativo principal. Este poderá ser compilado normalmente usando o BUILDH.BAT:
rocha.prg
/*
* Jose Carlos da Rocha
* Trabalho com DLL de codigo xBase para uso com Harbour
* Sao Paulo - 09/09/2014
* Baseados nos exemplos BabuDLL e outros
*/
#include "FiveWin.ch"
FUNCTION Main()
local oWndMain, oBarMain
DEFINE WINDOW oWndMain TITLE "Janela dentro do EXE" MDI MENU BuildMenuMain() COLOR "N/W"
DEFINE BUTTONBAR oBarMain OF oWndMain SIZE 60, 60 2007
DEFINE BUTTON OF oBarMain ACTION WinExec( "RunDLL32.exe rochadll.dll,CusTstBrw" )
DEFINE BUTTON OF oBarMain ACTION UseDLL( "CusTstBrw", "rochadll.dll" )
SET MESSAGE OF oWndMain TO "Testing the FiveWin DLLs" CENTERED
ACTIVATE WINDOW oWndMain MAXIMIZED VALID MsgYesNo( "Quer sair?" )
RETURN nil
FUNCTION BuildMenuMain()
local oMenu
MENU oMenu
MENUITEM "Administracao"
MENU
MENUITEM "&Clientes..." ACTION HbDllEntry( "Customer" ) MESSAGE "Manutencao de Clientes"
MENUITEM "&Browse..." ACTION HbDllEntry( "CusTeste" ) MESSAGE "Browse de Clientes"
MENUITEM "&Customer..." ACTION CusTstBrw() MESSAGE "Browse de Clientes"
SEPARATOR
MENUITEM "&Sair" ACTION oWnd:End() ;
MESSAGE "Sair do sistema"
ENDMENU
oMenu:AddMdi() // Add standard MDI menu options
ENDMENU
return oMenu
FUNCTION UseDLL( cFuncName, cDllName )
local hDLL, cFarProc
hDLL = LoadLibrary( cDllName )
if hDll > 32
cFarProc := GetProcAddress( hDLL, "DLLSYMINIT", .T., _INT )
CallDLL( cFarProc )
Eval( &( "{||" + cFuncName + "() }" ) )
endif
return nil
//-------------------------------------------------------------------------//
#include "dll.ch"
DLL32 FUNCTION CusTstBrw() AS LONG PASCAL LIB "rochadll.dll"
DLL32 FUNCTION HBDLLENTRY( cProc AS LPSTR ) AS LONG PASCAL LIB "rochadll.dll"
DLL32 FUNCTION HBDLLENTRY2( cProc AS LPSTR, pItem1 AS LONG, pItem2 AS LONG ) AS LONG PASCAL LIB "rochadll.dll"
DLL32 FUNCTION HBDLLENTRY3( cProc AS LPSTR, pItem1 AS _INT, pItem2 AS _INT ) AS _INT PASCAL LIB "rochadll.dll"
Vejamos agora algumas caracteristicas:
...
DEFINE BUTTON OF oBarMain ACTION WinExec( "RunDLL32.exe rochadll.dll,CusTstBrw" )
...
No trecho acima faço execução de uma função dentro da .DLL usando execução de aplicativo externo.
...
DEFINE BUTTON OF oBarMain ACTION UseDLL( "CusTstBrw", "rochadll.dll" )
...
Neste trecho, usando funções do Harbour exemplifico como chamar uma função existente na .DLL. Esta função deve ser EXPORTável e para tal foi necessária a definição de chamada desta função:
...
DLL32 FUNCTION CusTstBrw() AS LONG PASCAL LIB "rochadll.dll"
...
Abaixo vemos as formas de chamar nossas funções usando HbDllEntry, mas particularmente acho feio assim:
...
MENUITEM "&Clientes..." ACTION HbDllEntry( "Customer" ) MESSAGE "Manutencao de Clientes"
MENUITEM "&Browse..." ACTION HbDllEntry( "CusTeste" ) MESSAGE "Browse de Clientes"
...
A função HbDllEntry() enxerga as funções que não são EXPORTáveis.
Vale lembrar que as .DLL não ficaram pequenas, mas o fator levado em consideração é que para a manutenção fica mais produtivo ter várias .DLLs no sistema e ao modificar uma ou outra, podemos atualizar somente elas sem prejuÃzo ao EXE principal.
Outra coisa importante:
As telas usadas no aplicativo, não importando qual .DLL poderá usá-la, deverão ser compiladas com o .EXE principal.
O Harbour usado foi a versão 3.2-17626.
O download deste trabalho encontra-se no 4shared.com.
Tenho grande certeza, que seria possivel chamar código xBase contido nas .DLLs através de outras linguagens como Delphi ou Visual Basic. Vale a pena testar e retornar.