I have an existing database with blobs contain OLE compound files. I have a requirement to read these OLE compound files and open them in the Delphi 7 TOleContainer control.
Note that I don't have the source of the app that reads and write to the database. The database remains in active use, so my solution will be used on an ongoing basis, not just for a one-off data extraction.
TOleContainer has a SaveAsDocument method, and by experimentation I have found that, for a given file, this method produces OLE compound files which are identical to those created in the database when that file is added.
However, TOleContainer does NOT have a corresponding LoadFromDocument method. It has other Load* and Create* methods, but none seem capable or suitable for loading the output from SaveAsDocument.
The delphi 7 implementation of SaveAsDocument is this, from the OleCtnrs.pas module:
procedure TOleContainer.SaveAsDocument(const FileName: string);
var
TempStorage: IStorage;
PersistStorage: IPersistStorage;
begin
CheckObject;
if FModSinceSave then SaveObject;
FOleObject.QueryInterface(IPersistStorage, PersistStorage);
if PersistStorage <> nil then
begin
OleCheck(StgCreateDocFile(PWideChar(WideString(Filename)), STGM_READWRITE
or STGM_SHARE_EXCLUSIVE or STGM_CREATE, 0, TempStorage));
OleCheck(OleSave(PersistStorage, TempStorage, False));
PersistStorage.SaveCompleted(nil);
end;
end;
Please provide an implementation of LoadFromDocument which is capable of loading the output from SaveToDocument, and which I can use to patch OleCtnrs.pas. Or else point me to an existing solution.
Thanks!
You have to load the file by using TOleContainer.CreateObjectFromFile. Do not use TOleContainer.LoadFromStream/File, that only works with files that are saved with TOleContainer.SaveToStream/File. Files saved that way get a Delphi specific header containing a four byte code (BDOC) and size (and maybe something more).
According to the documentation for Delphi 2007 (should be the same for ), you can use 'TOleContainer.LoadFromStream'. From the Delphi 7 help file (emphasis mine):
Call LoadFromStream to load an OLE object from a stream. If OldStreamFormat is true, LoadFromStream loads OLE objects saved by a TOleContainer object as well as OLE objects saved using the current format; if OldStreamFormat is false, LoadFromStream will not load OLE objects saved by the library. If there's already an OLE object in the container, it is destroyed and any changes the user made to it are discarded.
Related
I have created an application that manage an embedded database
My Customer want this application to be in one file
My Task is to modify my application so it can extract database file from exe , edit it ,and include if again at run time not in compile time
An executable file cannot be modified while the executable is running. Which means that in order to achieve your goal you would need another process. You could do the following:
Start your process.
Extract the DB from the process image.
Make changes to the DB.
Extract another executable file from the original image.
Start a second process based on this extracted images.
Terminate the first process.
Have the second process update the disk image with the modified DB.
Frankly this is a quite terrible idea. Don't even attempt this. The complexity serves no useful purpose, and the whole concept feels brittle.
Keep the data in a file separate from the program, as nature intended.
This is of course a bad idea, just think of what virusscanners are going to think of this approach. Also what happens if the exe crashes, will your db now lose all of its updates?
You can have the exe create a self extracting archive containing all the files needed.
This works as follows (the steps are the same as #David above, except that the components listed do most of the work for you).
Extract self extracting zip.
this contains: the real exe to be started upon extract
the database
some files needed to recreate a new self extracting exe
Upon closing the program it will create a new zip file, including:
Itself (in readonly form)
The database
some files needed to recreate a new self extracting exe
It will then transform the zip-file into a new self-extracting exe
the new self-extracting archive will start the exe included in it's embedded zip file as per #1.
Here's some sample code from sfx-zip-delphi.
program SelfExtractingZip;
{$APPTYPE CONSOLE}
uses
// Add a ZipForge unit to the program
SysUtils, ZipForge, Classes;
var
archiver : TZipForge;
begin
// Create an instance of the TZipForge class
archiver := TZipForge.Create(nil);
try
with archiver do
begin
// Set the name of the archive file we want to create.
// We set extension to exe because we create a SFX archive
FileName := 'C:\test.exe';
// See SFXStub demo in ZipForge\Demos\Delphi folder
// to learn how to create a SFX stub
SFXStub := 'C:\SFXStub.exe';
// Because we create a new archive,
// we set Mode to fmCreate
OpenArchive(fmCreate);
// Set base (default) directory for all archive operations
BaseDir := 'C:\';
// Add the C:\test.txt file to the archive
AddFiles('c:\test.txt');
CloseArchive();
end;
except
on E: Exception do
begin
Writeln('Exception: ', E.Message);
// Wait for the key to be pressed
Readln;
end;
end;
end.
Solutions for self extracting exe's
Paid
If you want a paid solution: http://www.componentace.com/sfx-zip-delphi.htm
Free
Or free: http://tpabbrevia.sourceforge.net/Self-Extracting_Archives
The abbrevia docs for self-extracting files are here: https://sourceforge.net/projects/tpabbrevia/postdownload?source=dlp
See page 293.
I use Delphi and TmongoWire. I would like to insert TFileStream (picture, pdf) with TmongoWire on a MongoDb server. My work is based on this demo file: Delphi MongoDB driver
d:=BSON([
'id',mongoObjectID,
'name',ItemForm.txtName.Text,
'address',ItemForm.txtAddress.Text,
'phone',ItemForm.txtPhone.Text,
'picture', TFileStream.Create('C:\temp\mongotest.jpeg', fmOpenRead or fmShareDenyWrite)
]);
FMongoWire.Insert(mwx1Collection,d);
//LoadItems;
LoadItem(ListView1.Items.Add,d);
UpdateCount;}
Inserting a stream file does not work, can somebody see why ?
The program does not compile : Error --> BSON can not be called with these arguments
There is no problem to insert a simple text field (code below) :
d:=BSON([
'id',mongoObjectID,
'name',ItemForm.txtName.Text,
'address',ItemForm.txtAddress.Text,
'phone',ItemForm.txtPhone.Text
]);
Thanks
Please refer to this tutorial: http://owlyci.com/docs/articles/WorkingWithGridFS
In GridFS files are referenced by names (or _id in rare cases), so you should save streams to GridFS with some filenames (aaa.jpg, 81276482634823.dat or any other), and then store this filenames to your collection
d:=BSON([
'id',mongoObjectID,
'name',ItemForm.txtName.Text,
'address',ItemForm.txtAddress.Text,
'phone',ItemForm.txtPhone.Text,
'picture', 'aaa.jpg'
]);
MongoDB has it's own sub-system specifically to store (large) files: GridFS
see more here: http://docs.mongodb.org/manual/core/gridfs/
and for a TMongoStream object that uses this: https://github.com/stijnsanders/TMongoWire/blob/master/mongoStream.pas
(For the sake of completeness: Delphi doesn't know by itself how to convert a TFileStream to a Variant, which causes the error.)
I am able to open a CHM file by passing a ShortInteger and casting it as a Word for the dwData parameter. I.E.
Unit Help; //this is where the Id's are set with their description
Interface
Const
Address_File = 35; //delphi identifies Address_File as a shortint
etc..
Call get help pass my ID
GetHelp(Address_File); //call get help pass my ID to open to the Address_File topic
GetHelp procedure
procedure GetHelp(HelpID : Word);
begin
Application.HelpFile := ProgramPath + 'help.chm';
HtmlHelpW(0, PWideChar(Application.HelpFile),HH_HELP_CONTEXT , HelpID);
end;
HtmlHelpW function
function HtmlHelpW(hwndCaller : HWND; pszFile: PWideChar; uCommand : Integer;
dwData : DWORD) : HWND; stdcall; external 'hhctrl.ocx' name 'HtmlHelpW';
As I pass different ShortIntegers I am able to initialize the help file at different sections.
However I can't figure out how the values are mapped. There are some sections in the chm file that I want to be able to map to but the short Integer or context ID associated with them is not documented in the program or is not mapped.
Free Pascal comes with a chmls.exe util that has a command that tries to recover the alias (context) data:
chmls, a CHM utility. (c) 2010 Free Pascal core.
Usage: chmls [switches] [command] [command specific parameters]
Switches :
-h, --help : this screen
-p, --no-page : do not page list output
-n,--name-only : only show "name" column in list output
Where command is one of the following or if omitted, equal to LIST.
list <filename> [section number]
Shows contents of the archive's directory
extract <chm filename> <filename to extract> [saveasname]
Extracts file "filename to get" from archive "filename",
and, if specified, saves it to [saveasname]
extractall <chm filename> [directory]
Extracts all files from archive "filename" to directory
"directory"
unblockchm <filespec1> [filespec2] ..
Mass unblocks (XPsp2+) the relevant CHMs. Multiple files
and wildcards allowed
extractalias <chmfilename> [basefilename] [symbolprefix]
Extracts context info from file "chmfilename"
to a "basefilename".h and "basefilename".ali,
using symbols "symbolprefix"contextnr
extracttoc <chmfilename> [filename]
Extracts the toc (mainly to check binary TOC)
extractindex <chmfilename> [filename]
Extracts the index (mainly to check binary index)
This might be a start, since at least you'll know which pages are exported using an ID, and maybe the URL names will give some information.
The util is in recent releases (make sure you get 2.6.0) and also available in Free Pascal source, which should be convertable to Delphi with relatively minor effort.
Basically the chmls tool was created out of various test codebases. The testprograms decompiled and printed contents of different CHM sections and were used while creating the helpfile compiler, chmcmd, which is also part of FPC.
In Delphi, calling a help file is rather easy. In any VCL Forms application, you can set the HelpContext property of almost any control to a unique Context ID, which corresponds to a particular topic in the Help File. The Help File was compiled with these mappings, but when you decompile it, these mappings are no longer there. You must have access to the original help file project in order to know these ID's.
Set HelpContext of controls to the corresponding Context ID in the Help File
Set HelpType of controls to htContext to use the HelpContext ID
Assign Application.HelpFile to the appropriate location of the CHM file
When pressing F1 anywhere in your application, the help file will open based on the Help Context ID on the control, or its parent control
If you don't have the original project, and you don't want to re-create it, then you would have a long task of iterating through the Context ID's of your help file. Try to call the help file starting from 0 through 1,000 or possibly 50,000, depending on the size of it.
A practice I implement is a set of constants in a designated unit called HelpConstants.pas which is shared across our common application base. Each constant name uniquely and briefly describes the topic which it represents. Upon starting the application, I dynamically assign these Context ID's to their corresponding controls (usually forms) and VCL takes care of the rest.
I got the utility Marco suggested from
https://github.com/alrieckert/freepascal_arm/blob/master/packages/chm/bin/i386-win32/chmls.exe
(download by selecting View Raw).
I was able to extract all the context tags from the .chm help file and add the one I was interested in to my C++ Builder program by calling Application->HelpJump().
HTH
I have read multiple posts on the issue, and none seem to come to a decent conclusion to my question. (Perhaps I'm trying to see if anything has popped up lately.)
I have a small charity app that handles pledges. In doing so, it needs to work with and print documents.
Thing is, if Word is open in the background, the app thread will hang and won't respond to the closure of Word, and I have to roll back manually and close word. Sure, that all works fine, but I simply cannot guarantee that the end user will close Word, even if I put the instruction in a user manual.
I'm not too fussed about speed, but I guess that if it can be enhanced, it would be a nice little bonus.
Have any libraries been released for Delphi that will allow me to open, edit, print, and save documents? If not, is there a way to use Word Automation in such a way that it will not conflict with another open handle of Word when opened?
If you use GetActiveOleObject, you will get the running instance of Word.
By using CreateOleObject, you will get a new instance and shouldn't be troubled by other running instances.
In case you use the TWordApplication, wrapper you can set ConnectKind to ckNewInstance to accomplish this. By default, TWordApplication will try to connect with a running instance.
If you want to open edit and print Word documents and you don't mind using RTF format for what you're doing, investigate TRichView.
It will generate rich documents that are in RTF format, which is one of the formats MS word supports. I don't think it directly reads .DOC files but you can convert .DOC and .DOCX into RTF, for most simple files, but certain advanced formatting features would be lost in the conversion.
It has the advantage of working without any need for even any copy of MS Word to be installed on the machine that is going to do the document processing. For production of receipts and other simple documents, this would be the most reliable technique; Don't use Word directly, at all.
procedure PrintViaWord (const filename: string);
const
wdUserTemplatesPath = 2;
var
wrdApp, wrdDoc, wrdSel: variant;
begin
wrdApp:= CreateOleObject ('Word.Application'); // create new instance
sleep (5000); // this fixes a bug in Word 2010 to do with opening templates
wrdDoc:= wrdApp.documents.add (
wrdApp.Options.DefaultFilePath[wdUserTemplatesPath] + '\mytemplate.dot');
wrdDoc.Select;
wrdSel:= wrdApp.selection;
wrdApp.Options.CheckSpellingAsYouType:= 0;
wrdSel.paragraphformat.alignment:= 1;
wrdSel.typetext ('This is a program demonstrating how to open Word in the background'
+ ' and add some text, print it, save it and exit Word');
wrdDoc.SaveAs (filename + '.doc');
wrdApp.ActivePrinter:= 'Konica Minolta 211';
wrdApp.PrintOut;
WrdDoc.SaveAs (filename + '.doc');
wrdApp.quit;
wrdSel:= unassigned;
wrdDoc:= unassigned;
wrdApp:= unassigned
end;
I am an engineer and not a software programmer, so please excuse my ignorance.
I have written a Delphi(7SE) program to read “real” datatype from a USB port connected to two digital thermometers.
I have completed this much of the program.
What I have not completed as yet is explained by the following:
I wish to save this “real” data to a Binary File(s). A text file would be fine as well, but i'm concerned about having a big data file.
I also wish to read this data back from the Binary/Text File to display the data using my Delphi application.
I don’t think this would be too difficult. I currently save my data in .CSV format.
The twist here is that the binary file should contain data from different sessions initiated by the user of my application.
So when I click on say, a button called “ historical” data, a new window/form would pop up that would show different session times that I had started & stopped from earlier times. Then a session would be selected and data then retrieved for displaying.
Can this be done in one binary files or would you have to use 2 files: one for the “real” data and another which indexes the different session times?
My requirement for this way of saving binary data is that I would not have to keep typing in filenames and therefore keeping track of many data files.
For example a thermo.hst(historical data) and a thermo.idx (index file) file would contain all the information such as actual temp data, time of read data, session start & end times etc.
Any useful pointers and hopefully code with as much detail would be greatly appreciated.
I hope this sample code isn't too late to be helpful.
(I've added this as another answer from me so that I can cleanly list the code. If this or my previous post answers your question, please click the answer icon so I get reputation points!)
Below is some rough code that shows how to read the sections in an ini file and find the largest filename. I confirmed it compiles and seems to return valid values, but you'll need confirm it does what you need. It's more to show you the idea...
Note that if your data filenames have an extension, you'll have add code to remove the extension in my sample code using something like: FileName := ChangeFileExt(Filename, '').
// Call with an open inifile. Returns the name of the next filename, or '' if trouble
Function GetNextFileName( const IniFile: TInifile):String;
const
BASE_FILENAME = 'File.'; // sections in the ini file will be [File.1], [File.2], ... [File.100], etc.
var
Sections: TStringList;
NumericPartAsString: String;
NumericPartAsInteger: Integer;
ListIndex: Integer;
LargestFileNumberSeenSoFar: Integer;
begin
Result := '';
Sections := TStringList.Create;
IniFile.ReadSections(Sections); // fills StringList with the names of all sections in the ini file
if( Sections.Count = 0) then
Result := BASE_FILENAME + '1'
else
begin // find largest extension
LargestFileNumberSeenSoFar := -1;
ListIndex := 0;
while ListIndex <= (Sections.Count - 1) do // for every string (which is also a filename) in the string list:
begin
NumericPartAsString := StringReplace(Sections.Strings[ListIndex], BASE_FILENAME, '', []); // remove base filename
if (NumericPartAsString <> '') then
begin
NumericPartAsInteger := StrToIntDef(NumericPartAsString, -1);
if (NumericPartAsInteger > LargestFileNumberSeenSoFar) then
LargestFileNumberSeenSoFar := NumericPartAsInteger;
end;
inc(ListIndex);
end;
if (LargestFileNumberSeenSoFar > -1) then
Result := BASE_FILENAME + IntToStr(LargestFileNumberSeenSoFar + 1);
end;
Sections.Free;
end; { GetNextFileName }
procedure TForm1.Button1Click(Sender: TObject);
var
IniFile: TInifile;
NewFileName: String;
begin
IniFile := TInifile.Create('c:\junk\ini.ini');
NewFileName := GetNextFileName(Inifile);
if (NewFileName = '') then
ShowMessage('Error finding new filename')
else
ShowMessage('New filename is ' + NewFileName);
IniFile.Free;
end;
By using a database, you've in part just renamed part of the problem from "typing in file names and keeping track of many data files" to "typing in data set name and keeping track of many data sets."
In both cases, as an example, either the user or the program has to create a new file/data set name, and choose from a list of files/data sets to open later. And in both cases you have to make a call to a function named something like "DeleteDataSet".
You might re-consider whether you really need a database (with associated learning curve for API, how to define the data structure, update it in the field when something changes, browse the data in a viewer, access the data programatically, proprietary format, database maintenance, repair tools, installation, etc.) I'm sure that you could learn all these, and they might be valuable in future projects. But, maybe a simpler approach would be more appropriate and adequate for this one-time project by a non-software engineer.
If you're willing to have a proliferation of many unique, standalone data files on one folder, I'd encourage you to stick with what's working: use one CSV file per data set. (Have you run into speed or size issues with CSV files containing a single data set thus far? That would be an enormous amount of data!) One nice thing about CSV files is that you can just pop them into an editor to view or edit...
And, then, add a second file that contains filenames, and other descriptive information. This file would be a simple TIniFile:
[My Name one]
Date=06 June 2010
StartTime=12:30pm
StopTime=3:15pm
FileName=Data1.csv
[My Name two]
...
The tools available in Delphi for TIniFile will let you easily manage this list, including ReadSections into a string list that you can just assign to a combo box for the user to select a data set. (See example below) And, like the CSV files, you can just edit the .ini file in any text editor.
You'll need to build a UI to allow a user to delete a dataset (section in the ini file and associated .csv file). To give you an idea how the ini file would be used, here's the pseudo-code for deleting a data set:
(In IDE Object Inspector, set ComboBox.Style := csDropDownList to prevent user from typing in a name that doesn't exist.)
Load a combo-box that shows available data sets.
1. ComboBox.Items := IniFile.ReadSections;
In the combo-box's OnSelect event handler:
2. DeleteFile(IniFile.ReadString(CombBox.Text, 'FileName', ''));
3. IniFile.EraseSection(ComboBox.Text); // remove the section from the inifile
Heck, that's not a lot of code, even after you add a bit of protection and error checking!
Maybe the above solution will be voted down by others here as trying to put a round peg in a square hole or re-inventing the wheel. And I might agree with them. There are good arguments against this approach, including proliferation of many files. But, if it was me, I'd at least consider this approach as keeping-it-simple and not requiring anything new but that you learn the TIniFile object, which is quite powerful.
The data can be interleaved. Just start every block (a set of history) with a header that identifies the block and contains its length. When reading you can then easily separate that.
You can hold an additional index file next to this for fast access if you require, but if this is the case, I would start studying some embedded database. (TDBF, sqlite or embedded firebird).
I would also head in the database direction if I expected that my querying would get more complicated in the future.
If it is all about logging, the data doesn't get gigantic and the performance of the view is fine, I would keep the binary file and avoid the hassle of having users to install and maintain a databsae solution. (TDBF is maybe an exception to that, since it is completely statically linked)