I want to define an array of anonymous method: TFunc<Integer>:
function MyFunc: Integer;
begin
end;
procedure TForm84.Button1Click(Sender: TObject);
var A: TArray<TFunc<Integer>>;
begin
A := [MyFunc];
end;
But Delphi compiler prompt error:
[dcc32 Error] E2010 Incompatible types: 'System.SysUtils.TFunc<System.Integer>' and 'Integer'
Using array for native data type like integer or string works for similar construct.
The compiler isn't able to decide whether your use of MyFunc means to call the function or to refer to it as function.
So you'll likely need to help the compiler out:
SetLength(A, 1);
A[0] := MyFunc;
MyFunc() does not take any input parameters, so Delphi allows it to optionally be called without parenthesis, eg:
var
I: Integer;
I := MyFunc();
I := MyFunc; // <-- same as above!
In a statement like A := [MyFunc];, the compiler is apparently not smart enough to figure out that the context in which MyFunc is used requires the address of MyFunc() to be passed to the array, and so it instead calls MyFunc() and passes the return value to the array, hence the error message.
So, you need to be more explicit to the compiler that the address is needed.
Try this:
A := [#MyFunc];
Or:
A := [Addr(MyFunc)];
Or:
type
TIntFuncArray := TArray<TFunc<Integer>>;
var
A: TIntFuncArray;
begin
A := TIntFuncArray.Create(MyFunc); // or maybe # or Addr(), not sure
end;
Related
In a Delphi program, have the following pattern:
TDelegate=reference to procedure(const Arg: TMyType);
TRouter = class
...
public
procedure RegisterHandler(const route: string: handler: TDelegate);
end;
THandlerContainer = class
public
function getDelegate: TDelegate;
procedure register(const Router: TRouter);
end; // class
...
procedure THandlerContainer.register(const router: TRouter)
begin
router.RegisterHandler('route', getDelegate);
end;
Basically, I'm registering function references to be used to handle some message handling (based on the "route" string).
I would like to simplify the pattern for my coworkers so that they do not have to call router.RegisterHandler themselves for each implementation but simply have to add an attribute to their class and then pass an instance to a method of TRouter that will use RTTI to find all method decorated by that attribute and register them.
I have therefore created a simple attribute RegisterMessageHandlerAttribute for that decoration (with a custom constructor for receiving the routing string) and wrote a method of TRouter that uses the RTTI to find all method decorated with that atribute:
function TRouter.RegisterHandlers(const HandlerContainerClass:
TObject);
var
RTTIContext: TRttiContext;
RttiType : TRttiType;
prop: TRttiMethod;
Attr: TCustomAttribute;
begin
RTTIContext := TRttiContext.Create;
try
RttiType := RTTIContext.GetType(HandlerContainerClass);
if assigned(RttiType) then
begin
for prop in RttiType.GetMethods do
begin
for Attr in Prop.GetAttributes do
begin
if (Attr is RegisterMessageHandlerAttribute) then
begin
Self.RegisterHandler(
(Attr as RegisterMessageHandlerAttribute).Route,
TDelegate(Prop.Invoke(HandlerContainerClass, []).AsPointer); // <--- this fails
);
end;
end;
end;
end;
finally
RTTIContext.Free;
end;
result := Handlers.ToArray;
end;
Unfortunately, the compilers complains on the line where I retrieve the lambda by calling the method:
TDelegate(Prop.Invoke(HandlerContainerClass, []).AsPointer);
...
[dcc32 Error] GIT.MessageQueue.Router.pas(169): E2089 Invalid typecast
My problem is that I have no idea how to take the TValue type returned by Prop.Invoke and use it as a function reference of type TDelegate.
Just use .AsType<TDelegate>() - this returns the content of the TValue as TDelegate. That function is also making sure that you don't turn something that is inside the TValue into something that is not explicitly assignment compatible (not like Variants do). But since that is the exact return type of your function it will just work.
P.S. You need to type the parentheses explicitly because otherwise you might get an E2010 error from the compiler.
If you like to use a method's pointer as an argument, you need to type the method as function of object like this works good:
type TAcceptor = function(filename:string):boolean of object;
function acceptor(filename:string):boolean;
begin
result := filename <> '';
end;
What if you like to use the pointer of a sub-method? It does not work:
procedure TForm1.Button1Click(Sender:TObject);
function acceptor(filename:string):boolean of object;
begin
result := filename <> '';
end;
begin
end;
The error occour: ; expected but OF found!
Question: Is there any subfunction-pointer? Can i cast it?
I don't see how that this would be possible.
http://docwiki.embarcadero.com/RADStudio/XE6/en/Procedural_Types
If you look under the method pointers section, it specifically says that nested procedures and functions cannot be used:
"Nested procedures and functions (routines declared within other
routines) cannot be used as procedural values, nor can predefined
procedures and functions."
You might be able to work around it using an anonymous method. Something like:
procedure TForm1.Button1Click(Sender:TObject);
begin
DoSomethingWithAcceptor(function(FileName: string): Boolean
begin
Result := FileName <> '';
end);
end;
CAUTION
I know that the following is not universally applicable, but it works for all known Win32 versions of Delphi. As long as you are aware of this, and check its functionality in new versions, it is a viable hack, IMO.
Passing nested functions to methods
In older code, I used this to do some "poor man's anonymous methods":
type
TLocal = packed record
Code: Pointer; // local (nested) function
Frame: Pointer; // outer stack frame for local function
end;
To fill such a local inside a method, I wrote the function Local:
function Local(LocalFunction: Pointer): TLocal;
asm
MOV [EDX].TLocal.Frame,EBP
MOV [EDX].TLocal.Code,EAX
end;
Inside my unit (some kind of generic collection), I wrote a function to call them, passing one parameter (of type TGeneric, in this case, which is not important here, you can also pass a pointer or some such).
// Calls local function using local closure provided, passing
// T as parameter to the local.
function CallLocal(T: TGeneric; const Local: TLocal): TGeneric;
asm
PUSH [EDX].TLocal.Frame
CALL [EDX].TLocal.Code
ADD ESP,4
end;
It was used like this:
function TStdCollection.AsArray: TGenericArray;
var
I: Integer;
A: TGenericArray;
procedure ToArray(E: TGeneric);
begin
Result[I] := E.Traits.Copy(E);
Inc(I);
end;
begin
SetLength(A, Count);
I := 0;
ForEach(Local(#ToArray));
Assert(I = Count);
Result := A;
end;
The code in the nested function makes a copy of the element and stores it in the array. The main procedure then passes the nested function ToArray (together with its stack frame) as parameter to ForEach, which is implemented this way:
function TStdCollection.ForEach(Operation: TLocal): ICollection;
var
Enum: IEnumerator;
Elem: TGeneric;
begin
Enum := GetEnumerator;
Elem := Enum.First;
while Elem <> nil do
begin
CallLocal(Elem, Operation);
Elem := Enum.Next;
end;
Result := Self;
end;
These examples show how to use the Locals. I hope this more or less answers your question.
Note
Note that this code was written in the Delphi 6 timeframe. I know there are better alternatives these days, like generics and anonymous methods. But if compatibility with Delphi 7 is required, the above might be a solution.
There is method:
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
begin
a := b;
Result := T_Result;
end;
Now the exception happening on a := b; , happening Access violation Exception.
Ofcourse I can try and catch it. but I don't want to do that....
So Is there any way can determine use some way and skip the assignment. like:
if (! now I know it will happening that Exception){
a := b; // so I can skip
}
Result := T_Result;
Maybe it's very easy, but because I don't know use delphi, So hope your guys can help me. thanks.
Update1:
b: Boolean;//Some friend need to know what is the b param type.
Update2:
I'm try to use :
if b<> nil then Enabled := b;
but I can't build it , it will display: E2008 Incompatible types
Update3:
I'm trying to debug it, and when I'm debug, on the Local variables panel display:
a Inaccessible value
I'm use .NET called it. there is metadata:
bool get_Param{ [param: In, MarshalAs(UnmanagedType.Bool)] [PreserveSig] set; }
actually I'm not use .NET access it. I'm use .NET access a DirectShow filter, and the directshow filter is current method(write by delphi)
Update4:
this is partial C# code
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), SuppressUnmanagedCodeSecurity, Guid("hidden")]
public interface IDCDSPFilterInterface{
bool get_Param{ [param: In, MarshalAs(UnmanagedType.Bool)] [PreserveSig] set; }
.. hidden other ..
}}
I'm try to use :
if b<> nil then Enabled := b;
but I can't build it , it will display: E2008 Incompatible types
Pointer variables are ABC of Pascal. http://en.wikipedia.org/wiki/Pascal_(programming_language)#Pointer_types
So the proper way to write that check would be
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
var ptr: ^BOOL;
begin
ptr := #a;
if nil = ptr then ....
a := b;
Result := T_Result;
end;
That is the basic question to you explicit questions.
Now, in reality that check does not help. It would only protect your from nil/NULL pointers, but that is not what probably happens. What happens is probably a random garbage pointer instead of nil. Due to error in the calling code.
Again, you can check that via var ptr: Pointer {untyped}; ptr := #Self; if ptr = nil then ... or just if nil <> Self or just if Assigned(Self) - but that would only protect you from NIL pointers, not from RANDOM GARBAGE pointers.
More so, i think that actual garbage is not in pointer to the variable a, but to the pointer to Self and b being a member of TEST classm, thus the real statement is a := Self.b;.
Since you use stdcall i think you're trying to make a DLL for using from an EXE made in a in non-Delphi language. Most probably you either made a wrong definition for function in that client app code. Actually, you just can make a proper declaration is you Test is a class. You only can make a proper if get_Param is a method of RECORD Test or perhaps if it is STATIC CLASS method of Test class. So the proper way to write your function would be like following
function Test.get_Param(out a : BOOL): HRESULT;
begin
a := b;
Result := T_Result;
end;
function DLL_get_Param(const TestObject: pointer; out a : BOOL): HRESULT; stdcall;
var MyTest: Test;
begin
pointer(MyTest) := TestObject;
Result := MyTest.DLL_get_Param(a);
end;
export DLL_get_Param;
Read Delphi documentation what you can get/put to/from DLL functions.
Integers, floats, pointers, IInterface. You cannot pass into DLL complex and behaving objects like stings, dynamic arrays, object instances. And since you cannot pass an object instance, you cannot pass a Self variable and you cannot call a method.
One very expensive way to catch it would be like
{global} var TestInstances: TList;
type
TEST = class...
procedure AfterConstructon; override;
procedure BeforeConstructon; override;
....
procedure Test.AfterConstructon;
begin
inherited;
TestInstances.Add(Self); // single-thread assumption here
end;
procedure Test.BeforeConstructon;
begin
TestInstances.Remove(Self); // single-thread assumption here
inherited;
end;
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
begin
if not ( TestInstances.IndexOf(Self) >= 0 {found!} ) // single-thread assumption here
then ... WTF ???
...
....
initialization
TestInstances := TList.Create;
finalization
TestInstances.Free;
end;
If your DLL can be used by multi-threaded application you should also wrap the marked calls into http://docwiki.embarcadero.com/Libraries/XE2/en/System.SyncObjs.TCriticalSection
There is a gross mismatch across the two sides of your interop boundary. Your Delphi function does not match the C# declaration.
The solution is not to test for parameter validity. Your Delphi code, given the declaration of the function in the question, is correct. The solution is to make both sides of the interop boundary match. I cannot tell you more than that until you show both sides of the interop boundary.
Since I can't see where you've decalred b, I'm going to assume it's a member of Test.
So one strong possibility is that you have an invalid instance of Test, and you get an Access Violation trying to read b in order to assign it to a. As an example the following use of get_Param would raise an exception.
var
LTest: Test;
LA: Boolean;
begin
LTest := nil;
LTest.get_Param(LA);
end;
The point is that you need a valid instance of Test in order to use it. E.g.
var
LTest: Test;
LA: Boolean;
begin
LTest := Test.Create;
try
LTest.get_Param(LA);
finally
LTest.Free;
end;
end;
Here are my types...
unit unitTestInterfaces;
interface
type
IFoo = interface
['{169AF568-4568-429A-A8F6-C69F4BBCC6F0}']
function TestFoo1:string;
function TestFoo:string;
end;
IBah = interface
['{C03E4E20-2D13-45E5-BBC6-9FDE12116F95}']
function TestBah:string;
function TestBah1:string;
end;
TFooBah = class(TInterfacedObject, IFoo, IBah)
//IFoo
function TestFoo1:string;
function TestFoo:string;
//IBah
function TestBah1:string;
function TestBah:string;
end;
implementation
{ TFooBah }
function TFooBah.TestBah: string;
begin
Result := 'TestBah';
end;
function TFooBah.TestBah1: string;
begin
Result := 'TestBah1';
end;
function TFooBah.TestFoo: string;
begin
Result := 'TestFoo';
end;
function TFooBah.TestFoo1: string;
begin
Result := 'TestFoo1';
end;
end.
And here is my code to run the example...
var
fb:TFooBah;
f:IFoo;
b:IBah;
begin
try
fb := TFooBah.Create;
/// Notice we've used IBah here instead of IFoo, our writeln() still outputs the
/// result from the TestBah() function, presumably because it's the "first" method
/// in the IBah interface, just as TestFoo1() is the "first" method in the IFoo
/// interface.
(fb as IUnknown).QueryInterface(IBah,f); //So why bother with this way at all??
//f := fb as IBah; //causes compile error
//f := fb; //works as expected
if Assigned(f)then
begin
writeln(f.TestFoo1); //wouldn't expect this to work since "f" holds reference to IBah, which doesn't have TestFoo1()
end;
(fb as IUnknown).QueryInterface(IBah,b);
if Assigned(f) then
begin
writeln(b.TestBah1);
end;
except on E:Exception do
writeln(E.Message);
end;
end.
It seems that in the first call to QueryInterface, even though we are assigning the wrong type of interface to the "f" variable, it will still try to execute the 'first' method of whatever it's pointing to, as opposed to the method with the name "TestFoo1". Using f := fb works as expected, so is there a reason we would ever use QueryInterface instead of the syntax f := fb?
I guess you are breaking the rules here:
QueryInterface will put the interface into f which you requested. You are responsible that f is of the appropriate type. As the second parameter is untyped the compiler cannot warn you about your fault.
I would argue that the better syntax is neither the QueryInterface one nor the f := fb one. It is the one you commented out:
f := fb as IBah; //causes compile error
and that is precisely because it has type checking, which covers the problem with QueryInterface that it doesn't check its arguments.
Please note that f := Fb as IFoo, the call Supports( Fb, IFoo ) etc tec all call QueryInterface in the background. So the QueryInterface method is used but you get the nice syntax with autocasting, is, as and methods like support.
At the moment I give delphi2010 a trial and found the TValue type of the Rtti Unit. TValue have very interessting features, but I can't find a way to assign an interface.
I try the following
program Project1;
uses
Classes, SysUtils, Rtti;
var
list : IInterfaceList;
value : TValue;
begin
// all these assignments works
value := 1;
value := 'Hello';
value := TObject.Create;
// but nothing of these assignments works
list := TInterfaceList.Create;
value := list; // [DCC Fehler] Project1.dpr(15): E2010 incompatible types: 'TValue' and 'IInterfaceList'
value.From[list]; // [DCC Fehler] Project1.dpr(16): E2531 Method 'From' requires explicit typarguments
value.From<IInterfaceList>[list]; // [DCC Fehler] Project1.dpr(17): E2035 Not enough parameters
end.
I can't find any further information. Not in the delphi help system and not on the internet. What do I do wrong?
Your last try is the closest. TValue.From is a class function that creates a TValue from a parameter. You probably put the square brackets in there because that's how CodeInsight showed it, right? That's actually a glitch in CodeInsight; it does that for generics-based functions, where you should be using parenthesis instead. The proper syntax looks like this:
Value := TValue.From<IInterfaceList>(list);
This is a working version of the program:
program Project1;
uses
Classes, SysUtils, Rtti;
var
list : IInterfaceList;
value : TValue;
begin
// all these assignments works
value := 1;
value := 'Hello';
value := TObject.Create;
// but nothing of these assignments works
list := TInterfaceList.Create;
value := TValue.From(list);
end.