2010-08-05 15 views
5

ऐसे लंबे प्रश्न के लिए खेद है। बस मैंने अपनी समस्या को हल करने की कोशिश कर कई दिनों बिताए हैं, और मैं थक गया हूं।WinINet एसिंक्रोनस मोड आपदा

मैं WinINet को एक असीमित मोड में उपयोग करने का प्रयास कर रहा हूं। और मुझे कहना होगा ... यह बस पागल है। मैं वास्तव में यह समझ नहीं सकता। यह बहुत सी चीजें करता है, लेकिन दुर्भाग्यवश इसकी एसिंक्रोनस एपीआई इतनी खराब डिजाइन है कि इसे उच्च स्थिरता मांगों के साथ गंभीर अनुप्रयोग में उपयोग नहीं किया जा सकता है।

मेरी समस्या निम्न है: मुझे बहुत सारे HTTP/HTTPS लेन-देन क्रमशः करने की आवश्यकता है, जबकि मुझे अनुरोध पर तुरंत उन्हें निरस्त करने में सक्षम होना चाहिए। INTERNET_FLAG_ASYNC ध्वज के साथ

  1. के माध्यम से प्रारंभ WinInet उपयोग InternetOpen समारोह:

    मैं followig तरह से WinINet उपयोग करने के लिए जा रहा था।

  2. एक वैश्विक कॉलबैक फ़ंक्शन इंस्टॉल करें (InternetSetStatusCallback के माध्यम से)।

अब, लेन-देन है कि मैं क्या करने के लिए सोचा था कि क्या प्रदर्शन करने के लिए:

  1. लेनदेन राज्य का वर्णन विभिन्न सदस्यों के साथ एक प्रति लेनदेन संरचना आवंटित करें।
  2. लेनदेन शुरू करने के लिए InternetOpenUrl पर कॉल करें। एसिंक्रोनस मोड में यह आमतौर पर तुरंत एक त्रुटि के साथ लौटाता है, जो ERROR_IO_PENDING है। इसके पैरामीटर में से एक 'संदर्भ' है, वह मान जो कॉलबैक फ़ंक्शन को पास किया जाएगा। हम इसे प्रति लेनदेन राज्य संरचना में सूचक के लिए सेट करते हैं।
  3. इसके कुछ ही समय बाद वैश्विक कॉलबैक फ़ंक्शन को (0 थ्रेड से) स्थिति INTERNET_STATUS_HANDLE_CREATED कहा जाता है। इस समय हम WinINet सत्र हैंडल को सहेजते हैं।
  4. लेनदेन पूरा होने पर वर्तमान में कॉलबैक फ़ंक्शन INTERNET_STATUS_REQUEST_COMPLETE के साथ बुलाया जाता है। यह हमें लेनदेन पूरा होने वाले मूल धागे को सूचित करने के लिए कुछ अधिसूचना तंत्र (जैसे एक ईवेंट सेट करना) का उपयोग करने की अनुमति देता है।
  5. लेनदेन जारी करने वाले धागे को यह पता चलता है कि यह पूरा हो गया है। फिर यह सफाई करता है: WinINet सत्र संभाल बंद करता है (InternetCloseHandle द्वारा), और राज्य संरचना को हटा देता है।

अभी तक कोई समस्या नहीं है।

निष्पादन के बीच में एक लेनदेन को कैसे रोकें? एक तरीका उचित WinINet हैंडल को बंद करना है। और चूंकि WinINet में InternetAbortXXXX जैसे फ़ंक्शन नहीं हैं - हैंडल को बंद करना एकमात्र तरीका है।

वास्तव में यह काम किया। ऐसा लेनदेन तुरंत ERROR_INTERNET_OPERATION_CANCELLED त्रुटि कोड के साथ पूरा हो जाता है। लेकिन यहाँ सभी समस्याओं शुरू ...

पहले अप्रिय आश्चर्य नहीं है कि मैं का सामना करना पड़ा है कि WinINet लेन-देन भी के बाद यह पहले से ही निरस्त कर दिया गया है के लिए कभी कभी कॉलबैक फ़ंक्शन को लागू करने की आदत है। एमएसडीएन के अनुसार INTERNET_STATUS_HANDLE_CLOSING कॉलबैक फ़ंक्शन का अंतिम आमंत्रण है। लेकिन यह झूठ है।मुझे क्या लगता है कि कभी-कभी एक ही हैंडल के लिए INTERNET_STATUS_REQUEST_COMPLETE अधिसूचना का परिणाम होता है।

मैंने इसे बंद करने से पहले लेनदेन संभाल के लिए कॉलबैक फ़ंक्शन को अक्षम करने का भी प्रयास किया, लेकिन इससे मदद नहीं मिली। ऐसा लगता है कि WinINet का कॉलबैक आमंत्रण तंत्र असीमित है। इसलिए - लेनदेन संभाल बंद होने के बाद भी यह कॉलबैक फ़ंक्शन को कॉल कर सकता है।

यह एक समस्या को लागू करता है: जब तक WinINet कॉलबैक फ़ंक्शन को कॉल कर सकता है - जाहिर है कि मैं लेनदेन राज्य संरचना को मुक्त नहीं कर सकता। लेकिन मुझे कैसे पता चलेगा कि WinINet इसे कॉल करने के लिए बहुत दयालु होगा या नहीं? मैंने जो देखा उससे - कोई स्थिरता नहीं है।

फिर भी मैंने इसे लगभग काम किया है। इसके बजाय अब मैं आवंटित लेनदेन संरचनाओं के वैश्विक मानचित्र (पाठ्यक्रम के महत्वपूर्ण खंड द्वारा संरक्षित) रखता हूं। फिर, कॉलबैक फ़ंक्शन के अंदर मैं सुनिश्चित करता हूं कि लेनदेन वास्तव में मौजूद है और कॉलबैक आमंत्रण की अवधि के लिए इसे लॉक करें।

लेकिन फिर मुझे एक और समस्या मिली है, जिसे मैं अब तक हल नहीं कर सका। यह तब उठता है जब मैं शुरू होने के कुछ ही समय बाद लेनदेन रद्द कर देता हूं।

क्या होता है कि मैं InternetOpenUrl पर कॉल करता हूं, जो ERROR_IO_PENDING त्रुटि कोड देता है। तब मैं प्रतीक्षा करता हूं (आमतौर पर बहुत छोटा) जब तक कॉलबैक फ़ंक्शन INTERNET_STATUS_HANDLE_CREATED अधिसूचना के साथ नहीं कहा जाएगा। फिर - लेनदेन संभाल बचाया जाता है, ताकि अब हमारे पास हैंडल/संसाधन लीक के बिना निरस्त करने का अवसर हो, और हम आगे बढ़ सकते हैं।

मैंने इस पल के ठीक बाद निरस्त करने की कोशिश की। यही है, इसे प्राप्त करने के बाद तुरंत को संभाल लें। अनुमान लगाओ क्या होता है? WinINet क्रैश, अवैध स्मृति पहुंच! और यह कॉलबैक फ़ंक्शन में जो कुछ भी करता है उससे संबंधित नहीं है। कॉलबैक फ़ंक्शन को भी नहीं कहा जाता है, क्रैश WinINet के अंदर कहीं गहरा है।

दूसरी ओर यदि मैं अगली अधिसूचना (जैसे 'नाम हल करने') की प्रतीक्षा करता हूं - आमतौर पर यह काम करता है। लेकिन कभी-कभी दुर्घटनाग्रस्त हो जाती है! यदि मैं हैंडल प्राप्त करने और इसे बंद करने के बीच कुछ न्यूनतम Sleep डालता हूं तो समस्या गायब हो जाती है। लेकिन जाहिर है इसे गंभीर समाधान के रूप में स्वीकार नहीं किया जा सकता है।

यह सब मुझे निष्कर्ष निकालता है: WinINet खराब रूप से डिज़ाइन किया गया है।

  • विशिष्ट सत्र (लेनदेन) के लिए कॉलबैक फ़ंक्शन आमंत्रण के दायरे के बारे में कोई सख्त परिभाषा नहीं है।
  • उस क्षण के बारे में कोई सख्त परिभाषा नहीं है जिसमें से मुझे WinINet हैंडल को बंद करने की अनुमति है।
  • कौन जानता है और क्या?

क्या मैं गलत हूँ? क्या वह कुछ है जिसे मैं समझ नहीं पा रहा हूं? या WinINet बस सुरक्षित रूप से इस्तेमाल नहीं किया जा सकता है?

संपादित करें:

यह कम से कम कोड ब्लॉक कि 2 मुद्दा यह दर्शाता है: दुर्घटना। मैंने सभी त्रुटि प्रबंधन और आदि को हटा दिया है

HINTERNET g_hINetGlobal; 

struct Context 
{ 
    HINTERNET m_hSession; 
    HANDLE m_hEvent; 
}; 

void CALLBACK INetCallback(HINTERNET hInternet, DWORD_PTR dwCtx, DWORD dwStatus, PVOID pInfo, DWORD dwInfo) 
{ 
    if (INTERNET_STATUS_HANDLE_CREATED == dwStatus) 
    { 
     Context* pCtx = (Context*) dwCtx; 
     ASSERT(pCtx && !pCtx->m_hSession); 

     INTERNET_ASYNC_RESULT* pRes = (INTERNET_ASYNC_RESULT*) pInfo; 
     ASSERT(pRes); 
     pCtx->m_hSession = (HINTERNET) pRes->dwResult; 

     VERIFY(SetEvent(pCtx->m_hEvent)); 
    } 
} 

void FlirtWInet() 
{ 
    g_hINetGlobal = InternetOpen(NULL, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC); 
    ASSERT(g_hINetGlobal); 
    InternetSetStatusCallback(g_hINetGlobal, INetCallback); 

    for (int i = 0; i < 100; i++) 
    { 
     Context ctx; 
     ctx.m_hSession = NULL; 
     VERIFY(ctx.m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL)); 

     HINTERNET hSession = InternetOpenUrl(
      g_hINetGlobal, 
      _T("http://ww.google.com"), 
      NULL, 0, 
      INTERNET_FLAG_NO_UI | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD, 
      DWORD_PTR(&ctx)); 

     if (hSession) 
      ctx.m_hSession = hSession; 
     else 
     { 
      ASSERT(ERROR_IO_PENDING == GetLastError()); 
      WaitForSingleObject(ctx.m_hEvent, INFINITE); 
      ASSERT(ctx.m_hSession); 
     } 

     VERIFY(InternetCloseHandle(ctx.m_hSession)); 
     VERIFY(CloseHandle(ctx.m_hEvent)); 

    } 

    VERIFY(InternetCloseHandle(g_hINetGlobal)); 
} 
आमतौर पर पहले/दूसरे यात्रा अनुप्रयोग क्रैश पर

Access violation reading location 0xfeeefeee. 

वर्थ ऊपर दिए गए पते सी ++ (कम से कम MSVC) में लिखे कोड को विशेष अर्थ नहीं है कि नोट करने के लिए: WinINet द्वारा बनाई धागा में से एक एक पहुँच उल्लंघन उत्पन्न करता है। AFAIK जब आप किसी ऑब्जेक्ट को हटाते हैं जिसमें vtable (यानी - वर्चुअल फ़ंक्शन हैं) - यह उपरोक्त पते पर सेट है। ताकि यह पहले से हटाए गए ऑब्जेक्ट के वर्चुअल फ़ंक्शन को कॉल करने का प्रयास हो।

+4

मैं मानता हूं कि इसका उपयोग करना थोड़ा मुश्किल है, लेकिन मुझे एक ही समस्या नहीं दिखाई दे रही है। आपके पहले मुद्दे के लिए, क्या आप सुनिश्चित हैं कि INTERNET_STATUS_HANDLE_CLOSING और INTERNET_STATUS_REQUEST_COMPLETE ** ** ** हैंडल के लिए भेजे गए हैं? मुझे अपने कॉलबैक में अलग-अलग हैंडल मिलते हैं, हालांकि अजीब रूप से REQUEST_COMPLETE मुझे इंटरनेट ओपन से प्राप्त हैंडल देता है। इसके अलावा, आपका दूसरा परिदृश्य मेरे लिए दुर्घटना का कारण नहीं बनता है। मैं विंडोज एक्सपी एसपी 3 का उपयोग कर रहा हूँ। – Luke

+0

ल्यूक, प्रतिक्रिया के लिए धन्यवाद। हां, INTERNET_STATUS_REQUEST_COMPLETE किसी अन्य हैंडल के लिए आता है। लेकिन यह वही 'संदर्भ' मान के साथ आता है, जिसका उपयोग मैं अपने अनुरोध की स्थिति को इंडेंट करने के लिए करता हूं। मतलब - यदि इस स्थिति को हटाने के बाद यह अधिसूचना आती है - तो एक समस्या होगी। लेकिन, जैसा कि मैंने उल्लेख किया है, यह चारों ओर काम किया जा सकता है। दूसरी समस्या बहुत अधिक क्रूर लगती है। और यह अक्सर होता है (हालांकि हमेशा नहीं)। मैं WinINet द्वारा रिपोर्ट किए जाने के बाद सत्र ** हैंडल ** तुरंत बंद करने के बारे में बात कर रहा हूं। फिर, एक छोटी अवधि के बाद, WinINet कोड पहुंच उल्लंघन उत्पन्न करता है। – valdo

+4

मुझे लगता है कि आंतरिक रूप से InternetOpenUrl InternetConnect + HttpOpenRequest (http संसाधनों के लिए) के समतुल्य है, इसलिए कॉलबैक दोनों कनेक्ट हैंडल और अनुरोध हैंडल (किस विशेष घटनाओं के आधार पर) के साथ आक्रमण किया जाता है। चूंकि InternetOpenUrl केवल एक संदर्भ पैरामीटर लेता है, यह दोनों हैंडल के लिए पारित हो जाता है। मैंने कई बार कोशिश की है, लेकिन HANDLE_CREATED के दौरान InternetCloseHandle को कॉल करते समय मैं इसे क्रैश नहीं कर सकता। सुनिश्चित नहीं है कि समस्या क्या हो सकती है। – Luke

उत्तर

2

ल्यूक के लिए विशेष धन्यवाद।

सभी समस्याओं गायब हो जाते हैं जब मैं स्पष्ट रूप से InternetConnect + HttpOpenRequest + ऑल-इन-एक InternetOpenUrl के बजाय HttpSendRequest का उपयोग करें।

मुझे अनुरोध अनुरोध ('कनेक्शन' हैंडल से भ्रमित नहीं होने पर) पर कोई सूचना प्राप्त नहीं होती है। इसके अलावा कोई और दुर्घटनाग्रस्त नहीं है।

+1

मैं दृढ़ता से सुझाव देता हूं कि आप क्या सोचते हैं कि zjp ने क्या सुझाव दिया है। आपका नया कोड बदल गया होगा ताकि यह समस्या अब न हो, लेकिन आप * वेरिएबल का उपयोग कर रहे थे जो स्कोप से बाहर हो गए थे। –

7

संदर्भ सीटीएक्स की घोषणा समस्या का स्रोत है, इसे एक (;;) लूप के भीतर घोषित किया गया है, इसलिए यह प्रत्येक लूप के लिए बनाया गया एक स्थानीय चर है, यह नष्ट हो जाएगा और प्रत्येक लूप के अंत में अब तक पहुंच योग्य नहीं होगा।

एक परिणाम के रूप में, जब एक कॉलबैक शुरू हो जाती है, ctx पहले से ही नष्ट कर दिया गया, सूचक एक को नष्ट कर दिया ctx को अंक कालबैक में पारित किया जा रहा, अवैध स्मृति सूचक दुर्घटना का कारण बनता है।