I have not so much experience with building DLLs in Delphi. I am facing a problem because one of my customer is really fond of having a DLL implementing some core functionalities and several applications relying on it (and other DLLs).

I have not so much experience with building DLLs in Delphi. I am facing a problem because one of my customer is really fond of having a DLL implementing some core functionalities and several applications relying on it (and other DLLs).

Now the problem I am facing is that type system seems to be duplicated across the executables and the DLL (I can recall reading something about it but finding a reference seems hard). This means that if I build an instance of type X in the DLL and pass it (as a function's result) to the executable, I won't be able to check if that type is of type X ("MyVar is X" will return false).

// var LInstance: TMyAbstractType;
// var LValue: TValue;

LValue := TValue.From(LInstance);

if LValue.IsInstanceOf(TMyAbstractType) then
ShowMessage('1 - LValue is TMyAbstractType')
else if LValue.AsObject is TMyAbstractType then
ShowMessage('2 - LValue is TMyAbstractType')
else
ShowMessage('3 - LValue ClassName: ' + LValue.AsObject.QualifiedClassName);

Does anybody have some suggestion to workaround this? Apart from checking ClassName as a string...

I have a complete simple project showing the isse and it is available here: https://www.dropbox.com/s/6fpo9m0cq3m84fb/TypesInDLL.zip?dl=0
Clicking on Button1 will trigger code from DLL, Button2 implements the same thing in the executable (and everything works fine).


https://www.dropbox.com/s/6fpo9m0cq3m84fb/TypesInDLL.zip?dl=0

Comments

  1. Use packages instead of DLLs.

    Since you are trying to access Delphi specific types across the module boundaries, your DLLs aren't going to be usable by anything other than a host built with the Delphi version. In which case there is no limitation to using packages.

    ReplyDelete
  2. David Heffernan Unfortunately this is not an option... They already have several applications in this architecture. With me, they are adding a new application that makes use of the dll. If it was up to me, I would have left dll (and bpl) out of the game...

    ReplyDelete
  3. Andrea Magni, Explain exactly what problem you are trying to solve (why you need to know types), the chance is better at getting a good answer that way. How does the other applications in this suite solve the problem?

    ReplyDelete
  4. Andrea Magni Don't really see how you can expect to share Delphi types across a DLL boundary. How is this done at present?

    I mean, if you can use packages because you have to fit in with the existing solution, why don't you just use the existing solution?

    ReplyDelete
  5. How about sharing the instance data as Json and recreating the object in the application?

    Directly sharing an instance of a specific type sounds like a risky project. What if there is a difference in the VMT structure?

    ReplyDelete
  6. you can not share Objects in Delphi DLL unless the EXE and the DLL uses Packages.
    the easiest solution is to use an Interface

    type
    IMyAbstractType = Interface
    function SayHello: string;
    end;

    type
    BuildInstance = function(const ALang: string): IMyAbstractType ; stdcall;

    the other solution is to use a "C" Wrapper

    // EXE side (the object instance is an opaque type)
    type
    BuildInstance = function(const ALang: string): THandle; stdcall;
    SayHello= procedure(Handle: THandle); stdcall;

    // DLL side (you can use the real object type)
    function BuildIsntance(const ALang: string): TMyAbstractType;
    begin
    ...
    end;
    procedure SayHello(AType: TMyAbstractType); stdcall;
    begin
    AType.SayHello();
    end;

    ReplyDelete
  7. Thanks all for the suggestions but as I said to refractor several applications to fix this the proper way is out of discussion (not by me). The current state is far from perfect (and I pointed this out day one but this doesn't mean they will fix it immediately). The sample project reproduce the scenario I have to deal with (dll creating instances of some subclass of X and exe making use of them). The indirection (through TValue) is there because mimics the actual dependency injection mechanism involved (MARS's one but would probably have the same issue with others). I do have a workaround (avoiding the DI mechanism) but I was looking for suggestions to fix it.

    ReplyDelete
  8. AFAIK, this is by design and it is the reason using packages is recommended. The type system related to information in the executable, so the same type in 2 executables and you are in trouble. In theory, if the DLL uses runtime packages the shared RTL types would not be defined only once... but at that point you are making the architecture way too convoluted.

    If you are using DLLs, don't rely on type identity!

    ReplyDelete
  9. Like I already said, clearly there is an existing approach in this application. You just need to do the same.

    ReplyDelete
  10. Thanks Marco Cantù, I will try to push for a general refactor but honestly it is hard to justify the effort given they have a ton of code actually working. David Heffernan the common scenario here is to simply load the dll, call the function and use it. They don't bother with different vmt as they will deploy a new dll and update all existing executables each time (that is rare in fact) they change it. (Again obviously far from perfect). They never had to check if the returned type is inherited from the superclass while I need it in order to decide whether to inject the instance or not. But I will workaround this by checking names (as they are all known). Thanks all!

    ReplyDelete
  11. Andrea Magni You really should not be passing Delphi classes between modules. There are many more things that can go wrong beyond type identity. For instance, if your classes have any class vars then you will have two copies of them. Global variables (for instance locks) will be duplicated. So it's very easy to run into thread safety issues.

    If you can't use packages, and I can understand that you can't refactor the entire system, then you should probably be looking to pass interfaces across the module boundaries rather than Delphi classes.

    Presumably you have a shared memory manager already which (assuming the same Delphi compiler used for all modules) allows you to pass strings, dynamic arrays etc.

    ReplyDelete
  12. your code will work fine if all the methods of the shared objects are virtual because the VMT will call DLL's code...and dynamic variables (string, TArray) must not be modified because the memory manager is not shared.

    any way, from the EXE point of view, the DLL objet will never match the local definition because they are two distinct definition of the same object. One workaround could be to export a function in the DLL

    function IsTMyAbstractType(Instance: TObject): Boolean; stdcall;
    begin
    Result := Instance is TMyAbstractType;
    end;

    ReplyDelete
  13. Using DLLs that expose plain C-language style calls is absolutely fine, even if not the best of Delphi. I know of some large and complex application architecture that allow Delphi and VC++ plugins, so they have a common DLL interface. But that's a fairly plain interface with modules that are highly isolated. It works, but does require extra effort and not using a ton of handy features.

    ReplyDelete
  14. Paul TOTH Thanks for the suggestion! This may be easy and effective. 🙂

    ReplyDelete
  15. Andrea Magni Rather than make all methods be virtual, which leads to poor class design and can't readily be enforced, you can use interfaces to achieve the exact same effect.

    ReplyDelete
  16. David Heffernan As I said, it is not my choice and I can't push too much as they actually have a decent argument not to refactor everything just for my new application. If this can make you even more uncomfortable, their base class has all methods marked as abstract 😉
    Paul TOTH having a type checking function inside the dll works (haven't tested actually but it should) but then I would have to add another function to do the actual injection (calling SetValue on a TRttiField that has the "exe version of X" as a type but would receive an "dll version of X" instance. And SetValue will complain as internally is checking types... So no way except bad hacks. I will stick to a more conventional approach and leave the DI thing out...

    ReplyDelete
  17. Andrea Magni That base class is essentially an interface in that case. What do the people that designed the architecture say? It sounds like they must already know the constraints of their design. You might be well served by talking to them rather than us. After all, they designed this architecture, we didn't. They understand it, we don't know anything about it.

    ReplyDelete
  18. David Heffernan What can I say, working as a consultant I am often called to solve a specific problem and discussing the entire system can be hard and rather useless. I tried but they resisted :-) I have learnt that people with a strong opinion on some topic usually get there by experience and I am not too much inclined to strive to make people work my way. I made some suggestions and they listened. Maybe in the future we will have a chance to improve things more. I asked here because, as I wrote, I am not so confident with this topic and having some suggestions from other always leads to better choices and a much more wide angle view.

    ReplyDelete
  19. Andrea Magni One change that might be possible and should not require any code refactoring would be to introduce a package that contains the custom types that are shared between modules and then link all the involved modules to that package. That way you have a shared RTL plus the custom types and only one single RTTI.

    In your example that would mean:
    - create new package (MyPackage.dpk)
    - add the MyAbstractType.pas to that package
    - in project options of exe and dll under Packages\Runtime Packages set "Link with runtime packages" to true and put MyPackage into the "Runtime packages" field (clear all other entries). Make sure this is set for all configurations

    - click Button1 and voila

    ReplyDelete
  20. Stefan Glienke many thanks. I will discuss your solution with the dev team. I have to admit I am not really a fan of runtime packages (neither I am so confident with them as I got projects built with runtime packages only once or twice. Though probably the larger Delphi project I have ever seen is one of these)...
    Thanks again everybody

    ReplyDelete
  21. Please read my article rvelthuis.de - Rudy's Delphi Corner - DLL dos and don'ts. It explains what you should and shouldn‘t do in DLLs.

    ReplyDelete

Post a Comment