Criação de DLL com código xBase para uso com Harbour.
Enviado: 09 Set 2014 06:29
Amiguinhos,
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
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:
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:
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
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:
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
Vejamos agora algumas caracteristicas:
No trecho acima faço execução de uma função dentro da .DLL usando execução de aplicativo externo.
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:
Abaixo vemos as formas de chamar nossas funções usando HbDllEntry, mas particularmente acho feio assim:
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.
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
Código: Selecionar todos
/*
* 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:
Código: Selecionar todos
#pragma BEGINDUMP
...
void pascal __export HBDLLENTRY( char * cProcName )
...
void pascal __export CusTstBrw()
...
#pragma ENDDUMP
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:
Código: Selecionar todos
%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
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
Código: Selecionar todos
if %GT% == gtgui %bcdir%\bin\ilink32 -Gn -aa -s -Tpd @b32.bc
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:
Código: Selecionar todos
%windir%\System32\RunDLL32 rochadll.dll,CusTstBrw
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
Código: Selecionar todos
/*
* 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"
Código: Selecionar todos
...
DEFINE BUTTON OF oBarMain ACTION WinExec( "RunDLL32.exe rochadll.dll,CusTstBrw" )
...
Código: Selecionar todos
...
DEFINE BUTTON OF oBarMain ACTION UseDLL( "CusTstBrw", "rochadll.dll" )
...
Código: Selecionar todos
...
DLL32 FUNCTION CusTstBrw() AS LONG PASCAL LIB "rochadll.dll"
...
Código: Selecionar todos
...
MENUITEM "&Clientes..." ACTION HbDllEntry( "Customer" ) MESSAGE "Manutencao de Clientes"
MENUITEM "&Browse..." ACTION HbDllEntry( "CusTeste" ) MESSAGE "Browse de Clientes"
...
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.