Solved

my COM Server freezes when the client is busy

Posted on 2004-07-31
14
341 Views
Last Modified: 2013-11-25

Hi.  I am new with COM, but have come far enough to create a Visual C++ COM client and a VB COM Server that communicate through the proper interfaces and event sinks.  (Yes, I'm proud of that.  VB is great for front-end development and I still get to use Visual C++ as a powerful back-end.)

The problem is that when when the C++ client shows a modal dialog, the VB front-end COM server is frozen as well, as if it is a child window of the C++ back-end.  Why is this??  Is this because they are both in the same "thread", or in the same window parent-child lineage (I don't really know whether either of these are true for me or not), or what?

I have not studied up on multithreading, the apartment model, etc.,  within COM, that would be quite a research project   I wanted to get an opinion on whether this is the right direction to go first.

Incidentally here is the COM command I'm using in the C++ engine to instantiate my VB server front-end.  I'm including this just in case it's as simple as changing one of these parameters (I lifted this from some working sample code, do not 100% understand all the parameters yet).

CoCreateInstance(CLSID_myHMIClass, NULL, CLSCTX_SERVER, IID__myHMIClass, (void**)&m_pHMIObj)

Thanks very much in advance.
0
Comment
Question by:riceman0
  • 8
  • 6
14 Comments
 
LVL 9

Expert Comment

by:_ys_
ID: 11702566
COM servers should never instantiate their own UIs. What if the server happened to be on a different physical machine - who's ever going to interact with the UI ?

That said, the simplest COM apartments are non re-entrant, unless you multi thread things. So yes, your single thread is holding things up. However, there is a COM interface IMessageFilter that allows such situations to be overcome. But alas your client is VB, so it's not exposed and hence not an option.

Two solutions:
1) If your COM server really needs a UI, implement it as an ActiveX control, and hide/show it as necessary. At least your VB client won't hang-up.

2) Expose properties on your COM server (as opposed to a UI) and mock up a VB UI, reading/setting properties of the COM server. This allows any client to re-use it (DOS, Windows, whatever). It can even reside on a different physical machine.
0
 

Author Comment

by:riceman0
ID: 11707154

Not sure if I was clear: it's my "client" that is instantiating my COM server.  And my client is VC++, not VB.

Perhaps I'm misusing my terms: my "client" is VC++, which is an application that is started and monitors/commands hardware, collects/stores data and does some number-crunching.  My "server" is a VB program (an ActiveX EXE) that is created (when the user desires a UI) from the VC++ client using the CoCreateInstance command above.  Does that change any part of your answer?

The two entities will always be on the same machine, one is installed along with the other.  The only reason we are using COM is to allow VB and VC++ to communicate seamlessly.  As I said, we like the power and VC++ and versatility of VB and saw it as a way to get them working together.  It sounds like you think this is not a good use of the technology... even if they are on the same machine?

I need to read up on threading within the COM model.  In the meantime, can you provide a few more details on how to implement the two solutions you describe?  They both sound reasonable but I'm not sure how to get started.


0
 
LVL 9

Expert Comment

by:_ys_
ID: 11713772
> it's my "client" that is instantiating my COM server.  And my client is VC++, not VB.
My mistake. It was the phrase 'VB front end' that threw me off.

> It sounds like you think this is not a good use of the technology
Ooops. Again my mistake. I advocate COM. It saddens me to see .NET transform COM into a VB-esque facade.

Back to the question in hand ...

> My "server" is a VB program (an ActiveX EXE)
Good. As this is a seperate EXE (assuming it not ActiveX Document EXE - it isn't - right) threading is not as issue for you. Seperate EXE's cannot share threads ... ever!

I created my own sample projects to reproduce your problem, and it didn't hang up - unless I inititiated the C++ GUI from within a VB method - if you follow me. In this scenario then yes, VB will appear to hang up. It's apartment is non re-entrant. It'll wait for the method call to finish, which waits on C++ to return control, currently displaying a modal dialog.

Is this your situation ??

> My "server" is a VB program (an ActiveX EXE) that is created (when the user desires a UI) from the VC++ client [...]
--- When the user desires a UI ---
Why is VC++ required to displayed a modal dialog? Would it not be easier to have VB display this dialog as well? Properties and values could be requested from the C++ COM instance.
0
 

Author Comment

by:riceman0
ID: 11726122

Could you explain "initiated the C++ GUI from withing a VB method"?  Again, the way I'm using the terms: the GUI is a VB server which is being initiated from my C++ client using CoCreateInstance.

I ran it again, VB still seems to hang up.  Do you know how I can change to a "re-entrant" apartment??

We could make it so that C++ is not required to display a modal dialog, however we get the same effect on the VB side when we do serious number crunching on the C side.   What we need to do is separate them completely.   What steps would you take to implement the UI as an ActiveX control and incorporate it into VC++ (as you suggested above?

If you could answer that or give me the steps you took to create those sample projects -- new project, type of project, wizard used or code added, we'll call this a good answer, thanks.  Or if you could even send me the sample code that appears to work that would be even more useful.  A working project is worth five readings of the documentation, in my opinion.  

Thanks.
0
 
LVL 9

Expert Comment

by:_ys_
ID: 11726387
I'll get back to go in 24 hours with something substantial. Ok?
0
 

Author Comment

by:riceman0
ID: 11728248

Ok, great.  You've already earned the points.  I'll accept your next reply.

But let me replace my last batch of questions (although sample code would still be useful) with this one:

On a similar project the VB GUI COM server can be created and used to start an analysis in the VC++ engine/client.  During this period there are lots of messages being sent to keep the displayed data updated.  The VB HMI Server(a VB ActiveX EXE) doesn't completely lock up, but occasionally when I try to shift focus to the HMI application window I get the following messagebox:

"An action could not be completed because a
component (ENGINE) is not responding.  Choose
"Switch To" to activate the component and correct
the problem."

(buttons at the bottom: "Switch To..." "Retry" "Cancel")

If I keep clicking I will eventually get focus, so I guess I'm catching it in the middle of some message processing.  But this is very annoying to a user.

On the VB side I just expose an event as so:
Public Event TalkHMItoPM(ByVal iDevice As Integer, ByVal iCommand As Integer, ByVal iAction As Integer, ByVal iData As Integer, ByVal dData As Double)
which is called by the C++ engine every few iterations to send data to be displayed.  

Do you know what that dialog box really means (in COM- and thread-speak), and if there is anything I can do to streamline this interface?

Like I said, you've earned the points and I will "Accept" your next reply but any thoughts you have on this slightly new situation would be very much appreciated.



0
 

Author Comment

by:riceman0
ID: 11728595

Thought: maybe this has to has to do with the apartment being "non re-entrant" like you said.  I'll follow that string but any thoughts you have on how to make it "re-rentrant" would be appreciated.  
0
Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

 
LVL 9

Accepted Solution

by:
_ys_ earned 200 total points
ID: 11733939
>Do you know what that dialog box really means (in COM- and thread-speak)
The objects is STA (Single Threaded Apartment). It's currently in the middle of a method call, and didn't complete it in time to handle your request.

For MFC applications it's a case of not replying to this message in a reasonable amount of time. It's just too busy doing other things.

>and if there is anything I can do to streamline this interface?
Sorry, but no. This dialog is part of the OS.

An implementation of IMessageFilter would allow you to say, literally, wait a second ... or two ...

> give me the steps you took to create those sample projects
-- VB --
Create a new ActiveX EXE.
Add a Form
Add a Class
- Class_Initialize creates and displays the VB form
- Class_Terminate unloads the form

-- C++ --
------------------>-----------------
#import "VBActiveEXE.exe" named_guids no_namespace
#include "windows.h"

int main ( )
{
    HRESULT hr = CoInitialize (0);
    if (SUCCEEDED (hr))
    {
        _VbClass *pVbClass = 0;
        if (SUCCEEDED (hr = CoCreateInstance (CLSID__VbClass, CLSCTX_SERVER, 0, IID__VbClass,
                                                 reinterpret_cast<void**>(&pVbClass) )))
        {
            sleep (10000); // 10 seconds
            MessageBox ( 0, "Hang-up", "Hang-up", MB_OK || MB_ICONQUESTION || MB_SYSTEMMODAL );
            pVbClass->Release ( );
        }

        CoUninitialize ( );
    }
    return 0;
}
------------------>-----------------

With this sample the VB UI remain fully responsive.

>  how to make it "re-rentrant"
Within VB open your project settings. There's an option for thread pooling - defaults to 1. Increase this number.

HTH,
_ys_
0
 

Author Comment

by:riceman0
ID: 11737974

I've accepted your answer because it was very helpful.  But as it turned out, as is often the case it only helped us narrow down the problem.  Hopefully you will see this, but otherwise I'll start a new topic.

The VB server/HMI and C++ client/engine are perfectly independent: after CoCreateInstance I can run a modal dialog box and it locks up the engine but the HMI is free as a bird, which is perfect.

The problem starts when the user tries to start an "analysis" from the HMI.  We have written a 'TalkToEngine' event on the VB side (with some message parameters) and  implemented an event sink class in our C engine which traps the events (derived from IDispEventSimpleImpl).  The HMI can pass a message through this event which causes our event sink to call StartAnalysis.  StartAnalysis does a lot of number crunching and sends messages to the HMI.

When StartAnalysis is called from some location in the engine, it works fine; the analysis starts and the HMI (if instantiated) recevies and displays messages.  However, when StartAnalysis is called from the event sink (or from a method called from the event sink, et al.), then the HMI becomes very sluggish, it doesn't respond to mouse events, you occasionally get the "Switch to..." dialog box, until the analysis is complete.

I suppose this is because the call to StartAnalysis is not allowing the Event Sink to complete its processing.  Apparently the event sink must run its course and release something to allow the two objects to operate independently again.  The problem is that I don't know how invoke the correct routines in the engine from the event sink *without* tying up the event sink until those routines are done.

I actually tried setting a flag that is picked up by a timer outside of the event sink (inelegant and low-performance) and starting a new thread to run StartAnalysis (but for some reason the new thread can't talk back to the VB/Server/HMI), but both were problematic.  There *has* to be a simpler way to do this.






0
 
LVL 9

Expert Comment

by:_ys_
ID: 11750956
Lengthy event handling has always been a concern with COM developers. COM+ introduced Queued Components, designed to alleviate this - event processing is seperated from where it is raised. For your situation another technology is the last thing you need.

What you could do, and this was prevalent prior to COM+, is spawn off a thread from within the event handler. This allows the event processing to think it's finished - and your VB UI won't hang-up waiting for it.

HRESULT CEngine::Event_StartAnalysis ( )
{
    DWORD dwThreadId;
    HANDLE hThread = CreateThread (0, 0, AnalysisThread,
                                                              this,  // pass pointer to self to the new thread
                                                              0, &dwThreadId);

    if (hThread) CloseHandle (hThread);
}
// control reverts to VB here, but the event analysis continues on the independant thread

DWORD __stdcall AnalysisThread (void* pInfo)
{
    CEngine *pEngine = reinterpret_cast<CEngine*>(pInfo);  // this was pointer to self
    pEngine->StartAnalysis ( );

    return 0;
}

This does mean that your engine would have to be coded as multi-threaded.

If you want to be able to access your VB component a little more work is required. For a start calls to CoInitialize[Ex]/CoUninitialize are absolutely required. If your C++ engine stores a private copy of your VB component's interface you'll to place your VB component's pointer into the GIT - it'll make your multi-threaded life much easier to code against. The CComGITPtr helper class prevails in this realm:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/vclrfCComGITPtr.asp
0
 
LVL 9

Expert Comment

by:_ys_
ID: 11784448
Was wondering if you had a chance to read through my suggestion.

I understand you've created a follow-up question, but as RomMod suggested, might be better to delete and re-create the question for visibility of others - that's also why I posted here and not there.
0
 

Author Comment

by:riceman0
ID: 11797385

I did, and we have actually just fixed it, following the path you suggested.  However we ended up in a slightly different place.  I am interested in what you think:

We spawned a thread from the event which allows the event to close out, like you suggested (although would using some DCOM commands be an improvement)?  I learned a little bit about apartment models (started with the index page in my COM book for GITs), STAs, MTAs, etc.  At first I tried to run CoInitializeEx on both the event-spawned thread and the engine main process with the MULTITHREADED parameter -- I thought this would force both to end up in the same MTA, so they would both have access to the interface with the HMI.  This didn't work; my leading theory was that the problem was that my VB front end is in a different process with its own apartments.

So I read about GITs, and it said that each process has its own GIT, so I wondered whether my HMI EXE and my engine EXE (now multi-threaded) could access the same GIT.  Instead I tried some example code (out of Don Box's Essential COM) that uses CreateStreamOnHGlobal, CoMarshalInterface on the main thread, and CreateStreamOnHGlobal and CoUnmarshalInterface on the event thread to pass the interface to the HMI.  So I trial-and-errored it, which is not ideal but I seem (with your hel) to have gotten the job done.  

Any thoughts about this solution?  Anything further I can/should learn from this?

Regarding my other topic, I was told it had fallen down the list and probably wouldn't be answered unless I re-asked it.  But at this point it doesn't matter.  I'll delete it pretty soon.
0
 
LVL 9

Expert Comment

by:_ys_
ID: 11799326
> would using some DCOM commands be an improvement
The calls would still be synchronous, unless you started creating your own proxy/stub pairs - and you don't want to go there. What you have is fine, legal and it works.

In some respects you're already doing DCOM - communication between two seperate processes using COMs SCM.

> my leading theory was that the problem was that my VB front end is in a different process with its own apartments.
Every apartment is process bound - it exists within a single process. Furthermore each process can only have one MTA. So if two threads within the same process call CoInitializeEx ( COINIT_MULTITHREADED ) then they will reside within the same MTA.

>At first I tried to run CoInitializeEx on both the event-spawned thread and the engine main process with the MULTITHREADED parameter -- I thought this would force both to end up in the same MTA, so they would both have access to the interface with the HMI
As pointed out, as long as these two threads reside within the same process, this should work.

HRESULT CEngine::Event_StartAnalysis ( )
{
    if (S_OK == CoInitializeEx ( COINIT_MULTITHREADED ))
    {
        IWhatever *pWhatever = 0;
        if (SUCCEEDED (CoCreateInstance (CLSID_Whatever, 0, CLSCTX_SERVER,
                                               IID_IWhatever, reinterpret_cast<void**>(&pWhatever))))
        {
            DWORD dwThreadId;
            pWhatever->AddRef ( );
            HANDLE hThread = CreateThread (0, 0, AnalysisThread,
                                                              pWhatever,  // pass pointer to the new thread
                                                              0, &dwThreadId);

            if (hThread) CloseHandle (hThread);

            pWhatever->Release ( );
        }

        CoUninitialize ( );
    }
}

DWORD __stdcall AnalysisThread (void* pInfo)
{
    if (S_OK == CoInitializeEx ( COINIT_MULTITHREADED ))
    {
        IWhatever *pWhatever = reinterpret_cast<IWhatever*>(pInfo);  // provided interface pointer
        pWhatever->Release ( );

        CoUninitialize ( );
    }
    return 0;
}

Verify that the call to CoInitializeEx succeeds. If it does then I don't really see a reason for your failure.

CoMarshalInterface etc. will do the job but I don't see them as being necessary. MTAs were created to avoid exactly this situation.
0
 
LVL 9

Expert Comment

by:_ys_
ID: 11808392
Quick correction on terminology:

Regarding re-rentrant and non re-entrant. Technically not true. All apartments are re-entrant. I should have used the terms blocking and non blocking instead.

STAs are blocking. They block method calls until the current method call has completed. And they are re-entrant as the call stack could potentially re-enter the same object[/apartment] multiple times.

MTAs are non blocking. Method calls and threads have a free reign. Any synchronising has to be performed by the object implementor.
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Suggested Solutions

Introduction This article is a continuation of the C/C++ Visual Studio Express debugger series. Part 1 provided a quick start guide in using the debugger. Part 2 focused on additional topics in breakpoints. As your assignments become a little more …
For most people, the WrapPanel seems like a magic when they switch from WinForms to WPF. Most of us will think that the code that is used to write a control like that would be difficult. However, most of the work is done by the WPF engine, and the W…
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now