Author Topic: Non-DDE IPC?  (Read 26963 times)

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Non-DDE IPC?
« on: March 30, 2006, 02:15:35 pm »
(actually this should go into "Bounties :lol:)

To get rid of DDE and all the problems caused by it once and for all, we might want to think about a solution that does without DDE again.

Under Linux, we could probably implement the functionality that we need very easily and very efficiently using msgsnd() and msgrcv(). Just take everything passed in argv[] and send them one by one. The other end reads them one by one from a poller thread and posts a wxCommandEvent to the application object. Should be like 10-15 lines of code.

If Microsoft supported named pipes for Windows98, we could have used those in the exact same way. But of course that would be too easy, named pipes only work on NT4/2000/XP... so we have to do something less elegant.

I believe something involving a mutex, a semaphore, and a shared memory area would work.

The idea is this:
  • call the instance checking function as early as possible (before initialising anything)
  • this may forward the commandline and terminate the application, or the application goes on like normal
  • the first instance of Code::Blocks creates a mutex, a semaphore, and a shared memory page
  • the primary instance also starts a poll thread which blocks in the semaphore
  • all other instances know they are not the first instance from the mutex's error code
  • in a non-primary instance, all argv arguments are written to a shared memory area one by one, and the semaphore is posted every time (that'll weak up the poller)
  • the poller reads from the shared memory one by one and blocks again

This scheme misses one vital thing yet: we either need to append items to a linked list inside the shared memory area (lock mutex!), or we have to make sure only exactly one message can be passed at a time. Otherwise, we will almost certainly overwrite messages before the poller gets them, sooner or later.
I think a linked list is probably too complicated (we would need more excessive memory management, too).
We are probably off better by waiting for a second semaphore (and thus blocking) until the poller thread posts the semaphore (it does that once in every iteration of its loop).
That way, exactly one commandline argument at a time can be forwarded to the primary process (no matter how many processes are being started simultaneously).
The order of appearance is not guaranteed by this, but we don't need that feature. Also, it is not overly efficient, because it generates n messages for n commandline arguments. However, it is simple. Simple things are good.

Here is some code which I quickly typed together to illustrate (it will probably not compile, and it does not prevent overwriting earlier arguments, but the general idea should become clear):
Code
class PollingThread : public wxThread //  should actually be a singleton
{
    HANDLE sem;
    bool abort;
    void* shared;
   
    PollingThread(HANDLE s, void* shm) : abort(false), shared(shm)
    {
        sem = DuplicateHandle(s);
    };
   
    virtual int Entry()
    {
        for(;;)
        {
            WaitForSingleObject(sem, INFINITE);
            if(abort)
                break;
               
            int *code = (int*) shared;
            char *argv = (char*) ( ((int*) shared) +1 );
           
            wxCommandEvent e(*code);
            e.SetString(cbC2U(argv));
            wxTheApp->PostMessage(e);
        }
        return 0;
    };
   
    void NotifyAppShutdown() // have to call this at shutdown, or the app will hang
    {
        abort = true;
        ReleaseSemaphore(sem, 1,  NULL);
    };
};


class InstanceChecker
{
    HANDLE  hMutex;
    HANDLE  hSemaphore;
    bool primary;
   
    void*   shared;
   
public:

    InstanceChecker()
    {
        hMutex = CreateMutex (NULL, FALSE, TEXT("Code::Blocks ipc mutex"));
        primary = GetLastError() == ERROR_ALREADY_EXISTS; // who am I?
       
        hSemaphore = CreateSemaphore(NULL, 0, 1, TEXT("Code::Blocks ipc semaphore"));
       
        if(primary)
        {
            HANDLE hFile = CreateFile ("/temp_path/codeblocks.ipc", GENERIC_READ | GENERIC_WRITE,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
                                       
            if (hFile == INVALID_HANDLE_VALUE)
                ; // uh... what to do now?
               
            SetFilePointer(hFile, 512, 0, FILE_BEGIN); // make sure we have 512 bytes of storage
            SetEndOfFile(hFile);
           
            HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
            shared = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
           
            CloseHandle(hFile);
            CloseHandle(hMapping);
           
            PollingThread t = new PollingThread(hSemaphore, shared);
            t->Run();
        }
        else
        {
            // Give up time slice, for the unlikely case that two processes are started at almost exactly the same time at first run.
            // In that case, we should allow the other process to create the shared memory file.
            // Two full slices are ~100 ms, that should be more than enough, even in the worst of cases.
            Sleep(0);
            Sleep(0);
            HANDLE hFile = CreateFile ("/temp_path/codeblocks.ipc", GENERIC_READ | GENERIC_WRITE,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                                       
            if (hFile == INVALID_HANDLE_VALUE)
                ; // might want to retry in this case
               
            HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
            shared = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
           
            CloseHandle(hFile);
            CloseHandle(hMapping);
        }
    };
   
    ~InstanceChecker()
    {
        if(shared)
            UnmapViewOfFile(shared);
        // Don't delete file. For one reason, it is small and being reused.
        // Also, it avoids the possible race condition with several processes being launched simultaneously.
        CloseHandle(hSemaphore);
        CloseHandle(hMutex);
    }
   
    void DoCheck(int argc, char* argv)
    {
        if (primary)
            return;   // continue normal operation
           
        if(argc == 0)
        {
            PostOtherInstance(ACTIVATE, wxEmptyString);
        }
        else
        {
            for(unsigned int i = 0; i < argc; ++i)
                PostOtherInstance(OPEN, cbC2U(argv[i]));
        }
        wxTheApp->ExitMainLoop();
    };
   
    void PostOtherInstance(int event, const wxString& arg)
    {
        int *code = (int*) shared;
        char *argv = (char*) ( ((int*) shared) +1 );
       
        *code = event;
        strcpy(argv, arg);
        ReleaseSemaphore(hSemaphore, 1,  NULL);
    };
}

Unluckily, I cannot spend the necessary time to implement this properly at the present time, as  I have every minute until 30 April already planned out. :(

Now here's the question:
Is anybody (preferrably someone experienced in both IPC and Windows programming) volunteering to follow that idea and make it work? ;)
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

takeshimiya

  • Guest
Re: Non-DDE IPC?
« Reply #1 on: March 30, 2006, 02:32:39 pm »
Never used IPC, but the wx sample 'IPC' using TCP/IP isn't enough?
Or the problems of doing IPC with TCP/IP is the firewall thing?

Offline Der Meister

  • Regular
  • ***
  • Posts: 307
Re: Non-DDE IPC?
« Reply #2 on: March 30, 2006, 02:35:08 pm »
Does any desktop-firewall block tcp/ip traffic from and to 127.0.0.1?
Real Programmers don't comment their code. If it was hard to write, it should be hard to understand.
Real Programmers don't write in BASIC. Actually, no programmers write in BASIC, after the age of 12.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #3 on: March 30, 2006, 02:38:44 pm »
Does any desktop-firewall block tcp/ip traffic from and to 127.0.0.1?
Yes, ZoneAlarm shows big, red warning alerts like "Code::Blocks is trying to act as a server". That does not precisely look nice.
We don't want to give a newbie the impression that we're running a spambot or a backdoor server or anything, right?
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

takeshimiya

  • Guest
Re: Non-DDE IPC?
« Reply #4 on: March 30, 2006, 02:41:41 pm »
So, the only problem with TCP/IP is the firewall thing?
Or there are other problems attached with it?

(Just wondering for personal usage for doing IPC).
« Last Edit: March 30, 2006, 02:43:36 pm by Takeshi Miya »

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Non-DDE IPC?
« Reply #5 on: March 30, 2006, 02:44:44 pm »
What are the DDE problems that caused this post?
Can someone enlighten me? Thomas?
Be patient!
This bug will be fixed soon...

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #6 on: March 30, 2006, 03:06:03 pm »
What are the DDE problems that caused this post?
Can someone enlighten me? Thomas?

The only remaining DDE problems that I've managed to come across is when default.conf gets deleted (but associations are already set) and when DDE is turned off on the command line.  The former is caused by the compiler selection dialog and the latter is caused because the DDE part of the associations still exist in the registry so Windows thinks Code::Blocks still uses DDE.  At least both of these issues were present last time I checked (2 days ago).

By the way, wouldn't it be easier to use WM_COPYDATA?  Or is doing this using wxWidgets a PITA?  The window can be easily found using FindWindow() if you know the class name and a dummy (hidden) window could be set up before the main wxWidgets frame is up and running to catch messages that may happen while the program is initializing.  The dummy window could be destroyed as soon as the wxWidgets frame is up.  That last bit depends on whether or not you can specify a window class name in wxWidgets and add your own WindowProc() style code to handle the WM_COPYDATA message.

I'll go have a look in the wxWidgets docs for that last bit to see if it is supported for the Windows wxWidgets.

Cheers
« Last Edit: March 30, 2006, 03:08:30 pm by TheNullinator »

sethjackson

  • Guest
Re: Non-DDE IPC?
« Reply #7 on: March 30, 2006, 03:07:23 pm »
Does any desktop-firewall block tcp/ip traffic from and to 127.0.0.1?

Yes mine does, and it asks me wether to permit traffic or not. I agree with thomas. It gives a negative impression.
« Last Edit: August 12, 2006, 04:29:29 pm by sethjackson »

Offline Der Meister

  • Regular
  • ***
  • Posts: 307
Re: Non-DDE IPC?
« Reply #8 on: March 30, 2006, 03:15:44 pm »
I wonder why a desktop firewall blocks traffic that only uses 127.0.0.1 as source and destination. This doesn't make any sense as neither this traffic can go out of this machine nor can any traffic from outside come to this process.
But anyway, as these damn firewall at least report such traffic it is really not a good solution.
Real Programmers don't comment their code. If it was hard to write, it should be hard to understand.
Real Programmers don't write in BASIC. Actually, no programmers write in BASIC, after the age of 12.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #9 on: March 30, 2006, 03:33:58 pm »
What are the DDE problems that caused this post?
Can someone enlighten me? Thomas?
Well, we're having one issue after the other (the broken batch builds being the last in a long row) and somehow every problem seems to be related in some way to either DDE directly or a change which was necessary to make DDE work. Maybe that's just my impression, but it looks to me that way. :lol:

I mean, this is just a proposal, we don't need go that way if there are any objections. But isn't it at least worth thinking about? I believe we might get a solution which is a lot less painful (and could implement the same thing for Linux with minimal changes, too). We would also need less registry tampering :)

Quote
I wonder why a desktop firewall blocks traffic that only uses 127.0.0.1 as source and destination.
That is what I call SIS ("self-important software"). Firewalls and Antivirus software does that to stress its own importance. I suspect that antivirus scans are being made extra slow on purpose, too, for the same reason. You have to see that it is working hard. If there is never a network alert and a scans of your entire hard disk finishes in 5 minutes, then it can't be any good.
If you weren't being told that the software just saved your life again every 5 minutes, then you would not be willing to spend money on a product that makes your PC 30% slower...

"Warning: Spooler subsystem is trying to access the internet: 127.0.0.1. View detailled threat information?"
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

Offline mandrav

  • Project Leader
  • Administrator
  • Lives here!
  • *****
  • Posts: 4315
    • Code::Blocks IDE
Re: Non-DDE IPC?
« Reply #10 on: March 30, 2006, 03:38:58 pm »
What are the DDE problems that caused this post?
Can someone enlighten me? Thomas?
Well, we're having one issue after the other (the broken batch builds being the last in a long row) and somehow every problem seems to be related in some way to either DDE directly or a change which was necessary to make DDE work. Maybe that's just my impression, but it looks to me that way. :lol:

These issues are not related to DDE at all...
Anyhow, writing a replacement for DDE would take great amounts of time (which we don't have) and testing under all supported platforms.
It's not worth it.
We will just fix this batch-build issue and go on with other, important, things ;)
Be patient!
This bug will be fixed soon...

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #11 on: March 30, 2006, 03:43:21 pm »
Well, we're having one issue after the other (the broken batch builds being the last in a long row) and somehow every problem seems to be related in some way to either DDE directly or a change which was necessary to make DDE work. Maybe that's just my impression, but it looks to me that way. :lol:

Also it was DDE that was causing projects to be loaded twice because Windows was sending the file on the command line and through DDE.  That was hidden away until the project file version was updated from v1.3 to v1.4.

However, ditching it may not be worth the effort.  But on the other hand, I'm all for working on different ways to do things (plenty of free time, ya'know ;) (or should that be :(? ;))).
« Last Edit: March 30, 2006, 03:47:58 pm by TheNullinator »

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #12 on: April 01, 2006, 05:27:39 pm »
I've just finished knocking together a WM_COPYDATA IPC system.  It runs great under Windows XP SP2 in unicode mode.  I'm just running the ANSI wxWidgets+Code::Blocks build now to test it on my Windows ME system in the morning.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #13 on: April 04, 2006, 01:15:13 pm »
Another peculiarity appears in recent builds (since about 5-6 days) for me.

Header files are not opened any more. Neither when double-clicked, nor when run from the console. The IDE does start, so the file associations are good, but nothing happens. Source and resource files work just fine.
Although I don't know why this happens, I feel tempted to attribute this to the DDE workarounds, too ;)

Anyone else experiencing this?

"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

Offline tiwag

  • Developer
  • Lives here!
  • *****
  • Posts: 1196
  • sailing away ...
    • tiwag.cb
Re: Non-DDE IPC?
« Reply #14 on: April 04, 2006, 03:07:19 pm »
i don't have this issue with svn rev 2285
my header files open fine for editing

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #15 on: April 06, 2006, 04:57:26 pm »
I'm putting finishing touches on the WM_COPYDATA IPC system now.  I'll finish them tomorrow hopefully.  It works fine under Windows ME (ANSI) and Windows XP (ANSI/unicode).  It works good over a network aswell.  It is fully supportive of multiple instances of Code::Blocks and sends files to the last active window so you can choose which one you want to receive a file.

There are two problems I've come across so far.  They are:

  • Sometimes a message is displayed saying the config file couldn't be saved.  This happens only when wildly double clicking on C::B registered files (in particular non-projects/workspaces) in Windows Explorer.
  • If the first instance is still loading and another codeblocks.exe is executed with a file argument and the first instance crashes while loading a plugin, the codeblocks.exe with the file argument hangs.

The first issue seems to have been fixed with some timed retry mods to CfgMgrBldr::Close().  I'm going to find some other way around that one though.  I've got an idea about the second one, too.

I'll upload a diff file when those issues are fixed (and any others that may arise) so if you guys want to use it, then that's cool.  Otherwise, I had fun doing it anyway.   :)

Cheers

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #16 on: April 06, 2006, 07:52:43 pm »
Quote
The first issue seems to have been fixed with some timed retry mods to CfgMgrBldr::Close().
Eeeeeeeeh... please do not ever tamper with that function. This is absolutely no go.

First of all, it is not recommended to use CfgMgrBldr for anything at all. The actual reason why CfgMgrBldr has such a stupid name is to give you a hint that this is not thought to be used in normal operation. Treat it as if it was a private internal class of Manager (although it is not, for simplicity).

Also, calling Close() will result in undefined behaviour, and will leak memory - use Free() instead. However, it is best if you don't use Free() either, since that, too, may result in undefined behaviour.

The configuration manager and all pointer references to it are, by definition, valid from its first invokation to the destruction of the Manager singleton (which happens immediately before the application is terminated). Parts of the SDK rely on this fact and may crash if you destroy ConfigManager.

The same or similar is true for other managers. The shutdown procedure follows a well-defined order of destruction which ensures that certain things are reliably availabler at given times.

For example, a plugin developer can rely blindly on the availability and validity of ScriptingManager, ProjectManager, EditorManager, PersonalityManager, MacrosManager, UserVariableManager, MessageManager, and ConfigManager (no null-pointer checks required!) for the entire lifetime of a plugin.
Also, this ensures that for example EditorManager is valid for the entire lifetime of ProjectManager, because the former is used by the latter.
If you free managers in an uncontrolled manner, these contraints are no longer effective and very, very nasty things (which are impossible to debug) may happen.

What can be done is, you can simply do all your IPC before ConfigManager is instantiated for the first time. In that case, nothing is saved to the config.
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #17 on: April 06, 2006, 11:36:42 pm »
Quote
The first issue seems to have been fixed with some timed retry mods to CfgMgrBldr::Close().
Eeeeeeeeh... please do not ever tamper with that function. This is absolutely no go.

Yeah, I thought it was a bit of an out-of-the-way function.  That's why I'm looking for a different way.  The original version of the IPC code needed the config file to check /environment/single_instance but I rewrote a lot of it yesterday to support mutliple instances so I can move everything to before ConfigManager is instantiated.

Also, calling Close() will result in undefined behaviour, and will leak memory - use Free() instead. However, it is best if you don't use Free() either, since that, too, may result in undefined behaviour.

I never called Close(), it got called naturally through CfgMgrBldr::~CfgMgrBldr() when the applicated terminated.  It just happened to happen at the same time in two different instances of Code::Blocks that were sending IPC messages.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #18 on: April 06, 2006, 11:53:26 pm »
Quote
I never called Close(), it got called naturally through CfgMgrBldr::~CfgMgrBldr() when the applicated terminated.  It just happened to happen at the same time in two different instances of Code::Blocks that were sending IPC messages.
Ah ok, that is something we'll have to live with then, because I am not using a Windows mutex for portability (only way we could avoid that). wxWidgets mutexes don't work between processes... :(
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #19 on: April 07, 2006, 12:49:51 am »
Ah ok, that is something we'll have to live with then, because I am not using a Windows mutex for portability (only way we could avoid that). wxWidgets mutexes don't work between processes... :(

Yeah, bummer about that.  :(  I got a bit excited when I saw wxMutex in the class list of the wx docs, but then I found out they were local to the process.

I've fixed those two issues.  I did some really frantic clicking and opening of multiple instances all at the same time without any issue.  Also tested the plugin crash issue and it worked properly.

I'm building the ANSI version now to test on Windows ME and if all goes well, I'll post the diff.

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #20 on: April 07, 2006, 06:04:21 am »
I've attached a 7z archive of the diff file for the DDE replacement.  I've tested it quite a bit on both Windows ME and XP (both ANSI and unicode) and haven't come across any more problems.

I had to modify associations.cpp to detect and remove DDE information from the association keys in the registry or Windows thinks Code::Blocks still handles DDE messages.  The only other modified files are app.cpp and app.h.

[attachment deleted by admin]

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #21 on: April 07, 2006, 10:02:30 am »
Tested. First, it tossed a "cannot find file" error at me every time. After manually removing all the stale DDE keys from the registry (the automatic uninstall does not seem to catch them all), that error was gone.

Although writing the window ID to the registry gives me a bit of a pain in the stomach, it seems to work perfectly for some files.
For some? Yes, unluckily... While all source files go into one instance just fine, XRC files and headers are opened in a separate instance. Worse yet, if I select 10 XRC files and select "open" from Explorer's context menu, 10 instances of Code::Blocks launch in parallel, each opening one file...

So... too early for thumbs up yet, but it looks like a start :)
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #22 on: April 07, 2006, 10:35:00 am »
Tested. First, it tossed a "cannot find file" error at me every time. After manually removing all the stale DDE keys from the registry (the automatic uninstall does not seem to catch them all), that error was gone.

Hmm, I'll look into that a bit more.

Although writing the window ID to the registry gives me a bit of a pain in the stomach, it seems to work perfectly for some files.

Yeah, that is a rather dodgy thing to be doing  :)  but it did the job at the time.  I'm looking into other ways of achieving the same result.

For some? Yes, unluckily... While all source files go into one instance just fine, XRC files and headers are opened in a separate instance.

I don't have any issues with header files but do experience some with XRCs now you mention it.  I'll look into that one more, too.

Worse yet, if I select 10 XRC files and select "open" from Explorer's context menu, 10 instances of Code::Blocks launch in parallel, each opening one file...

Oh man, I didn't even think of testing that.   :roll:  :)

So... too early for thumbs up yet, but it looks like a start :)

A start it is.  :)  And thanks for the pointers to issues you encountered I hadn't come across.  :)  I'll go have a look at them and see what I can do.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #23 on: April 07, 2006, 12:51:57 pm »
I still think that using a memory mapped file should work fine (could get rid of the registry stuff, and makes it cross-platform-able, too).
It is possible to open a named MMF and query the error message if it was already created (works the same way as a named mutex!).

There is even a complete wrapper class available to handle memory mapped files and shared memory areas: http://www.naughter.com/memmap.html
It may not be 100% what you need and might need some tweaking, but I guess it is quite good for a start.

One would also need a kind of linked list or queue that does not allocate memory on the heap, but is happy to live inside a dedicated memory area, as well as a mutex to protect access to it. That's probably the hardest part.

Then, you could start up and open the file mapping (or a named shared memory section, doesn't matter). If it does *not* exist, then you are the first instance. Write your window handle *here* (not into the registry).

If it *does* exist, you are (obviously) not the first one to use it, so you lock the mutex, append your data to the linked list, and release the mutex.
Then fire off a notification to the window ID which you can find in the shared memory and quit.

Instead of using window IDs and sending messages, one could also use a sepmaphore to signal that new data has arrived, this is somewhat nicer.
The main instance of Code::Blocks could either launch a separate thread that blocks on the semaphore, or simply query the semaphore's state (non-blocking) from inside OnIdle. The latter solution would delay opening files slightly if the PC is under heavy load, but that is not necessarily a bad thing. Actually, it is even a very desirable feature, since this avoids adding more and more load when the machine is already thrashing anyway.

If using things like mmap, mutexes, and semaphores, we could implement the very same functionality for Windows and Linux and only need to change a few lines in each.
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #24 on: April 07, 2006, 01:07:48 pm »
Hmm, sounds pretty good.  :)  I'll have a look into it now.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #25 on: April 07, 2006, 04:23:59 pm »
Actually this code snippet is almost exactly what we need (forget the DLL stuff):
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/using_shared_memory_in_a_dynamic_link_library.asp

Darn, lol... should have looked at MSDN earlier.  8)
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #26 on: April 07, 2006, 04:44:26 pm »
The memory mapped file IPC system is going pretty well.  About up to the WM_COPYDATA system's usefulness.  It is sending the files to the last active window quite well.  The custum queue that resides in the shared memory (64K) seems to be holding up, too.

There are some issues when the currently active window is closed and you open a file from Explorer before activating one of the other windows.  The file gets stuck in the queue because noone can claim it.  I'm adding a check now to see if the window a file wanted to be opened in is destroyed and if so it will be adopted by the next active window.

Code::Blocks also likes to hang after being closed (only instances that had the main window).  It needs a bit of help from the Task Manager to be on it's merry way.  It doesn't use any CPU time, it just never terminates.

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #27 on: April 07, 2006, 04:46:07 pm »
Actually this code snippet is almost exactly what we need (forget the DLL stuff):
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/using_shared_memory_in_a_dynamic_link_library.asp

Darn, lol... should have looked at MSDN earlier.  8)

Pretty good example, that one.  :)  Quite a good idea, too.

Offline thomas

  • Administrator
  • Lives here!
  • *****
  • Posts: 3979
Re: Non-DDE IPC?
« Reply #28 on: April 07, 2006, 07:58:06 pm »
Quote
There are some issues when the currently active window is closed and you open a file from Explorer before activating one of the other windows.  The file gets stuck in the queue because noone can claim it.
This is no problem at all if you use the semaphore approach instead of messaging a window. The worst thing to happen is you might lose one "open file" event, but for this to happen, you would have to hit the window's close box and open the next file within, say 0.1 seconds or so... :lol:

  • When the application starts, the shared memory area either exists, or it does not.
  • If it does not exist, you start as usual. Some time later, during your application's OnIdle function, the semaphore is queried regularly (non-blocking). It costs very few CPU cycles to do that. At some point, its status will be signalled, so you know that you have data waiting for you.
  • If the SHM does already exist, you know at least one other process must be running. Thus, you lock the mutex, append to the list, release the mutex, and signal the semaphore. Then you exit the application (closing the file mapping, of course). You don't bother to create a window or initialise the application in any way. This will probably take only a fraction of a second.
  • When the last instance has terminated, all file mappings are closed, so there is no SHM any more. If you used a memory mapped file (not just system VM), then the OS will save the data to disk, otherwise it will discard the data. The next instance that starts will know that it is the primary instance.
  • The only possible pitfall is that the primary instance could be closed just at the very same time as another instance is still busy writing to the SHM. You have to click very fast to achieve this.
    Anyway, what will happen? Nothing:
    • The worst thing to happen is that the application does not open these files. So what, you just closed it! Of course you would not expect the application to open more files if you close it, so that is perfectly good behaviour.
    • Still, if this is a problem, you can simply post the semaphore once during startup for good. There is no harm in doing so (except wasting ~200 CPU cycles once).
    • After starting up, the application will eventually get into OnIdle, and will see that the semaphore is signalled (because you just posted it). It will therefore look at the list and find any data that may be there (or find nothing, which is fine, too).
« Last Edit: April 07, 2006, 08:01:27 pm by thomas »
"We should forget about small efficiencies, say about 97% of the time: Premature quotation is the root of public humiliation."

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #29 on: April 08, 2006, 04:17:20 pm »
This is no problem at all if you use the semaphore approach instead of messaging a window.

I'm not using any window messages or WindowProcs any more nor the registry for storing the current window handle (now stored in the shared memory as you suggested).  :)  The problem was fixed when I fixed the hang on exit for an instance with a window.

This is the basic flow of things in OnInit:
  • Create 64K named shared memory.
  • Map the memory.  If this is the first instance, initialize some data in the memory
  • Create the semaphore initially unsignalled.
  • Create the mutex which is initially owned.
  • If this is not the first instance and files were passed on the command line, wait for a window to exist (handle stored at the start of the shared memory) and make it the foreground window (if done just before loading the file in OnIdle, it sometimes doesn't work on XP and never works on ME) using mutex protection, add the files to the queue using mutex protection increasing the semaphore count for each file, and then terminate.
  • Create the main frame and register it as the current window to receive new files.
  • Release the mutex so files can be added to the queue.

In OnIdle the state of the semaphore is checked and if signalled and a file exists in the queue for this instance's window or can be taken from a no longer existing window (can only be done by activating this instance's window), remove it from the queue and open it.  If no file existed for this window, the semaphore count is kept correct.

It (ANSI build) seems to work quite well under both XP and ME at the moment.  I'm yet to do a unicode build for testing on XP.  More tests and code tidy-ups planned for tomorrow morning.  I'll post a diff when satisfied that all issues are sorted out on my two Windows boxes.
« Last Edit: April 08, 2006, 04:27:49 pm by TheNullinator »

DJMaze

  • Guest
Re: Non-DDE IPC?
« Reply #30 on: April 23, 2006, 01:13:42 am »
Ehm i was wondering why use shared memory when the message handler failed?

It is pretty easy to create a message queue with mutexes.
Code
int main(int argc, char** argv)
{
HANDLE Mutex = CreateMutex(NULL, false, _T("CodeBlocks"));
if (GetLastError() != 0) {
HWND hwndCB = 0;
while (!hwndCB) {
hwndCB = FindWindowEx(NULL, NULL, _T("CodeBlocks"), _T("CodeBlocks"));
}
// now send the commands to the handler
SendMessage();
return 0;
} else {
CreateWindow("CodeBlocks","CodeBlocks",....);
}
// now all CB stuff
CloseHandle(Mutex);
}

The "CodeBlocks" window is invisible and receives the messages and puts each command inside a list for processing.

Shared memory in combination with a queue is also fine. For example your 64k shared memory block will be a list of strings.
char **cmd;

The first *cmd will contain the argc so that each consecutive call will add data to the end. (don't forget to lock the memory while editing it)
After that send a message to trigger the handler.

The handler will then shift the elements off the beginning of the array in combination with memmove()

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #31 on: May 16, 2006, 06:14:38 am »
Hi,

Sorry for disappearing for a month and a bit.  I had a few health issues.

I rewrote most of the code this morning and last night and fixed a few good bugs in the process.  Here is a list of current issues I know about with the new code.

  • Sometimes the active window is not registered correctly as the current window so files are opened in the previously active window.
  • The window is never brought to the foreground after a file has been opened under Windows ME -- but the taskbar button flashes.
  • Sometimes under Windows ME a message about the system being low on memory is displayed after during the opening of a selection of about 30 files or so.  Also happens on repeated openings of a selection of about 8 files.  System Monitor didn't report a lack of memory.  :?  This issue doesn't occur if you repeatedly open a single file at a time.

I have placed the IPC code in a new class in the files ipc.h and ipc.cpp.  It should be easy enough to modify the code to support other operating systems.

Cheers

[attachment deleted by admin]

TheNullinator

  • Guest
Re: Non-DDE IPC?
« Reply #32 on: May 16, 2006, 06:18:52 am »
@DJMaze

I had considered taking that approach earlier on, but it seemed to me, personally, a tad untidy after I'd implemented a bit of it, though that may have just been my way of going about it.  I think Thomas' idea of using shared memory is very clean and probably easier to port.

Cheers
« Last Edit: May 16, 2006, 06:21:51 am by TheNullinator »