تلقي تحذيرات الرابط عند بناء "ملحقات مدارة" ل c + + DLL المشاريع

الأعراض

تظهر إحدى رسائل الخطأ التالية في وقت التحويل البرمجي أو في وقت الارتباط:
LNK2001 خطأ أدوات الرابط
'الرمز الخارجي دون حل"الرمز"'

تحذير LNK4210 أدوات الرابط
'. يوجد مقطع CRT; قد يكون هناك غير معالج تهيئة ثابتة أو teminators'You تلقي "تحذيرات الرابط" عند بناء "ملحقات مدارة" ل c + + DLL المشاريع

تحذير LNK4243 أدوات الرابط
' ترجمة DLL الذي يحتوي على كائنات مع/clr لا ترتبط ب/NOENTRY؛ قد لا يتم تشغيل الصورة بشكل صحيح '.
قد تحدث هذه التحذيرات أثناء الظروف التالية:
  • عند التحويل البرمجي لربط كائنات مع رمز التبديل clr/ .
  • عند إنشاء أحد المشاريع التالية: قالب خدمة ويب ASP.NET; قالب مكتبة الفئة؛ أو قالب مكتبة عنصر تحكم Windows.
  • عندما تقوم بإضافة التعليمات البرمجية التي تستخدم المتغيرات العمومية أو الفئات الأصلية (أي لم __gc أو __value) مع أعضاء البيانات الثابتة. على سبيل المثال، مكتبة قالب تحكم ActiveX (ATL) Microsoft Foundation فئات (MFC) وفئات وقت التشغيل C (CRT)
ملاحظة: قد تتلقى أخطاء LNK2001 و LNK4210 مع المشاريع التي لم تتأثر بالمشكلة الموضحة في هذه المقالة. إلا أن المشروع بالتأكيد يتأثر المشكلة الموضحة في هذه المقالة إذا حل LNK2001 أو تحذير LNK4210 يؤدي إلى تحذير LNK4243، أو إذا ربط المشروع بإنشاء تحذير LNK4243.

السبب

يتم إنشاء المشاريع التالية بشكل افتراضي كمكتبة ارتباط حيوي (DLL) دون أي ربط للمكتبات الأصلية (مثل CRT أو ATL أو MFC) ودون أي المتغيرات العمومية أو الفئات الأصلية مع أعضاء البيانات الثابتة:
  • قالب خدمة ويب ASP.NET
  • قالب مكتبة فئة
  • قالب مكتبة عنصر تحكم Windows
إذا قمت بإضافة التعليمات البرمجية التي تستخدم المتغيرات العمومية أو الفئات الأصلية مع أعضاء البيانات الثابتة (على سبيل المثال، استخدام مكتبات CRT و MFC أو ATL المتغيرات العمومية)، سوف تتلقى رسائل خطأ الرابط في وقت التحويل البرمجي. عند حدوث ذلك، يجب إضافة التعليمات البرمجية لتهيئة المتغيرات الثابتة يدوياً. لمزيد من المعلومات حول كيفية القيام بذلك، راجع قسم "حل المشكلة" من هذه المقالة.

وتشير هذه الوثيقة إلى المتغيرات العمومية واعضاء البيانات الثابتة للفئات الأصلية كعلم "السكون" أو "المتغيرات ثابتة" من هذه النقطة.

يحدث هذه المشكلة بسبب مشكلة تحميل DLL المختلطة. يمكن أن تواجه Dll مختلطة (أي DLLs التي تحتوي على التعليمات البرمجية المدارة واﻷصلية) سيناريوهات حالة توقف تام في بعض الحالات عندما يتم تحميلها إلى مساحة عنوان العملية، خاصة عندما يكون النظام تحت الضغط. تم تمكين رسائل الخطأ الرابط المذكورة سابقا في الرابط للتأكد من أن العملاء على علم باحتمال توقف تام والحلول الموضحة في هذا المستند. للحصول على وصف تفصيلي للمشكلة تحميل DLL المختلطة، راجع المستند التقني التالي:
مشكلة في تحميل DLL المختلطة
http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

الحل

ملحقات مدارة لمشاريع c + + التي تم إنشاؤها لا تربط بين مكتبات C/c + + الأصلية مثل وقت التشغيل (CRT) مكتبة C، ATL أو MFC DLLs افتراضياً ولا تستخدم أي المتغيرات ثابتة. بالإضافة إلى ذلك، تحدد إعدادات المشروع أنه ينبغي ربط Dll مع تمكين خيار /NOENTRY .

يتم ذلك للربط مع نقطة إدخال يؤدي التعليمات البرمجية المدارة تشغيلها أثناء DllMain، غير أمن (راجع DllMain لمجموعة محدودة من الأشياء التي يمكنك القيام بها أثناء نطاقه).

دون نقطة إدخال DLL ليس لديه أية طريقة تهيئة المتغيرات ثابتة باستثناء أنواع بسيطة للغاية مثل الإعداد الصحيحة. يمكنك عادة لم يتم المتغيرات ثابتة في /NOENTRY DLL.

تعتمد مكتبات MFC أو ATL و CRT كافة على المتغيرات ثابتة، لذلك كما لا يمكنك استخدام هذه المكتبات من هذه DLLs دون الأول إجراء تعديلات.

إذا كان DLL الخاص بك وضع مختلط يحتاج إلى استخدام ثوابت أو المكتبات التي تعتمد على ثوابت (مثل ATL أو MFC أو CRT)، ثم يجب تعديل DLL الخاص بك، حيث أنه يتم تهيئة الإحصائيات يدوياً.

الخطوة الأولى لتهيئة يدوياً للتأكد من أن تعطيل رمز التهيئة التلقائية غير أمن مع Dll مختلطة، ويمكن أن يتسبب في حالة توقف تام. لتعطيل رمز التهيئة، اتبع الخطوات.

إزالة نقطة إدخال DLL المدارة

  1. الارتباط مع /NOENTRY. في "مستكشف الحلول", انقر نقراً مزدوجاً فوق عقده المشروع، انقر فوق خصائص. في مربع الحوار صفحات الخصائص، انقر فوق الرابطوانقر فوق سطر الأوامروقم بإضافة رمز التبديل هذا الحقل خيارات إضافية.
  2. ارتباط msvcrt.lib. في مربع الحوار صفحات الخصائص ، انقر فوق
    الرابط، انقر فوق إدخال.، ثم قم بإضافة msvcrt.lib إلى تبعيات إضافية
    property.
  3. إزالة nochkclr.obj. على صفحة " الإدخال " (نفس الصفحة كما هو موضح في الخطوة السابقة)، إزالة nochkclr.obj من الخاصية تبعيات إضافية .
  4. ارتباط في CRT. على صفحة " الإدخال " (نفس الصفحة كما هو موضح في الخطوة السابقة)، إضافة __DllMainCRTStartup@12 إلى الخاصية مراجع رمز القوة.

    إذا كنت تستخدم موجه الأوامر، حدد إعدادات المشروع أعلاه بما يلي:
    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12

تعديل المكونات التي تستهلك DLL إينيتياليزياتيون يدوية

بعد إزالة نقطة إدخال صريح، يجب عليك تعديل المكونات التي تستهلك DLL للتهيئة اليدوي، اعتماداً على الطريقة التي يتم تطبيق DLL الخاص بك:
  • يتم إدخال DLL الخاص بك استخدام DLL يصدر (__declspec (dllexport))، والعملاء الخاصة بك تستخدم تعليمات برمجية تمت إدارتها إذا كانت مرتبطة بشكل ثابت أو حيوي DLL الخاص بك.
  • DLL الخاص بك هو DLL المستندة إلى COM.
  • يمكن للمستهلكين dll الخاص بك استخدام التعليمات البرمجية المدارة و DLL الخاص بك يحتوي على أي تصدير DLL أو إدارة نقاط الإدخال.

تعديل ملفات Dll التي تقوم بإدخالها باستخدام DLL يصدر والمستهلكين التي يتعذر عليها استخدام التعليمات البرمجية المدارة

لتعديل ملفات Dll التي تقوم بإدخالها باستخدام dll يصدر (__declspec(dllexport)) والمستهلكين التي يتعذر عليها استخدام التعليمات البرمجية المدارة، اتبع الخطوات التالية:
  1. إضافة اثنين من الصادرات الجديدة إلى DLL الخاص بك, كما هو موضح في التعليمات البرمجية التالية:
    // init.cpp
    #include <windows.h>
    #include <_vcclrit.h>

    // Call this function before you call anything in this DLL.
    // It is safe to call from multiple threads; it is not reference
    // counted; and is reentrancy safe.

    __declspec(dllexport) void __cdecl DllEnsureInit(void)
    {
    // Do nothing else here. If you need extra initialization steps,
    // create static objects with constructors that perform initialization.
    __crt_dll_initialize();
    // Do nothing else here.
    }

    // Call this function after this whole process is totally done
    // calling anything in this DLL. It is safe to call from multiple
    // threads; is not reference counted; and is reentrancy safe.
    // First call will terminate.

    __declspec(dllexport) void __cdecl DllForceTerm(void)
    {
    // Do nothing else here. If you need extra terminate steps,
    // use atexit.
    __crt_dll_terminate();
    // Do nothing else here.
    }

    ملاحظة: في Visual c + + 2005, يجب عليك إضافة اللغة وقت التشغيل دعم برنامج التحويل البرمجي خيار (/clr:oldSyntax) ترجمة نموذج التعليمات البرمجية السابق بنجاح. لإضافة خيار المحول البرمجي دعم وقت تشغيل اللغة العامة، اتبع الخطوات التالية:

    1. انقر فوق المشروعومن ثم انقر فوق

      خصائص اسم المشروع .



      ملاحظة: اسم المشروع عنصرا نائبا لاسم المشروع.
    2. توسيع خصائص تكوينومن ثم انقر فوق

      عام.

    3. في الجزء الأيسر، انقر فوق لتحديد دعم وقت تشغيل اللغة العامة, بناء الجملة القديمة (/clr: oldsyntax) في

      إعدادات المشروع دعم "وقت تشغيل اللغة العامة" .
    4. انقر فوق

      تطبيق، ثم انقر فوق موافق.

    لمزيد من المعلومات حول خيارات برنامج التحويل البرمجي دعم وقت تشغيل اللغة العامة، قم بزيارة موقع شبكة مطوري Microsoft (MSDN) التالي على الويب:

    تطبيق هذه الخطوات لكل مادة.
  2. DLL الخاص بك يمكن أن يكون العديد من المستهلكين. إذا كان لديها عدة مستخدمين، أضف التعليمات البرمجية التالية إلى ملف.def DLL في قسم تصدير:
    DllEnsureInitPRIVATEDllForceTermPRIVATE

    إذا لم تقم بإضافة هذه البنود، وإذا كان لديك اثنان من ال DLLs تصدير الدالات، سيكون التطبيق الذي يرتبط DLL أخطاء الارتباط. بشكل عام، الدالات المصدرة لها نفس الأسماء. في حالة مولتيكونسومير، كل المستهلك يمكن أن يرتبط بشكل ثابت أو حيوي DLL الخاص بك.
  3. إذا كان المستهلك مرتبط بشكل ثابت إلى مكتبة الارتباط الديناميكي، قبل استخدام DLL في المرة الأولى، أو قبل أن تستخدم أي شيء يعتمد عليها في التطبيق الخاص بك، قم بإضافة استدعاء التالية:
    // Snippet 1
    typedef void (__stdcall *pfnEnsureInit)(void);
    typedef void (__stdcall *pfnForceTerm)(void);

    {
    // ... initialization code
    HANDLE hDll=::GetModuleHandle("mydll.dll");
    If(!hDll)
    {
    // Exit, return; there is nothing else to do.
    }
    pfnEnsureInit pfnDll=::( pfnEnsureInit) GetProcAddress(hDll,
    "DllEnsureInit");
    if(!pfnDll)
    {
    // Exit, return; there is nothing else to do.
    }

    pfnDll();

    // ... more initialization code
    }

  4. بعد الاستخدام الأخير لل DLL في التطبيق الخاص بك، أضف التعليمات البرمجية التالية:
    // Snippet 2
    {
    // ... termination code
    HANDLE hDll=::GetModuleHandle("mydll.dll");
    If(!hDll)
    {
    // exit, return; there is nothing else to do
    }
    pfnForceTerm pfnDll=::( pfnForceTerm) GetProcAddress(hDll,
    "DllForceTerm");
    if(!pfnDll)
    {
    // exit, return; there is nothing else to do
    }

    pfnDll();

    // ... more termination code
    }

  5. إذا تم ربط المستهلك بشكل حيوي ب DLL، إدراج التعليمات البرمجية كما يلي:
    • إدراج أجزاء التعليمات البرمجية المتكررة 1 (راجع الخطوة رقم 3) مباشرة بعد أول LoadLibrary ل DLL.
    • إدراج أجزاء التعليمات البرمجية المتكررة 2 (راجع الخطوة 4) مباشرة قبل الأخير FreeLibrary ل DLL.

لتعديل المستند إلى COM DLL

  • تعديل دالات التصدير DLL دلكانونلوادنوو DllGetClassObject، DllRegisterServerو دلونريجيستيرسيرفير كما هو موضح في التعليمات البرمجية التالية:
    //  Implementation of DLL Exports.
    #include <_vcclrit.h>

    STDAPI DllCanUnloadNow(void)
    {

    if ( _Module.GetLockCount() == 0 )
    {
    __crt_dll_terminate();
    return S_OK;
    }
    else
    {
    return S_FALSE;

    }

    }

    STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
    {
    if ( !( __crt_dll_initialize() ) )
    {
    return E_FAIL;
    }
    else
    {
    return _Module.GetClassObject(rclsid, riid, ppv);
    }
    }

    STDAPI DllRegisterServer(void)
    {
    if ( !( __crt_dll_initialize() ) )
    {
    return E_FAIL;
    }
    // Call your registration code here
    HRESULT hr = _Module.RegisterServer(TRUE)

    return hr;
    }


    STDAPI DllUnregisterServer(void)
    {
    HRESULT hr = S_OK;
    __crt_dll_terminate();

    // Call your unregistration code here
    hr = _Module.UnregisterServer(TRUE);
    return hr;
    }

تعديل ملف DLL الذي يحتوي على العملاء التي تستخدم تعليمات برمجية تمت إدارتها وتصدير DLL أو نقاط الإدخال المدارة

لتعديل ملف DLL الذي يحتوي على العملاء التي تستخدم التعليمات البرمجية المدارة و dll يصدر أو نقاط الإدخال المدارة، اتبع الخطوات التالية:
  1. تنفيذ فئة مدارة باستخدام دالات عضو ثابت للتهيئة واﻹنهاء. إضافة ملف.cpp إلى المشروع الخاص بك تنفيذ فئة مدارة مع أعضاء ثابتة للتهيئة واﻹنهاء:
    // ManagedWrapper.cpp
    // This code verifies that DllMain is not automatically called
    // by the Loader when linked with /noentry. It also checks some
    // functions that the CRT initializes.

    #include <windows.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    #include "_vcclrit.h"

    #using <mscorlib.dll>
    using namespace System;

    public __gc class ManagedWrapper {
    public:
    static BOOL minitialize() {
    BOOL retval = TRUE;
    try {
    retval = __crt_dll_initialize();
    } catch(System::Exception* e) {
    Console::WriteLine(e->Message);
    retval = FALSE;
    }
    return retval;
    }
    static BOOL mterminate() {
    BOOL retval = TRUE;
    try {
    retval = __crt_dll_terminate();
    } catch(System::Exception* e) {
    Console::WriteLine(e->Message);
    retval = FALSE;
    }
    return retval;
    }
    };

    BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID
    lpvReserved) {
    Console::WriteLine(S"DllMain is called...");
    return TRUE;
    } /* DllMain */

  2. استدعاء هذه الدالات قبل الإشارة إلى DLL وبعد الانتهاء من استخدامه. استدعاء دالات العضو التهيئة واﻹنهاء في الرئيسي:
    // Main.cpp
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";

    int main() {
    int retval = ManagedWrapper::minitialize();
    ManagedWrapper::mterminate();
    }

مستخدمي Visual c + +.NET 2002

ملاحظة: على الرغم من أنه لا توجد رسالة خطأ الرابط LNK4243 في إصدار المنتج Visual c + +.NET 2002، ينصح مستخدمي Visual c + +.NET 2002 اتباع الإرشادات المذكورة سابقا عند تطوير Dll مختلطة.


إصدار المنتج Visual c + +.NET 2003 يحتوي على عنوان إضافية لجعل تهيئة اليدوي أكثر ملاءمة. لجعل الحلول المدرجة في هذا العمل المقالة مع Visual c + +.NET 2002، يجب إضافة ملف رأس للمشروع الخاص بك يسمى _vcclrit.h بالنص التالي:
/**** _vcclrit.h
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* Purpose:
* This file defines the functions and variables used by user
* to initialize CRT and the dll in IJW scenario.
*
****/

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

extern IMAGE_DOS_HEADER __ImageBase;

BOOL WINAPI _DllMainCRTStartup(
HANDLE hDllHandle,
DWORD dwReason,
LPVOID lpreserved
);
#ifdef __cplusplus
}
#endif

#ifdef _cplusplus
#define __USE_GLOBAL_NAMESPACE ::
#else
#define __USE_GLOBAL_NAMESPACE
#endif

// Used to lock
__declspec( selectany ) LONG volatile __lock_handle = 0;

// Init called
__declspec(selectany) BOOL volatile __initialized = FALSE;

// Term called
__declspec( selectany ) BOOL volatile __terminated = FALSE;

__inline BOOL WINAPI __crt_dll_initialize()
{
// Try to make the variable names unique, so that the variables
// do not even clash with macros.
static BOOL volatile (__retval) = FALSE;
static DWORD volatile (__lockThreadId) = 0xffffffff;
DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
int (__int_var)=0;

// Take Lock; this is needed for multithreaded scenario.
// Additionally, the threads need to wait here to make sure that the dll
// is initialized when they get past this function.
while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
{
++(__int_var);
if ((__lockThreadId) == (__currentThreadId))
{
return TRUE;
}
__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

// If you hang in this loop, this implies that your
// dllMainCRTStartup is hung on another thread.
// The most likely cause of this is a hang in one of your
// static constructors or destructors.
}
// Note: you do not really need any interlocked stuff here because the
// writes are always in the lock. Only reads are outside the lock.
(__lockThreadId) = (__currentThreadId);
__try {
if ( (__terminated) == TRUE )
{
(__retval) = FALSE;
}
else if ( (__initialized) == FALSE )
{
(__retval) = (_DllMainCRTStartup)( ( HINSTANCE )( &__ImageBase ), DLL_PROCESS_ATTACH, 0 );
(__initialized) = TRUE;
}

} __finally {
// revert the __lockThreadId
(__lockThreadId) = 0xffffffff;
// Release Lock
__USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
}
return (__retval);
}

__inline BOOL WINAPI __crt_dll_terminate()
{
static BOOL volatile (__retval) = TRUE;
static DWORD volatile (__lockThreadId) = 0xffffffff;
DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
int (__int_var)=0;

// Take Lock; this lock is needed to keep Terminate
// in sync with Initialize.
while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
{
++(__int_var);
if ((__lockThreadId) == (__currentThreadId))
{
return TRUE;
}
__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

// If you hang in this loop, this implies that your
// dllMainCRTStartup is hung on another thread. The most likely
// cause of this is a hang in one of your static constructors
// or destructors.
}
// Note: you do not really need any interlocked stuff here because the
// writes are always in the lock. Only reads are outside the lock.
(__lockThreadId) = (__currentThreadId);
__try {
if ( (__initialized) == FALSE )
{
(__retval) = FALSE;
}
else if ( (__terminated) == FALSE )
{
(__retval) = _DllMainCRTStartup( ( HINSTANCE )( &(__ImageBase) ), DLL_PROCESS_DETACH, 0 );
(__terminated) = TRUE;
}
} __finally {
// revert the __lockThreadId
(__lockThreadId) = 0xffffffff;
// Release Lock
__USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
}
return (__retval);
}

المراجع

لمزيد من المعلومات، انقر فوق رقم المقالة التالية لعرضها في "قاعدة معارف Microsoft":

الخطأ 309694 : استثناء أبدوماينونلواديد عند استخدام ملحقات لمكونات Visual c + + مدارة

خصائص

رقم الموضوع: 814472 - آخر مراجعة: 15‏/01‏/2017 - المراجعة: 1

تعليقات