7

I'm converting some functions into a DLL which relate to Windows 7 functionality. I can't pass a TForm through DLL, so I need to pass its handle instead. except, once I have that handle on the other side, how do I reconstruct it back into a TForm instance? Also, what's the appropriate way to pass the handle (HWND) through a Delphi DLL to be compatible to call from C# for example?

If not possible, then I at least need to know how to change the color of a window using windows API only, no reference to the TForm. The goal is that from within this DLL, it needs to somehow change the color of a Form. Passing the handle to the DLL is no problem, but how to use that handle to work with the form that the handle represents?

What I'm doing specifically is putting together a single DLL that contains everything needed to make a Delphi7 application compatible with Windows7, for example, drawing glass, properly showing multiple forms in the taskbar (and minimizing forms), showing the green progress in the taskbar's icon, and whatever else might be involved. This type of work though requires modifying the form. I need to be able to do those modifications from within the DLL.

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • The core question is if and how you can change the color of a form from within a DLL, when the form's outside the DLL. I was hoping there was a way to build a TForm based on the form's handle, but the answers have already concluded no, unless I decide to use ShareMem. However I've already concluded I'm abandoning the DLL and just doing a single unit for the library. Thanks for all the input, all of it helped. – Jerry Dodge Nov 20 '11 at 02:40
  • A am going to ask a new question in regards to the same subject - seeing as I was taking the entire wrong approach in the first place, and have to steer into a different direction. – Jerry Dodge Nov 21 '11 at 22:02

3 Answers3

9

In general, you can convert an HWND to a VCL TWinControl-derived object pointer using the VCL's FindControl() function in the Controls unit. You can then check if the TWinControl is actually a TForm using the is operator.

However, as others have stated, passing VCL objects over the DLL boundary in general is dangerous and can cause problems if both EXE and DLL are not compiled with the exact same VCL version, RTL version, memory manager, etc. To pass VCL objects over the DLL boundary safely, change your DLL project into a BPL Package project, and make sure Dynamic RTL is enabled in both EXE and BPL.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    You can do this even if the external DLL and the app aren't sharing the same memory manager? The `is` operator fails because the VMTs don't match, IIRC. If this is so, can you provide simple source for a DLL I can pass a `TForm.Handle` to that will return something distinctive from the `TForm` that isn't available from a direct API call using the `HWND`? – Ken White Nov 19 '11 at 03:12
  • 1
    @KenWhite The generic window handle is associated with a global atom through which you get at the instance of `TWinControl`. See [`FindControl`](http://docwiki.embarcadero.com/VCL/en/Controls.FindControl), as @Remy answered, also check the implementation of `ObjectFromHWnd` and `RM_GetObjectInstance` message handling in `TWinControl.DefaultHandler`. – Ondrej Kelle Nov 19 '11 at 11:21
  • 1
    If the main app and the DLL use the same runtime packages they also share the memory manager and `is` operator will work. Otherwise `is` will not work but you can still use `ClassNameIs`, hard-cast to the DLL's TWinControl-descendant class if the RTL/VCL is the same, with limitations as to what you can safely do with such cast, of course. – Ondrej Kelle Nov 19 '11 at 11:26
  • @David Last I checked, handles were managed by Windows, not by the Application. You can get instances of controls on other applications' forms using its handle, as demonstrated above. – Jerry Dodge Nov 19 '11 at 11:30
  • @DavidHeffernan But of course pointers can pass across module boundaries. What misinformation are you talking about? – Ondrej Kelle Nov 19 '11 at 13:06
  • I'm talking about windows form handles, not objects or pointers. – Jerry Dodge Nov 19 '11 at 15:54
  • @TOndrej, usning runtime packages would mean sharing the same memory manager, which I specifically mentioned (and the original question didn't, BTW),. Remy said you could pass a `TWinControl.Handle` from an application to a DLL that did NOT share the same memory manager and retrieve the `TWinControl` using the `is` operator, which you cannot AFAIK. I asked for a simple example of DLL code (90% of which would be generated by the IDE itself) that demonstrated doing so. So far, you notice no reply from him. :) – Ken White Nov 19 '11 at 16:55
  • @Remy, I'm afraid I have to downvote this as being wrong. I'll be glad to reverse that vote if you can demonstrate otherwise. :) – Ken White Nov 19 '11 at 16:55
  • 1
    @KenWhite: that is not what I said. I said that given any `HWND`, you can pass it to the `Controls.FindControl()` function to retrieve it's owning `TWinControl` object, if it has one. If `FindControl()` returns a non-nil pointer, then you can use the `is` operator to check if the `TWinControl` is a `TForm` or not. However, that does not work if the EXE and DLL are not sharing the same RTL. – Remy Lebeau Nov 19 '11 at 17:27
  • @KenWhite: however, if the EXE and DLL are compiled with different compiler/RTL versions, it is not safe to access an EXE's object from inside the DLL and vice versa. The DLL has different machine code and different object layouts than the EXE, and so may be accessing offsets for methods/properties that are different than the EXE is expecting (and vice versa). Use Packages or COM to guarantee binary compatibility across module boundaries. – Remy Lebeau Nov 19 '11 at 17:32
  • 1
    @Remy, then the downvote is still appropriate, as that is not what the question asked at all. It specifically asked about passing an `HWND` across EXE/DLL boundaries without the use of packages or a shared memory manager. The discussions of other answers also referenced the lack of a shared memory manager. That's what prompted my first comment to your answer immediately (which specifically mentioned using `is` **without** using the same memory manager. – Ken White Nov 19 '11 at 17:43
  • So let me get this straight: IF the DLL is unique to its host application. and the DLL was using ShareMem, then I could be safe to pass a HWND into the DLL to do WinAPI functions, but without ShareMem, I cannot? And also, ShareMem requires Borland pre-requisites, right? So how about reconstructing the form control's canvas inside the DLL using the form's handle? (Which I know that is possible at least without a DLL) – Jerry Dodge Nov 19 '11 at 17:53
  • Thanks Ken, I knew that was possible, just didn't know exactly how. I try to avoid using ShareMem for that reason of requiring it with the app, but there are cases like this where it can come in handy. Would you mind re-answering it with this approach, and I'll accept it? – Jerry Dodge Nov 19 '11 at 18:43
  • @David Yes I know I can't directly use the "Application" reference from within the DLL, thanks for the reminder though. But using ShareMem I can pass the form object as a parameter. Still though, as mentioned above, I'm pretty much abandoning the DLL idea, at least for this project, becaue I will NEED to do some fancy tricks with the "Application" instance. – Jerry Dodge Nov 19 '11 at 19:21
  • `ShareMem` merely shares the memory manager. You still have to share the RTL as well, by enabling the use of Runtime Packages in both EXE and DLL, in order to safely use VCL objects across module boundaries. But if you just stick with `HWND` and API functions (like `GetDC()` for instance) and nothing else, then you do not need anything extra for that. – Remy Lebeau Nov 20 '11 at 19:45
4

You can't get a TForm from a handle. There's no such thing outside of your Delphi app, unless you're using packages (as David's answer said).

You can use the Handle (HWND) directly in API calls, but only for API calls.

You can pass that HWND directly to API calls like GetDC to get a device context (DC), and then use that DC directly with the DrawTheme related functions like DrawThemeText or anything else that requires a windows DC. You can also pass it to other API calls that require an HWND.

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • So what would be the best way to draw the form to be a certain color? This is for the glass effect, the form needs to be forced to be a certain color in order to draw the glass appropriately. – Jerry Dodge Nov 19 '11 at 01:07
  • You use the `ThemeServices` drawing functions (from the API, not Delphi). I personally feel your entire approach is wrong; you're doing a lot of work to basically force non-compliant applications into compliance. You might try just adding [TThemeManager](http://www.soft-gems.net/index.php?option=com_content&task=view&id=17&Itemid=33) to your app itself, which will add most of the theme support you're looking for as far as the background goes; you can then just add whatever additional features you want. (Or more simply, upgrade to a more recent version of Delphi that includes what you want.) – Ken White Nov 19 '11 at 01:13
  • Correction to my last comment - I forgot D7 included `TXPManifest`, which contains `TThemeManager`. The separate component isn't needed; if you're not already, you could just drop `TXPManifest` (from the `Win32` tab) into your D7 app to get the same results. – Ken White Nov 19 '11 at 01:21
  • My object was to contain all necessary functionality to make use of the special windows 7 effects in one library (DLL) but since I see it's next to impossible, I'm abandoning the DLL and I'll just do a single unit to do it all. In the end, on application startup, it will initialize this library and do certain tricks to prepare the app for Win7, and inherit some custom forms as well. – Jerry Dodge Nov 19 '11 at 01:53
  • 1
    Jerry, seems like a much better idea. :) – Ken White Nov 19 '11 at 02:04
  • It seems to me that this answer was correct until the edit went in, and now the content of the edit is incorrect. I already deleted all my other comments in this question since it has become something of a train wreck. But as it stands I'm disappointed by the poor level of information that this question contains for future readers. – David Heffernan Nov 19 '11 at 22:48
  • @David, you're right. I was right to stop where I did the first time; I should have left it alone. Removed last edit. Thanks. – Ken White Nov 19 '11 at 23:36
  • @Ken btw about the XPManifest, all I have to do is include the unit `XPMan` somewhere in the project and all the themes come alive. Don't even need to have a TXPManifest on the form. – Jerry Dodge Nov 20 '11 at 02:42
3

You can't pass Delphi objects across DLL boundaries. It simply does not work. There is no mechanism to export a Delphi class from a DLL.

You are aware of this but passing a handle across the boundary does not help. You wish to operate on a TForm on the other side of the boundary. But the only TForm instance that can make sense is the only which created the handle, and that instance is trapped by the module boundary.

There are some objects which can be recreated from just a handle. For example, bitmaps and icons have this property. This is because they have no state beyond what is stored in the GDI handle. More complex VCL objects do have such state and therein lies the problem.

Your options are:

  1. Use packages. This works a treat but you must use the same compiler version for all modules.
  2. Use interfaces or COM. This gives you freedom of mixing compiler versions and even different languages.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Yes, this is my point, I'm not trying to pass the class, I'm trying to pass the handle (which should convert to just an integer, as far as I know). Problem is how do I create a form instance to represent the form from that handle? – Jerry Dodge Nov 19 '11 at 00:04
  • Added more to the question, if what I need isn't possible – Jerry Dodge Nov 19 '11 at 00:07
  • 3
    @Jerry, David's point is that you *can't*. An `HWND` isn't a `TForm`, and can't be turned into one. It's a generic API window handle. You can then use that window handle directly through the Windows API, but you can't turn it into a TForm or a class representing that HWND. – Ken White Nov 19 '11 at 00:19