3 de junho de 2009

Componente Delphi para embutir arquivos numa aplicação

Recentemente, um amigo me perguntou se havia jeito de inserir um arquivo inteiro num formulário do Delphi/C++ Builder. A ideia dele era construir uma aplicação que, entre outras coisas, tocasse um MP3 mas não queria ter que distribuir o tal MP3 junto com a aplicação.

Um excelente recurso das linguagens de programação visual (Delphi, VB, Visual C#) é a facilidade de componentização, isto é, criar um pedaço de código funcional que pode ser usado em diversos projetos. Assim, ajudei esse amigo a montar um componente no Delphi que é capaz de embutir um arquivo qualquer num Form ou Data Module. Da mesma forma que o arquivo externo, esse conteúdo embutido é acessível através de um Stream.

Para construir o componente, criei um novo projeto de pacote Win32 no Delphi mas, se você possuir um pacote, pode incluir o novo componente nele mesmo. Criei um novo componente Win32, escolhi TComponent como ancestral (classe base) e dei-lhe o nome de TWRecurso. Depois, adicionei-o ao projeto do pacote.

A interação desse componente no ambiente de programação é restrita a uma única propriedade exibida no Object Inspector: o nome do arquivo a ser embutido. Ao modificar esse nome, o componente inserirá no Form o conteúdo do arquivo indicado. Na classe TWRecurso, crio a propriedade FileName e um MemoryStream para guardar o conteúdo do arquivo:
TWRecurso = class(TComponent)
private
FFileName: String;
FDados: TMemoryStream;
protected
procedure SetFileName (AFileName: String);
public
{ ... }
property Dados: TMemoryStream read FDados;
published
property FileName: String read FFileName write SetFileName;
end;

FileName está na área published pois será visível no Object Inspector. Já Dados (o TMemoryStream) está na área public, onde poderá ser acessado via programa mas não pelo Inspector. Como, então, Dados será embutido no Form?

A mágica está no mecanismo de persistência dos componentes no Delphi. A função DefineProperties, que é parte desse mecanismo, nos dá a oportunidade de acrescentar ao Form o que for necessário. Reproduzo abaixo a sobrecarga da função para esse exemplo:
procedure TWRecurso.DefineProperties(Filer: TFiler);
function DeveGravar: Boolean;
begin
Result := (FDados.Size > 0);
end;
begin
inherited;
Filer.DefineBinaryProperty('Dados', LoadDadosProperty, StoreDadosProperty, DeveGravar);
end;

O código mostra que estamos definindo uma nova propriedade Dados, indicando as funções que devem ser usadas para gravação e leitura e informa que esta propriedade só deve ser gravada quando nosso MemoryStream interno tiver algum dado.

As funções para gravação e leitura recebem um Stream como parâmetro, bastando gravar nele nosso conteúdo - ou ler dele, na função de leitura. Um detalhe é que não há como saber de antemão a quantidade de bytes que têm que ser lidos; então, esta informação também tem que ser incluída.
procedure TWRecurso.LoadDadosProperty(Reader: TStream);
var lSize : Int64;
begin
FDados.Clear;
Reader.Read(lSize, sizeof (lSize));
FDados.CopyFrom (Reader, lSize);
end;

procedure TWRecurso.StoreDadosProperty(Writer: TStream);
var lSize : Int64;
begin
FDados.Position := 0;
lSize := FDados.Size;
Writer.Write(lSize, sizeof (lSize));
Writer.CopyFrom(FDados, FDados.Size);
end;

Se mudar o nome do arquivo, o nosso Memory Stream deve carregar o contéudo do novo arquivo:
procedure TWRecurso.SetFileName (AFileName: String);
begin
if FileName <> AFileNAme then begin
FFileName := AFileName;
FDados.Clear;
if (FileExists (AFileName) ) then
FDados.LoadFromFile(AFileName);
end;
end;

Não mostrei, mas o construtor de TWRecurso deve instanciar FDados e iniciar FFileName com brancos, enquanto o destrutor faz a limpeza necessária.

Montei esse exemplo com Delphi2005 mas não deve haver diferenças profundas para outras versões. Lembro, ainda, que o C++ Builder é capaz de compilar e instalar componentes feitos em pascal. Basta criar um pacote no C++ Builder e incluir o fonte WRecurso.pas para ter o componente também nesse ambiente.

Com esse componente, é possível embutir qualquer tipo de arquivo num Form: XML, MP3, TXT, etc.

14 comentários :

valdumon disse...

Gostaria muito de embutir arquivos mp3 e não distribuir os arquivos junto e saber também como tocá-los com seu componente.

valdumon@gmail.com

valdumon disse...

Não consegui nem instalar seu componente amigo. Não tá faltando nada junto não?

valdumon@gmail.com

Luís Gustavo Fabbro disse...

Está dando alguma mensagem de erro ? Esse componente foi construido com uma versão mais antiga do Delphi e pode ser que alguma das classes/funções em uso tenha mudado.

Além disso, o download contém apenas o fonte do componente; para instalá-lo, ele tem antes que ser adicionado a um projeto de pacote de componentes.

[]s

Luís Gustavo Fabbro disse...

Você está usando qual biblitoeca para tocar o MP3 ? O componente disponibiliza o conteúdo do arquivo na forma de um TStream. É preciso entender o modo como a biblioteca aceita o conteúdo MP3 para ver como passar o TSream para ela.

Att.

valdumon disse...

Então caro amigo Luís Gustavo Fabbro,
estou trabalhando com matérias para concursos em mp3, até que eu já fiz meus próprios códigos anti-pirataria e funciona com meus programas já feitos. Fiquei interessado em poder vender um programa com os arquivos de mp3 embutidos como você disse, juntamente com o meu sistema anti-pirataria, funcionaria bem.

valdumon disse...

Luís Gustavo Fabbro
estou trabalhando com matérias para concursos em mp3, até que eu criei um sistema de travar a cópia de programas e funciona bem. Estava precisando de algo assim, de embutir o arquivo mp3 e distribuir sem o arquivo e tocá-los. Se puder me ajudar eu agradeço.

Luís Gustavo Fabbro disse...

Que ponto do processo você está precisando de ajuda ? O quê exatamente você não conseguiu fazer ?

Anônimo disse...

Caro Luís:
Gostaria de saber se o componente funciona com o Lazarus e se é possível criar um "Instalador" de programas com ele.
Parabéns, e obrigado.
Carlos Alves.

Luís Gustavo Fabbro disse...

Carlos

Eu não sei qual o grau de compatibilidade do Lazarus com a VCL para te dizer se o componente do post é compatível. Só fazendo o teste mesmo pra saber se o esquema de stream utilizado pelo componente está contemplado no Lazarus.

Quanto à usá-lo pra criar um instalador, não vejo problemas. Eu mesmo já montei um instalador simples que incluia no executável Delphi o conteúdo a ser instalado como um arquivo ZIP. Isso é possível porque o componente em si não faz qualquer inferência sobre os bytes armazenados.

[]s

Tiago Shimizu disse...

Luíz, parabéns pelo compartilhamento do conhecimento por meio dos artigos. Fiquei interessado no componente aqui criado, porem, estou precisando utiliza-lo com alguns vídeos, pequenos, e meu problema está sendo encontrar o player para os mesmos. Por acaso conheceria algum player compativel como o stream ao qual o componente trabalha?
Obrigado.
Tiago.

Luís Gustavo Fabbro disse...

Tiago

Eu nunca precisar utilizar componentes de vídeo mas você pode encontrar algum que sirva aos seus propósitos no site de projetos open source SourceForge.

Veja, por exemplo, o componente TSCap32.

Tiago Shimizu disse...

OK. Vou estar verificando o componente indicado e também o site.
Muito obrigado pela ajuda.

valdumon disse...

Se eu colocar sua unit WRecurso no mesmo diretório do meu projeto vai funcionar?

Luís Gustavo Fabbro disse...

Não há uma pasta obrigatória onde a unit deva estar. Mas ela precisa ser adicionada antes a um projeto de pacote de componentes e este pacote deve ser compilado e instalado para que você possa, então, embutir arquivos em formulários de outros projetos seus.

[]s

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.