Toon posts:

[C++] CoInitialize() in DLL werkt vaag

Pagina: 1
Acties:
  • 101 views sinds 30-01-2008
  • Reageer

Verwijderd

Topicstarter
Momenteel ben ik bezig aan een (DLL) plugin voor LCDInfo, waarmee ik info uit MSN Messenger op mijn LCD kan toveren. Om info uit MSN te kunnen halen, heb je een COM-object nodig genaamd IMessenger. Normaal gesproken zou je dus zoiets doen:
C++:
1
2
3
4
5
6
7
8
IMessenger *msn;
BSTR name;
CoInitialize(NULL);
if (SUCCEEDED(CoCreateInstance(CLSID_Messenger, NULL, CLSCTX_LOCAL_SERVER, IID_IMessenger, reinterpret_cast<LPVOID *>(&msn)))) {
if (SUCCEEDED(msn->get_MySigninName(&name))) {
return name;
}
}


En als ik dat letterlijk zo neerzet, werkt het ook prima. Het probleem is dat deze variabele iedere x milliseconden (laten we zeggen 500) wordt uitgelezen. En er worden nog meer variabelen op deze manier uitgelezen, dus iedere 500msec wordt er een keer of 10 een soortgelijke functie aangeroepen. Aangezien iedere CoCreateInstance() een nieuw msmsgs.exe proces spawnt, kun je op je vingers natellen dat dit gruwelijk veel resources en cpu tijd vreet (bij testen bleek dit ongeveer 50% van een 2.5ghz machine weg te vreten).

Mijn briljante (kuch) oplossing:
We maken een IMessenger *msn; en een bool loaded; in de class ipv lokaal in de functie in die class, roepen dan bij het initialiseren van de class de volgende code aan:
C++:
1
2
CoInitialize(NULL);
loaded = SUCCEEDED(CoCreateInstance(CLSID_Messenger, NULL, CLSCTX_LOCAL_SERVER, IID_IMessenger, reinterpret_cast<LPVOID *>(&msn)));


bij het opvragen van een variabele dit:
C++:
1
2
3
4
5
6
BSTR name;
if (SUCCEEDED(msn->get_mySigninName(&name))) {
return name;
} else {
return "failed";
}


en bij het sluiten dit:
C++:
1
2
3
msn->Release();
delete msn;
CoUnitialize();


Zou moeten werken zou ik denken, maar nee dus. Ik krijg bij msn->get_mySigninName() de foutmelding (HRESULT) "CoInitialize has not been called". Als ik in de variabele-opvraagfunctie bovenaan CoInitialize(NULL); zet en onderaan CoUninitialize();, krijg ik de melding "The
application called an interface that was marshalled for a different thread."

Nu begrijp ik wel dat blijkbaar het initaliseren en deinitialiseren door een andere thread gedaan wordt dan het opvragen van de variabele, maar nu is de vraag hoe ik dit oplos. Ik heb al geprobeerd dmv een "loaded" boolean het aanmaken van het msn object in de opvraagfunctie te plaatsen, en dat gaat (uiteraard) de eerste keer goed, maar resulteert de eerstvolgende keer in de melding "Object is not connected to
server". Ik begin inmiddels een beetje radeloos te worden en heb echt geen idee hoe ik dit op zou moeten lossen... iemand enig idee? Alvast heel erg bedankt :)

[ Voor 9% gewijzigd door Verwijderd op 14-11-2005 22:41 . Reden: overal c++ code van gemaakt ]


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

dat is exact wat ik tijdens heel je betoog zat te denken:

CoInitialize en CoUninitialize moeten 1malig aangeroepen worden door de thread die COM-klassen wil gebruiken.

ik denk dus dat je met 2 verschillende threads zit. Je eerst mogelijkheid had beide functies in 1 thread(functie) staan, blijkbaar wordt bij je 2de voorbeeld het ding aangeroepen door een andere thread (GUI thread?)

edit wat denk je van asynchrone calls? of je threads te synchroniseren (MUTEX)

[ Voor 10% gewijzigd door H!GHGuY op 14-11-2005 19:57 ]

ASSUME makes an ASS out of U and ME


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Maak je bool loaded een Thread Local?

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Verwijderd

Topicstarter
Hrm. Nouja, het zit zo: die dll bevat één dataclass, met de functies onCreate(), onDestroy(), en getValue(). In die laatste moet ik mijn data ophalen. Hoe het precies threadmatig in elkaar zit weet ik niet, die foutmeldingen zijn het enige waar ik iets uit af kan leiden.
bool loaded (en het msn object dan ook uiteraard) thread locals maken klinkt goed, alleen hoe doe je zoiets? Als ik het probeer (met __declspec(thread)) krijg ik dit:
error C2480: 'Data::msn' : 'thread' is only valid for data items of static extent
error C2480: 'Data::loaded' : 'thread' is only valid for data items of static extent
Vergeet niet dat ze in een class staan he :)
En asynchrone calls? Mutexen? Hoe werkt dat :X

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 23-04 23:33

curry684

left part of the evil twins

Asynchrone calls zijn ranzig, dit los je op met een worker thread voor je MSN functionaliteit. Deze kan vervolgens in z'n eigen thread CoInitialize en CoUninitialize uitvoeren. Vervolgens gebruik je een GuardedQueue (een Stack bewaakt door een Mutex) om te communiceren data uit de DLL stubs te queuen voor de messenger thread.

Hoe dat werkt? Zoek CreateMutex eens op in MSDN, examples genoeg :)

edit:
hoe het threadmatig zit weet je trouwens wel uit die foutmeldingen ;) LCDInfo gebruikt blijkbaar meerdere threads en roept de DLL stubs aan 'as required' vanuit de thread die op dat moment even aan het werk is. Grote kans dat ie intern met een threadpool werkt, dan is het al helemaal ridicuul onbetrouwbaar om een andere aanpak dan degene die ik hier voorstel te gebruiken.

[ Voor 33% gewijzigd door curry684 op 14-11-2005 22:54 ]

Professionele website nodig?


Verwijderd

Topicstarter
Oke, ik probeer nu de hele mutex rommel erin te bakken, maar dat gaat ook een beetje moeilijk.
In OnCreate() doe ik dit:
C++:
1
MSNMutex = CreateMutex(NULL, false, "LCDinfoMSNA");

en in getValue():
C++:
1
HANDLE tmpMutex = OpenMutex(NULL, false, "LCDinfoMSNA");


maar tmpMutex is NULL, en GetLastError() geeft 5 (access denied) :|
Ik snap écht niet waar het aan ligt.

-edit-

alweer een stukje verder, door ipv NULL de goeie security parameters mee te geven (SYNCHRONIZE bij OpenMutex, EVENT_MODIFY_STATE bij OpenEvent), maar nu failt WaitForSingleObject(tmpEventComplete, 1000) met een Access Denied. En dat terwijl ik deze zo geopend heb:
C++:
1
HANDLE tmpEventComplete = OpenEvent(EVENT_MODIFY_STATE, false, "LCDinfoMSNC");

[ Voor 35% gewijzigd door Verwijderd op 15-11-2005 02:00 ]


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 23-04 23:33

curry684

left part of the evil twins

Geen named mutex aanmaken, gewoon NULL voor name meegeven en vanaf dat moment altijd dat object gebruiken. Het ding aanmaken kun je het beste in de PROCESS_ATTACH van je DllMain doen.

Nav. je edit: irri dat LCDInfo die security zo strak gebruikt, alhoewel wel verdomd netjes (doet vrijwel geen enkele Windows-developer). Even spelen met de parameters though.

OpenMutex is btw niet nodig, CreateMutex doet automatisch Open als het ding nog niet bestaat. En je hebt SYNCHRONIZE access sowieso nodig voor de Wait-functies.

[ Voor 56% gewijzigd door curry684 op 15-11-2005 02:06 ]

Professionele website nodig?


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 18:39

Tomatoman

Fulltime prutser

curry684 schreef op dinsdag 15 november 2005 @ 02:00:
Geen named mutex aanmaken, gewoon NULL voor name meegeven en vanaf dat moment altijd dat object gebruiken. Het ding aanmaken kun je het beste in de PROCESS_ATTACH van je DllMain doen.
In de PROCESS_ATTACH en PROCESS_DETACH kun je meteen met iedere thread eenmalig CoInitialize() resp. CoUnitialize() aanroepen. Wel oppassen dat je geen functies aanroept die impliciet CoInitialize() aanroepen, waardoor je een dubbel call krijgt. In C++ Builder wordt CoInitialize() voor zover ik weet automatisch aangeroepen als je ActiveX.h includet.

Een goede grap mag vrienden kosten.


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 23-04 23:33

curry684

left part of the evil twins

tomatoman schreef op dinsdag 15 november 2005 @ 02:06:
[...]
In de PROCESS_ATTACH en PROCESS_DETACH kun je meteen met iedere thread eenmalig CoInitialize() resp. CoUnitialize() aanroepen.
Dit kan niet, PROCESS_ATTACH wordt namelijk alleen aangeroepen door de thread die de LoadLibrary uitvoert. THREAD_ATTACH wordt alleen aangeroepen voor threads die na de process attach worden aangemaakt. Je mist dus een collectie threads in theorie. Hele probleem bestaat overigens niet als ie zoals ik voorstel een guarded queue implementeert.

Professionele website nodig?


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 18:39

Tomatoman

Fulltime prutser

curry684 schreef op dinsdag 15 november 2005 @ 02:08:
[...]

Dit kan niet, PROCESS_ATTACH wordt namelijk alleen aangeroepen door de thread die de LoadLibrary uitvoert. THREAD_ATTACH wordt alleen aangeroepen voor threads die na de process attach worden aangemaakt. Je mist dus een collectie threads in theorie.
Hmm ja, daar heb je een goed punt.

Een goede grap mag vrienden kosten.


Verwijderd

Topicstarter
Goed! Ik ben weer een stukje verder. Ik nam aan dat EVENT_MODIFY_STATE automatisch ook SYNCHRONIZE rechten impliceerde, maar dat is dus niet zo, stom van me. Ik zit nu met een timeout bij het wachten, maar i'm working on it ;)

Hoe ik het doe:
Ik spawn een thread in de OnCreate(), met een pointer naar een datastructuur waar in- en outputvariabelen in komen. Vervolgens wacht die op een event. De getValue() neemt de mutex in, vult de datastructuur met data, firet de event, wacht op een 'completed' event, en geeft daarna weer de mutex vrij. Als het goed is blijft alle msn crap dan in één thread hangen. In theorie zou dit moeten werken, toch? :P

-edit-

woohoo! ik heb het ding nu iig zo ver dat in de aparte thread '1337' in een int in de datastructuur wordt gestopt, en deze verschijnt netjes op mn lcd 8)

[ Voor 13% gewijzigd door Verwijderd op 15-11-2005 02:21 ]


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 18:39

Tomatoman

Fulltime prutser

Dat klinkt alsof je het helemaal goed doet. :)

Een goede grap mag vrienden kosten.


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 23-04 23:33

curry684

left part of the evil twins

Ik snap je verhaal niet :P

In pseudocode moet je dit doen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PROCESS_ATTACH:
  CreateEvent(StopThread);
  CreateEvent(StuffInQueue);
  CreateMutex(SyncObj);
  CreateThread(WorkerThread);
  return;

PROCESS_DETACH:
  SetEvent(StopThread);
  WaitForSingleObject(WorkerThread);
  return;

WorkerThread:
  CoInitialize();
  CreateMSN();
  while(WaitForMultipleObjects(StopThread, StuffInQueue, INFINITE) != StopThread)
  do
    WaitForSingleObject(SyncObj);
    SendThingiesToMSN();
    ReleaseMutex(SyncObj);
  end
  DestroyMSN();
  CoUninitialize();
  return;

OnEvent:
  WaitForSingleObject(SyncObj);
  PutStuffInQueue();
  ReleaseMutex(SyncObj);
  SetEvent(StuffInQueue);
  return;

Beide events moeten van het type auto-reset zijn vanzelfsprekend :) De CloseHandles in PROCES_DETACH mag je zelf invullen ;)

[ Voor 7% gewijzigd door curry684 op 15-11-2005 02:31 ]

Professionele website nodig?


Verwijderd

Topicstarter
Wauw, ik heb nog nooit zoveel over C++ geleerd als vandaag, met mutexen, events, threads, vanalles :P
Maargoed. The moment we've all been waiting for: het werkt \o/
Momenteel staat mijn msn nick op mijn lcd te prijken, en het totaal gebruikt 0% cpu, terwijl het iedere 500ms geupdate wordt.

Wat het dus uiteindelijk geworden is:
een MSNThreadData struct:
C++:
1
2
3
4
5
6
7
struct MSNThreadData {
    bool requestIsImage;
    int requestNr;
    BSTR requestedString;
    LCDBitmap requestedBMP;
    bool exitThread;
};


variabelen in de Data class:
C++:
1
2
3
4
5
    MSNThreadData *MSNData;
    HANDLE  MSNMutex;
    HANDLE  MSNEvent;
    HANDLE  MSNEventComplete;
    DWORD   MSNThreadId;


Data::onCreate():
C++:
1
2
3
4
5
6
7
        MSNMutex = CreateMutex(NULL, false, "LCDinfoMSNA");
        MSNData = new MSNThreadData();
        MSNData->exitThread = false;
        MSNEvent = CreateEvent(NULL, true, false, "LCDinfoMSNB");
        MSNEventComplete = CreateEvent(NULL, true, false, "LCDinfoMSNC");

        CreateThread(NULL, 0, MSNThread, MSNData, 0, &MSNThreadId);


Data::onDestroy():
C++:
1
2
3
4
5
6
7
        HANDLE tmpMutex;
        tmpMutex = OpenMutex(SYNCHRONIZE, false, "LCDinfoMSNA");
        if (WaitForSingleObject(tmpMutex, INFINITE) == WAIT_OBJECT_0) {
            MSNData->exitThread = true;         
            SetEvent(MSNEvent);
        }
        ReleaseMutex(tmpMutex);


Data::getValue():
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        HANDLE tmpMutex = OpenMutex(SYNCHRONIZE, false, "LCDinfoMSNA");
        HANDLE tmpEvent = OpenEvent(SYNCHRONIZE | EVENT_MODIFY_STATE, false, "LCDinfoMSNB");
        HANDLE tmpEventComplete = OpenEvent(SYNCHRONIZE | EVENT_MODIFY_STATE, false, "LCDinfoMSNC");
        DWORD result;

        if ((result = WaitForSingleObject(tmpMutex, 1000)) == WAIT_OBJECT_0) {

            // put input data in MSNData struct
            MSNData->requestNr = 0;
            
            SetEvent(tmpEvent);
            if ((result = WaitForSingleObject(tmpEventComplete, 1000)) == WAIT_OBJECT_0) {
                ResetEvent(tmpEventComplete);

                // read output data from MSNData struct
                value = BSTRToString(MSNData->requestedString);
            }
            ReleaseMutex(tmpMutex);
        }


en natuurlijk de thread zelf:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DWORD WINAPI MSNThread(LPVOID lpParam) {
    HANDLE MSNEvent = OpenEvent(SYNCHRONIZE | EVENT_MODIFY_STATE, false, "LCDinfoMSNB");
    HANDLE MSNEventComplete = OpenEvent(SYNCHRONIZE | EVENT_MODIFY_STATE, false, "LCDinfoMSNC");
    MSNThreadData *data = (MSNThreadData *)lpParam;
    
    IMessenger *msn;

    CoInitialize(NULL);
    CoCreateInstance(CLSID_Messenger, NULL, CLSCTX_LOCAL_SERVER, IID_IMessenger, reinterpret_cast<LPVOID *>(&msn));

    while (!data->exitThread) {
        if (WaitForSingleObject(MSNEvent, INFINITE) == WAIT_OBJECT_0) {
            if (!data->requestIsImage) {
                switch(data->requestNr) {
                    case 0:
                        msn->get_MyFriendlyName(&(data->requestedString));
                        break;
                }
            }
            
            ResetEvent(MSNEvent);
            SetEvent(MSNEventComplete);
        } else
            data->exitThread = true;
    }

    CoUninitialize();

    return 0;
}


Hartstikke bedankt, zonder jullie was het niet gelukt _O_

[ Voor 4% gewijzigd door Verwijderd op 15-11-2005 02:33 ]


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 23-04 23:33

curry684

left part of the evil twins

Verwijderd schreef op dinsdag 15 november 2005 @ 02:32:
Wauw, ik heb nog nooit zoveel over C++ geleerd als vandaag, met mutexen, events, threads, vanalles :P
Da's weinig C++ hoor, da's allemaal Win32 door de bocht ;)
Maargoed. The moment we've all been waiting for: het werkt \o/
Cheers, en het ziet er zo snel goed uit :) Ik zie alleen zo snel het risico dat je geen queue gebruikt maar 1 enkel event, daarmee kun je dus dingen overschrijven voor ze getoond zijn. Tevens vergeet je alle CloseHandles, lijpe shit omdat je er een proces mee kunt opblazen. En tot slot heb je al die named kernel objects niet nodig, anonymous voldoet ook en is een stuk efficienter :)

Named kernel objects heb je alleen nodig bij interprocess communication. Zolang je binnen 1 process space zit voldoet anonymous.

Professionele website nodig?


Verwijderd

Topicstarter
Ik geloof dat ik wel snap wat je bedoelt, namelijk dat ik niet allemaal handles hoef te openen, maar er gewoon eentje kan (/moet) gebruiken, en inderdaad, de CloseHandle()s ben ik vergeten :X
Wat ik niet snap is het probleem met dat ik geen queue heb maar 1 variabele die wordt hergebruikt. Zolang de mutex 'bezet' is als er iets aan het gebeuren is met die struct (input schrijven, output schrijven, output lezen) kan er toch niets mee gebeuren? Of zie ik iets over het hoofd?

-edit-
ik zie net dat ik allerlei dingen vergeten ben qua afsluiting.. deletes, msn->Release(); e.d... achja tis al laat :X

[ Voor 13% gewijzigd door Verwijderd op 15-11-2005 02:45 ]


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 18:39

Tomatoman

Fulltime prutser

Waar het om gaat is dat je bijna altijd een handle die je verkrijgt met een functie zoals CreateXXX() uiteindelijk moet vrijgeven met CloseHandle(). Doe je dat niet, dan heb je een geheugenlek. Dat je daar nu niets van merkt is omdat de DLL wordt geünloadet (fijn vernederlandst woord :)), zodat je er niet mee wordt lastiggevallen. Maar eigenlijk zouden er voor al die handles die je gebruikt CloseHandle() aanroepen moeten plaatsvinden (wel in de juiste volgorde natuurlijk). Let op: er zijn uitzonderingen. Zie de documentatie bij de CreateThread/CreateEvent/CreateMutex/Create... functies voor meer uitleg.

Een goede grap mag vrienden kosten.


Verwijderd

Topicstarter
Wat ik me nu wel afvraag: een WaitForSingleObject(), maakt die de mutex automatisch 'bezet'? Ik kan het zogauw niet in de MSDN documentatie vinden, en ik kan het ook niet makkelijk testen, omdat het eigenlijk een multithreaded systeem is dat zich gedraagt als een singlethreaded iets (het is uiteindelijk 1 proces dat iedere 500ms een functie aanroept).

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 18:39

Tomatoman

Fulltime prutser

Verwijderd schreef op dinsdag 15 november 2005 @ 03:00:
Wat ik me nu wel afvraag: een WaitForSingleObject(), maakt die de mutex automatisch 'bezet'?
Ja, dat klopt. Wat jij 'bezet' noemt, heet in de wereld van thread synchronization 'nonsignaled', niet bezet heet signaled. Een mutex wordt heeft als eigenschap dat hij automatisch nonsignaled wordt zodra een thread bezit neemt van de mutex. WaitForSingleObject is een functie die een thread laat wachten, todat het synchronisatie-object waar je op wacht signaled wordt. Zoals gezegd geldt bij een mutex dat als je met WaitForSingleObject op de mutex hebt gewacht, je het bezit over de mutex krijgt zodra deze signaled wordt. Het bezit over de mutex nemen is gelijk aan de mutex nonsignaled maken (= mutex is bezet).

WaitForSingleObject reset sommige synchronisatie-objecten, waaronder mutexen, inderdaad automatisch naar de nonsignaled state. Dat geldt echter niet voor alle synchronisatie-objecten! Een voorbeeld van een synchronisatie-object waarbij dat niet het geval is, is een 'manual-reset event'.

Een goede grap mag vrienden kosten.

Pagina: 1