Boletim Pascal #40 - 07 DE MARÇO DE 2003 (Original: 27-OCT-2002)
Índice
1. Algumas palavras dos editores
2. Windows hooks (ou "Como todos esses programas espiões funcionam?")
3. Codificação e decodificação base64 (MIME)
4. Examplo de um serviço do Windows com uma thread
5. Capturando a saída de um aplicativo console
6. Assembler nativo em Delphi (IV) - Records
7. Fóruns / Listas
8. Delphi na Rede
- Sites em português
- Componentes, bibliotecas e utilidades
. Freeware
- Artigos, dicas e truques
- Tutoriais
- Outros links
________________________________________________________________________
1. Algumas palavras dos editores
Editorial da versão em português
--------------------------------
Agora sim, podemos ver alguns avanços no andamento do nosso Boletim
Pascal! Graças (não me canso de repetir) ao trabalho voluntário dos
nossos tradutores, estamos conseguindo, pouco a pouco, atualizar as
traduções em português do Boletim. Até que consigamos ficar em dia
com o Boletim, vou procurar manter meus comentários ao mínimo. Prefiro
que vocês recuperem o tempo perdido lendo as excelentes matérias dessa
edição! :)
Agora chega de conversa. Divirtam-se com nosso boletim!
Demian Lessa
demian@knowhow-online.com.br
Editor da versão em português
__________________
Editorial da versão em inglês
-----------------------------
Eu pretendia ter esse número publicado algum tempo atrás como prometido
mas devido a circunstâncias bastante particulares um novo vírus ainda
não identificado infectou meu PC e destruiu o primeiro cilindro de meu
disco rígido (o MBR, o setor de boot e parte da FAT da primeira
partição) então, vá lá saber... (ainda estou instalando os componentes
Delphi que utilizava). Peço desculpas pelo atraso.
Gostaria de agradecer aos autores que contribuíram com artigos para esse
número e fico feliz em entregar-lhes os seguintes prêmios:
* Florin Sabau ("Windows Hooks")
· llPDFLib v1.1 - por llionsoft, Shareware ($70, $280 com fontes)
llPDFLib é uma biblioteca Object Pascal para a criação de documentos
PDF. Não utiliza DLLs ou software externo de terceiros para gerar os
PDFs. A biblioteca consiste de um componente TPDFDocument com métodos
e propriedades análogos ao TPrinter do Delphi, mas orientados à
geração do arquivo PDF. http://www.llion.net/
* Jochen Fromm ("Capturando a Saída de um Aplicativo Console")
. Greatis Form Designer v3.4 - por Greatis Software, Shareware ($49.95)
Esse é um editor de formulários de tempo de execução que permite ao
usuário movimentar e redimensionar qualquer controle do formulário.
Não é preciso preparar o formulário para utilizar o Form Designer.
Apenas coloque um componente TFormDesigner num formulário, defina a
propriedade Active para True e pronto! Para Delphi 4-7 e BCB 3-6.
http://www.greatis.com/formdes.htm
* Kim Sandell ("Example of a Windows Service, with a thread")
· Developer Information Library (DIL) CD - by UK Borland User Group
Mais de 17,000 dicas, truques, FAQs e artigos técnicos · Atualizações
e patches para ferramentas da Borland · Mais de 4000 components e
Ferramentas · Mais de 4000 bitmaps prontos para uso e outros 20000 em
formato compactado · Mais de 350 scripts javascript prontos para uso ·
Conjunto completo de How-Tos para Linux · e muito, muito mais...
http://www.richplum.co.uk/dil/index.asp
Para o próximo número, teremos o seguinte prêmio para um de nossos
colaboradores:
* TSDBGridFooter v2.0 por Jovan Sedlan, Shareware ($74.50)
Esse componente é uma ferramenta poderosa que permite cálculos
automáticos no seu DBGrid e exibe essa informação num rodapé
configurável abaixo do grid. É projetado para funcionar com o
TSDBGrid (também incluído) apesar de poder ser utilizado em qualquer
descendente de TCustomDBGrid. http://www.softpile.com/Development/Libraries/Review_24756_index.html
Mudando de assunto, a primeira atualização do Delphi 7 foi liberada:
* Delphi 7 Update Pack 1
http://community.borland.com/article/0,1410,29209,00.html
Espero que goste dessa edição.
Cordialmente,
Ernesto De Spirito
boletim-pascal-owner@yahoogrupos.com.br
__________________
Colaboraram nesta edição: Dave Murray e Charl Linssen
________________________________________________________________________
JfControls Library. Multi-language. Multi-appearance. Skins. Privileges.
More than 40 integrated and customizable components. Impressive GUI.
Centralized resources administration. Multiple programming problems
solved. For Delphi 3-6 and C++ Builder 3-5. http://www.jfactivesoft.com/
________________________________________________________________________
2. Windows hooks (ou "Como todos esses programas espiões funcionam?")
Por Florin Sabau <aaa111@go.ro>
Tradução: Adilson Vahldick
Você já parou para pensar como os programas espiões conseguem capturar
tudo que é digitado nos computadores onde estão instalados? Bem, eu não
sei exatamente como ELES fazem isto :), mas mostrarei uma possível
técnica usando Windows Hooks. Este artigo irá mostrar como criar
programas que "escutam" por uma certa combinação de teclas e, quando
ativada, executar alguma tarefa (como ejetar a unidade de CDROM).
Tecnicamente, um hook é somente uma outra subrotina ("hook procedure")
que "fica no caminho" do mecanismo normal de tratamento de mensagens
do Windows. A hook procedure pode ser instalada no sistema e assim
ela captura certas mensagens do Windows ANTES delas serem enviadas
para as devidas rotinas de tratamento. Windows contém vários tipos
diferentes de hooks; cada tipo fornece acesso a um aspecto diferente
do mecanismo de tratamento de mensagens do Windows. As constantes que
identificam os tipos de hooks estão em Windows.pas. Abaixo temos alguns
destes tipos, com uma breve descrição:
WH_KEYBOARD: instala uma hook procedure que monitora as mensagens do
teclado. Utilizaremos este em nosso programa.
WH_MOUSE: instala uma hook procedure que monitora mensagens do mouse.
WH_CBT: instala uma hook procedure que recebe notificações úteis para
uma aplicação de treinamento baseada em computador (CBT).
WH_JOURNALRECORD: instala uma hook procedure que grava as mensagens de
entrada postadas para a fila de mensagens de sistema. Este hook é útil
para a gravação de macros.
WH_JOURNALPLAYBACK: instala uma hook procedure que envia mensagens
previamente gravadas pela WH_JOURNALRECORD hook procedure.
Devido a muitos programas poderem instalar um hook no sistema ao mesmo
tempo, o Windows mantém internamente um "hook chain", que é apenas uma
lista de ponteiros para as hook procedures que os programas instalaram.
Quando uma mensagem acontece no sistema, o Windows primeiro passa por
cada uma das procedures no hook chain, uma depois da outra. Então, caso
a mensagem não tenha sido bloqueada por qualquer uma das hook
procedures, o Windows encaminha a mensagem para a janela adequada.
Mais um assunto antes de ir para a próxima seção: hooks podem ser
classificados de uma outra forma. Existem os hooks de sistema (globais)
que recebem mensagens de todos os threads do sistema, e os hooks
específicos de thread (locais), que recebem mensagens apenas de um
determinado thread. Devido a uma hook procedure global poder ser chamada
no contexto de qualquer aplicação (que capturam mensagens de todas as
aplicações), elas devem estar localizadas em uma DLL (Dynamic Link
Library). Esta restrição não se aplica aos hooks específicos de threads,
onde a hook procedure pode estar em qualquer parte da aplicaçao que
controla o thread a ser interceptado.
Neste artigo nós trataremos somente com hooks globais.
Instalando uma WH_KEYBOARD hook procedure no hook chain
-------------------------------------------------------
A API de hooks contém 3 importantíssimas funções: SetWindowsHookEx
(que instala uma hook procedure), UnhookWindowsHookEx (que desinstala
a hook procedure) e CallNextHookEx (que chama a próxima hook procedure
no hook chain). Os parâmetros destas funções são os seguintes (em
windows.pas):
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod:
HINST; dwThreadId: DWORD): HHOOK; stdcall;
"idHook": tipo do hook a ser instalado (p.e. WH_KEYBOARD);
"lpfn" : aponta para a hook procedure para onde a mensagem será
enviada;
"hmod" : handle da DLL que instala a hook, normalmente hInstance
(para hooks globais) ou 0 para hooks locais;
"HINST" : identificador do thread ao qual o hook estará associado.
Se 0 a hook procedure é associada com todos os threads.
Retorna um valor usado para identificar o hook.
function UnhookWindowsHookEx(hhk: HHOOK): BOOL; stdcall;
"hhk": identifica o hook a ser desinstalado.
Retorna True se ocorreu com sucesso ou False se falhou.
function CallNextHookEx(hhk: HHOOK; nCode: Integer; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
"hhk": identificador do hook corrente;
"nCode", "wParam", "lParam": parâmetros a serem enviados para a
próxima hook procedure no hook chain.
Retorna o valor retorna pela próxima hook procedure do chain.
Nós veremos adiante o que isto significa.
A hook procedure
----------------
A hook procedure para o teclado tem o seguinte formato:
function HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM):
LRESULT; stdcall;
"nCode":
= HC_ACTION - os parâmetros wParam e lParam contém informações
sobre as mensagens das teclas pressionadas
= HC_NOREMOVE - os parâmetros wParam e lParam contêm informações
sobre as mensagens das teclas pressionadas mas a mensagem não
foi removida da fila de mensagens (uma aplicação chamou a função
PeekMessage especificando o flag PM_NOREMOVE).
"wParam": especifica o código virtual da tecla que gerou a mensagem
(p.e. VK_F9 para a tecla de função F9)
"lParam": especifica informações adicionais (do tipo contador de
repetição, scan code ...); não utilizado em nosso programa;
veja Win32SDK para maiores detalhes;
HookProc deve retornar um valor não zerado para evitar que o Windows
passe a mensagem para os demais hooks da cadeia, ou para a window
procedure alvo, ou zero para deixar que o Windows passe a mensagem
para a window procedure alvo.
Exemplo
-------
Este exemplo cria uma hook global para o teclado e quando uma certa
combinação de teclas ocorrem ele executará alguma coisa (veja abaixo):
WinKey + F9: mostra a janela principal se ela estiver oculta;
WinKey + F10: ejeta a unidade de CDROM;
WinKey + F12: encerra a aplicação.
A comunicação entre a DLL que implementa a hook e a aplicação é feita
com a função SendMessage da API, que envia uma HOOK_MSG (definida em
constants.inc) para a aplicação principal, com os comandos (SHOW,
EJECT, QUIT) em wParam (veja abaixo).
Como não querermos ser incomodados com a janela sendo mostrada a todo
instante, quando clica-se o botão de minimizar a janela é ocultada (até
da lista de tarefas), mas poderá ser visualizada com a combinação
WinKey+F9.
Para o código fonte completo veja o arquivo anexo. Eu somente mostrarei
aqui as partes mais importantes da aplicação:
1. HookDll.dpr
function KeyboardProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM):
LRESULT; stdcall;
var
Handled: Boolean;
KeyState: TKeyboardState;
Han: HWND;
function WinKeyPressed: boolean;
begin
Result := (KeyState[VK_LWIN] and $80 <> 0)
or (KeyState[VK_RWIN] and $80 <> 0);
end;
begin
Handled := False;
Result := 1;
if nCode = HC_ACTION then
begin
GetKeyboardState(KeyState);
Han:=FindWindow('TForm1',APP_CAPTION);
if (IsWindow(Han)) and (KeyState[wParam] and $80 <> 0)
and WinKeyPressed then
begin
Handled := True;
case wParam of
VK_F9: SendMessage(Han, HOOK_MSG, APP_SHOW, 0);
VK_F10: SendMessage(Han, HOOK_MSG, EJECT_CDROM, 0);
VK_F12: SendMessage(Han, HOOK_MSG, APP_QUIT, 0);
else
Handled := False;
end;
end;
end;
if not Handled then
Result := CallNextHookEx(hhk, nCode, wParam, lParam);
end;
Na entrada da hook procedure nós testamos se o flag HC_ACTION em nCode
(assim sabemos que tivemos o pressionamento de uma tecla), então
salvamos em KeyState o estado (se foi pressionado ou não, CAPSLOCK
ligado, etc.) de todas as teclas virtuais utilizando GetKeyboardState.
Nós também procuramos pelo handle da janela principal da aplicação para
a qual serão enviados os comandos (APP_SHOW, EJECT_CDROM, APP_QUIT). Se
a janela for encontrada (IsWindow(han)) e tiver um mensagem de tecla
pressionada, então enviamos um comando para a janela principal de acordo
com o pressionamento. Se não foi possível tratar a mensagem
(Handled=false) então os parâmetros são passados para a próxima hook na
cadeia.
2. Hooks.Dpr & Unit1.pas
A principal função aqui é HOOK_MSG_PROC que recebe os comandos enviados
da DLL:
type TForm1=class(TForm)
...
procedure HOOK_MSG_PROC(var Msg: TMessage); message HOOK_MSG;
...
end;
...
procedure TForm1.HOOK_MSG_PROC(var Msg: TMessage);
begin
case Msg.WParam of
APP_SHOW:
begin
Application.ShowMainForm := True;
Visible:=True;
end;
EJECT_CDROM: mciSendString('set CDAudio door open', nil, 0, 0);
APP_QUIT: Close;
end;
end;
O recurso de "Ocultar na minimização" é executado pela especialização
do tratamento da mensagem WM_SYSCOMMAND que é enviada pelo Windows
quando um comando de sistema (close, minimize, maximize) ocorre. Então
a janela será oculta se receber um comando SC_MINIMIZE:
procedure TForm1.OnMinimize(var Msg: TMessage);
begin
if Msg.WParam = SC_MINIMIZE then
begin
if not IsHookInstalled then
begin
ShowMessage('Install the hook first or you''ll'#13#10 +
'not be able to access the program');
Exit;
end;
Application.ShowMainForm := False;
Visible := False;
end else
Inherited;
end;
Para esconder o programa da lista de tarefas utilizamos:
function RegisterServiceProcess(dwProcessID, dwType: integer):
integer; stdcall; external 'KERNEL32.DLL';
NOTA: A técnica de ocultamento acima funciona somente no Windows 9x.
É claro que isto é exemplo bem simples de aplicação de hooks, mas as
possibilidades são enormes. Um pequeno programa espião, talvez! :)
De qualquer maneira, se você tiver alguma dúvida sobre este artigo,
sinta-se a vontade de me mandar uma mensagem para <aaa111@go.ro>.
__________________
OBSERVAÇÃO: O código completo está anexo.
________________________________________________________________________
Qual foi a última vez que você votou para o Boletim Pascal? Suporte essa
iniciativa votando para o Boletim Pascal no The Programming Top 100!
http://top100borland.com/in.php?who=20
________________________________________________________________________
3. Codificação e decodificação base64 (MIME)
Por Daniel Wischnewski
delphi3000@wischnewski.tv
Tradução: Demian Lessa
Eu escrevi a unidade a seguir para substituir os componentes INDY
TIdEncoderMIME e TIdDecoderMIME. Esse "Codec" é utilizado em software
de email, basicamente.
A primeira razão foi a lentidão dos componentes INDY. A segunda foi que
por serem componentes, precisam da VCL, uma carga extra para sistemas
não baseados na VCL.
Ambas as rotinas são escritas em Assembler e sobrecarregadas em versões
distintas para facilitar o acesso.
Estou certo de que alguns de vocês serão capazes de aumentar ainda mais
a velocidade do código. Por favor, me avisem. Grato.
__________________
NOTA: O código fonte completo está em anexo.
________________________________________________________________________
AnyShape Transpack v2.0 - by MindBlast Software (DELPHI + KYLIX)
Tired of boring, rectangular windows? AnyShape Transpack is a cross-
platform component that eases the creation of transparent, weirdly
shaped windows with WYSIWYG editing, design-time preview, automatic
dragging, REAL stay-on-top forms, and the ability to combine regions.
http://www.mindblastsoftware.com/?page=transpack&ref=PascalNL
________________________________________________________________________
4. Examplo de um serviço do Windows com uma thread
Por Kim Sandell - kim.sandell@nsftele.com
www.nsftele.com
Tradução: Demian Lessa
Delphi 5 e 6 têm um modelo de projeto para serviços, mas é incompleto.
Esse exemplo estende esse modelo e o completa. Também mostra como
iniciar uma thread que emite um sinal (beep) a cada dois segundos. Você
pode usar esse código como modelo quando for desenvolver servidores como
serviços.
O exemplo foi desenvolvido para Delphi 5-7 e funciona nos Windows
NT/2000/XP. A opção de inicialização do serviço está definida como
MANUAL. Se você quiser que o serviço inicie automaticamente com o
Windows, então terá que mudar isso. MAS CUIDADO! Se sua aplicação
travar enquanto roda como um serviço não há como finalizá-la.
__________________
NOTA: O código fonte completo está em anexo.
__________________
Perfil sumário do autor:
Nome : Kim Sandell Nascido : 1973 Helsinki, Finland
Habilidades : VB, Delphi (1-6), C/C++, SQL, Interbase, IP Networks.
Áreas especiais: System Service, Protocols, Security (VPN),
Authentication, Servers (Deamons) + More
Empresa: NSF Telecom Ab Título: CTO
WWW : http://www.nsftele.com EMail : kim.sandell@nsftele.com
________________________________________________________________________
5. Capturando a saída de um aplicativo console
Por Jochen Fromm <Jochen.Fromm@manserv.de>
Tradução: Demian Lessa
Como você inicia um aplicativo DOS ou de console e captura a saída
enquanto o programa está rodando? Por exemplo, como você captura a
saída do comando FileCompare (FC)?
Já existem dois artigos tratando desse assunto,
http://www.delphi3000.com/articles/article_2112.asp
http://www.delphi3000.com/articles/article_2298.asp
mas eles só capturam a saída quando o processo finaliza. Existem ainda
dois artigos na Microsoft sobre o redirecionamento das saídas de
aplicativos DOS:
* Microsoft Knowledge Base Article - Q190351
HOWTO: Spawn Console Processes with Redirected Standard Handles
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q190351
* Microsoft Knowledge Base Article - Q150956
INFO: Redirection Issues on Windows 95 MS-DOS Applications
http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q150956
A idéia básica é iniciar o aplicativo de console com CreateProcess
e redirecionar os canais (pipes) de entrada e saída, permitindo ao
processo que disparou o CreateProcess (processo pai) o acesso à saída
do aplicativo de console.
É importante capturar a saída enquanto o processo ainda está em
execução. Se o canal de saída for bloqueado por overflow, novos dados
não podem ser escritos do aplicativo de console para o canal de saída
e o programa pára. Nesse caso, temos o clássico caso de deadlock: o
usuário (processo pai) espera que o processo filho termine e o processo
filho espera a liberação do buffer de saída pelo processo pai.
__________________
NOTA: Código fonte e aplicativo de exemplo estão em anexo.
________________________________________________________________________
6. Assembler nativo em Delphi (IV) - Records
Por Ernesto De Spirito <eds2008 @ latiumsoftware.com>
Tradução: Cristiano de Favari
Passando records como parâmetros
================================
Como arrays estáticos, records são internamente passados como ponteiros
para os dados, independentemente se o parâmetro é passado por valor ou
por referência (também como "var" ou como "const").
Dada as seguintes declarações...
type
TRecord = record
Id: integer;
Name: string;
end;
var
a, b: TRecord;
procedure InitRecord(var r: TRecord; Id: integer; const Name: string);
begin
r.Id := Id;
r.Name := Name;
end;
...uma chamada para a procedure InitRecord em assembler seria assim:
// Em Object Pascal:
// InitRecord(a, n, s);
// Em Inline Assembler:
asm
lea eax, a // EAX := @a; // 1st parameter in EAX
mov edx, n // EDX := n; // 2nd parameter in EDX
mov ecx, s // ECX := s; // 3rd parameter in ECX
call InitRecord // InitRecord;
end;
Acessando os campos de um record
================================
Campos de records estão localizados em um certo offset de um endereço do
record (o endereço do primeiro campo). No exemplo, assumindo que nós
temos o endereço do record do tipo TRecord no registrador EAX, o campo
Id está localizado em [EAX+0] (ou simplesmente [EAX]), e o campo Name
está localizado em [EAX+4], mas normalmente nós não escrevemos código
usando números explicitamente. Ao invés disto, para produzir código
auto-explicável e de fácil manutenção, nós temos cinco alternativas:
mov edx, [eax + TRecord.Name]
mov edx, (TRecord PTR [eax]).Name
mov edx, (TRecord [eax]).Name
mov edx, TRecord[eax].Name
mov edx, [eax].TRecord.Name
As cinco sentenças anteriores seriam montadas como:
mov edx, [eax + 4]
No lugar de um registrador (como EAX), as sintaxes também se aplicam
para nome de variáveis locais ou globais.
Você pode deduzir da primeira sintaxe que em inline assembler a
expressão RecordType.Field é avaliada em tempo de compilação como
uma constante representando o offset no qual o campo está localizado
no RecorType. Por exemplo, a seguinte sentença é válida:
mov ecx, TRecord.Name // mov ecx, 4
Voltando ao assunto, a procedure InitRecord (apresentada acima) pode ser
implementada em assembler desta forma:
procedure InitRecord(var r: TRecord; Id: integer; const Name: string);
asm // EAX = @r; EDX = Id; ECX = @Name[1]
mov (TRecord PTR [eax]).Id, edx // EAX^.Id := EDX; // Id
// _LStrAsg(@EAX^.Name, @Name) --> EAX^.Name := Name
lea eax, (TRecord PTR [eax]).Name // EAX := @(EAX^.Name);
mov edx, ecx // EDX := @Name[1];
call System.@LStrAsg // _LStrAsg(EAX, EDX)
end;
Na entrada da procedure, nós temos EAX apontando para o registro
(primeiro parâmetro), EDX contendo o Id (segundo parâmetro), EDX
apontando para o dado da string Name (terceiro parâmetro). Atribuir
um inteiro é bem simples, mas atribuir uma string é um pouco mais
complicado.
Se a string destino não é uma string vazia então
begin
Decremente a contagem de referência da string destino;
Se a contagem de referência da string destino chegou a zero então
Libere a string destino;
end;
Se a String Origem não for uma string vazia então
Incremente a contagem de referência da String origem;
Designe origem para o destino;
A procedure _LStrAsg (da Unit System) implementa esta lógica para nós. A
procedure recebe dois parâmetros: o primeiro (em EAX) é a string destino
passada por referência e o segundo (em EDX) é a string origem passada
por valor (o que é passado na verdade é o ponteiro, visto que strings
são ponteiros para os caracteres de fato). Então, no nosso caso, EAX
deveria ser o endereço de uma variável string que será atribuída (isto é
@r.Name), enquanto EDX deveria ser o valor a ser atribuído:
EAX --> r.Name --> r.Name[1] ==> EAX = @r.Name
EDX --> Name[1] ==> EDX = @Name[1]
Ref.: "-->" significa "aponta para"
Então, preparamos EAX e EDX e então chamamos _LStrAsg:
lea eax, (TRecord PTR [eax]).Name // EAX := @(EAX^.Name);
mov edx, ecx // EDX := @Name[1];
call System.@LStrAsg // _LStrAsg(EAX, EDX)
Funções de baixo nível para trabalhar com records
=================================================
Como arrays estáticos, se o record é passado por valor, é
responsabilidade da função chamada preservar o record. Quando uma função
precisa trocar o valor de um ou mais campos do record passado por valor,
normalmente ela cria uma cópia local e trabalha com a cópia. O
compilador cria uma cópia para nós no "begin" das funções Pascal, mas
nas funções puramente assembler temos que fazê-lo nós mesmos. Um jeito
de fazer isto é como mostrado na parte III com arrays estáticos. Aqui
está outro modo:
procedure OperateOnRecordPassedByValue(r: TRecord);
var
_r: TRecord;
asm
// Copia os elementos de "r" (parâmetros) em "_r" (cópia local)
// Move(r, _r, sizeof(TRecord));
lea edx, _r // EDX := @_r;
mov ecx, type TRecord // ECX := sizeof(TRecord);
call Move // Move(EAX^, EDX^, ECX);
lea eax, _r // EAX := @_r;
mov edx, TRecord_TypeInfo // EDX := TRecord_TypeInfo;
call System.@AddRefRecord // System._AddRefRecord(EAX,EDX);
lea eax, _r // EAX := @_r; // optional
// Aqui vai o resto da função. Nós trabalharemos no
// record "_r" (a cópia local), agora apontada por EAX.
end;
Desta vez nós chamamos a procedure Move ao invés de copiarmos os dados
com REP MOVSB. Deste modo, nós escrevemos menos código.
IMPORTANTE: Copiar os valores da memória apenas funciona com records que
não contém campos do tipo reference-counted tais como strings, arrays
dinâmicos ou variantes do tipo string ou arrays dinâmicos.
Se nós tivermos um ou mais campos string, ou campos de algum outro tipo
reference-counted, depois de copiar os valores de memória, nós temos que
incrementar seus respectivos contadores de referência. A procedure
_AddRefRecord (da Unit System) realiza isto. Ela possui dois parâmetros:
um ponteiro para o record (em EAX) e um ponteiro para informação do tipo
de dado para o record gerado pelo compilador (em EDX).
A informação de tipo para o record é basicamente uma estrutura de dados
que contém as posições e tipos de campos reference-counted do registro.
As procedures que trabalham com records declaradas na Unit System,
(_InitializeRecord, _AddRefRecord, _CopyRecord, e _FinalizeRecord)
requerem um ponteiro para a informação do tipo de dado como seu último
parâmetro.
Mas, onde estão os dados? Bem, infelizmente, não há um símbolo para
acessar sua localização diretamente. Nós temos que conseguir seu
endereço através de uma chamada para a função TypeInfo, mas não há uma
função que nós possamos chamar através do código assembler porque não é
uma função verdadeira, e sim uma função interna que o compilador resolve
em tempo de compilação.
Um possível contorno é inicializar uma variável global, chamando a
função TypeInfo de nosso código Pascal:
var
TRecord_TypeInfo: pointer;
:
initialization
TRecord_TypeInfo := TypeInfo(TRecord);
E então podemos usá-la como:
procedure OperateOnRecordPassedByValue(r: TRecord);
var
_r: TRecord;
asm
// Copiar os elementos de "r" (parâmetro) para "_r" (cópia local)
// Move(_r, r, sizeof(TRecord));
lea edx, _r // EDX := @_r;
mov ecx, TYPE TRecord // ECX := sizeof(TRecord);
call Move // Move(EAX^, EDX^, ECX);
// System._AddRefRecord(@_r, TypeInfo(TRecord));
lea eax, _r // EAX := @_r;
mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord);
call System.@AddRefRecord // System._AddRefRecord(EAX, EDX);
lea eax, _r // EAX := @_r; // opcional
// Aqui vai o resto da função. Nós trabalharemos no
// record "_r" (a cópia local), agora apontada em EAX.
// Nós temos que finalizar a cópia local antes de retornarmos
// System._FinalizeRecord(@_r, TypeInfo(TRecord));
lea eax, _r // EAX := @_r;
mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord);
call System.@FinalizeRecord // System._FinalizeRecord(EAX, EDX);
end;
Note que antes da função retornar, nós temos que fazer a chamada a
_FinalizeRecord para destruir o record local (por exemplo, isto
decrementará a contagem de referência de strings apontadas por campos
string).
Chamar Move e então _AddRefRecord é um jeito válido de copiar records
se e apenas se o record de destino tenha sido inicializado (depois de
chamar _AddRefRecord, o record é inicializado). Se o record de destino
já estiver inicializado, então nós temos que chamar _CopyRecord ao invés
disto.
Por Exemplo:
procedure proc(const r: TRecord);
var
_r: TRecord;
begin
// _r := r;
asm
mov edx, eax // EDX := @r;
lea eax, _r // EAX := @_r;
mov ecx, TRecord_TypeInfo // ECX := TypeInfo(TRecord);
call System.@CopyRecord // System._CopyRecord(EAX, EDX, ECX);
end;
end;
Note que como isto é uma função Pascal normal (não uma função Assembler
completa), o compilador automaticamente gera código para inicializar
e finalizar a variável record local (no "begin" e "end" da procedure
respectivamente).
A combinação Move mais _AddRefRecord é idêntica em efeito a
_InitializeRecord mais _CopyRecord:
procedure OperateOnRecordPassedByValue(r: TRecord);
var
_r: TRecord;
asm
// Copiar os elementos de "r" (parâmetro) para "_r" (cópia local)
// Move(_r, r, sizeof(TRecord));
// System._InitializeRecord(@_r, TypeInfo(TRecord));
push eax // Push(EAX); // @r
lea eax, _r // EAX := @_r;
mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord);
call System.@InitializeRecord // System._InitializeRecord(EAX, EDX);
// _r := r;
lea eax, _r // EAX := @_r;
pop edx // EDX := Pop(); // @r
mov ecx, TRecord_TypeInfo // EDX := TypeInfo(TRecord);
call System.@CopyRecord // System._CopyRecord(EAX, EDX, ECX);
lea eax, _r // EAX := @_r; // optional
// Aqui vai o resto da função. Nós trabalharemos no
// record "_r" (a cópia local), agora apontada em EAX.
// Nós temos que finalizar a cópia local antes de retornarmos
// System._FinalizeRecord(@_r, TypeInfo(TRecord));
lea eax, _r // EAX := @_r;
mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord);
call System.@FinalizeRecord // System._FinalizeRecord(EAX, EDX);
end;
Como _AddRefRecord, a procedure _InitializeRecord é apenas destinada
para ser usada com records não inicializados.
Retornando valores de records
=============================
Retornar valores de records é exatamente o mesmo que retornar valores de
array estático. Funções que retornam records recebem um último parâmetro
adicional que é o ponteiro para a localização em memória onde o valor de
retorno deve ser armazenado, isto é, o valor do último parâmetro é
@Result. A memória para o record de resultado deveria ser alocada,
inicializada e liberada pelo chamador (não é de responsabilidade da
função chamada). Por exemplo, vamos considerar a seguinte função:
function MakeRecord(Id: integer; const Name: string): TRecord;
begin
Result.Id := Id;
Result.Name := Name;
end;
A função é declarada para receber dois parâmetros e retornar um record,
mas internamente é como uma procedure com três parâmetros:
1) EAX = O Id para o novo record
2) EDX = O nome para o novo record
3) ECX = O endereço do record de resultado (@Result)
A função pode ser reescrita em assembler como segue :
function MakeRecord(Id: integer; const Name: string): TRecord;
asm // EAX = Id; EDX = @Name[1]; ECX = @Result
mov (TRecord PTR [ecx]).Id, eax // ECX^.Id := EAX; // Id
// (@Result)^.Id := EAX;
// Result.Id := EAX;
// Result.Name := Name;
// System.@LStrAsg(@(Result.Name), @Name[1])
// System.@LStrAsg(@(ECX^.Name), @Name[1])
lea eax, (TRecord PTR [ecx]).Name // EAX := @(ECX^.Name);
call System.@LStrAsg // _LStrAsg(EAX, EDX)
end;
NOTA: Nós não designamos o valor EDX antes de chamar _LStrAsg
porque EDX já contém o valor desejado (passado como parâmetro)
Chamando funções que retornam records
=====================================
Considere o seguinte código:
a := MakeRecord(n, s);
Alguém seria tentado a pensar que o compilador traduz como:
asm
mov eax, n
mov edx, s
lea ecx, a // ECX := @a; // @Result
call MakeRecord
end;
Mas as coisas não acontecem deste jeito, ao menos no Delphi 5. O
compilador aloca e inicializa uma variável local que armazena o
resultado e então copia o resultado do record para o record de destino.
Nós não temos apenas ineficiência realizando uma cópia que seria
desnecessária se usássemos um código como o acima, mas- como nós temos
visto acima- a cópia por si mesma não é tão inocente como uma chamada
para a procedure Move (_CopyRecord checa a informação de tipo de dado
em runtime para localizar os campos que requerem tratamento especial).
É claro, a variável local invisível é primeiro inicializada e
eventualmente finalizada. Este modo é extremamente ineficiente. Se
você precisa de velocidade, chame funções record-returning usando
assembler como mostrado acima, passando diretamente o endereço da
variável que irá guardar o resultado como o último parâmetro (@Result).
Bem, é isto por enquanto. Na próxima parte, veremos algumas coisas
básicas sobre o trabalho com objetos.
__________________
NOTA: O Código fonte e a aplicação DEMO estão anexados.
________________________________________________________________________
7. Fóruns / Listas
Para juntar-se a qualquer um de nossos fóruns de discussão, a melhor
forma é assinar através da web, já que dessa forma você poderá acessar
todas as funcionalidades disponíveis no site (como alterar suas opções
de assinatura, visualizar mensagens passadas, baixar arquivos, etc.).
Um ID Yahoo! é necessário para isso e você pode adquirir o seu de forma
gratuita registrando-se como um usuário Yahoo! Mas se você não deseja
registrar-se ou se não tem acesso completo à Internet, você pode fazer
sua assinatura por e-mail (basta ter acesso a uma conta de e-mail).
* Delphi: Participe da lista de discussão do Grupo Delphi-BR e faça
parte da mais profissional lista Delphi do Brasil. Essa lista tem como
objetivo a troca de informações entre os diversos programadores Delphi
em lingua portuguesa. Aqui você encontrará grandes programadores
brasileiros dando suas contribuições para a comunidade e mantendo uma
via de comunicação entre todos os programadores Delphi brasileiros.
http://br.groups.yahoo.com/group/delphi-br/
Assinatura:
http://br.groups.yahoo.com/group/delphi-br/join
delphi-br-subscribe@yahoogrupos.com.br
* Kylix (inglês): Programação Kylix.
http://groups.yahoo.com/group/KylixGroup/
Assinatura:
http://groups.yahoo.com/group/KylixGroup/join
KylixGroup-subscribe@yahoogroups.com
* Componentes (inglês): Esse é um fórum para pesquisa/recomendação de
componentes de software (componetes VCL e CLX, objetos ActiveX, DLLs,
SOs, etc.), assim como utilitários, tutoriais, informações, etc.
http://tech.groups.yahoo.com/group/components/
Assinatura:
http://tech.groups.yahoo.com/group/components/join
components-subscribe@yahoogroups.com
* Desenvolvedores de Software (inglês): Esse é um fórum para discussões
sobre o desenvolvimento de software e para a troca de experiências de
trabalho em ambientes profissionais e comerciais. Esse não é um fórum
sobre programação; os assuntos abordados são mais gerais e
independentes de linguagem de programação.
http://tech.groups.yahoo.com/group/software-developers/
Assinatura:
http://tech.groups.yahoo.com/group/software-developers/join
software-developers-subscribe@yahoogroups.com
________________________________________________________________________
8. Delphi na Rede
Por Dave Murray <irongut @ vodafone.net>
Sites em português
==================
* Comunidade Delphi de Rio Preto
Portal com componentes, novidades, artigos, links e mais.
http://www.delphirp.com.br/
* Planet Delphi
Site brasileiro especializado em componentes para Delphi. Download
grátis.
http://www.fprass.hpg.ig.com.br/
* Delphi Company
Site do Fernando Gonçalves dedicado ao mundo Delphi
http://www.delphicompany.hpg.ig.com.br/
* IntereSite
Tudo sobre Delphi
http://www.ulbrajp.com.br/~tecnobyte/
* Advanced Developers
Delphi, productos, novidades, consultoria...
http://www.adev.com.br
* Fórum Delphi-BR
http://br.groups.yahoo.com/group/delphi-br/
* Fórum Delphi !!
http://www.qualyinf.com.br/forum/delphi/delphi.html
Componentes, bibliotecas e utilidades
=====================================
Freeware
--------
* Copernic Agent 6.0 - Freeware (adware, but not spyware) *NEW*
It's the successor of Copernic 2001 5.0, the top metasearch software
for the Internet ("Top Ten Internet Tools" by USA Today, 5-star rating
from ZDNet, and "Best For 2001 - Top Five Software" by PC Magazine).
Are you tired of wasting your time with meaningless results from
search engines? Try searching with Copernic. It will surprise you!
http://www.copernic.com/desktop/products/agent/index.html
* MathX
2D and 3D graphics of one- and two-variables functions using OpenGL.
Full source code is included.
http://www.opensource.as.ro/public/MathX.zip
Artigos, dicas e truques
========================
* Delphi 7 Update 1 - by Anders Ohlsson
This update for Delphi 7 contains an updated MSSQL driver that fixes
the problem with empty user names and passwords. It also fixes an
issue with extra NULL characters being added to VARCHAR columns. The
update also contains several documentation updates.
http://community.borland.com/article/0,1410,29209,00.html
* Migrating BDE Applications to dbExpress - by Bill Todd
This white paper discusses the migration of BDE-based applications to
dbExpress.
http://community.borland.com/article/0,1410,29106,00.html
* Cross-platform Development with Borland RAD Tools
A white paper on native cross-platform development with Borland tools.
http://community.borland.com/article/0,1410,29139,00.html
* Report to the Delphi Community on Project JEDI - by Alan C. Moore
Alan C. Moore, the Project JEDI director, provides an update on the
many projects under development with the Project JEDI team.
http://community.borland.com/article/0,1410,29157,00.html
* Borland Kylix 3 versus Linux GCC Development - by William Roetzheim
http://community.borland.com/article/0,1410,29171,00.html
* Using the .NET Preview compiler in the Delphi 7 IDE - by John Kaster
An Open Tool for using the preview compiler with the Delphi 7 IDE is
available for download.
http://community.borland.com/article/0,1410,29159,00.html
* Top Windows API Books for Delphi Developers - by Zarko Gajic
Recommended selection of top books on programming Delphi with Windows
API, with links to book reviews. Books cover all of the most common
Windows API functions, and each function has the syntax and an example
of its use in Delphi Pascal.
http://delphi.about.com/library/toppicks/aatpdelphiapibook.htm
* How to validate an IBAN?
http://www.swissdelphicenter.ch/en/showcode.php?id=1470
* How to get the installed keyboard layouts?
http://www.swissdelphicenter.ch/en/showcode.php?id=1471
* How to get/set the caret blink time?
http://www.swissdelphicenter.ch/en/showcode.php?id=1472
* How to encrypt/decrypt files with Windows NTFS functions?
http://www.swissdelphicenter.ch/en/showcode.php?id=1473
* How to get mouse wheel line count?
http://www.swissdelphicenter.ch/en/showcode.php?id=1474
* How to obtain all characters in Delphi 4+ IDE - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=179
* How to obtain object property list - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=180
* How to refresh Windows desktop - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=181
* How to draw rotated text - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=182
* How to swap two numbers without a third variable - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=183
* How to turn numlock on by code - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=184
* Access a MySQL database from Delphi Standard or Personal - TheSaviour
How to access a MySQL database from Delphi Standard or Personal using
the TMySQL component.
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=185
* How to sort a TList - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=186
* How to set the width of a TComboBox - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=187
* How to access a remote registry - by m3Rlin
www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=188
* How to change a property of all components at one time - by M. Suesens
How to change a special property of all components that own this
property at one time.
http://www.delphi3000.com/articles/article_3365.asp
* CPU ID unit - by Mironescu Tony
http://www.delphi3000.com/articles/article_3367.asp
* How to implement a Sequential List in a database - by Adesh Jain
http://www.delphi3000.com/articles/article_3372.asp
* Export ALL tables from MS jet to CSV via ADO - by John Pears
http://www.delphi3000.com/articles/article_3373.asp
* High speed parser V1.5 - by Yuriy Pisarev
Component is intended for some mathematics and logical calculations.
Works at high speed - about 10000000 operations per second for simple
mathematics formulas and a little more for logical formulas.
http://www.delphi3000.com/articles/article_3374.asp
* Safety Design with a Static Instance - by Max Kleiner
How to build a real Singleton?
http://www.delphi3000.com/articles/article_3376.asp
* Interbase Backup on the Fly in a thread - by Kim Sandell
Make interbase database backups on the fly, in a background thread.
http://www.delphi3000.com/articles/article_3378.asp
* Example of a Windows Service, with a thread - by Kim Sandell
Delphi 5&6 has a template project for services, but it is incomplete.
This example builds on that template and completes the service. It
also shows how to start a thread that beeps every 2 seconds. You can
use this as a base when developing servers as services.
http://www.delphi3000.com/articles/article_3379.asp
* Interbase Sweep on the Fly in a thread - by Kim Sandell
In the Interbase Admin components there is a IBValidationService but
is hard to use as it is. Sweeping is just one of the functions of the
validation service. This component makes doing sweeps of databases
alot easier, and also works in a thread.
http://www.delphi3000.com/articles/article_3380.asp
* How to write a TCP Redirector - by Kim Sandell
Many people ask how to write servers in Indy, this primer goes through
how a TCP server is created, and how to redirect all traffic to
another remote server. This is the same as port-mapping in firewalls.
http://www.delphi3000.com/articles/article_3381.asp
* ENTER instead of TAB key - by Léo Souza
A simple alternative to allow form navigation with the ENTER key.
http://www.delphi3000.com/articles/article_3382.asp
* How to create a animated (rotating) hourglass - Henk Schreij
http://www.delphi3000.com/articles/article_3383.asp
* How to implement an Array Property - Max Kleiner
In an interface we can't use fields so when you declare a class that
implements one or more interfaces, you must provide an implementation
of all the methods declared in the interface and the fields too.
http://www.delphi3000.com/articles/article_3387.asp
* Change the color of a Listview or TreeView - by LExter LExter
http://www.delphi3000.com/articles/article_3388.asp
* SQL without bureaucracy - by Josir Gomes
How to quickly run SQL without dropping components on your form.
http://www.delphi3000.com/articles/article_3389.asp
* Keyboard Hook - by William Egge
Creating a keyboard hook in your application.
http://www.delphi3000.com/articles/article_3390.asp
* Converting a SID to a string - by Bryan Ashby
Convert a Security Identifier (SID) into a human-readable string.
http://www.delphi3000.com/articles/article_3391.asp
* Changing Creation and Last accessed date/time for files - David Bolton
http://www.delphi3000.com/articles/article_3392.asp
* How to check if an OLE object is installed - by Mike Shkolnik
http://www.delphi3000.com/articles/article_3394.asp
* How to read contact list from MS Outlook - by Mike Shkolnik
http://www.delphi3000.com/articles/article_3395.asp
* Fast data transfer to MS Excel - by Mike Shkolnik
http://www.delphi3000.com/articles/article_3396.asp
* Multi Lingual BoolToStr() and SexToStr() - by Mike Heydon
http://www.delphi3000.com/articles/article_3398.asp
Tutoriais
=========
* "Navigating and Editing a ClientDataSet" - by Cary Jensen
You navigate and edit a ClientDataSet in a manner similar to how you
navigate and edit almost another other dataset. This article provides
an introductory look at basic ClientDataSet navigation and editing.
http://community.borland.com/article/0,1410,29122,00.html
* Sophisticated Delphi Pascal techniques - by Zarko Gajic
A Beginner's Guide to Delphi Programming: Chapter 7. Time to extend
your Delphi Pascal knowledge to the max. Here are some intermediate
Delphi problems and articles for everyday development tasks.
http://delphi.about.com/library/weekly/aa091702a.htm
* Designing an XML Grammar with DTDs - by Philip Page
XML is a great medium for data transfer and definition, but it must be
consistent to make it consumable. Learn more about creating a DTD to
determine consistent XML.
http://builder.com.com/article.jhtml?id=u00320021004ppg01.htm
* SQL Basics: Creating and Altering Tables - by Shelley Doll
Learn the basic DDL commands to add, alter, and remove tables and
databases with this article from the Builder.com SQL basics series.
http://builder.com.com/article.jhtml?id=u00320020902dol01.htm
* SQL Basics: Number Data Types - by Shelley Doll
Failing to understand number data types poses a DBA's greatest risk of
compromised data. The SQL92 standard dictates how database
manufacturers define number behaviors, such as length and truncation.
http://articles.techrepublic.com.com/5100-10878_11-1051673.html
* SQL Basics: String Data Types - by Shelley Doll
Data type implementations vary from database to database but a working
knowledge of the SQL specification will always give you a good idea of
what's going on. This article breaks down the basic rules of deploying
string data types.
http://builder.com.com/article.jhtml?id=u00320020918dol01.htm
* A Guide to Automatic Garbage Collection Systems - by Paul Tyma
Java and .NET feature automatic garbage collection, which allows you
to worry about programming instead of system cleanup. Learn more about
the approaches often used to add this feature to applications.
http://builder.com.com/article.jhtml?id=u00320020930gcn01.htm
Outros links
============
* The Delphi Bug List
http://buglist.jrsoftware.org/
* Delphi 7/.NET User Group Tour 2002, US / Canada - by David Intersimone
Borland is taking Delphi 7 and the Delphi Preview for Microsoft .Net
on the road in the US and Canada.
http://community.borland.com/article/0,1410,29089,00.html
* Pascal and its Successors - by Niklaus Wirth
Pascal was designed in 1969 in the spirit of Algol 60 with a concisely
defined syntax representing the paradigm of structured programming.
With the advent of the micro computer it became widely known and was
adopted in many schools and universities. In 1979 it was followed by
Modula-2 which catered for the needs of modular programming in teams.
In an effort to reduce language complexity and to accommodate object-
oriented programming, Oberon was designed in 1988. This article
presents some aspects of the evolution of this family of languages.
http://www.swissdelphicenter.ch/en/niklauswirth.php
* Pascal IRC channel
There is a very nice IRC channel about Pascal on DalNet
(http://www.dal.net). The channel is just about Pascal (not Delphi).
You can find it on irc.dal.net at #turbopascal. The channel also has
a nice page with lots of sample codes. You can find their homepage at
http://www.pastcow.org - As you can imagine, a channel for Delphi
users can also be found there: #delphi.
* BorCon Europe
London UK, October 28-29 2002
http://www.borconeurope2002.com/
* BorCon Japan
Tokyo Japan, November 19-20 2002
http://www.borland.com/jp/
* BorCon France
Paris France, November 21-22 2002
http://info.borland.fr/conference/2002/
________________________________________________________________________
VOCÊ PODE NOS AJUDAR
Nós precisamos de sua ajuda para manter esse boletim ativo e cada vez
maior. Você pode ajudar indicando o boletim a seus amigos e colegas:
http://www.latiumsoftware.com/br/pascal/index.php
Você também pode votar para nós em um ou todos esses rankings para dar
maior visibilidade a nosso site e assim aumentar o número de assinantes
do boletim:
http://www.programmingpages.com/?r=latiumsoftwarecomenpascal
http://top100borland.com/in.php?who=20
São alguns segundos para você que REALMENTE significam muito para nós.
Não esqueça que também precisamos de artigos para os boletins e que
existe um prêmio para um dos autores em cada número (em inglês).
Todos os artigos serão considerados mas nós estamos particularmente
interessados em artigos sobre Kylix já que existe pouco material
disponível online.
* Envie seu artigo em inglês para
pascal-newsletter-owner@yahoogroups.com
* Envie seu artigo em português para
boletim-pascal-owner@yahoogrupos.com.br
________________________________________________________________________
Se você não recebeu o código fonte completo dos exemplos neste número,
você pode obtê-los em http://www.latiumsoftware.com/br/pascal/p0040.zip
________________________________________________________________________
Esse boletim é fornecido "COMO ESTÁ" sem garantias de qualquer tipo. Seu
uso implica na aceitação dos termos de licença e isenção de garantia que
podem ser lidos em http://www.latiumsoftware.com/br/pascal/index.php
Os artigos são propriedade e copyright de seus respectivos autores e
foram reproduzidos aqui com sua permissão. Você pode redistribuir esse
boletim desde que na sua íntegra (incluindo notas de propriedade e
copyright), sem alterações e de forma gratuita.
________________________________________________________________________
Página do grupo:.....: http://br.groups.yahoo.com/group/boletim-pascal/
Assinar..............: boletim-pascal-subscribe@yahoogrupos.com.br
Cancelar assinatura..: boletim-pascal-unsubscribe@yahoogrupos.com.br
Assinar/cancelar.....: http://groups.yahoo.com/group/boletim-pascal/join
Problemas com sua assinatura? boletim-pascal-owner@yahoogrupos.com.br
________________________________________________________________________
Boletim Pascal http://www.latiumsoftware.com/br/pascal/index.php
Copyright (c) 2003 por Ernesto De Spirito. Todos os direitos reservados.
________________________________________________________________________
|