How to get instance of TForm from a Handle?
Asked Answered
S

3

8

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.

Steffaniesteffen answered 18/11, 2011 at 23:3 Comment(2)
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.Steffaniesteffen
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.Steffaniesteffen
B
5

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.

Blind answered 19/11, 2011 at 0:26 Comment(8)
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.Steffaniesteffen
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 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.)Blind
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.Blind
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.Steffaniesteffen
Jerry, seems like a much better idea. :)Blind
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.Thrower
@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.Blind
@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.Steffaniesteffen
F
10

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.

Fructuous answered 19/11, 2011 at 2:23 Comment(15)
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?Blind
@KenWhite The generic window handle is associated with a global atom through which you get at the instance of TWinControl. See FindControl, as @Remy answered, also check the implementation of ObjectFromHWnd and RM_GetObjectInstance message handling in TWinControl.DefaultHandler.Kristiankristiansand
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.Kristiankristiansand
@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.Steffaniesteffen
@DavidHeffernan But of course pointers can pass across module boundaries. What misinformation are you talking about?Kristiankristiansand
I'm talking about windows form handles, not objects or pointers.Steffaniesteffen
@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. :)Blind
@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. :)Blind
@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.Fructuous
@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.Fructuous
@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.Blind
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)Steffaniesteffen
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?Steffaniesteffen
@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.Steffaniesteffen
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.Fructuous
B
5

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.

Blind answered 19/11, 2011 at 0:26 Comment(8)
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.Steffaniesteffen
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 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.)Blind
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.Blind
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.Steffaniesteffen
Jerry, seems like a much better idea. :)Blind
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.Thrower
@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.Blind
@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.Steffaniesteffen
T
4

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.
Thrower answered 18/11, 2011 at 23:10 Comment(3)
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?Steffaniesteffen
Added more to the question, if what I need isn't possibleSteffaniesteffen
@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.Blind

© 2022 - 2024 — McMap. All rights reserved.