-1

Note to the reviewer: Please sorry for my English, I don't speak it well. But all Information's should be reproduced. If you have any questions, let me know it. Thanks, Jens

Hello,

This is more a little Article to the "DELPHI" Community there on STACKOVERFLOW, to show, how I solve the Problem during the Days of my efforts, and my Question's here on StackOverflow.

Sometimes, you can faced with the Problem, that you can not get a "Control has no Parent Window" Error Message by Delphi or C++ Builder or both, when you try to use Delphi 7 Desktop Application Component's (like TForm, or TPanel) between Desktop Application Components that will be managed by the C++ Builder 6.0 under/in a Microsoft Windows 11 Developer Environment PC. Before we start: I am not a profesional Programmer for any firm or other individual Persopn. My lovely things are to have fun by programing Computer Application's.
And as such, I don't want Donation's. Please be fair enough, to drop a Donate with little bit Money-Value to the Site-Owner that are linked. You help others, and you together - believe me ;-)

PLEASE NOTE: ALL LINKS, AND INFORMATION THERE ARE FOR TESTS, AND EDUCATION PURPOSES. IT IS NOT ALLOWED, TO MAKE PAID SOFTWARE WITH THE DOWNLOADABLE SOFTWARE. STACKOVERFLOW, AND ME DONT MAKE NO GURANTEES: YOU USE ALL INFORMATION'S ON YOUR OWN RISK !!!

First of all, Delphi-7, and C++ Builder 6.0 was products from BORLAND, before BORLAND was sell. The goal was it, to hold the same look and feel of the Developer IDE (Integrated Development Environment) by using different programing Languages. So you could use Delphi (Pascal), Java or C++ Builder in the same manner, the only Exception was the programing Language.

You can download Delphi-7, and C++ Builder or any other Retro-Software here.
Use the following Links, to Download: Delphi-7, and C++ Builder 6.0.

As a little Limit: these two Software Products comes in 32-Bit CPU-Mode, only. I hope this is not a nogo for you (under Microsoft Windows 10/11 64-Bit).


If you are a real Programmer, you have to deal sometimes with different Programing Languages. And good Application's are programed with more as one High-Level Language. (First to speak about Assembly).

The fact is, if you try to use mixed Programing-Languages-Application's, you stand for the Problem, that each Language comes with her own Memory-Model. So C++ has it's own Memory-Model than Pascal (Delphi). You could compile Delphi Component Code under C++ Builder, but you can not use C++ Builder class members or simple Functions directly from Delphi.

Commonly, each C++ Compiler tends to produce mangeled Function-Name's. This means, that the Function-Name become a post-Name, that indicates the Parameter-Name for the given Parameter-Position.

E.g.: If you have the Function "void foo(void) { ... }" Then the Function-Name foo becomes the extended Name 3fooeV (pseudo-fictiv). The first Number (3) indecates the followed Symbol that comes with three characters, then the Function-Name itself, followed by the Parameter(s) Name List-Name "eV" for "void".

C++ Compilers have a second fact: They produce case sensitive function, and class names.

Other than Pascal/Delphi Compilers: The produce caseinsensitive function, and class names. This means, that you are more flexible under Delphi - you can write the function call: FOO; like: foo; or FOo; ...

In a hex viewer, you can see, that the Compiler produced UpperCase Symbols for the Function-Names, if you have a closer look.

With C++, you have to made more attention. But then, you have binary code, that is faster than it's Delphi Pedant.

To bring all Worlds (Delphi, and C++ Builder) under a common way, we can use wrapper-Function's. This are little Function's in C-Style (without the mangled String's before, and after the Function-Name. I dont want to deep go into this to get the Time short to read this Text here, and to save Ressources :-)


I expected, that you are familar with the Creation of minimal Delphi, and C++ Builder Project's. Else, I would advice you to use the Search-Engine "google.de" to close your knowledge Gap's.

Task: Create a TForm as main application, and place an other "external" TForm or TPanel, that binary data are located in a Windows 10 32-Bit .DLL file.

This sounds on the first look easier as it is. Because, you can not simply use the "mangled" Functions from the C++ Builder .DLL in a Delphi Application. You would be fall, if you try it. If you have luck, some Function calls works, and other's not - then you have a UB = undefined behaivor. Because the application don't know the Memory-Manager's that come with Delphi or BCB (Borland C++ Builder). It will not work properly, if you "damangle" the Function Names and call it, too.

The next Problem occur, whenn you simply use TForm creation Function member's that was shiped with Delphi, you come to the Point, that you initialize 2 different Thread's. And as such, 2 Application/Thread-Window's will be open. This can be confused the User (if you think so, that you have 10 or more such kind of Forms. The C++ Code make it possible, to Hide the internal Frame, and made it away from Task-Bar.

To combine the two different Worlds of Delphi, and C++ (Builder), I created a TForm application in Delphi, while in C++ Builder I created a .DLL project to demonstrate the usage, that is need to avoid "control has no parent window".

Example:

  1. Create a Application TForm Project in Delphi-7,
  2. Create a DLL Project in C++ Builder 6.0,
  3. Create Code, that creates a TForm in C++ Builder,
  4. Create Code, that calls the TForm in C++ Builder,
  5. Test your Application.

Code Example for Point 1:

// the following 3 procedure declaration rest in dbgFrame.dll:
procedure create__MyCppFrame(frm: Integer); stdcall;
  external 'dbgFrame.dll' name 'create__MyCppFrame';

procedure destroy_MyCppFrame; stdcall;
  external 'dbgFrame.dll' name 'destroy_MyCppFrame';

procedure resize__MyCppFrame(frm: Integer); stdcall;
  external 'dbgFrame.dll' name 'resize__MyCppFrame';

// Event-Code
procedure TForm2.FormCreate(Sender: TObject);
begin
  create__MyCppFrame(
    ScrollBox29.Handle,  // your TComponent Control Handle
  );
end;
procedure TForm2.FormResize(Sender: TObject);
begin
  resize__MyCppFrame(ScrollBox29.Handle);
end;
procedure TForm2.FormDestroy(Sender: TObject);
begin
  destroy_MyCppFrame;
end;

Code Example for Point 3:

#include <vcl.h>

#include <Classes.hpp>
#include <Graphics.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Grids.hpp>
#include <ExtCtrls.hpp>
#include <Buttons.hpp>
#include <Dialogs.hpp>
#include <ComCtrls.hpp>

#include "clientForm.h"
#pragma hdrstop
#pragma package(smart_init)
// --------------------------------------------------------------------

#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
        return 1;
}
// --------------------------------------------------------------------
static TForm   * myParentForm    = NULL;
static TPanel  * myPanel_1       = NULL;

class MyOpenFileClass: public TObject
{
public:
  void __fastcall myFormActivate(System::TObject* Sender) {
    ShowWindow(Application->Handle, SW_HIDE);
  }
};
static MyOpenFileClass * myOpenFileClass = NULL;

extern "C"
__declspec(dllexport) void
__stdcall create__MyCppFrame( int ParentForm )
{
  try {
    RECT rect;
    HWND parw = (HWND)ParentForm;
    GetWindowRect(parw,&rect);
    MapWindowPoints(HWND_DESKTOP, GetParent(parw), (LPPOINT)&rect, 2);

    myParentForm = new TForm((HWND)ParentForm);
    myParentForm->OnActivate   = &(myOpenFileClass->myFormActivate);
    myParentForm->BorderStyle  = bsNone;
    myParentForm->Width        = rect.right;
    myParentForm->Height       = rect.bottom;

    myPanel_1  = new TPanel(myParentForm);
    myPanel_1->Parent  = myParentForm;
    myPanel_1->Align   = alLeft;
    myPanel_1->Font    = new TFont();
    myPanel_1->Visible = true;

    myParentForm->Visible = true;

    DWORD dwExStyle = GetWindowLong(Application->Handle, GWL_EXSTYLE);
    dwExStyle      &= ~WS_EX_APPWINDOW ;
    dwExStyle      |=  WS_EX_TOOLWINDOW;

    SetWindowLong(Application->Handle, GWL_EXSTYLE, dwExStyle);
    ShowWindow   (Application->Handle, SW_HIDE);
  }
  catch (Exception &e) {
    AnsiString s1 = "Error in external module: \r\n";
    AnsiString s2 = e.Message;
    AnsiString s3 = s1 + s2;
    ShowMessage(s3);
  }
}

Note to the Reader's: I have give a effort for this Post, but I am too tired to make any changes there. So you can give me Feedback, and I try answer.

If you have other Questions that are related to this Posting, and/or StackOverflow, please don't use my E-Mail, Discuss it here.

Thank you for reading, and watching :-) Jens

2
  • I have no idea what ‘when I try to move a part of the components within the TFrame, I get the message that the Splitter1 (TSplitter that I used for) has no parent window’ is supposed to mean. Commented Oct 20, 2022 at 18:52
  • @user3840170: TSplitter is a component, that comes apart from Delphi/C++ Builder 1.0 from Windows 95, and upwards. It can be use to resize some parts (other visual Components) within TPanel's. TPanels are like Containers, which can be use to tile your application in visual parts. And TFrame's are "parent window" less TForm's, that can be place in all other Application's that support the VCL (Visual Component List) that was formed by BORLAND. But the BORLAND Version of Delphi, and C++ Builder are out-of-date, and as such ... retro : -)
    – Jens
    Commented Oct 21, 2022 at 10:51

2 Answers 2

2

I think this is better suited for stackoverflow ...

Anyway are you sure TCustomFrameClass is not conflicting name? I have seen it before especially in BCB6 that sometimes (especially when Add to project is used for stuff that should be just included) compiler does not throw error on conflicting class or function names and code gets messed up as it uses "randomly" which of the conflicting stuff to use so on some compilations code works and on other (after any minor change in code like adding empty line) does not and vice versa.

I would try to rename the DLL window class to something like TCustomFrameClass_BCB6 just to be sure.

Also you can pass the Handle of parent to BCB6 as its used durring the new so something like:

extern "C" __declspec(dllexport) TCustomFrameClass_BCB6 __stdcall getMyCppFrameClass_BCB6(TObject *parent)
{
  if (Frame1 == NULL) {
      Frame1 = new TFrame1(parent);
  }
  return __classid(TFrame1);
}

Also if Frame1 is global hope its not exported, in such case you should make sure its also not conflicting ...

Not sure which type you need to pass parent as I expect one of these: TObject*,TCustomControl*,HANDLE

In case you want to dock your form to your main app use:

void main_manual_dock(TForm *win,TWinControl *docksite,TAlign align)
    {
    int x,y;
    x=win->Width;
    y=win->Height;
    win->WindowState=wsMinimized;   // hide before dock ocure (must be visible)
    win->Visible=true;
    win->ManualDock(docksite,docksite,align);
    win->Width=x;
    win->Height=y;
    win->WindowState=wsNormal;
    }

just beware if scaling is used on windows it might mess up the window dimensions ...

3
  • Hello @Spekte, I was thinking in both: to publish this Article in Stackoverflow, or there. I wait since month, that I can post Articles, but I close out, and the only way was to post there. So, I saw, You change my bad English - thanks !!! Make it more Sense, to create a Package under C++ ? I have read that is better than a DLL ... ??? Can I give than the Function parameters 1:1 (also form Delphi TObject to C++ TObject, and back ??)
    – Jens
    Commented Oct 16, 2022 at 8:11
  • @Jens I never used packages, ... C++ VCL objects are also written and compiled in Pascal so they should be the same however if you have different version of VCL then problems occur so you should not mix the types (that is why I would use the _BCB6 suffix)
    – Spektre
    Commented Oct 16, 2022 at 11:43
  • 1
    Thank you for the Hint. One of the Problem was, that I mix the 32-Bit Code with 64-Bit Code, and vice versa. BORLAND C++Builder 6 was/is a 32-Bit Application, so I can not use 64-Bit DLL Files directly. For now, I use BCB6 with Delphi 7 - both are 32-Bit, and it seems, the Problem's fly away...
    – Jens
    Commented Jun 3, 2023 at 13:59
1

Okay, I get it done by rewrite the source code. I created a .dll project in the C++ Builder Version 6.0 32-Bit, which contains the following code:

#include <basepch.h>
#include <vcl.h>

#include <Classes.hpp>
#include <Graphics.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Buttons.hpp>
#include <Dialogs.hpp>
#include <ComCtrls.hpp>

#pragma hdrstop
#pragma package(smart_init)
//---------------------------------------------------------------------------

#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
        return 1;
}
//---------------------------------------------------------------------------
static TForm     * myParentForm = NULL;
static TPanel    * myPanel1     = NULL;
static TSplitter * mySplitter   = NULL;

extern "C"
__declspec(dllexport) void
__stdcall create__MyCppFrame ( int ParentForm )
{
  try {
    RECT rect;
    HWND parw = (HWND)ParentForm;
    GetWindowRect(parw,&rect);
    MapWindowPoints(HWND_DESKTOP, GetParent(parw), (LPPOINT)&rect, 2);

    myParentForm = new TForm((HWND)ParentForm);
    myParentForm->BorderStyle = bsNone;
    myParentForm->Width       = rect.right;
    myParentForm->Height      = rect.bottom;

    myPanel1 = new TPanel(myParentForm);
    myPanel1->Parent  = myParentForm;
    myPanel1->Caption = "testPanel";
    myPanel1->Align   = alLeft;
    myPanel1->Font    = new TFont();
    myPanel1->Visible = true;

    mySplitter = new TSplitter(myParentForm);
    mySplitter->Parent    = myParentForm;
    mySplitter->Left      = myPanel1->Left + 2;
    mySplitter->Align     = alLeft;
    mySplitter->Visible   = true;

    myParentForm->Visible = true;
  }
  catch (Exception &e) {
    AnsiString s1 = "Error in external module: \r\n";
    AnsiString s2 = e.Message;
    AnsiString s3 = s1 + s2;
    ShowMessage(s3);
  }
}

extern "C"
__declspec(dllexport) void
__stdcall destroy_MyCppFrame(void)
{
  delete mySplitter;
  delete myPanel1;
  delete myParentForm;
}

with this code, I have a "workaround" about the "Splitter" has no parent window problem.

Tricky thing on this is, to give the DLL-Calling Function a HWND Window Handle. But the Problem is, that the HWND Handle is typed as "void*". And as such, this void* stands in C for a Pointer Type for any kind of data, and can not directly used as Integer.

To "force" a Integer Type to this (Pascal Handle), I "static_cast" this Integer (Pointer) to the HWND type - because void* can be anything you like, since data into the Computer System can anything other data, you should be aware, and have attention, what you do with this data.

It maybe can be a Call to a destruction Function of the OS, which would be worse a word as destructive. And as such, the Application will crash.

In Delphi, I give the target Handle to the .dll, which have the trick, that I create a new TForm with the Pointer HWND (Integer) as Parent.

To mimic the illusion of "no TForm" - I set the BorderStyle to bsNone in the C++ Builder Code. This remove the TForm title bar, and it's border around the client area of the Form.

In Delphi-7, I have the following code fragments:

// the following two external procedures declarations stands global
// in a Delphi interface section of a Unit, which is named you like:
...
procedure create__MyCppFrame(frm: Integer); stdcall; external 'dbgFrame.dll' name 'create__MyCppFrame';
procedure destroy_MyCppFrame; stdcall; external 'dbgFrame.dll'  name 'destroy_MyCppFrame';
...

// I have place the create__MyCppFrame procedure in the FormCreate Event
// Handler of a normal TForm

procedure TForm1.FormCreate(Sender: TObject);
begin
...
  create__MyCppFrame ( ScrollBox29.Handle );
...
end;

// and in the FormDestroy Event-Handler, I call the following procedure
// to clean up the Elements, that I had allocated in the C++ Builder
// source.
// If you don't call this procedure, you will get a AV or other memory
// AV's.

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ...
  destroy_MyCppFrame;
  ...
end;

I hope this helps other programmers, and save time to use this solution, that stand for the same problem.

Thank You for reading.

Extension:

In Delphi-7 - there exists a Event Handler Function of descent classes of TForm named FormResize - TForm1.FormResize(Sender: TObject); which can be set/use to control the behaivour after the TForm form is "resized". Resize stands then for maximize/dimension change of the Form Window onto the Microsoft Windows 10/11 Desktop.

With this Handler, we have the possibility, to change the TForm in the C++ Builder Source Code without many things. We only have to save the Handle (HWND) of the controlling Component, and give it over - to the .DLL Function.
Here is the Delphi-Code Fragment:

    ...
    procedure resize__MyCppFrame(frm: Integer); stdcall; external 'dbgFrame.dll'  name 'resize__MyCppFrame';
    ...
    
    procedure TForm1.FormResize(Sender: TObject);
    begin
      resize__MyCppFrame(ScrollBox29.Handle);
    end;

The C++ Builder Code follow as:

    extern "C"
    __declspec(dllexport) void
    __stdcall resize__MyCppFrame(int ParentForm)
    {
      // the two new lines are sanity check's only
      if ((ParentForm != NULL)
      || ((myParentForm != NULL)))
      {
        RECT rect;
        HWND parw = (HWND)ParentForm;
        GetWindowRect(parw,&rect);
        MapWindowPoints(HWND_DESKTOP, GetParent(parw), (LPPOINT)&rect, 2);
    
        myParentForm->Width  = rect.right;
        myParentForm->Height = rect.bottom;
      }
    }

The crux on all is, that I have allocate a new TForm, a second Form will be display/spawn on the Taskbar of Windows 10/11. At the current Time, I don't know, if this second TForm is displayed in full manner on other Version's of Microsoft Windows. But it should not be a Problem, because, if you click on the main Form, or on the second Form, you will land on the main Form. Please don't ask me for this Reason, and why. This is Windows : -)

No more is need : )
Hope this helps
Jens

0

Not the answer you're looking for? Browse other questions tagged .