I passed ref of interface from Visio Add-ins to MyCOMServer (Interface Marshalling in Delphi have to pass interface as pointer in internals method of MyCOMServer. I try to pass interface to internal method as pointer of interface, but after back cast when i try call method of interface I get exception. Simple example(Fisrt block execute without error, but At Second block I get Exception after addressed to property of IVApplication interface):
procedure TMyCOMServer.test(const Interface_:IDispatch); stdcall;
var
IMy:_IMyInterface;
V: Variant;
Str: String;
I: integer;
Vis: IVApplication;
begin
......
{First code Block}
Self.QuaryInterface(_IMyInterface,IMy);
str := IMy.ApplicationName;
V := Integer(IMy);
i := V;
Pointer(IMy) := Pointer(i);
str := IMy.SomeProperty; // normal completion
{Second code Block}
str := (Interface_ as IVApplication).Path;
V := Interface_;
I := V;
Pointer(Vis) := Pointer(i);
str := Vis.Path; // 'access violation at 0x76358e29: read of address 0xfeeefeee'
end;
Why I can't do like this?
When you have an object that implements multiple interfaces and you cast between them you will get different addresses. It has to do something with how to find the methods of those interfaces.
Let's say that you have two interfaces and a class that implements them, the methods show just a message with the methodname:
type
IMyIntfA = interface
['{21ADE2EF-55BB-4B78-A23F-9BB92BE55683}']
procedure A;
procedure X;
end;
IMyIntfB = interface
['{7E1B90CF-569B-4DD1-8E46-7E7255D2373A}']
procedure B;
end;
TMyObject = class(TInterfacedObject, IMyIntfA, IMyIntfB, IUnknown)
public
procedure A;
procedure X;
procedure B;
end;
When you tell the compiler to call A from IMyIntfA, it knows that A is located at the address of IMyIntfA plus an offset. The same applies to calling method B from IMyIntfB.
But what you are doing is putting the reference to IMyIntfB in a var of IMyIntfA and then call method A. The result is that the address of the method the compiler calculates is totally wrong.
var
lIntfA: IMyInterfaceA;
lIntfB: IMyInterfaceB;
begin
lIntfA := TMyObject.Create; //TMyObject implements IMyInterfA, IMyInterfB
lInfB := lIntfA as IMyInterfaceB;
if Integer(lIntfA) <> Integer(lIntfB) then
ShowMessage('I told you so');
Pointer(lIntfA) := Pointer(lIntfB);
lIntfA.A; //procedure B is called, because B is at "Offset 1", like A
lIntfA.X; //total mayhem, X is at "Offset 2", but there is nothing at IMyIntfB + offset 2
end;
PS: I'am not a guru and I don't know the technical details about how everything is implemented. This is only a rough explanation which should give you an idea of why your code goes wrong. If you want your code to succeed do this:
Vis := Interface_ as IVApplication;
Str := (Vis.Path);
I'm only guessing since I don't know much about COM, but casting an interface to an integer or pointer does mess up the internal reference counting. Your interface is likely to be released, which would explain the access violation.
EDIT: I wonder that Pointer(Vis) := Pointer(i) works anyway. Shouldn't the cast create a temporary object. Maybe that's why Vis does not get assigned?
Related
when implementing an Event with the definition below Spring4D will add and invoke method but will not remove handler ( with IEvent<TaskItemChangeEvent>.Remove(MyProc) ) when asked as it does not identify it.
{$M+}
TaskItemChangeEvent = reference to procedure(const TaskItem: ITaskItem; Event: TTaskListEvent);
The following does work but I do not want to be forced to be bound to an object.
{$M+}
TaskItemChangeEvent = procedure(const TaskItem: ITaskItem; Event: TTaskListEvent) of Object;
I believe the issue is this line in TEventBase.Remove as a reference to procedure is not a TMethod?
if TMethod(handlers[i]) = TMethod(handler) then
The reason is the compiler possibly creating different instances of the anonymous method between the place where you add and where you remove them.
Look at the following code:
var
proc: TProc;
procedure Add(p: TProc);
begin
proc := p;
end;
procedure Remove(p: TProc);
begin
Writeln(PPointer(#proc)^ = PPointer(#p)^);
end;
procedure A;
var
p: TProc;
begin
p := procedure begin end;
Add(p);
Remove(p);
end;
procedure B;
begin
Add(procedure begin end);
Remove(procedure begin end);
end;
procedure C;
begin
Add(A);
Remove(A);
end;
begin
A;
B;
C;
Readln;
end.
You will notice that in B and C it will print False because the two anonymous methods being passed to Add and Remove differ from each other. While in B it's obvious in C it is not but the compiler actually transforms the code into this:
procedure C;
begin
Add(procedure begin A(); end);
Remove(procedure begin A(); end);
end;
That means if you want to use IEvent<> with a method reference type and be able to remove you need to keep the reference that you added in order for them to be equal and thus be able to be found when calling Remove.
The fact that internally in TEventBase the references are all handled as TMethod has nothing to do with that - when passing an anonymous method it is being transformed into a TMethod. After all an anonymous method type is an interface being backed by an object which the compiler creates which makes it possible to do such conversion and causes the necessity to keep the reference that was added in order to remove it.
Is it possible to call the same function definition with parameters by value and later in the run time, by reference?
something like:
function myfunc(a:string);
begin
a:='abc';
end;
...
later:
b:='cde';
myfunc(b);
caption:=b; //prints 'cde'
...
later:
myfunc(#b);
caption:=b; //prints 'abc'
??
Not the same function, no. You need to use overloaded functions instead, eg:
function myfunc(a: string); overload;
begin
// use a as needed, caller is not updated...
a := 'abc';
end;
function myfunc(a: PString); overload;
begin
// modify a^ as needed, caller is updated...
a^ := 'abc';
end;
b := 'cde';
myfunc(b);
Caption := b; //prints 'cde'
b := 'cde';
myfunc(#b);
Caption := b; //prints 'abc'
First note that your function cannot be called by reference because of the way it's declared.
function myfunc(a: string); overload;
You would have to change this to:
function myfunc(var a: string); overload;
But if you want to call the same function, you need to call the function with a discard variable that is a copy of the input.
b := 'cde';
discard := b;
myfunc(discard);
caption := b; //uses 'cde'
However you can create a second function that trivially wraps the first for you and discards the by ref change:
function myfunc2(s: string);
begin
Result := myfunc(s);
end;
//Now the earlier code becomes:
b := 'cde';
myfunc2(b);
caption := b; //uses 'cde'
Note, that unlike Remy's answer this uses a different function name and not an overload, because the compiler would be unable to detect which of the 2 functions to call. However, I would strongly argue that this is a good thing. One of the worst things you can do for the maintainability of your program is write identically named functions that are fundamentally different. It makes it very confusing to figure out what is happening on a particular line of code.
I've started working with OOP today in Delphi. I've made a simple 'Box' with a function that returns the volume when the user enters the length, breadth and height.
Here's my class:
unit clsBox;
interface
uses
SysUtils;
Type
TBox = class(TObject)
private
fL, fB, fH : Integer;
constructor Create (a, b, c : Integer);
function getVolume : Integer;
public
end;
implementation
{ TBox }
constructor TBox.Create(a, b, c: Integer);
begin
a := fL;
b := fB;
c := fH;
end;
function TBox.getVolume: Integer;
begin
Result := fL*fb*fh;
end;
end.
I have also created the variable for the box in the private section of the original unit
myBox : TBox;
But when I try this:
procedure TForm1.btnGetVolumeClick(Sender: TObject);
var
l,b,h : Integer;
begin
l := StrToInt(edtLegth.Text);
b := StrToInt(edtBreadth.Text);
h := StrToInt(edtHeight.Text);
myBox := TBox.Create(l,b,h); //<---- here
end;
It gives me an error saying Too many actual parameteres
Your constructor is private and so cannot be seen from the other unit. From the other unit, the parameterless constructor declared in TObject can be seen and that is what the compiler assumes you are calling.
Make your constructor public.
You'll have the same problem when you want to call getVolume. Perhaps that's intended to be used as a property getter.
Your constructor also performs its initialization incorrectly. All three assignment statements are incorrect and need to have their operands reversed.
The names of the constructor parameters are not informative. How can the reader deduce their use from the names a, b and c?
In the following code the record constructor does something strange.
It works OK in all instances, except in the line marked below:
program Project9;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Collections;
type
TIntegerPair = TPair<Integer, Integer>;
type
TMiniStack<T> = record
public
Items: array[0..10] of T;
SP: integer;
procedure Init;
procedure PushInline(const Item: T); inline;
procedure PushNormal(const Item: T);
end;
procedure TMiniStack<T>.Init;
begin
FillChar(Items, SizeOf(Items), #0);
SP:= 0;
end;
procedure TMiniStack<T>.PushInline(const Item: T);
begin
Items[SP]:= Item;
Inc(SP);
end;
procedure TMiniStack<T>.PushNormal(const Item: T);
begin
Items[SP]:= Item;
Inc(SP);
end;
procedure RecordConstructorFail;
var
List1: TMiniStack<TIntegerPair>;
List2: array[0..2] of TIntegerPair;
Pair: TIntegerPair;
a: string;
begin
Writeln('start test...');
FillChar(List1, SizeOf(List1), #0);
List1.Init;
List1.PushInline(TIntegerPair.Create(1, 1));
List1.PushInline(Pair.Create(2, 2)); <<--- Failure
List2[0]:= TIntegerPair.Create(1, 1);
List2[1]:= Pair.Create(2, 2);
if (List1.Items[0].Key <> 1) or (List1.Items[1].Key <> 2) then Writeln('something is wrong with List1-Inline');
if (List2[0].Key <> 1) or (List2[1].Key <> 2) then Writeln('something is wrong with List1');
List1.Init;
List1.PushNormal(TIntegerPair.Create(1, 1));
List1.PushNormal(Pair.Create(2, 2));
if (List1.Items[0].Key <> 1) or (List1.Items[1].Key <> 2) then Writeln('something is wrong with List1-Normal');
Writeln('Done');
Readln(a);
Writeln(a);
end;
begin
RecordConstructorFail;
end.
Why does this line cause a failure?
List1.PushInline(Pair.Create(2, 2)); <<- Failure: Dumps the data somewhere else.
Is it a compiler bug?
Or am I missing something?
I'm using Delphi XE6.
In my view, inlined or not, an expression that is a constructor call on an instance does not evaluate to a value. And so cannot be passed as an argument. Think of
Pair.Create(...)
as though it is a procedure. It yields no value.
In my view the code should be rejected by the compiler and the fact that it is not rejected is the bug.
That the code appears to work when you don't inline it is, in my view, down to chance.
This is all guesswork though. Record constructors aren't properly documented. The documentation for constructors is for classes and has not been updated to cover records. The documentation for classes says that the instance form is an expression that is a reference to the instance.
When a constructor is called using an object reference (rather than a class reference), it does not create an object. Instead, the constructor operates on the specified object, executing only the statements in the constructor's implementation, and then returns a reference to the object.
But this doesn't make much sense for a record because there are value types rather than reference types.
My best guess is that the parser regards this expression as being the same type as the instance, because that's how it is for constructors of classes. But the codegen doesn't actually yield a value.
I have a class of type TObject called CModelItem
I want to have a list of these objects and be able to modify the values of each one
So, I created a class
CQueueList = class(TList)
private
public
end;
and I make
QueueList : CQueueList;
in var
Now, I can add the CModelItem to this list, like so:
QueueList := CQueueList.Create;
for idx := 0 to ndx - 1 do
begin
MyItem := CModelItem.Create;
MyItem.CopyHead(CModelItem(RunList.Objects[idx]));
MyItem.ReadData;
MyItem.NumOfIterations := NumRepEdit.Value;
MyItem.IsInQueue := True;
MyItem.LogEvents := EventsCheckBox.Checked;
MyItem.LogMatch := MatchCheckBox.Checked;
MyItem.LogUpdates := UpdatesCheckBox.Checked;
QueueList.Add(MyItem);
end;
I can also use it, so I can do:
DefForm := TRunDefForm.Create(Self, QueueList.Items[idx]);
with DefForm taking in a component and a CModelItem
But I'm running into problems trying to modify the values of an object in QueueL
First, I can't access something like MyItem.IsInQueue by doing
QueueList.Items[idx].IsInQueue := blah;
because it tells me IsInQueue is 'an undeclared identifier'
I've also tried making a new CModelItem and copying the information over, like this:
idx := QueueListBox.ItemIndex;
MyItem := QueueList.Items[idx];
and this compiles fine, but throws up an 'access violation error' when it goes into that function
I noticed that QueueList.Items[idx] is a pointer, but I'm really not sure how I should be accessing it
The compiler complains because TList.Items returns an untyped pointer.
You can use a typecast:
CModelItem(QueueL.Items[idx]).IsInQueue := blah;
You can also reimplement the Items property in your CQueueList class:
private
function GetItems(Index: Integer): CModelItem;
public
property Items[Index: Integer]: CModelItem read GetItems; default;
end;
function CQueueList.GetItems(Index: Integer): CModelItem;
begin
Result := inherited Items[Index];
end;
As you've seen, using a local variable works; although the access violation is a bug probably somewhere else in your code.