Visual studio 代码二进制兼容性

Visual studio code binary compatibility

假设我们有一个 .exe 和多个 .dll,它们都是使用不同的 Visual studio 版本用 C / C++ 编写的。

.exe 和 .dll 可能有大量的第三方静态库,并且很难升级整个项目(c++ 或 dll)以使用更新的 visual studio.

除了可能因 visual studio 切换而提高的二进制兼容性:

可能还有更多问题 - 如调试等: https://randomascii.wordpress.com/2013/09/11/debugging-optimized-codenew-in-visual-studio-2012/

此外,据我所知,在较早的 visual studio 中,C++ 函数名称重整也存在问题(从 visual studio 更改为 visual studio) 移植解决方案会产生更多问题。

  1. 分配/释放问题

据我所知,p = malloc() 在 .exe 上下文中执行(exe 使用 vs2010 编译),然后在 .dll 上下文中执行 free(p)(dll 使用 vs2013 编译)只会使应用程序崩溃.

我想一种方法是根本不使用 CRT 分配函数(没有 malloc、free、new、...)——而是直接使用 windows api(LocalAlloc、.. .) - 那么代码将适用于 visual studio 的不同版本,但是用你自己的分配方案覆盖所有分配过程听起来像是一项乏味的任务。

你知道任何其他方法可以使 vs 版本混合成为可能吗?

  1. 我还测试了 C++ 函数修改在 visual studio 2010 和 2013 之间兼容,但它现在是否成为标准 - 它会在下一个 visual studio 版本中以不兼容的方式改变吗?

问这类问题有时真的很有用 - 你会从评论你的问题的人那里得到非常有用的 link。

我想在此处复制粘贴 link: http://siomsystems.com/mixing-visual-studio-versions/

这是技术背景/问题描述。

我自己尝试为问题 #1 制作某种解决方案的原型。 (问题 #2 对我来说还是有点不清楚)

主要问题本身来自 dll 的 MSVCR100.DLL / MSVCR120.DLL / ...,它倾向于使用自己的内存管理例程,而不是尝试拥有一些共同点 - 例如靠windowsapi。据我所知,vs2015 中引入的通用 CRT 可能正试图解决这个问题 - 但是 - 不确定 - 它需要更深入的研究。

我自己想的 - "ok, if we are loading MSVCR120.DLL - why we cannot intercept malloc/realloc/free functions and route to our CRT"。此解决方案适用于使用 "single threaded DLL" 或 "multi threaded DLL" 运行-时间库的 exe 和 dll。

我从这个网站上找到了 minhooks:

http://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x-x-API-Hooking-Libra

并像这样编写代码片段:

crtCompatibility.cpp:

#include <Windows.h>
#include "MinHook.h"                    //MH_Initialize
#include <map>
#include <vector>
#include <atlstr.h>                     //CStringW
#include <Psapi.h>                      //EnumProcessModules

using namespace std;

map<CStringW, bool> g_dlls;                 // dll file path (lowercased) for all loaded .dll's within process.
map<CStringW, vector<void*> > g_mapHooks;   // dll file path to hooks accosiated with given dll.
map<CStringW, bool> g_myCrtDlls;            // filenames only of all crt's which we enabled by default.
CRITICAL_SECTION g_dllCheck;
bool             g_executingInCrt = false;  // true if executing in dll's crt, don't reroute such mallocs then, otherwise crt gets angry to you.

DWORD g_monitorThread = (DWORD) -1;

#define CRTS_TO_HOOK 10                     // Maximum CRT's to hook
bool hookIsFree[CRTS_TO_HOOK] = { true, true, true, true, true, true, true, true, true, true };

//-------------------------------------------
// malloc rerouter.
//-------------------------------------------

typedef void* (__cdecl *pFuncMalloc) (size_t size);
pFuncMalloc porigMalloc[CRTS_TO_HOOK] = { 0 };
map<CStringW, int> g_AllocationId;

template <int i>
void* __cdecl _thisCrtMalloc( size_t size )
{
    if( !g_executingInCrt && g_monitorThread == GetCurrentThreadId() )
        return malloc( size );

    return porigMalloc[i]( size );
}

pFuncMalloc thisCrtMalloc[CRTS_TO_HOOK] =
{
    _thisCrtMalloc<0>, _thisCrtMalloc<1>, _thisCrtMalloc<2>, _thisCrtMalloc<3>, _thisCrtMalloc<4>, 
    _thisCrtMalloc<5>, _thisCrtMalloc<6>, _thisCrtMalloc<7>, _thisCrtMalloc<8>, _thisCrtMalloc<9>
};

//-------------------------------------------
// realloc rerouter.
//-------------------------------------------

typedef void* (__cdecl *pFuncRealloc) (void* p, size_t size);
pFuncRealloc porigRealloc[CRTS_TO_HOOK] = { 0 };

template <int i>
void* __cdecl _thisCrtRealloc( void* p, size_t size )
{
    if( !g_executingInCrt && g_monitorThread == GetCurrentThreadId() )
        return realloc( p, size );

    return porigRealloc[i]( p, size );
}

pFuncRealloc thisCrtRealloc[CRTS_TO_HOOK] =
{
    _thisCrtRealloc<0>, _thisCrtRealloc<1>, _thisCrtRealloc<2>, _thisCrtRealloc<3>, _thisCrtRealloc<4>,
    _thisCrtRealloc<5>, _thisCrtRealloc<6>, _thisCrtRealloc<7>, _thisCrtRealloc<8>, _thisCrtRealloc<9>
};

//-------------------------------------------
// free rerouter.
//-------------------------------------------

typedef void( __cdecl *pFuncFree ) (void*);
pFuncFree porigFree[CRTS_TO_HOOK] = { 0 };

template <int i>
void __cdecl _thisCrtFree( void* p )
{
    if( !g_executingInCrt && g_monitorThread == GetCurrentThreadId() )
        return free( p );

    porigFree[i]( p );
}

pFuncFree thisCrtFree[CRTS_TO_HOOK] =
{
    _thisCrtFree<0>, _thisCrtFree<1>, _thisCrtFree<2>, _thisCrtFree<3>, _thisCrtFree<4>,
    _thisCrtFree<5>, _thisCrtFree<6>, _thisCrtFree<7>, _thisCrtFree<8>, _thisCrtFree<9>
};


//
//  Normally we could just return true here. But just to minimize amount of hooks
//  enabled accross whole process, we know which plugins are using which visual studio
//  crt.
//
bool CrtNeedsToBeHooked( const wchar_t* pDll )
{
    if( wcsicmp( pDll, L"msvcr120.dll") == 0 )
        return true;

    return false;
}

//
//  Loading one dll might load another (dependent) dll as well.
//  Same is with FreeLibrary. We keep here record of which dll's are loaded
//  to compare with previous state.
//
void EnumDlls( bool bCheckNew )
{
    EnterCriticalSection( &g_dllCheck);
    HMODULE dlls[1024] = { 0 };
    DWORD nItems = 0;
    wchar_t path[MAX_PATH];
    HANDLE hProcess = GetCurrentProcess();

    if( !EnumProcessModules( hProcess, dlls, sizeof( dlls ), &nItems ) )
    {
        LeaveCriticalSection( &g_dllCheck);
        return;
    }

    // Not visited.
    for( auto it = g_dlls.begin(); it != g_dlls.end(); it++ )
        it->second = false;

    nItems /= sizeof( HMODULE );
    for( unsigned int i = 0; i < nItems; i++ )
    {
        path[0] = 0;
        if( !GetModuleFileNameExW( hProcess, dlls[i], path, sizeof( path ) / sizeof( path[0] ) ) )
            continue;

        _wcslwr_s( path, MAX_PATH );

        auto it = g_dlls.find( path );
        if( it != g_dlls.end() )
        {
            // Visited.
            it->second = true;
            continue;
        }

        g_dlls[path] = true;

        if( !bCheckNew )
            continue;

        wchar_t* pDll = wcsrchr( path, L'\' );
        if( pDll ) pDll++;

        //
        // MSVCRxxx.dll (For example MSVCR100.DLL) is loading, let's hook it's memory allocation routines
        // and route them to our CRT.
        //
        // (P.S. this might be different .dll name for vs2015, haven't tested)
        //
        if( _wcsnicmp( pDll, L"MSVCR", 5 ) == 0 && CrtNeedsToBeHooked(pDll) && g_myCrtDlls.find( pDll ) == g_myCrtDlls.end() )
        {
            // While we are executing our code in hookLoadLibrary, we can execute GetProcLibrary
            // functions, and it's possible to get dead lock because of this.
            // kernel32.dll is waiting for LoadLibrary to complete, but we are waiting for GetProcLibrary
            // to complete.
            LeaveCriticalSection( &g_dllCheck);
            void* f = GetProcAddress( dlls[i], "malloc" );
            void* f2 = GetProcAddress( dlls[i], "realloc" );
            void* f3 = GetProcAddress( dlls[i], "free" );
            EnterCriticalSection( &g_dllCheck);

            int FoundFreeSlot = -1;

            for( int freeSlot = 0; freeSlot < CRTS_TO_HOOK; freeSlot++ )
                if( hookIsFree[freeSlot] == true )
                {
                    FoundFreeSlot = freeSlot;
                    break;
                }

            if( FoundFreeSlot != -1 )
            {
                vector<void*> vecTargets;

                // Hook malloc, realloc, free functions CRT compatibility.
                vecTargets.push_back( f );
                MH_CreateHook( f, thisCrtMalloc[FoundFreeSlot], (void**)&porigMalloc[FoundFreeSlot] );

                vecTargets.push_back( f2 );
                MH_CreateHook( f2, thisCrtRealloc[FoundFreeSlot], (void**)&porigRealloc[FoundFreeSlot] );

                vecTargets.push_back( f3 );
                MH_CreateHook( f3, thisCrtFree[FoundFreeSlot], (void**)&porigFree[FoundFreeSlot] );

                g_mapHooks[path] = vecTargets;
                MH_EnableHook( MH_ALL_HOOKS );
                g_AllocationId[path] = FoundFreeSlot;
                hookIsFree[FoundFreeSlot] = false;
            }
        }
    } //for

    //
    // Check if .dll's were freed.
    //
    for( auto it = g_dlls.begin(); it != g_dlls.end(); )
    {
        if( !it->second )
        {
            // Release trampolines.
            auto hooks = g_mapHooks.find( it->first );
            if( hooks != g_mapHooks.end() )
            {
                // Release allocation slot.
                int allocSlot = g_AllocationId[ it->first ];
                if( allocSlot < CRTS_TO_HOOK )
                    hookIsFree[allocSlot] = true;

                vector<void*>& vec = hooks->second;
                for( size_t i = 0; i < vec.size(); i++ )
                    MH_RemoveHook2( vec[i], false );
            }

            // Dll was freed.
            g_dlls.erase( it++ );
            continue;
        }
        it++;
    } //for

    if( !bCheckNew )
    {
        // Collect CRT names upon which we are running at. .NET might try to draw multiple CRTs.
        for( auto it = g_dlls.begin(); it != g_dlls.end(); it++ )
        {
            CStringW path = it->first;
            wchar_t* pPath = path.GetBuffer( MAX_PATH );
            _wcslwr_s( pPath, MAX_PATH );

            wchar_t* pDll = wcsrchr( pPath, L'\' );
            if( pDll ) pDll++;

            if( _wcsnicmp( pDll, L"MSVCR", 5 ) == 0 )
                g_myCrtDlls[pDll] = true;
        }
    }

    LeaveCriticalSection( &g_dllCheck );
} //EnumDlls

//-------------------------------------------
// Intercepts LoadLibraryW
//-------------------------------------------

typedef HMODULE( WINAPI *pFuncLoadLibraryW )(const wchar_t* file);
pFuncLoadLibraryW g_origLoadLibraryW = NULL;

HMODULE WINAPI hook_LoadLibraryW( const wchar_t* file )
{
    bool bUpdateLock = g_monitorThread == GetCurrentThreadId();
    if( bUpdateLock )
        g_executingInCrt = true;

    HMODULE h = g_origLoadLibraryW( file );

    if( bUpdateLock )
        g_executingInCrt = false;

    if( !h )
        return h;

    EnumDlls( true );
    return h;
} //hook_LoadLibraryW

//-------------------------------------------
// Intercepts LoadLibraryA
//-------------------------------------------

typedef HMODULE( WINAPI *pFuncLoadLibraryA )(const char* file);
pFuncLoadLibraryA g_origLoadLibraryA = NULL;

HMODULE WINAPI hook_LoadLibraryA( const char* file )
{
    bool bUpdateLock = g_monitorThread == GetCurrentThreadId();
    if( bUpdateLock )
        g_executingInCrt = true;

    HMODULE h = g_origLoadLibraryA( file );

    if( bUpdateLock )
        g_executingInCrt = false;

    if( !h )
        return h;

    EnumDlls( true );
    return h;
} //hook_LoadLibraryW

//-------------------------------------------
// Intercepts FreeLibrary
//-------------------------------------------

typedef BOOL( WINAPI *pFuncFreeLibrary ) (HMODULE h);
pFuncFreeLibrary g_origFreeLibrary;

BOOL WINAPI hook_FreeLibrary( HMODULE h )
{
    bool bUpdateLock = g_monitorThread == GetCurrentThreadId();
    if( bUpdateLock )
        g_executingInCrt = true;

    BOOL b = g_origFreeLibrary( h );

    if( bUpdateLock )
        g_executingInCrt = false;

    if( !b )
        return b;

    EnumDlls( true );
    return b;
} //hook_FreeLibrary

//
//  This function intercepts and starts monitor what new dll's gets loaded and freed.
//  If there is loaded MSVCRxxx.DLL different CRT run-time than we're running in - we intercepts
//  it's memory allocation routines, so allocation in .dll would work identically to allocation in main .exe
//
void EnableCrtMonitor(void)
{
    EnumDlls( false );
    MH_Initialize();
    MH_CreateHookApi( L"kernel32.dll", "LoadLibraryW", hook_LoadLibraryW, (void**)&g_origLoadLibraryW );
    MH_CreateHookApi( L"kernel32.dll", "LoadLibraryA", hook_LoadLibraryA, (void**)&g_origLoadLibraryA );
    MH_CreateHookApi( L"kernel32.dll", "FreeLibrary", hook_FreeLibrary, (void**)&g_origFreeLibrary );
    MH_EnableHook( MH_ALL_HOOKS );
}

class CCrtCompatibilityEnabler
{
public:
    CCrtCompatibilityEnabler()
    {
        InitializeCriticalSection( &g_dllCheck);
        EnableCrtMonitor();
    }

    ~CCrtCompatibilityEnabler()
    {
        MH_DisableHook( MH_ALL_HOOKS );
        MH_Uninitialize();
        DeleteCriticalSection(&g_dllCheck);
    }
} g_CheckCrtShutdown;

//
//  This function enables or disables CRT compatibility hooks.
//
//  Enabling can be done like this:
//      EnableDisableCrtCompatibility( GetCurrentThreadId() );
//  and disabling - running without any argument.
//
//  When hooks are enabled - for thread which is monitored - all memory allocations
//  will be performed using main .exe CRT.
//
void EnableDisableCrtCompatibility( DWORD monitorThread = (DWORD) -1)
{
    g_monitorThread = monitorThread;
}

然而,我在编写这段代码时遇到了多个问题,所以如果有一些问题——它们不一定容易修复——但它适用于我自己的代码(这是相对较大的代码库,超过 3-4我已连接的 CRT)。

调试时,我从 CRT 未分配的内存中获得了内存空闲回调 _thisCrtFree - 我怀疑 MSVCRxxx.DLL 可以在内部分配内存,并且仍然调用 free 到分配的内存 - 我已经决定不要与 .NET 线程作斗争(我使用了混合模式 C++ 代码)——而是要指定将执行分配/重新分配/释放的确切线程。

启用 CRT 兼容模式可以这样做:

EnableDisableCrtCompatibility( GetCurrentThreadId() );

<call to dll>
    p = malloc(1024);
<back to exe>

EnableDisableCrtCompatibility( );

free( p );

因此现在可以跨 .dll/.exe / vs 版本边界分配和释放内存。

但请注意更高级别的 类 - 像矢量/字符串不保证在 visual studio 版本之间向后兼容 - 所以你需要单独测试。但是,如果您有自己的 string/array 类 - 您可以自己控制代码二进制兼容性。

另请注意,如果您有一些在 .dll 启动期间初始化的全局变量 - 则内存管理未挂钩,并且仍在使用旧的 CRT malloc/realloc/free。 (见代码 - g_executingInCrt)

请随意使用此代码,有任何问题都可以问我。

可以在这里找到稍微修改过的 minhooks 版本:

http://www.saunalahti.fi/~tarmpika/codesamples/MinHook.zip

Update 1.5.2016 上述方法不适用于延迟加载的 dll - 因为 .dll 是在某些特定函数调用时加载的 (不在 LoadLibrary 中) - 并且再次初始化 crt - 没有工作。我已经更新了代码,以便 .dll 负责启用或禁用 crt 兼容性。但代码与我上面写的几乎相同。如果您还需要检查构造函数/析构函数状态 - 用范围运算符包装代码是有意义的。例如 - 像这样:

void UseExeCrt( bool bExesCrt )
{
    if( bExesCrt )
        EnableDisableCrtCompatibility( GetCurrentThreadId() );
    else
        EnableDisableCrtCompatibility( );
}

... class implemented in .exe, declaration for .dll:

class __declspec(dllimport) CMyOwnClass
{
public:
    void GetComplexData();

    ...
};

... data resides in .dll:

vector<String> g_someDllsData;


... function resides in .dll:

    ...
    UseExeCrt( true );                  // Use exe's CRT for allocating CMyOwnClass.
    {
        CMyOwnClass tempClass;
        tempClass.GetComplexData();     // Since function resides in .exe - we use .exe's CRT.

        UseExeCrt( false );             // Now we use some data which resides in .dll, and we must use .dll's crt.

        g_someDllsData.resize( ... );


        UseExeCrt( true );              // Before deleting CMyOwnClass - we must use again .exe's CRT
    }   //~CMyOwnClass is called.

    UseExeCrt( false );                 // Now back to .dll's CRT. (Just to be safe).

但基本规则是尽量减少 main exe crt 的执行时间 - 只是为了安全起见。

18.5.2016 Update 事实证明 - 在加载复杂应用程序时,LoadLibrary + GetProcAddress 中的挂钩可能会死锁挂起,并且许多 CRT 都被连接起来了。主要问题是,如果您已挂钩 LoadLibrary,则不应将类似 api 的函数与关键部分一起使用。 kernel32.dll 在一个线程中等待您,而您在另一个线程中等待 kernel32.dll。现在,我已通过仅挂钩我需要的内容(请参阅函数 CrtNeedsToBeHooked)以及在 GetProcAddress 调用期间释放关键部分来最大限度地减少潜在的死锁情况。

但通常情况下,如果你已经挂钩了一些 api,你需要处理多状态/多线程状态机,api & api 挂钩引入 - 问题可能是重现非常重要(我的快速电脑甚至在虚拟机上都没有出现问题,但一些较慢的电脑出现了这个问题)。

理论上您可以假设您完全控制您的应用程序正在加载的 .dll,但这并不完全正确。例如 - 存在一些第 3 方应用程序 - 如 tortoise svn,它作为资源管理器扩展加载,每当你打开浏览对话框时都会依次显示。

在一台机器上安装了这个 .dll,在另一台机器上没有。问题可能只出现在第一台机器上。