Some code refactoring: Dynamic loading of DLL functions is now handled at a centralized place.

This commit is contained in:
LoRd_MuldeR 2015-11-22 21:45:09 +01:00
parent b82098a29e
commit 7f5d618eba
8 changed files with 172 additions and 139 deletions

View File

@ -38,6 +38,7 @@
<ClCompile Include="src\Terminal_Win32.cpp" /> <ClCompile Include="src\Terminal_Win32.cpp" />
<ClCompile Include="src\Translation.cpp" /> <ClCompile Include="src\Translation.cpp" />
<ClCompile Include="src\UpdateChecker.cpp" /> <ClCompile Include="src\UpdateChecker.cpp" />
<ClCompile Include="src\Utils_Win32.cpp" />
<ClCompile Include="src\Version.cpp" /> <ClCompile Include="src\Version.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -62,6 +63,7 @@
<ClInclude Include="src\3rd_party\keccak\include\keccak_impl.h" /> <ClInclude Include="src\3rd_party\keccak\include\keccak_impl.h" />
<ClInclude Include="src\3rd_party\strnatcmp\include\strnatcmp.h" /> <ClInclude Include="src\3rd_party\strnatcmp\include\strnatcmp.h" />
<ClInclude Include="src\DirLocker.h" /> <ClInclude Include="src\DirLocker.h" />
<ClInclude Include="src\Utils_Win32.h" />
<CustomBuild Include="include\Mutils\UpdateChecker.h"> <CustomBuild Include="include\Mutils\UpdateChecker.h">
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)tmp\$(ProjectName)\MOC_%(Filename).cpp" "%(FullPath)"</Command> <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)tmp\$(ProjectName)\MOC_%(Filename).cpp" "%(FullPath)"</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MOC "$(SolutionDir)tmp\$(ProjectName)\MOC_%(Filename).cpp"</Message> <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MOC "$(SolutionDir)tmp\$(ProjectName)\MOC_%(Filename).cpp"</Message>

View File

@ -99,6 +99,9 @@
<ClCompile Include="src\Registry_Win32.cpp"> <ClCompile Include="src\Registry_Win32.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="src\Utils_Win32.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="src\CriticalSection_Win32.h"> <ClInclude Include="src\CriticalSection_Win32.h">
@ -170,6 +173,9 @@
<ClInclude Include="include\MUtils\Registry.h"> <ClInclude Include="include\MUtils\Registry.h">
<Filter>Public Headers</Filter> <Filter>Public Headers</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="src\Utils_Win32.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CustomBuild Include="include\Mutils\UpdateChecker.h"> <CustomBuild Include="include\Mutils\UpdateChecker.h">

View File

@ -26,6 +26,7 @@
//MUtils //MUtils
#include <MUtils/CPUFeatures.h> #include <MUtils/CPUFeatures.h>
#include <MUtils/OSSupport.h> #include <MUtils/OSSupport.h>
#include "Utils_Win32.h"
//Qt //Qt
#include <QLibrary> #include <QLibrary>
@ -94,11 +95,11 @@ MUtils::CPUFetaures::cpu_info_t MUtils::CPUFetaures::detect(void)
if(strlen(features.vendor) < 1) strncpy_s(features.vendor, 0x40, "Unknown", _TRUNCATE); if(strlen(features.vendor) < 1) strncpy_s(features.vendor, 0x40, "Unknown", _TRUNCATE);
#if (!(defined(_M_X64) || defined(_M_IA64))) #if (!(defined(_M_X64) || defined(_M_IA64)))
QLibrary Kernel32Lib("kernel32.dll"); const IsWow64ProcessFun isWow64ProcessPtr = MUtils::Win32Utils::resolve<IsWow64ProcessFun>(QLatin1String("kernel32"), QLatin1String("IsWow64Process"));
if(IsWow64ProcessFun IsWow64ProcessPtr = (IsWow64ProcessFun) Kernel32Lib.resolve("IsWow64Process")) if(isWow64ProcessPtr)
{ {
BOOL x64flag = FALSE; BOOL x64flag = FALSE;
if(IsWow64ProcessPtr(GetCurrentProcess(), &x64flag)) if(isWow64ProcessPtr(GetCurrentProcess(), &x64flag))
{ {
features.x64 = (x64flag == TRUE); features.x64 = (x64flag == TRUE);
} }

View File

@ -32,6 +32,12 @@
#include <QWidget> #include <QWidget>
#include <QMutex> #include <QMutex>
//Win32 API
#ifndef _INC_WINDOWS
#define WIN32_LEAN_AND_MEAN 1
#include <Windows.h>
#endif //_INC_WINDOWS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// BROADCAST // BROADCAST
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -109,7 +115,7 @@ bool MUtils::GUI::set_window_icon(QWidget *const window, const QIcon &icon, cons
if((!icon.isNull()) && window->winId()) if((!icon.isNull()) && window->winId())
{ {
const int extend = (bIsBigIcon ? 32 : 16); const int extend = (bIsBigIcon ? 32 : 16);
if(HICON hIcon = qicon_to_hicon(icon, extend, extend)) if(HICON hIcon = (HICON) MUtils::Win32Utils::qicon_to_hicon(icon, extend, extend))
{ {
if(new Internal::WindowIconHelper(window, hIcon, bIsBigIcon)) if(new Internal::WindowIconHelper(window, hIcon, bIsBigIcon))
{ {

View File

@ -40,6 +40,7 @@
#include <MUtils/OSSupport.h> #include <MUtils/OSSupport.h>
#include <MUtils/GUI.h> #include <MUtils/GUI.h>
#include "CriticalSection_Win32.h" #include "CriticalSection_Win32.h"
#include "Utils_Win32.h"
//Qt //Qt
#include <QMap> #include <QMap>
@ -486,17 +487,8 @@ static QReadWriteLock g_wine_lock;
static const bool detect_wine(void) static const bool detect_wine(void)
{ {
bool is_wine = false; void *const ptr = MUtils::Win32Utils::resolve<void*>(QLatin1String("ntdll"), QLatin1String("wine_get_version"));
return (ptr != NULL);
QLibrary ntdll("ntdll.dll");
if(ntdll.load())
{
if(ntdll.resolve("wine_nt_to_unix_file_name") != NULL) is_wine = true;
if(ntdll.resolve("wine_get_version") != NULL) is_wine = true;
ntdll.unload();
}
return is_wine;
} }
const bool &MUtils::OS::running_on_wine(void) const bool &MUtils::OS::running_on_wine(void)
@ -531,11 +523,9 @@ const bool &MUtils::OS::running_on_wine(void)
typedef QMap<size_t, QString> KFMap; typedef QMap<size_t, QString> KFMap;
typedef HRESULT (WINAPI *SHGetKnownFolderPath_t)(const GUID &rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); typedef HRESULT (WINAPI *SHGetKnownFolderPath_t)(const GUID &rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
typedef HRESULT (WINAPI *SHGetFolderPath_t)(HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath); typedef HRESULT (WINAPI *SHGetFolderPath_t) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath);
static QScopedPointer<KFMap> g_known_folders_map; static QScopedPointer<KFMap> g_known_folders_map;
static SHGetKnownFolderPath_t g_known_folders_fpGetKnownFolderPath;
static SHGetFolderPath_t g_known_folders_fpGetFolderPath;
static QReadWriteLock g_known_folders_lock; static QReadWriteLock g_known_folders_lock;
const QString &MUtils::OS::known_folder(known_folder_t folder_id) const QString &MUtils::OS::known_folder(known_folder_t folder_id)
@ -598,22 +588,16 @@ const QString &MUtils::OS::known_folder(known_folder_t folder_id)
//Initialize on first call //Initialize on first call
if(g_known_folders_map.isNull()) if(g_known_folders_map.isNull())
{ {
QLibrary shell32("shell32.dll");
if(shell32.load())
{
g_known_folders_fpGetFolderPath = (SHGetFolderPath_t) shell32.resolve("SHGetFolderPathW");
g_known_folders_fpGetKnownFolderPath = (SHGetKnownFolderPath_t) shell32.resolve("SHGetKnownFolderPath");
}
g_known_folders_map.reset(new QMap<size_t, QString>()); g_known_folders_map.reset(new QMap<size_t, QString>());
} }
QString folderPath; QString folderPath;
//Now try to get the folder path! //Now try to get the folder path!
if(g_known_folders_fpGetKnownFolderPath) if(const SHGetKnownFolderPath_t known_folders_fpGetKnownFolderPath = MUtils::Win32Utils::resolve<SHGetKnownFolderPath_t>(QLatin1String("shell32"), QLatin1String("SHGetKnownFolderPath")))
{ {
WCHAR *path = NULL; WCHAR *path = NULL;
if(g_known_folders_fpGetKnownFolderPath(s_folders[folderId].guid, KF_FLAG_CREATE, NULL, &path) == S_OK) if(known_folders_fpGetKnownFolderPath(s_folders[folderId].guid, KF_FLAG_CREATE, NULL, &path) == S_OK)
{ {
//MessageBoxW(0, path, L"SHGetKnownFolderPath", MB_TOPMOST); //MessageBoxW(0, path, L"SHGetKnownFolderPath", MB_TOPMOST);
QDir folderTemp = QDir(QDir::fromNativeSeparators(MUTILS_QSTR(path))); QDir folderTemp = QDir(QDir::fromNativeSeparators(MUTILS_QSTR(path)));
@ -624,10 +608,10 @@ const QString &MUtils::OS::known_folder(known_folder_t folder_id)
CoTaskMemFree(path); CoTaskMemFree(path);
} }
} }
else if(g_known_folders_fpGetFolderPath) else if(const SHGetFolderPath_t known_folders_fpGetFolderPath = MUtils::Win32Utils::resolve<SHGetFolderPath_t>(QLatin1String("shell32"), QLatin1String("SHGetFolderPathW")))
{ {
QScopedArrayPointer<WCHAR> path(new WCHAR[4096]); QScopedArrayPointer<WCHAR> path(new WCHAR[4096]);
if(g_known_folders_fpGetFolderPath(NULL, s_folders[folderId].csidl | CSIDL_FLAG_CREATE, NULL, NULL, path.data()) == S_OK) if(known_folders_fpGetFolderPath(NULL, s_folders[folderId].csidl | CSIDL_FLAG_CREATE, NULL, NULL, path.data()) == S_OK)
{ {
//MessageBoxW(0, path, L"SHGetFolderPathW", MB_TOPMOST); //MessageBoxW(0, path, L"SHGetFolderPathW", MB_TOPMOST);
QDir folderTemp = QDir(QDir::fromNativeSeparators(MUTILS_QSTR(path.data()))); QDir folderTemp = QDir(QDir::fromNativeSeparators(MUTILS_QSTR(path.data())));
@ -721,10 +705,6 @@ quint64 MUtils::OS::current_file_time(void)
typedef DWORD(_stdcall *GetPathNameByHandleFun)(HANDLE hFile, LPWSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); typedef DWORD(_stdcall *GetPathNameByHandleFun)(HANDLE hFile, LPWSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags);
static QReadWriteLock g_getFilePath_lock;
static QScopedPointer<QLibrary> g_getFilePath_kernel32;
static GetPathNameByHandleFun g_getFilePath_prt = NULL;
static QString get_file_path_drive_list(void) static QString get_file_path_drive_list(void)
{ {
QString list; QString list;
@ -797,55 +777,24 @@ static QString get_file_path_fallback(const HANDLE &hFile)
return filePath; return filePath;
} }
static bool get_file_path_init()
{
QWriteLocker writeLock(&g_getFilePath_lock);
if (g_getFilePath_prt)
{
return true; /*already initialized*/
}
if (g_getFilePath_kernel32.isNull())
{
g_getFilePath_kernel32.reset(new QLibrary("kernel32.dll"));
}
if (!g_getFilePath_kernel32->isLoaded())
{
if (!g_getFilePath_kernel32->load())
{
return false; /*faild to load kernel32.dll*/
}
}
g_getFilePath_prt = (GetPathNameByHandleFun) g_getFilePath_kernel32->resolve("GetFinalPathNameByHandleW");
return (g_getFilePath_prt != NULL);
}
QString MUtils::OS::get_file_path(const int &fd) QString MUtils::OS::get_file_path(const int &fd)
{ {
if (fd >= 0) if (fd >= 0)
{ {
QReadLocker readLock(&g_getFilePath_lock); const GetPathNameByHandleFun getPathNameByHandleFun = MUtils::Win32Utils::resolve<GetPathNameByHandleFun>(QLatin1String("kernel32"), QLatin1String("GetFinalPathNameByHandleW"));
if (!getPathNameByHandleFun)
if (!g_getFilePath_prt)
{
readLock.unlock();
if (!get_file_path_init())
{ {
qWarning("MUtils::OS::get_file_path() --> fallback!"); qWarning("MUtils::OS::get_file_path() --> fallback!");
return get_file_path_fallback((HANDLE)_get_osfhandle(fd)); return get_file_path_fallback((HANDLE)_get_osfhandle(fd));
} }
readLock.relock();
}
const HANDLE handle = (HANDLE) _get_osfhandle(fd); const HANDLE handle = (HANDLE) _get_osfhandle(fd);
const DWORD len = g_getFilePath_prt(handle, NULL, 0, FILE_NAME_OPENED); const DWORD len = getPathNameByHandleFun(handle, NULL, 0, FILE_NAME_OPENED);
if (len > 0) if (len > 0)
{ {
if (wchar_t *const buffer = (wchar_t*)_malloca(sizeof(wchar_t) * len)) if (wchar_t *const buffer = (wchar_t*)_malloca(sizeof(wchar_t) * len))
{ {
const DWORD ret = g_getFilePath_prt(handle, buffer, len, FILE_NAME_OPENED); const DWORD ret = getPathNameByHandleFun(handle, buffer, len, FILE_NAME_OPENED);
if ((ret > 0) && (ret < len)) if ((ret > 0) && (ret < len))
{ {
const QString path(MUTILS_QSTR(buffer)); const QString path(MUTILS_QSTR(buffer));
@ -1414,68 +1363,29 @@ void MUtils::OS::shell_change_notification(void)
typedef BOOL (_stdcall *Wow64DisableWow64FsRedirectionFun)(void *OldValue); typedef BOOL (_stdcall *Wow64DisableWow64FsRedirectionFun)(void *OldValue);
typedef BOOL (_stdcall *Wow64RevertWow64FsRedirectionFun )(void *OldValue); typedef BOOL (_stdcall *Wow64RevertWow64FsRedirectionFun )(void *OldValue);
static QReadWriteLock g_wow64redir_lock;
static QScopedPointer<QLibrary> g_wow64redir_kernel32;
static Wow64DisableWow64FsRedirectionFun g_wow64redir_disable = NULL;
static Wow64RevertWow64FsRedirectionFun g_wow64redir_revert = NULL;
static bool wow64fsredir_init()
{
QWriteLocker writeLock(&g_wow64redir_lock);
if(g_wow64redir_disable && g_wow64redir_revert)
{
return true; /*already initialized*/
}
if(g_wow64redir_kernel32.isNull())
{
g_wow64redir_kernel32.reset(new QLibrary("kernel32.dll"));
}
if(!g_wow64redir_kernel32->isLoaded())
{
if(!g_wow64redir_kernel32->load())
{
return false; /*faild to load kernel32.dll*/
}
}
g_wow64redir_disable = (Wow64DisableWow64FsRedirectionFun) g_wow64redir_kernel32->resolve("Wow64DisableWow64FsRedirection");
g_wow64redir_revert = (Wow64RevertWow64FsRedirectionFun) g_wow64redir_kernel32->resolve("Wow64RevertWow64FsRedirection");
return (g_wow64redir_disable && g_wow64redir_revert);
}
#define WOW64FSREDIR_INIT(RDLOCK) do \
{ \
while(!(g_wow64redir_disable && g_wow64redir_revert)) \
{ \
(RDLOCK).unlock(); \
if(!wow64fsredir_init()) return false; \
(RDLOCK).relock(); \
} \
} \
while(0)
bool MUtils::OS::wow64fsredir_disable(void *oldValue) bool MUtils::OS::wow64fsredir_disable(void *oldValue)
{ {
QReadLocker readLock(&g_wow64redir_lock); const Wow64DisableWow64FsRedirectionFun wow64redir_disable = MUtils::Win32Utils::resolve<Wow64DisableWow64FsRedirectionFun>(QLatin1String("kernel32"), QLatin1String("Wow64DisableWow64FsRedirection"));
WOW64FSREDIR_INIT(readLock); if(wow64redir_disable)
if(g_wow64redir_disable(oldValue)) {
if (wow64redir_disable(oldValue))
{ {
return true; return true;
} }
}
return false; return false;
} }
bool MUtils::OS::wow64fsredir_revert(void *oldValue) bool MUtils::OS::wow64fsredir_revert(void *oldValue)
{ {
QReadLocker readLock(&g_wow64redir_lock); const Wow64RevertWow64FsRedirectionFun wow64redir_disable = MUtils::Win32Utils::resolve<Wow64RevertWow64FsRedirectionFun>(QLatin1String("kernel32"), QLatin1String("Wow64RevertWow64FsRedirection"));
WOW64FSREDIR_INIT(readLock); if (wow64redir_disable)
if(g_wow64redir_revert(oldValue)) {
if (wow64redir_disable(oldValue))
{ {
return true; return true;
} }
}
return false; return false;
} }

View File

@ -413,7 +413,7 @@ void MUtils::Terminal::set_icon(const QIcon &icon)
typedef DWORD (__stdcall *SetConsoleIconFun)(HICON); typedef DWORD (__stdcall *SetConsoleIconFun)(HICON);
if(SetConsoleIconFun SetConsoleIconPtr = (SetConsoleIconFun) kernel32.resolve("SetConsoleIcon")) if(SetConsoleIconFun SetConsoleIconPtr = (SetConsoleIconFun) kernel32.resolve("SetConsoleIcon"))
{ {
if(HICON hIcon = qicon_to_hicon(icon, 16, 16)) if(HICON hIcon = (HICON) MUtils::Win32Utils::qicon_to_hicon(icon, 16, 16))
{ {
SetConsoleIconPtr(hIcon); SetConsoleIconPtr(hIcon);
DestroyIcon(hIcon); DestroyIcon(hIcon);

115
src/Utils_Win32.cpp Normal file
View File

@ -0,0 +1,115 @@
///////////////////////////////////////////////////////////////////////////////
// MuldeR's Utilities for Qt
// Copyright (C) 2004-2015 LoRd_MuldeR <MuldeR2@GMX.de>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// http://www.gnu.org/licenses/lgpl-2.1.txt
//////////////////////////////////////////////////////////////////////////////////
#include "Utils_Win32.h"
//Win32 API
#ifndef _INC_WINDOWS
#define WIN32_LEAN_AND_MEAN 1
#include <Windows.h>
#endif //_INC_WINDOWS
//Qt
#include <QIcon>
#include <QPair>
#include <QReadWriteLock>
#include <QLibrary>
#include <QHash>
///////////////////////////////////////////////////////////////////////////////
// QICON TO HICON
///////////////////////////////////////////////////////////////////////////////
uintptr_t MUtils::Win32Utils::qicon_to_hicon(const QIcon &icon, const int w, const int h)
{
if(!icon.isNull())
{
QPixmap pixmap = icon.pixmap(w, h);
if(!pixmap.isNull())
{
return (uintptr_t) pixmap.toWinHICON();
}
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////
// RESOLVE FUNCTION
///////////////////////////////////////////////////////////////////////////////
typedef QHash<QString, uintptr_t> FunctionMap;
typedef QPair<QSharedPointer<QLibrary>, FunctionMap> LibraryItem;
static QReadWriteLock g_resolve_lock;
static QHash<QString, LibraryItem> g_resolve_libs;
uintptr_t MUtils::Win32Utils::resolve_helper(const QString &libraryName, const QString &functionName)
{
QReadLocker rdLock(&g_resolve_lock);
//Fuction already loaded?
const QString libNameLower = libraryName.toLower();
if (g_resolve_libs.contains(libNameLower))
{
LibraryItem &lib = g_resolve_libs[libNameLower];
if (lib.second.contains(functionName))
{
qWarning("TEST: Function already there!");
return lib.second[functionName];
}
}
//Accquire write access!
rdLock.unlock();
QWriteLocker wrLock(&g_resolve_lock);
//Load library
while (!g_resolve_libs.contains(libNameLower))
{
qWarning("TEST: Library not there -> going to load now!");
QSharedPointer<QLibrary> lib(new QLibrary(libNameLower));
if (!(lib->isLoaded() || lib->load()))
{
qWarning("Failed to load library: \"%s\"", MUTILS_UTF8(libNameLower));
return NULL;
}
g_resolve_libs.insert(libNameLower, qMakePair(lib, FunctionMap()));
}
//Lookup the function
LibraryItem &lib = g_resolve_libs[libNameLower];
while (!lib.second.contains(functionName))
{
qWarning("TEST: Function not there -> going to resolve now!");
void *const ptr = lib.first->resolve(functionName.toLatin1().constData());
if (!ptr)
{
lib.second.insert(functionName, NULL);
qWarning("Failed to resolve function: \"%s\"", MUTILS_UTF8(functionName));
return NULL;
}
qWarning("TEST: Function resolved to 0x%p", ptr);
lib.second.insert(functionName, reinterpret_cast<uintptr_t>(ptr));
}
//Return function pointer
return lib.second[functionName];
}

View File

@ -21,28 +21,21 @@
#pragma once #pragma once
//Win32 API #include <stdint.h>
#ifndef _INC_WINDOWS #include <MUtils/Global.h>
#define WIN32_LEAN_AND_MEAN 1 class QIcon;
#include <Windows.h>
#endif //_INC_WINDOWS
//Qt namespace MUtils
#include <QIcon>
///////////////////////////////////////////////////////////////////////////////
// QICON TO HICON
///////////////////////////////////////////////////////////////////////////////
static HICON qicon_to_hicon(const QIcon &icon, const int w, const int h)
{ {
if(!icon.isNull()) namespace Win32Utils
{ {
QPixmap pixmap = icon.pixmap(w, h); uintptr_t qicon_to_hicon(const QIcon &icon, const int w, const int h);
if(!pixmap.isNull()) uintptr_t resolve_helper(const QString &libraryName, const QString &functionName);
template<class T>
T resolve(const QString &libraryName, const QString &functionName)
{ {
return pixmap.toWinHICON(); return reinterpret_cast<T>(resolve_helper(libraryName, functionName));
} }
} }
return NULL;
} }