I use Windows 10, Delphi Berlin and Microsoft Office 2007. I try to get the number of opened Excel window. When I download Excel file and open it a seperated Excel runs so only one workbook exists in one Excel window.
I imported Microsoft Office 12.0 Object Library and wrote 2 procedures. Button1Click works with tExcelApplication and Button2Click does with CreateOleObject('excel.application'). After I run Excel the former works well but Count is recognized as an error just in the editor and the latter returns 0.
How Can I remove the annoying error message or get the _Excel to work?
type
TForm1 = class(TForm)
ExcelApplication1: TExcelApplication;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Caption := IntToStr(ExcelApplication1.Windows.Count);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
_Excel: Variant;
begin
_Excel := CreateOleObject('excel.application');
Button2.Caption := _Excel.windows.count;
end;
The message in the IDE is because you are using late bound COM. The method calls are dispatched at runtime and only at runtime do you find out whether or not the method exists. Because of that the compiler can't check the validity of your code. Your code is fine because it executes correctly, but the nature of late bound COM means the IDE thinks your code contains syntax errors. You just have to ignore it when using late bound COM.
You can switch instead to early bound COM and have the compiler be able to check the syntax of your code.
There are pros and cons of both approaches. Late bound can often yield simpler and more concise code. But at the expense of frustration when you only find out your mistakes at runtime.
If the value returned is zero then I guess the obvious conclusion is that there are no windows. The late bound code is creating a new instance of Excel, but the early bound code is attaching to an existing instance. To obtain an existing instance, if there is one, use GetActiveOleObject.
Related
I have a fairly complex Excel sheet made by an external company. It uses several formulas, lookups etc.
From Delphi (version 6 or 7) , is it possible to have this sheet open in the background, allow a user to input a few variables, then pass these on to excel and have the calculated result returned to the delphi program ?
I know how to open excel and read the entire sheet using ADO, but here I need to send data both ways, by reference of range or coordinate.
This could be done as a unit written in Delphi, but the logic is already done in Excel, and it's simpler to write such calculations in Excel.
Simplified example:
procedure do_it;
var
v1: integer;
v2: integer;
begin
v1 := strtoint(inp1.text); // operator provided numbers
v2 := strtoint(inp2.text);
theresult.caption := get_excel_value(v1, v2); // get back result from excel formulas
end;
function get_excel_value(v1, v2: integer) : string;
begin
// pseudocode
myExcel.range('A3').text := v1;
myExcel.range('A4').text := v2;
myExcel.recalculate; // update excel sheet on demand
result := myExcel.range('A10').text;
end;
Is it correct to assume that I need to be looking in the direction of Excel COM or Excel OLE ?
If possible to do - can the excel sheet call and use macros internally (invisible to Delphi) ?
Microsoft Office (Word, Excel and others) are applications which can be fully automated from another application. Everything you can do by hand can also be done programmatically from another application and of course from your Delphi application.
To ease the automation, Delphi is delivered with non-visual components which are "wrapper" around the underlying COM objects and exposes the same properties, methods and events as the COM interface.
Delphi has several sets of components for working with several Office versions. There are components for Office 2000, Office XP and Office 2010. 2010 version is available from Delphi XE2. Of course there are more Office versions, but don’t worry: Microsoft has carefully made his interfaces upward compatible. For example, if you use the Office 2000 components, you can still automate all Office versions from 2000 to the latest. The only restriction is that you cannot easily use new features if you use the old component.
Actually, it is better to use the older components which are capable of doing what you need to do!
This is because the compatibility in Microsoft office API goes upward and not downward.
Since there are several sets of components, you must make sure the correct version is installed.
o see the installed packages, launch Delphi and go to the menu / Component / Install Packages. You see a long list of all installed design time packages with a checkbox telling that the package is available or not.
Locate “Microsoft Office 2000 Sample Automation Server Wrapper Components” (Or Office XP) and check the checkbox in front of the one you plan to use. Click OK and verify that your component palette now include a tab “Servers”.
As an example, we will create a sample application to insert a sentence at the end of a Word document. This is quick and easy!
Create a new VCL forms application, drop a TWordApplication and a TButton on the form. Then add the code below as the button’s OnClick handler.
The quickest way to locate it is to enter WordApplication in the component palette search tool.
TForm1.Button1Click(Sender: TObject);
begin
WordApplication1.Connect;
WordApplication1.Visible := TRUE;
WordApplication1.Selection.EndOf(wdStory, wdMove);
WordApplication1.Selection.Text := 'Delphi Rocks !' + #13;
WordApplication1.Selection.EndOf(wdStory, wdMove);
WordApplication1.Disconnect;
end;
Adding to fpiette's good answer:
Another way to do it is without the components, but instead to directly create an Excel COM object on the fly in the code. Example:
uses
ComObj, Variants;
var
V: Variant;
Square: Double; // could also be of type string
begin
try
// Get a running Excel instance, if there is one
V := GetActiveOleObject('Excel.Application');
except
on EOleSysError do // Probably Excel was not running
begin
// Create a new Excel instance
V := CreateOleObject('Excel.Application');
V.Visible := True; // Optional
end;
end;
//
// Do some work with Excel - a few examples
//
// If no workbook is open, then create one
if VarIsClear(V.ActiveWorkbook) then
begin
V.Workbooks.Add;
end;
V.ActiveSheet.Cells[4, 1].Value := 7; // Put a value into cell A4
V.ActiveSheet.Cells[5, 1].Formula := '=A4 * A4'; // Put a formula into A5
V.Calculate; // Normally not needed, but in case the user has chosen manual calculation
Square := V.ActiveSheet.Cells[5, 1].Value; // Read the value of A5
end;
It should be noted, even if perhaps obvious, that all of this works only if Excel is installed on the computer where your Delphi program is run.
I have a dll and a test application written in Delphi. The test application uses multiple threads to call the function exported by the dll. The exported function has a trivial thread safe implementation. When running the test application various errors (access violation, invalid pointer operation, stack overflow etc) happens or the application freezes. In some cases the application finishes without errors.
Note that these errors only happen (surface) when using multiple threads. When calling the function from the main thread only then everything works fine.
I have found that adding ShareMem to both the dll and the application stops all these kind of errors. But I don't understand why. To my knowledge ShareMem is only needed when passing long strings between the dll and the application. As far as I know WideString is not a long string.
Also according to this post ShareMem should not be required:
Why can Delphi DLLs use WideString without using ShareMem?
Here is the source of the dll:
library External;
uses
Winapi.Windows;
type
TMyType = class
private
FText: string;
end;
function DoSomething(input: WideString; out output: WideString): Bool; stdcall;
var
x: TObject;
begin
x := TMyType.Create;
try
output := x.ClassName;
finally
x.Free;
end;
Result := True;
end;
exports
DoSomething;
begin
end.
Here is the test application:
program ConsoleTest;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Winapi.Windows,
OtlParallel;
function DoSomething(input: WideString; out output: WideString): Bool; stdcall; external 'External.dll' name 'DoSomething';
var
sResult: WideString;
begin
try
Parallel.&For(0, 500).Execute(procedure(value: Integer)
var
sResult: WideString;
begin
DoSomething('hhh', sResult);
end);
WriteLn('Done');
ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Why ShareMem makes the bugs go away and is there another way to fix these bugs?
I am using Delphi XE2 and OmniThread 3.07.5.
Update
- Same issue when run from a VCL application's button's on click event handler
- If DoSomething uses a critical section inside then runs fine
- If FText field is removed from TMyClass then no errors are reported but the application randomly freezes
For the standard memory manager (FastMM) to support multi threading, you need to set the IsMultiThread flag.
When you use RTL for threading, this flag is automatically set. As revealed in the comments to the question, OTL also use RTL to start its threads. So the memory manager in your executable is aware of threading, but the distinct memory manager in the dll causes errors. When you use "sharemem", there is only one memory manager which is aware of threading because of OTL, so you encounter no errors.
An alternative solution, apart from using a shared memory manager, would be to set the flag also for the memory manager in the dll.
I have a problem that Delphi (2010) IDE and the program both hang during debugging when I run a thread.
Both windows do not respond. When I kill the program, IDE works again.
It took time, I had to delete pieces of my program and I found the problem.
It is caused by VirtualStringTree.
So if I put just empty VirtualStringTree (v. 5.5.3) on form, one button to execute TThread with just "Sleep(2000)" in Execute procedure and run such program under debugger, it hangs (usually at first click). When I remove the VST, it works.
I have also noticed that Windows Reporting Service is started but I haven't found anything in the Windows event log.
Does anyone have any idea how this is possible?
Full source here
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, VirtualTrees, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
VirtualStringTree1: TVirtualStringTree;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TTestThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TTestThread }
procedure TTestThread.Execute;
begin
FreeOnTerminate:=True;
Sleep(2000);
end;
{ TForm }
procedure TForm1.Button1Click(Sender: TObject);
begin
TTestThread.Create(False);
end;
end.
EDIT:
I have also tried to debug Delphi process. I attached from second to first IDE. When I click the button to start the thread, both IDEs hang. So I have tried with Delphi 7 which I also have installed. That worked. It stays in a loop somewhere in ntdll.NtWaitForMultipleObjects, KERNELBASE.WaitForMultipleObjectsEx, USER32.MsgWaitForMultipleObjects.
I have completely reinstalled Delphi, no change. It may also be related to this line in VirtualStringTree source: "WaitForSingleObject(WorkEvent, INFINITE);". When I remove it, it does not freeze. But I think it is necessary there.
Finally I installed Delphi XE and that works correctly. It is mysterious.
TTestThread is a descendant of TThread in your code but it still needs a variable declaration and a proper constructor call.
var
MyThread: TTestThread;
The proper call to instantiate it would be
MyThread := TTestThread.Create(False);
rather than trying to invoke the constructor as you have in the button click event.
Good luck and have fun.
RP
Application Description:
I have an application that allows a user to run multiple concurrent queries via threads (up to 100 at once).
I have a class that I use for logging errors. If an error occurs in the application, I create an instance of the class and call a procedure to write the error to a log file.
Question:
I need to make the error logging code thread safe. I've noticed that if a lot of threads are running at the same time and generating the same error (e.g. cannot connect to database), I'm getting i/o error 32 (caused by the application attempting to write to a file that's already open).
As a quick and dirty fix, I've put the code that writes to file in a try... except block inside a repeat loop. If there is an exception (e.g. the file has already been opened by another instance of the class, kicked off by another thread), then it sets a flag to "false". The loop continues to execute until the flag is "true" (i.e. no error writing to file), as follows:
procedure TErrorLogging.logError(error: string);
var
f: textfile;
ok: boolean;
begin
repeat
ok := true;
try
assignfile(f, fLogFilename);
if fileExists(fLogFilename) then append(f) else rewrite(f);
writeln(f, error);
closefile(f);
except
ok := false;
end;
until ok;
end;
I'm aware that the correct way to protect blocks of code is by using Critical Sections, but I'm not sure how I'd implement that, given that there are a number of different threads that use the logging class, and each instance of the thread has its own instance of the logging class that it uses to write to file (so they're not all just synchronizing against the same block of code).
The options, as I can see them:
Use the code as above. Are there any issues with leaving this code as it is? It's a quick and dirty fix, but it works.
Use a global TCriticalSection (how?).
Use a single procedure somewhere that creates an instance of the logging class, which the threads will synchronize against (which defeats the object of having a logging class, I suppose).
Creating instance of a logging class whenever you want to append log entry is wrong as well as opening and closing a log file over and over again. I would personally use one instance of a class which internally uses a string list and whose basic methods are thread safe. Something like this:
type
TErrorLog = class
private
FList: TStringList;
FLock: TRTLCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure Add(const ErrorText: string);
procedure SaveToFile(const FileName: string);
end;
implementation
{ TErrorLog }
constructor TErrorLog.Create;
begin
inherited Create;
InitializeCriticalSection(FLock);
FList := TStringList.Create;
end;
destructor TErrorLog.Destroy;
begin
EnterCriticalSection(FLock);
try
FList.Free;
inherited Destroy;
finally
LeaveCriticalSection(FLock);
DeleteCriticalSection(FLock);
end;
end;
procedure TErrorLog.Clear;
begin
EnterCriticalSection(FLock);
try
FList.Clear;
finally
LeaveCriticalSection(FLock);
end;
end;
procedure TErrorLog.Add(const ErrorText: string);
begin
EnterCriticalSection(FLock);
try
FList.Add(ErrorText);
finally
LeaveCriticalSection(FLock);
end;
end;
procedure TErrorLog.SaveToFile(const FileName: string);
begin
EnterCriticalSection(FLock);
try
FList.SaveToFile(FileName);
finally
LeaveCriticalSection(FLock);
end;
end;
Not knowing Delphi, as a general design rule (if possible), I would have your logError function insert into a thread safe Array, ArrayList, Queue object or such that you have available, and then have it write to the file in the background, perhaps every 5-10 seconds or so. This should not only take care of the i/o problem, but should also scale to thousands of writes per second in case you want to log other events for debugging or such.
I do some reporting on a form with reportbuilder.
On the main form I select some items on a grid and then a generate the reports of the items.
I want to do this in a Tthread but i get an error 'List index out of bounds'.
Here is the call stack:
Classes.TList.Get(1244868)
Classes.TList.Get(???)
Forms.TScreen.GetCustomForms(???)
Forms.TApplication.DoActionIdle
Forms.TApplication.Idle(???)
Forms.TApplication.HandleMessage
Forms.TApplication.Run
Seems some form is either not being added to the Screen.Forms
collection in a timely manner or one is being freed from it during the
loop in DoActionIdle.
Any ideas on how to circumvent this problem?
I work on windows XP and delphi 2010.
I Also I've the problem with a test procedure on my application
TForm3 is just a form with no code.
TDebugThread = class(TThread)
protected
procedure Execute; override;
public
constructor Create();
end;
constructor TDebugThread.Create;
begin
FreeOnTerminate := True;
inherited Create(False);
end;
procedure TDebugThread.Execute;
var
oReport: DeBugReport.TForm3;
begin
inherited;
oReport:= DeBugReport.TForm3.Create(Nil);
try
sleep(1000);
finally
oReport.Free;
end;
end;
....
procedure RunThread();
begin
TDebugThread.Create();
end;
Recapitulation:
I have a list of some Intervention on a form. Each detail and resumation of the intervention can I print on 2/5 reports. Therefore I use reports components (reportbuilder) on another form (not visible). The new feature was to multiselect some interventions on the list and set the reports in a folder in pdf format. That's was simple just on each intervention call the reportform and some parameters to change and save into pdf.
But this take to long. The user must wait until the procedure was ended. No problem I set the procedure in a thread. But there I get the error 'List index out of bounds'. ArgggArggg, I was suspected that the reportform (created, to his job and then destroyed) the problem was but hoped that there was another solution. I was thinking to change the TForm into TDataModule. Can I set all the components of the form into the datamodule. I use the TDbGrid to see some values in design. But in the Tdatamodule I can't set a TDBGrid. Ok, I can live without the TDbGrid. So I transformed the TForm into TDataModule.
But the TDataModule is not the answer. There I get the error 'Graphics.OutOfResource' from a TBitmap. I think that the TBitmap is calling from the TppReport. Now I'm done. I'm changing my code all more than 2 days with no result. I leave the TThread for this time.
Let's take a look at TApplication.DoActionIdle:
procedure TApplication.DoActionIdle;
var
I: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[I] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
UpdateActions;
end;
Let's assume that Screen.CustomFormCount and is implemented correctly and always returns the number of items indexed by Screen.CustomForms. In which case the conclusion is that the body of the loop is deleting a form. That is Screen.CustomFormCount is changing during the execution of the loop.
The only way that can happen is if one of the form's action update handlers results in a form being deleted. So, I can't tell you any more than that, but this analysis should lead you to the root cause of the problem.
And the second part of your question is quite simple. You cannot use VCL components outside the main GUI thread.
In fact it is plausible that destroying the VCL form in your thread is what is leading to Screen.CustomFormCount changing during the execution in the GUI thread of TApplication.DoActionIdle.