RTL custom interface types as records, added callbacks (marshalled using virtual interfaces)
This commit is contained in:
@@ -33,6 +33,7 @@ type
|
||||
|
||||
// --- Analysis ---
|
||||
class function AnalyzeType(ATypeInfo: PTypeInfo): IStaticType; static;
|
||||
class function GenerateMethodType(RType: TRttiMethodType): IStaticType; static;
|
||||
class function GenerateInterfaceDefinition(RType: TRttiInterfaceType): IStaticType; static;
|
||||
class procedure EnterAnalysis(Info: PTypeInfo); static;
|
||||
class procedure ExitAnalysis(Info: PTypeInfo); static;
|
||||
@@ -72,6 +73,7 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
Myc.Ast.Evaluator,
|
||||
Myc.Ast.RTL.Core;
|
||||
|
||||
{ TRtlTypeRegistry }
|
||||
@@ -168,12 +170,71 @@ var
|
||||
begin
|
||||
rType := FCtx.GetType(ATypeInfo);
|
||||
case rType.TypeKind of
|
||||
tkInterface: Result := GenerateInterfaceDefinition(rType as TRttiInterfaceType);
|
||||
// Check for Anonymous Method (treated as Interface in Delphi but should be stMethod in AST)
|
||||
tkInterface:
|
||||
begin
|
||||
var rIntf := rType as TRttiInterfaceType;
|
||||
// If the Interface has an "Invoke" method and is marked as reference-to (or is generic TFunc/TProc),
|
||||
// map it directly to stMethod.
|
||||
// Note: RTTI for "IsReferenceTo" is not always directly available,
|
||||
// but we can check if there is exactly one method named "Invoke".
|
||||
var method := rIntf.GetMethod('Invoke');
|
||||
if Assigned(method) and (Length(rIntf.GetMethods) = 1) then
|
||||
begin
|
||||
// It is functionally a Delegate -> Map to stMethod
|
||||
var params := method.GetParameters;
|
||||
var pTypes: TArray<IStaticType>;
|
||||
SetLength(pTypes, Length(params));
|
||||
|
||||
for var i := 0 to High(params) do
|
||||
pTypes[i] := ResolveType(params[i].ParamType);
|
||||
|
||||
var retType: IStaticType;
|
||||
if Assigned(method.ReturnType) then
|
||||
retType := ResolveType(method.ReturnType)
|
||||
else
|
||||
retType := TTypes.Void;
|
||||
|
||||
Result := TTypes.CreateMethod(pTypes, retType);
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Classic Interface -> Map to Generic Record
|
||||
Result := GenerateInterfaceDefinition(rIntf);
|
||||
end;
|
||||
end;
|
||||
|
||||
// NEW: Support for classic method pointers (of object)
|
||||
tkMethod: Result := GenerateMethodType(rType as TRttiMethodType);
|
||||
|
||||
else
|
||||
Result := ResolveType(rType);
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TRtlTypeRegistry.GenerateMethodType(RType: TRttiMethodType): IStaticType;
|
||||
var
|
||||
params: TArray<TRttiParameter>;
|
||||
pTypes: TArray<IStaticType>;
|
||||
retType: IStaticType;
|
||||
i: Integer;
|
||||
begin
|
||||
params := RType.GetParameters;
|
||||
SetLength(pTypes, Length(params));
|
||||
|
||||
for i := 0 to High(params) do
|
||||
begin
|
||||
pTypes[i] := ResolveType(params[i].ParamType);
|
||||
end;
|
||||
|
||||
if Assigned(RType.ReturnType) then
|
||||
retType := ResolveType(RType.ReturnType)
|
||||
else
|
||||
retType := TTypes.Void;
|
||||
|
||||
Result := TTypes.CreateMethod(pTypes, retType);
|
||||
end;
|
||||
|
||||
class function TRtlTypeRegistry.GenerateInterfaceDefinition(RType: TRttiInterfaceType): IStaticType;
|
||||
var
|
||||
methods: TArray<TRttiMethod>;
|
||||
@@ -260,8 +321,80 @@ begin
|
||||
Result := TValue.From<Int64>(sc.Value.AsInt64);
|
||||
end;
|
||||
end;
|
||||
vkText: Result := TValue.From<string>(AData.AsText);
|
||||
vkVoid: Result := TValue.Empty;
|
||||
|
||||
vkText:
|
||||
begin
|
||||
Result := TValue.From<string>(AData.AsText);
|
||||
end;
|
||||
|
||||
vkMethod:
|
||||
begin
|
||||
// We can only map script methods to Interfaces (Anonymous Methods are Interfaces).
|
||||
if ATargetType.TypeKind <> tkInterface then
|
||||
raise EInteropException.CreateFmt(
|
||||
'Cannot marshal script method to non-interface type "%s". Target must be an interface or anonymous method.',
|
||||
[ATargetType.Name]);
|
||||
|
||||
// Cast to Interface Type
|
||||
var rIntf := ATargetType as TRttiInterfaceType;
|
||||
var scriptFunc := AData.AsMethod;
|
||||
|
||||
// Create a proxy
|
||||
var vIntf :=
|
||||
TVirtualInterface.Create(
|
||||
rIntf.Handle,
|
||||
procedure(Method: TRttiMethod; const Args: TArray<TValue>; out Result: TValue)
|
||||
var
|
||||
scriptArgs: TArray<TDataValue>;
|
||||
scriptResult: TDataValue;
|
||||
i: Integer;
|
||||
begin
|
||||
// Args[0] is always the Instance (Self) for Interfaces, skip it.
|
||||
SetLength(scriptArgs, Length(Args) - 1);
|
||||
for i := 0 to High(scriptArgs) do
|
||||
begin
|
||||
scriptArgs[i] := FromTValue(Args[i + 1]);
|
||||
end;
|
||||
|
||||
try
|
||||
scriptResult := scriptFunc(scriptArgs);
|
||||
|
||||
// The caller comes from the Delphi world. We must ensure the TCO stack is flushed before returning.
|
||||
TEvaluatorVisitor.HandleTCO(scriptResult);
|
||||
except
|
||||
on E: Exception do
|
||||
raise EInteropException
|
||||
.CreateFmt('Error executing script callback for "%s.%s": %s', [rIntf.Name, Method.Name, E.Message]);
|
||||
end;
|
||||
|
||||
if Assigned(Method.ReturnType) then
|
||||
begin
|
||||
Result := ToTValue(scriptResult, Method.ReturnType);
|
||||
end;
|
||||
end
|
||||
);
|
||||
|
||||
// Extract the Interface
|
||||
var intfRef: IInterface;
|
||||
if vIntf.QueryInterface(rIntf.GUID, intfRef) = S_OK then
|
||||
begin
|
||||
// Instead of using TValue.From(intfRef) (which creates a TValue of type IInterface),
|
||||
// we force the TValue to the exact target type (e.g. TStringProc).
|
||||
// PassArg requires exact type matching for Interfaces/Anonymous Methods.
|
||||
|
||||
TValue.Make(@intfRef, ATargetType.Handle, Result);
|
||||
end
|
||||
else
|
||||
raise EInteropException.CreateFmt('Failed to create virtual interface for "%s".', [rIntf.Name]);
|
||||
end;
|
||||
|
||||
vkVoid:
|
||||
begin
|
||||
if ATargetType.TypeKind = tkInterface then
|
||||
raise EInteropException.Create(
|
||||
'Cannot pass Void to Interface parameter (did you mean nil? or is your lambda returning void?)');
|
||||
Result := TValue.Empty;
|
||||
end
|
||||
else
|
||||
raise EInteropException.CreateFmt('Marshalling for DataKind %s not implemented.', [TRttiEnumerationType.GetName(AData.Kind)]);
|
||||
end;
|
||||
@@ -285,6 +418,7 @@ begin
|
||||
// Recursively wrap returned interfaces
|
||||
tkInterface: Result := WrapInstance(AValue.AsInterface, AValue.TypeInfo);
|
||||
else
|
||||
raise EInteropException.CreateFmt('FromTValue: Unsupported Kind %s', [TRttiEnumerationType.GetName(AValue.Kind)]);
|
||||
Result := TDataValue.Void;
|
||||
end;
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user