Trying to build Excel RTD server in Delphi - excel

I'm trying to build an RTD server for Excel in Delphi and I cannot get this part of the code to work:
function TRtdServer.RefreshData(var TopicCount: Integer): PSafeArray;
//Called when Excel is requesting a refresh on topics. RefreshData will be called
//after an UpdateNotify has been issued by the server. This event should:
//- supply a value for TopicCount (number of topics to update)
//- The data returned to Excel is an Object containing a two-dimensional array.
// The first dimension represents the list of topic IDs.
// The second dimension represents the values associated with the topic IDs.
var
Data : OleVariant;
begin
//Create an array to return the topics and their values
//note:The Bounds parameter must contain an even number of values, where each pair of values specifies the upper and lower bounds of one dimension of the array.
Data:=VarArrayCreate([0, 1, 0, 0], VT_VARIANT);
Data[0,0]:=MyTopicId;
Data[1,0]:=GetTime();
if Main.Form1.CheckBoxExtraInfo.Checked then Main.Form1.ListBoxInfo.Items.Add('Excel called RefreshData. Returning TopicId: '+IntToStr(Data[0,0])+' and Value: '+Data[1,0]);
TopicCount:=1;
// RefreshTimer.Enabled:=true;
//Result:=PSafeArray(VarArrayAsPSafeArray(Data));
Result:=PSafeArray(TVarData(Data).VArray);
end;
I'm not sure about this part:
Result:=PSafeArray(TVarData(Data).VArray);
But it could be any part of the code.
Excel just doesn't show any result in the cell containing the rtd() function call. I did manage to get a result into the cell the first time Excel calls my "ConnectData" function that simple returns a string instead of a PSafeArray (although the very first call to that function fails to produce a result (N/A). Only after changing the Topic in the RTD() call it displays a result (one time only))
I based the code on an example in C# from https://blog.learningtree.com/excel-creating-rtd-server-c/
Can anyone point me in the right direction?

OleVariant owns the data it holds, and will release that data when itself goes out of scope. So you are returning an invalid PSafeArray pointer to Excel. You need to either:
release ownership of the array pointer before returning it:
function TRtdServer.RefreshData(var TopicCount: Integer): PSafeArray;
var
Data : OleVariant;
begin
...
Result := PSafeArray(TVarData(Data).VArray);
TVarData(Data).VArray = nil; // <-- add this
end;
use SafeArrayCopy() to make a copy of the array, and then return the copy:
uses
..., ActiveX;
function TRtdServer.RefreshData(var TopicCount: Integer): PSafeArray;
var
Data : OleVariant;
begin
...
OleCheck(
SafeArrayCopy(
PSafeArray(TVarData(Data).VArray),
Result
)
);
end;

Related

Memory issues when using spring.Nullable with DUnitX

Recently at my company we tried to use DUnitX with all it's blessings to test classes we wrote. Since those classes reflect entities in database all fields have to accept null values as well as specific type (e. g. Integer or string).
Since spring4d already have those we tried to use them:
INaleznosc = interface
['{D5D6C901-3DB9-4EC2-8070-EB0BEDBC7B06}']
function DajPodstawaVAT(): TNullableCurrency;
property PodstawaVAT: TNullableCurrency read DajPodstawaVAT;
end;
TNaleznosc = class(TInterfacedObject, INaleznosc)
strict private
FId: TNullableInt64;
FPodstawaVAT: Currency;
function TNaleznosc.DajPodstawaVAT(): TNullableCurrency;
published
property PodstawaVAT: TNullableCurrency read DajPodstawaVAT;
end;
INaleznoscFunkcje = interface
['{509288AB-110A-4A52-BE93-3723E5725F4B}']
function DajPodstawaVAT(pID: TNullableInt64): TNullableCurrency;
end;
function TNaleznosc.DajPodstawaVAT(): TNullableCurrency;
begin
FPodstawaVAT := FFunkcje.DajPodstawaVAT(FId);
end;
procedure TTestNaleznosc.PodstawaVATGetterNieWywolujefunkcji();
var
funkcjeNaleznosc: TMock<INaleznoscFunkcje>;
klasa: INaleznosc;
id: TNullableInteger;
begin
//initialize tested elements
funkcjeNaleznosc := TMock<INaleznoscFunkcje>.Create();
id := 15;
klasa := TNaleznosc.Create(funkcjeNaleznosc, id, zmienne);
//setup expected behaviour from mock
funkcjeNaleznosc.Setup.WillReturn(2).When.DajPodstawaVAT(id);
funkcjeNaleznosc.Setup.Expect.Once.When.DajPodstawaVAT(id);
//this triggers getter
klasa.PodstawaVAT;
end;
When this code is executed we get AV exception First chance exception at $00000000. Exception class $C0000005 with message 'access violation at 0x00000000: access of address 0x00000000'. Process Tests.exe (6556).
Eventually we narrowed this issue down to Move procedure in System.Rtti unit, TValueDataImpl.ExtractRawDataNoCopy function:
when Length(FData) is less or equal to 8 it works fine
when Length(FData) is between 9 and 32 at line 5905 of System unit (FISTP QWORD PTR [EDX+8] {Save Second 8}) whole call stack disappears beside two lines (we are not sure whether it's relevant or not, but it doesn't look like good sign) and after getting to topmost function (according to call stack) we get error.
Call stack before "saving second 8"
Call stack after "saving second 8"
Is it our fault or is it some issue with system/spring/dunitx units? How can we use nullable types and tests at the same time?
I am not sure if Delphi Mocks has a generic type parameter on its WillReturn method but if so then pass TNullableCurrency there - otherwise the compiler will infer the type from the parameter 2 you are passing and obviously internally it fails to put that into the TNullableCurrency it should return.
If it does not and only allows TValue then you need to pass one that contains a TNullableCurrency and not 2 which it would by using its implicit operator like so: TValue.From<TNullableCurrency>(2)
Furthermore I am not sure if they did fix the code in the SameValue routine in Delphi Mocks when the value to be compared is a record (as TNullableCurrency is)
Edit: no, they did not - see https://github.com/VSoftTechnologies/Delphi-Mocks/issues/39
You might want to consider giving Spring4D mocks a try which should be able to handle nullables.

Pervasive Stored Proceedure with variable

I want to run this code from my query windows inside Excel but I get an error after it completes the first statement. If can run it fine in PCC. I'm assuming I need to create a stored procedure with a variable. Parent = 'B-8579-K' is the variable value.
How to I create the stored procedure?
How do I call the stored procedure and pass on the variable?
Will the call even work from within Excel without causing an error?
delete z_Expl_BOM_Temp;
insert into z_Expl_BOM_Temp
select
Parent, L1_Child_Seq, L1_Child, L1_Child_QTY,
L2_Child_Seq, L2_Child, L2_Child_QTY,
L3_Child_Seq, L3_Child, L3_Child_QTY,
L4_Child_Seq, L4_Child, L4_Child_QTY,
L5_Child_Seq, L5_Child, L5_Child_QTY
from
EGC_Expl_BOM_TT
where
Parent = 'B-8579-K' ;
Creating Stored Procedures in Pervasive are documented here.
An example of a stored procedure that takes a parameter is:
CREATE PROCEDURE Checkmax(in :classid integer);
BEGIN
DECLARE :numenrolled integer;
DECLARE :maxenrolled integer;
SELECT COUNT(*) INTO :numenrolled FROM Enrolls WHERE class_ID = :classid;
SELECT Max_size INTO :maxenrolled FROM Class WHERE id = :classid;
IF (:numenrolled >= :maxenrolled) THEN
PRINT 'Enrollment Failed. Number of students enrolled reached maximum allowed for this class' ;
ELSE
PRINT 'Enrollment Possible. Number of students enrolled has not reached maximum allowed for this class';
END IF;
END;
And then calling it:
CALL Checkmax(101)
I finally got the SP created without an error:
CREATE PROCEDURE EGC_Expl_BOM_TT(in :CParent varchar(20));
BEGIN
delete from z_Expl_BOM_Temp;
insert into z_Expl_BOM_Temp
select
Parent, L1_Child_Seq, L1_Child, L1_Child_QTY,
L2_Child_Seq, L2_Child, L2_Child_QTY,
L3_Child_Seq, L3_Child, L3_Child_QTY,
L4_Child_Seq, L4_Child, L4_Child_QTY,
L5_Child_Seq, L5_Child, L5_Child_QTY
from
EGC_Exploded_BOM
where
Parent = :CParent;
END;
I can call is just fine in PCC. However, when I run the CALL in Excel's query window is will execute the procedure but when it completes I get this error:
"The query did not run, or the database table could not be opened.
Check the database server or contact your database administrator. Make sure the external database is available and hasn't been moved or reorganized, then try the operation again."
This might now be turning into a VBA question. Can I run the CALL with VBA?

Inno Setup function CheckItem syntax and usage

Following on from my question Inno Setup disable component selection when a specific component is selected, I think there may be a way to get this to work without the problem of checked states set in code being permanent (though use of the Checked property) by instead using:
function CheckItem(const Index: Integer; const AOperation: TCheckItemOperation): Boolean;
in TNewCheckListBox, however I am having trouble getting the syntax correct. I am trying:
CheckItem(CompIndexSync, coUncheck) := Checked[CompIndexClient];
where the CompIndexes are constants assigned to the indexes for the component values. I get an Identifier Expected error at compile. Can someone advise how to correctly use this function and what I am doing wrong?
The CheckItem member of the TNewCheckListBox class is a method of type function which updates the checked state by the AOperation operation and returns True if any changes were made to the state of the item at Index, or any of its children. Here is its prototype (source):
function TNewCheckListBox.CheckItem(const Index: Integer;
const AOperation: TCheckItemOperation): Boolean;
The problem was that you were trying to assign a value to the function result. That's not what you can do in Pascal language in general.
What you want to do with the item(s) is passed by the AOperation parameter. In pseudocode e.g.:
var
CheckList: TNewCheckListBox;
Operation: TCheckItemOperation;
begin
...
if ShouldCheck then
Operation := coCheck
else
Operation := coUncheck;
if CheckList.CheckItem(ItemIndex, Operation) then
MsgBox('An item has changed its state.', mbInformation, MB_OK);
end;

Reducing overhead when accessing objects used by thread

A thread is looping through a list of 1000 objects every second.
The objects hold simple configuration data. When some conditons are met, a worker thread is given the configuration data and does some work based on that.
Now I want to bring up a settings dialog with the configuration data so I can change the data inside such an object. But then I have to access objects in the list while the thread is also continously accessing them. I know how to use a critical section, but if the thread enters a critical section each time it checks on an object, then the critical section will be entered and left 1000 times per second. Maybe there is a smarter way?
How to make threadsafe with the least overhead when:
a) loading the config data into the settings dialog form (which uses a TListView in virtual mode and needs to access the list of objects on demand)
b) and saving the form input back to the object?
EDIT: More detail was requested.
The objects are in a TList and basically look like this:
TConfigData = class
ID:Integer;
Name: String;
SwitchTime: TDateTime;
end;
The data of the ConfigData object needs to be loaded into the Settings Dialog form so it can be edited, and then, if the user clicks OK, the ConfigData object should be updated and the thread will happily use this new data next time the obkect is accessed. However, updating must not happen at the same time as the thread is reading the ConfigData object.
EDIT 2: Additional details:
The threads are reading ID, Name and SwitchTime but only SwitchTime is changed by threads. (When work is done, new time is calculated and thats what triggers next work event).
The settings dialog can change both Name and SwitchTime, but not ID.
After a bit of thought, you can get away without using critical sections at all just by using InterlockedExchangePointer:
You need to add a routine to update the config for an item:
procedure TMyThread.UpdateConfig(const aIndex: Integer; const aID:Integer;
const aName: String; const aSwitchTime: TDateTime);
var
newConfigToEdit: TConfigData;
oldValue: TConfigData;
begin
newConfigToEdit := TConfigData.Create;
newConfigToEdit.ID := aID;
newConfigToEdit.Name := aName;
newConfigToEdit.SwitchTime := aSwitchTime;
repeat
oldvalue := InterlockedExchangePointer(FConfigDataList.List[aIndex], newConfigToEdit);
until (oldvalue <> nil) and (oldValue <> newConfigToEdit);
oldvalue.Free; // Free the replaced one
end;
That routine would replace the config for the item with Index of aIndex. To get the config in your thread you will need to be a bit clever. We get a copy of it and replace the value in the list with nil while we are working on it. This prevents the other thread from replacing it. Once we have finished we put back the replaced value.
procedure TMyThread.Execute;
var
configToUse: TConfigData;
begin
repeat
// Get the config and replace it with nil so it won't be changed
configToUse := InterlockedExchangePointer(FConfigDataList.List[idx], nil);
// Do stuff with the config
// Put the config back
FConfigDataList.List[idx] := configToUse;
// You could also use the line below instead of the assignment
// InterlockedExchangePointer(FConfigDataList.List[idx], configToUse);
until Terminated;
end;
If you want to kick off a worker thread with the config then you should make a clone of it and then pass in the clone to the worker because it can be changed.
Main thread (pseudo)code (ObjList is the global variable):
if ConfigUpdated(i) then
ObjList[i].ConfigVersion := ObjList[i].ConfigVersion + 1;
Other thread(s) (pseudo)code (ObjConfVer is the local for thread)
for i := 0 to ObjCount - 1 do
if ObjConfVer[i] < ObjList[i].ConfigVersion then
begin
// Here you have to take care that main thread will can not change config while you handling it
// Do something about new config
ObjConfVer[i] := ObjList[i].ConfigVersion;
// Exit from critical section
end;
If you have n threads which works with the same ObjList it is allow to each thread doing something about changed config independently.
BTW if you are using FPC/Lazarus this link may be usefull: Parallel procedures

Is there a way to save a Treeview's items and data so it can be accessed in a thread?

A TreeView item and data holds a filename and the data holds a TBitmap. My question pertains to somehow saving the items and the data in the treeview so the items and data can be accessed in a thread. If this is possible, after saving items and data, I could access the items and data in the thread rather than accessing it in Synchronize. As the code written now is too slow because of accessing the GUI TreeItems and Data in Synchronize.
if not Terminated then
begin
Synchronize(
procedure
var
i: integer;
begin
for i := 1 to Form1.TreeView1.Items.Count - 1 do
begin
{ get the bitmap }
iImageEnIO.WIAParams.ProcessingBitmap := iImageEnIO.IEBitmap;
{ The following line prevents me from accessing the TreeView data in a thread }
iImageEnIO.WIAParams.Transfer(TIEWiaItem(Form1.TreeView1.Items[i].Data), False);
{ Set the filename }
iFilename := Form1.TreeView1.Items[i].Text + '.jpg';
{ Add the image to the iIEImageList }
iIndex := iIEImageList.AppendImageRef(TIEBitmap.Create(iImageEnIO.IEBitmap), iFileName);
iIEImageList.Filename[iIndex] := iFileName;
end;
end);
The threads code to access the bitmaps in the thread itself works very well, but if I can move the code that gets the bitmap to the thread rather than in Synchronize would be much better. So my question is "Is there ay way to save a treeview items and data in Synchronize so it can be accessed in the thread outside of Synchronize"?
iImageEnIO.OnProgress := ImageEnProcProgress;
iImageEnIO.OnFinishWork := ImageEnProcFinishWork;
{ Get the bitmap from the imagelist and save the image in the thread }
iCount := iIEImageList.ImageCount;
for j := 0 to iCount-1 do
begin
{ get the filename from the string list }
iFilename := iIEImageList.Filename[j];
{ attach the iIEBitmap2 to iImageEnIO }
iImageEnIO.AttachedIEBitmap := iIEImageList.Image[j];
iPath := IncludeTrailingPathDelimiter(iFolder) + iFilename;
iImageEnIO.SaveToFile(iPath);
end;
I hope I have asked my question correctly and it is clear what I would like to try to do.
I would like to turn this on its head. You want to know how to read the data from the GUI control when executing your thread method. That is a fundamental design flaw. The solution to your problem will involve not attempting to do that at all.
The tree view should not be the owner of the data. It is a GUI control and should merely present a view of the data. The data should be held in a structure that is not bound to the VCL threading rules. Once you have the data structure separated from the GUI, your problem becomes trivial. Once you reach that point, there is no problem to be solved.
So, what kind of a structure do you need? Although it is stored in a tree view, it appears to be a flat list. Store it in a TList<T> container. What do you use for T? Well, that's just the information needed for each item. That could be a record. Or it could be a class. If you are holding non-value type objects in the item type, then a class is probably better. In which case TObjectList<T> is a better fit. So, it would look like this:
type
TItem = class
private
FFileName: string;
FBitmap: TBitmap;
end;
Then your container is simply TObjectList<TItem>. Instantiate it like this:
FItems := TObjectList<TItem>.Create(True);
The True is for the OwnsObjects parameter. This means that when you delete items from the container, or delete the container, the items are destroyed.
At this point you are able to populate the tree view by iterating over the container, and creating nodes to represent the items.
Then when your thread needs to operate on the data, it can refer to FItems which is separated from the GUI control.
The moral of the story is that you should not use your GUI controls as your primary data containers.

Resources