Link to home
Start Free TrialLog in
Avatar of riceman0
riceman0

asked on

my COM Server freezes when the client is busy


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.
Avatar of _ys_
_ys_

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.
Avatar of riceman0

ASKER


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.


> 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.

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.
I'll get back to go in 24 hours with something substantial. Ok?

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.




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.  
ASKER CERTIFIED SOLUTION
Avatar of _ys_
_ys_

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial

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.






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
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.

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.
> 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.
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.