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
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
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
Use packages instead of DLLs.
ReplyDeleteSince 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.
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...
ReplyDeleteAndrea 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?
ReplyDeleteAndrea Magni Don't really see how you can expect to share Delphi types across a DLL boundary. How is this done at present?
ReplyDeleteI 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?
How about sharing the instance data as Json and recreating the object in the application?
ReplyDeleteDirectly sharing an instance of a specific type sounds like a risky project. What if there is a difference in the VMT structure?
you can not share Objects in Delphi DLL unless the EXE and the DLL uses Packages.
ReplyDeletethe 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;
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.
ReplyDeleteAFAIK, 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.
ReplyDeleteIf you are using DLLs, don't rely on type identity!
Like I already said, clearly there is an existing approach in this application. You just need to do the same.
ReplyDeleteThanks 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!
ReplyDeleteAndrea 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.
ReplyDeleteIf 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.
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.
ReplyDeleteany 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;
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.
ReplyDeletePaul TOTH Thanks for the suggestion! This may be easy and effective. 🙂
ReplyDeleteAndrea 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.
ReplyDeleteDavid 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 😉
ReplyDeletePaul 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...
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.
ReplyDeleteDavid 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.
ReplyDeleteAndrea 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.
ReplyDeleteIn 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
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)...
ReplyDeleteThanks again everybody
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