假设我们有一个.exe和多个.dll,它们都是使用不同的Visual Studio版本用C / C ++编写的。
.exe和.dll可能有大量的第三方静态库,并且很难升级整个项目(c ++或dll)以使用更新的visual studio。
除了因视觉工作室切换而可能引起的二进制兼容性: Visual studio 2015 run-time dependencies or how to get rid of Universal CRT?
可能还有更多问题 - 比如调试等等: https://randomascii.wordpress.com/2013/09/11/debugging-optimized-codenew-in-visual-studio-2012/
另外正如我在较旧的视觉工作室中所理解的那样,C ++函数名称修改也存在问题(从视觉工作室变为视觉工作室) 移植解决方案会带来更多问题。
正如我所理解的那样,p = malloc()在.exe的上下文中执行(exe用vs2010编译),然后在.dll的上下文中执行free(p)(dll用vs2013编译)只会使应用程序崩溃。 / p>
我想一种方法是根本不使用CRT分配函数(没有malloc,free,new,...) - 但是直接使用windows api(LocalAlloc,...) - 那么代码可以在不同版本中使用视觉工作室的,但用您自己的分配方案覆盖所有分配程序听起来像繁琐的任务。
你知道其他任何方式可以使vs版本混合吗?
答案 0 :(得分:1)
提出这类问题有时候非常有用 - 您可以从评论您问题的人那里获得非常有用的链接。
我想复制粘贴链接到这里: http://siomsystems.com/mixing-visual-studio-versions/
这是问题的技术背景/描述。
我自己试图为问题#1制定某种解决方案的原型。 (问题#2对我来说仍然有点不清楚)
主要问题本身来自dll的MSVCR100.DLL / MSVCR120.DLL / ......它们倾向于使用自己的内存管理例程而不是试图拥有共同点 - 例如依赖windows api。据我所知,可能是vs2015中引入的通用CRT试图摆脱这个问题 - 但是 - 不确定 - 它需要更深入的研究。
我自己想 - "好吧,如果我们加载MSVCR120.DLL - 为什么我们不能拦截malloc / realloc / free函数并路由到我们的CRT"。这个解决方案适用于使用"单线程DLL"的exe和dll;或"多线程DLL"运行时库。
我从这个网站上找到了机会:
http://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x-x-API-Hooking-Libra
并编写了代码snipet:
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&#) 39;我已经联系起来了。)
调试时,我从内存中获得了内存空闲回调_thisCrtFree,而CRT没有分配内存 - 我怀疑MSVCRxxx.DLL可以在内部分配内存,并且仍然可以自由调用分配的内存 - 我已经决定了不要与.NET线程作斗争(我使用混合模式C ++代码) - 但要指定将执行分配/重新分配/免费的确切线程。
启用CRT兼容模式可以这样做:
EnableDisableCrtCompatibility( GetCurrentThreadId() );
<call to dll>
p = malloc(1024);
<back to exe>
EnableDisableCrtCompatibility( );
free( p );
因此,现在可以通过.dll / .exe / vs版本边界分配和释放内存。
但请注意,更高级别的类 - 如矢量/字符串不保证在Visual Studio版本之间向后兼容 - 因此您需要单独测试它。但是,如果你有自己的字符串/数组类 - 你可以自己控制代码二进制兼容性。
另请注意,如果你有一些在.dll启动期间初始化的全局变量 - 那么内存管理不会被挂钩,而且旧的CRT malloc / realloc / free正在使用中。 (参见代码 - g_executingInCrt)
请随意使用此代码,如果您有任何疑问,请与我联系。
可以在这里找到略有修改的minhooks版本:
http://www.saunalahti.fi/~tarmpika/codesamples/MinHook.zip
更新 1.5.2016 上面的方法无法使用延迟加载的dll&#39; s - 因为.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).
但基本规则是尽量减少主要的执行时间 - 只是为了安全。
18.5.2016 更新事实证明,当加载复杂的应用程序并且许多CRT正在加载时,LoadLibrary + GetProcAddress中的挂钩会挂起死锁迷上了。主要问题是,如果您已连接LoadLibrary,则不应使用具有关键部分的类似api的函数。 kernel32.dll在一个线程中等着你,你在另一个线程中等待kernel32.dll。我现在通过仅挂钩我需要的东西(参见函数CrtNeedsToBeHooked)以及在GetProcAddress调用期间释放临界区来最小化潜在的死锁情况。
但是,如果你已经挂上了一些api,那么你需要处理多态/多线程状态机,这是api&amp; api hook介绍 - 问题可能是非常重要的重现(我的快速PC甚至虚拟机问题没有发生,但一些较慢的PC出现这个问题)。
从理论上讲,你可以假设你已经完全控制了你的应用程序正在加载的.dll,但这并不完全正确。例如 - 存在一些第三方应用程序 - 如tortoise svn,它作为资源管理器扩展加载,每当你打开浏览对话框时,它都会显示。
在一台机器上,这个.dll安装在另一台机器上 - 不是。问题可能仅在第一台机器上出现。