Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to contain a ActiveX exe server within my program 2

Status
Not open for further replies.

vb5prgrmr

Programmer
Jul 5, 2002
3,622
US
Okay, my container application is or has a standard interface. i.e. logo, menu system, and toolbar. When a new exe is detected in the same directory, I shell it with a /r so that it can register what it needs within the settings database. (not just any exe...) These settings tell the container app what menus to display and what buttons to display when it is shelled. Now, when this dropin exe is shelled for operation, it is passed the hwnd of a MDI child form that is maximized in the MDIParent form that is the container and it is passed the hwnd of a textbox that is not visible (the form is a dummy blank form...). Now, this dropin exe uses the SetParent API to affix itself to the child form and then uses sendmessage to the hidden text box to pass its hidden text boxes hwnd so further communication can commence between the two as needed. All works well but this is such a cludge. I know there has to be a better way, but as I said it works well and allows me to add functionality that I had never thought of when the container was created.

Now, over the past few days I have been rereading all my books on activex dll's, controls, and exe's and I quote from the book titled "Advanced Microsoft Visual Basic 6.0 Second Edition" Published by Microsoft Press and I guess authored by "The Mandelbrot Set" with a forward by Sean Alexander.

Okay, the quote is... "Controls (UserControls, that is) can be brought entirely inside an application or compiled to OCXs. (Servers have the potential to be brought inside an application, also)." ...,which can be found on pg 483, chapter 11 (Mixing Languages with visual studio).

Okay,... So a server is an ActiveX exe, as I understand it from reading seven chapters from three different books and countless web examples, but I cannot find an example or set of instructions that will allow me to contain an activex exe within my container program as I presently do.

After reviewing the licensing scheme at MS about UC's I cannot use a UC in the way I want.

So, the question is. How to make an activex exe's form become a child or be contained by another exe/activex exe as the objective is... To allow a customer to download the container program and the options that they want. Then if they want other options, all they would need to do is download the new exe into the same directory and it would be picked up by the container program through its existing methods (see above if you have forgotten already )

Once again, this is something like the MMC (now renamed to just Computer Management) but please do not get confused with thinking I want to know how to create a program for the MMC as I have that documentation around here somewhere already.

So, any hints or clues???
 
An ActiveX EXE can be a DCOM server but in general I believe in COM terms server can refer to any COM DLL, OCX, or EXE (i.e. ActiveX EXE). The term server is used to distinguish it from the client that is using this server.

Instead of the unweildy-sounding approach you've taken, I believe that MMC loads snap-ins as ActiveX DLLs. It sounds as if it uses (effectively) CreateObject() calls and assigns these objects to generic "snap-in" reference variables that conform to a standardized "snap-in" interface.


To do this kind of thing yourself then, you'd design your own interface(s) first. These would have "generic" properties, methods, and events that your client (container) program uses to communicate with snap-ins (servers). These snap-ins would contain classes that Implement the interface(s).

While I haven't tried it, the VB6 documentation indirectly suggests that you can use Implements in a Form or a UserControl as well as in a Class module. I seldom have Forms in a VB6 DLL except as custom dialogs where needed. So I have no idea whether you might get away with making an MDI Child Form as an interface. That sounds almost ideal for your purpose though.


To avoid writing MIDL and creating a TLB for your interface(s) you might get away with creating an ActiveX DLL Project and then stubbing out all of your Forms, UserControls, and Classes with the necessary Public members. Compile and register this, and you have something you can reference and Implement in each of your snap-ins, each of which being a uniquely fleshed out ActiveX DLL Project.

In your container/client you'd have code like:
Code:
Private SnapinUI As MyAppSnapins.UIForm
:
:
Set SnapinUI = CreateObject(strSpecificSnapinProgID)
SnapinUI.Show vbModeless, Me


These are all just top-of-the-head suggestions though. I haven't done this.

It depends on what you can do with MDI children in a DLL, and success depends on creating a generic enough interface or interfaces for communication with all snap-ins you want to implement. There is also the issue of events vs. object arrays to consider, if events are required.
 
Umm... to clarify, MyAppSnapins.UIForm in the air-code above is a reference to the interface and not a specific concrete implementation of the class. The actual implementations are what gets "grabbed" using CreateObject() calls, and there might be many of these.
 
I follow most of that but there is a reason why I want, if I can, to use an activex exe...

If you remember the "registration" process that I presently use, I would like to keep this, or some other sort of process/interface that will allow the exe to be "dropped-in" or else I would have to continually modify the container/client and redistribute it for every expansion/idea/process that is added.

 
Well, that's where loading by ProgID comes in.

These are just String values, and you'd have a different one for each of your various "drop-in" DLLs. Or maybe multiples for each depending on how many interfaces you use.

That's why I tried to clarify things in my second post above. You only need to compile against the generic DLL or TLB where your interfaces are defined. Switching among the actual DLLs is done at runtime, using different ProgIDs similar to when you use late binding.

To add a new DLL you just need an updated INI file or something that the container can load the String values from when it starts.
 
Okay, I understand that part,... But what I don't understand is... All the examples I have, in my three books, show forms outside of the calling application (even the coffee example from MS) and so I am pretty much clueless along these lines.

(Now, here is where I have to admit that 99.98% of my programming experience with VB has been with standard exe projects and thus I know I have been missing out on a lot of VB’s power but what I do know has served me well.)

Now, I could use a dll and perhaps an expendable exe to do the loading of the settings database to get those strings for instantiation of the dll, but, still hoping for an exe as a simple shellexecute with a /r (i.e. as the way I do it now) would still work… OR, I could alter the setup1.exe project (not the first time) to have it load the settings database with those strings but from what you are saying is that my hopes for an exe are just that…


 
This /r is just a command line parameter that your program reads and acts on, I assume.

I wasn't saying my suggestions are the answer for you. I was just trying to suggest a more normal way to do this, "normal" in terms of things VB6 is set up to provide for you.

Your approach seems as if it has to go behind the back of Windows to accomplish what you're after. That isn't a criticism, I guess it just seems difficult to support and potentially hazardous. How are you grabbing those hWnd values, how do you keep things from falling apart if two copies of your application are running, etc. are questions I'd worry about with window spelunking techniques.


If you are using MDI techniques I'm not sure why you have to share information about the childrens' menus, buttons, etc. It seems like a child Form will just show its own UI and be happy. The MDI mechanism should handle things like menu-merging, right?

Perhaps I simply misunderstand the full extent of what your application needs to be able to do.
 
After some quick experimentation I have to conclude I'm probably wrong about my suggestions working.

I wasn't able to get a child Form to load into an MDI parent Form in the manner I suggested.


So to get back on track with your original question... wasn't your SetParent call handling this for you?
 
Yes it is and yes those are the problems I normally face, but with some work arounds (hacks you might want to say) I have overcome some of those problems...

>The title bar of the MDI parent does not show that it is the active window when a form from the dynamic-link library (DLL) has focus.

Main problem

>A menu from a form in the DLL does not merge with a menu of the MDI parent form

Solved: by use of the "settings" database

>When a form from the DLL is maximized, the title bar of the DLL form is still visible

Solved: no caption, no controlbox

>When MDI child forms that are part of the MDI project receive focus, they do not appear on top of a form from the DLL

Solved: Append (use SetParent) to dummy child form and not MDI Parent form

>A form from the DLL can be moved over a toolbar that exists in the MDI project

Solved: By the appending of the form to a dummy child form

>When a DLL form is moved out of the visible area of the MDI form, scroll bars do not appear on the MDI parent

Solved: By making dummy mdi child form maximized (although have not tried cascading windows to see if this is a factor...)

Okay, I have recreated a couple of test programs and both are standard exe projects.

First project up is the child application that I want to be "contained". Start new standard exe project and add a button to form1 (command1). Change project name to "ProjectChildApp", change the following Form1 properties...
Name = formChildApp
Caption = ""
ControlBox = False
Optional WindowState = Maximized

and add the following code...
Code:
Option Explicit

Private Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long

Private Sub Form_Load()
'normally within a sub main proc
If Command <> "" Then
  SetParent Me.hWnd, CLng(Command)
  Me.WindowState = vbMaximized
Else
  'code here for not showing application and ending application
End If
End Sub

Private Sub Command1_Click()
MsgBox "If you see this, then you see the child application running in a parent container"
'now say that this is a close button for this application...
'we would then send a message to the parent or container application's hidden text box
'to tell it we are ending so it can unload its dummy form we have attached ourselves to.
'however, if the container application closes its dummy form, then we would recieve a
'message to close, either from a sendmessage communication to our hidden text box or by
'a sendmessage/wm_close
End Sub
Now, save and then compile this project into a temporary directory.

Next up is the "container" Program... Start a new standard exe project and rename the project to MDIContainerApp. Add a MDI form and then change the following properties of Form1.
Name = formDummy
Caption = ""
ControlBox = False
MDIChild = True
WindowState = Maximized

Then add the following code to the MDI Parent form.
Code:
Option Explicit

Private Const PROCESS_TERMINATE = &H1
Private Const SW_SHOWNORMAL = 1

Private Declare Function OpenProcess Lib "kernel32.dll" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function TerminateProcess Lib "kernel32" (ByVal hProcess As Long, ByVal uExitCode As Long) As Long

Dim PID As Long

Private Sub MDIForm_Load()

formDummy.Show

PID = Shell(App.Path & "\ProjectChildApp.exe " & formDummy.hwnd)

End Sub

Public Sub KillChild()
Dim HandleToProcess As Long
HandleToProcess = OpenProcess(PROCESS_TERMINATE, 0, PID)
If HandleToProcess Then
  TerminateProcess HandleToProcess, 1&
  CloseHandle HandleToProcess
End If
End Sub

Private Sub MDIForm_Unload(Cancel As Integer)
KillChild
End Sub

Run>>>


Okay, now this is just the bare bones to get the "child application" to be contained by the "Parent application or container program". There is a whole lot more for getting interprocess communication going. Like the dummy form has an invisible text box on it and in the shell, its handle would be passed as a second arguement. Then, the "child" application would use that handle to send a message (wm_settext) to the text box with the handle of its hidden text box. (Both applications use change event to "parse" these communications).

Then there is the issue of resizing the MDI parent form. When this happens, the dummy form resizes as it should, but the child form or containd application does not and so the dummy form contains code to resize the contained application.

Also, I actually use sendmessage with wm_close instead of above code but since it is so much shorter, I used the above for demo. This allows me to intercept the message via query unload and if I need to, I can send a message back to the dummy form to turn cancel to true and display a message box if necessary (Think Settings.mdb once again).

On the other hand, if the child closes itself, it will send a message to the dummy form's invisible text box with the appropriate message to tell the dummy form to close itself and do whatever it has to do to maintain the uniform look of the container application.

Then of course, if you look at the "child" application to be contained and its handling of the command object, well that is just for demo as the actual thing is a whole lot more complicated. i.e. yes, intercepting the /r parameter so it can "register" itself in the settings database.

Note: A long time ago in this forum, someone else had the same probem with this method and they also wanted to get rid of the "greying" out of the containers title bar and at the time I thought I had it solved (I think 98 had just come out), but as you can see, I have not. So that is why I'm wanting to change over to COM/ActiveX if I can.


But, it is looking like I will be stuck with what I have unless someone can pull a rabbit out of the hidden and therefore not well known hat... :(

 
Opps, I should have said...

Save container app in same directory and then Run>>>

 
Okay,... if it comes down to that this cannot be done via com, well then, I just might have a solution to the last problem...

SetWindowRgn to cut off the title bar
A flat picture box where I can append my own system menu, draw a fake title bar in, and create a controlbox
Then another picturebox to create a menu system with

Problems with this route...
Wow, a whole lot of code!!!
Figuring out just when to make the fake titlebar disabled.
hooking and subclassing to intercept windows messages and menu messages
and probable a half dozen other things I have not thought of yet...
 
I haven't gone to look into what MMC actually does but it doesn't seem to attempt things on quite this level. It almost feels more like the MMC "shell" contains several pre-built "views" that can be selected for display, and each snap-in gets passed a predefined object that is used to display information and accept input.

Looking back it seems as if this kind of thing is a rarity outside of a few products. There is the MMC and there are IDEs like Visual Basic itself, Visual Studio (including releases since 6.0), Eclipse, and so on. But even Office doesn't attempt such a thing for switching around among individual programs.

I wonder if the lack of a frequent need is why there is no obvious pre-built plumbing in Windows for doing these sorts of things. In the end, doesn't this duplicate much of what the Windows Shell is doing for you anyway?
 
I guess what I'm saying is that I'm out of ideas right now.

Perhaps somebody else can add more productive comments?
 
Well dilettante,

It looks like I'm going to be stuck with this by hook and crook way. I was kind of hoping that someone would give me that missing piece of info but from the looks of it, there is a good reason why it is missing. :)

Well have a star for your effort my good person and have a safe profitable/safe/joyous year.

 
I'm wondering if there is any mileage in hooking the WM_CREATE message and then modifying CREATESTRUCT so that the created window is a 'genuine' MDI child.
 
Oooohhh, Aaaahhh,... Hmmmm...

Okay, so then the CREATESTRUCT lpCreateParams would contain a pointer to the MDICREATESTRUCT UDT, but then...

Okay, getting confused here because correct me if I am wrong. Even if I shell even with a hide parameter, the window is "technically" created. Is it not? So then I would not be able to intercept the WM_CREATE message and modify the CREATESTRUCT with a pointer to a MDICREATESTRUCT. So that means, I would have to hook the system right?

I have one thing to say about that. EEEEKKK! :)

Okay, maybe I need just a little more of a hint or direction but this does sound like less work than creating a system menu, faking a title bar, and creating a menu attached to a picture box and all the hooking that will entail...

Well, I've got some investigating to do and strogm, if you have any more pointers in the direction I need to go, I'm all [bigears] :)

Thanks
 
Thanks for the star, for effort at least as you say.

I wish I had more experience doing this sort of thing but I haven't needed it myself.

Sorry about the wild goose chase.
 
Your welcome dilettante and I don't think it was a wild goose chase as I had already been down that road, or at least part of it.

So, where do we stand now... Still trying to figure out how to either use a COM server within a container program or as strongm pointed out, hooking the other process to intercept the WM_CREATE message and modify the CREATESTRUCT's lpCreateParams with a pointer to a MDICREATESTRUCT UDT.

Okay strongm, (if you are still reading this thread) from what I have read since my last post, is that VB is not really made for creating system wide hooks. In fact impossible if I read things right as it can only do it from a standard dll. So is that right or are my eyes getting crosseyed already?

Next, every example (but one, which I will get to in a minute) is for hooking your own programs messages or things like the mouse and keyboard.

The exception, which uses a dll to hook into another process and if I skimmed things correctly, this is a major leap forward in my quest as per strongm's suggestion.

So strongm, please correct me if I am wrong on this...

shell with vbhide
use returned PID with openprocess to return handle to other process (Should I use CloseProcess at this point?)
then use code from link to hook the messages
after that, use an api to show the window
catch the WM_CREATE and modify it as noted above
Keep hook in place to listen for WM_DESTROY message and upon that, remove the hook

So strongm, do I have the process anywhere near correct?

(I think I can, I think I can, I think I can...) :)

Mike
 
>In fact impossible if I read things right as it can only do it from a standard dll.

Not quite correct. Some of the hooks (the low-level mouse and keyboard hooks, the Journaling hook and the CBT hook) are systemwide even from within VB)

SO I'd consider using the CBT hook - however, I should point out that I'm only speculating on this as a solution. I've not actually tried it in anger (and a brief experiment attempted during a quite moment earlier today didn't work out, so this may not be feasible ...)
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top