2
votes

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.

In essence the base class should look like this:

TWrapperBase = class
public
  FLibHandle: THandle;
  procedure MapMethods;
end;

procedure TWrapperBase.MapMethods;
var
  MyField: TRttiField;
  MyAttribute: TCustomAttribute;
  pMethod: pointer;
begin
  FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
  for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
    for MyAttribute in MyField.GetAttributes do
      if MyAttribute.InheritsFrom(TMyMapperAttribute) then
      begin
        pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
        if Assigned(pMethod) then
          MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;

And a descending class could look like this:

TDecendant = class(TWrapperBase)
private type
  TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
  [TMyMapperAttribute('MyDllMethodName')]
  FSomeDLLMethod: TSomeDLLMethod;
public
  property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;

I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.

I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).

My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?

With the old Rtti, this could be done but only for published properties and without any type checking:

if IsPublishedProp(Self, 'SomeDLLMethod') then
  SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
2

2 Answers

5
votes

There are two problems:

First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.

if Assigned(pMethod) then
begin
  TValue.Make(@pMethod, MyField.FieldType.Handle, value);
  MyField.SetValue(Self, value);
 end;

Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.

For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.

I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010

1
votes

You could try something like:

Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));

or

PPointer(PByte(Self) + Field.Offset)^ := pMethod;