event handling in delphi - delphi

I got a sample of code im trying to understand in delphi,can anyone explain this means below,i know it creates and sets events but im trying to understand why the (nil,true,false,'pishu') i want to know the significance of the nil,true,false and the word in inverted comma's it seems i can write any word in there.
var
Form1: TForm1;
h:thandle;
st:string;
fopen:textfile;
countstr:integer;
implementation
{$R *.dfm}
procedure TForm1.Button2Click(Sender: TObject);
begin
setEvent(h);
CloseHandle(h);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
h:=createevent(nil,true,false,'pishu');
resetevent(h);
end;
end.
procedure TForm1.Button1Click(Sender: TObject);
begin
h:=createevent(nil,true,true,'pishu');
waitforsingleobject(h,infinite);
image1.Canvas.Brush.Color:=clblack;
image1.Canvas.Ellipse(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clyellow;
image1.Canvas.Ellipse(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clblue;
image1.Canvas.Ellipse(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clred;
image1.Canvas.Ellipse(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clgreen;
image1.Canvas.Ellipse(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
//рисуем квадраты
image1.Canvas.Brush.Color:=clblack;
image1.Canvas.Rectangle(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clyellow;
image1.Canvas.Rectangle(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clblue;
image1.Canvas.Rectangle(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clred;
image1.Canvas.Rectangle(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
image1.Canvas.Brush.Color:=clgreen;
image1.Canvas.Rectangle(random(image1.Width-100),random(image1.Width),random(image1.Width),random(image1.Width));
CloseHandle(h);
end;
end.

CreateEvent takes several parameters. It's defined on MSDN as HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPTSTR lpName). The parameters are:
lpEventAttributes
==================
Ignored. Must be NULL.
bManualReset
============
Boolean that specifies whether a manual-reset or auto-reset
event object is created. If TRUE, then you must use the
ResetEvent function to manually reset the state to nonsignaled.
If FALSE, the system automatically resets the state to nonsignaled
after a single waiting thread has been released.
bInitialState
=============
Boolean that specifies the initial state of the event object.
If TRUE, the initial state is signaled; otherwise, it is nonsignaled.
lpName
======
Pointer to a null-terminated string that specifies the name of the
event object. The name is limited to MAX_PATH characters and can contain
any character except the backslash path-separator character (\). Name
comparison is case sensitive.
If lpName matches the name of an existing named event object, the bManualReset
and bInitialState parameters are ignored because they have already been set by
the creating process.
If lpName is NULL, the event object is created without a name.
If lpName matches the name of an existing semaphore, mutex, waitable timer,
job, or file-mapping object, the function fails and the GetLastError function
returns ERROR_INVALID_HANDLE. This occurs because these objects share the same
name space.
This explains why you can type almost anything as the "word with inverted commas" (the lpName).
For more information, you can see the MSDN web site's documentation on CreateEvent
here.

MSDN is your friend when it comes to API documentation questions .See this for answers to your question about CreateEvent.

Related

TStringList is not passing value

So i have a procedure, that is getting the list of dom nodes.
procedure TmainForm.getNodeListByClass(className:string; outputList:TStringList);
var
foundNode:TDomTreeNode;
foundNodesList:TStringlist;
begin
foundNodesList:=Tstringlist.Create;
foundNode:=nodeFindNodeByClassName(DomTree.RootNode,className);
if Assigned(foundNode) then
getNodeList(foundNode,foundNodesList);
outputList:=foundNodesList;
freeandnil(foundNodesList);
end;
And a procedure that is using it
procedure TmainForm.getByXpathBtnClick(Sender: TObject);
var
temp:TStringlist;
begin
temp:=TStringlist.Create;
temp.Add('testval');
getNodeListByClass('table_input',temp);
memo1.Lines:=temp;
getNodeListByClass('left iteminfo',temp);
dbgForm.memo1.Lines:=temp;
getNodeListByClass('left',temp);
dbgForm.memo2.Lines:=temp;
freeandnil(temp);
end;
And i really don't understand, why it wouldn't work, result of first procedure is always empty.
I found out, that when the first procedure is executing, "foundNodesList" have the correct list, and setting it to "outputList" is working too, but as soon as its returning to the second procedure (in "temp" list) its just empty.
So its clearing old data from "test" ('testval' what i am writing in the beginning), but not adding the result from the first one.
Could someone point me in the right direction?
The problem is here
outputList := foundNodesList;
FreeAndNil(foundNodesList);
The assignment is a reference assignment. I think that you are expecting the content of foundNodesList to be transferred into outputList. But what happens is that you end up with two variables referring to the same instance.
Your code can be fixed very easily. You do not need a temporary string list, you can simply populate the string list passed into the method.
procedure TmainForm.getNodeListByClass(className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
begin
outputList.Clear;
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, outputList);
end;
Note that in the other function when you write
memo1.Lines := temp;
this works a little differently. The Lines property of a TMemo has a property setter that copies the right hand side, rather than taking a reference. Your code that performs assignment to Lines is therefore correct.
You must understand that objects are reference types in Delphi, and that these references are passed by value. So your procedure
procedure TmainForm.getNodeListByClass(className:string; outputList:TStringList);
var
foundNode:TDomTreeNode;
foundNodesList:TStringlist;
begin
foundNodesList:=Tstringlist.Create;
foundNode:=nodeFindNodeByClassName(DomTree.RootNode,className);
if Assigned(foundNode) then
getNodeList(foundNode,foundNodesList);
outputList:=foundNodesList;
freeandnil(foundNodesList);
end;
will never change the outputList of the caller. Indeed, the line
outputList:=foundNodesList;
merely sets the getNodeListByClass procedure's own local variable outputList, which was only a copy of the pointer to the caller's string list. Hence, this copy of the pointer is changed, but the actual object, and the caller's pointer to it, are left unchanged.
Also, even if this had not been the case, your code would have had a bug, because
freeandnil(foundNodesList);
destroys the string list object foundNodesList, and this is the same object that outputList points to at that point. Hence, if the caller would have been able to see the "new" outputList (if it had been a var parameter), it would only see a dangling pointer (memory corruption bug).
What you need is
procedure TmainForm.getNodeListByClass(const className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
foundNodesList: TStringlist;
begin
foundNodesList := TStringList.Create;
try
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, foundNodesList);
outputList.Assign(foundNodeList);
finally
foundNodeList.Free;
end;
end;
assuming your functions do what I think they do. But this can be simplified to
procedure TmainForm.getNodeListByClass(const className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
begin
outputList.Clear;
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, outputList);
end;
(I don't know if you want to append the list or replace it. You have to adjust the code accordingly.)
Also, notice that you always must protect your objects, using try..finally blocks, for instance. Your code must never leak resources (memory, for instance), not even if an exception is raised!

Test in DUnitx with Delphi-Mocks passing private record

I am new to DUnitx and Delphi-Mocks so please be patient. The only other post I could find on this topic was 3 years old and not answered. Returning records in Delphi-Mocks
Delphi Rio 10.3.
Windows 10
I want to test this procedure:
procedure TdmMariaDBConnection.Notify;
var
LViewModel : IPsViewModel;
begin
FMainViewModel.HandleCommands(FCommandRecord);
for LViewModel in FObservers do
LViewModel.HandleCommands(FCommandRecord);
end;
The interfaces and record type are declared as:
IPsView = interface(IInvokable)
['{F5532762-09F8-42C4-9F9F-A8F7FF7FA0C6}']
procedure HandleCommands(const Value: TPsCommandRecord);
procedure AfterCreate;
procedure BeforeDestroy;
end;
IPsViewModel = interface(IInvokable)
['{322DAB08-6A7C-4B61-B656-BC5346ACFC14}']
procedure HandleCommands(const Value: TPsCommandRecord);
end;
IPsMainViewModel = interface(IInvokable)
['{98FFB416-6C22-492F-BC85-D9A1ECA667FE}']
procedure Attach(const observer: IPsView);
procedure Notify;
procedure LoadFrame(const Value: TPanel);
procedure LoadForm(const Value: integer);
procedure LoadModalForm(const Value: integer);
procedure HandleCommands(const Value: TPsCommandRecord);
procedure SetViewFactory(Value: IPsViewFactory);
property ViewFactory: IPsViewFactory write SetViewFactory;
end;
TPsCommandRecord = record
CommandType: integer;
CommandObject: TObject;
CommandMessage: TPsTaskDialogMessageRecord;
end;
I have the Notify procedure in the protected section
type
TdmMariaDBConnection = class(TDataModule, IPsModel)
procedure DataModuleDestroy(Sender: TObject);
procedure DataModuleCreate(Sender: TObject);
private
FObservers : TList<IPsViewModel>;
FMainViewModel : IPsMainViewModel;
FCommandRecord : TPsCommandRecord;
protected
procedure Notify;
….
end;
In my test project I have a descendent class
TTestabledmMariaDBConnection = class(TdmMariaDBConnection)
end;
var
CUT : TTestabledmMariaDBConnection;
procedure TTestModel_MariaDBConnection.Setup;
begin
CUT := TTestabledmMariaDBConnection.Create(nil);
end;
so I can call protected methods. What I have so far that doesn't work because I cannot provide the private record instance from TdmMariaDBConnection, and just focusing on the MainViewModel for now.
procedure TTestModel_MariaDBConnection.NotifyCallsMainViewModelHandleCommands;
var
MVMMock : TMock<IPsMainViewModel>;
LCommandRecord : TPsCommandRecord;
begin
//Arrange
MVMMock := TMock<IPsMainViewModel>.Create;
MVMMock.Setup.Expect.Once.When.HandleCommands(LCommandRecord);
//Act
CUT.Attach(MVMMock);
CUT.Notify;
//Assert
try
MVMMock.Verify();
Assert.Pass();
except on E: EMockException do
Assert.Fail(E.Message);
end;
end;
Obviously the addition of LCommandRecord are wrong I just added them to get it to compile. I need(I think) the record instance from The test class in the setup. I tried adding a function to get that but it didn't work either.
function TdmMariaDBConnection.GetCommandRecord: TPsCommandRecord;
begin
Result := FCommandRecord;
end;
MVMMock.Setup.Expect.Once.When.HandleCommands(CUT.GetCommandRecord);
The test doesn't even complete, I get an incomplete circle in TestInsight GUI instead of the hoped for Green check.
Any help would be appreciated. Also is this the right use of Verify? I can only find the explanation that it does nothing when passing, so how to add an Assert?
Thanks in advance
Gary
The way you setup the mock it will be very strict about the parameters being passed and checks for equality to the specified setup when calling Verify.
There is also a long standing issue in Delphi Mocks that record parameters are not properly compared for equality (they only equal if the parameters where the exact same address - see SameValue in Delphi.Mocks.Helpers.pas - I know of this issue because it is my code being used with my permission - I wrote a better version some while ago being used in Spring4D which also has mocking fwiw). This is why even if it would not run in a circle with your added GetCommandRecord it might not pass.
What I usually suggest people to do (I wrote 2 mocking libraries for Delphi so far) when using mocks is to be as permissive as possible. Fortunately Delphi Mocks supports parameter matcher that let you specify that actually you don't care that much for the exact value of the parameter being passed.
That being said simply change your setup to call
MVMMock.Setup.Expect.Once.When.HandleCommands(It0.IsAny<TPsCommandRecord>);
That tells the internal matcher recording calls to the mock from the SUT that it does not matter what value comes in which satisfies the expectation.
By the way for a similar reason as with the SameValue bug it will not work using It0.IsEqualTo(LCommandRecord) because the used comparer for records internally calls System.Generics.Defaults.Equals_Binary which just does a flat memory compare of the record which possibly fails for any reference type.

Error on opening an MDI ChildForm from another MDI ChildForm's TButton via TMenuItem on MDI ParentForm

I have 2 MDIChild Forms and an MDIParent Form with a TMainMenu. In the TMainMenu, I have the following TMenuItem.OnClick procedure to open ChildForm2:
procedure TfrmMain.miOpenChildForm2Click(Sender: TObject);
begin
TfrmChildForm2.Create(self).Show;
end;
Now, I want to access the above procedure from ChildForm1 by a TButton.OnClick procedure below:
procedure TfrmChildForm1.btnOpenChildForm2Click(Sender: TObject);
begin
frmMain.miOpenChildForm2Click; // Error Here: E2035 Not enough actual parameters
End;
I am getting an error on the above second procedure:
E2035 Not enough actual parameters
I don't know exactly where to correct it. I tried putting '()' at the end of the procedure call, but no avail.
The procedure definition
procedure TfrmMain.miOpenChildForm2Click(Sender: TObject);
tells you that the procedure expects to receive a parameter Sender of type TObject. Calling it with frmMain.miOpenChildForm2Click; does not pass that parameter. The parameter is not optional.
Sender is intended to tell you what triggered the event, for use in cases where it matters, such as when you're using one event handler for multiple controls. It allows you to differentiate where the call to the event originated.
You can use the button or menu item that was clicked in the call as the parameter
frmMain.miOpenChildForm2Click(btnOpenChildForm2);
If it doesn't matter where the call came from, you can pass nil instead
frmMain.miOpenChildForm2Click(nil);
As a note: MDI has been deprecated for at least a decade, and Windows has not had much support for it for at least that long. Modern applications do not use MDI, and new development most likely shouldn't include it either.

How to replace TListbox Items property with my own published object list based type in a TCustomListBox control?

Overview
This question is a second attempt based on this one I recently asked: How can I make a TList property from my custom control streamable?
Although I accepted the answer in that question and it worked, I soon realized that TCollection is not the solution or requirement I was looking for.
Requirements
To keep my requirements as simple and clear to understand as possible, this is what I am attempting to:
Derive a new custom control based on TCustomListBox
Replace the Items property with my own Items type, eg a TList.
The TList (Items property) will hold objects, each containing a caption and a image index property etc.
Ownerdraw my listbox and draw its icons and text etc.
Create a property editor to edit the Items at design-time.
With that in mind, I know how to create the custom control, I know how to work with TList or even TObjectList for example, I know how to ownerdraw the control and I also know how to create the property editor.
Problem
What I don't know is how to replace the standard listbox Items type with my own? well I kind of do (publishing my own property that shares the same name), only I need to make sure it is fully streamable with the dfm.
I have searched extensively on this subject and have tried studying code where TListView and TTreeView etc publishes its Items type but I have found myself more confused than ever.
In fact I came across this very old question asked by someone else on a different website which asks very much what I want to do: Streaming a TList property of a component to a dfm. I have quoted it below in the event the link is lost:
I recently wrote a component that publishes a TList property. I then created a property editor for the TList to enable design-time editing. The problem is that the TList doesn't stream to the dfm file, so all changes are lost when the project is closed. I assume this is because TList inherits from TObject and not from TPersistant. I was hoping there was an easy work around for this situation (or that I have misunderstood the problem to begin with). Right now all I can come up with is to switch to a TCollection or override the DefineProperties method. Is there any other way to get the information in the TList streamed to and from the dfm?
I came across that whilst searching keywords such as DefineProperties() given that this was an alternative option Remy Lebeau briefly touched upon in the previous question linked at the top, it also seemed to be the answer to that question.
Question
I need to know how to replace the Items (TStrings) property of a TCustomListBox derived control with my own Items (TList) or Items (TObjectList) etc type but make it fully streamable with the dfm. I know from previous comments TList is not streamable but I cannot use TStrings like the standard TListBox control does, I need to use my own object based list that is streamable.
I don't want to use TCollection, DefineProperties sounds promising but I don't know how exactly I would implement this?
I would greatly appreciate some help with this please.
Thank you.
Override DefineProperties procedure in your TCustomListBox (let's name it TMyListBox here). In there it's possible to "register" as many fields as you wish, they will be stored in dfm in the same way as other fields, but you won't see them in object inspector. To be honest, I've never encountered having more then one property defined this way, called 'data' or 'strings'.
You can define 'normal' property or binary one. 'Normal' properties are quite handy for strings, integers, enumerations and so on. Here is how items with caption and ImageIndex can be implemented:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure ReadData(reader: TReader);
procedure WriteData(writer: TWriter);
protected
procedure DefineProperties(filer: TFiler); override;
//other stuff
public
//other stuff
property Items: TList read fItems; //not used for streaming, not shown in object inspector. Strictly for use in code itself. We can make it read-only to avoid memory leak.
published
//some properties
end;
that's DefineProperties implementation:
procedure TMyListBox.DefineProperties(filer: TFiler);
begin
filer.DefineProperty('data', ReadData, WriteData, items.Count>0);
end;
fourth argument, hasData is Boolean. When your component is saved to dfm, DefineProperties is called and it's possible to decide at that moment is there any data worth saving. If not, 'data' property is omitted. In this example, we won't have this property if there is no items present.
If we expect to ever use visual inheritance of this control (for example, create a frame with this listBox with predefined values and then eventually change them when put to form), there is a possibility to check, is value of this property any different than on our ancestor. Filer.Ancestor property is used for it. You can watch how it's done in TStrings:
procedure TStrings.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then
begin
Result := True;
if Filer.Ancestor is TStrings then
Result := not Equals(TStrings(Filer.Ancestor))
end
else Result := Count > 0;
end;
begin
Filer.DefineProperty('Strings', ReadData, WriteData, DoWrite);
end;
This would save a little bit of space (or lots of space if image is stored within) and sure is elegant, but in first implementation it can well be omitted.
Now the code for WriteData and ReadData. Writing is much easier usually and we may begin with it:
procedure TMyListBox.WriteData(writer: TWriter);
var i: Integer;
begin
writer.WriteListBegin; //in text dfm it will be '(' and new line
for i:=0 to items.Count-1 do begin
writer.WriteString(TListBoxItem(items[I]).caption);
writer.WriteInteger(TListBoxItem(items[I]).ImageIndex);
end;
writer.WriteListEnd;
end;
In dfm it will look like this:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
Output from TCollection seems more elegant to me (triangular brackets and then items, one after another), but what we have here would suffice.
Now reading it:
procedure TMyListBox.ReadData(reader: TReader);
var item: TListBoxItem;
begin
reader.ReadListBegin;
while not reader.EndOfList do begin
item:=TListBoxItem.Create;
item.Caption:=reader.ReadString;
item.ImageIndex:=reader.ReadInteger;
items.Add(item); //maybe some other registering needed
end;
reader.ReadListEnd;
end;
That's it. In such a way rather complex structures can be streamed with ease, for example, two-dimensional arrays, we WriteListBegin when writing new row and then when writing new element.
Beware of WriteStr / ReadStr - these are some archaic procedures which exist for backward compatibility, ALWAYS use WriteString / ReadString instead!
Other way to do is to define binary property. That's used mostly for saving images into dfm. Let's say, for example, that listBox has hundreds of items and we'd like to compress data in it to reduce size of executable. Then:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure LoadFromStream(stream: TStream);
procedure SaveToStream(stream: TStream);
protected
procedure DefineProperties(filer: TFiler); override;
//etc
end;
procedure TMyListBox.DefineProperties(filer: TFiler);
filer.DefineBinaryProperty('data',LoadFromStream,SaveToStream,items.Count>0);
end;
procedure TMyListBox.SaveToStream(stream: TStream);
var gz: TCompressionStream;
i: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TCompressionStream.Create(stream);
try
value:=items.Count;
//write number of items at first
gz.Write(value, SizeOf(value));
//properties can't be passed here, only variables
for i:=0 to items.Count-1 do begin
item:=TListBoxItem(items[I]);
value:=Length(item.Caption);
//almost as in good ol' Pascal: length of string and then string itself
gz.Write(value,SizeOf(value));
gz.Write(item.Caption[1], SizeOf(Char)*value); //will work in old Delphi and new (Unicode) ones
value:=item.ImageIndex;
gz.Write(value,SizeOf(value));
end;
finally
gz.free;
end;
end;
procedure TMyListBox.LoadFromStream(stream: TStream);
var gz: TDecompressionStream;
i: Integer;
count: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TDecompressionStream.Create(stream);
try
gz.Read(count,SizeOf(count)); //number of items
for i:=0 to count-1 do begin
item:=TListBoxItem.Create;
gz.Read(value, SizeOf(value)); //length of string
SetLength(item.caption,value);
gz.Read(item.caption[1],SizeOf(char)*value); //we got our string
gz.Read(value, SizeOf(value)); //imageIndex
item.ImageIndex:=value;
items.Add(item); //some other initialization may be needed
end;
finally
gz.free;
end;
end;
In dfm it would look like this:
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
78 is sort of signature of ZLib, 9C means default compression, so it works (there are only 2 items actually, not hundreds). Of course, this is just one example, with BinaryProperties any possible format may be used, for example saving to JSON and putting it into stream, or XML or something custom. But I'd not recommend to use binary unless it's absolutely inevitable, because it's difficult to see from dfm, what happens in component.
It seems like good idea to me to actively use streaming when implementing component: we can have no designer at all and set all values by manually editing dfm and see if component behaves correctly. Reading/loading itself can be tested easily: if component is loaded, then saved and text is just the same, it's all right. It's so 'transparent' when streaming format is 'human-readable', self-explaining that it often overweighs drawbacks (like file size) if there are any.

Accessing Sub functions /procedures from DPR or other function / procedure in Delphi

As much I know - Subroutines are with Private access mode to its parent unction / procedure, right?
Is there any way to access them from "outer-world" - dpr or other function / procedure in unit?
Also - which way takes more calcualtion and space to compiled file?
for example:
function blablabla(parameter : tparameter) : abcde;
procedure xyz(par_ : tpar_);
begin
// ...
end;
begin
// ...
end;
procedure albalbalb(param : tparam) : www;
begin
xyz(par_ : tpar_); // is there any way to make this function public / published to access it therefore enabling to call it this way?
end;
// all text is random.
// also, is there way to call it from DPR in this manner?
// in C++ this can be done by specifing access mode and/or using "Friend" class .. but in DELPHI?
Nested procedures/functions - those declared inside another procedure or function, are a special type, because they can access the stack (and thereby parameters/local variables) of the procedure they are nested in. Because of this, and Delphi scope rules, there is no way to access them outside the "parent" procedure. You use them only if you need to take advantage of their special features. AFAIK Delphi/Pascal is one of the few languages to have this feature. From a compiler point of view the call has some extra code to allow accessing the parent stack frame, IIRC.
AFAIK "friend" class/functions in C++ are different - they are class access methods, while in your example you are using plain procedures/functions.
In Delphi all procedure/classes declared in the same unit are automatically "friend", unless strict private declarations are used in latest Delphi releases. For example this code snippets will work, as long everything is in the same unit:
type
TExample = class
private
procedure HelloWorld;
public
...
end;
implementation
function DoSomething(AExample: TExample);
begin
// Calling a private method here works
AExample.HelloWordl;
end;
Note: Embedded Routines <> Private/Protected Methods.
Embedded routines i.e. routines inside routines can not be accessed by external routines.
You have posted an example of an Embedded routine, I also heard them called Internal Routines.
Here is another example:
procedure DoThis;
function DoThat : Boolean;
begin
// This Routine is embedded or internal routine.
end;
begin
// DoThat() can only be accessed from here no other place.
end;
Regardless of visibility, methods on classes, can be called using Delphi 2010 via RTTI. I have detailed how to do this in this article.
If you are in the same Unit methods on a class can be accessed by any other code regardless of visibility, unless they are marked with Strict Private. This Question has more details and good example code in the accepted answer.
If you are in two different units you can use the Protected Method Hack to access the protected methods. Which is detailed in detailed in this article.
Yes, you can access a subroutine, which is nested in other (parent) subroutine, from the outer world. Though it's somewhat tricky. I've found this howto in the web.
How to pass nested routine as a procedural parameter (32 bit)
Delphi normally does not support passing nested routines as procedural parameters:
// This code does not compile:
procedure testpass(p: tprocedure);
begin
p;
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(inner);
end;
The obvious workaround is to pass procedure address and typecast it within testpass:
// This code compiles and runs OK
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(#inner);
end;
There is, however, a pitfall in the above example - if the "inner" routine references any variable that was pushed onto the stack before the "inner" procedure was called from testpass (calltestpass parameters - if there were any, or local variables in calltestpass - if there were any), your system most probably crashes:
// This code compiles OK but generates runtime exception (could even be
// EMachineHangs :-) )
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
The reason is, in simple words, that the stack frame arrangement
was "broken" by the call to testpass routine and "inner" procedure
incorrectly calculates parameters and local variables location
(do not blame Delphi, please).
The workaround is to set up the correct stack context before
"inner" is called from within "testpass".
// This code compiles and runs OK
{$O-}
procedure testpass(p: pointer);
var callersBP: longint;
begin
asm // get caller's base pointer value at the very beginning
push dword ptr [ebp]
pop callersBP
end;
// here we can have some other OP code
asm // pushes caller's base pointer value onto stack and calls tProcedure(p)
push CallersBP
Call p
Pop CallersBP
end;
// here we can have some other OP code
end;
{$O+}
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
Please note the optimization is switched OFF for testpass routine - optimization generally does not handle mixed OP/assembler code very well.
No, there is no way to do what you're asking. The xyz function is callable only by the enclosing blablabla function. Outside that function, xyz is not in scope and there is no way to name it. If C++ allowed nested function, there wouldn't be any way to refer to it, either, just like there's no way to refer to functions with static linkage from outside the current translation unit.
If you need to call xyz from outside the blablabla function, then move xyz outside. If you need to call it from outside the current unit, then you need to declare that function in the unit's interface section. Then, add that unit to the external code's uses clause and you can call xyz from wherever you want, even the DPR file.
If xyz refers to variables or parameters of the blablabla function, then you'll need to pass them in as parameters since xyz will no longer have access to them otherwise.
The concept of access specifiers isn't really relevant here since we're not talking about classes. Units have interface and implementation sections, which aren't really the same as public and private sections of a class.

Resources