diff options
Diffstat (limited to 'src/md/enc')
28 files changed, 27447 insertions, 0 deletions
diff --git a/src/md/enc/.gitmirror b/src/md/enc/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/md/enc/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/md/enc/CMakeLists.txt b/src/md/enc/CMakeLists.txt new file mode 100644 index 0000000000..0c34b7bbc5 --- /dev/null +++ b/src/md/enc/CMakeLists.txt @@ -0,0 +1,25 @@ +add_definitions(-DNO_CRT) + +set(MDRUNTIMERW_SOURCES + liteweightstgdbrw.cpp + metamodelenc.cpp + metamodelrw.cpp + peparse.cpp + rwutil.cpp + stgio.cpp + stgtiggerstorage.cpp + stgtiggerstream.cpp + mdinternalrw.cpp +) + +convert_to_absolute_path(MDRUNTIMERW_SOURCES ${MDRUNTIMERW_SOURCES}) + +if(CLR_CMAKE_PLATFORM_UNIX) + add_compile_options(-fPIC) +endif(CLR_CMAKE_PLATFORM_UNIX) + +add_subdirectory(dac) +add_subdirectory(wks) +if(WIN32) + add_subdirectory(dbi) +endif(WIN32)
\ No newline at end of file diff --git a/src/md/enc/crossgen/.gitmirror b/src/md/enc/crossgen/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/md/enc/crossgen/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/md/enc/crossgen/MDRuntimeRW_crossgen.nativeproj b/src/md/enc/crossgen/MDRuntimeRW_crossgen.nativeproj new file mode 100644 index 0000000000..d47053fac6 --- /dev/null +++ b/src/md/enc/crossgen/MDRuntimeRW_crossgen.nativeproj @@ -0,0 +1,15 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <PropertyGroup> + <!-- All features are set in file:..\..\MD.props --> + <BuildSysBinaries>true</BuildSysBinaries> + <MetadataFlavor>wks</MetadataFlavor> + <OutputName>mdruntimerw_crossgen</OutputName> + </PropertyGroup> + + <!--Leaf project Properties--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\xplat\SetCrossGen.props" /> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\MD\enc\enc.settings.targets" /> + + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" /> +</Project> diff --git a/src/md/enc/dac/.gitmirror b/src/md/enc/dac/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/md/enc/dac/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/md/enc/dac/CMakeLists.txt b/src/md/enc/dac/CMakeLists.txt new file mode 100644 index 0000000000..b651fc28ac --- /dev/null +++ b/src/md/enc/dac/CMakeLists.txt @@ -0,0 +1,5 @@ + +include(${CLR_DIR}/dac.cmake) +include(../../md_dac.cmake) + +add_library(mdruntimerw_dac ${MDRUNTIMERW_SOURCES}) diff --git a/src/md/enc/dac/dirs.proj b/src/md/enc/dac/dirs.proj new file mode 100644 index 0000000000..eb38212025 --- /dev/null +++ b/src/md/enc/dac/dirs.proj @@ -0,0 +1,19 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <PropertyGroup> + <BuildInPhase1>true</BuildInPhase1> + <BuildInPhaseDefault>false</BuildInPhaseDefault> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + </PropertyGroup> + + <!--The following projects will build during PHASE 1--> + <ItemGroup Condition="'$(BuildExePhase)' == '1'"> + <ProjectFile Include="HostLocal\mdruntimerw_dac.nativeproj" /> + </ItemGroup> + + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" /> +</Project> diff --git a/src/md/enc/dbi/.gitmirror b/src/md/enc/dbi/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/md/enc/dbi/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/md/enc/dbi/CMakeLists.txt b/src/md/enc/dbi/CMakeLists.txt new file mode 100644 index 0000000000..7898bfafdd --- /dev/null +++ b/src/md/enc/dbi/CMakeLists.txt @@ -0,0 +1,2 @@ +include(../../md_dbi.cmake) +add_library(mdruntimerw-dbi ${MDRUNTIMERW_SOURCES})
\ No newline at end of file diff --git a/src/md/enc/dbi/MDRuntimeRW-dbi.props b/src/md/enc/dbi/MDRuntimeRW-dbi.props new file mode 100644 index 0000000000..eb5a7d44b9 --- /dev/null +++ b/src/md/enc/dbi/MDRuntimeRW-dbi.props @@ -0,0 +1,10 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <PropertyGroup> + <!-- All features are set in file:..\..\MD.props --> + <MetadataFlavor>mscordbi</MetadataFlavor> + </PropertyGroup> + + <!--Leaf project Properties--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\MD\enc\enc.settings.targets" /> + +</Project> diff --git a/src/md/enc/dbi/dirs.proj b/src/md/enc/dbi/dirs.proj new file mode 100644 index 0000000000..6a153f09a0 --- /dev/null +++ b/src/md/enc/dbi/dirs.proj @@ -0,0 +1,19 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <PropertyGroup> + <BuildInPhase1>true</BuildInPhase1> + <BuildInPhaseDefault>false</BuildInPhaseDefault> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + </PropertyGroup> + + <!--The following projects will build during PHASE 1--> + <ItemGroup Condition="'$(BuildExePhase)' == '1'"> + <ProjectFile Include="HostLocal\mdruntimerw-dbi.nativeproj" /> + </ItemGroup> + + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" /> +</Project> diff --git a/src/md/enc/dirs.proj b/src/md/enc/dirs.proj new file mode 100644 index 0000000000..32ac18cb68 --- /dev/null +++ b/src/md/enc/dirs.proj @@ -0,0 +1,23 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <PropertyGroup> + <BuildInPhase1>true</BuildInPhase1> + <BuildInPhaseDefault>false</BuildInPhaseDefault> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + </PropertyGroup> + + <!--The following projects will build during PHASE 1--> + <ItemGroup Condition="'$(BuildExePhase)' == '1'"> + <ProjectFile Include="wks\mdruntimerw.nativeproj" /> + <ProjectFile Include="dbi\dirs.proj" /> + <ProjectFile Include="dac\dirs.proj" /> + <ProjectFile Include="winrt-ro\mdruntimerw-winrt-ro.nativeproj" /> + <ProjectFile Include="winrt-rw\mdruntimerw-winrt-rw.nativeproj" /> + </ItemGroup> + + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" /> +</Project> diff --git a/src/md/enc/enc.settings.targets b/src/md/enc/enc.settings.targets new file mode 100644 index 0000000000..dcbe410196 --- /dev/null +++ b/src/md/enc/enc.settings.targets @@ -0,0 +1,45 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\MD\MD.props" /> + + <!--Leaf project Properties--> + <PropertyGroup> + <MDEncSrcDirectory>$(ClrSrcDirectory)\MD\enc\</MDEncSrcDirectory> + <UserIncludes>$(UserIncludes); + $(ClrSrcDirectory)\MD\inc; + $(ClrSrcDirectory)\vm; + $(ClrSrcDirectory)\strongname\inc + </UserIncludes> + <ClAdditionalOptions>$(ClAdditionalOptions) -DUNICODE -D_UNICODE -DNO_CRT</ClAdditionalOptions> + <OutputPath>$(ClrLibDest)</OutputPath> + <TargetType>LIBRARY</TargetType> + <PCHHeader>stdafx.h</PCHHeader> + <EnableCxxPCHHeaders>true</EnableCxxPCHHeaders> + <!--PCH: Both precompiled header and cpp are on the same ..\ path this is likely to be wrong.--> + <PCHCompile>$(MDEncSrcDirectory)\stdafx.cpp</PCHCompile> + <PCHObject>stdafx_mdruntimerw.obj</PCHObject> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="$(ClrSrcDirectory)inc\corguids.nativeproj"> + <Comment>clrinternal.h</Comment> + </ProjectReference> + </ItemGroup> + + <!--Leaf Project Items--> + <ItemGroup> + <CppCompile Include="$(MDEncSrcDirectory)\LiteWeightStgdbRW.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\MetaModelENC.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\MetaModelRW.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\peparse.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\RWUtil.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\StgIO.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\StgTiggerStorage.cpp" /> + <CppCompile Include="$(MDEncSrcDirectory)\StgTiggerStream.cpp" /> + + <CppCompile Include="$(MDEncSrcDirectory)\ImpTlb.cpp" Condition="'$(FeatureCominteropTlbSupport)' == 'true'" /> + + <!-- Content is under #ifdef FEATURE_METADATA_INTERNAL_APIS --> + <CppCompile Include="$(MDEncSrcDirectory)\MDInternalRW.cpp" /> + </ItemGroup> +</Project> diff --git a/src/md/enc/imptlb.cpp b/src/md/enc/imptlb.cpp new file mode 100644 index 0000000000..62eba2c523 --- /dev/null +++ b/src/md/enc/imptlb.cpp @@ -0,0 +1,8058 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// =========================================================================== +// File: ImpTlb.CPP +// + +// + +// --------------------------------------------------------------- +// Who When What +// --------------------------------------------------------------- +// WGE 970906 Created +// +// =========================================================================== +#include "stdafx.h" + +#include "imptlb.h" +#include <posterror.h> +#include <strongname.h> +#include <nsutilpriv.h> + +#include "..\compiler\regmeta.h" +#include "..\compiler\importhelper.h" +#include "tlbutils.h" // For GenerateMangledTypeName(). +#include <tlbimpexp.h> +#include "sstring.h" +#include "strsafe.h" + +#include <metahost.h> + +// Pointer to the activated CLR interface provided by the shim. +extern ICLRRuntimeInfo *g_pCLRRuntime; + +#ifdef wcsncmp + #undef wcsncmp +#endif +#ifdef wcsncpy + #undef wcsncpy +#endif + +// deprecated: use the secureCrt replacements +// _CRTIMP int __cdecl wcsncmp(const wchar_t *, const wchar_t *, size_t); +// _CRTIMP wchar_t * __cdecl wcsncpy(wchar_t *, const wchar_t *, size_t); + +#define S_CONVERSION_LOSS _HRESULT_TYPEDEF_(3) // Non-error code meaning a conversion lost information. + +#define ADD_ITF_MEMBERS_TO_CLASS // Define to add interface members to the CoClass. +#define ITF_MEMBER_RESOLUTION_NAMEONLY // Define to ignore signatures when looking for collisions (ie, when defined + // void Foo(int) and void Foo(String) collide). + +// defines controlling ctor of non-creatable objects. +#define NONCREATABLE_CTOR_VISIBILITY mdAssem // Define to a visibility flag. + +#define MAX_CLASSNAME_SIZE 1024 + +#ifndef lengthof +#define lengthof(rg) (sizeof(rg)/sizeof(rg[0])) +#endif + +#ifndef IfNullGo +#define IfNullGo(x) do {if (!(x)) IfFailGo(E_OUTOFMEMORY);} while (0) +#endif + +#define BUILD_CUSTOM_ATTRIBUTE(type,bytes) {*reinterpret_cast<UNALIGNED type*>(__pca) = bytes; __pca += sizeof(type); _ASSERTE(__pca-__ca <= sizeof(__ca));} +#define INIT_CUSTOM_ATTRIBUTE(n) {_ASSERTE((n) <= (sizeof(__ca)-sizeof(SHORT)));__pca = __ca; BUILD_CUSTOM_ATTRIBUTE(USHORT,1);} +#define SIZEOF_CUSTOM_ATTRIBUTE() ((ULONG) (__pca - __ca)) +#define PTROF_CUSTOM_ATTRIBUTE() (&__ca[0]) +#define DECLARE_CUSTOM_ATTRIBUTE(n) BYTE __ca[(n)+sizeof(SHORT)*2], *__pca;__pca=__ca; INIT_CUSTOM_ATTRIBUTE(n); +#define APPEND_STRING_TO_CUSTOM_ATTRIBUTE(str) {int l = (int)strlen(str); __pca=(BYTE*)CPackedLen::PutLength(__pca,l);memcpy(__pca,str,l);__pca+=l;} +#define FINISH_CUSTOM_ATTRIBUTE() {BUILD_CUSTOM_ATTRIBUTE(short,0);} + +#define DECLARE_DYNLEN_CUSTOM_ATTRIBUTE(n) CQuickArray<BYTE> __tmpCAArray; IfFailGo(__tmpCAArray.ReSizeNoThrow(n + sizeof(SHORT)*2)); BYTE *__ca, *__pca; __ca = __tmpCAArray.Ptr(); __pca=__ca; BUILD_CUSTOM_ATTRIBUTE(USHORT,1); +#define BUILD_DYNLEN_CUSTOM_ATTRIBUTE(type,bytes) {*reinterpret_cast<UNALIGNED type*>(__pca) = bytes; __pca += sizeof(type); _ASSERTE(__pca-__ca <= (int)__tmpCAArray.Size());} +#define FINISH_DYNLEN_CUSTOM_ATTRIBUTE() {BUILD_DYNLEN_CUSTOM_ATTRIBUTE(short,0);} + +#define APPEND_WIDE_STRING_TO_CUSTOM_ATTRIBUTE(str) \ +{ \ + CQuickArray<char> __tmpStr; \ + int __cStr = WszWideCharToMultiByte(CP_ACP, 0, str, -1, 0, 0, NULL, NULL); \ + IfFailGo(__tmpStr.ReSizeNoThrow(__cStr)); \ + __cStr = WszWideCharToMultiByte(CP_ACP, 0, str, -1, __tmpStr.Ptr(), __cStr, NULL, NULL); \ + __pca=(BYTE*)CPackedLen::PutLength(__pca,__cStr); \ + memcpy(__pca,__tmpStr.Ptr(),__cStr); \ + __pca+=__cStr; \ +} + +// The maximum number of bytes the encoding of a DWORD can take. +#define DWORD_MAX_CB 4 + +// The maximum number of bytes the encoding of a DWORD can take. +#define STRING_OVERHEAD_MAX_CB 4 + +// Use the unused variant types m_knowntypes for common types. +#define VT_SLOT_FOR_GUID VT_EMPTY +#define VT_SLOT_FOR_IENUMERABLE VT_NULL +#define VT_SLOT_FOR_MULTICASTDEL VT_I2 +#define VT_SLOT_FOR_TYPE VT_I4 +#define VT_SLOT_FOR_STRINGBUF VT_I8 + +static LPCWSTR szObject = W("System.Object"); +static LPCWSTR szValueType = W("System.ValueType"); +static LPCWSTR szEnum = W("System.Enum"); + +static LPCWSTR TLB_CLASSLIB_ARRAY = {W("System.Array")}; +static LPCWSTR TLB_CLASSLIB_DATE = {W("System.DateTime")}; +static LPCWSTR TLB_CLASSLIB_DECIMAL = {W("System.Decimal")}; +static LPCWSTR TLB_CLASSLIB_VARIANT = {W("System.Variant")}; +static LPCWSTR TLB_CLASSLIB_GUID = {W("System.Guid")}; +static LPCWSTR TLB_CLASSLIB_IENUMERABLE = {W("System.Collections.IEnumerable")}; +static LPCWSTR TLB_CLASSLIB_MULTICASTDELEGATE = {W("System.MulticastDelegate")}; +static LPCWSTR TLB_CLASSLIB_TYPE = {W("System.Type")}; +static LPCWSTR TLB_CLASSLIB_STRINGBUFFER = {W("System.Text.StringBuilder")}; + +static LPCWSTR COM_STDOLE2 = {W("StdOle")}; +static LPCWSTR COM_GUID = {W("GUID")}; + +static const LPCWSTR PROP_DECORATION_GET = {W("get_")}; +static const LPCWSTR PROP_DECORATION_SET = {W("set_")}; +static const LPCWSTR PROP_DECORATION_LET = {W("let_")}; +static const int PROP_DECORATION_LEN = 4; + +static const LPCWSTR DLL_EXTENSION = {W(".dll")}; +static const int DLL_EXTENSION_LEN = 4; +static const LPCWSTR EXE_EXTENSION = {W(".exe")}; +static const int EXE_EXTENSION_LEN = 4; + +static LPCWSTR const OBJECT_INITIALIZER_NAME = {W(".ctor")}; +static const int OBJECT_INITIALIZER_FLAGS = mdPublic | mdSpecialName; +static const int OBJECT_INITIALIZER_IMPL_FLAGS = miNative | miRuntime | miInternalCall; +static const int NONCREATABLE_OBJECT_INITIALIZER_FLAGS = NONCREATABLE_CTOR_VISIBILITY | mdSpecialName; + +static const COR_SIGNATURE OBJECT_INITIALIZER_SIG[3] = { + (IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS), 0, ELEMENT_TYPE_VOID }; + +static const int DEFAULT_INTERFACE_FUNC_FLAGS = mdPublic | mdVirtual | mdAbstract | mdHideBySig | mdNewSlot; +static const int DEFAULT_PROPERTY_FUNC_FLAGS = mdPublic | mdVirtual | mdAbstract | mdHideBySig | mdSpecialName | mdNewSlot; +static const int DEFAULT_CONST_FIELD_FLAGS = fdPublic | fdStatic | fdLiteral; +static const int DEFAULT_RECORD_FIELD_FLAGS = fdPublic; +static const int DELEGATE_INVOKE_FUNC_FLAGS = mdPublic | mdVirtual; + +static const int DEFAULT_ITF_FUNC_IMPL_FLAGS = miNative | miRuntime | miInternalCall; + +static const WCHAR VTBL_GAP_FUNCTION[] = {W("_VtblGap")}; +static const int VTBL_GAP_FUNCTION_FLAGS = mdPublic | mdSpecialName; +static const int VTBL_GAP_FUNC_IMPL_FLAGS = miRuntime; +static const COR_SIGNATURE VTBL_GAP_SIGNATURE[] = {IMAGE_CEE_CS_CALLCONV_DEFAULT, 0, ELEMENT_TYPE_VOID}; +static const LPCWSTR VTBL_GAP_FORMAT_1 = {W("%ls%d")}; +static const LPCWSTR VTBL_GAP_FORMAT_N = {W("%ls%d_%d")}; + +static const LPCWSTR ENUM_TYPE_NAME = {COR_ENUM_FIELD_NAME_W}; +static const DWORD ENUM_TYPE_FLAGS = fdPublic; +static const COR_SIGNATURE ENUM_TYPE_SIGNATURE[] = {IMAGE_CEE_CS_CALLCONV_FIELD, ELEMENT_TYPE_I4}; +static const DWORD ENUM_TYPE_SIGNATURE_SIZE = lengthof(ENUM_TYPE_SIGNATURE); + +static const LPCWSTR DYNAMIC_NAMESPACE_NAME = {W("DynamicModule")}; + +static const LPCWSTR UNSAFE_ITF_PREFIX = {W("Unsafe.")}; + +static const LPCWSTR GET_ENUMERATOR_MEMBER_NAME = {W("GetEnumerator")}; + +static const WCHAR CLASS_SUFFIX[] = {W("Class")}; +static const DWORD CLASS_SUFFIX_LENGTH = lengthof(CLASS_SUFFIX); +static const WCHAR EVENT_ITF_SUFFIX[] = {W("_Event")}; +static const DWORD EVENT_ITF_SUFFIX_LENGTH = lengthof(EVENT_ITF_SUFFIX); +static const WCHAR EVENT_PROVIDER_SUFFIX[] = {W("_EventProvider")}; +static const DWORD EVENT_PROVIDER_SUFFIX_LENGTH = lengthof(EVENT_ITF_SUFFIX); +static const WCHAR EVENT_HANDLER_SUFFIX[] = {W("EventHandler")}; +static const DWORD EVENT_HANDLER_SUFFIX_LENGTH = lengthof(EVENT_HANDLER_SUFFIX); + +static const WCHAR EVENT_ADD_METH_PREFIX[] = {W("add_")}; +static const DWORD EVENT_ADD_METH_PREFIX_LENGTH = lengthof(EVENT_ADD_METH_PREFIX); +static const WCHAR EVENT_REM_METH_PREFIX[] = {W("remove_")}; +static const DWORD EVENT_REM_METH_PREFIX_LENGTH = lengthof(EVENT_REM_METH_PREFIX); + +static const WCHAR DELEGATE_INVOKE_METH_NAME[] = {W("Invoke")}; +static const DWORD DELEGATE_INVOKE_METH_NAME_LENGTH = lengthof(EVENT_ADD_METH_PREFIX); + +// {C013B386-CC3E-4b6d-9B67-A3AE97274BBE} +static const GUID FREE_STATUS_GUID = +{ 0xc013b386, 0xcc3e, 0x4b6d, { 0x9b, 0x67, 0xa3, 0xae, 0x97, 0x27, 0x4b, 0xbe } }; + +// {C013B387-CC3E-4b6d-9B67-A3AE97274BBE} +static const GUID DELETED_STATUS_GUID = +{ 0xc013b387, 0xcc3e, 0x4b6d, { 0x9b, 0x67, 0xa3, 0xae, 0x97, 0x27, 0x4b, 0xbe } }; + +// {C013B388-CC3E-4b6d-9B67-A3AE97274BBE} +static const GUID USED_STATUS_GUID = +{ 0xc013b388, 0xcc3e, 0x4b6d, { 0x9b, 0x67, 0xa3, 0xae, 0x97, 0x27, 0x4b, 0xbe } }; + +static const GUID IID_IEnumerable = +{ 0x496b0abe, 0xcdee, 0x11d3, { 0x88, 0xe8, 0x00, 0x90, 0x27, 0x54, 0xc4, 0x3a } }; + + + #define STRUCTLAYOUT tdSequentialLayout +// ULONG_MAX is a flag meaning "don't convert". +static const ULONG rdwTypeFlags[] = { + tdPublic | tdSealed, // TKIND_ENUM = 0, + tdPublic | tdSealed | tdBeforeFieldInit | STRUCTLAYOUT, // TKIND_RECORD = TKIND_ENUM + 1, + tdPublic | tdAbstract, // TKIND_MODULE = TKIND_RECORD + 1, + tdPublic | tdInterface | tdAbstract | tdImport, // TKIND_INTERFACE = TKIND_MODULE + 1, + tdPublic | tdInterface | tdAbstract | tdImport, // TKIND_DISPATCH = TKIND_INTERFACE + 1, + tdPublic | tdImport, // TKIND_COCLASS = TKIND_DISPATCH + 1, + tdPublic | tdImport, // TKIND_ALIAS = TKIND_COCLASS + 1, + tdPublic | tdSealed | tdExplicitLayout, // TKIND_UNION = TKIND_ALIAS + 1, + ULONG_MAX, // TKIND_MAX = TKIND_UNION + 1 +}; +static const LPCWSTR g_szTypekind[] = { + W("Enum "), + W("Record "), + W("Module "), + W("Interface "), + W("Dispinterface"), + W("Coclass "), + W("Alias "), + W("Union "), +}; + +#define NATIVE_TYPE_NONE ((CorNativeType)(NATIVE_TYPE_MAX+1)) + +#define NON_CONVERTED_PARAMS_FLAGS (PARAMFLAG_FRETVAL|PARAMFLAG_FLCID) + + +//***************************************************************************** +// External declarations. +//***************************************************************************** +extern mdAssemblyRef DefineAssemblyRefForImportedTypeLib( + void *pAssembly, // Assembly importing the typelib. + void *pvModule, // Module importing the typelib. + IUnknown *pIMeta, // IMetaData* from import module. + IUnknown *pIUnk, // IUnknown to referenced Assembly. + BSTR *pwzNamespace, // The namespace of the resolved assembly. + BSTR *pwzAsmName, // The name of the resolved assembly. + Assembly **AssemblyRef); // The resolved assembly. + +extern mdAssemblyRef DefineAssemblyRefForExportedAssembly( + LPCWSTR szFullName, // Assembly full name. + IUnknown *pIMeta); // Metadata emit interface. + +static HRESULT _UnpackVariantToConstantBlob(VARIANT *pvar, BYTE *pcvType, void **pvValue, __int64 *pd); +static INT64 _DoubleDateToTicks(const double d); +static HRESULT TryGetFuncDesc(ITypeInfo *pITI, int i, FUNCDESC **ppFunc); + +//***************************************************************************** +// Class factory. +//***************************************************************************** +CImportTlb* CImportTlb::CreateImporter( + LPCWSTR szLibrary, + ITypeLib *pitlb, + BOOL bGenerateTCEAdapters, + BOOL bUnsafeInterfaces, + BOOL bSafeArrayAsSystemArray, + BOOL bTransformDispRetVals, + BOOL bPreventClassMembers, + BOOL bSerializableValueClasses) +{ + return new (nothrow) CImportTlb(szLibrary, pitlb, bGenerateTCEAdapters, bUnsafeInterfaces, bSafeArrayAsSystemArray, bTransformDispRetVals, bPreventClassMembers, bSerializableValueClasses); +} // CImportTlb* CImportTlb::CreateImporter() + +//***************************************************************************** +// Default constructor. +//***************************************************************************** +CImportTlb::CImportTlb() + : m_szLibrary(NULL), + m_pITLB(NULL), + m_bGenerateTCEAdapters(false), + m_bSafeArrayAsSystemArray(false), + m_bTransformDispRetVals(false), + m_bPreventClassMembers(false), + m_bSerializableValueClasses(false), + m_pEmit(NULL), + m_pImport(NULL), + m_pITI(NULL), + m_pOrigITI(NULL), + m_psAttr(NULL), + m_arSystem(mdAssemblyRefNil), + m_Notify(NULL), + m_trValueType(0), + m_trEnum(0), + m_bUnsafeInterfaces(FALSE), + m_tkSuppressCheckAttr(mdTokenNil), + m_tdHasDefault(0), + m_szName(NULL), + m_szMember(NULL), + m_wzNamespace(NULL), + m_tkInterface(0), + m_szInterface(NULL), + m_pMemberNames(NULL), + m_cMemberProps(0), + m_ImplIface(eImplIfaceNone) +{ + // Clear the known types array. The values will be lazily initialized. + memset(m_tkKnownTypes, 0, sizeof(m_tkKnownTypes)); + memset(m_tkAttr, 0, sizeof(m_tkAttr)); +} // CImportTlb::CImportTlb() + +//***************************************************************************** +// Complex constructor. +//***************************************************************************** +CImportTlb::CImportTlb( + LPCWSTR szLibrary, // Name of library being imported. + ITypeLib *pitlb, // The type library to import from. + BOOL bGenerateTCEAdapters, // A flag indicating if the TCE adapters are being generated. + BOOL bUnsafeInterfaces, // A flag indicating that runtime security checks should be disabled + BOOL bSafeArrayAsSystemArray,// A flag indicating whether to import SAFEARRAY's as System.Array's. + BOOL bTransformDispRetVals, // A flag indicating if we should do [out,retval] transformation on disp only itfs. + BOOL bPreventClassMembers, // A flag indicating if we should add members to CoClasses. + BOOL bSerializableValueClasses) // A flag indicating if we should mark value classes serializable. + : m_szLibrary(szLibrary), + m_pITLB(pitlb), + m_bGenerateTCEAdapters(bGenerateTCEAdapters), + m_bUnsafeInterfaces(bUnsafeInterfaces), + m_bSafeArrayAsSystemArray(bSafeArrayAsSystemArray), + m_bTransformDispRetVals(bTransformDispRetVals), + m_bPreventClassMembers(bPreventClassMembers), + m_bSerializableValueClasses(bSerializableValueClasses), + m_pEmit(0), + m_pImport(0), + m_pITI(0), + m_pOrigITI(0), + m_psAttr(0), + m_arSystem(mdAssemblyRefNil), + m_Notify(0), + m_trValueType(0), + m_trEnum(0), + m_tkSuppressCheckAttr(mdTokenNil), + m_tdHasDefault(0), + m_szName(0), + m_szMember(0), + m_wzNamespace(0), + m_tkInterface(0), + m_szInterface(0), + m_pMemberNames(0), + m_cMemberProps(0), + m_ImplIface(eImplIfaceNone) +{ + if (pitlb) + pitlb->AddRef(); + + // Clear the known types array. The values will be lazily initialized. + memset(m_tkKnownTypes, 0, sizeof(m_tkKnownTypes)); + memset(m_tkAttr, 0, sizeof(m_tkAttr)); + +#if defined(TLB_STATS) + m_bStats = QueryPerformanceFrequency(&m_freqVal); +#endif +} // CImportTlb::CImportTlb() + +//***************************************************************************** +// Destructor. +//***************************************************************************** +CImportTlb::~CImportTlb() +{ + if (m_pEmit) + m_pEmit->Release(); + if (m_pImport) + m_pImport->Release(); + if (m_pITLB) + m_pITLB->Release(); + if (m_Notify) + m_Notify->Release(); + + if (m_wzNamespace) + ::SysFreeString(m_wzNamespace); +} // CImportTlb::~CImportTlb() + + +//***************************************************************************** +// Allow the user to specify a namespace to be used in the conversion. +//***************************************************************************** +HRESULT CImportTlb::SetNamespace( + WCHAR const *pNamespace) +{ + HRESULT hr=S_OK; // A result. + + IfNullGo(m_wzNamespace=::SysAllocString(pNamespace)); + +ErrExit: + + return hr; +} // HRESULT CImportTlb::SetNamespace() + +//***************************************************************************** +// Allow the user to specify a notification object to be used in the conversion. +//***************************************************************************** +HRESULT CImportTlb::SetNotification( + ITypeLibImporterNotifySink *pNotify) +{ + _ASSERTE(m_Notify == 0); + m_Notify = pNotify; + pNotify->AddRef(); + + return S_OK; +} // HRESULT CImportTlb::SetNotification() + +//***************************************************************************** +// Allow the user to specify the MetaData scope to be used in the conversion. +//***************************************************************************** +HRESULT CImportTlb::SetMetaData( + IUnknown *pIUnk) +{ + HRESULT hr; + _ASSERTE(m_pEmit == 0); + IfFailGo(pIUnk->QueryInterface(IID_IMetaDataEmit2, (void**)&m_pEmit)); +ErrExit: + return hr; +} // HRESULT CImportTlb::SetMetaData() + +//***************************************************************************** +// Import a TypeLibrary into a CompLib. +//***************************************************************************** +HRESULT CImportTlb::Import() +{ +#ifndef DACCESS_COMPILE + HRESULT hr; // A result. + mdModule md; // Module token. + VARIANT vt = {0}; // For setting options. + ITypeLib2 *pITLB2 = 0; // To get custom attributes. + IMetaDataDispenserEx *pDisp = 0; // To create export scope. + TLIBATTR *psAttr=0; // The library's attributes. + BSTR szLibraryName = 0; // The library's name. + LPCWSTR wzFile; // The filename of the typelib (no path). + LPCWSTR wzSource; // Source of the typelib, for CA. + + _ASSERTE(m_Notify); + + // Quick sanity check. + if (!m_pITLB) + return (E_INVALIDARG); + + // Check to see if the type library implements ITypeLib2. + if (m_pITLB->QueryInterface(IID_ITypeLib2, (void **)&pITLB2) != S_OK) + pITLB2 = 0; + + // If custom attribute for namespace exists, use it. + if (pITLB2) + { + VARIANT vt; + VariantInit(&vt); + if (pITLB2->GetCustData(GUID_ManagedName, &vt) == S_OK) + { + if (V_VT(&vt) == VT_BSTR) + { + // If there already was a namespace set, release it. + if (m_wzNamespace) + SysFreeString(m_wzNamespace); + + // If the namespace ends with .dll then remove the extension. + LPWSTR pDest = wcsstr(vt.bstrVal, DLL_EXTENSION); + if (pDest && (pDest[DLL_EXTENSION_LEN] == 0 || pDest[DLL_EXTENSION_LEN] == ' ')) + *pDest = 0; + + if (!pDest) + { + // If the namespace ends with .exe then remove the extension. + pDest = wcsstr(vt.bstrVal, EXE_EXTENSION); + if (pDest && (pDest[EXE_EXTENSION_LEN] == 0 || pDest[EXE_EXTENSION_LEN] == ' ')) + *pDest = 0; + } + + if (pDest) + { + // We removed the extension so re-allocate a string of the new length. + m_wzNamespace = SysAllocString(vt.bstrVal); + SysFreeString(vt.bstrVal); + IfNullGo(m_wzNamespace); + } + else + { + // There was no extension to remove so we can use the string returned + // by GetCustData(). + m_wzNamespace = vt.bstrVal; + } + } + else + { + VariantClear(&vt); + } + } + } + + // Use the namespace name if we don't know the filename. + if (!m_szLibrary) + m_szLibrary = m_wzNamespace; + + // If the typelib was exported from COM+ to begin with, don't import it. + if (pITLB2) + { + ::VariantInit(&vt); + hr = pITLB2->GetCustData(GUID_ExportedFromComPlus, &vt); + if (vt.vt != VT_EMPTY) + { + if (0) + { + // com emulates option is ON + } + else + { + IfFailGo(PostError(TLBX_E_CIRCULAR_IMPORT, m_szLibrary)); + } + } + } + + _ASSERTE(m_pEmit); + IfFailGo(m_pEmit->QueryInterface(IID_IMetaDataImport2, (void **)&m_pImport)); + + // Initialize the reserved names map. + IfFailGo(m_ReservedNames.Init()); + + // Initialize the default interface to class interface map for the TLB being imported. + IfFailGo(m_DefItfToClassItfMap.Init(m_pITLB, m_wzNamespace)); + + // Create the Object classref record and AssemblyRef for mscorlib.dll. + IfFailGo(_DefineSysRefs()); + + // Create the library record. + IfFailGo(_NewLibraryObject()); + + // Note that this was imported. + IfFailGo(m_pITLB->GetLibAttr(&psAttr)); + if (SUCCEEDED(::QueryPathOfRegTypeLib(psAttr->guid, psAttr->wMajorVerNum, psAttr->wMinorVerNum, psAttr->lcid, &szLibraryName))) + wzSource = szLibraryName; + else + wzSource = m_szLibrary; + + // We can't base the decision on SYSKIND. For example, we can have a SYS_WIN64 tlb loaded as 32-bit with 4-byte aligned pointers. + m_cbVtableSlot = 0; + + IfFailGo(m_pImport->GetModuleFromScope(&md)); + // Skip the path or drive info + wzFile = wcsrchr(wzSource, W('\\')); + if (wzFile == 0) + { // That's odd, should have been a fully qualified path. Just use an empty string. + wzFile = W(""); + } + else + { // skip leading backslash + wzFile++; + } + + // Convert the typelib. + IfFailGo(ConvertTypeLib()); + +ErrExit: + if (psAttr) + m_pITLB->ReleaseTLibAttr(psAttr); + if (szLibraryName) + ::SysFreeString(szLibraryName); + if (pITLB2) + pITLB2->Release(); + if (pDisp) + pDisp->Release(); + + return (hr); +#else + DacNotImpl(); + return E_NOTIMPL; +#endif // #ifndef DACCESS_COMPILE +} // HRESULT CImportTlb::Import() + +//***************************************************************************** +// Create the Complib to represent the TypeLib. +//***************************************************************************** +HRESULT CImportTlb::_NewLibraryObject() +{ + HRESULT hr; // A result. + TLIBATTR * psAttr=0; // The library's attributes. + BSTR szLibraryName=0; // The library's name. + CQuickArray<WCHAR> rScopeName; // The name of the scope. + + // Information about the library. + IfFailGo(m_pITLB->GetLibAttr(&psAttr)); + IfFailGo(m_pITLB->GetDocumentation(MEMBERID_NIL, &szLibraryName, 0, 0, 0)); + + // Create the scope name by using the typelib name and adding .dll. + IfFailGo(rScopeName.ReSizeNoThrow(SysStringLen(szLibraryName) + 5 * sizeof(WCHAR))); + StringCchPrintf(rScopeName.Ptr(), rScopeName.Size(), W("%s.dll"), szLibraryName); + + IfFailGo(m_pEmit->SetModuleProps(rScopeName.Ptr())); + +ErrExit: + if (psAttr) + m_pITLB->ReleaseTLibAttr(psAttr); + + if (szLibraryName) + ::SysFreeString(szLibraryName); + + return (hr); +} // HRESULT CImportTlb::_NewLibraryObject() + +//***************************************************************************** +// Define an assembly ref for mscorlib, typeref for Object. +//***************************************************************************** +HRESULT CImportTlb::_DefineSysRefs() +{ + HRESULT hr; // A result. + WCHAR szPath[_MAX_PATH]; + WCHAR szDrive[_MAX_DRIVE]; + WCHAR szDir[_MAX_PATH]; + DWORD dwLen; // Length of system directory name. + IMetaDataDispenserEx *pDisp = 0; // To import mscorlib. + IMetaDataAssemblyImport *pAImp = 0; // To read mscorlib assembly. + IMetaDataAssemblyEmit *pAEmit = 0; // To create mscorlib assembly ref. + ASSEMBLYMETADATA amd = {0}; // Assembly metadata. + mdToken tk; // A token. + const void *pvPublicKey; // Public key. + ULONG cbPublicKey; // Length of public key. + BYTE *pbToken=0; // Compressed token for public key. + ULONG cbToken; // Length of token. + ULONG ulHashAlg; // Hash algorithm. + DWORD dwFlags; // Assembly flags. + + // Get the dispenser. + IfFailGo(g_pCLRRuntime->GetInterface( + CLSID_CorMetaDataDispenser, + IID_IMetaDataDispenserEx, + (void **)&pDisp)); + + // Get the name of mscorlib. + //@todo: define, function, etc., instead of hard coded "mscorlib" + dwLen = lengthof(szPath) - 13; // allow space for "mscorlib" ".dll" "\0" + IfFailGo(pDisp->GetCORSystemDirectory(szPath, dwLen, &dwLen)); + SplitPath(szPath, szDrive, _MAX_DRIVE, szDir, _MAX_PATH, 0, 0, 0, 0); + MakePath(szPath, szDrive, szDir, W("mscorlib"), W(".dll")); + + // Open the scope, get the details. + IfFailGo(pDisp->OpenScope(szPath, 0, IID_IMetaDataAssemblyImport, (IUnknown**)&pAImp)); + IfFailGo(pAImp->GetAssemblyFromScope(&tk)); + IfFailGo(pAImp->GetAssemblyProps(tk, &pvPublicKey,&cbPublicKey, &ulHashAlg, + szPath,lengthof(szPath),&dwLen, &amd, &dwFlags)); + + if (!StrongNameTokenFromPublicKey((BYTE*)(pvPublicKey),cbPublicKey, &pbToken,&cbToken)) + { + hr = StrongNameErrorInfo(); + goto ErrExit; + } + dwFlags &= ~afPublicKey; + + // Define the assembly ref. + IfFailGo(m_pEmit->QueryInterface(IID_IMetaDataAssemblyEmit, (void**)&pAEmit)); + IfFailGo(pAEmit->DefineAssemblyRef(pbToken,cbToken, szPath, &amd,0,0,dwFlags, &m_arSystem)); + + IfFailGo(m_TRMap.DefineTypeRef(m_pEmit, m_arSystem, szObject, &m_trObject)); + + m_tkKnownTypes[VT_DISPATCH] = m_trObject; + m_tkKnownTypes[VT_UNKNOWN] = m_trObject; + m_tkKnownTypes[VT_VARIANT] = m_trObject; + +ErrExit: + if (pbToken) + StrongNameFreeBuffer(pbToken); + if (pDisp) + pDisp->Release(); + if (pAEmit) + pAEmit->Release(); + if (pAImp) + pAImp->Release(); + + return hr; +} // HRESULT CImportTlb::_DefineSysRefs() + +//***************************************************************************** +// Lazily get the token for a CustomAttribute. +//***************************************************************************** +HRESULT CImportTlb::GetAttrType( + int attr, // The attribute for which the type is desired. + mdToken *pTk) // Put the type here. +{ + HRESULT hr = S_OK; // A result. + mdTypeRef tr; // An intermediate typeref. + DWORD dwSigSize; // The size of the sig for special sigs. + DWORD dwMaxSigSize; // The max size of the special sig. + COR_SIGNATURE *pSig; // Pointer to the start of the sig, + COR_SIGNATURE *pCurr; // Current sig pointer. + mdTypeRef trType; // The typeref for System.Type. + + _ASSERTE(attr >= 0); + _ASSERTE(attr < ATTR_COUNT); + + //@todo: globally define these names. +#define INTEROP_ATTRIBUTE(x) static COR_SIGNATURE x##_SIG[] = INTEROP_##x##_SIG; +#define INTEROP_ATTRIBUTE_SPECIAL(x) + INTEROP_ATTRIBUTES(); +#undef INTEROP_ATTRIBUTE +#undef INTEROP_ATTRIBUTE_SPECIAL +#define INTEROP_ATTRIBUTE(x) \ + case ATTR_##x: \ + IfFailGo(m_pEmit->DefineTypeRefByName(m_arSystem, INTEROP_##x##_TYPE_W, &tr)); \ + IfFailGo(m_pEmit->DefineMemberRef(tr, W(".ctor"), x##_SIG, lengthof(x##_SIG), &m_tkAttr[attr])); \ + break; +#define INTEROP_ATTRIBUTE_SPECIAL(x) + + if (IsNilToken(m_tkAttr[attr])) + { + switch (attr) + { + INTEROP_ATTRIBUTES(); + + case ATTR_COMEVENTINTERFACE: + { + // Retrieve token for System.Type. + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_TYPE, &trType)); + + // Build the sig. + dwMaxSigSize = 5 + sizeof(mdTypeRef) * 2; + pSig = (COR_SIGNATURE*)_alloca(dwMaxSigSize); + pCurr = pSig; + *pCurr++ = IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS; + *pCurr++ = 2; + *pCurr++ = ELEMENT_TYPE_VOID; + *pCurr++ = ELEMENT_TYPE_CLASS; + pCurr += CorSigCompressToken(trType, pCurr); + *pCurr++ = ELEMENT_TYPE_CLASS; + pCurr += CorSigCompressToken(trType, pCurr); + dwSigSize = (DWORD)(pCurr - pSig); + _ASSERTE(dwSigSize <= dwMaxSigSize); + + // Declare the typeref and the member ref for the CA. + IfFailGo(m_pEmit->DefineTypeRefByName(m_arSystem, INTEROP_COMEVENTINTERFACE_TYPE_W, &tr)); \ + IfFailGo(m_pEmit->DefineMemberRef(tr, W(".ctor"), pSig, dwSigSize, &m_tkAttr[attr])); \ + break; + } + + case ATTR_COCLASS: + { + // Retrieve token for System.Type. + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_TYPE, &trType)); + + // Build the sig. + dwMaxSigSize = 4 + sizeof(mdTypeRef); + pSig = (COR_SIGNATURE*)_alloca(dwMaxSigSize); + pCurr = pSig; + *pCurr++ = IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS; + *pCurr++ = 1; + *pCurr++ = ELEMENT_TYPE_VOID; + *pCurr++ = ELEMENT_TYPE_CLASS; + pCurr += CorSigCompressToken(trType, pCurr); + dwSigSize = (DWORD)(pCurr - pSig); + _ASSERTE(dwSigSize <= dwMaxSigSize); + + // Declare the typeref and the member ref for the CA. + IfFailGo(m_pEmit->DefineTypeRefByName(m_arSystem, INTEROP_COCLASS_TYPE_W, &tr)); \ + IfFailGo(m_pEmit->DefineMemberRef(tr, W(".ctor"), pSig, dwSigSize, &m_tkAttr[attr])); \ + break; + } + } + } +#undef INTEROP_ATTRIBUTE +#undef INTEROP_ATTRIBUTE_SPECIAL + + *pTk = m_tkAttr[attr]; +ErrExit: + return hr; +} // HRESULT CImportTlb::GetAttrType() + +//***************************************************************************** +// Create the TypeDefs. +//***************************************************************************** +HRESULT +CImportTlb::ConvertTypeLib() +{ + HRESULT hr; + int cTi; // Count of TypeInfos. + int i; // Loop control. + + // How many TypeInfos? + IfFailGo(cTi = m_pITLB->GetTypeInfoCount()); + + // Iterate over them. + for (i = 0; i < cTi; ++i) + { + // Get the TypeInfo. + hr = m_pITLB->GetTypeInfo(i, &m_pITI); + if (SUCCEEDED(hr)) + { + // Save up the original TypeInfo (may be later alias-resolved). + _ASSERTE(m_pOrigITI == NULL); + m_pOrigITI = m_pITI; + m_pOrigITI->AddRef(); + + // Retrieve the attributes of the type info. + IfFailGo(m_pITI->GetTypeAttr(&m_psAttr)); + + // Convert the TypeInfo. + hr = ConvertTypeInfo(); + if (FAILED(hr)) + { + if (hr == CEE_E_CVTRES_NOT_FOUND || hr == TLBX_I_RESOLVEREFFAILED) + { // Reflection emit is broken, no need to try to continue. + goto ErrExit; + } + + BSTR szTypeInfoName = NULL; + hr = m_pITI->GetDocumentation(MEMBERID_NIL, &szTypeInfoName, 0, 0, 0); + if (SUCCEEDED(hr)) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_INVALID_TYPEINFO, szTypeInfoName); + } + else + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_INVALID_TYPEINFO_UNNAMED, i); + } + if (szTypeInfoName != NULL) + ::SysFreeString(szTypeInfoName); +#if defined(_DEBUG) + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_TlbImp_BreakOnErr)) + _ASSERTE(!"Invalid type"); +#endif + } + + // Release for next TypeInfo. + m_pOrigITI->Release(); + m_pOrigITI = NULL; + + m_pITI->ReleaseTypeAttr(m_psAttr); + m_psAttr = NULL; + m_pITI->Release(); + m_pITI = NULL; + } + } + +ErrExit: + if (m_pOrigITI != NULL) + { + m_pOrigITI->Release(); + m_pOrigITI = NULL; + } + + if (m_psAttr != NULL) + { + m_pITI->ReleaseTypeAttr(m_psAttr); + m_psAttr = NULL; + } + if (m_pITI != NULL) + { + m_pITI->Release(); + m_pITI = NULL; + } + return hr; +} // CImportTlb::ConvertTypeLib + +//***************************************************************************** +// Convert a single ITypeInfo into the scope. +//***************************************************************************** +HRESULT CImportTlb::ConvertTypeInfo() // S_OK or error. +{ + HRESULT hr; // A result. + BSTR bstrManagedName=0; // Managed name (or part thereof). + CQuickArray<WCHAR> qbClassName; // The name of the class. + ULONG ulFlags; // TypeDef flags. + WORD wTypeInfoFlags; // TypeInfo flags. Alias flags, if an alias. + mdToken tkAttr; // Attribute type for flags. + TYPEKIND tkindAlias; // TYPEKIND of an aliased TypeInfo. + GUID guid; // GUID of the typeinfo. + BOOL bConversionLoss=false; // If true, info was lost converting sigs. + mdToken tkParent; // Parent of the typedef. + mdToken td; // For looking up a TypeDef. + ITypeInfo2 *pITI2=0; // For getting custom value. + +#if defined(TLB_STATS) + WCHAR rcStats[16]; // Buffer for stats. + LARGE_INTEGER __startVal; + QueryPerformanceCounter(&__startVal); +#endif + + m_tdTypeDef = mdTypeDefNil; + + // Get some information about the TypeInfo. + IfFailGo(m_pITI->GetDocumentation(MEMBERID_NIL, &m_szName, 0, 0, 0)); + +#if defined(_DEBUG) + LPWSTR strShouldBreakOnTypeName = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_TlbImp_BreakOnTypeImport); + if ((NULL != strShouldBreakOnTypeName) && (wcsncmp(strShouldBreakOnTypeName, m_szName, MAX_CLASSNAME_LENGTH) == 0)) + _ASSERTE(!"MD_TlbImp_BreakOnTypeImport"); +#endif + + // Assume that we will be able to convert the typeinfo. + guid = m_psAttr->guid; + wTypeInfoFlags = m_psAttr->wTypeFlags; + + // If this typeinfo is an alias, see what it is an alias for. If for a built-in + // type, we will just skip it. If for a user-defined type, we will duplicate + // that definition under this alias' name and guid. + if (m_psAttr->typekind == TKIND_ALIAS) + { + hr = _ResolveTypeDescAliasTypeKind(m_pITI, &m_psAttr->tdescAlias, &tkindAlias); + IfFailGo(hr); + if (hr == S_OK) + { + TYPEDESC tdesc = m_psAttr->tdescAlias; + m_pITI->ReleaseTypeAttr(m_psAttr); + m_pITI->Release(); + + IfFailGo(_ResolveTypeDescAlias(m_pOrigITI, &tdesc, &m_pITI, &m_psAttr, &guid)); + // Now m_pOrigITI refers to the alias whereas m_pITI is the TypeInfo of the aliased type. + + // We should no longer have an alias. + _ASSERTE(m_psAttr->typekind == tkindAlias); + _ASSERTE(tkindAlias != TKIND_ALIAS); + + ulFlags = rdwTypeFlags[tkindAlias]; + } + else + ulFlags = ULONG_MAX; + } + else + { + ulFlags = rdwTypeFlags[m_psAttr->typekind]; + } + + // Figure out the name. + + // If the type info is for a CoClass, we need to decorate the name. + if (m_psAttr->typekind == TKIND_COCLASS) + { + // Generate a mangled name for the component. + IfFailGo(GetManagedNameForCoClass(m_pOrigITI, qbClassName)); + m_szMngName = qbClassName.Ptr(); + } + else + { + IfFailGo(GetManagedNameForTypeInfo(m_pOrigITI, m_wzNamespace, NULL, &bstrManagedName)); + m_szMngName = bstrManagedName; + } + + if (m_psAttr->typekind == TKIND_INTERFACE || + (m_psAttr->typekind == TKIND_DISPATCH && m_psAttr->wTypeFlags & TYPEFLAG_FDUAL)) + { + // If the interface is not derived from IUnknown, or not an interface, we can't convert it. + if (IsIUnknownDerived(m_pITI, m_psAttr) != S_OK) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_NOTIUNKNOWN, m_szName); + ulFlags = ULONG_MAX; + } + // If the interface is not derived from IDispatch, but claims to be [dual], give a warning but convert it. + if ((m_psAttr->wTypeFlags & TYPEFLAG_FDUAL) && IsIDispatchDerived(m_pITI, m_psAttr) != S_OK) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_W_DUAL_NOT_DISPATCH, m_szName); + } + } + else + if (m_psAttr->typekind == TKIND_MODULE) + { // If module has no vars, skip it. We currently don't import module functions. + if (m_psAttr->cVars == 0) + ulFlags = ULONG_MAX; + } + + // If something we can convert... + if (ulFlags != ULONG_MAX) + { + // Interfaces derive from nil... + if (IsTdInterface(ulFlags)) + tkParent = mdTypeDefNil; + else // ... enums from Enum, ... + if (m_psAttr->typekind == TKIND_ENUM) + { + if (IsNilToken(m_trEnum)) + IfFailGo(m_TRMap.DefineTypeRef(m_pEmit, m_arSystem, szEnum, &m_trEnum)); + tkParent = m_trEnum; + } + else // ... structs from ValueType, ... + if (m_psAttr->typekind == TKIND_RECORD || m_psAttr->typekind == TKIND_UNION) + { + if (IsNilToken(m_trValueType)) + IfFailGo(m_TRMap.DefineTypeRef(m_pEmit, m_arSystem, szValueType, &m_trValueType)); + tkParent = m_trValueType; + } + else // ... and classes derive from Object. + tkParent = m_trObject; + + // The typelib importer generates metadata into an empty ReflectionEmit scope. Because + // RE manages type names itself, duplicate checking is turned off. Because of user-defined + // names (via CUSTOM), it is possible for the user to declare a duplicate. So, + // before adding the new type, check for duplicates. + hr = m_pImport->FindTypeDefByName(m_szMngName, mdTypeDefNil, &td); + if (hr != CLDB_E_RECORD_NOTFOUND) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_DUPLICATE_TYPE_NAME, m_szMngName); + IfFailGo(TLBX_E_DUPLICATE_TYPE_NAME); + } + + // Create the typedef. + IfFailGo(m_pEmit->DefineTypeDef(m_szMngName, ulFlags, tkParent, 0, &m_tdTypeDef)); + IfFailGo(_AddGuidCa(m_tdTypeDef, guid)); + + // Save the typeinfo flags. + if (wTypeInfoFlags) + { + IfFailGo(GetAttrType(ATTR_TYPELIBTYPE, &tkAttr)); + DECLARE_CUSTOM_ATTRIBUTE(sizeof(WORD)); + BUILD_CUSTOM_ATTRIBUTE(WORD, wTypeInfoFlags); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + // Mark unsafe interfaces (suppressed security runtime checks). + if (m_bUnsafeInterfaces) + { + if (m_tkSuppressCheckAttr == mdTokenNil) + { + mdTypeRef tr; + COR_SIGNATURE rSig[] = {IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS, 0, ELEMENT_TYPE_VOID}; + IfFailGo(m_pEmit->DefineTypeRefByName(m_arSystem, COR_SUPPRESS_UNMANAGED_CODE_CHECK_ATTRIBUTE, &tr)); + IfFailGo(m_pEmit->DefineMemberRef(tr, COR_CTOR_METHOD_NAME_W, rSig, lengthof(rSig), &m_tkSuppressCheckAttr)); + } + + DECLARE_CUSTOM_ATTRIBUTE(0); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, m_tkSuppressCheckAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + + // Fill in the details depending on the type of the TypeInfo. + switch (m_psAttr->typekind) + { + case TKIND_ENUM: + hr = ConvEnum(m_pITI, m_psAttr); + break; + + case TKIND_RECORD: + hr = ConvRecord(m_pITI, m_psAttr, FALSE); + break; + + case TKIND_UNION: + hr = ConvRecord(m_pITI, m_psAttr, TRUE); + break; + + case TKIND_MODULE: + hr = ConvModule(m_pITI, m_psAttr); + break; + + case TKIND_INTERFACE: + hr = ConvIface(m_pITI, m_psAttr); + break; + + case TKIND_DISPATCH: + hr = ConvDispatch(m_pITI, m_psAttr); + break; + + case TKIND_COCLASS: + hr = ConvCoclass(m_pITI, m_psAttr); + break; + + case TKIND_ALIAS: + _ASSERTE(!"Alias should have been resolved!"); + break; + + default: + _ASSERTE(!"Unexpected TYPEKIND"); + break; + } + if (FAILED(hr)) + goto ErrExit; + + if (hr == S_CONVERSION_LOSS) + { + bConversionLoss = true; + IfFailGo(GetAttrType(ATTR_COMCONVERSIONLOSS, &tkAttr)); + DECLARE_CUSTOM_ATTRIBUTE(0); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(),SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + } + + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + else + hr = S_OK; + +#if defined(TLB_STATS) + LARGE_INTEGER __stopVal; + QueryPerformanceCounter(&__stopVal); + DWORD __delta; + __delta = (DWORD)(__stopVal.QuadPart - __startVal.QuadPart); + StringCchPrintf(rcStats, COUNTOF(rcStats), W(" %.2f"), + ((float)__delta*1000)/(float)m_freqVal.QuadPart); +#endif + + // Report that this type has been converted. + ReportEvent(NOTIF_TYPECONVERTED, TLBX_I_TYPEINFO_IMPORTED, m_szName); + +ErrExit: + if (pITI2) + pITI2->Release(); + if (m_szName) + ::SysFreeString(m_szName), m_szName = 0; + if (bstrManagedName) + ::SysFreeString(bstrManagedName); + return (hr); +} // HRESULT CImportTlb::ConvertTypeInfo() + + +//***************************************************************************** +// Determine if the type explicitly implements IEnumerable. +//***************************************************************************** +HRESULT CImportTlb::ExplicitlyImplementsIEnumerable( + ITypeInfo *pITI, // ITypeInfo* to check for IEnumerable. + TYPEATTR *psAttr, // TYPEATTR of TypeInfo. + BOOL fLookupPartner) // Flag indicating if we should look at the partner itf. +{ + HREFTYPE href; // HREFTYPE of an implemented interface. + ITypeInfo *pItiIface=0; // ITypeInfo for an interface. + TYPEATTR *psAttrIface=0; // TYPEATTR for an interface. + BOOL fFoundImpl = FALSE; + int i = 0; + HRESULT hr = S_OK; + ITypeInfo* pITISelf2 = NULL; + TYPEATTR psAttrSelf2; + int ImplFlags = 0; + + // Look through each of the implemented/inherited interfaces + for (i=0; i<psAttr->cImplTypes && !fFoundImpl; ++i) + { + // Get an interface + IfFailGo(pITI->GetRefTypeOfImplType(i, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pItiIface)); + IfFailGo(pItiIface->GetTypeAttr(&psAttrIface)); + IfFailGo(pITI->GetImplTypeFlags(i, &ImplFlags)); + + if (!(ImplFlags & IMPLTYPEFLAG_FSOURCE)) + { + hr = ExplicitlyImplementsIEnumerable(pItiIface, psAttrIface, TRUE); + if (hr == S_OK) + fFoundImpl = TRUE; + + // Check this interface for the IEnumerable. + if (psAttrIface->guid == IID_IEnumerable) + fFoundImpl = TRUE; + } + + pItiIface->ReleaseTypeAttr(psAttrIface); + psAttrIface = 0; + pItiIface->Release(); + pItiIface = 0; + } + + if ( fLookupPartner && (pITI->GetRefTypeOfImplType(-1, &href) == S_OK) ) + { + IfFailGo(pITI->GetRefTypeInfo(href, &pItiIface)); + IfFailGo(pItiIface->GetTypeAttr(&psAttrIface)); + + hr = ExplicitlyImplementsIEnumerable(pItiIface, psAttrIface, FALSE); + if (hr == S_OK) + fFoundImpl = TRUE; + + // Check this interface for the IEnumerable. + if (psAttrIface->guid == IID_IEnumerable) + fFoundImpl = TRUE; + } + + +ErrExit: + if (psAttrIface) + pItiIface->ReleaseTypeAttr(psAttrIface); + if (pItiIface) + pItiIface->Release(); + + return (fFoundImpl) ? S_OK : S_FALSE; +} + + +//***************************************************************************** +// Convert the details for a coclass. +//***************************************************************************** +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +HRESULT CImportTlb::ConvCoclass( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr) // TYPEATTR of TypeInfo. +{ + BOOL fHadDefaultItf = FALSE; + HRESULT hr; // A result. + int i; // Loop control. + HREFTYPE href; // HREFTYPE of an implemented interface. + ITypeInfo *pItiIface=0; // ITypeInfo for an interface. + TYPEATTR *psAttrIface=0; // TYPEATTR for an interface. + int ImplFlags; // ImplType flags. + mdToken tkIface; // Token for an interface. + CQuickArray<mdToken> rImpls; // Array of implemented interfaces. + CQuickArray<mdToken> rEvents; // Array of implemented event interfaces. + CQuickArray<mdToken> rTmpImpls; // Temporary array of impls. + CQuickArray<ITypeInfo*> rImplTypes; // Array of implemented ITypeInfo*s. + CQuickArray<ITypeInfo*> rSrcTypes; // Array of source ITypeInfo*s. + int ixSrc; // Index into rSrcTypes for source interfaces. + int ixImpl; // Index into rImpls for implemented interface. + int ixTmpImpl; // Index into rTmpImpls. + mdToken mdCtor; // Dummy token for the object initializer. + mdToken tkAttr; // Token for custom attribute type. + mdToken token; // Dummy token for typeref. + BOOL fInheritsIEnum = FALSE; + +#ifdef _DEBUG + int bImplIEnumerable=0; // If true, the class implements IEnumerable. +#endif + + // Size the rImpls and rSrcs arrays large enough for impls, events, the IEnumerable itf and two ending nulls. + IfFailGo(rImpls.ReSizeNoThrow(psAttr->cImplTypes+2)); + memset(rImpls.Ptr(), 0, (psAttr->cImplTypes+2)*sizeof(mdToken)); + IfFailGo(rEvents.ReSizeNoThrow(psAttr->cImplTypes+1)); + memset(rEvents.Ptr(), 0, (psAttr->cImplTypes+1)*sizeof(mdToken)); + IfFailGo(rTmpImpls.ReSizeNoThrow(psAttr->cImplTypes+3)); + memset(rTmpImpls.Ptr(), 0, (psAttr->cImplTypes+3)*sizeof(mdToken)); + IfFailGo(rImplTypes.ReSizeNoThrow(psAttr->cImplTypes+2)); + memset(rImplTypes.Ptr(), 0, (psAttr->cImplTypes+2)*sizeof(ITypeInfo*)); + IfFailGo(rSrcTypes.ReSizeNoThrow(psAttr->cImplTypes+1)); + memset(rSrcTypes.Ptr(), 0, (psAttr->cImplTypes+1)*sizeof(ITypeInfo*)); + ixImpl = -1; + ixSrc = -1; + ixTmpImpl = -1; + + if (ExplicitlyImplementsIEnumerable(pITI, psAttr) == S_OK) + fInheritsIEnum = TRUE; + + // Build the list of implemented and event interfaces. + // The EE cares about implemented interfaces, so we convert them to actual + // tokens and add them to the typedef. VB cares about event interfaces, + // but we are going to add a list of typeref names as a custom attribute. + // We can't build the list as we go along, because the default may not + // be the first event source. So, we store tokens for the implemented + // interfaces, but ITypeInfo*s for the event sources. + for (i=0; i<psAttr->cImplTypes; ++i) + { + IfFailGo(pITI->GetRefTypeOfImplType(i, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pItiIface)); + IfFailGo(pItiIface->GetTypeAttr(&psAttrIface)); + IfFailGo(pITI->GetImplTypeFlags(i, &ImplFlags)); + + // If the interface is derived from IUnknown, or not an interface, we can't use it as an interface. + // Don't add explicit IUnknown or IDispatch. + if ((IsIUnknownDerived(pItiIface, psAttrIface) != S_OK && psAttrIface->typekind != TKIND_DISPATCH) || + psAttrIface->guid == IID_IDispatch || + psAttrIface->guid == IID_IUnknown) + { + pItiIface->ReleaseTypeAttr(psAttrIface); + psAttrIface = 0; + pItiIface->Release(); + pItiIface = 0; + continue; + } + + // Add the event to the impls list or the events list. + if (ImplFlags & IMPLTYPEFLAG_FSOURCE) + { + // Get the token for the event interface. + IfFailGo(_GetTokenForEventItf(pItiIface, &tkIface)); + + // If we've already marked this CoClass as implementing this source interface, don't do so again. + for (int iCheck=0; iCheck <= ixSrc; iCheck++) + { + if (rEvents[iCheck] == tkIface) + goto LoopEnd; + } + + // Add the source interface to the list of source interfaces. + ++ixSrc; + + // If this is explicitly the default source interface... + if (ImplFlags & IMPLTYPEFLAG_FDEFAULT) + { + // Put the def source ITypeInfo at the head of the list of source + // ITypeInfo's. + for (int ix = ixSrc; ix > 0; --ix) + { + rSrcTypes[ix] = rSrcTypes[ix-1]; + rEvents[ix] = rEvents[ix-1]; + } + rEvents[0] = tkIface; + rSrcTypes[0] = pItiIface; + } + else + { + rEvents[ixSrc] = tkIface; + rSrcTypes[ixSrc] = pItiIface; + } + } + else + { + // Get the token for the interface. + IfFailGo(_GetTokenForTypeInfo(pItiIface, FALSE, &tkIface)); + + // If we've already marked this CoClass as implementing this interface, don't do so again. + for (int iCheck=0; iCheck <= ixImpl; iCheck++) + { + if (rImpls[iCheck] == tkIface) + goto LoopEnd; + } + + // Add the implemented interface to the list of implemented interfaces. + ++ixImpl; + + // If this is explicitly the default interface... + if (ImplFlags & IMPLTYPEFLAG_FDEFAULT) + { + fHadDefaultItf = TRUE; + // Put the new interface at the start of the list. + for (int ix=ixImpl; ix > 0; --ix) + { + rImpls[ix] = rImpls[ix-1]; + rImplTypes[ix] = rImplTypes[ix-1]; + } + rImpls[0] = tkIface; + rImplTypes[0] = pItiIface; + } + else + { + rImpls[ixImpl] = tkIface; + rImplTypes[ixImpl] = pItiIface; + } + } + +LoopEnd: + pItiIface->ReleaseTypeAttr(psAttrIface); + psAttrIface = 0; + pItiIface = 0; // Pointer now owned by array. + } + + // Create an interface that will represent the class. + IfFailGo(_CreateClassInterface(pITI, rImplTypes[0], rImpls[0], rEvents[0], &tkIface)); + + // Create a temporary array of interface tokens. + if (fHadDefaultItf) + { + // default interface should be the first interface + rTmpImpls[++ixTmpImpl] = rImpls[0]; + rTmpImpls[++ixTmpImpl] = tkIface; + } + else + { + rTmpImpls[++ixTmpImpl] = tkIface; + if (ixImpl >= 0) + rTmpImpls[++ixTmpImpl] = rImpls[0]; + } + if (ixSrc >= 0) + rTmpImpls[++ixTmpImpl] = rEvents[0]; + if (ixImpl >= 0) + { + memcpy(&rTmpImpls[ixTmpImpl + 1], &rImpls[1], ixImpl * sizeof(mdTypeRef)); + ixTmpImpl += ixImpl; + } + if (ixSrc >= 0) + { + memcpy(&rTmpImpls[ixTmpImpl + 1], &rEvents[1], ixSrc * sizeof(mdTypeRef)); + ixTmpImpl += ixSrc; + } + + // Check to see if the default interface has a member with a DISPID of DISPID_NEWENUM. + BOOL fIEnumFound = FALSE; + if (ixImpl >= 0) + { + // The ITypeInfo for the default interface had better be set. + _ASSERTE(rImplTypes[0]); + + if ( (!fInheritsIEnum) && (HasNewEnumMember(rImplTypes[0]) == S_OK) ) + { + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_IENUMERABLE, &tkIface)); + rTmpImpls[++ixTmpImpl] = tkIface; + fIEnumFound = TRUE; + } + } + + // Else Check to see if the IEnumerable Custom Value exists on the CoClass. + if (!fIEnumFound) + { + BOOL CVExists = FALSE; + _ForceIEnumerableCVExists(pITI, &CVExists); + if (CVExists && !fInheritsIEnum) + { + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_IENUMERABLE, &tkIface)); + rTmpImpls[++ixTmpImpl] = tkIface; + fIEnumFound = TRUE; + } + } + + // Add the implemented interfaces and event interfaces to the TypeDef. + IfFailGo(m_pEmit->SetTypeDefProps(m_tdTypeDef, ULONG_MAX/*Classflags*/, + ULONG_MAX, (mdToken*)rTmpImpls.Ptr())); + + // Create an initializer for the class. + ULONG ulFlags; + if (psAttr->wTypeFlags & TYPEFLAG_FCANCREATE) + ulFlags = OBJECT_INITIALIZER_FLAGS; + else + ulFlags = NONCREATABLE_OBJECT_INITIALIZER_FLAGS; + { + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, OBJECT_INITIALIZER_NAME, ulFlags, + OBJECT_INITIALIZER_SIG, sizeof(OBJECT_INITIALIZER_SIG), 0/*rva*/, OBJECT_INITIALIZER_IMPL_FLAGS/*flags*/, &mdCtor)); + } + + // Set ClassInterfaceType.None on the generated class. + DECLARE_CUSTOM_ATTRIBUTE(sizeof(short)); + BUILD_CUSTOM_ATTRIBUTE(short, clsIfNone); + IfFailGo(GetAttrType(ATTR_CLASSINTERFACE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + + + if (!m_bPreventClassMembers) + { + // Iterate over the implemented interfaces, and add the members to the coclass. + m_ImplIface = eImplIfaceDefault; + for (i=0; i<=ixImpl; ++i) + { + _ASSERTE(rImplTypes[i]); + + // Interface info. + m_tkInterface = rImpls[i]; + pItiIface = rImplTypes[i]; + rImplTypes[i] = 0; // ownership transferred. + + // Get interface name for decoration. + if (m_szInterface) + ::SysFreeString(m_szInterface), m_szInterface = 0; + IfFailGo(pItiIface->GetDocumentation(MEMBERID_NIL, &m_szInterface, 0,0,0)); + + // Add the interface members to the coclass. + IfFailGo(pItiIface->GetTypeAttr(&psAttrIface)); + switch (psAttrIface->typekind) + { + case TKIND_DISPATCH: + hr = ConvDispatch(pItiIface, psAttrIface, false); + break; + case TKIND_INTERFACE: + hr = ConvIface(pItiIface, psAttrIface, false); + break; + default: + hr = S_OK; + _ASSERTE(!"Unexpected typekind for implemented interface"); + } + pItiIface->ReleaseTypeAttr(psAttrIface); + psAttrIface = 0; + IfFailGo(hr); + m_ImplIface = eImplIface; + rImplTypes[i] = pItiIface; + pItiIface = 0; // ownership transferred back. + } + + // Add the methods of the event interfaces to the class. + for (i=0; i<=ixSrc; ++i) + IfFailGo(_AddSrcItfMembersToClass(rEvents[i])); + } + + // If there are source interfaces, add a custom value for that. + if (ixSrc >= 0) + { + CQuickArray<char> rEvents; // Output buffer. + int cbCur; // Current location in output buffer. + int cbReq; // Size of an individual piece. + CQuickArray<WCHAR> rEvent; + + // Save 6 bytes at the beginning of the buffer for the custom attribute prolog and + // the string length. The string length may require 1, 2, or 4 bytes to express. + cbCur = 6; + + // For each event interface... + for (int ix=0; ix <= ixSrc; ++ix) + { + pItiIface = rSrcTypes[ix]; + rSrcTypes[ix] = 0; + + // Get the typeref name for the interface. + for(;;) + { + int cchReq; + IfFailGo(_GetTokenForTypeInfo(pItiIface, FALSE, &token, rEvent.Ptr(), (int)rEvent.MaxSize(), &cchReq, TRUE)); + if (cchReq <= (int)rEvent.MaxSize()) + break; + IfFailGo(rEvent.ReSizeNoThrow(cchReq)); + } + + // Append to the buffer. See how much space is required, get it. + cbReq = WszWideCharToMultiByte(CP_UTF8,0, rEvent.Ptr(),-1, 0,0, 0,0); + + // make sure we have enough space for the extra terminating 0 and for the 00 00 suffix + size_t cbNewSize; + if (!ClrSafeInt<size_t>::addition(cbCur, cbReq, cbNewSize) || + !ClrSafeInt<size_t>::addition(cbNewSize, 3, cbNewSize)) + { + IfFailGo(COR_E_OVERFLOW); + } + if (cbNewSize > rEvents.MaxSize()) + { + IfFailGo(rEvents.ReSizeNoThrow(cbNewSize)); + } + // Do the conversion. + WszWideCharToMultiByte(CP_UTF8,0, rEvent.Ptr(),-1, rEvents.Ptr()+cbCur,cbReq, 0,0); + cbCur += cbReq; + pItiIface->Release(); + } + pItiIface = 0; + + // Add an extra terminating 0. + *(rEvents.Ptr()+cbCur) = 0; + ++cbCur; + + // Now build the custom attribute. + int iLen = cbCur - 6; + char *pBytes = rEvents.Ptr(); + + // Length may be encoded with less the 4 bytes. + int lenPad = 4 - CPackedLen::Size(iLen); + _ASSERTE(lenPad >= 0); + + pBytes += lenPad; + cbCur -= lenPad; + + // Prologue. + pBytes[0] = 0x01; + pBytes[1] = 0x00; + + CPackedLen::PutLength(pBytes + 2, iLen); + + // Zero named properties/fields. + pBytes[cbCur + 0] = 0x00; + pBytes[cbCur + 1] = 0x00; + cbCur += 2; + + // Finally, store it. + IfFailGo(GetAttrType(ATTR_COMSOURCEINTERFACES, &tkAttr)); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, pBytes, cbCur, 0)); + } + +ErrExit: + if (psAttrIface) + pItiIface->ReleaseTypeAttr(psAttrIface); + if (pItiIface) + pItiIface->Release(); + // Clean up any left-over ITypeInfo*. + for (ULONG ix=0; ix < rImplTypes.Size(); ++ix) + if (rImplTypes[ix]) + (rImplTypes[ix])->Release(); + for (ULONG ix=0; ix < rSrcTypes.Size(); ++ix) + if (rSrcTypes[ix]) + (rSrcTypes[ix])->Release(); + m_tkInterface = 0; + if (m_szInterface) + ::SysFreeString(m_szInterface), m_szInterface = 0; + m_ImplIface = eImplIfaceNone; + return (hr); +} // HRESULT CImportTlb::ConvCoclass() +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +//***************************************************************************** +// Convert an enum to a class with fields that have default values. +//***************************************************************************** +HRESULT CImportTlb::ConvEnum( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr) // TYPEATTR of TypeInfo. +{ + HRESULT hr; // A result. + int i; // Loop control. + VARDESC *psVar=0; // VARDESC for a member. + mdFieldDef mdField; // The FieldDef for the enum's type. + + // Create the field definition for the enum type. Always import as an __int32. + IfFailGo(m_pEmit->DefineField(m_tdTypeDef, ENUM_TYPE_NAME, ENUM_TYPE_FLAGS, ENUM_TYPE_SIGNATURE,ENUM_TYPE_SIGNATURE_SIZE, + 0,0, -1, &mdField)); + + // Iterate over the vars. + for (i=0; i<psAttr->cVars; ++i) + { + // Get variable information. + IfFailGo(pITI->GetVarDesc(i, &psVar)); + // Do the conversion. + IfFailGo(_ConvConstant(pITI, psVar, true/*enum member*/)); + // Release for next var. + pITI->ReleaseVarDesc(psVar); + psVar = 0; + } + + hr = S_OK; + +ErrExit: + if (psVar) + pITI->ReleaseVarDesc(psVar); + return (hr); +} // HRESULT CImportTlb::ConvEnum() + +//***************************************************************************** +// Convert a record to a class with fields. +//***************************************************************************** +HRESULT CImportTlb::ConvRecord( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr, // TYPEATTR of TypeInfo. + BOOL bUnion) // Convert as a union? +{ + HRESULT hr=S_OK; // A result. + int i; // Loop control. + VARDESC *psVar=0; // VARDESC for a member. + mdFieldDef mdField; // Token for a given field. + CQuickArray<COR_FIELD_OFFSET> rLayout; // Array for layout information. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + + // Unions with embedded Object Types can't really be converted. Just reserve correct size. + if (bUnion && (HasObjectFields(pITI, psAttr) == S_OK)) + { + IfFailGo(m_pEmit->SetClassLayout(m_tdTypeDef, psAttr->cbAlignment, 0, psAttr->cbSizeInstance)); + goto ErrExit; + } + + // Prepare for layout info. + IfFailGo(rLayout.ReSizeNoThrow(psAttr->cVars+1)); + + // Iterate over the vars. + for (i=0; i<psAttr->cVars; ++i) + { + // Get variable information. + IfFailGo(pITI->GetVarDesc(i, &psVar)); + // Do the conversion. + IfFailGo(_ConvField(pITI, psVar, &mdField, bUnion)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + // Save the layout info. + rLayout[i].ridOfField = mdField; + rLayout[i].ulOffset = psVar->oInst; + // Release for next var. + pITI->ReleaseVarDesc(psVar); + psVar = 0; + } + + // If it is a union, Save the layout information. + if (bUnion) + { + rLayout[psAttr->cVars].ridOfField = mdFieldDefNil; + IfFailGo(m_pEmit->SetClassLayout(m_tdTypeDef, psAttr->cbAlignment, rLayout.Ptr(), -1)); + } + else // Not a union. Preserve the alignment. + IfFailGo(m_pEmit->SetClassLayout(m_tdTypeDef, psAttr->cbAlignment, 0, -1)); + + // If we are marking these as serializable - do so now. + if (m_bSerializableValueClasses) + { + mdToken tkAttr; + IfFailGo(GetAttrType(ATTR_SERIALIZABLE, &tkAttr)); + DECLARE_CUSTOM_ATTRIBUTE(0); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(),SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + +ErrExit: + if (psVar) + pITI->ReleaseVarDesc(psVar); + return (hr); +} // HRESULT CImportTlb::ConvRecord() + +//***************************************************************************** +// Convert an module to a class with fields that have default values. +// @FUTURE: convert methods as PInvoke methods. +//***************************************************************************** +HRESULT CImportTlb::ConvModule( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr) // TYPEATTR of TypeInfo. +{ + HRESULT hr; // A result. + int i; // Loop control. + VARDESC *psVar=0; // VARDESC for a member. + + // Iterate over the vars. + for (i=0; i<psAttr->cVars; ++i) + { + // Get variable information. + IfFailGo(pITI->GetVarDesc(i, &psVar)); + // Do the conversion. + IfFailGo(_ConvConstant(pITI, psVar)); + // Release for next var. + pITI->ReleaseVarDesc(psVar); + psVar = 0; + } + + hr = S_OK; + +ErrExit: + if (psVar) + pITI->ReleaseVarDesc(psVar); + return (hr); +} // HRESULT CImportTlb::ConvModule() + +//***************************************************************************** +// Convert metadata for an interface. +//***************************************************************************** +HRESULT CImportTlb::ConvIface( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr, // TYPEATTR of TypeInfo. + BOOL bVtblGapFuncs) // Vtable gap functions? +{ + HRESULT hr; // A result. + ITypeInfo *pITIBase=0; // ITypeInfo* of base interface. + TYPEATTR *psAttrBase=0; // TYPEATTR of base interface. + ITypeInfo *pITISelf2=0; // ITypeInfo* of partner. + TYPEATTR *psAttrSelf2=0; // TYPEATTR of partner. + mdToken tkImpls[3]={0,0,0}; // Token of implemented interfaces. + int ixImpls = 0; // Index of current implemented interface. + HREFTYPE href; // href of base interface. + mdToken tkIface; // Token for an interface. + BOOL fInheritsIEnum = FALSE; + + // If there is a partner interface, prefer it. + if (pITI->GetRefTypeOfImplType(-1, &href) == S_OK) + { + IfFailGo(pITI->GetRefTypeInfo(href, &pITISelf2)); + IfFailGo(pITISelf2->GetTypeAttr(&psAttrSelf2)); + } + + // Base interface? + if (psAttr->cImplTypes == 1) + { + IfFailGo(pITI->GetRefTypeOfImplType(0, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + // If this interface extends something other than IDispatch or IUnknown, record that + // fact as an "implemented interface". + if (psAttrBase->guid != IID_IDispatch && psAttrBase->guid != IID_IUnknown) + { + // Get Token of the base interface. + IfFailGo(_GetTokenForTypeInfo(pITIBase, FALSE, &tkImpls[ixImpls++])); + } + else + { // Maybe we're "funky"... + if (pITISelf2) + { + pITIBase->ReleaseTypeAttr(psAttrBase); + pITIBase->Release(); + pITIBase = 0; + psAttrBase = 0; + + if (psAttrSelf2->cImplTypes == 1) + { + IfFailGo(pITISelf2->GetRefTypeOfImplType(0, &href)); + IfFailGo(pITISelf2->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + if (psAttrBase->guid != IID_IDispatch && psAttrBase->guid != IID_IUnknown) + { + // Get Token of the base interface. + IfFailGo(_GetTokenForTypeInfo(pITIBase, FALSE, &tkImpls[ixImpls++])); + } + } + else + { + BSTR szTypeInfoName; + pITISelf2->GetDocumentation(MEMBERID_NIL, &szTypeInfoName, 0, 0, 0); + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_INVALID_TYPEINFO, szTypeInfoName); + SysFreeString(szTypeInfoName); + + IfFailGo(TLBX_E_INVALID_TYPEINFO); + } + } + } + + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + } + + if (ExplicitlyImplementsIEnumerable(pITI, psAttr) == S_OK) + fInheritsIEnum = TRUE; + + // If this interface has a NewEnum member then have it implement IEnumerable. + if ( (!fInheritsIEnum) && (HasNewEnumMember(pITI) == S_OK) ) + { + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_IENUMERABLE, &tkIface)); + tkImpls[ixImpls++] = tkIface; + } + + // If not processing an implemented interface, add additional interface properties. + if (m_ImplIface == eImplIfaceNone) + { + // Set base interface as an implemented interface. + if (tkImpls[0]) + IfFailGo(m_pEmit->SetTypeDefProps(m_tdTypeDef, ULONG_MAX/*flags*/, ULONG_MAX/*extends*/, tkImpls)); + + // If the interface is not derived from IDispatch mark it as IUnknown based. + if (IsIDispatchDerived(pITI, psAttr) == S_FALSE) + { + mdMemberRef mr; + // Note that this is a vtable, but not IDispatch derived. + // Custom attribute buffer. + DECLARE_CUSTOM_ATTRIBUTE(sizeof(short)); + // Set up the attribute. + BUILD_CUSTOM_ATTRIBUTE(short, ifVtable); + // Store the attribute + IfFailGo(GetAttrType(ATTR_INTERFACETYPE, &mr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, mr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + } + + // Convert the members on the interface (and base interfaces). + // If this interface had a "funky partner", base the conversion on that. + if (pITISelf2) + IfFailGo(_ConvIfaceMembers(pITISelf2, psAttrSelf2, bVtblGapFuncs, psAttr->wTypeFlags & TYPEFLAG_FDUAL, fInheritsIEnum)); + else + IfFailGo(_ConvIfaceMembers(pITI, psAttr, bVtblGapFuncs, psAttr->wTypeFlags & TYPEFLAG_FDUAL, fInheritsIEnum)); + +ErrExit: + if (psAttrSelf2) + pITISelf2->ReleaseTypeAttr(psAttrSelf2); + if (pITISelf2) + pITISelf2->Release(); + if (psAttrBase) + pITIBase->ReleaseTypeAttr(psAttrBase); + if (pITIBase) + pITIBase->Release(); + return (hr); +} // HRESULT CImportTlb::ConvIface() + +//***************************************************************************** +// Convert the metadata for a dispinterface. Try to convert as a normal +// interface. +//***************************************************************************** +HRESULT CImportTlb::ConvDispatch( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr, // TYPEATTR of TypeInfo. + BOOL bVtblGapFuncs) // Vtable gap functions for interface implementations? +{ + HRESULT hr; // A result. + HREFTYPE href; // Base interface href. + ITypeInfo *pITIBase=0; // Base interface ITypeInfo. + TYPEATTR *psAttrBase=0; // TYPEATTR of base interface. + mdMemberRef mr; // MemberRef for custom value. + DWORD attr[2] = {0x00010001, 0x00000002}; + BYTE bIface = ifDispatch; // Custom value means "dispinterface" + BOOL fInheritsIEnum = FALSE; + + // If this is a dual interface, treat it like a normal interface. + if ((psAttr->wTypeFlags & TYPEFLAG_FDUAL)) + { + hr = ConvIface(pITI, psAttr, bVtblGapFuncs); + goto ErrExit; + } + + if (ExplicitlyImplementsIEnumerable(pITI, psAttr) == S_OK) + fInheritsIEnum = TRUE; + + // If there is a vtable view of this interface (funky dispinterface). + // @FUTURE: what would be really nice here would be an alias mechanism, so that we could + // just point this dispinterface to that other interface, in those situations that it + // is dual. OTOH, that is probably pretty rare, because if that other interface + // were dual, why would the dispinterface even be needed? + if (pITI->GetRefTypeOfImplType(-1, &href) == S_OK) + { + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + IfFailGo(_ConvIfaceMembers(pITIBase, psAttrBase, bVtblGapFuncs, TRUE, fInheritsIEnum)); + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + goto ErrExit; + } + + // If not processing an implemented interface, mark the interface type. + if (m_ImplIface == eImplIfaceNone) + { + // If this interface has a NewEnum member then have it implement IEnumerable. + if ((S_OK == HasNewEnumMember(pITI)) && !fInheritsIEnum) + { + mdToken tkImpl[2] = {0,0}; + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_IENUMERABLE, &tkImpl[0])); + IfFailGo(m_pEmit->SetTypeDefProps(m_tdTypeDef, ULONG_MAX, ULONG_MAX, tkImpl)); + } + + // Note that this is a dispinterface. + DECLARE_CUSTOM_ATTRIBUTE(sizeof(short)); + // Set up the attribute. + BUILD_CUSTOM_ATTRIBUTE(short, ifDispatch); + // Store the attribute + IfFailGo(GetAttrType(ATTR_INTERFACETYPE, &mr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, mr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + + IfFailGo(_ConvDispatchMembers(pITI, psAttr, fInheritsIEnum)); + +ErrExit: + if (psAttrBase) + pITIBase->ReleaseTypeAttr(psAttrBase); + if (pITIBase) + pITIBase->Release(); + return (hr); +} // HRESULT CImportTlb::ConvDispatch() + +//***************************************************************************** +// Determine if an interface is derived from IUnknown. +//***************************************************************************** +HRESULT CImportTlb::IsIUnknownDerived( + ITypeInfo *pITI, // The containing ITypeInfo. + TYPEATTR *psAttr) // The ITypeInfo's TYPEATTR +{ + HRESULT hr=S_OK; // A result. + + HREFTYPE href; // Base interface href. + ITypeInfo *pITIBase=0; // Base interface ITypeInfo. + TYPEATTR *psAttrBase=0; // TYPEATTR of base interface. + + // This should never be called on CoClasses. + _ASSERTE(psAttr->typekind != TKIND_COCLASS); + + // If IDispatch or IUnknown, we've recursed far enough. + if (IsEqualGUID(psAttr->guid, IID_IUnknown) || IsEqualGUID(psAttr->guid, IID_IDispatch)) + { + goto ErrExit; + } + + // Handle base interface. + if (psAttr->cImplTypes == 1) + { + IfFailGo(pITI->GetRefTypeOfImplType(0, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + // IUnknow derived if base interface is. + hr = IsIUnknownDerived(pITIBase, psAttrBase); + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + } + else + { // No base interface, not IUnknown, not IDispatch. Not very COM-ish, so don't try to handle. + hr = S_FALSE; + } + +ErrExit: + if (psAttrBase) + pITIBase->ReleaseTypeAttr(psAttrBase); + if (pITIBase) + pITIBase->Release(); + return (hr); +} // HRESULT CImportTlb::IsIUnknownDerived() + +//***************************************************************************** +// Determine if an interface is derived from IDispatch. Note that a pure +// dispinterface doesn't derive from IDispatch. +//***************************************************************************** +HRESULT CImportTlb::IsIDispatchDerived( + ITypeInfo *pITI, // The containing ITypeInfo. + TYPEATTR *psAttr) // The ITypeInfo's TYPEATTR +{ + HRESULT hr=S_OK; // A result. + + HREFTYPE href; // Base interface href. + ITypeInfo *pITIBase=0; // Base interface ITypeInfo. + TYPEATTR *psAttrBase=0; // TYPEATTR of base interface. + + // If IDispatch, we've recursed far enough. + if (IsEqualGUID(psAttr->guid, IID_IDispatch)) + { + goto ErrExit; + } + + if (psAttr->typekind == TKIND_DISPATCH) + { + IfFailGo(pITI->GetRefTypeOfImplType(-1, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + // IDispatch derived if base interface is. + hr = IsIDispatchDerived(pITIBase, psAttrBase); + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + + goto ErrExit; + } + + // Handle base interface. + if (psAttr->cImplTypes == 1) + { + IfFailGo(pITI->GetRefTypeOfImplType(0, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + // IDispatch derived if base interface is. + hr = IsIDispatchDerived(pITIBase, psAttrBase); + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + } + else + { // No base interface, not IDispatch. Done. + hr = S_FALSE; + } + +ErrExit: + if (psAttrBase) + pITIBase->ReleaseTypeAttr(psAttrBase); + if (pITIBase) + pITIBase->Release(); + return (hr); +} // HRESULT CImportTlb::IsIDispatchDerived() + +//***************************************************************************** +// Determine if an interface has a member with a DISPID of DISPID_NEWENUM. +//***************************************************************************** +HRESULT CImportTlb::HasNewEnumMember( // S_OK if has NewEnum, S_FALSE otherwise. + ITypeInfo *pItfTI) // The interface in question. +{ + HRESULT hr = S_OK; // A result. + BOOL bHasNewEnumMember=FALSE;// If true, has a NewEnum + TYPEATTR *pAttr = NULL; // A TypeInfo's typeattr + FUNCDESC *pFuncDesc = NULL; // A Function's FuncDesc + VARDESC *pVarDesc = NULL; // A properties VarDesc + int i; // Loop control. + ITypeInfo *pITISelf2=0; // Partner interface. + HREFTYPE href; // HREF of partner. + WCHAR IEnumCA[] = W("{CD2BC5C9-F452-4326-B714-F9C539D4DA58}"); + + + // If there is a partner interface, prefer it. + if (pItfTI->GetRefTypeOfImplType(-1, &href) == S_OK) + { + IfFailGo(pItfTI->GetRefTypeInfo(href, &pITISelf2)); + pItfTI = pITISelf2; + } + + // Retrieve the attributes of the interface. + IfFailGo(pItfTI->GetTypeAttr(&pAttr)); + + if ((pAttr->typekind == TKIND_DISPATCH) || ((pAttr->typekind == TKIND_INTERFACE) && (IsIDispatchDerived(pItfTI, pAttr) == S_OK))) + { + // Check to see if the ForceIEnumerable custom value exists on the type + _ForceIEnumerableCVExists(pItfTI, &bHasNewEnumMember); + + // Check to see if the interface has a function with a DISPID of DISPID_NEWENUM. + for (i = 0; i < pAttr->cFuncs; i++) + { + IfFailGo(TryGetFuncDesc(pItfTI, i, &pFuncDesc)); + + if (FuncIsNewEnum(pItfTI, pFuncDesc, i) == S_OK) + { + // Throw a warning if we find more than one func with DISPID_NEWENUM. + if (bHasNewEnumMember == TRUE) + { + BSTR ObjectName; + pItfTI->GetDocumentation(-1, &ObjectName, NULL, NULL, NULL); + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_INVALID_TYPEINFO, ObjectName); + SysFreeString(ObjectName); + } + + // The interface has a function with a DISPID of DISPID_NEWENUM. + bHasNewEnumMember = TRUE; + break; + } + + pItfTI->ReleaseFuncDesc(pFuncDesc); + pFuncDesc = NULL; + } + + // Check to see if the interface as a property with a DISPID of DISPID_NEWENUM. + for (i = 0; i < pAttr->cVars; i++) + { + IfFailGo(pItfTI->GetVarDesc(i, &pVarDesc)); + + if (PropertyIsNewEnum(pItfTI, pVarDesc, i) == S_OK) + { + // Throw a warning if we find more than one func with DISPID_NEWENUM. + if (bHasNewEnumMember == TRUE) + { + BSTR ObjectName; + pItfTI->GetDocumentation(-1, &ObjectName, NULL, NULL, NULL); + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_INVALID_TYPEINFO, ObjectName); + SysFreeString(ObjectName); + } + + // The interface has a property with a DISPID of DISPID_NEWENUM. + bHasNewEnumMember = TRUE; + break; + } + + pItfTI->ReleaseVarDesc(pVarDesc); + pVarDesc = NULL; + } + } + else + { + // Check to see if the ForceIEnumerable custom value exists on the type + // If it does, spit out a warning. + _ForceIEnumerableCVExists(pItfTI, &bHasNewEnumMember); + + if (bHasNewEnumMember) + { + // Invalid custom attribute on the iface. + BSTR CustomValue = SysAllocString((const WCHAR*)&IEnumCA[0]); + BSTR ObjectName; + pItfTI->GetDocumentation(-1, &ObjectName, NULL, NULL, NULL); + + ReportEvent(NOTIF_CONVERTWARNING, TLBX_W_IENUM_CA_ON_IUNK, CustomValue, ObjectName); + + SysFreeString(CustomValue); + SysFreeString(ObjectName); + + bHasNewEnumMember = FALSE; + } + } + + hr = bHasNewEnumMember ? S_OK : S_FALSE; + +ErrExit: + if (pAttr) + pItfTI->ReleaseTypeAttr(pAttr); + if (pFuncDesc) + pItfTI->ReleaseFuncDesc(pFuncDesc); + if (pVarDesc) + pItfTI->ReleaseVarDesc(pVarDesc); + if (pITISelf2) + pITISelf2->Release(); + return hr; +} // HRESULT CImportTlb::HasNewEnumMember(ITypeInfo *pItfTI) + +//***************************************************************************** +// Determine if a given function is a valid NewEnum member. +//***************************************************************************** +HRESULT CImportTlb::FuncIsNewEnum( // S_OK if the function is the NewEnum member S_FALSE otherwise. + ITypeInfo *pITI, // The ITypeInfo that contains the function. + FUNCDESC *pFuncDesc, // The function in question. + DWORD index) // The function index +{ + + HRESULT hr = S_OK; + BOOL bIsValidNewEnum = FALSE; + TYPEDESC* pType = NULL; + TYPEATTR* pAttr = NULL; + ITypeInfo* pITIUD = NULL; + long lDispSet = 0; + + _GetDispIDCA(pITI, index, &lDispSet, TRUE); + + if ((pFuncDesc->memid == DISPID_NEWENUM) || (lDispSet == DISPID_NEWENUM)) + { + if (pFuncDesc->funckind == FUNC_DISPATCH) + { + if ((pFuncDesc->invkind == INVOKE_PROPERTYGET) || (pFuncDesc->invkind == INVOKE_FUNC)) + { + if (pFuncDesc->cParams == 0) + { + pType = &pFuncDesc->elemdescFunc.tdesc; + } + else if ((m_bTransformDispRetVals) && (pFuncDesc->cParams == 1) && (pFuncDesc->lprgelemdescParam[0].paramdesc.wParamFlags & PARAMFLAG_FRETVAL)) + { + pType = pFuncDesc->lprgelemdescParam[0].tdesc.lptdesc; + } + } + } + else if (pFuncDesc->funckind == FUNC_PUREVIRTUAL) + { + if ((pFuncDesc->cParams == 1) && + ((pFuncDesc->invkind == INVOKE_PROPERTYGET) || (pFuncDesc->invkind == INVOKE_FUNC)) && + (pFuncDesc->lprgelemdescParam[0].paramdesc.wParamFlags & PARAMFLAG_FRETVAL) && + (pFuncDesc->lprgelemdescParam[0].tdesc.vt == VT_PTR)) + { + pType = pFuncDesc->lprgelemdescParam[0].tdesc.lptdesc; + } + } + + if (pType) + { + if (pType->vt == VT_UNKNOWN || pType->vt == VT_DISPATCH) + { + // The member returns an IUnknown * or an IDispatch * which is valid. + bIsValidNewEnum = TRUE; + } + else if (pType->vt == VT_PTR) + { + pType = pType->lptdesc; + if (pType->vt == VT_USERDEFINED) + { + IfFailGo(pITI->GetRefTypeInfo(pType->hreftype, &pITIUD)); + IfFailGo(pITIUD->GetTypeAttr(&pAttr)); + if (IsEqualGUID(pAttr->guid, IID_IEnumVARIANT) || + IsEqualGUID(pAttr->guid, IID_IUnknown) || + IsEqualGUID(pAttr->guid, IID_IDispatch)) + { + // The member returns a valid interface type for a NewEnum member. + bIsValidNewEnum = TRUE; + } + } + } + } + } + +ErrExit: + if (pAttr) + pITIUD->ReleaseTypeAttr(pAttr); + if (pITIUD) + pITIUD->Release(); + + if (FAILED(hr)) + return hr; + else + return bIsValidNewEnum ? S_OK : S_FALSE; +} // HRESULT CImportTlb::FuncIsNewEnum(FUNCDESC *pFuncDesc) + +//***************************************************************************** +// Determine if a given function is a valid NewEnum member. +//***************************************************************************** +HRESULT CImportTlb::PropertyIsNewEnum( // S_OK if the function is the NewEnum member S_FALSE otherwise. + ITypeInfo *pITI, // The ITypeInfo that contains the property. + VARDESC *pVarDesc, // The function in question. + DWORD index) // The property index. +{ + HRESULT hr = S_OK; + BOOL bIsValidNewEnum = FALSE; + TYPEDESC* pType = NULL; + TYPEATTR* pAttr = NULL; + ITypeInfo* pITIUD = NULL; + long lDispSet = 0; + + _GetDispIDCA(pITI, index, &lDispSet, FALSE); + + if ( ((pVarDesc->memid == DISPID_NEWENUM) || (lDispSet == DISPID_NEWENUM)) && + (pVarDesc->elemdescVar.paramdesc.wParamFlags & PARAMFLAG_FRETVAL) && + (pVarDesc->wVarFlags & VARFLAG_FREADONLY)) + { + pType = &pVarDesc->elemdescVar.tdesc; + if (pType->vt == VT_UNKNOWN || pType->vt == VT_DISPATCH) + { + // The member returns an IUnknown * or an IDispatch * which is valid. + bIsValidNewEnum = TRUE; + } + else if (pType->vt == VT_PTR) + { + pType = pType->lptdesc; + if (pType->vt == VT_USERDEFINED) + { + IfFailGo(pITI->GetRefTypeInfo(pType->hreftype, &pITIUD)); + IfFailGo(pITIUD->GetTypeAttr(&pAttr)); + if (IsEqualGUID(pAttr->guid, IID_IEnumVARIANT) || + IsEqualGUID(pAttr->guid, IID_IUnknown) || + IsEqualGUID(pAttr->guid, IID_IDispatch)) + { + // The member returns a valid interface type for a NewEnum member. + bIsValidNewEnum = TRUE; + } + } + } + } + +ErrExit: + if (pAttr) + pITIUD->ReleaseTypeAttr(pAttr); + if (pITIUD) + pITIUD->Release(); + + if (FAILED(hr)) + return hr; + else + return bIsValidNewEnum ? S_OK : S_FALSE; +} // HRESULT CImportTlb::FuncIsNewEnum(FUNCDESC *pFuncDesc) + +//***************************************************************************** +// Determine is a TypeInfo has any object fields. +//***************************************************************************** +HRESULT CImportTlb::HasObjectFields( // S_OK, S_FALSE, or error. + ITypeInfo *pITI, // The TypeInfo in question. + TYPEATTR *psAttr) // Attributes of the typeinfo. +{ + HRESULT hr; // A result. + + int i; // Loop control. + VARDESC *psVar=0; // VARDESC for a member. + + // Iterate over the vars. + for (i=0; i<psAttr->cVars; ++i) + { + // Get variable information. + IfFailGo(pITI->GetVarDesc(i, &psVar)); + + // See if it is an object type. + IfFailGo(IsObjectType(pITI, &psVar->elemdescVar.tdesc)); + // If result is S_FALSE, not an Object; keep looking. + if (hr == S_OK) + goto ErrExit; + + // Release for next var. + pITI->ReleaseVarDesc(psVar); + psVar = 0; + } + + hr = S_FALSE; + +ErrExit: + if (psVar) + pITI->ReleaseVarDesc(psVar); + return hr; +} // HRESULT CImportTlb::HasObjectFields() + +//***************************************************************************** +// Is a given type an Object type? +//***************************************************************************** +HRESULT CImportTlb::IsObjectType( // S_OK, S_FALSE, or error. + ITypeInfo *pITI, // The TypeInfo in question. + const TYPEDESC *pType) // The type. +{ + HRESULT hr; // A result. + TYPEDESC tdTemp; // Copy of TYPEDESC, for R/W. + ITypeInfo *pITIAlias=0; // Typeinfo of the aliased type. + TYPEATTR *psAttrAlias=0; // TYPEATTR of the aliased typeinfo. + int bObjectField=false; // The question to be answered. + int iByRef=0; // Indirection. + + // Strip off leading VT_PTR and VT_BYREF + while (pType->vt == VT_PTR) + pType = pType->lptdesc, ++iByRef; + if (pType->vt & VT_BYREF) + { + tdTemp = *pType; + tdTemp.vt &= ~VT_BYREF; + pType = &tdTemp; + ++iByRef; + } + + // Determine if the field is/has object type. + switch (pType->vt) + { + case VT_PTR: + _ASSERTE(!"Should not have VT_PTR here"); + break; + + // These are object types. + case VT_BSTR: + case VT_DISPATCH: + case VT_VARIANT: + case VT_UNKNOWN: + case VT_SAFEARRAY: + case VT_LPSTR: + case VT_LPWSTR: + bObjectField = true; + break; + + // A user-defined may or may not be/contain Object type. + case VT_USERDEFINED: + // User defined type. Get the TypeInfo. + IfFailGo(pITI->GetRefTypeInfo(pType->hreftype, &pITIAlias)); + IfFailGo(pITIAlias->GetTypeAttr(&psAttrAlias)); + + // Some user defined class. Is it a value class, or a VOS class? + switch (psAttrAlias->typekind) + { + // Alias -- Is the aliased thing an Object type? + case TKIND_ALIAS: + hr = IsObjectType(pITIAlias, &psAttrAlias->tdescAlias); + goto ErrExit; + // Record/Enum/Union -- Does it contain an Object type? + case TKIND_RECORD: + case TKIND_ENUM: + case TKIND_UNION: + // Byref/Ptrto record is Object. Contained record might be. + if (iByRef) + bObjectField = true; + else + { + hr = HasObjectFields(pITIAlias, psAttrAlias); + goto ErrExit; + } + break; + // Class/Interface -- An Object Type. + case TKIND_INTERFACE: + case TKIND_DISPATCH: + case TKIND_COCLASS: + bObjectField = true; + break; + default: + //case TKIND_MODULE: -- can't pass one of these as a parameter. + _ASSERTE(!"Unexpected typekind for user defined type"); + bObjectField = true; + } // switch (psAttrAlias->typekind) + break; + + case VT_CY: + case VT_DATE: + case VT_DECIMAL: + // Pointer to the value type is an object. Contained one isn't. + if (iByRef) + bObjectField = true; + else + bObjectField = false; + break; + + // A fixed array is an Object type. + case VT_CARRAY: + bObjectField = true; + break; + + // Other types I4, etc., are not Object types. + default: + bObjectField = false; + break; + } // switch (vt=pType->vt) + + + hr = bObjectField ? S_OK : S_FALSE; + +ErrExit: + if (psAttrAlias) + pITIAlias->ReleaseTypeAttr(psAttrAlias); + if (pITIAlias) + pITIAlias->Release(); + + return hr; +} // HRESULT CImportTlb::IsObjectType() + +//***************************************************************************** +// Convert the functions on an interface. Convert the functions on the +// base interface first, because in COM Classic, parent's functions are also +// in the derived interface's vtable. +//***************************************************************************** +HRESULT CImportTlb::_ConvIfaceMembers( + ITypeInfo *pITI, // The containing ITypeInfo. + TYPEATTR *psAttr, // The ITypeInfo's TYPEATTR + BOOL bVtblGapFuncs, // Add functions for vtblGaps? + BOOL bAddDispIds, // Add DispIds to the member? + BOOL bInheritsIEnum) // Inherits from IEnumerable. +{ + HRESULT hr=S_OK; // A result. + int i; // Loop control. + FUNCDESC *psFunc=0; // FUNCDESC for a member. + + HREFTYPE href; // Base interface href. + ITypeInfo *pITIBase=0; // Base interface ITypeInfo. + TYPEATTR *psAttrBase=0; // TYPEATTR of base interface. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + + _ASSERTE( (psAttr->typekind == TKIND_INTERFACE) || (psAttr->typekind == TKIND_DISPATCH) ); + + // If IDispatch or IUnknown, we've recursed far enough. + if (IsEqualGUID(psAttr->guid, IID_IUnknown) || IsEqualGUID(psAttr->guid, IID_IDispatch)) + { + if (m_cbVtableSlot == 0) + { + m_cbVtableSlot = psAttr->cbSizeInstance; + } + m_Slot = (psAttr->cbSizeVft / m_cbVtableSlot); + goto ErrExit; + } + + // Handle base interface. + if (psAttr->cImplTypes == 1) + { + IfFailGo(pITI->GetRefTypeOfImplType(0, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + IfFailGo(_ConvIfaceMembers(pITIBase, psAttrBase, bVtblGapFuncs, bAddDispIds, bInheritsIEnum)); + + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + } + else + { // No base interface, not IUnknown, not IDispatch. We shouldn't be here. + m_Slot = 0; + if (m_cbVtableSlot == 0) + { + m_cbVtableSlot = psAttr->cbSizeInstance; + } + _ASSERTE(!"Interface does not derive from IUnknown."); + } + + // Loop over functions. + IfFailGo(_FindFirstUserMethod(pITI, psAttr, &i)); + IfFailGo(BuildMemberList(pITI, i, psAttr->cFuncs, bInheritsIEnum)); + + BOOL bAllowIEnum = !bInheritsIEnum; + for (i=0; i<(int)m_MemberList.Size(); ++i) + { + // Convert the function. + IfFailGo(_ConvFunction(pITI, &m_MemberList[i], bVtblGapFuncs, bAddDispIds, FALSE, &bAllowIEnum)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + } + + // Add the property info. + IfFailGo(_ConvPropertiesForFunctions(pITI, psAttr)); + + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + +ErrExit: + // Release FuncDescs. + FreeMemberList(pITI); + + if (psAttrBase) + pITIBase->ReleaseTypeAttr(psAttrBase); + if (pITIBase) + pITIBase->Release(); + if (psFunc) + pITI->ReleaseFuncDesc(psFunc); + return (hr); +} // HRESULT CImportTlb::_ConvIfaceMembers() + +//***************************************************************************** +// Convert the functions on a source interface to add_ and remove_ method. +// Convert the functions on the base interface first, because in COM Classic, +// parent's functions are also in the derived interface's vtable. +//***************************************************************************** +HRESULT CImportTlb::_ConvSrcIfaceMembers( + ITypeInfo *pITI, // The containing ITypeInfo. + TYPEATTR *psAttr, // The ITypeInfo's TYPEATTR + BOOL fInheritsIEnum) +{ + HRESULT hr=S_OK; // A result. + int i; // Loop control. + FUNCDESC *psFunc=0; // FUNCDESC for a member. + HREFTYPE href; // Base interface href. + ITypeInfo *pITIBase=0; // Base interface ITypeInfo. + TYPEATTR *psAttrBase=0; // TYPEATTR of base interface. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + + _ASSERTE( (psAttr->typekind == TKIND_INTERFACE) || (psAttr->typekind == TKIND_DISPATCH) ); + + // If IDispatch or IUnknown, we've recursed far enough. + if (IsEqualGUID(psAttr->guid, IID_IUnknown) || IsEqualGUID(psAttr->guid, IID_IDispatch)) + { + if (m_cbVtableSlot == 0) + { + m_cbVtableSlot = psAttr->cbSizeInstance; + } + m_Slot = (psAttr->cbSizeVft / m_cbVtableSlot); + goto ErrExit; + } + + // Handle base interface. + if (psAttr->cImplTypes == 1) + { + IfFailGo(pITI->GetRefTypeOfImplType(0, &href)); + IfFailGo(pITI->GetRefTypeInfo(href, &pITIBase)); + IfFailGo(pITIBase->GetTypeAttr(&psAttrBase)); + + IfFailGo(_ConvSrcIfaceMembers(pITIBase, psAttrBase, fInheritsIEnum)); + pITIBase->ReleaseTypeAttr(psAttrBase); + psAttrBase = 0; + pITIBase->Release(); + pITIBase = 0; + } + else + { // No base interface, not IUnknown, not IDispatch. We shouldn't be here. + m_Slot = 0; + if (m_cbVtableSlot == 0) + { + m_cbVtableSlot = psAttr->cbSizeInstance; + } + _ASSERTE(!"Interface does not derive from IUnknown."); + } + + // Loop over functions. + IfFailGo(_FindFirstUserMethod(pITI, psAttr, &i)); + IfFailGo(BuildMemberList(pITI, i, psAttr->cFuncs, fInheritsIEnum)); + + // If we have any properties, we want to skip them. Should we add gaps? + if (m_cMemberProps != 0) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_W_NO_PROPS_IN_EVENTS, m_szName); + bConversionLoss = true; + } + + for (i = m_cMemberProps; i<(int)m_MemberList.Size(); ++i) + { + // Convert the function. + IfFailGo(_GenerateEvent(pITI, &m_MemberList[i], fInheritsIEnum)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + } + + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + +ErrExit: + // Release FuncDescs. + FreeMemberList(pITI); + + if (psAttrBase) + pITIBase->ReleaseTypeAttr(psAttrBase); + if (pITIBase) + pITIBase->Release(); + if (psFunc) + pITI->ReleaseFuncDesc(psFunc); + return (hr); +} // HRESULT CImportTlb::_ConvIfaceMembers() + +//***************************************************************************** +// Add the property definitions for property functions. +//***************************************************************************** +HRESULT CImportTlb::_ConvPropertiesForFunctions( + ITypeInfo *pITI, // ITypeInfo* being converted. + TYPEATTR *psAttr) // TypeAttr for the typeinfo. +{ + HRESULT hr=S_OK; // A result. + int ix; // Loop control. + int ix2; // More loop control. + mdProperty pd; // A property token. + USHORT ms; // Some method's semantics. + mdToken tk; // A method's token. + mdMethodDef mdFuncs[6] ={0}; // Array of setter, getter, other. + FUNCDESC *psF=0; // FUNCDESC of Get, Put, or PutRef. + TYPEDESC *pProperty; // TYPEDESC of property type. + BOOL bPropRetval; // Is the property type a [retval]? + ULONG ixValue; // Index of the value parameter for putters. + int ixVarArg; // Index of vararg param, if any. + CQuickBytes qbComSig; // new signature + BYTE *pbSig; // Pointer into the signature. + ULONG sigFlags; // Signature handling flags. + ULONG cbTotal; // Size of the signature. + ULONG cb; // Size of a signature element. + LPWSTR pszName; // Possibly decorated name of property. + CQuickArray<WCHAR> qbName; // Buffer for name decoration. + int iSrcParam; // Param count, as looping through params. + int cDestParams; // Count of destination params. + CQuickArray<BYTE> qbDummyNativeTypeBuf; // A dummy native type array. + ULONG iNativeOfs=0; // Current offset in native type buffer. + BOOL bNewEnumMember=FALSE; // Is this a NewEnum property? + BOOL bConversionLoss=FALSE; // Was some type not fully converted? + int cFound; // Functions found matching a given property. + + // Using semantics as an index, so be sure array is big enough. + _ASSERTE(lengthof(mdFuncs) > msOther); + + for (ix=m_cMemberProps; ix<(int)m_MemberList.Size(); ++ix) + { // See if this one needs to be processed. + if (m_MemberList[ix].m_mdFunc == 0) + continue; + + MemberInfo *pMember = &m_MemberList[ix]; + pMember->GetFuncInfo(tk, ms); + + // Get the name. + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember = 0; + IfFailGo(pITI->GetDocumentation(pMember->m_psFunc->memid, &m_szMember, 0,0,0)); + + // Found one. Put in the right slot. + _ASSERTE(ms == msGetter || ms == msSetter || ms==msOther); + mdFuncs[msSetter] = mdFuncs[msGetter] = mdFuncs[msOther] = 0; + mdFuncs[ms] = tk; + pMember->m_mdFunc = 0; + + // Look for related functions. + cFound = 1; + for (ix2=ix+1; ix2<(int)m_MemberList.Size(); ++ix2) + { + MemberInfo *pMember2 = &m_MemberList[ix2]; + if (pMember2->m_mdFunc != 0 && pMember2->m_psFunc->memid == pMember->m_psFunc->memid) + { // Found a related function. + pMember2->GetFuncInfo(tk, ms); + _ASSERTE(ms == msGetter || ms == msSetter || ms==msOther); + _ASSERTE(mdFuncs[ms] == 0); + mdFuncs[ms] = tk; + pMember2->m_mdFunc = 0; + // If have found all three, don't bother looking for more. + if (++cFound == 3) + break; + } + } + + // Build the signature for the property. + hr = _GetFunctionPropertyInfo(pMember->m_psFunc, &ms, &psF, &pProperty, &bPropRetval, TRUE, m_szMember); + + // The function really should have a property associated with it, to get here. Check anyway. + _ASSERTE(pProperty); + if (!pProperty) + continue; + + // Some sort of property accessor. + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE + 1)); + pbSig = (BYTE *)qbComSig.Ptr(); + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_PROPERTY, pbSig); + // Count of parameters. + + // If this is a getter, see if there is a retval. + if (psF->invkind == INVOKE_PROPERTYGET) + { // Examine each param, and count all except the [retval]. + for (cDestParams=iSrcParam=0; iSrcParam<psF->cParams; ++iSrcParam) + { + if ((psF->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & NON_CONVERTED_PARAMS_FLAGS) == 0) + ++cDestParams; + } + // There is no new value param for getters. + ixValue = -1; + } + else + { + // This is a putter, so 1 param is new value, others are indices (or lcid). + for (cDestParams=iSrcParam=0; iSrcParam<psF->cParams-1; ++iSrcParam) + { + if ((psF->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & NON_CONVERTED_PARAMS_FLAGS) == 0) + ++cDestParams; + } + // The last parameter is the new value. + ixValue = psF->cParams - 1; + } + + //------------------------------------------------------------------------- + // See if there is a vararg param. + ixVarArg = psF->cParams + 1; + if (psF->cParamsOpt == -1) + { + // If this is a PROPERTYPUT or PROPERTYPUTREF, skip the last non-retval parameter (it + // is the new value to be set). + BOOL bPropVal = (psF->invkind & (INVOKE_PROPERTYPUT | INVOKE_PROPERTYPUTREF)) ? TRUE : FALSE; + // Find the vararg param. + for (iSrcParam=psF->cParams-1; iSrcParam>=0; --iSrcParam) + { + // The count of optional params does not include any lcid params, nor does + // it include the return value, so skip those. + if ((psF->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & (PARAMFLAG_FRETVAL|PARAMFLAG_FLCID)) != 0) + continue; + // If haven't yet seen the property value, this param is it, so skip it, too. + if (bPropVal) + { + bPropVal = FALSE; + continue; + } + ixVarArg = iSrcParam; + break; + } // for (iSrcParam=cParams-1... + } + + // Put in the count of index parameters. + _ASSERTE(cDestParams >= 0); + cb = CorSigCompressData(cDestParams, &pbSig[cbTotal]); + cbTotal += cb; + + // Create the signature for the property type. + sigFlags = SIG_ELEM | (bPropRetval ? SIG_RET : (SigFlags)0); + IfFailGo(_ConvSignature(pITI, pProperty, sigFlags, qbComSig, cbTotal, &cbTotal, qbDummyNativeTypeBuf, 0, &iNativeOfs, bNewEnumMember)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + // Fill in the "index" part of the property's signature. + for (iSrcParam=0; iSrcParam<psF->cParams; ++iSrcParam) + { + if (psF->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & NON_CONVERTED_PARAMS_FLAGS) + continue; + if (iSrcParam == static_cast<int>(ixValue)) + continue; + sigFlags = SIG_FUNC | SIG_USE_BYREF; + if (iSrcParam == ixVarArg) + sigFlags |= SIG_VARARG; + IfFailGo(_ConvSignature(pITI, &psF->lprgelemdescParam[iSrcParam].tdesc, sigFlags, qbComSig, cbTotal, &cbTotal, qbDummyNativeTypeBuf, 0, &iNativeOfs, bNewEnumMember)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + } + + // Get the property name. Add interface name and make unique, if needed. + // m_szInterface should be non-null if processing an implemented interface; should be null otherwise. + _ASSERTE(m_ImplIface == eImplIfaceNone || m_szInterface != 0); + IfFailGo(qbName.ReSizeNoThrow(wcslen(m_szMember)+2)); + wcscpy_s(qbName.Ptr(), wcslen(m_szMember)+2, m_szMember); + IfFailGo(GenerateUniqueMemberName(qbName, (PCCOR_SIGNATURE)qbComSig.Ptr(), cbTotal, m_szInterface, mdtProperty)); + pszName = qbName.Ptr(); + + // Define the property. + IfFailGo(m_pEmit->DefineProperty(m_tdTypeDef, pszName, 0/*dwFlags*/, + (PCCOR_SIGNATURE) qbComSig.Ptr(), cbTotal, 0, 0, -1, + mdFuncs[msSetter], mdFuncs[msGetter], &mdFuncs[msOther], + &pd)); + + // Handle dispids for non-implemented interfaces, and for default interface + if (m_ImplIface != eImplIface) + { + // Set the dispid CA on the property. + long lDispSet = 1; + _SetDispIDCA(pITI, pMember->m_iMember, psF->memid, pd, TRUE, &lDispSet, TRUE); + + // If this property is default property, add a custom attribute to the class. + if (lDispSet == DISPID_VALUE) + IfFailGo(_AddDefaultMemberCa(m_tdTypeDef, m_szMember)); + } + + // Add the alias information if the type is an alias. + IfFailGo(_HandleAliasInfo(pITI, pProperty, pd)); + } + + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + +ErrExit: + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember=0; + + return hr; +} // HRESULT CImportTlb::_ConvPropertiesForFunctions() + +//***************************************************************************** +// Convert the vars and functions of a dispinterface. Vars actually turn +// into a getter and possibly a setter. +//***************************************************************************** +HRESULT CImportTlb::_ConvDispatchMembers( + ITypeInfo *pITI, // ITypeInfo* to convert. + TYPEATTR *psAttr, // TypeAttr of ITypeInfo. + BOOL fInheritsIEnum) +{ + HRESULT hr; // A result. + int i; // Loop control. + BOOL bConversionLoss=FALSE; // If true, some attributes were lost on conversion. + + IfFailGo(_FindFirstUserMethod(pITI, psAttr, &i)); + IfFailGo(BuildMemberList(pITI, i, psAttr->cFuncs, fInheritsIEnum)); + + // Dispatch members really have no slot. + m_Slot = 0; + + // Loop over properties. + for (i=0; i<m_cMemberProps; ++i) + { + IfFailGo(_ConvProperty(pITI, &m_MemberList[i])); + } + + // Loop over functions. + BOOL bAllowIEnum = !fInheritsIEnum; + for (; i<(int)m_MemberList.Size(); ++i) + { + // Get variable information. + IfFailGo(_ConvFunction(pITI, &m_MemberList[i], FALSE, TRUE, FALSE, &bAllowIEnum)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = TRUE; + } + + // Add the property info. + IfFailGo(_ConvPropertiesForFunctions(pITI, psAttr)); + + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + +ErrExit: + // Free the func descs. + FreeMemberList(pITI); + + return (hr); +} // HRESULT CImportTlb::_ConvDispatchMembers() + +//***************************************************************************** +// Examine the functions on an interface, and skip the first 3 or first 7 +// if the functions are IUnknown or IDispatch members. +//***************************************************************************** +HRESULT CImportTlb::_FindFirstUserMethod( + ITypeInfo *pITI, // The Typedef to examine. + TYPEATTR *psAttr, // TYPEATTR for the typedef. + int *pIx) // Put index of first user function here. +{ + HRESULT hr = S_OK; // A result. + int i; // Loop control. + FUNCDESC *psFunc=0; // FUNCDESC for a member. + BSTR szName=0; // A function's name. + + // Note: this is a terrible workaround, but in some situations the methods from IUnknown / IDispatch will + // show up as though native dispatch functions. + i = 0; + if (psAttr->cFuncs >= 3) + { + IfFailGo(TryGetFuncDesc(pITI, i, &psFunc)); + if (psFunc->memid == 0x60000000 && + psFunc->elemdescFunc.tdesc.vt == VT_VOID && + psFunc->cParams == 2 && + psFunc->lprgelemdescParam[0].tdesc.vt == VT_PTR && // -> VT_USERDEFINED + psFunc->lprgelemdescParam[1].tdesc.vt == VT_PTR && // -> VT_PTR -> VT_VOID + SUCCEEDED(pITI->GetDocumentation(psFunc->memid, &szName, 0,0,0)) && + (wcscmp(szName, W("QueryInterface")) == 0) ) + i = 3; + pITI->ReleaseFuncDesc(psFunc); + psFunc=0; + if (szName) + ::SysFreeString(szName); + szName = 0; + if (psAttr->cFuncs >= 7) + { + IfFailGo(TryGetFuncDesc(pITI, i, &psFunc)); + if (psFunc->memid == 0x60010000 && + psFunc->elemdescFunc.tdesc.vt == VT_VOID && + psFunc->cParams == 1 && + psFunc->lprgelemdescParam[0].tdesc.vt == VT_PTR && // -> VT_UINT + SUCCEEDED(pITI->GetDocumentation(psFunc->memid, &szName, 0,0,0)) && + (wcscmp(szName, W("GetTypeInfoCount")) == 0) ) + i = 7; + pITI->ReleaseFuncDesc(psFunc); + psFunc=0; + if (szName) + ::SysFreeString(szName); + szName = 0; + } + } + + *pIx = i; + +ErrExit: + if (psFunc) + pITI->ReleaseFuncDesc(psFunc); + if (szName) + ::SysFreeString(szName); + return (hr); +} // HRESULT CImportTlb::_FindFirstUserMethod() + +//***************************************************************************** +// Given a FUNCDESC that is has INVOKE_PROPERTY* decoration, determine +// the role of the function, and the property signature type. +//***************************************************************************** +HRESULT CImportTlb::_GetFunctionPropertyInfo( + FUNCDESC *psFunc, // Function for which to get info. + USHORT *pSemantics, // Put appropriate semantics here. + FUNCDESC **ppSig, // Put FUNCDESC for signature here. + TYPEDESC **ppProperty, // Put TYPEDESC for return here. + BOOL *pbRetval, // If true, the type is [retval] + BOOL fUseLastParam, // If true, default to the last parameter as the return value + BSTR strName) // Name of the property +{ + FUNCDESC *psTmp; // FUNCDESC for some method. + FUNCDESC *psGet=0; // FUNCDESC for Get method defining a property. + FUNCDESC *psPut=0; // FUNCDESC for Put method defining a property. + FUNCDESC *psPutRef=0; // FUNCDESC for PutRef method defining a property. + FUNCDESC *psF; // A FUNCDESC. + TYPEDESC *pReturn=0; // The FUNCDESC's return type. + int cFound=0; // Count of functions found. + int i; // Loop control. + HRESULT hr = S_OK; + + if (psFunc->invkind & INVOKE_PROPERTYGET) + { // A "Get", so return type is property type. + *ppSig = psFunc; + *pSemantics = msGetter; + } + else + { + _ASSERTE(psFunc->invkind & (INVOKE_PROPERTYPUT | INVOKE_PROPERTYPUTREF)); + // Search for the "best" method from which to grab the signature. We prefer the Get(), + // Followed by the Put(), followed by the PutRef() + // Also look for Put() and PutRef(), so we can + for (int iFunc=0; iFunc<(int)m_MemberList.Size() && cFound<3; ++iFunc) + { + // Get a FUNCDESC from the list. + psTmp = m_MemberList[iFunc].m_psFunc; + + // Is it for the same func? + if (psTmp->memid != psFunc->memid) + continue; + + // Is it the Get()? If so, it is the one we want. + if (psTmp->invkind & INVOKE_PROPERTYGET) + { + psGet = psTmp; + ++cFound; + continue; + } + + // Is it the Put()? Use it if we don't find a Get(). + if (psTmp->invkind & INVOKE_PROPERTYPUT) + { + psPut = psTmp; + ++cFound; + continue; + } + + // Is it the PutRef()? Keep track of it. + if (psTmp->invkind & INVOKE_PROPERTYPUTREF) + { + psPutRef = psTmp; + ++cFound; + } + } + // Get the best FUNCDESC for the signature. + *ppSig = psGet ? psGet : (psPut ? psPut : psFunc); + + // Determine whether this is a the "Set" or "VB specific Let" function. + if (psFunc->invkind & INVOKE_PROPERTYPUTREF) + { // This function is the PROPERTYPUTREF. Make it the setter. If + // there is also a PROPERTYPUT, it will be the "letter". + *pSemantics = msSetter; + } + else + { // We are looking at the PROPERTYPUT function (the "Let" function in native VB6.). + + // If there is also a PROPERTYPUTREF, make this the "VB Specific Let" function. + if (psPutRef) + { // A PPROPERTYPUTREF also exists, so make this the "Let" function. + *pSemantics = msOther; + } + else + { // There is no PROPERTYPUTREF, so make this the setter. + *pSemantics = msSetter; + } + } + } + + // Occasionally there is a property with no discernable type. In that case, lose the + // property on conversion. + + // Determine the type of the property, based on the "best" accessor. + psF = *ppSig; + *pbRetval = FALSE; + if (psF->invkind & INVOKE_PROPERTYGET) + { // look for [retval]. + for (i=psF->cParams-1; i>=0; --i) + { + if (psF->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FRETVAL) + { // will consume a level of indirection (later). + *pbRetval = TRUE; + pReturn = &psF->lprgelemdescParam[i].tdesc; + break; + } + } + // If no [retval], check return type. + if (!pReturn && psF->elemdescFunc.tdesc.vt != VT_VOID && psF->elemdescFunc.tdesc.vt != VT_HRESULT) + pReturn = &psF->elemdescFunc.tdesc; + + if (fUseLastParam) + { + // We may have stripped the [retval] if this is a disp-only interface. Just use the last parameter. + if (!pReturn && (psF->cParams > 0)) + pReturn = &psF->lprgelemdescParam[psF->cParams-1].tdesc; + } + else + { + // If there is no type, don't try to set the getter. + if (pReturn && pReturn->vt == VT_VOID) + pReturn = NULL; + } + + if (!pReturn) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_PROPGET_WITHOUT_RETURN, strName, m_szMngName); + } + } + else + { // Find lastmost param that isn't [retval]. (Should be the last param, but it is + // possible to write an IDL with a PROPERTYPUT that has a [retval]. + for (i=psF->cParams-1; i>=0; --i) + { + if ((psF->lprgelemdescParam[psF->cParams-1].paramdesc.wParamFlags & PARAMFLAG_FRETVAL) == 0) + { + { // First, and possibly only, param. + pReturn = &psF->lprgelemdescParam[i].tdesc; + break; + } + } + } + } + +//ErrExit: + if (pReturn == 0) + *pSemantics = 0; + *ppProperty = pReturn; + + return hr; +} // HRESULT CImportTlb::_GetFunctionPropertyInfo() + +//***************************************************************************** +// Convert a function description to metadata entries. +// +// This can be rather involved. If the function is a INVOKE_PROPERTY*, +// determine if it will be converted as a COM+ property, and if so, which +// of up to three functions will be selected to provide the property +// signature. +// The function return type is found by scaning the parameters looking for +// [retval]s. +//***************************************************************************** +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +HRESULT CImportTlb::_ConvFunction( + ITypeInfo *pITI, // Containing TypeInfo. + MemberInfo *pMember, // iNFO for the function. + BOOL bVtblGapFuncs, // Add functions for vtblGaps? + BOOL bAddDispIds, // Add DispIds to the member? + BOOL bDelegateInvokeMeth, // Convert function for a delegate invoke + BOOL* bAllowIEnum) // Allowed to change this function to GetEnumerator +{ + HRESULT hr; // A result. + int iSrcParam; // Param count, as looping through params. + int iDestParam; // Param count, as looping through params. + int cDestParams; // Count of destination params. + int ixOpt; // Index of first param that is optional due to cParamsOpt. + int ixVarArg; // Index of vararg param, if any. + mdMethodDef mdFunc; // Token of new member. + BSTR szTypeName=0; // Name of the type. + DWORD dwFlags=0; // Member flags. + DWORD dwImplFlags=0; // The impl flags. + WCHAR *pszName=0; // Possibly decorated name of member. + CQuickArray<WCHAR> qbName; // Buffer for decorated name. + TYPEDESC *pReturn=0; // Return type. + int bRetval=false; // Is the return result a [retval] parameter? + int ixRetval; // Which param is the [retval]? + TYPEDESC *pReturnRetval=0; // Return type from [retval] (incl. indirection). + WORD wRetFlags=0; // Return type flags. + ULONG offset=0; // Offset of function + BSTR *rszParamNames=0; // Parameter names. + UINT iNames; // Count of actual names. + CQuickBytes qbComSig; // new signature + BYTE *pbSig; // Pointer into the signature. + ULONG sigFlags; // Signature handling flags. + CQuickArray<BYTE> qbNativeBuf; // Native type buffer. + CQuickArray<BYTE> qbDummyNativeTypeBuf; // A dummy native type array. + CQuickArray<ULONG> qbNativeOfs; // Offset of native type for each param. + CQuickArray<ULONG> qbNativeLen; // Length of native type for each param. + ULONG iNativeOfs=0; // Current offset in native type buffer. + ULONG iNewNativeOfs=0; // New offset in native type buffer. + ULONG cb; // Size of an element. + ULONG cbTotal = 0; // Size of the signature. + int bOleCall=false; // Is the implementation OLE style?(HRESULT or IDispatch) + USHORT msSemantics=0; // Property's methodsemantics. + WCHAR szSpecial[40]; // To build name of special function. + mdToken tkAttr; // Token for custom attribute type. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + enum {ParamRetval=-1, ParamNone=-2}; + int iParamError=ParamNone; // Index of param with conversion error. + BOOL bNewEnumMember = FALSE; // A flag indicating if the member is the NewEnum member. + int iLCIDParam = -1; // Index of the LCID parameter. + FUNCDESC *psFunc = pMember->m_psFunc; + + // If we might insert vtable gaps, then we'd better have initialized the vtable slot size. + _ASSERTE(!bVtblGapFuncs || (m_cbVtableSlot != 0)); + + // Retrieve the member name from the member info. + IfNullGo(m_szMember = SysAllocString(bDelegateInvokeMeth ? DELEGATE_INVOKE_METH_NAME : pMember->m_pName)); + +#ifdef _DEBUG + LPWSTR funcName = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TlbImpShouldBreakOnConvFunction); + if (funcName) + { + if (wcscmp(funcName, pMember->m_pName) == 0) + _ASSERTE(!"TlbImpBreakOnConvFunction"); + + delete [] funcName; + } +#endif //_DEBUG + + // Determine if the member is the new enum member. + if ((*bAllowIEnum)) + { + bNewEnumMember = FuncIsNewEnum(pITI, psFunc, pMember->m_iMember) == S_OK; + + // Once a method is converted in this interface, don't convert any more. + if (bNewEnumMember) + *bAllowIEnum = FALSE; + } + + + // We should NEVER have a new enum member when we are dealing with a delegate invoke meth. + if (bNewEnumMember && bDelegateInvokeMeth) + { + // Get the real name of the method + BSTR szTypeInfoName = NULL; + BSTR szMemberName = NULL; + hr = m_pITI->GetDocumentation(MEMBERID_NIL, &szTypeInfoName, 0, 0, 0); + if (FAILED(hr)) + szTypeInfoName = SysAllocString(W("???")); + + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_EVENT_WITH_NEWENUM, szTypeInfoName); + + SysFreeString(szMemberName); + SysFreeString(szTypeInfoName); + + IfFailGo(TLBX_E_EVENT_WITH_NEWENUM); + } + + // If there is a gap in the vtable, emit a special function. + if (bVtblGapFuncs) + { + if ((psFunc->oVft / m_cbVtableSlot) != m_Slot) + { + ULONG n = psFunc->oVft / m_cbVtableSlot; + // Make sure slot numbers are monotonically increasing. + if (n < m_Slot) + { + IfFailGo(pITI->GetDocumentation(MEMBERID_NIL, &szTypeName, 0, 0, 0)); + IfFailGo(PostError(TLBX_E_BAD_VTABLE, m_szMember, szTypeName, m_szLibrary)); + } + + n -= m_Slot; + if (n == 1) + _snwprintf_s(szSpecial, lengthof(szSpecial), lengthof(szSpecial) - 1, VTBL_GAP_FORMAT_1, VTBL_GAP_FUNCTION, m_Slot); + else + _snwprintf_s(szSpecial, lengthof(szSpecial), lengthof(szSpecial) - 1, VTBL_GAP_FORMAT_N, VTBL_GAP_FUNCTION, m_Slot, n); + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, szSpecial, VTBL_GAP_FUNCTION_FLAGS, VTBL_GAP_SIGNATURE,sizeof(VTBL_GAP_SIGNATURE), + 0/* rva*/, VTBL_GAP_FUNC_IMPL_FLAGS, &mdFunc)); + m_Slot += n; + } + // What we will expect next time. + ++m_Slot; + } + + //------------------------------------------------------------------------- + // Determine the return type. + // If this is an hresult function, prepare to munge return, params. + if (psFunc->elemdescFunc.tdesc.vt == VT_HRESULT) + { + bOleCall = true; + } + else + { + if ((psFunc->elemdescFunc.tdesc.vt != VT_VOID) && (psFunc->elemdescFunc.tdesc.vt != VT_HRESULT)) + pReturn = &psFunc->elemdescFunc.tdesc; + } + + // Look for [RETVAL]. + for (iSrcParam=0; iSrcParam<psFunc->cParams; ++iSrcParam) + { + if (psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & PARAMFLAG_FRETVAL) + { + // If already have a return, or a DISPATCH function, error. + if (pReturn != 0) + { // Unexpected return found. + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_AMBIGUOUS_RETURN, m_szName, m_szMember); + IfFailGo(TLBX_E_AMBIGUOUS_RETURN); + } + else + { // Found a return type. + wRetFlags = psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags; + pReturn = &psFunc->lprgelemdescParam[iSrcParam].tdesc; + bRetval = true; + ixRetval = iSrcParam; + } + break; + } + } + + // Check to see if there is an LCID parameter. + for (iSrcParam=0;iSrcParam<psFunc->cParams;iSrcParam++) + { + if (psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & PARAMFLAG_FLCID) + { + if (iLCIDParam != -1) + IfFailGo(PostError(TLBX_E_MULTIPLE_LCIDS, m_szName, m_szMember)); + iLCIDParam = iSrcParam; + } + } + + //------------------------------------------------------------------------- + // Size buffers to accomodate parameters. + // Resize the native type length array. + IfFailGo(qbNativeBuf.ReSizeNoThrow(1)); + IfFailGo(qbNativeLen.ReSizeNoThrow(psFunc->cParams + 1)); + IfFailGo(qbNativeOfs.ReSizeNoThrow(psFunc->cParams + 1)); + memset(qbNativeLen.Ptr(), 0, (psFunc->cParams + 1)*sizeof(int)); + memset(qbNativeOfs.Ptr(), 0, (psFunc->cParams + 1)*sizeof(int)); + + // resize to make room for calling convention and count of argument + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE + 1)); + pbSig = (BYTE *)qbComSig.Ptr(); + + //------------------------------------------------------------------------- + // Determine which params need to be marked optional, by virtue of cParamsOpt count. + if (psFunc->cParamsOpt == 0) + ixVarArg = ixOpt = psFunc->cParams + 1; + else + { + if (psFunc->cParamsOpt == -1) + { // Varargs. + ixVarArg = ixOpt = psFunc->cParams + 1; + // If this is a PROPERTYPUT or PROPERTYPUTREF, skip the last non-retval parameter (it + // is the new value to be set). + BOOL bPropVal = (psFunc->invkind & (INVOKE_PROPERTYPUT | INVOKE_PROPERTYPUTREF)) ? TRUE : FALSE; + // Find the vararg param. + for (iSrcParam=psFunc->cParams-1; iSrcParam>=0; --iSrcParam) + { + // The count of optional params does not include any lcid params, nor does + // it include the return value, so skip those. + if ((psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & (PARAMFLAG_FRETVAL|PARAMFLAG_FLCID)) != 0) + continue; + // If haven't yet seen the property value, this param is it, so skip it, too. + if (bPropVal) + { + bPropVal = FALSE; + continue; + } + ixVarArg = iSrcParam; + break; + } // for (iSrcParam=cParams-1... + } + else + { // ixOpt will be index of first optional parameter. + short cOpt = psFunc->cParamsOpt; + ixOpt = 0; + ixVarArg = psFunc->cParams + 1; + for (iSrcParam=psFunc->cParams-1; iSrcParam>=0; --iSrcParam) + { + // The count of optional params does not include any lcid params, nor does + // it include the return value, so skip those. + if ((psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & (PARAMFLAG_FRETVAL|PARAMFLAG_FLCID)) == 0) + { + if (--cOpt == 0) + { + ixOpt = iSrcParam; + break; + } + } + } // for (iSrcParam=cParams-1... + } + } + + + //------------------------------------------------------------------------- + // Get the parameter names. + rszParamNames = reinterpret_cast<BSTR*>(_alloca((psFunc->cParams+1) * sizeof(BSTR*))); + + // Get list of names. + IfFailGo(pITI->GetNames(psFunc->memid, rszParamNames, psFunc->cParams+1, &iNames)); + + // zero name pointer for non-named params. + for (iSrcParam=iNames; iSrcParam<=psFunc->cParams; ++iSrcParam) + rszParamNames[iSrcParam] = 0; + + //------------------------------------------------------------------------- + // Convert the calling convention, param count, and return type. + cDestParams = psFunc->cParams; + if (bRetval) + --cDestParams; + if (iLCIDParam != -1) + --cDestParams; + + if (pReturn) + { + // Param count + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, pbSig); + cb = CorSigCompressData(cDestParams, &(pbSig[cbTotal])); + cbTotal += cb; + // Return type or [retval]. + if (bRetval) + sigFlags = (SigFlags)(wRetFlags & SIG_FLAGS_MASK) | SIG_FUNC, iParamError=ixRetval; + else + sigFlags = SIG_FUNC, iParamError=ParamRetval; + IfFailGo(_ConvSignature(pITI, pReturn, sigFlags, qbComSig, cbTotal, &cbTotal, qbNativeBuf, iNativeOfs, &iNewNativeOfs, bNewEnumMember)); + qbNativeLen[0] = iNewNativeOfs - iNativeOfs; + qbNativeOfs[0] = iNativeOfs; + iNativeOfs = iNewNativeOfs; + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + } + else + { // No return value + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, pbSig); + cb = CorSigCompressData(cDestParams, &(pbSig[cbTotal])); + cbTotal += cb; + cb = CorSigCompressData(ELEMENT_TYPE_VOID, &pbSig[cbTotal]); + cbTotal += cb; + } + + //------------------------------------------------------------------------- + // Translate each parameter. + for (iSrcParam=0, iDestParam=0; iSrcParam<psFunc->cParams; ++iSrcParam) + { + if (!(psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & NON_CONVERTED_PARAMS_FLAGS)) + { + sigFlags = (SigFlags)(psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & SIG_FLAGS_MASK) | SIG_FUNC | SIG_USE_BYREF; + if (iSrcParam == ixVarArg) + sigFlags |= SIG_VARARG; + iParamError = iSrcParam; + IfFailGo(_ConvSignature(pITI, &psFunc->lprgelemdescParam[iSrcParam].tdesc, sigFlags, qbComSig, cbTotal, &cbTotal, qbNativeBuf, iNativeOfs, &iNewNativeOfs, bNewEnumMember)); + qbNativeLen[iDestParam+1] = iNewNativeOfs - iNativeOfs; + qbNativeOfs[iDestParam+1] = iNativeOfs; + iNativeOfs = iNewNativeOfs; + iDestParam++; + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + } + } + iParamError = ParamNone; + + //------------------------------------------------------------------------- + // Get the previously decorated name. Add interface name and make unique. + if (bDelegateInvokeMeth) + { + pszName = (WCHAR*)DELEGATE_INVOKE_METH_NAME; + } + else + { + // m_szInterface should be non-null if processing an implemented interface; should be null otherwise. + _ASSERTE(m_ImplIface == eImplIfaceNone || m_szInterface != 0); + IfFailGo(qbName.ReSizeNoThrow(wcslen(pMember->m_pName)+2)); + wcscpy_s(qbName.Ptr(), wcslen(pMember->m_pName)+2, pMember->m_pName); + IfFailGo(GenerateUniqueMemberName(qbName, (PCCOR_SIGNATURE)qbComSig.Ptr(), cbTotal, m_szInterface, mdtMethodDef)); + pszName = qbName.Ptr(); + } + + // Determine the function's semantics, flags and impl flags. + if (!bDelegateInvokeMeth) + { + msSemantics = pMember->m_msSemantics; + dwImplFlags = DEFAULT_ITF_FUNC_IMPL_FLAGS; + dwFlags = msSemantics ? DEFAULT_PROPERTY_FUNC_FLAGS : DEFAULT_INTERFACE_FUNC_FLAGS; + // If processing an implemented interface, remove the abstract bit. Methods on classes are not abstract. + if (m_ImplIface != eImplIfaceNone) + dwFlags &= ~mdAbstract; + } + else + { + msSemantics = 0; + dwImplFlags = miRuntime; + dwFlags = DELEGATE_INVOKE_FUNC_FLAGS; + } + + //------------------------------------------------------------------------- + // Create the function definition in the metadata. + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, pszName, dwFlags, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, + 0 /* rva*/, dwImplFlags | (bOleCall ? 0 : miPreserveSig), &mdFunc)); + + // If the method is part of a property, save info to set up the property. + if (msSemantics) + pMember->SetFuncInfo(mdFunc, msSemantics); + + // Handle dispids for non-implemented interfaces, and for default interface + if (m_ImplIface != eImplIface) + { + // Add the DispIds if the flag is set. + long lDispSet = 1; + _SetDispIDCA(pITI, pMember->m_iMember, psFunc->memid, mdFunc, bAddDispIds, &lDispSet, TRUE); + + // If this method is the default, and not a property accessor, add a custom attribute to the class. + if (lDispSet == DISPID_VALUE && msSemantics == 0) + IfFailGo(_AddDefaultMemberCa(m_tdTypeDef, m_szMember)); + } + + DECLARE_CUSTOM_ATTRIBUTE(sizeof(int)); + + // If this method has an LCID then set the LCIDConversion attribute. + if (iLCIDParam != -1) + { + // Dispid for the function. + BUILD_CUSTOM_ATTRIBUTE(int, iLCIDParam); + IfFailGo(GetAttrType(ATTR_LCIDCONVERSION, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdFunc, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + + // Save the func flags for anyone that needs typelib's flags. + if (psFunc->wFuncFlags) + { + IfFailGo(GetAttrType(ATTR_TYPELIBFUNC, &tkAttr)); + INIT_CUSTOM_ATTRIBUTE(sizeof(WORD)); + BUILD_CUSTOM_ATTRIBUTE(WORD, psFunc->wFuncFlags); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdFunc, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + //------------------------------------------------------------------------- + // Convert the param info for the return type. + if (pReturn) + { // store return value parameter as sequence 0 + if (bRetval) + { + hr = _IsAlias(pITI, &psFunc->lprgelemdescParam[ixRetval].tdesc); + IfFailGo(hr); + if (qbNativeLen[0] || hr == S_OK) + { + IfFailGo(_ConvParam(pITI, mdFunc, 0, &psFunc->lprgelemdescParam[ixRetval], ParamNormal, 0 /*rszParamNames[ixRetval+1]*/, + &qbNativeBuf[qbNativeOfs[0]], qbNativeLen[0])); + } + } + else + { + hr = _IsAlias(pITI, &psFunc->elemdescFunc.tdesc); + IfFailGo(hr); + if (qbNativeLen[0] || hr == S_OK) + { + IfFailGo(_ConvParam(pITI, mdFunc, 0, &psFunc->elemdescFunc, ParamNormal, 0, + &qbNativeBuf[qbNativeOfs[0]], qbNativeLen[0])); + } + } + } + + //------------------------------------------------------------------------- + // Convert parameter info (flags, native type, default value). + for (iSrcParam=iDestParam=0; iSrcParam<psFunc->cParams; ++iSrcParam) + { + if ((psFunc->lprgelemdescParam[iSrcParam].paramdesc.wParamFlags & NON_CONVERTED_PARAMS_FLAGS) == 0) + { + ParamOpts opt = ParamNormal; + if (iSrcParam >= ixOpt) + opt = ParamOptional; + else + if (iSrcParam == ixVarArg) + opt = ParamVarArg; + iDestParam++; + IfFailGo(_ConvParam(pITI, mdFunc, iDestParam, &psFunc->lprgelemdescParam[iSrcParam], opt, rszParamNames[iSrcParam + 1], + &qbNativeBuf[qbNativeOfs[iDestParam]], qbNativeLen[iDestParam])); + } + } + + + //------------------------------------------------------------------------- + // If processing an implemented interface, set up MethodImpls. + if (m_ImplIface != eImplIfaceNone) + { + // Define a memberref on the implemented interface. + mdToken mrItfMember; + IfFailGo(m_pEmit->DefineMemberRef(m_tkInterface, pMember->m_pName, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, &mrItfMember)); + + // Define a method impl. + IfFailGo(m_pEmit->DefineMethodImpl(m_tdTypeDef, mdFunc, mrItfMember)); + } + + if (bConversionLoss) + { + hr = S_CONVERSION_LOSS; + ReportEvent(NOTIF_CONVERTWARNING, TLBX_I_UNCONVERTABLE_ARGS, m_szName, m_szMember); + } + +ErrExit: + // Special case for typeload load failures -- they're very hard to diagnose. + if (hr == TYPE_E_CANTLOADLIBRARY) + { + if (iParamError >= 0 && iParamError < psFunc->cParams && rszParamNames[iParamError+1]) + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_PARAM_ERROR_NAMED, m_szName, rszParamNames[iParamError+1], m_szMember); + else + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_PARAM_ERROR_UNNAMED, m_szName, iParamError, m_szMember); + } + if (rszParamNames) + { + for (iSrcParam=0; iSrcParam<=psFunc->cParams; ++iSrcParam) + if (rszParamNames[iSrcParam]) + ::SysFreeString(rszParamNames[iSrcParam]); + } + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember=0; + if (szTypeName) + ::SysFreeString(szTypeName); + + return (hr); +} // HRESULT CImportTlb::_ConvFunction() +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + + +HRESULT CImportTlb::_SetHiddenCA(mdTypeDef token) +{ + mdToken tkAttr; + HRESULT hr = S_OK; + + DECLARE_CUSTOM_ATTRIBUTE(sizeof(short)); + BUILD_CUSTOM_ATTRIBUTE(short, TYPEFLAG_FHIDDEN); + IfFailGo(GetAttrType(ATTR_TYPELIBTYPE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + m_pEmit->DefineCustomAttribute(token, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0); + +ErrExit: + return S_OK; +} + +HRESULT CImportTlb::_ForceIEnumerableCVExists(ITypeInfo* pITI, BOOL* CVExists) +{ + ITypeInfo2 *pITI2 = 0; + *CVExists = FALSE; + HRESULT hr = S_OK; + + pITI->QueryInterface(IID_ITypeInfo2, reinterpret_cast<void**>(&pITI2)); + + if (pITI2) + { + VARIANT vCustomData; + VariantInit(&vCustomData); + + IfFailGo(pITI2->GetCustData(GUID_ForceIEnumerable, &vCustomData)); + + if (V_VT(&vCustomData) != VT_EMPTY) + *CVExists = TRUE; + + VariantClear(&vCustomData); + } + +ErrExit: + if (pITI2) + pITI2->Release(); + + return S_OK; +} + + +HRESULT CImportTlb::_GetDispIDCA( + ITypeInfo* pITI, + int iMember, + long* lDispSet, + BOOL bFunc + ) +{ + ITypeInfo2 *pITI2=0; // For getting custom value. + HRESULT hr = S_OK; + long lDispId = DISPID_UNKNOWN; + + // Get the ITypeInfo2 interface if possible + pITI->QueryInterface(IID_ITypeInfo2, reinterpret_cast<void**>(&pITI2)); + + if (pITI2) + { + VARIANT vCustomData; + VariantInit(&vCustomData); + + if (bFunc) + IfFailGo(pITI2->GetFuncCustData(iMember, GUID_DispIdOverride, &vCustomData)); + else + IfFailGo(pITI2->GetVarCustData(iMember, GUID_DispIdOverride, &vCustomData)); + + if ((V_VT(&vCustomData) == VT_I2) || (V_VT(&vCustomData) == VT_I4)) + { + hr = VariantChangeType(&vCustomData, &vCustomData, 0, VT_I4); + if (hr == S_OK) + lDispId = vCustomData.lVal; + } + + VariantClear(&vCustomData); + } + +ErrExit: + if (lDispSet != NULL) + *lDispSet = lDispId; + + if (pITI2) + pITI2->Release(); + + return S_OK; +} + + + +HRESULT CImportTlb::_SetDispIDCA( + ITypeInfo* pITI, + int iMember, + long lDispId, + mdToken func, + BOOL fAlwaysAdd, + long* lDispSet, + BOOL bFunc + ) +{ + WCHAR DispIDCA[] = W("{CD2BC5C9-F452-4326-B714-F9C539D4DA58}"); + ITypeInfo2 *pITI2=0; // For getting custom value. + HRESULT hr = S_OK; + + // Get the ITypeInfo2 interface if possible + pITI->QueryInterface(IID_ITypeInfo2, reinterpret_cast<void**>(&pITI2)); + + if (pITI2) + { + VARIANT vCustomData; + VariantInit(&vCustomData); + + if (bFunc) + IfFailGo(pITI2->GetFuncCustData(iMember, GUID_DispIdOverride, &vCustomData)); + else + IfFailGo(pITI2->GetVarCustData(iMember, GUID_DispIdOverride, &vCustomData)); + + if ((V_VT(&vCustomData) == VT_I2) || (V_VT(&vCustomData) == VT_I4)) + { + hr = VariantChangeType(&vCustomData, &vCustomData, 0, VT_I4); + if (hr == S_OK) + { + lDispId = vCustomData.lVal; + fAlwaysAdd = true; + } + } + else if (V_VT(&vCustomData) != VT_EMPTY) + { + // Invalid Variant type on the data - spit out a warning. + BSTR CustomValue = SysAllocString((const WCHAR*)&DispIDCA[0]); + BSTR ObjectName; + IfFailGo(pITI2->GetDocumentation(iMember+1, &ObjectName, NULL, NULL, NULL)); + + ReportEvent(NOTIF_CONVERTWARNING, TLBX_W_NON_INTEGRAL_CA_TYPE, CustomValue, ObjectName); + + SysFreeString(CustomValue); + SysFreeString(ObjectName); + } + + VariantClear(&vCustomData); + } + + // Set the dispid CA on the property. + if (fAlwaysAdd) + { + mdToken tkAttr; + DECLARE_CUSTOM_ATTRIBUTE(sizeof(DISPID)); + BUILD_CUSTOM_ATTRIBUTE(DISPID, lDispId); + IfFailGo(GetAttrType(ATTR_DISPID, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(func, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + +ErrExit: + if (lDispSet != NULL) + { + *lDispSet = lDispId; + } + + if (pITI2) + pITI2->Release(); + + return S_OK; +} + + +HRESULT CImportTlb::_CheckForPropertyCustomAttributes(ITypeInfo* pITI, int index, INVOKEKIND* ikind) +{ + HRESULT hr; + VARIANT vCustomData; + ITypeInfo2* pITI2 = 0; + BOOL found = FALSE; + + VariantInit(&vCustomData); + + // Get the ITypeInfo2 interface if possible + pITI->QueryInterface(IID_ITypeInfo2, reinterpret_cast<void**>(&pITI2)); + if (pITI2) + { + // First, check for PropGet + hr = pITI2->GetFuncCustData(index, GUID_PropGetCA, &vCustomData); + IfFailGo(hr); + if (V_VT(&vCustomData) != VT_EMPTY) + { + *ikind = INVOKE_PROPERTYGET; + found = TRUE; + goto ErrExit; + } + + // Second, check for PropPut + VariantClear(&vCustomData); + hr = pITI2->GetFuncCustData(index, GUID_PropPutCA, &vCustomData); + IfFailGo(hr); + if (V_VT(&vCustomData) != VT_EMPTY) + { + *ikind = INVOKE_PROPERTYPUT; + found = TRUE; + goto ErrExit; + } + } + +ErrExit: + VariantClear(&vCustomData); + + if (pITI2) + pITI2->Release(); + + if (found) + return S_OK; + + return S_FALSE; +} + +//***************************************************************************** +// Generate an event with an add and remove method +//***************************************************************************** +HRESULT CImportTlb::_GenerateEvent( + ITypeInfo *pITI, // Containing TypeInfo. + MemberInfo *pMember, // Info for the function + BOOL fInheritsIEnum) +{ + HRESULT hr = S_OK; // A result. + mdMethodDef mdAdd; // Token of add_XXX method. + mdMethodDef mdRemove; // Token of remove_XXX method. + CQuickArray<WCHAR> qbName; // Buffer for decorated name. + CQuickArray<BYTE> qbSig; // The signature. + ULONG cb; // Size of an element. + ULONG cbTotal = 0; // Size of the signature. + mdTypeDef tdDelegate; // The delegate type def. + mdEvent tkEvent; // The token for the event. + + // If this method is a property method, then skip the event. + // Also look at the property semantic - it might be we couldn't import this as a property + // and fell back to a method. + if ((pMember->m_psFunc->invkind != INVOKE_FUNC) && (pMember->m_msSemantics != 0)) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_W_NO_PROPS_IN_EVENTS, m_szName); + return S_CONVERSION_LOSS; + } + + // Generate the delegate. + IfFailGo(_GenerateEventDelegate(pITI, pMember, &tdDelegate, fInheritsIEnum)); + + // Generate the sig for the add and remove methods. + IfFailGo(qbSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 2 + 1)); + cbTotal = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, qbSig.Ptr()); + cb = CorSigCompressData(1, &(qbSig[cbTotal])); + cbTotal += cb; + cb = CorSigCompressData(ELEMENT_TYPE_VOID, &qbSig[cbTotal]); + cbTotal += cb; + cb = CorSigCompressData(ELEMENT_TYPE_CLASS, &qbSig[cbTotal]); + cbTotal += cb; + cb = CorSigCompressToken(tdDelegate, &qbSig[cbTotal]); + cbTotal += cb; + + // Generate the add method. + IfFailGo(qbName.ReSizeNoThrow(EVENT_ADD_METH_PREFIX_LENGTH + wcslen(pMember->m_pName) + 1)); + StringCchPrintf(qbName.Ptr(), qbName.Size(), W("%s%s"), EVENT_ADD_METH_PREFIX, pMember->m_pName); + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, qbName.Ptr(), DEFAULT_INTERFACE_FUNC_FLAGS, + qbSig.Ptr(), cbTotal, 0 /* rva*/, DEFAULT_ITF_FUNC_IMPL_FLAGS, &mdAdd)); + + // Generate the remove method. + IfFailGo(qbName.ReSizeNoThrow(EVENT_REM_METH_PREFIX_LENGTH + wcslen(pMember->m_pName) + 1)); + StringCchPrintf(qbName.Ptr(), qbName.Size(), W("%s%s"), EVENT_REM_METH_PREFIX, pMember->m_pName); + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, qbName.Ptr(), DEFAULT_INTERFACE_FUNC_FLAGS, + qbSig.Ptr(), cbTotal, 0 /* rva*/, DEFAULT_ITF_FUNC_IMPL_FLAGS, &mdRemove)); + + // Define the event itself. + IfFailGo(m_pEmit->DefineEvent(m_tdTypeDef, pMember->m_pName, 0, tdDelegate, + mdAdd, mdRemove, mdTokenNil, NULL, &tkEvent)); + +ErrExit: + + return (hr); +} // HRESULT CImportTlb::_GenerateEvent() + +//***************************************************************************** +// Generate an add and remove method +//***************************************************************************** +HRESULT CImportTlb::_GenerateEventDelegate( + ITypeInfo *pITI, // Containing TypeInfo. + MemberInfo *pMember, // Info for the source interface func + mdTypeDef *ptd, // The output typedef. + BOOL fInheritsIEnum) +{ + HRESULT hr = S_OK; // A result. + BSTR bstrSrcItfName = NULL; // The name of the source interface. + CQuickArray<WCHAR> qbEventHandlerName; // The name of the event handler. + BSTR szOldName = NULL; // The old value m_tdTypeDef. + mdTypeDef tdOldTypeDef = NULL; // The old value m_szName. + CQuickArray<BYTE> qbSig; // The signature. + ULONG cb; // Size of an element. + ULONG cbTotal = 0; // Total size of signature. + mdMethodDef mdFunc; // The defined function. + mdTypeRef trMulticastDelegate; // The type ref for System.MulticastDelegate. + mdToken tkAttr; // Custom attribute type. + + // Store the old values of the ITypeInfo name and of the current type def. + szOldName = m_szName; + tdOldTypeDef = m_tdTypeDef; + m_szName = NULL; + + // Retrieve the full name of the source interface. + IfFailGo(GetManagedNameForTypeInfo(pITI, m_wzNamespace, NULL, &bstrSrcItfName)); + + // Generate a unique name for the event handler which will be of the form: + // <SrcItfName>_<MethodName>_EventHandler<PotentialSuffix> + IfFailGo(qbEventHandlerName.ReSizeNoThrow(wcslen(bstrSrcItfName) + wcslen(pMember->m_pName) + EVENT_HANDLER_SUFFIX_LENGTH + 6)); + StringCchPrintf(qbEventHandlerName.Ptr(), qbEventHandlerName.Size(), W("%s_%s%s"), bstrSrcItfName, pMember->m_pName, EVENT_HANDLER_SUFFIX); + IfFailGo(GenerateUniqueTypeName(qbEventHandlerName)); + + // Set the information on the current type. + IfNullGo(m_szName = SysAllocString(qbEventHandlerName.Ptr())); + + // Retrieve the parent type ref. + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_MULTICASTDEL, &trMulticastDelegate)); + + // Create the typedef for the event interface. + IfFailGo(m_pEmit->DefineTypeDef(m_szName, tdPublic | tdSealed, trMulticastDelegate, NULL, &m_tdTypeDef)); + + // Hide the interface from Object Browsers (EventHandler) + _SetHiddenCA(m_tdTypeDef); + + // Make the interface ComVisible(false). + { + DECLARE_CUSTOM_ATTRIBUTE(sizeof(BYTE)); + BUILD_CUSTOM_ATTRIBUTE(BYTE, FALSE); + IfFailGo(GetAttrType(ATTR_COMVISIBLE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + + // Generate the sig for the constructor. + IfFailGo(qbSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 2 + 1)); + cbTotal = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, qbSig.Ptr()); + cb = CorSigCompressData(2, &(qbSig[cbTotal])); + cbTotal += cb; + cb = CorSigCompressData(ELEMENT_TYPE_VOID, &qbSig[cbTotal]); + cbTotal += cb; + cb = CorSigCompressData(ELEMENT_TYPE_OBJECT, &qbSig[cbTotal]); + cbTotal += cb; + cb = CorSigCompressData(ELEMENT_TYPE_U, &qbSig[cbTotal]); + cbTotal += cb; + + // Generate the constructor. + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, OBJECT_INITIALIZER_NAME, OBJECT_INITIALIZER_FLAGS, + qbSig.Ptr(), cbTotal, 0 /* rva*/, miRuntime, &mdFunc)); + + // Generate the invoke method. + BOOL bAllowIEnum = !fInheritsIEnum; + IfFailGo(_ConvFunction(pITI, pMember, FALSE, FALSE, TRUE, &bAllowIEnum)); + + // Set the output typedef. + *ptd = m_tdTypeDef; + +ErrExit: + if (m_szName) + ::SysFreeString(m_szName); + if (m_szMember) + ::SysFreeString(m_szMember); m_szMember=0; + if (bstrSrcItfName) + ::SysFreeString(bstrSrcItfName); + + // Restore the initial values for the ITypeInfo name and the type def. + m_szName = szOldName; + m_tdTypeDef = tdOldTypeDef; + + return (hr); +} // HRESULT CImportTlb::_GenerateEventDelegate() + +//***************************************************************************** +//***************************************************************************** +struct MDTOKENHASH : HASHLINK +{ + mdToken tkKey; + mdToken tkData; +}; // struct MDTOKENHASH : HASHLINK + +class CTokenHash : public CChainedHash<MDTOKENHASH> +{ +public: + virtual bool InUse(MDTOKENHASH *pItem) + { return (pItem->tkKey != NULL); } + + virtual void SetFree(MDTOKENHASH *pItem) + { + pItem->tkKey = NULL; + pItem->tkKey = NULL; + } + + virtual ULONG Hash(const void *pData) + { + // Do case-insensitive hash + return (ULONG)(ULONG_PTR)pData; + } + + virtual int Cmp(const void *pData, void *pItem){ + return (mdToken)(ULONG_PTR)pData != reinterpret_cast<MDTOKENHASH*>(pItem)->tkKey; + } +}; // CTokenHash : public CChainedHash<MDTOKENHASH> + +//***************************************************************************** +// Copy methods and events from a source interface to a class that sources the +// given interface. +//***************************************************************************** +HRESULT CImportTlb::_AddSrcItfMembersToClass( // S_OK or error. + mdTypeRef trSrcItf) // Typeref of the source interface. +{ + HRESULT hr=S_OK; // A result. + ULONG i; // Generic counter. + HCORENUM MemberEnum = NULL; // The enum of members. + ULONG cMembers = 0; // Temp count of members. + mdTypeDef tdSrcItf; // A type def to the interface. + mdEvent tkItfEvent; // The token of the interface event. + mdEvent tkClassEvent; // The token of the class event. + mdToken tkEventType; // The event type. + mdMethodDef mdItfMethod; // The method def of the interface method. + mdMethodDef mdAddMethod; // The add method. + mdMethodDef mdRemoveMethod; // The remove method. + mdMethodDef mdFireMethod; // The fire method. + mdMethodDef mdClassMethod; // The method def of the class method. + CQuickArray<mdMethodDef> qbOtherMethods; // The other methods for the property. + ULONG cchOtherMethods; // The cound of other methods. + CQuickArray<WCHAR> qbMemberName; // Name of the member. + CQuickArray<WCHAR> qbEventItfFullName; // Full name of the event interface. + CQuickArray<WCHAR> qbEventItfName; // Name of the event interface. + ULONG cchName; // Length of a name, in wide chars. + ULONG ItfMemberAttr; // The attributes of the interface member. + ULONG ItfMemberImplFlags; // The impl flags of the interface member. + PCCOR_SIGNATURE ItfMemberSig; // The signature of the interface member. + ULONG ItfMemberSigSize; // The size of the member signature. + mdMemberRef mrItfMember; // A member ref to the interface member def. + BSTR bstrSrcItfName = NULL; // The name of the CoClass. + mdAssemblyRef ar; // The assembly ref. + CTokenHash ItfMDToClassMDMap; // The interface MD to class MD map. + MDTOKENHASH * pItem; // An item in the token hashtable. + + // Retrieve the name of the event interface. + do { + IfFailGo(m_pImport->GetTypeRefProps( + trSrcItf, + &ar, + qbEventItfFullName.Ptr(), + (ULONG)qbEventItfFullName.MaxSize(), + &cchName)); + if (hr == CLDB_S_TRUNCATION) + { + IfFailGo(qbEventItfFullName.ReSizeNoThrow(cchName)); + continue; + } + break; + } while (1); + IfFailGo(qbEventItfName.ReSizeNoThrow(cchName)); + ns::SplitPath(qbEventItfFullName.Ptr(), NULL, 0, qbEventItfName.Ptr(), (int)qbEventItfName.Size()); + + // Resolve the typeref to a typedef. + IfFailGo(m_pImport->FindTypeDefByName(qbEventItfFullName.Ptr(), mdTokenNil, &tdSrcItf)); + + // Define methods and method impl's for all the methods in the interface. + while ((hr = m_pImport->EnumMethods(&MemberEnum, tdSrcItf, &mdItfMethod, 1, &cMembers)) == S_OK) + { + // Retrieve the method properties. + do { + IfFailGo(m_pImport->GetMethodProps( + mdItfMethod, + NULL, + qbMemberName.Ptr(), + (ULONG)qbMemberName.MaxSize(), + &cchName, + &ItfMemberAttr, + &ItfMemberSig, + &ItfMemberSigSize, + NULL, + &ItfMemberImplFlags)); + if (hr == CLDB_S_TRUNCATION) + { + IfFailGo(qbMemberName.ReSizeNoThrow(cchName)); + continue; + } + break; + } while (1); + + // Define a member ref on the class to the interface member def. + IfFailGo(m_pEmit->DefineMemberRef(trSrcItf, qbMemberName.Ptr(), ItfMemberSig, ItfMemberSigSize, &mrItfMember)); + + // Generate a unique name for the class member. + IfFailGo(GenerateUniqueMemberName(qbMemberName, NULL, 0, qbEventItfName.Ptr(), mdtMethodDef)); + + // Define a member on the class. + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, qbMemberName.Ptr(), ItfMemberAttr & ~mdAbstract, + ItfMemberSig, ItfMemberSigSize, 0/*rva*/, ItfMemberImplFlags, &mdClassMethod)); + + // Define a method impl. + IfFailGo(m_pEmit->DefineMethodImpl(m_tdTypeDef, mdClassMethod, mrItfMember)); + + // Add the interface member to the map. + if ((pItem = ItfMDToClassMDMap.Add((const void *)(ULONG_PTR)mdItfMethod)) == NULL) + IfFailGo(E_FAIL); + PREFIX_ASSUME(pItem != NULL); + pItem->tkKey = mdItfMethod; + pItem->tkData = mdClassMethod; + } + IfFailGo(hr); + + m_pImport->CloseEnum(MemberEnum); + MemberEnum = NULL; + + // Define all the events in the interface on the class. + while ((hr = m_pImport->EnumEvents(&MemberEnum, tdSrcItf, &tkItfEvent, 1, &cMembers)) == S_OK) + { + // Retrieve the properties of the property. + do { + IfFailGo(m_pImport->GetEventProps( + tkItfEvent, + NULL, + qbMemberName.Ptr(), + (ULONG)qbMemberName.MaxSize(), + &cchName, + &ItfMemberAttr, + &tkEventType, + &mdAddMethod, + &mdRemoveMethod, + &mdFireMethod, + qbOtherMethods.Ptr(), + (ULONG)qbOtherMethods.MaxSize(), + &cchOtherMethods)); + if (hr == CLDB_S_TRUNCATION) + { + IfFailGo(qbMemberName.ReSizeNoThrow(cchName)); + IfFailGo(qbOtherMethods.ReSizeNoThrow(cchOtherMethods)); + continue; + } + break; + } while (1); + + // NULL terminate the array of other methods. + IfFailGo(qbOtherMethods.ReSizeNoThrow(cchOtherMethods + 1)); + qbOtherMethods[cchOtherMethods] = NULL; + + // Replace all the interface method def's with the equivalent class method def's. + if (!IsNilToken(mdAddMethod)) + { + pItem = ItfMDToClassMDMap.Find((const void *)(ULONG_PTR)mdAddMethod); + _ASSERTE(pItem); + mdAddMethod = pItem->tkData; + } + if (!IsNilToken(mdRemoveMethod)) + { + pItem = ItfMDToClassMDMap.Find((const void *)(ULONG_PTR)mdRemoveMethod); + _ASSERTE(pItem); + mdRemoveMethod = pItem->tkData; + } + _ASSERTE(IsNilToken(mdFireMethod)); + _ASSERTE(cchOtherMethods == 0); + + // Generate a unique name for the event. + IfFailGo(GenerateUniqueMemberName(qbMemberName, NULL, 0, qbEventItfName.Ptr(), mdtEvent)); + + // Define property on the class. + IfFailGo(m_pEmit->DefineEvent(m_tdTypeDef, qbMemberName.Ptr(), ItfMemberAttr, + tkEventType, mdAddMethod, mdRemoveMethod, mdFireMethod, qbOtherMethods.Ptr(), &tkClassEvent)); + } + IfFailGo(hr); + + m_pImport->CloseEnum(MemberEnum); + MemberEnum = NULL; + +ErrExit: + if (MemberEnum) + m_pImport->CloseEnum(MemberEnum); + if (bstrSrcItfName) + ::SysFreeString(bstrSrcItfName); + + return hr; + +#undef ITF_MEMBER_SIG +#undef ITF_MEMBER_SIG_SIZE +} // HRESULT CImportTlb::_AddSrcItfMembersToClass() + +//***************************************************************************** +// Compare the two signatures ignoring the return type. If the signatures +// match then TRUE will be returned, FALSE will be returned otherwise. +// This method assumes the two signatures are in the same scope. +//***************************************************************************** +HRESULT CImportTlb::CompareSigsIgnoringRetType( + PCCOR_SIGNATURE pbSig1, // The 1st method signature. + ULONG cbSig1, // Size of the 1st method signature. + PCCOR_SIGNATURE pbSig2, // The 2nd method signature. + ULONG cbSig2) // Size of the 2nd method signature. +{ + HRESULT hr = S_OK; + PCCOR_SIGNATURE pbSig1Start; + PCCOR_SIGNATURE pbSig2Start; + ULONG Sig1ParamCount; + ULONG Sig2ParamCount; + ULONG cbSig1RetType; + ULONG cbSig2RetType; + + // Save the start of the signatures. + pbSig1Start = pbSig1; + pbSig2Start = pbSig2; + + // Skip the calling conventions. + CorSigUncompressData(pbSig1); + CorSigUncompressData(pbSig2); + + // Compare the param count. + Sig1ParamCount = CorSigUncompressData(pbSig1); + Sig2ParamCount = CorSigUncompressData(pbSig2); + if (Sig1ParamCount != Sig2ParamCount) + return S_FALSE; + + // Skip the return type. + cbSig1RetType = cbSig1 - (ULONG)(pbSig1 - pbSig1Start); + IfFailGo(_CountBytesOfOneArg(pbSig1, &cbSig1RetType)); + pbSig1 += cbSig1RetType; + cbSig2RetType = cbSig2 - (ULONG)(pbSig2 - pbSig2Start); + IfFailGo(_CountBytesOfOneArg(pbSig2, &cbSig2RetType)); + pbSig2 += cbSig2RetType; + + // Update the remaining sig sizes. + cbSig1 -= (ULONG) (pbSig1 - pbSig1Start); + cbSig2 -= (ULONG) (pbSig2 - pbSig2Start); + + // If the remaining sig sizes are different then the sigs don't match. + if (cbSig1 != cbSig2) + return S_FALSE; + + // Compare the rest of the sigs using memcmp. + if (memcmp(pbSig1, pbSig2, cbSig1) != 0) + return S_FALSE; + + // The parameters match. + return S_OK; + +ErrExit: + // An error occured. + return hr; +} // HRESULT CImportTlb::CompareSigsIgnoringRetType() + +//***************************************************************************** +// Look up a method in the emit scope. This lookup method does not take the +// return type into account when comparing using a sig. So 2 methods with +// the same name, same parameters and a different return type will be +// considered the same. +//***************************************************************************** +HRESULT CImportTlb::FindMethod( // S_OK or CLDB_E_RECORD_NOTFOUND, or error. + mdTypeDef td, // The method typedef. + LPCWSTR szName, // The method name. + PCCOR_SIGNATURE pbReqSig, // The method signature. + ULONG cbReqSig, // Size of the method signature. + mdMethodDef *pmb) // Put the method here. +{ + HRESULT hr = S_OK; // A result. + PCCOR_SIGNATURE pbFoundSig = NULL; + ULONG cbFoundSig = 0; + ULONG MethodAttr; + ULONG MethodImplFlags; + mdMethodDef md; + CQuickArray<WCHAR> qbMethodName; + HCORENUM MethodEnum = NULL; + ULONG cMethods = 0; + ULONG cchName; + BOOL bMethodFound = FALSE; + + // Go through all the methods on the class looking for one with the + // same name and same parameters. + while ((hr = m_pImport->EnumMethods(&MethodEnum, td, &md, 1, &cMethods)) == S_OK) + { + // Retrieve the method properties. + do { + IfFailGo(m_pImport->GetMethodProps( + md, + NULL, + qbMethodName.Ptr(), + (ULONG)qbMethodName.MaxSize(), + &cchName, + &MethodAttr, + &pbFoundSig, + &cbFoundSig, + NULL, + &MethodImplFlags)); + if (hr == CLDB_S_TRUNCATION) + { + IfFailGo(qbMethodName.ReSizeNoThrow(cchName)); + continue; + } + break; + } while (1); + + // Compare the name of the method. + if (wcscmp(szName, qbMethodName.Ptr()) != 0) + continue; + + // If the signature of the requested method is specified, then compare + // the signature against the signature of the found method ignoring + // the return type. + if (pbReqSig) + { + IfFailGo(hr = CompareSigsIgnoringRetType(pbReqSig, cbReqSig, pbFoundSig, cbFoundSig)); + if (hr == S_FALSE) + continue; + } + + // We have found the member. + bMethodFound = TRUE; + break; + } + IfFailGo(hr); + +ErrExit: + if (MethodEnum) + m_pImport->CloseEnum(MethodEnum); + + return bMethodFound ? S_OK : CLDB_E_RECORD_NOTFOUND; +} + +//***************************************************************************** +// Look up a property in the emit scope. +//***************************************************************************** +HRESULT CImportTlb::FindProperty( // S_OK or CLDB_E_RECORD_NOTFOUND, or error. + mdTypeDef td, // The property typedef. + LPCWSTR szName, // The property name. + PCCOR_SIGNATURE pbSig, // The property signature. + ULONG cbSig, // Size of the property signature. + mdProperty *ppr) // Put the property here. +{ + HRESULT hr; // A result. + RegMeta *pRegMeta = (RegMeta*)(m_pEmit); + LPUTF8 szNameAnsi; + + if (szName == NULL) + { + return CLDB_E_RECORD_NOTFOUND; + } + UTF8STR(szName, szNameAnsi); + + hr = ImportHelper::FindProperty( + &(pRegMeta->GetMiniStgdb()->m_MiniMd), + m_tdTypeDef, + szNameAnsi, + pbSig, + cbSig, + ppr); + return hr; +} // HRESULT CImportTlb::FindProperty() + +//***************************************************************************** +// Look up a event in the emit scope. +//***************************************************************************** +HRESULT CImportTlb::FindEvent( // S_OK or CLDB_E_RECORD_NOTFOUND, or error. + mdTypeDef td, // The event typedef. + LPCWSTR szName, // The event name. + mdEvent *pev) // Put the event here. +{ + HRESULT hr; // A result. + RegMeta *pRegMeta = (RegMeta*)(m_pEmit); + LPUTF8 szNameAnsi; + + if (szName == NULL) + { + return CLDB_E_RECORD_NOTFOUND; + } + UTF8STR(szName, szNameAnsi); + + hr = ImportHelper::FindEvent( + &(pRegMeta->GetMiniStgdb()->m_MiniMd), + m_tdTypeDef, + szNameAnsi, + pev); + return hr; +} // HRESULT CImportTlb::FindEvent() + +//***************************************************************************** +// Checks to see if the specified TYPEDESC is an alias. +//***************************************************************************** +HRESULT CImportTlb::_IsAlias( + ITypeInfo *pITI, // The ITypeInfo containing the TYPEDESC. + TYPEDESC *pTypeDesc) // The token of the param, field, etc. +{ + HRESULT hr = S_FALSE; // A result. + ITypeInfo *pTypeITI=0; // The ITypeInfo of the type. + ITypeLib *pTypeTLB=0; // The TLB that contains the type. + TYPEATTR *psTypeAttr=0; // TYPEATTR of the type. + + // Drill down to the actual type that is pointed to. + while (pTypeDesc->vt == VT_PTR) + pTypeDesc = pTypeDesc->lptdesc; + + // If the parameter is an alias then we need to add a custom attribute to the + // parameter that describes the alias. + if (pTypeDesc->vt == VT_USERDEFINED) + { + IfFailGo(pITI->GetRefTypeInfo(pTypeDesc->hreftype, &pTypeITI)); + IfFailGo(pTypeITI->GetTypeAttr(&psTypeAttr)); + if (psTypeAttr->typekind == TKIND_ALIAS) + { + hr = S_OK; + } + } + +ErrExit: + if (psTypeAttr) + pTypeITI->ReleaseTypeAttr(psTypeAttr); + if (pTypeITI) + pTypeITI->Release(); + return hr; +} // HRESULT CImportTlb::_IsAlias() + +//***************************************************************************** +// Add alias information if the TYPEDESC represents an alias. +//***************************************************************************** +HRESULT CImportTlb::_HandleAliasInfo( + ITypeInfo *pITI, // The ITypeInfo containing the TYPEDESC. + TYPEDESC *pTypeDesc, // The TYPEDESC. + mdToken tk) // The token of the param, field, etc. +{ + HRESULT hr = S_OK; // A result. + ITypeInfo *pTypeITI=0; // The ITypeInfo of the type. + ITypeLib *pTypeTLB=0; // The TLB that contains the type. + TYPEATTR *psTypeAttr=0; // TYPEATTR of the type. + BSTR bstrAliasTypeName=0; // The name of the alias type. + BSTR bstrAliasTypeLibName=0; // The name of the typelib that contains the alias type. + + // Drill down to the actual type that is pointed to. + while (pTypeDesc->vt == VT_PTR) + pTypeDesc = pTypeDesc->lptdesc; + + // If the parameter is an alias then we need to add a custom attribute to the + // parameter that describes the alias. + if (pTypeDesc->vt == VT_USERDEFINED) + { + IfFailGo(pITI->GetRefTypeInfo(pTypeDesc->hreftype, &pTypeITI)); + IfFailGo(pTypeITI->GetTypeAttr(&psTypeAttr)); + if (psTypeAttr->typekind == TKIND_ALIAS) + { + // Retrieve the name of the alias type. + IfFailGo(pTypeITI->GetContainingTypeLib(&pTypeTLB, NULL)); + IfFailGo(GetNamespaceOfRefTlb(pTypeTLB, &bstrAliasTypeLibName, NULL)); + IfFailGo(GetManagedNameForTypeInfo(pTypeITI, bstrAliasTypeLibName, NULL, &bstrAliasTypeName)); + + // Add the ComAliasName CA to the parameter. + _AddStringCa(ATTR_COMALIASNAME, tk, bstrAliasTypeName); + } + } + +ErrExit: + if (psTypeAttr) + pTypeITI->ReleaseTypeAttr(psTypeAttr); + if (pTypeITI) + pTypeITI->Release(); + if (pTypeTLB) + pTypeTLB->Release(); + if (bstrAliasTypeLibName) + ::SysFreeString(bstrAliasTypeLibName); + if (bstrAliasTypeName) + ::SysFreeString(bstrAliasTypeName); + return hr; +} // HRESULT CImportTlb::_HandleAliasInfo() + +//***************************************************************************** +// Convert one of a function's parameters. +//***************************************************************************** +HRESULT CImportTlb::_ConvParam( + ITypeInfo *pITI, // Containing TypeInfo. + mdMethodDef mdFunc, // Owning member. + int iSequence, // Parameter sequence. + const ELEMDESC *pdesc, // Param flags, default value. + ParamOpts paramOpts, // Is param normal, optional, or vararg? + LPCWSTR szName, // Name of the parameter. + BYTE *pbNative, // Native type info, if any. + ULONG cbNative) // Size of native type info. +{ + HRESULT hr; // A result. + mdParamDef pdParam; // Token of the parameter. + DWORD dwFlags; // Param flags. + USHORT Sequence = static_cast<USHORT>(iSequence); + BYTE cvType = ELEMENT_TYPE_VOID; // ELEMENT_TYPE_* flag for constant value + void *pcvValue=0; // constant value blob + __int64 d; // For cases where value is a date. + int bDecimal=0; // If true, constant is a decimal. + mdToken tkAttr; // For custom attribute token. + DECIMAL decVal; // Decimal constant value. + + // Compute the flags. Only make sense on non-return params. + dwFlags = 0; + if (iSequence > 0) + { + if (pdesc->paramdesc.wParamFlags & PARAMFLAG_FIN) + dwFlags |= pdIn; + if (pdesc->paramdesc.wParamFlags & PARAMFLAG_FOUT) + dwFlags |= pdOut; + if (pdesc->paramdesc.wParamFlags & PARAMFLAG_FOPT) + dwFlags |= pdOptional; + if (paramOpts == ParamOptional) + dwFlags |= pdOptional; + } + + // Get any default values. Return type, param with iSequence==0, has no default. + if (pdesc->paramdesc.wParamFlags & PARAMFLAG_FHASDEFAULT && iSequence != 0) + { + switch (pdesc->paramdesc.pparamdescex->varDefaultValue.vt) + { + case VT_CY: + case VT_DECIMAL: + case VT_DATE: + case VT_UNKNOWN: + case VT_DISPATCH: + break; + default: + // This workaround is because a typelib can store anything that can convert to VT_I4 with a value of 0 + // for the default value of an interface pointer. But, a VT_I2(0) confuses the consumers + // of the managed wrapper dll. So, if it is an interface on the unmanaged side, make + // the constant value an ET_CLASS. + if (cbNative > 0 && (*pbNative == NATIVE_TYPE_INTF || + *pbNative == NATIVE_TYPE_IUNKNOWN || + *pbNative == NATIVE_TYPE_IDISPATCH)) + { + cvType = ELEMENT_TYPE_CLASS; + pcvValue = 0; + } + else + IfFailGo( _UnpackVariantToConstantBlob(&pdesc->paramdesc.pparamdescex->varDefaultValue, &cvType, &pcvValue, &d) ); + } + } + + // Create the param definition. + IfFailGo(m_pEmit->DefineParam(mdFunc, iSequence, szName, dwFlags, cvType, pcvValue, -1, &pdParam)); + + // Add the native type if it there is any. + if (cbNative > 0) + IfFailGo(m_pEmit->SetFieldMarshal(pdParam, (PCCOR_SIGNATURE) pbNative, cbNative)); + + if (pdesc->paramdesc.wParamFlags & PARAMFLAG_FHASDEFAULT && iSequence != 0) + { + switch (pdesc->paramdesc.pparamdescex->varDefaultValue.vt) + { + case VT_CY: + IfFailGo(VarDecFromCy(pdesc->paramdesc.pparamdescex->varDefaultValue.cyVal, &decVal)); + IfFailGo(DecimalCanonicalize(&decVal)); + goto StoreDecimal; + case VT_DECIMAL: + // If there is a decimal constant value, set it as a custom attribute. + { + decVal = pdesc->paramdesc.pparamdescex->varDefaultValue.decVal; + StoreDecimal: + DECLARE_CUSTOM_ATTRIBUTE(sizeof(BYTE)+sizeof(BYTE)+sizeof(UINT)+sizeof(UINT)+sizeof(UINT)); + BUILD_CUSTOM_ATTRIBUTE(BYTE, decVal.scale); + BUILD_CUSTOM_ATTRIBUTE(BYTE, decVal.sign); + BUILD_CUSTOM_ATTRIBUTE(UINT, decVal.Hi32); + BUILD_CUSTOM_ATTRIBUTE(UINT, decVal.Mid32); + BUILD_CUSTOM_ATTRIBUTE(UINT, decVal.Lo32); + IfFailGo(GetAttrType(ATTR_DECIMALVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(pdParam, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + case VT_DATE: + { + DECLARE_CUSTOM_ATTRIBUTE(sizeof(__int64)); + __int64 date = _DoubleDateToTicks(pdesc->paramdesc.pparamdescex->varDefaultValue.date); + BUILD_CUSTOM_ATTRIBUTE(__int64, date); + IfFailGo(GetAttrType(ATTR_DATETIMEVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(pdParam, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + case VT_UNKNOWN: + { + DECLARE_CUSTOM_ATTRIBUTE(0); + IfFailGo(GetAttrType(ATTR_IUNKNOWNVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(pdParam, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + case VT_DISPATCH: + { + DECLARE_CUSTOM_ATTRIBUTE(0); + IfFailGo(GetAttrType(ATTR_IDISPATCHVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(pdParam, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + default: + break; + } + } + + // Add the alias information if the param is an alias. + IfFailGo(_HandleAliasInfo(pITI, (TYPEDESC*)&pdesc->tdesc, pdParam)); + + // If a vararg param, set the custom attribute. + if (paramOpts == ParamVarArg) + { + mdToken tkAttribute; + DECLARE_CUSTOM_ATTRIBUTE(0); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(GetAttrType(ATTR_PARAMARRAY, &tkAttribute)); + IfFailGo(m_pEmit->DefineCustomAttribute(pdParam, tkAttribute, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + +ErrExit: + return hr; +} // HRESULT CImportTlb::_ConvParam() + +//***************************************************************************** +// Convert a constant into a field with a default value. +//***************************************************************************** +HRESULT CImportTlb::_ConvConstant( + ITypeInfo *pITI, // Containing TypeInfo. + VARDESC *psVar, // VARDESC for the property. + BOOL bEnumMember) // If true, type is containing class. +{ + HRESULT hr; // A result. + mdFieldDef mdField; // Token of the new field. + DWORD dwFlags; // Member flags. + CQuickBytes qbComSig; // The COM+ Signature of the field. + ULONG cb, cbTotal; + BYTE cvType = ELEMENT_TYPE_VOID; // E_T_Type for constant value + void *pcvValue; // Pointer to constant value data. + mdToken tkAttr; // Type for custom attribute. + __int64 d; // For cases where value is a date. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + BYTE *pbSig; // Pointer to signature bytes. + CQuickArray<BYTE> qbNativeBuf; // Native type buffer. + ULONG cbNative = 0; // Size of native type. + int bDecimal = 0; // If the value is a decimal. + DECIMAL decVal; // Decimal constant value. + + // Information about the member. + IfFailGo(pITI->GetDocumentation(psVar->memid, &m_szMember, 0,0,0)); + + // resize to make room for calling convention and count of argument + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 4)); + pbSig = (BYTE *)qbComSig.Ptr(); + + // Compute properties. + dwFlags = DEFAULT_CONST_FIELD_FLAGS; + + // Build the signature. + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_FIELD, pbSig); + if (bEnumMember) + { + cb = CorSigCompressData(ELEMENT_TYPE_VALUETYPE, &pbSig[cbTotal]); + cbTotal += cb; + cb = CorSigCompressToken(m_tdTypeDef, reinterpret_cast<ULONG*>(&pbSig[cbTotal])); + cbTotal += cb; + } + else + { + // Use the conversion function to get the signature. + ULONG cbSave = cbTotal; + IfFailGo(_ConvSignature(pITI, &psVar->elemdescVar.tdesc, SIG_FLAGS_NONE, qbComSig, cbTotal, &cbTotal, qbNativeBuf, 0, &cbNative, FALSE)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + if (psVar->elemdescVar.tdesc.vt == VT_DATE) + { + // But for dates, convert it as float -- DateTime is reported as R4 in a typelib! + cbTotal = cbSave; + cb = CorSigCompressData(ELEMENT_TYPE_R4, &pbSig[cbTotal]); + cbTotal += cb; + } + } + + // Get the default value. + switch (psVar->lpvarValue->vt) + { + case VT_CY: + case VT_DECIMAL: + case VT_DATE: + case VT_UNKNOWN: + case VT_DISPATCH: + break; + default: + // This workaround is because a typelib can store anything that can convert to VT_I4 with a value of 0 + // for the default value of an interface pointer. But, a VT_I2(0) confuses the consumers + // of the managed wrapper dll. So, if it is an interface on the unmanaged side, make + // the constant value an ET_CLASS. + BYTE *pbNative = NULL; + pbNative = qbNativeBuf.Ptr(); + if (cbNative > 0 && (*pbNative == NATIVE_TYPE_INTF || + *pbNative == NATIVE_TYPE_IUNKNOWN || + *pbNative == NATIVE_TYPE_IDISPATCH)) + { + cvType = ELEMENT_TYPE_CLASS; + pcvValue = 0; + } + else + IfFailGo( _UnpackVariantToConstantBlob(psVar->lpvarValue, &cvType, &pcvValue, &d) ); + } + + // Create the field definition. + IfFailGo(m_pEmit->DefineField(m_tdTypeDef, m_szMember, dwFlags, (PCCOR_SIGNATURE)pbSig, cbTotal, + cvType, pcvValue, -1, &mdField)); + + switch (psVar->lpvarValue->vt) + { + case VT_CY: + IfFailGo(VarDecFromCy(psVar->lpvarValue->cyVal, &decVal)); + IfFailGo(DecimalCanonicalize(&decVal)); + goto StoreDecimal; + case VT_DECIMAL: + // If there is a decimal constant value, set it as a custom attribute. + { + decVal = psVar->lpvarValue->decVal; + StoreDecimal: + DECLARE_CUSTOM_ATTRIBUTE(sizeof(BYTE)+sizeof(BYTE)+sizeof(UINT)+sizeof(UINT)+sizeof(UINT)); + BUILD_CUSTOM_ATTRIBUTE(BYTE, decVal.scale); + BUILD_CUSTOM_ATTRIBUTE(BYTE, decVal.sign); + BUILD_CUSTOM_ATTRIBUTE(UINT, decVal.Hi32); + BUILD_CUSTOM_ATTRIBUTE(UINT, decVal.Mid32); + BUILD_CUSTOM_ATTRIBUTE(UINT, decVal.Lo32); + IfFailGo(GetAttrType(ATTR_DECIMALVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + case VT_DATE: + { + DECLARE_CUSTOM_ATTRIBUTE(sizeof(__int64)); + __int64 date = _DoubleDateToTicks(psVar->lpvarValue->date); + BUILD_CUSTOM_ATTRIBUTE(__int64, date); + IfFailGo(GetAttrType(ATTR_DATETIMEVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + case VT_UNKNOWN: + { + DECLARE_CUSTOM_ATTRIBUTE(0); + IfFailGo(GetAttrType(ATTR_IUNKNOWNVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + case VT_DISPATCH: + { + DECLARE_CUSTOM_ATTRIBUTE(0); + IfFailGo(GetAttrType(ATTR_IDISPATCHVALUE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + break; + default: + break; + } + + // Save the field flags. + if (psVar->wVarFlags) + { + IfFailGo(GetAttrType(ATTR_TYPELIBVAR, &tkAttr)); + DECLARE_CUSTOM_ATTRIBUTE(sizeof(WORD)); + BUILD_CUSTOM_ATTRIBUTE(WORD, psVar->wVarFlags); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(mdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + // Set up the native description, if any. + if (cbNative > 0) + IfFailGo(m_pEmit->SetFieldMarshal(mdField, (PCCOR_SIGNATURE) qbNativeBuf.Ptr(), cbNative)); + + // Add the alias information if the type is an alias. + IfFailGo(_HandleAliasInfo(pITI, &psVar->elemdescVar.tdesc, mdField)); + + if (bConversionLoss) + { + hr = S_CONVERSION_LOSS; + ReportEvent(NOTIF_CONVERTWARNING, TLBX_I_UNCONVERTABLE_FIELD, m_szName, m_szMember); + } + +ErrExit: + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember=0; + return (hr); +} // HRESULT CImportTlb::_ConvConstant() + +//***************************************************************************** +// Convert a (record) field into a member. +//***************************************************************************** +HRESULT CImportTlb::_ConvField( + ITypeInfo *pITI, // Containing TypeInfo. + VARDESC *psVar, // VARDESC for the property. + mdFieldDef *pmdField, // Put field token here. + BOOL bUnion) // Convert as a union? +{ + HRESULT hr; // A result. + DWORD dwFlags; // Member flags. + CQuickBytes qbComSig; // The COM+ Signature of the field. + ULONG cb, cbTotal; // Size of a sig element, signature. + BYTE *pbSig; // Pointer to signature bytes. + CQuickArray<BYTE> qbNativeBuf; // Native type buffer. + ULONG cbNative; // Size of native type. + mdToken tkAttr; // CustomAttribute type. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + + // Information about the member. + IfFailGo(pITI->GetDocumentation(psVar->memid, &m_szMember, 0,0,0)); + + // Compute properties. + dwFlags = DEFAULT_RECORD_FIELD_FLAGS; + + // resize to make room for calling convention and count of argument + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 2)); + pbSig = (BYTE *)qbComSig.Ptr(); + + // Build the signature. + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_FIELD, pbSig); + IfFailGo(_ConvSignature(pITI, &psVar->elemdescVar.tdesc, SIG_FIELD, qbComSig, cbTotal, &cbTotal, qbNativeBuf, 0, &cbNative, FALSE)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + // Create the field definition. + IfFailGo(m_pEmit->DefineField(m_tdTypeDef, m_szMember, dwFlags, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, + ELEMENT_TYPE_VOID, NULL, -1, pmdField)); + + // Save the field flags. + if (psVar->wVarFlags) + { + IfFailGo(GetAttrType(ATTR_TYPELIBVAR, &tkAttr)); + DECLARE_CUSTOM_ATTRIBUTE(sizeof(WORD)); + BUILD_CUSTOM_ATTRIBUTE(WORD, psVar->wVarFlags); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(*pmdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + if (bConversionLoss) + { + IfFailGo(GetAttrType(ATTR_COMCONVERSIONLOSS, &tkAttr)); + DECLARE_CUSTOM_ATTRIBUTE(0); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(*pmdField, tkAttr, PTROF_CUSTOM_ATTRIBUTE(),SIZEOF_CUSTOM_ATTRIBUTE(),0)); + } + + // Set up the native description, if any. + if (cbNative > 0) + IfFailGo(m_pEmit->SetFieldMarshal(*pmdField, (PCCOR_SIGNATURE) qbNativeBuf.Ptr(), cbNative)); + + // Add the alias information if the type is an alias. + IfFailGo(_HandleAliasInfo(pITI, &psVar->elemdescVar.tdesc, *pmdField)); + + if (bConversionLoss) + { + hr = S_CONVERSION_LOSS; + ReportEvent(NOTIF_CONVERTWARNING, TLBX_I_UNCONVERTABLE_FIELD, m_szName, m_szMember); + } + +ErrExit: + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember=0; + return (hr); +} // HRESULT CImportTlb::_ConvField() + +//***************************************************************************** +// Convert a dispatch property into a pair of get/set functions. +//***************************************************************************** +HRESULT CImportTlb::_ConvProperty( + ITypeInfo *pITI, // Containing TypeInfo. + MemberInfo *pMember) // VARDESC for the property. +{ + HRESULT hr; // A result. + mdMethodDef mdFuncGet; // A get function. + mdMethodDef mdFuncSet; // A set function. + mdProperty pdProperty; // Property on the two functions. + DWORD dwFlags; // Function flags. + WCHAR *pszName=0; // Decorated name of member. + CQuickArray<WCHAR> qbName; // Buffer for decorated name. + CQuickBytes qbComSig; // com signature buffer + ULONG cb; // Size of an element. + ULONG cbTotal = 0; // Total size of signature. + BYTE *pbSig; // Pointer to signature buffer. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + CQuickArray<BYTE> qbNativeBuf; // Native type buffer. + ULONG iNativeOfs=0; // Current offset in native type buffer. + VARDESC *psVar = pMember->m_psVar; + + // Check to see if the property is the NewEnum member. + if (PropertyIsNewEnum(pITI, psVar, pMember->m_iMember) == S_OK) + return _ConvNewEnumProperty(pITI, psVar, pMember); + + // Get the name. + IfFailGo(pITI->GetDocumentation(psVar->memid, &m_szMember, 0,0,0)); + + // Create the get signature. + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 2)); + pbSig = reinterpret_cast<BYTE*>(qbComSig.Ptr()); + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, pbSig); + // Getter takes zero parameters. + cb = CorSigCompressData(0, &(pbSig[cb])); + cbTotal += cb; + // Getter returns the property type. + IfFailGo(_ConvSignature(pITI, &psVar->elemdescVar.tdesc, SIG_ELEM, qbComSig, cbTotal, &cbTotal, qbNativeBuf, 0, &iNativeOfs, FALSE)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + // Getter properties. + dwFlags = DEFAULT_PROPERTY_FUNC_FLAGS; + // If processing an implemented interface, remove the abstract bit. Methods on classes are not abstract. + if (m_ImplIface != eImplIfaceNone) + dwFlags &= ~mdAbstract; + + // Get the previously decorated name. Add interface name and make unique. + // m_szInterface should be non-null if processing an implemented interface; should be null otherwise. + _ASSERTE(m_ImplIface == eImplIfaceNone || m_szInterface != 0); + IfFailGo(qbName.ReSizeNoThrow(wcslen(pMember->m_pName)+2)); + wcscpy_s(qbName.Ptr(), wcslen(pMember->m_pName)+2, pMember->m_pName); + IfFailGo(GenerateUniqueMemberName(qbName, (PCCOR_SIGNATURE)qbComSig.Ptr(), cbTotal, m_szInterface, mdtMethodDef)); + pszName = qbName.Ptr(); + + // Create the get Accessor. + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, pszName, dwFlags, (PCCOR_SIGNATURE) qbComSig.Ptr(), cbTotal, + 0/*RVA*/, DEFAULT_ITF_FUNC_IMPL_FLAGS, &mdFuncGet)); + + // Handle dispids for non-implemented interfaces, and for default interface + if (m_ImplIface != eImplIface) + { + // Set the Dispid CA. + _SetDispIDCA(pITI, pMember->m_iMember, psVar->memid, mdFuncGet, TRUE, NULL, FALSE); + } + + // If processing an implemented interface, set up MethodImpls. + if (m_ImplIface != eImplIfaceNone) + { + // Define a memberref on the implemented interface. + mdToken mrItfMember; + IfFailGo(m_pEmit->DefineMemberRef(m_tkInterface, pMember->m_pName, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, &mrItfMember)); + + // Define a method impl. + IfFailGo(m_pEmit->DefineMethodImpl(m_tdTypeDef, mdFuncGet, mrItfMember)); + } + + // If not a read-only var, create the setter. + if ((psVar->wVarFlags & VARFLAG_FREADONLY) == 0) + { + // Create the setter signature. + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 3)); + pbSig = reinterpret_cast<BYTE*>(qbComSig.Ptr()); + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, pbSig); + // Setter takes one parameter. + cb = CorSigCompressData(1, &(pbSig[cb])); + cbTotal += cb; + // Setter returns nothing. + cb = CorSigCompressData(ELEMENT_TYPE_VOID, &pbSig[cbTotal]); + cbTotal += cb; + // Setter takes the property type. + IfFailGo(_ConvSignature(pITI, &psVar->elemdescVar.tdesc, SIG_ELEM, qbComSig, cbTotal, &cbTotal, qbNativeBuf, 0, &iNativeOfs, FALSE)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + // Setter properties. + dwFlags = DEFAULT_PROPERTY_FUNC_FLAGS; + // If processing an implemented interface, remove the abstract bit. Methods on classes are not abstract. + if (m_ImplIface != eImplIfaceNone) + dwFlags &= ~mdAbstract; + + // Get the previously decorated name. Add interface name and make unique. + // m_szInterface should be non-null if processing an implemented interface; should be null otherwise. + _ASSERTE(m_ImplIface == eImplIfaceNone || m_szInterface != 0); + IfFailGo(qbName.ReSizeNoThrow(wcslen(pMember->m_pName2)+2)); + wcscpy_s(qbName.Ptr(), wcslen(pMember->m_pName2)+2, pMember->m_pName2); + IfFailGo(GenerateUniqueMemberName(qbName, (PCCOR_SIGNATURE)qbComSig.Ptr(), cbTotal, m_szInterface, mdtMethodDef)); + pszName = qbName.Ptr(); + + // Create the setter Accessor. + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, pszName, dwFlags, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, + 0/*RVA*/, DEFAULT_ITF_FUNC_IMPL_FLAGS, &mdFuncSet)); + + // Handle dispids for non-implemented interfaces, and for default interface + if (m_ImplIface != eImplIface) + { + // Set the Dispid CA. + _SetDispIDCA(pITI, pMember->m_iMember, psVar->memid, mdFuncSet, TRUE, NULL, FALSE); + } + + // If processing an implemented interface, set up MethodImpls. + if (m_ImplIface != eImplIfaceNone) + { + // Define a memberref on the implemented interface. + mdToken mrItfMember; + IfFailGo(m_pEmit->DefineMemberRef(m_tkInterface, pMember->m_pName2, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, &mrItfMember)); + + // Define a method impl. + IfFailGo(m_pEmit->DefineMethodImpl(m_tdTypeDef, mdFuncSet, mrItfMember)); + } + } + else + { // read-only, setter method is nil. + mdFuncSet = mdMethodDefNil; + } + + // Create the property signature: 'type', or <fieldcallconv><type> + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_PROPERTY, pbSig); + cb = CorSigCompressData(0, &(pbSig[cb])); + cbTotal += cb; + // Property is just the property type. + IfFailGo(_ConvSignature(pITI, &psVar->elemdescVar.tdesc, SIG_ELEM, qbComSig, cbTotal, &cbTotal, qbNativeBuf, 0, &iNativeOfs, FALSE)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + // Get the property name. Add interface name and make unique, if needed. + // m_szInterface should be non-null if processing an implemented interface; should be null otherwise. + _ASSERTE(m_ImplIface == eImplIfaceNone || m_szInterface != 0); + IfFailGo(qbName.ReSizeNoThrow(wcslen(m_szMember)+2)); + wcscpy_s(qbName.Ptr(), wcslen(m_szMember)+2, m_szMember); + IfFailGo(GenerateUniqueMemberName(qbName, (PCCOR_SIGNATURE)qbComSig.Ptr(), cbTotal, m_szInterface, mdtProperty)); + pszName = qbName.Ptr(); + + // Set up the Property on the two methods. + IfFailGo(m_pEmit->DefineProperty(m_tdTypeDef, pszName, 0/*dwFlags*/, (PCCOR_SIGNATURE) qbComSig.Ptr(),cbTotal, ELEMENT_TYPE_VOID, NULL/*default*/, -1, + mdFuncSet, mdFuncGet, NULL, &pdProperty)); + + // Handle dispids for non-implemented interfaces, and for default interface + if (m_ImplIface != eImplIface) + { + // Set the Dispid CA on the property. + long lDispSet = 1; + _SetDispIDCA(pITI, pMember->m_iMember, psVar->memid, pdProperty, TRUE, &lDispSet, FALSE); + + // If this property is default property, add a custom attribute to the class. + if (lDispSet == DISPID_VALUE) + IfFailGo(_AddDefaultMemberCa(m_tdTypeDef, m_szMember)); + } + + // Add the alias information if the type is an alias. + IfFailGo(_HandleAliasInfo(pITI, &psVar->elemdescVar.tdesc, pdProperty)); + + if (bConversionLoss) + { + hr = S_CONVERSION_LOSS; + ReportEvent(NOTIF_CONVERTWARNING, TLBX_I_UNCONVERTABLE_ARGS, m_szName, m_szMember); + } + +ErrExit: + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember=0; + return (hr); +} // HRESULT CImportTlb::_ConvProperty() + +//***************************************************************************** +// Convert the NewEnum dispatch property into the GetEnumerator method. +//***************************************************************************** +HRESULT CImportTlb::_ConvNewEnumProperty( + ITypeInfo *pITI, // Containing TypeInfo. + VARDESC *psVar, // VARDESC for the property. + MemberInfo *pMember) +{ + HRESULT hr; // A result. + mdMethodDef mdGetEnum; // The GetEnumerator method. + CQuickBytes qbComSig; // com signature buffer + ULONG cb; // Size of an element. + ULONG cbTotal = 0; // Total size of signature. + BYTE *pbSig; // Pointer to signature buffer. + BOOL bConversionLoss=false; // If true, some attributes were lost on conversion. + CQuickArray<BYTE> qbNativeBuf; // Native type buffer. + ULONG iNativeOfs=0; // Current offset in native type buffer. + + // Get the name. + IfFailGo(pITI->GetDocumentation(psVar->memid, &m_szMember, 0,0,0)); + + // Create the GetEnumerator signature. + IfFailGo(qbComSig.ReSizeNoThrow(CB_MAX_ELEMENT_TYPE * 2)); + pbSig = reinterpret_cast<BYTE*>(qbComSig.Ptr()); + cbTotal = cb = CorSigCompressData((ULONG)IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS, pbSig); + + // GetEnumerator takes zero parameters. + cb = CorSigCompressData(0, &(pbSig[cb])); + cbTotal += cb; + + // Getter returns the property type. + IfFailGo(_ConvSignature(pITI, &psVar->elemdescVar.tdesc, SIG_ELEM, qbComSig, cbTotal, &cbTotal, qbNativeBuf, 0, &iNativeOfs, TRUE)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + + // Create the GetEnumerator method. + IfFailGo(m_pEmit->DefineMethod(m_tdTypeDef, GET_ENUMERATOR_MEMBER_NAME, DEFAULT_INTERFACE_FUNC_FLAGS, (PCCOR_SIGNATURE) qbComSig.Ptr(), cbTotal, + 0/*RVA*/, DEFAULT_ITF_FUNC_IMPL_FLAGS, &mdGetEnum)); + + // Set the Dispid CA. + _SetDispIDCA(pITI, pMember->m_iMember, psVar->memid, mdGetEnum, TRUE, NULL, FALSE); + + // Add the alias information if the type is an alias. + IfFailGo(_HandleAliasInfo(pITI, &psVar->elemdescVar.tdesc, mdGetEnum)); + + if (bConversionLoss) + { + hr = S_CONVERSION_LOSS; + ReportEvent(NOTIF_CONVERTWARNING, TLBX_I_UNCONVERTABLE_ARGS, m_szName, m_szMember); + } + +ErrExit: + if (m_szMember) + ::SysFreeString(m_szMember), m_szMember=0; + return (hr); +} // HRESULT CImportTlb::_ConvNewEnumProperty() + +//***************************************************************************** +// Given an ITypeLib*, come up with a namespace name. Use the typelib name +// unless there is one specified via custom attribute. +// +// NOTE: This returns the member variable m_wzNamespace if the typelib +// is the importing typelib. That must not be freed! +//***************************************************************************** +HRESULT CImportTlb::GetNamespaceOfRefTlb( + ITypeLib *pITLB, // TypeLib for which to get namespace name. + BSTR *pwzNamespace, // Put the name here. + CImpTlbDefItfToClassItfMap **ppDefItfToClassItfMap) // Put def itf to class itf map here. +{ + mdAssemblyRef arDummy; + BSTR wzAsmName = NULL; + HRESULT hr = S_OK; + + // If already resolved, just return assembly ref. + if (!m_LibRefs.Find(pITLB, &arDummy, pwzNamespace, &wzAsmName, NULL, ppDefItfToClassItfMap)) + { + // Add a reference to the typelib. + IfFailGo(_AddTlbRef(pITLB, &arDummy, pwzNamespace, &wzAsmName, ppDefItfToClassItfMap)); + } + +ErrExit: + if (wzAsmName) + ::SysFreeString(wzAsmName); + + return hr; +} // HRESULT CImportTlb::GetNamespaceOfRefTlb() + +//***************************************************************************** +// Given a TYPEDESC, resolve the USERDEFINED to the TYPEKIND. +//***************************************************************************** +HRESULT CImportTlb::_ResolveTypeDescAliasTypeKind( + ITypeInfo *pITIAlias, // The typeinfo containing the typedesc. + TYPEDESC *ptdesc, // The typedesc. + TYPEKIND *ptkind) // Put the aliased typekind. +{ + HRESULT hr; // A result. + ITypeInfo *pTIResolved=0; // The resolved ITypeInfo. + TYPEATTR *psResolved=0; // The resolved TypeInfo's TYPEATTR + + if (ptdesc->vt != VT_USERDEFINED) + { + *ptkind = TKIND_MAX; + return S_FALSE; + } + + hr = _ResolveTypeDescAlias(pITIAlias, ptdesc, &pTIResolved, &psResolved); + if (hr == S_OK) + *ptkind = psResolved->typekind; + else + *ptkind = TKIND_MAX; + + if (psResolved) + pTIResolved->ReleaseTypeAttr(psResolved); + if (pTIResolved) + pTIResolved->Release(); + + return hr; +} // HRESULT CImportTlb::_ResolveTypeDescAliasTypeKind() + +//***************************************************************************** +// Given a TYPEDESC in a TypeInfo, eliminate aliases (get to the aliased +// type). +//***************************************************************************** +HRESULT CImportTlb::_ResolveTypeDescAlias( + ITypeInfo *pITIAlias, // The typeinfo containing the typedesc. + const TYPEDESC *ptdesc, // The typedesc. + ITypeInfo **ppTIResolved, // Put the aliased ITypeInfo here. + TYPEATTR **ppsAttrResolved, // Put the ITypeInfo's TYPEATTR here. + GUID *pGuid) // Caller may want aliased object's guid. +{ + HRESULT hr; // A result. + ITypeInfo *pITI=0; // Referenced typeinfo. + TYPEATTR *psAttr=0; // TYPEATTR of referenced typeinfo. + + // If the TDESC isn't a USERDEFINED, it is already resolved. + if (ptdesc->vt != VT_USERDEFINED) + { + *ppTIResolved = pITIAlias; + pITIAlias->AddRef(); + // Need to addref the [out] psAttr. Only way to do it: + IfFailGo(pITIAlias->GetTypeAttr(ppsAttrResolved)); + hr = S_FALSE; + goto ErrExit; + } + + // The TYPEDESC is a USERDEFINED. Get the TypeInfo. + IfFailGo(pITIAlias->GetRefTypeInfo(ptdesc->hreftype, &pITI)); + IfFailGo(pITI->GetTypeAttr(&psAttr)); + + // If the caller needs the aliased object's guid, get it now. + if (pGuid && *pGuid == GUID_NULL && psAttr->guid != GUID_NULL) + *pGuid = psAttr->guid; + + // If the userdefined typeinfo is not itself an alias, then it is what the alias aliases. + // Also, if the userdefined typeinfo is an alias to a builtin type, then the builtin + // type is what the alias aliases. + if (psAttr->typekind != TKIND_ALIAS || psAttr->tdescAlias.vt != VT_USERDEFINED) + { + *ppsAttrResolved = psAttr; + *ppTIResolved = pITI; + if (psAttr->typekind == TKIND_ALIAS) + hr = S_FALSE; + psAttr = 0; + pITI = 0; + goto ErrExit; + } + + // The userdefined type was itself an alias to a userdefined type. Alias to what? + hr = _ResolveTypeDescAlias(pITI, &psAttr->tdescAlias, ppTIResolved, ppsAttrResolved, pGuid); + +ErrExit: + if (psAttr) + pITI->ReleaseTypeAttr(psAttr); + if (pITI) + pITI->Release(); + return hr; +} // HRESULT CImportTlb::_ResolveTypeDescAlias() + +//***************************************************************************** +// Create the TypeInfo records (AKA classes, AKA critters). +//***************************************************************************** +HRESULT CImportTlb::GetKnownTypeToken( + VARTYPE vt, // The type for which the token is desired. + mdTypeRef *ptr) // Put the token here. +{ + HRESULT hr = S_OK; // A result. + + _ASSERTE( + (vt >= VT_CY && vt <= VT_DECIMAL) || (vt == VT_SAFEARRAY) || (vt == VT_SLOT_FOR_GUID) || + (vt == VT_SLOT_FOR_IENUMERABLE) || (vt == VT_SLOT_FOR_MULTICASTDEL) || (vt == VT_SLOT_FOR_TYPE) || + (vt == VT_SLOT_FOR_STRINGBUF)); + + // If it has already been added, just return it. + if (m_tkKnownTypes[vt]) + { + *ptr = m_tkKnownTypes[vt]; + goto ErrExit; + } + + // Not yet created, so create the typeref now. + switch (vt) + { + //=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + // WARNING: the VT_EMPTY slot is used for System.GUID!! + case VT_SLOT_FOR_GUID: + _ASSERTE(VT_SLOT_FOR_GUID == VT_EMPTY); + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_GUID, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_SLOT_FOR_GUID])); // Put mdTypeRef here + break; + + // WARNING: the VT_NULL slot is used for System.Collections.IEnumerable!! + case VT_SLOT_FOR_IENUMERABLE: + _ASSERTE(VT_SLOT_FOR_IENUMERABLE == VT_NULL); + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_IENUMERABLE, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_SLOT_FOR_IENUMERABLE])); // Put mdTypeRef here + break; + + // WARNING: the VT_I2 slot is used for System.MulticastDelegate!! + case VT_SLOT_FOR_MULTICASTDEL: + _ASSERTE(VT_SLOT_FOR_MULTICASTDEL == VT_I2); + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_MULTICASTDELEGATE, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_SLOT_FOR_MULTICASTDEL])); // Put mdTypeRef here + break; + + // WARNING: the VT_I4 slot is used for System.Type!! + case VT_SLOT_FOR_TYPE: + _ASSERTE(VT_SLOT_FOR_TYPE == VT_I4); + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_TYPE, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_SLOT_FOR_TYPE])); // Put mdTypeRef here + break; + + // WARNING: the VT_I8 slot is used for System.Text.StringBuilder!! + case VT_SLOT_FOR_STRINGBUF: + _ASSERTE(VT_SLOT_FOR_STRINGBUF == VT_I8); + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_STRINGBUFFER, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_SLOT_FOR_STRINGBUF])); // Put mdTypeRef here + break; + + case VT_CY: + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_DECIMAL, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_CY])); // Put mdTypeRef here + break; + + case VT_DATE: + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_DATE, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_DATE])); // Put mdTypeRef here + break; + + case VT_DECIMAL: + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_DECIMAL, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_DECIMAL])); // Put mdTypeRef here + break; + + case VT_SAFEARRAY: + IfFailGo(m_TRMap.DefineTypeRef( + m_pEmit, // The emit scope. + m_arSystem, // The system assemblyref. + TLB_CLASSLIB_ARRAY, // URL of the TypeDef, wide chars. + &m_tkKnownTypes[VT_SAFEARRAY])); // Put mdTypeRef here + break; + + default: + _ASSERTE(!"Unknown type in GetKnownTypes"); + IfFailGo(E_INVALIDARG); + } + + _ASSERTE(!IsNilToken(m_tkKnownTypes[vt])); + *ptr = m_tkKnownTypes[vt]; + +ErrExit: + return hr; +} // HRESULT CImportTlb::GetKnownTypeToken() + + +//***************************************************************************** +// Given an ITypeInfo for a coclass, return an ITypeInfo for the default +// interface. This is either the explicitly marked default, or the first +// non-source interface. +//***************************************************************************** +HRESULT CImportTlb::GetDefaultInterface( // Error, S_OK or S_FALSE. + ITypeInfo *pCoClassTI, // The TypeInfo of the coclass. + ITypeInfo **pDefaultItfTI) // The returned default interface. +{ + HRESULT hr; // A result + HREFTYPE href; // HREFTYPE of an implemented interface. + INT ImplFlags; // ImplType flags. + ITypeInfo *pITI=NULL; // ITypeInfo for an interface. + TYPEATTR *pCoClassTypeAttr; // The type attributes of the coclass. + int NumInterfaces; // The number of interfaces on the coclass. + int i; // A counter. + + // Initialize the default interface to NULL. + *pDefaultItfTI = NULL; + + // Retrieve the number of interfaces the coclass has + IfFailGo(pCoClassTI->GetTypeAttr(&pCoClassTypeAttr)); + NumInterfaces = pCoClassTypeAttr->cImplTypes; + pCoClassTI->ReleaseTypeAttr(pCoClassTypeAttr); + + for (i=0; i < NumInterfaces; i++) + { + IfFailGo(pCoClassTI->GetImplTypeFlags(i, &ImplFlags)); + + if ((ImplFlags & (IMPLTYPEFLAG_FSOURCE | IMPLTYPEFLAG_FDEFAULT)) == IMPLTYPEFLAG_FDEFAULT) + { + // We have found a default interface. + if (*pDefaultItfTI) + (*pDefaultItfTI)->Release(); + + IfFailGo(pCoClassTI->GetRefTypeOfImplType(i, &href)); + IfFailGo(pCoClassTI->GetRefTypeInfo(href, pDefaultItfTI)); + break; + } + else if (!(ImplFlags & IMPLTYPEFLAG_FSOURCE) && !(*pDefaultItfTI)) + { + // If this is the first normal interface we encounter then we need to + // hang on to it in case we don't find any default interfaces. If that + // happens then this is the one that will be returned. + IfFailGo(pCoClassTI->GetRefTypeOfImplType(i, &href)); + IfFailGo(pCoClassTI->GetRefTypeInfo(href, pDefaultItfTI)); + } + } + + // Return either S_OK or S_FALSE depending on if we have found a default interface. + if (*pDefaultItfTI) + return S_OK; + else + return S_FALSE; + +ErrExit: + if (pITI) + pITI->Release(); + + return hr; +} // HRESULT CImportTlb::GetDefaultInterface() + +//***************************************************************************** +// Given a TypeInfo, return a TypeDef/TypeRef token. +//***************************************************************************** +HRESULT CImportTlb::_GetTokenForTypeInfo( + ITypeInfo *pITI, // ITypeInfo for which to get token. + BOOL bConvDefItfToClassItf, // If TRUE, convert the def itf to its class itf. + mdToken *pToken, // Put the token here. + __out_ecount (chTypeRef) __out_opt LPWSTR pszTypeRef, // Optional, put the name here. + int chTypeRef, // Size of the name buffer in characters. + int *pchTypeRef, // Optional, put size of name here. + BOOL bAsmQualifiedName) // Assembly qualified name or not? +{ + HRESULT hr; // A result. + ITypeLib *pITLB=0; // Containing typelib. + BSTR bstrNamespace=0; // Namespace of the type. + BSTR bstrFullName=0; // Fully qualified name of type. + BSTR bstrTempName=0; // Temp name. + BSTR bstrAsmName=0; // Assembly name. + LPCWSTR strTypeName=0; // The type name. + mdAssemblyRef ar; // The typelib's assembly ref. + TYPEATTR* psAttr = 0; // The TYPEATTR for the type info. + CImpTlbDefItfToClassItfMap *pDefItfToClassItfMap; // The default interface to class interface map. + + // Get the library. + IfFailGo(pITI->GetContainingTypeLib(&pITLB, 0)); + + // Resolve the external reference. + IfFailGo(_AddTlbRef(pITLB, &ar, &bstrNamespace, &bstrAsmName, &pDefItfToClassItfMap)); + + // If are converting default interfaces to class interfaces, then check + // to see if we need to do the convertion for the current ITypeInfo. + if (bConvDefItfToClassItf) + { + // Retrieve the TYPEATTR. + IfFailGo(pITI->GetTypeAttr(&psAttr)); + + // If we are dealing with an interface, then check to see if there + // is a class interface we should use. + if (psAttr->typekind == TKIND_INTERFACE || psAttr->typekind == TKIND_DISPATCH) + { + strTypeName = pDefItfToClassItfMap->GetClassItfName(psAttr->guid); + } + } + + // If we haven't found a class interface, then use the current interface. + if (!strTypeName) + { + // Get the name of the typeinfo. + IfFailGo(GetManagedNameForTypeInfo(pITI, bstrNamespace, NULL, &bstrFullName)); + strTypeName = bstrFullName; + } + + // Give name back to caller, if desired. + if (pszTypeRef) + wcsncpy_s(pszTypeRef, chTypeRef, strTypeName, chTypeRef-1); + if (pchTypeRef) + *pchTypeRef = (int)(wcslen(pszTypeRef) + 1); + + // Define the TypeRef (will return any existing typeref). + IfFailGo(m_TRMap.DefineTypeRef(m_pEmit, ar, strTypeName, pToken)); + + // If the caller desires an assembly qualified name, then provide it. + if (bAsmQualifiedName) + { + int cchAsmQualifiedName = SysStringLen(bstrFullName) + SysStringLen(bstrAsmName) + 2; + IfNullGo(bstrTempName = ::SysAllocStringLen(0, cchAsmQualifiedName)); + ns::MakeAssemblyQualifiedName(bstrTempName, cchAsmQualifiedName + 1, bstrFullName, SysStringLen(bstrFullName), bstrAsmName, SysStringLen(bstrAsmName)); + SysFreeString(bstrFullName); + bstrFullName = bstrTempName; + } + + // Give name back to caller, if desired. + if (pszTypeRef) + wcsncpy_s(pszTypeRef, chTypeRef, bstrFullName, chTypeRef-1); + if (pchTypeRef) + *pchTypeRef = (int)(wcslen(pszTypeRef) + 1); + +ErrExit: + if (bstrNamespace) + ::SysFreeString(bstrNamespace); + if (bstrFullName) + ::SysFreeString(bstrFullName); + if (bstrAsmName) + ::SysFreeString(bstrAsmName); + if (pITLB) + pITLB->Release(); + if (psAttr) + pITI->ReleaseTypeAttr(psAttr); + + return (hr); +} // HRESULT CImportTlb::_GetTokenForTypeInfo() + +//***************************************************************************** +// Given a TypeInfo for a source interface, creates a new event interface +// if none exists or returns an existing one. +//***************************************************************************** +HRESULT CImportTlb::_GetTokenForEventItf(ITypeInfo *pSrcItfITI, mdTypeRef *ptr) +{ +#ifndef DACCESS_COMPILE + HRESULT hr = S_OK; // A result. + ImpTlbEventInfo* pEventInfo; // The event information. + BSTR bstrSrcItfName = NULL; // The name of the CoClass. + CQuickArray<WCHAR> qbEventItfName; // The name of the event interface. + CQuickArray<WCHAR> qbEventProviderName; // The name of the event provider. + mdToken tkAttr; // Custom attribute type. + BSTR szOldName = NULL; // The old value m_tdTypeDef. + mdTypeDef tdOldTypeDef = NULL; // The old value m_szName. + TYPEATTR* psAttr = 0; // The TYPEATTR for the source interface. + mdTypeRef trEventItf; // A type ref to the event interface. + ITypeLib* pTypeTLB; // The typelib containing this interface. + mdAssemblyRef ar; // Dummy AssmRef. + BSTR wzNamespace=0; // Namespace of the event interface assembly. + BSTR wzAsmName=0; // Assembly name of the event interface assembly. + Assembly* SrcItfAssembly=0; // The Source Event Interface assembly. + CQuickArray<WCHAR> qbSrcItfName; // The name of the source interface. + CImpTlbDefItfToClassItfMap *pDefItfToClassItfMap; // The default interface to class interface map. + BOOL fInheritsIEnum = FALSE; + + // Retrieve the namespace of the typelib containing this source interface. + IfFailGo(pSrcItfITI->GetContainingTypeLib(&pTypeTLB, NULL)); + + // Resolve the external reference. + IfFailGo(_AddTlbRef(pTypeTLB, &ar, &wzNamespace, &wzAsmName, &pDefItfToClassItfMap)); + + // Get the assembly + namespace the source interface resides in. + // May return all NULL - indicating the importing assembly. + m_LibRefs.Find(pTypeTLB, &ar, &wzNamespace, &wzAsmName, &SrcItfAssembly, NULL); + if (SrcItfAssembly == NULL) + SrcItfAssembly = m_pAssembly; + + // Retrieve the full name of the source interface. + if (wzNamespace) + IfFailGo(GetManagedNameForTypeInfo(pSrcItfITI, (WCHAR*)wzNamespace, NULL, &bstrSrcItfName)); + else + IfFailGo(GetManagedNameForTypeInfo(pSrcItfITI, m_wzNamespace, NULL, &bstrSrcItfName)); + + // Start by looking up the event information for the source itf type info. + pEventInfo = m_EventInfoMap.FindEventInfo(bstrSrcItfName); + if (pEventInfo) + { + SysFreeString(bstrSrcItfName); + *ptr = pEventInfo->trEventItf; + return S_OK; + } + + // Store the old values of the ITypeInfo name and of the current type def. + szOldName = m_szName; + tdOldTypeDef = m_tdTypeDef; + m_szName = NULL; + + // Get some information about the TypeInfo. + IfFailGo(pSrcItfITI->GetDocumentation(MEMBERID_NIL, &m_szName, 0, 0, 0)); + IfFailGo(pSrcItfITI->GetTypeAttr(&psAttr)); + + if (ExplicitlyImplementsIEnumerable(pSrcItfITI, psAttr) == S_OK) + fInheritsIEnum = TRUE; + + // Generate a unique name for the event interface which will be of the form: + // <ImportingAssemblyNamespace>.<SrcItfName>_Event<PotentialSuffix> + + // Strip the namespace + IfFailGo(qbSrcItfName.ReSizeNoThrow(wcslen(bstrSrcItfName) + 2)); + ns::SplitPath((WCHAR*)bstrSrcItfName, NULL, 0, qbSrcItfName.Ptr(), (int)wcslen(bstrSrcItfName) + 1); + + // Add the namespace of the importing typelib and the event suffix + IfFailGo(qbEventItfName.ReSizeNoThrow(qbSrcItfName.Size() + wcslen(m_wzNamespace) + EVENT_ITF_SUFFIX_LENGTH + 7)); + StringCchPrintf(qbEventItfName.Ptr(), qbEventItfName.Size(), W("%s.%s%s"), m_wzNamespace, qbSrcItfName.Ptr(), EVENT_ITF_SUFFIX); + IfFailGo(GenerateUniqueTypeName(qbEventItfName)); + + // Generate a unique name for the event provider which will be of the form: + // <ImportingAssemblyNamespace>.<SrcItfName>_EventProvider<PotentialSuffix> + + // Add the namespace of the imporing typelib and the event suffix + IfFailGo(qbEventProviderName.ReSizeNoThrow(qbSrcItfName.Size() + wcslen(m_wzNamespace) + EVENT_PROVIDER_SUFFIX_LENGTH + 7)); + StringCchPrintf(qbEventProviderName.Ptr(), qbEventProviderName.Size(), W("%s.%s%s"), m_wzNamespace, qbSrcItfName.Ptr(), EVENT_PROVIDER_SUFFIX); + IfFailGo(GenerateUniqueTypeName(qbEventProviderName)); + + // Add the event provider as a reserved name. + m_ReservedNames.AddReservedName(qbEventProviderName.Ptr()); + + // Create the typedef for the event interface. + IfFailGo(m_pEmit->DefineTypeDef(qbEventItfName.Ptr(), tdPublic | tdInterface | tdAbstract, mdTypeDefNil, NULL, &m_tdTypeDef)); + + // Hide the event interface from the VB object browser (_Event) + _SetHiddenCA(m_tdTypeDef); + + // Make the interface ComVisible(false). + { + DECLARE_CUSTOM_ATTRIBUTE(sizeof(BYTE)); + BUILD_CUSTOM_ATTRIBUTE(BYTE, FALSE); + IfFailGo(GetAttrType(ATTR_COMVISIBLE, &tkAttr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + + // Set the ComEventInterface CA on the interface. + { + CQuickBytes asmQualifiedSrcItfName; + if (!ns::MakeAssemblyQualifiedName(asmQualifiedSrcItfName, bstrSrcItfName, wzAsmName)) + IfFailGo(E_OUTOFMEMORY); + DECLARE_DYNLEN_CUSTOM_ATTRIBUTE(wcslen((WCHAR*)asmQualifiedSrcItfName.Ptr()) + 5 + wcslen(qbEventProviderName.Ptr()) + 5); + APPEND_WIDE_STRING_TO_CUSTOM_ATTRIBUTE((WCHAR*)asmQualifiedSrcItfName.Ptr()); + APPEND_WIDE_STRING_TO_CUSTOM_ATTRIBUTE(qbEventProviderName.Ptr()); + IfFailGo(GetAttrType(ATTR_COMEVENTINTERFACE, &tkAttr)); + FINISH_DYNLEN_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(m_tdTypeDef, tkAttr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + } + + // Add the add_XXX and remove_XXX methods to the event interface. + IfFailGo(_ConvSrcIfaceMembers(pSrcItfITI, psAttr, fInheritsIEnum)); + + // Define a typeref for the event interface. + IfFailGo(m_pEmit->DefineTypeRefByName(TokenFromRid(1, mdtModule), qbEventItfName.Ptr(), &trEventItf)); + + // Add the event info to the map. + IfFailGo(m_EventInfoMap.AddEventInfo(bstrSrcItfName, trEventItf, qbEventItfName.Ptr(), qbEventProviderName.Ptr(), SrcItfAssembly)); + + // Set the out type ref. + *ptr = trEventItf; + +ErrExit: + if (bstrSrcItfName) + ::SysFreeString(bstrSrcItfName); + if (m_szName) + ::SysFreeString(m_szName); + if (psAttr) + pSrcItfITI->ReleaseTypeAttr(psAttr); + if (pTypeTLB) + pTypeTLB->Release(); + + // Restore the initial values for the ITypeInfo name and the type def. + m_szName = szOldName; + m_tdTypeDef = tdOldTypeDef; + + return (hr); +#else + DacNotImpl(); + return E_NOTIMPL; +#endif // #ifndef DACCESS_COMPILE +} // HRESULT CImportTlb::_GetTokenForEventItf() + +//***************************************************************************** +// Creates an interface with the same name as the class and which implements +// the default interface and the default event interface. +//***************************************************************************** +HRESULT CImportTlb::_CreateClassInterface(ITypeInfo *pCoClassITI, ITypeInfo *pDefItfITI, mdTypeRef trDefItf, mdTypeRef rtDefEvItf, mdToken *ptr) +{ + HRESULT hr = S_OK; // A result. + CQuickArray<mdToken> rImpls; // Array of implemented interfaces. + int ixImpl = -1; // Index into rImpls for implemented interface. + mdTypeDef tdTypeDef; // The class interface typedef. + BSTR bstrFullName = NULL; // The name of the CoClass. + TYPEATTR *psAttrIface=0; // TYPEATTR for an interface. + CQuickArray<WCHAR> qbClassName; // The name of the class. + + IfFailGo(rImpls.ReSizeNoThrow(3)); + memset(rImpls.Ptr(), 0, 3 * sizeof(mdToken)); + if (trDefItf) + rImpls[++ixImpl] = trDefItf; + if (rtDefEvItf) + rImpls[++ixImpl] = rtDefEvItf; + + // Retrieve the TypeAttr for the interface. + if (pDefItfITI) + IfFailGo(pDefItfITI->GetTypeAttr(&psAttrIface)); + + // Retrieve the name of the CoClass (use the original name if this is an alias). + IfFailGo(GetManagedNameForTypeInfo(m_pOrigITI, m_wzNamespace, NULL, &bstrFullName)); + + // Create the typedef. + IfFailGo(m_pEmit->DefineTypeDef(bstrFullName, rdwTypeFlags[TKIND_INTERFACE], mdTypeDefNil, 0, &tdTypeDef)); + + // Set the IID to the IID of the default interface. + IfFailGo(_AddGuidCa(tdTypeDef, psAttrIface ? psAttrIface->guid : GUID_NULL)); + + // Add the CoClass CA to the interface. + _AddStringCa(ATTR_COCLASS, tdTypeDef, m_szMngName); + + // Add the implemented interfaces and event interfaces to the TypeDef. + IfFailGo(m_pEmit->SetTypeDefProps(tdTypeDef, ULONG_MAX/*Classflags*/, + ULONG_MAX, (mdToken*)rImpls.Ptr())); + + // Set the out type def. + *ptr = tdTypeDef; + +ErrExit: + if (bstrFullName) + ::SysFreeString(bstrFullName); + if (psAttrIface) + pDefItfITI->ReleaseTypeAttr(psAttrIface); + + return (hr); +} // HRESULT CImportTlb::_CreateClassInterface() + +//***************************************************************************** +// Creates an interface with the same name as the class and which implements +// the default interface and the default event interface. +//***************************************************************************** +HRESULT CImportTlb::GetManagedNameForCoClass(ITypeInfo *pITI, CQuickArray<WCHAR> &qbClassName) +{ + HRESULT hr = S_OK; // A result. + BSTR bstrFullName=0; // Fully qualified name of type. + + // Retrieve the name of the CoClass. + IfFailGo(GetManagedNameForTypeInfo(pITI, m_wzNamespace, NULL, &bstrFullName)); + + // Resize the class name to accomodate the Class and potential suffix. + IfFailGo(qbClassName.ReSizeNoThrow(wcslen(bstrFullName) + CLASS_SUFFIX_LENGTH + 6)); + + // Set the class name to the CoClass name suffixed with Class. + StringCchPrintf(qbClassName.Ptr(), qbClassName.Size(), W("%s%s"), bstrFullName, CLASS_SUFFIX); + + // Generate a unique name for the class. + IfFailGo(GenerateUniqueTypeName(qbClassName)); + +ErrExit: + if (bstrFullName) + ::SysFreeString(bstrFullName); + + return (hr); +} // HRESULT CImportTlb::GetManagedNameForCoClass() + +//***************************************************************************** +// Creates an interface with the same name as the class and which implements +// the default interface and the default event interface. +//***************************************************************************** +HRESULT CImportTlb::GenerateUniqueTypeName(CQuickArray<WCHAR> &qbTypeName) +{ + HRESULT hr = S_OK; // A result. + WCHAR *pSuffix=0; // Location for suffix. + size_t cchSuffix; + WCHAR *pName=0; // The name without the namespace. + int iSuffix=2; // Starting value for suffix. + mdToken td; // For looking up a TypeDef. + BSTR szTypeInfoName=0; // Name of a typeinfo. + ITypeInfo *pITI=0; // A typeinfo. + + // Resize the class name to accomodate the Class and potential suffix. + IfFailGo(qbTypeName.ReSizeNoThrow(wcslen(qbTypeName.Ptr()) + 6)); + + // Set the suffix pointer. + pSuffix = qbTypeName.Ptr() + wcslen(qbTypeName.Ptr()); + cchSuffix = qbTypeName.Size() - wcslen(qbTypeName.Ptr()); + + // Set the name pointer. + WCHAR* pTemp = ns::FindSep(qbTypeName.Ptr()); + if (pTemp == NULL) + pName = qbTypeName.Ptr(); + else + pName = pTemp + 1; + + // Attempt to find a class name that is not in use. + for (;;) + { + // First check to see if the type name is in use in the metadata we + // have emitted so far. + hr = m_pImport->FindTypeDefByName(qbTypeName.Ptr(), mdTypeDefNil, &td); + if (hr == CLDB_E_RECORD_NOTFOUND) + { + // It is not in use in the metadata but we still need to check the + // typelib because the type might not have been emitted yet. + USHORT cReq = 4; + USHORT cFound = cReq; + BOOL bTypeInTlb = FALSE; + CQuickArray<ITypeInfo *> qbTI; + CQuickArray<MEMBERID> qbMemId; + + // Retrieve all the instances of the name in the typelib. + do + { + // Double the number of requested names. + cReq *= 2; + + // Resize the array's to accomodate the resquested names. + IfFailGo(qbTI.ReSizeNoThrow(cReq)); + IfFailGo(qbMemId.ReSizeNoThrow(cReq)); + + // Request the names. + cFound = cReq; + IfFailGo(m_pITLB->FindName(pName, 0, qbTI.Ptr(), qbMemId.Ptr(), &cFound)); + + // Release all the ITypeInfo's. + for (int i = 0; i < cFound; i++) + qbTI[i]->Release(); + } + while (cReq == cFound); + + // Check to see if one of the instances of the name is for a type. + for (int i = 0; i < cFound; i++) + { + if (qbMemId[i] == MEMBERID_NIL) + { + bTypeInTlb = TRUE; + break; + } + } + + // If the type name exists in the typelib, but we didn't find it as a type, + // we still need to do a deeper check, due to how FindName() works. + if (!bTypeInTlb && cFound > 0) + { + int cTi; // Count of TypeInfos. + int i; // Loop control. + + //@todo: this iterates over every typeinfo every time! We could cache + // the names, and skip the types already converted. However, this should + // be pretty rare. + + // How many TypeInfos? + IfFailGo(cTi = m_pITLB->GetTypeInfoCount()); + + // Iterate over them. + for (i=0; i<cTi; ++i) + { + // Get the TypeInfo, and its name. + IfFailGo(m_pITLB->GetTypeInfo(i, &pITI)); + IfFailGo(pITI->GetDocumentation(MEMBERID_NIL, &szTypeInfoName, 0, 0, 0)); + if (wcscmp(pName, szTypeInfoName) == 0) + { + bTypeInTlb = TRUE; + break; + } + + // Release for next TypeInfo. + ::SysFreeString(szTypeInfoName); + szTypeInfoName = 0; + pITI->Release(); + pITI = 0; + } + } + + // The type name is not in the typelib and not in the metadata then we still + // need to check to see if is a reserved name. + if (!bTypeInTlb) + { + if (!m_ReservedNames.IsReservedName(qbTypeName.Ptr())) + { + // The name is not a reserved name so we can use it. + break; + } + } + } + IfFailGo(hr); + + // Append the new suffix to the class name. + StringCchPrintf(pSuffix, cchSuffix, W("_%i"), iSuffix++); + } + +ErrExit: + if (szTypeInfoName) + ::SysFreeString(szTypeInfoName); + if (pITI) + pITI->Release(); + return (hr); +} // HRESULT CImportTlb::GenerateUniqueTypeName() + +//***************************************************************************** +// Generate a unique member name based on the interface member name. +//***************************************************************************** +HRESULT CImportTlb::GenerateUniqueMemberName(// S_OK or error + CQuickArray<WCHAR> &qbMemberName, // Original name of member. + PCCOR_SIGNATURE pSig, // Signature of the member. + ULONG cSig, // Length of the signature. + LPCWSTR szPrefix, // Possible prefix for decoration. + mdToken type) // Is it a property? (Not a method?) +{ + HRESULT hr; // A result. + mdToken tkMember; // Dummy location for token. + WCHAR *pSuffix=0; // Location for suffix. + size_t cchSuffix = 0; + int iSuffix=2; // Starting value for suffix. + + // Try to find a member name that is not already in use. + for (;;) + { // See if this is (finally) a unique member or property. + switch (type) + { + case mdtProperty: + hr = FindProperty(m_tdTypeDef, qbMemberName.Ptr(), 0, 0, &tkMember); + // If name is OK as property, check that there is no method or + // property with the name. + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindMethod(m_tdTypeDef, qbMemberName.Ptr(), 0,0, &tkMember); + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindEvent(m_tdTypeDef, qbMemberName.Ptr(), &tkMember); + break; + case mdtMethodDef: + hr = FindMethod(m_tdTypeDef, qbMemberName.Ptr(), pSig, cSig, &tkMember); + // If name is OK as method, check that there is no property or + // event with the name. + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindProperty(m_tdTypeDef, qbMemberName.Ptr(), 0,0, &tkMember); + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindEvent(m_tdTypeDef, qbMemberName.Ptr(), &tkMember); + break; + case mdtEvent: + hr = FindEvent(m_tdTypeDef, qbMemberName.Ptr(), &tkMember); + // If name is OK as event, check that there is no property or + // method with the name. + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindProperty(m_tdTypeDef, qbMemberName.Ptr(), 0,0, &tkMember); + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindMethod(m_tdTypeDef, qbMemberName.Ptr(), 0,0, &tkMember); + break; + default: + // Unexpected type. Make noise, but let it pass. + _ASSERTE(!"Unexpected token type in GenerateUniqueMemberName"); + hr = CLDB_E_RECORD_NOTFOUND; + } + + // If name was not found, it is unique. + if (hr == CLDB_E_RECORD_NOTFOUND) + { + hr = S_OK; + goto ErrExit; + } + // Test for failure. + IfFailGo(hr); + + // Make a test decoration. + if (szPrefix) + { + size_t iLenPrefix, iLenName; + iLenPrefix = wcslen(szPrefix); + iLenName = wcslen(qbMemberName.Ptr()); + IfFailGo(qbMemberName.ReSizeNoThrow(iLenName + iLenPrefix + 2)); + // Shift by prefix length, plus '_'. Note use of overlap-safe move. + memmove(&qbMemberName[iLenPrefix+1], &qbMemberName[0], (iLenName+1)*sizeof(WCHAR)); + wcscpy_s(qbMemberName.Ptr(), iLenPrefix + 1, szPrefix); + qbMemberName[iLenPrefix] = W('_'); + szPrefix = 0; + // Try again with prefix before trying a suffix. + continue; + } + if (!pSuffix) + { + IfFailGo(qbMemberName.ReSizeNoThrow(wcslen(qbMemberName.Ptr()) + 6)); + pSuffix = qbMemberName.Ptr() + wcslen(qbMemberName.Ptr()); + cchSuffix = qbMemberName.Size() - wcslen(qbMemberName.Ptr()); + } + StringCchPrintf(pSuffix, cchSuffix, W("_%i"), iSuffix++); + } + +ErrExit: + return hr; +} // HRESULT CImportTlb::GenerateUniqueMemberName() + +//***************************************************************************** +// Convert a TYPEDESC to a COM+ signature. +// +// Conversion rules: +// integral types are converted as-is. +// strings to strings, with native type decoration. +// VT_UNKNOWN, VT_DISPATCH as ref class (ie, Object) +// VT_PTR -> VT_USERDEFINED interface as Object +// VT_USERDEFINED record as value type. +// +// With SIG_FUNC: +// PTR to valuetype depends on other flags: +// [IN] or [RETVAL] valuetype + NATIVE_TYPE_LPSTRUCT +// [OUT] or [IN, OUT] byref valuetype +// PTR to integral type: +// [IN] @todo: see atti +// [OUT] [IN, OUT] byref type +// [RETVAL] type +// PTR to object +// [IN] @todo: see atti +// [OUT] [IN, OUT] byref object +// [RETVAL] object +// +// With SIG_FIELD: +// PTR to integral type adds ELEMENT_TYPE_PTR. +// +// Conversion proceeds in three steps. +// 1) Parse the COM type info. Accumulate VT_PTR and VT_BYREF into a count +// of indirections. Follow TKIND_ALIAS to determine the ultimate aliased +// type, and for non-user-defined types, convert that ultimate type. +// Collect array sizes and udt names. Determine element type and native +// type. +// 2) Normalize to COM+ types. Determine if there is conversion loss. +// 3) Emit the COM+ signature. Recurse to handle array types. Add native +// type info if there is any. +//***************************************************************************** +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +HRESULT CImportTlb::_ConvSignature( // S_OK, S_CONVERSION_LOSS, or error. + ITypeInfo *pITI, // [IN] The typeinfo containing the TYPEDESC. + const TYPEDESC *pType, // [IN] The TYPEDESC to convert. + ULONG Flags, // [IN] Flags describing the TYPEDESC. + CQuickBytes &qbSigBuf, // [IN, OUT] A CQuickBytes containing the signature. + ULONG cbSig, // [IN] Where to start building the signature. + ULONG *pcbSig, // [OUT] Where the signature ends (ix of first byte past; where to start next). + CQuickArray<BYTE> &qbNativeTypeBuf, // [IN, OUT] A CQuickBytes containing the native type. + ULONG cbNativeType, // [IN] Where to start building the native type. + ULONG *pcbNativeType, // [OUT] Where the native type ends (ix of first byte past; where to start next). + BOOL bNewEnumMember, // [IN] A flag indicating if the member is the NewEnum member. + int iByRef) // [IN] ByRef count of caller (for recursive calls). +{ + HRESULT hr=S_OK; // A result. + TYPEDESC tdTemp; // Copy of TYPEDESC, for R/W. + VARTYPE vt; // The typelib signature element. + int bByRef=false; // If true, convert first pointer as "ELEMENT_TYPE_BYREF". + COR_SIGNATURE et=0; // The COM+ signature element. + mdToken tk=0; // Token from some COM+ signature element. + ULONG nt=NATIVE_TYPE_NONE; // Native type decoration. + ITypeInfo *pITIAlias=0; // Typeinfo of the aliased type. + TYPEATTR *psAttrAlias=0; // TYPEATTR of the aliased typeinfo. + ITypeInfo *pITIUD=0; // TypeInfo of an aliased UserDefined type. + ITypeLib *pITLBUD=0; // TypeLib of an aliased UserDefined type. + BSTR bstrNamespace=0; // Namespace name. + BSTR bstrName=0; // UserDefined name. + int bConversionLoss=false; // If true, the conversion was lossy. + BYTE *pbSig; // Byte pointer for easy pointer math. + ULONG cb; // Size of a signature element. + ULONG cElems=0; // Count of elements in an array. + int i; // Loop control. + TYPEATTR *psAttr = 0; // The TYPEATTR for the user defined type being converted. + const StdConvertibleItfInfo *pConvertionInfo = 0; // The standard convertible interface information. + CQuickArray<BYTE> qbNestedNativeType;// A native type buffer used for array sig convertion. + ULONG iNestedNativeTypeOfs=0; // A native type offset. + ULONG nested=NATIVE_TYPE_NONE; // A nested native type. + + // VT_ to ELEMENT_TYPE_ translation table. + struct VtSig + { + CorElementType et; + CorNativeType nt; + short flags; + }; + + // The VARIANT_TYPE to sig mapping table. + static const VtSig + _VtInfo[MAX_TLB_VT] = + { + // Relies on {0} initializing the entire sub-structure to 0. + {ELEMENT_TYPE_MAX, NATIVE_TYPE_NONE, 0}, // VT_EMPTY = 0 + {ELEMENT_TYPE_MAX, NATIVE_TYPE_NONE, 0}, // VT_NULL = 1 + {ELEMENT_TYPE_I2, NATIVE_TYPE_NONE, 0}, // VT_I2 = 2 + {ELEMENT_TYPE_I4, NATIVE_TYPE_NONE, 0}, // VT_I4 = 3 + {ELEMENT_TYPE_R4, NATIVE_TYPE_NONE, 0}, // VT_R4 = 4 + {ELEMENT_TYPE_R8, NATIVE_TYPE_NONE, 0}, // VT_R8 = 5 + {ELEMENT_TYPE_VALUETYPE,NATIVE_TYPE_CURRENCY, 0}, // VT_CY = 6 + {ELEMENT_TYPE_VALUETYPE,NATIVE_TYPE_NONE, 0}, // VT_DATE = 7 + {ELEMENT_TYPE_STRING, NATIVE_TYPE_BSTR, 0}, // VT_BSTR = 8 + {ELEMENT_TYPE_OBJECT, NATIVE_TYPE_IDISPATCH, 0}, // VT_DISPATCH = 9 + {ELEMENT_TYPE_I4, NATIVE_TYPE_ERROR, 0}, // VT_ERROR = 10 scode + {ELEMENT_TYPE_BOOLEAN, NATIVE_TYPE_NONE, 0}, // VT_BOOL = 11 + {ELEMENT_TYPE_OBJECT, NATIVE_TYPE_STRUCT, 0}, // VT_VARIANT = 12 + {ELEMENT_TYPE_OBJECT, NATIVE_TYPE_IUNKNOWN, 0}, // VT_UNKNOWN = 13 + {ELEMENT_TYPE_VALUETYPE,NATIVE_TYPE_NONE, 0}, // VT_DECIMAL = 14 + {ELEMENT_TYPE_MAX, NATIVE_TYPE_NONE, 0}, // = 15 + {ELEMENT_TYPE_I1, NATIVE_TYPE_NONE, 0}, // VT_I1 = 16 + {ELEMENT_TYPE_U1, NATIVE_TYPE_NONE, 0}, // VT_UI1 = 17 + {ELEMENT_TYPE_U2, NATIVE_TYPE_NONE, 0}, // VT_UI2 = 18 + {ELEMENT_TYPE_U4, NATIVE_TYPE_NONE, 0}, // VT_UI4 = 19 + {ELEMENT_TYPE_I8, NATIVE_TYPE_NONE, 0}, // VT_I8 = 20 + {ELEMENT_TYPE_U8, NATIVE_TYPE_NONE, 0}, // VT_UI8 = 21 + + // it would be nice to convert these as I and U, with NT_I4 and NT_U4, but that doesn't work. + {ELEMENT_TYPE_I4, NATIVE_TYPE_NONE, 0}, // VT_INT = 22 INT is I4 on win32 + {ELEMENT_TYPE_U4, NATIVE_TYPE_NONE, 0}, // VT_UINT = 23 UINT is UI4 on win32 + + {ELEMENT_TYPE_VOID, NATIVE_TYPE_NONE, 0}, // VT_VOID = 24 + + {ELEMENT_TYPE_I4, NATIVE_TYPE_ERROR, 0}, // VT_HRESULT = 25 + {ELEMENT_TYPE_MAX, NATIVE_TYPE_NONE, 0}, // VT_PTR = 26 + {ELEMENT_TYPE_MAX, NATIVE_TYPE_NONE, 0}, // VT_SAFEARRAY = 27 + {ELEMENT_TYPE_SZARRAY, NATIVE_TYPE_FIXEDARRAY, 0}, // VT_CARRAY = 28 + {ELEMENT_TYPE_MAX, NATIVE_TYPE_NONE, 0}, // VT_USERDEFINED = 29 + {ELEMENT_TYPE_STRING, NATIVE_TYPE_LPSTR, 0}, // VT_LPSTR = 30 + {ELEMENT_TYPE_STRING, NATIVE_TYPE_LPWSTR, 0}, // VT_LPWSTR = 31 + }; + + _ASSERTE(pType && pcbSig && pcbNativeType); + + //------------------------------------------------------------------------- + // Parse COM signature + + // Strip off leading VT_PTR and VT_BYREF + while (pType->vt == VT_PTR) + pType = pType->lptdesc, ++iByRef; + if (pType->vt & VT_BYREF) + { + tdTemp = *pType; + tdTemp.vt &= ~VT_BYREF; + ++iByRef; + pType = &tdTemp; + } + + // Determine the element type, and possibly the token and/or native type. + switch (vt=pType->vt) + { + case VT_PTR: + _ASSERTE(!"Should not have VT_PTR here"); + break; + + // These are all known types (plus GUID). + case VT_CY: + case VT_DATE: + case VT_DECIMAL: + IfFailGo(GetKnownTypeToken(vt, &tk)); + et = _VtInfo[vt].et; + nt = _VtInfo[vt].nt; + break; + + case VT_SAFEARRAY: + if (m_bSafeArrayAsSystemArray && !IsSigVarArg(Flags)) + { + IfFailGo(GetKnownTypeToken(vt, &tk)); + et = ELEMENT_TYPE_CLASS; + nt = NATIVE_TYPE_SAFEARRAY; + } + else + { + IfFailGo(GetKnownTypeToken(vt, &tk)); + et = ELEMENT_TYPE_SZARRAY; + nt = NATIVE_TYPE_SAFEARRAY; + } + break; + + case VT_USERDEFINED: + // Resolve the alias to the ultimate aliased type. + IfFailGo(_ResolveTypeDescAlias(pITI, pType, &pITIAlias, &psAttrAlias)); + + // If the aliased type was built-in, convert that built-in type. + if (psAttrAlias->typekind == TKIND_ALIAS) + { // Recurse to follow the alias chain. + _ASSERTE(psAttrAlias->tdescAlias.vt != VT_USERDEFINED); + hr = _ConvSignature(pITIAlias, &psAttrAlias->tdescAlias, Flags, qbSigBuf, cbSig, pcbSig, qbNativeTypeBuf, cbNativeType, pcbNativeType, bNewEnumMember, iByRef); + goto ErrExit; + } + + // If the type is a coclass then we need to retrieve the default interface and + // substitute it for the coclass. Look up on the resolved alias, because it is + // that class that has a default interface. + if (psAttrAlias->typekind == TKIND_COCLASS) + { + ITypeInfo *pDefaultItf = NULL; + hr = GetDefaultInterface(pITIAlias, &pDefaultItf); + if ((hr != S_OK) || !pDefaultItf) + { + hr = E_UNEXPECTED; + goto ErrExit; + } + + pITIUD = pDefaultItf; + } + else + { // USERDEFINED class/interface/record/union/enum. Retrieve the type + // info for the user defined type. Note: use the TKIND_ALIAS typeinfo + // itself for this conversion (not the aliased type) to preserve + // names, lib locations, etc. + IfFailGo(pITI->GetRefTypeInfo(pType->hreftype, &pITIUD)); + } + + // pITIUD points to the typeinfo for which we'll create a signature. + IfFailGo(pITIUD->GetDocumentation(MEMBERID_NIL, &bstrName, 0,0,0)); + IfFailGo(pITIUD->GetContainingTypeLib(&pITLBUD, 0)); + IfFailGo(pITIUD->GetTypeAttr(&psAttr)); + IfFailGo(GetNamespaceNameForTypeLib(pITLBUD, &bstrNamespace)); + + // If the "User Defined Type" is GUID in StdOle2, convert to M.R.GUID + if (SString::_wcsicmp(bstrNamespace, COM_STDOLE2) == 0 && wcscmp(bstrName, COM_GUID) == 0) + { // Classlib valuetype GUID. + et = ELEMENT_TYPE_VALUETYPE; + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_GUID, &tk)); + } + else + { // Some user defined class. Is it a value class, or a VOS class? + tk = 0; + switch (psAttrAlias->typekind) + { + case TKIND_RECORD: + case TKIND_ENUM: + case TKIND_UNION: + et = ELEMENT_TYPE_VALUETYPE; + break; + case TKIND_INTERFACE: + case TKIND_DISPATCH: + case TKIND_COCLASS: + // A pointer to a user defined type of interface/dispatch/coclass + // is a straight COM+ object (the ref is implicit), so eliminate + // one byref count for those. + // Somehow, there are typelibs written with ([out, retval] IFoo *pOut); + if (iByRef <= 0) + { + // convert to an int. + bConversionLoss = true; + tk = 0; + et = ELEMENT_TYPE_I; + nt = NATIVE_TYPE_NONE; + iByRef = 0; + break; + } + else + { + --iByRef; + + // Check for references to Stdole2.IUnknown or Stdole2.IDispatch. + if (psAttr->guid == IID_IUnknown) + { + vt = VT_UNKNOWN; + goto IsReallyUnknown; + } + else if (psAttr->guid == IID_IDispatch) + { + vt = VT_DISPATCH; + goto IsReallyUnknown; + } + + // Check to see if this user defined type is one of the standard ones + // we generate custom marshalers for. + pConvertionInfo = GetConvertionInfoFromNativeIID(psAttr->guid); + if (pConvertionInfo) + { + // Convert the UTF8 string to unicode. + int MngTypeNameStrLen = (int)(strlen(pConvertionInfo->m_strMngTypeName) + 1); + WCHAR *strFullyQualifiedMngTypeName = (WCHAR *)_alloca(MngTypeNameStrLen * sizeof(WCHAR)); + int ret = WszMultiByteToWideChar(CP_UTF8, 0, pConvertionInfo->m_strMngTypeName, MngTypeNameStrLen, strFullyQualifiedMngTypeName, MngTypeNameStrLen); + _ASSERTE(ret != 0); + if (!ret) + IfFailGo(HRESULT_FROM_GetLastError()); + + // Create a TypeRef to the marshaller. + IfFailGo(m_TRMap.DefineTypeRef(m_pEmit, m_arSystem, strFullyQualifiedMngTypeName, &tk)); + + // The type is a standard interface that we need to convert. + et = ELEMENT_TYPE_CLASS; + nt = NATIVE_TYPE_CUSTOMMARSHALER; + break; + } + } + et = ELEMENT_TYPE_CLASS; + nt = NATIVE_TYPE_INTF; + break; + default: + //case TKIND_MODULE: -- can't pass one of these as a parameter. + //case TKIND_ALIAS: -- should already be resolved. + _ASSERTE(!"Unexpected typekind for user defined type"); + et = ELEMENT_TYPE_END; + } // switch (psAttrAlias->typekind) + } + break; + + IsReallyUnknown: + case VT_UNKNOWN: + case VT_DISPATCH: + // If the NewEnum member, retrieve the custom marshaler information for IEnumVARIANT. + if (bNewEnumMember && (pConvertionInfo=GetConvertionInfoFromNativeIID(IID_IEnumVARIANT))) + { + // Convert the UTF8 string to unicode. + int MngTypeNameStrLen = (int)(strlen(pConvertionInfo->m_strMngTypeName) + 1); + WCHAR *strFullyQualifiedMngTypeName = (WCHAR *)_alloca(MngTypeNameStrLen * sizeof(WCHAR)); + int ret = WszMultiByteToWideChar(CP_UTF8, 0, pConvertionInfo->m_strMngTypeName, MngTypeNameStrLen, strFullyQualifiedMngTypeName, MngTypeNameStrLen); + _ASSERTE(ret != 0); + if (!ret) + IfFailGo(HRESULT_FROM_GetLastError()); + + // Create a TypeRef to the marshaller. + IfFailGo(m_TRMap.DefineTypeRef(m_pEmit, m_arSystem, strFullyQualifiedMngTypeName, &tk)); + + // The type is a standard interface that we need to convert. + et = ELEMENT_TYPE_CLASS; + nt = NATIVE_TYPE_CUSTOMMARSHALER; + } + else + { + et = _VtInfo[vt].et; + nt = _VtInfo[vt].nt; + } + break; + + case VT_CARRAY: + // Determine the count of elements. + for (cElems=1, i=0; i<pType->lpadesc->cDims; ++i) + cElems *= pType->lpadesc->rgbounds[i].cElements; + + // Set the native type based on weither we are dealing with a field or a method sig. + if (IsSigField(Flags)) + { + nt = NATIVE_TYPE_FIXEDARRAY; + } + else + { + nt = NATIVE_TYPE_ARRAY; + } + + // Set the element type. + et = _VtInfo[vt].et; + break; + + case VT_BOOL: + // Special case for VARIANT_BOOL: If a field of a struct or union, convert + // as ET_I2. + if (IsSigField(Flags)) + vt = VT_I2; + // Fall through to default case. + + default: + if (vt > VT_LPWSTR) + { + ReportEvent(NOTIF_CONVERTWARNING, TLBX_E_BAD_VT_TYPE, vt, m_szName, m_szMember); + IfFailGo(PostError(TLBX_E_BAD_VT_TYPE, vt, m_szName, m_szMember)); + } + _ASSERTE(vt <= VT_LPWSTR && _VtInfo[vt].et != ELEMENT_TYPE_MAX); +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:26000) // "Disable PREFast/espX warning about buffer overflow" +#endif + et = _VtInfo[vt].et; + nt = _VtInfo[vt].nt; +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + break; + } // switch (vt=pType->vt) + + //------------------------------------------------------------------------- + // Normalize to COM+ types. + + // At this point the type, flags, and pointer nesting are known. Is this a legal combination? + // If not, what is the appropriate "simplifing assumption"? + + if (et == ELEMENT_TYPE_VOID) + { + if (IsSigField(Flags)) + { // A void as a field. No byref. + iByRef = 0; + } + else + { + // Param or return type. "void *" -> ET_I, "void **", "void ***",... -> ET_BYREF ET_I + if (iByRef > 1) + iByRef = 1; + else + if (iByRef == 1) + iByRef = 0; + } + tk = 0; + et = ELEMENT_TYPE_I; + nt = NATIVE_TYPE_NONE; + } + + if (et == ELEMENT_TYPE_STRING && iByRef == 0 && !IsSigField(Flags) && IsSigOut(Flags)) + { + // This is an [out] or [in, out] string parameter without indirections. + if (vt == VT_BSTR) + { + // [in, out] System.String does not make much sense. Managed strings are + // immutable and we do not have BSTR <-> StringBuilder marshaling support. + // Convert them to IntPtr. + bConversionLoss = true; + tk = 0; + et = ELEMENT_TYPE_I; + nt = NATIVE_TYPE_NONE; + } + else + { + _ASSERTE(vt == VT_LPSTR || vt == VT_LPWSTR); + + // [in, out] C-strings and wide strings have a lossless conversion to StringBuilder. + IfFailGo(GetKnownTypeToken(VT_SLOT_FOR_STRINGBUF, &tk)); + et = ELEMENT_TYPE_CLASS; + + // nt already has the right value + _ASSERTE(nt == (vt == VT_LPSTR ? NATIVE_TYPE_LPSTR : NATIVE_TYPE_LPWSTR)); + } + } + + if (iByRef) + { + if (et == ELEMENT_TYPE_VALUETYPE && iByRef >= 2) + { + bConversionLoss = true; + tk = 0; + et = ELEMENT_TYPE_I; + nt = NATIVE_TYPE_NONE; + iByRef = 0; + } + else + { + switch (Flags & SIG_TYPE_MASK) + { + case SIG_FIELD: + // If ptr to valuetype or class type, we can't handle it. + if (et == ELEMENT_TYPE_END || + et == ELEMENT_TYPE_CLASS || + et == ELEMENT_TYPE_OBJECT || + et == ELEMENT_TYPE_VALUETYPE) + { + bConversionLoss = true; + tk = 0; + et = ELEMENT_TYPE_I; + nt = NATIVE_TYPE_NONE; + iByRef = 0; + } + break; + case SIG_FUNC: + // Pointer to value type? + if (et == ELEMENT_TYPE_VALUETYPE) + { + // For [retval], eat one level of indirection; otherwise turn one into BYREF + if (IsSigOutRet(Flags)) + { // [out, retval], so reduce one level of indirection. + --iByRef; + } + else + { // Favor BYREF over NATIVE_TYPE_LPSTRUCT + if (IsSigUseByref(Flags)) + { + bByRef = true; + --iByRef; + } + if (iByRef > 0) + { + nt = NATIVE_TYPE_LPSTRUCT; + --iByRef; + } + } + } + else // Pointer to Object or base type. + { + if (IsSigRet(Flags)) + { // [retval] so consume one indirection. + _ASSERTE(iByRef > 0); + --iByRef; + } + if (iByRef > 0 && IsSigUseByref(Flags)) + { + bByRef = true; + --iByRef; + } + } + break; + case SIG_ELEM: + // This case comes up when a property type is from a [retval]. + if (IsSigRet(Flags)) + { + if (iByRef > 0) + --iByRef; + } + break; + } + } + } // if (iByRef) + + //------------------------------------------------------------------------- + // We don't want any ET_PTR, so if there are any byref counts left, bail. + if (iByRef) + { + bConversionLoss = true; + tk = 0; + et = ELEMENT_TYPE_I; + nt = NATIVE_TYPE_NONE; + iByRef = 0; + bByRef = false; + } + + //------------------------------------------------------------------------- + // Build COM+ signature. + + // Type has been analyzed, and possibly modified. Emit the COM+ signature. + _ASSERTE(et != ELEMENT_TYPE_MAX); + _ASSERTE(et != ELEMENT_TYPE_END); + + // If it is a pointer to something, emit that now. + if (bByRef || iByRef) + { + // Size the array to hold the elements. + IfFailGo(qbSigBuf.ReSizeNoThrow(cbSig + CB_MAX_ELEMENT_TYPE * (iByRef+(bByRef?1:0)))); + pbSig = reinterpret_cast<BYTE*>(qbSigBuf.Ptr()); + + // Put in any leading "BYREF" + if (bByRef) + { + pbSig = reinterpret_cast<BYTE*>(qbSigBuf.Ptr()); + cb = CorSigCompressData(ELEMENT_TYPE_BYREF, &pbSig[cbSig]); + cbSig += cb; + } + + // Put in the "PTR"s. + while (iByRef-- > 0) + { + cb = CorSigCompressData(ELEMENT_TYPE_PTR, &pbSig[cbSig]); + cbSig += cb; + } + } + + // Emit the type. + IfFailGo(qbSigBuf.ReSizeNoThrow(cbSig + CB_MAX_ELEMENT_TYPE)); + pbSig = reinterpret_cast<BYTE*>(qbSigBuf.Ptr()); + cb = CorSigCompressData(et, &pbSig[cbSig]); + cbSig += cb; + + // Add the class type, the array information, etc. + switch (et) + { + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_VALUETYPE: + // Size the array to hold the token. + IfFailGo(qbSigBuf.ReSizeNoThrow(cbSig + CB_MAX_ELEMENT_TYPE)); + pbSig = reinterpret_cast<BYTE*>(qbSigBuf.Ptr()); + + // If the token hasn't been resolved yet, do that now. + if (tk == 0) + { + _ASSERTE(pITIUD); + IfFailGo(_GetTokenForTypeInfo(pITIUD, TRUE, &tk)); + } + cb = CorSigCompressToken(tk, reinterpret_cast<ULONG*>(&pbSig[cbSig])); + cbSig += cb; + break; + + case ELEMENT_TYPE_SZARRAY: + // map to SZARRAY <subtype> + IfFailGo(qbSigBuf.ReSizeNoThrow(cbSig + CB_MAX_ELEMENT_TYPE)); + pbSig = reinterpret_cast<BYTE*>(qbSigBuf.Ptr()); + // Recurse on the type. + IfFailGo(_ConvSignature(pITI, &pType->lpadesc->tdescElem, SIG_ELEM, qbSigBuf, cbSig, &cbSig, qbNestedNativeType, 0, &iNestedNativeTypeOfs, bNewEnumMember)); + if (hr == S_CONVERSION_LOSS) + bConversionLoss = true; + break; + + case VT_DISPATCH: + case VT_UNKNOWN: + default: + _ASSERTE(tk == 0); + // et, nt assigned above. + break; + } // switch (et) + + // Do any native type info. + if (nt != NATIVE_TYPE_NONE) + { + if (iNestedNativeTypeOfs > 0) + CorSigUncompressData(reinterpret_cast<PCCOR_SIGNATURE>(qbNestedNativeType.Ptr()), &nested); + + if (nt == NATIVE_TYPE_FIXEDARRAY) + { + IfFailGo(qbNativeTypeBuf.ReSizeNoThrow(cbNativeType + NATIVE_TYPE_MAX_CB * 2 + DWORD_MAX_CB)); + cbNativeType += CorSigCompressData(nt, &qbNativeTypeBuf[cbNativeType]); + cbNativeType += CorSigCompressData(cElems, &qbNativeTypeBuf[cbNativeType]); + if (nested == NATIVE_TYPE_BSTR || nested == NATIVE_TYPE_LPWSTR || nested == NATIVE_TYPE_LPSTR) + { // Use the nested type. + cbNativeType += CorSigCompressData(nested, &qbNativeTypeBuf[cbNativeType]); + } + else + { // Use a default sub type. + cbNativeType += CorSigCompressData(NATIVE_TYPE_MAX, &qbNativeTypeBuf[cbNativeType]); + } + } + else if (nt == NATIVE_TYPE_ARRAY) + { + IfFailGo(qbNativeTypeBuf.ReSizeNoThrow(cbNativeType + NATIVE_TYPE_MAX_CB * 2 + DWORD_MAX_CB * 2)); + cbNativeType += CorSigCompressData(nt, &qbNativeTypeBuf[cbNativeType]); + if (nested == NATIVE_TYPE_BSTR || nested == NATIVE_TYPE_LPWSTR || nested == NATIVE_TYPE_LPSTR) + { // Use the nested type. + cbNativeType += CorSigCompressData(nested, &qbNativeTypeBuf[cbNativeType]); + } + else + { // Use a default sub type. + cbNativeType += CorSigCompressData(NATIVE_TYPE_MAX, &qbNativeTypeBuf[cbNativeType]); + } + // Use zero for param index. + cbNativeType += CorSigCompressData(0, &qbNativeTypeBuf[cbNativeType]); + // Use count from typelib for elem count. + cbNativeType += CorSigCompressData(cElems, &qbNativeTypeBuf[cbNativeType]); + } + else if (nt == NATIVE_TYPE_SAFEARRAY) + { + BOOL bPtrArray = FALSE; + CQuickArray<WCHAR> rTemp; + CQuickArray<char> rTypeName; + LPUTF8 strTypeName = ""; + TYPEDESC *pTypeDesc = &pType->lpadesc->tdescElem; + VARTYPE ArrayElemVT = pTypeDesc->vt; + + if (ArrayElemVT == VT_PTR) + { + bPtrArray = TRUE; + pTypeDesc = pType->lpadesc->tdescElem.lptdesc; + ArrayElemVT = pTypeDesc->vt; + if ((ArrayElemVT != VT_USERDEFINED) && (ArrayElemVT != VT_VOID)) + { + // We do not support deep marshalling pointers. + ArrayElemVT = VT_INT; + bConversionLoss = TRUE; + } + } + + // If we are dealing with a safe array of user defined types and if we + // are importing safe array's as System.Array then add the SafeArrayUserDefSubType. + if (ArrayElemVT == VT_USERDEFINED) + { + // Resolve the alias to the ultimate aliased type. + IfFailGo(_ResolveTypeDescAlias(pITI, pTypeDesc, &pITIAlias, &psAttrAlias)); + + // If the type is a coclass then we need to retrieve the default interface and + // substitute it for the coclass. Look up on the resolved alias, because it is + // that class that has a default interface. + if (psAttrAlias->typekind == TKIND_COCLASS) + { + ITypeInfo *pDefaultItf = NULL; + hr = GetDefaultInterface(pITIAlias, &pDefaultItf); + if ((hr != S_OK) || !pDefaultItf) + { + hr = E_UNEXPECTED; + goto ErrExit; + } + + pITIUD = pDefaultItf; + } + else + { // USERDEFINED interface/record/union/enum. Retrieve the type + // info for the user defined type. Note: use the TKIND_ALIAS typeinfo + // itself for this conversion (not the aliased type) to preserve + // names, lib locations, etc. + IfFailGo(pITI->GetRefTypeInfo(pTypeDesc->hreftype, &pITIUD)); + } + + // pITIUD points to the typeinfo for which we'll create a signature. + IfFailGo(pITIUD->GetTypeAttr(&psAttr)); + + // Get the typeref name for the type. + for(;;) + { + int cchReq; + mdToken tkDummy; + IfFailGo(_GetTokenForTypeInfo(pITIUD, TRUE, &tkDummy, rTemp.Ptr(), (int)rTemp.MaxSize(), &cchReq, TRUE)); + if (cchReq <= (int)rTemp.MaxSize()) + break; + IfFailGo(rTemp.ReSizeNoThrow(cchReq)); + } + + // Convert the type name to UTF8. + ULONG cbReq = WszWideCharToMultiByte(CP_UTF8, 0, rTemp.Ptr(), -1, 0, 0, 0, 0); + IfFailGo(rTypeName.ReSizeNoThrow(cbReq + 1)); + WszWideCharToMultiByte(CP_UTF8, 0, rTemp.Ptr(), -1, rTypeName.Ptr(), cbReq, 0, 0); + + // Determine the safe array element VT. + switch (psAttrAlias->typekind) + { + case TKIND_RECORD: + case TKIND_ENUM: + case TKIND_UNION: + if (bPtrArray) + { + ArrayElemVT = VT_INT; + bConversionLoss = TRUE; + } + else + { + ArrayElemVT = psAttrAlias->typekind == TKIND_ENUM ? VT_I4 : VT_RECORD; + strTypeName = rTypeName.Ptr(); + } + break; + + case TKIND_INTERFACE: + case TKIND_DISPATCH: + case TKIND_COCLASS: + if (!bPtrArray) + { + ArrayElemVT = VT_INT; + bConversionLoss = TRUE; + } + else + { + if (IsIDispatchDerived(pITIUD, psAttr) == S_FALSE) + ArrayElemVT = VT_UNKNOWN; + else + ArrayElemVT = VT_DISPATCH; + strTypeName = rTypeName.Ptr(); + } + break; + } + + // If we are not converting the SAFEARRAY to a System.Array, then + // we don't need to encode the name of the user defined type. + if (!m_bSafeArrayAsSystemArray) + strTypeName = ""; + } + + // Make sure the native type buffer is large enough. + ULONG TypeNameStringLen = (ULONG)strlen(strTypeName); + IfFailGo(qbNativeTypeBuf.ReSizeNoThrow(cbNativeType + NATIVE_TYPE_MAX_CB * 2 + DWORD_MAX_CB + TypeNameStringLen + STRING_OVERHEAD_MAX_CB)); + + // Add the native type to the native type info. + cbNativeType += CorSigCompressData(nt, &qbNativeTypeBuf[cbNativeType]); + + // Add the VARTYPE of the array. + cbNativeType += CorSigCompressData(ArrayElemVT, &qbNativeTypeBuf[cbNativeType]); + + // Add the type name to the native type info. + BYTE *pNativeType = (BYTE*)CPackedLen::PutLength(&qbNativeTypeBuf[cbNativeType], TypeNameStringLen); + cbNativeType += (ULONG)(pNativeType - &qbNativeTypeBuf[cbNativeType]); + memcpy(&qbNativeTypeBuf[cbNativeType], strTypeName, TypeNameStringLen); + cbNativeType += TypeNameStringLen; + } + else if (nt == NATIVE_TYPE_CUSTOMMARSHALER) + { + // Calculate the length of each string and then the total length of the native type info. + ULONG MarshalerTypeNameStringLen = (ULONG)strlen(pConvertionInfo->m_strCustomMarshalerTypeName); + ULONG CookieStringLen = (ULONG)strlen(pConvertionInfo->m_strCookie); + ULONG TotalNativeTypeLen = MarshalerTypeNameStringLen + CookieStringLen; + BYTE *pNativeType = 0; + + // Make sure the native type buffer is large enough. + IfFailGo(qbNativeTypeBuf.ReSizeNoThrow(cbNativeType + NATIVE_TYPE_MAX_CB + TotalNativeTypeLen + STRING_OVERHEAD_MAX_CB * 4)); + + // Add the native type to the native type info. + cbNativeType += CorSigCompressData(nt, &qbNativeTypeBuf[cbNativeType]); + + // Add an empty string for the typelib guid. + pNativeType = (BYTE*)CPackedLen::PutLength(&qbNativeTypeBuf[cbNativeType], 0); + cbNativeType += (ULONG)(pNativeType - &qbNativeTypeBuf[cbNativeType]); + + // Add an empty string for the unmanaged type name. + pNativeType = (BYTE*)CPackedLen::PutLength(&qbNativeTypeBuf[cbNativeType], 0); + cbNativeType += (ULONG)(pNativeType - &qbNativeTypeBuf[cbNativeType]); + + // Add the name of the custom marshaler to the native type info. + pNativeType = (BYTE*)CPackedLen::PutLength(&qbNativeTypeBuf[cbNativeType], MarshalerTypeNameStringLen); + cbNativeType += (ULONG)(pNativeType - &qbNativeTypeBuf[cbNativeType]); + memcpy(&qbNativeTypeBuf[cbNativeType], pConvertionInfo->m_strCustomMarshalerTypeName, MarshalerTypeNameStringLen); + cbNativeType += MarshalerTypeNameStringLen; + + // Add the cookie to the native type info. + pNativeType = (BYTE*)CPackedLen::PutLength(&qbNativeTypeBuf[cbNativeType], CookieStringLen); + cbNativeType += (ULONG)(pNativeType - &qbNativeTypeBuf[cbNativeType]); + memcpy(&qbNativeTypeBuf[cbNativeType], pConvertionInfo->m_strCookie, CookieStringLen); + cbNativeType += CookieStringLen; + } + else + { + IfFailGo(qbNativeTypeBuf.ReSizeNoThrow(cbNativeType + NATIVE_TYPE_MAX_CB + 1)); + cbNativeType += CorSigCompressData(nt, &qbNativeTypeBuf[cbNativeType]); + } + } + + // Return the size of the native type to the caller. + *pcbNativeType = cbNativeType; + + // Return size to caller. + *pcbSig = cbSig; + + // If there was a conversion loss, change the return code. + if (bConversionLoss) + hr = S_CONVERSION_LOSS; + +ErrExit: + if (bstrNamespace) + ::SysFreeString(bstrNamespace); + if (bstrName) + ::SysFreeString(bstrName); + if(psAttrAlias) + pITIAlias->ReleaseTypeAttr(psAttrAlias); + if (pITIAlias) + pITIAlias->Release(); + if (psAttr) + pITIUD->ReleaseTypeAttr(psAttr); + if (pITIUD) + pITIUD->Release(); + if (pITLBUD) + pITLBUD->Release(); + + return hr; +} // HRESULT CImportTlb::_ConvSignature() +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +//***************************************************************************** +// Build a sorted list of functions to convert. (Sort by vtable offset.) +//***************************************************************************** +HRESULT CImportTlb::BuildMemberList( + ITypeInfo *pITI, // TypeInfo with functions. + int iStart, // First function to take. + int iEnd, // Last function to take. + BOOL bInheritsIEnum) // Inherits from IEnumerable. +{ + HRESULT hr; // A result. + int bNeedSort = false; // If true, need to sort the array. + int ix = 0; // Loop counter. + int oVftPrev = -1; // To see if oVft is increasing. + TYPEATTR *psAttr = 0; // TypeAttr for pITI. + FUNCDESC *psFunc; // A FUNCDESC. + LPWSTR pszName; // Working pointer for name. + BSTR bstrName=0; // Name from typelib. + ITypeInfo2 *pITI2=0; // To get custom attributes. + VARIANT vt; // Variant type. + BOOL bFunctionToGetter; // Did a given getter come from a managed function? + + ::VariantInit(&vt); + + IfFailGo(pITI->GetTypeAttr(&psAttr)); + pITI->QueryInterface(IID_ITypeInfo2, reinterpret_cast<void**>(&pITI2)); + + // Get the vars. + IfFailGo(m_MemberList.ReSizeNoThrow(psAttr->cVars + iEnd - iStart)); + memset(m_MemberList.Ptr(), 0, m_MemberList.Size()*sizeof(MemberInfo)); + for (ix=0; ix<psAttr->cVars; ++ix) + { + IfFailGo(pITI->GetVarDesc(ix, &(m_MemberList[ix].m_psVar))); + m_MemberList[ix].m_iMember = ix; + } + m_cMemberProps = psAttr->cVars; + + // Get the funcs. + for (; iStart<iEnd; ++iStart, ++ix) + { + IfFailGo(TryGetFuncDesc(pITI, iStart, &(m_MemberList[ix].m_psFunc))); + psFunc = m_MemberList[ix].m_psFunc; + if (psFunc->oVft < oVftPrev) + bNeedSort = true; + oVftPrev = psFunc->oVft; + m_MemberList[ix].m_iMember = iStart; + } + + if (bNeedSort) + { + class Sorter : public CQuickSort<MemberInfo> + { + typedef CImportTlb::MemberInfo MemberInfo; + public: + Sorter(MemberInfo *p, int n) : CQuickSort<MemberInfo>(p,n) {} + virtual int Compare(MemberInfo *p1, MemberInfo *p2) + { + if (p1->m_psFunc->oVft < p2->m_psFunc->oVft) + return -1; + if (p1->m_psFunc->oVft == p2->m_psFunc->oVft) + return 0; + return 1; + } + }; + Sorter sorter(m_MemberList.Ptr()+m_cMemberProps, (int)m_MemberList.Size()-m_cMemberProps); + sorter.Sort(); + // Check for duplicates. + oVftPrev = -1; + for (ix=m_cMemberProps; ix<(int)m_MemberList.Size(); ++ix) + { + if (m_MemberList[ix].m_psFunc->oVft == oVftPrev) + { + hr = TLBX_E_BAD_VTABLE; + break; + } + oVftPrev = m_MemberList[ix].m_psFunc->oVft; + } + } + + // Build the list of unique names. + m_pMemberNames = new (nothrow) CWCHARPool; + IfNullGo(m_pMemberNames); + + // Property names. No possibility of collisions. + for (ix=0; ix<m_cMemberProps; ++ix) + { + IfFailGo(pITI->GetDocumentation(m_MemberList[ix].m_psVar->memid, &bstrName, 0,0,0)); + IfNullGo(pszName = m_pMemberNames->Alloc((ULONG)wcslen(bstrName)+PROP_DECORATION_LEN+1)); + wcscpy_s(pszName, wcslen(bstrName)+PROP_DECORATION_LEN+1, PROP_DECORATION_GET); + wcscat_s(pszName, wcslen(bstrName)+PROP_DECORATION_LEN+1, bstrName); + m_MemberList[ix].m_pName = pszName; + if ((m_MemberList[ix].m_psVar->wVarFlags & VARFLAG_FREADONLY) == 0) + { + IfNullGo(pszName = m_pMemberNames->Alloc((ULONG)wcslen(bstrName)+PROP_DECORATION_LEN+1)); + wcscpy_s(pszName, wcslen(bstrName)+PROP_DECORATION_LEN+1, PROP_DECORATION_SET); + wcscat_s(pszName, wcslen(bstrName)+PROP_DECORATION_LEN+1, bstrName); + m_MemberList[ix].m_pName2 = pszName; + } + ::SysFreeString(bstrName); + bstrName = 0; + } + + // Function names. Because of get_/set_ decoration, collisions are possible. + for (ix=m_cMemberProps; ix<(int)m_MemberList.Size(); ++ix) + { + int bNewEnumMember = FALSE; + + // Build a name based on invkind. + psFunc = m_MemberList[ix].m_psFunc; + + // Unless we are doing the [out, retval] transformation for disp only interfaces, + // we need to clear the [retval] flag. + if (!m_bTransformDispRetVals) + { + if (psFunc->funckind == FUNC_DISPATCH) + { // If [RETVAL] is set, clear it. + for (int i=0; i<psFunc->cParams; ++i) + if ((psFunc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FRETVAL) != 0) + psFunc->lprgelemdescParam[i].paramdesc.wParamFlags &= ~PARAMFLAG_FRETVAL; + } + } + + BOOL bExplicitManagedName = FALSE; + if ( (!bNewEnumMember) && (!bInheritsIEnum) && (FuncIsNewEnum(pITI, psFunc, m_MemberList[ix].m_iMember) == S_OK) ) + { + // The member is the new enum member so set its name to GetEnumerator. + IfNullGo(bstrName = SysAllocString(GET_ENUMERATOR_MEMBER_NAME)); + bNewEnumMember = TRUE; + + // To prevent additional methods from implementing the NewEnum method, we mark the interface + bInheritsIEnum = TRUE; + } + else + { + // If the managed name custom value is set for this member, then use it. + if (pITI2) + { + hr = pITI2->GetFuncCustData(m_MemberList[ix].m_iMember, GUID_ManagedName, &vt); + if (hr == S_OK && vt.vt == VT_BSTR) + { + IfNullGo(bstrName = SysAllocString(vt.bstrVal)); + bExplicitManagedName = TRUE; + } + ::VariantClear(&vt); + } + + if (!bstrName) + IfFailGo(pITI->GetDocumentation(psFunc->memid, &bstrName, 0,0,0)); + } + + // If this is a property getter, see if it was originally a function. + bFunctionToGetter = FALSE; + if (psFunc->invkind == INVOKE_PROPERTYGET && pITI2) + { + hr = pITI2->GetFuncCustData(m_MemberList[ix].m_iMember, GUID_Function2Getter, &vt); + if (hr == S_OK && vt.vt == VT_I4 && vt.lVal == 1) + bFunctionToGetter = TRUE; + ::VariantClear(&vt); + } + + + // Check for the propget and propset custom attributes if this not already a property. + if ( (psFunc->invkind & (INVOKE_PROPERTYGET | INVOKE_PROPERTYPUT | INVOKE_PROPERTYPUTREF)) == 0 ) + { + INVOKEKIND ikind; + if (S_OK == _CheckForPropertyCustomAttributes(pITI, m_MemberList[ix].m_iMember, &ikind)) + psFunc->invkind = ikind; + } + + + // If this is a property accessor, but not the 'new enum member', and not + // originally from a managed function (that was exported as a getter), + // decorate the name appropriately. If the managed name was set explicitly by + // the Guid_ManagedName attribute, then don't try an decorate it. + ULONG nChars = 0; + if (!bExplicitManagedName && (psFunc->invkind & (INVOKE_PROPERTYGET | INVOKE_PROPERTYPUT | INVOKE_PROPERTYPUTREF) && !bNewEnumMember && !bFunctionToGetter)) + { + nChars = (ULONG)wcslen(bstrName)+PROP_DECORATION_LEN+1; + IfNullGo(pszName = m_pMemberNames->Alloc(nChars)); + + USHORT msSemantics=0; // Property's methodsemantics. + FUNCDESC *psF; // FUNCDESC of Get, Put, or PutRef. + TYPEDESC *pProperty; // TYPEDESC of property type. + BOOL bPropRetval; // Is the property type a [retval]? + IfFailGo(_GetFunctionPropertyInfo(psFunc, &msSemantics, &psF, &pProperty, &bPropRetval, FALSE, bstrName)); + + m_MemberList[ix].m_msSemantics = msSemantics; + switch(msSemantics) + { + case msGetter: + wcscpy_s(pszName, nChars, PROP_DECORATION_GET); + break; + case msSetter: + wcscpy_s(pszName, nChars, PROP_DECORATION_SET); + break; + case msOther: + wcscpy_s(pszName, nChars, PROP_DECORATION_LET); + break; + default: + _ASSERTE(msSemantics == 0); + *pszName = 0; + break; + } + wcscat_s(pszName, nChars, bstrName); + } + else + { + nChars = (ULONG)wcslen(bstrName)+1; + IfNullGo(pszName = m_pMemberNames->Alloc(nChars)); + wcscpy_s(pszName, nChars, bstrName); + } + + // Check for name collision, restore original name if collision occurs. + for (int index=0; index<ix; index++) + { + if ( (m_MemberList[index].m_pName) && (wcscmp(pszName, m_MemberList[index].m_pName) == 0) ) + { + wcscpy_s(pszName, nChars, bstrName); + m_MemberList[ix].m_msSemantics = 0; + } + } + + // Save the unique name. + m_MemberList[ix].m_pName = pszName; + ::SysFreeString(bstrName); + bstrName = 0; + } + +ErrExit: + if (pITI2) + pITI2->Release(); + if (psAttr) + pITI->ReleaseTypeAttr(psAttr); + if (bstrName) + ::SysFreeString(bstrName); + ::VariantClear(&vt); + return hr; +} // HRESULT CImportTlb::BuildMemberList() + +//***************************************************************************** +// Free the list built in BuildMemberList(). +//***************************************************************************** +HRESULT CImportTlb::FreeMemberList( + ITypeInfo *pITI) // TypeInfo with functions. +{ + int ix; // Loop control. + for (ix=0; ix<m_cMemberProps; ++ix) + pITI->ReleaseVarDesc(m_MemberList[ix].m_psVar); + m_cMemberProps = 0; + for (; ix<(int)m_MemberList.Size(); ++ix) + pITI->ReleaseFuncDesc(m_MemberList[ix].m_psFunc); + m_MemberList.Shrink(0); + if (m_pMemberNames) + { + delete m_pMemberNames; + m_pMemberNames = 0; + } + return S_OK; +} // HRESULT CImportTlb::FreeMemberList() + +//***************************************************************************** +// Set a GUID CustomAttribute on an object. +//***************************************************************************** +HRESULT CImportTlb::_AddGuidCa( // S_OK or error. + mdToken tkObj, // Object to be attributed. + REFGUID guid) // The GUID. +{ + HRESULT hr; // A result. + mdMemberRef mr; // MemberRef for GUID CA. + WCHAR wzGuid[40]; // Buffer for Guid, Unicode. + CHAR szGuid[40]; // Buffer for Guid, Ansi. + DECLARE_CUSTOM_ATTRIBUTE(40); + + // If GUID_NULL, don't store it. + if (guid == GUID_NULL) + return S_OK; + + // Get the GUID as a string. + // ----+----1----+----2----+----3----+----4 + // {12345678-1234-1234-1234-123456789012} + GuidToLPWSTR(guid, wzGuid, lengthof(wzGuid)); + _ASSERTE(wzGuid[37] == W('}')); + wzGuid[37] = W('\0'); + WszWideCharToMultiByte(CP_UTF8, 0, wzGuid+1,-1, szGuid,sizeof(szGuid), 0,0); + + // Put it in the Custom Attribute. + APPEND_STRING_TO_CUSTOM_ATTRIBUTE(szGuid); + + // Store the attribute + IfFailGo(GetAttrType(ATTR_GUID, &mr)); + FINISH_CUSTOM_ATTRIBUTE(); + IfFailGo(m_pEmit->DefineCustomAttribute(tkObj, mr, PTROF_CUSTOM_ATTRIBUTE(), SIZEOF_CUSTOM_ATTRIBUTE(), 0)); + +ErrExit: + return hr; +} // HRESULT CImportTlb::_AddGuidCa() + +//***************************************************************************** +// Add a default member as a custom attribute. +//***************************************************************************** +HRESULT CImportTlb::_AddDefaultMemberCa(// S_OK or error. + mdToken tkObj, // TypeDef with default member. + LPCWSTR wzName) // Name of the default member. +{ + // Only set once per typedef. + if (tkObj == m_tdHasDefault) + return S_OK; + m_tdHasDefault = tkObj; + + return _AddStringCa(ATTR_DEFAULTMEMBER, tkObj, wzName); +} // HRESULT CImportTlb::_AddDefaultMemberCa() + +//***************************************************************************** +// Add a string custom attribute of the given type to the token. +//***************************************************************************** +HRESULT CImportTlb::_AddStringCa( // S_OK or error. + int attr, // The type of the CA. + mdToken tk, // Token to add the CA to. + LPCWSTR wzString) // String to put in the CA. +{ + HRESULT hr = S_OK; // A result. + mdMemberRef mr; // MemberRef for DefaultMember CA. + BYTE *pca; // Pointer to custom attribute. + BYTE *ca; // Pointer to custom attribute. + int wzLen; // Length of wide string. + int len; // Length of the string. + CQuickArray<BYTE> buf; + + if (wzString == NULL) + { + hr = E_INVALIDARG; + goto ErrExit; + } + + // Prolog, up to 4 bytes length, string, epilog + wzLen = (int)wcslen(wzString); + len = WszWideCharToMultiByte(CP_UTF8,0, wzString, wzLen, 0,0, 0,0); + IfFailGo(buf.ReSizeNoThrow(2 + 4 + len + 2)); + ca = pca = buf.Ptr(); + + // Add prolog. + *reinterpret_cast<UNALIGNED USHORT*>(pca) = 1; + pca += sizeof(USHORT); + + // Add length. + pca = reinterpret_cast<BYTE*>(CPackedLen::PutLength(pca, len)); + + // Add string. + WszWideCharToMultiByte(CP_UTF8,0, wzString, wzLen, reinterpret_cast<char*>(pca), len, 0, 0); + pca += len; + + // Add epilog. + *reinterpret_cast<UNALIGNED USHORT*>(pca) = 0; + pca += sizeof(USHORT); + + // Store the attribute + IfFailGo(GetAttrType(attr, &mr)); + IfFailGo(m_pEmit->DefineCustomAttribute(tk, mr, ca, (ULONG)(pca-ca), 0)); + +ErrExit: + return hr; +} // HRESULT CImportTlb::_AddStringCa() + +//***************************************************************************** +// Add a referenced typelib to the list of referenced typelibs. Check if +// it is "this" typelib first. +//***************************************************************************** +HRESULT CImportTlb::_AddTlbRef( // S_OK or error. + ITypeLib *pITLB, // The referenced typelib. + mdAssemblyRef *par, // The AssemblyRef in this module. + BSTR *pwzNamespace, // The namespace contained in the resolved assembly. + BSTR *pwzAsmName, // The name of the resolved assembly. + CImpTlbDefItfToClassItfMap **ppDefItfToClassItfMap) // The default interface to class interface map. +{ + HRESULT hr = S_OK; // A result. + IUnknown *pIUnk=0; // IUnknown for external assembly. + mdAssemblyRef ar=0; // Assembly ref in the module containing the typeref. + ITypeLib2 *pITLB2=0; // To get custom attributes. + VARIANT vt; // Variant type. + Assembly* ResolvedAssembly=0; // The resolved assembly. + CImpTlbDefItfToClassItfMap *pDefItfToClassItfMap; // Temp def itf to class itf map. + + // Validate the arguments. + _ASSERTE(pITLB && par && pwzNamespace && pwzAsmName); + + // Initialize the out parameters to NULL. + *par = mdTokenNil; + *pwzNamespace = NULL; + *pwzAsmName = NULL; + if (ppDefItfToClassItfMap) + *ppDefItfToClassItfMap = NULL; + + ::VariantInit(&vt); + + // If not the importing typelib, add it to the list. + if (pITLB == m_pITLB) + { // Not an external assembly. + //*par = mdAssemblyRefNil; + *par = TokenFromRid(1, mdtModule); + IfNullGo(*pwzNamespace = SysAllocStringLen(m_wzNamespace, SysStringLen(m_wzNamespace))); + *pwzAsmName = NULL; + if (ppDefItfToClassItfMap) + *ppDefItfToClassItfMap = &m_DefItfToClassItfMap; + return S_OK; + } + + // If already resolved, just return assembly ref. + if (m_LibRefs.Find(pITLB, par, pwzNamespace, pwzAsmName, NULL, ppDefItfToClassItfMap)) + return S_OK; + + // See if the typelib was exported, in which case it already has assembly ref information. + if (pITLB->QueryInterface(IID_ITypeLib2, reinterpret_cast<void**>(&pITLB2)) == S_OK) + { + hr = pITLB2->GetCustData(GUID_ExportedFromComPlus, &vt); + if (vt.vt == VT_BSTR) + { + // Use the CA data to get a reference. + //CQuickArray<BYTE> rBuf; + //int iLen; + // The buffer should have been converted with CP_ACP, and should convert back directly. + //IfFailGo(rBuf.ReSizeNoThrow(iLen=::SysStringLen(vt.bstrVal))); + //if (iLen=WszWideCharToMultiByte(CP_ACP,0, vt.bstrVal,iLen, (char*)rBuf.Ptr(),iLen, 0,0)) + { + // Define the assembly ref for the exported assembly. + //ar = DefineAssemblyRefForExportedAssembly(rBuf.Ptr(),(DWORD)rBuf.Size(), m_pEmit); + ar = DefineAssemblyRefForExportedAssembly(vt.bstrVal, m_pEmit); + + // Retrieve the namespace from the typelib. + IfFailGo(GetNamespaceNameForTypeLib(pITLB, pwzNamespace)); + + // Set the assembly name. + IfNullGo(*pwzAsmName = SysAllocStringLen(vt.bstrVal, SysStringLen(vt.bstrVal))); + } + } + } + + // If it wasn't directly converted to a reference, callback to the resolver. + if (IsNilToken(ar)) + { + // Get the assembly for that typelib. + if (FAILED(m_Notify->ResolveRef(pITLB, &pIUnk))) + IfFailGo(TLBX_I_RESOLVEREFFAILED); + + // If a NULL assembly was returned, then stop converting the type but + // continue the import. + if (pIUnk == NULL) + IfFailGo(TLBX_E_INVALID_TYPEINFO); + + // Create an assembly ref in local assembly for referenced assembly. + ar = DefineAssemblyRefForImportedTypeLib(m_pAssembly, m_pModule, m_pEmit, pIUnk, pwzNamespace, pwzAsmName, &ResolvedAssembly); + } + + // Make sure the ref was resolved before adding to cache. + if (IsNilToken(ar)) + IfFailGo(TLBX_I_RESOLVEREFFAILED); + + // Add the TLB to the list of references. + IfFailGo(m_LibRefs.Add(pITLB, this, ar, *pwzNamespace, *pwzAsmName, ResolvedAssembly, &pDefItfToClassItfMap)); + + // Set the output parameters. + *par = ar; + if (ppDefItfToClassItfMap) + *ppDefItfToClassItfMap = pDefItfToClassItfMap; + +ErrExit: + if (FAILED(hr)) + { + if (*pwzNamespace) + { + SysFreeString(*pwzNamespace); + *pwzNamespace = NULL; + } + if (*pwzAsmName) + { + SysFreeString(*pwzAsmName); + *pwzAsmName = NULL; + } + } + if (pIUnk) + pIUnk->Release(); + if (pITLB2) + pITLB2->Release(); + VariantClear(&vt); + + return hr; +} // HRESULT CImportTlb::_AddTlbRef() + +//***************************************************************************** +// Error reporting helper. +//***************************************************************************** +HRESULT CImportTlb::ReportEvent( // Returns the original HR. + int ev, // The event kind. + int hrRpt, // HR. + ...) // Variable args. +{ + HRESULT hr; // A result. + va_list marker; // User text. + BSTR bstrBuf=0; // BSTR for bufferrr. + BSTR bstrMsg=0; // BSTR for message. + const int iSize = 1024; // Message size; + + // We need a BSTR anyway for the call to ReportEvent, so just allocate a + // big one for the buffer. + IfNullGo(bstrBuf = ::SysAllocStringLen(0, iSize)); + + // Format the message. + va_start(marker, hrRpt); + hr = FormatRuntimeErrorVa(bstrBuf, iSize, hrRpt, marker); + va_end(marker); + + // Display it. + IfNullGo(bstrMsg = ::SysAllocString(bstrBuf)); + m_Notify->ReportEvent(static_cast<ImporterEventKind>(ev), hrRpt, bstrMsg); + +ErrExit: + // Clean up. + if (bstrBuf) + ::SysFreeString(bstrBuf); + if (bstrMsg) + ::SysFreeString(bstrMsg); + return hrRpt; +} // HRESULT CImportTlb::ReportEvent() + +//***************************************************************************** +// Helper function to perform the shared functions of creating a TypeRef. +//***************************************************************************** +HRESULT CImpTlbTypeRef::DefineTypeRef( // S_OK or error. + IMetaDataEmit *pEmit, // Emit interface. + mdAssemblyRef ar, // The system assemblyref. + const LPCWSTR szURL, // URL of the TypeDef, wide chars. + mdTypeRef *ptr) // Put mdTypeRef here +{ + HRESULT hr = S_OK; // A result. + LPCWSTR szLookup; // The name to look up. + mdToken tkNester; // Token of enclosing class. + + // If the name contains a '+', this is a nested type. The first part becomes + // the resolution scope for the part after the '+'. + szLookup = wcsrchr(szURL, NESTED_SEPARATOR_WCHAR); + if (szLookup) + { + CQuickArray<WCHAR> qbName; + IfFailGo(qbName.ReSizeNoThrow(szLookup - szURL + 1)); + wcsncpy_s(qbName.Ptr(), (szLookup - szURL + 1), szURL, szLookup - szURL); + IfFailGo(DefineTypeRef(pEmit, ar, qbName.Ptr(), &tkNester)); + ar = tkNester; + ++szLookup; + } + else + szLookup = szURL; + + // Look for the item in the map. + CImpTlbTypeRef::TokenOfTypeRefHashKey sSearch, *pMapped; + + sSearch.tkResolutionScope = ar; + sSearch.szName = szLookup; + pMapped = m_Map.Find(&sSearch); + + if (pMapped) + { + *ptr = pMapped->tr; + goto ErrExit; + } + + // Wasn't found, create a new one and add to the map. + hr = pEmit->DefineTypeRefByName(ar, szLookup, ptr); + if (SUCCEEDED(hr)) + { + sSearch.tr = *ptr; + pMapped = m_Map.Add(&sSearch); + IfNullGo(pMapped); + } + +ErrExit: + return (hr); +} // HRESULT CImpTlbTypeRef::DefineTypeRef() + +//***************************************************************************** +// Free the held typelibs in the list of imported typelibs. +//***************************************************************************** +CImpTlbLibRef::~CImpTlbLibRef() +{ + for (ULONG i = 0; i < Size(); i++) + { + SysFreeString(operator[](i).szNameSpace); + delete operator[](i).pDefItfToClassItfMap; + } +} // CImpTlbLibRef::~CImpTlbLibRef() + +//***************************************************************************** +// Add a new typelib reference to the list. +//***************************************************************************** +HRESULT CImpTlbLibRef::Add( + ITypeLib *pITLB, + CImportTlb *pImporter, + mdAssemblyRef ar, + BSTR wzNamespace, + BSTR wzAsmName, + Assembly* assm, + CImpTlbDefItfToClassItfMap **ppMap) +{ + HRESULT hr = S_OK; // A result. + TLIBATTR *pAttr=0; // A typelib attribute. + ULONG i; // Index. + CTlbRef *pTlbRef=0; // A pointer to the TlbRef struct. + CImpTlbDefItfToClassItfMap *pDefItfToClassItfMap = NULL; // ptr to the default interface to class interface map. + + // Validate the arguments. + _ASSERTE(wzNamespace); + _ASSERTE(wzAsmName); + + IfFailGo(pITLB->GetLibAttr(&pAttr)); + +#if defined(_DEBUG) + for (i=0; i<Size(); ++i) + { + if (operator[](i).guid == pAttr->guid) + { + _ASSERTE(!"External TypeLib already referenced"); + goto ErrExit; + } + } +#else + i = (ULONG)Size(); +#endif + + // Allocate and initialize the default interface to class interface map. + pDefItfToClassItfMap = new (nothrow) CImpTlbDefItfToClassItfMap(); + IfNullGo(pDefItfToClassItfMap); + IfFailGo(pDefItfToClassItfMap->Init(pITLB, wzNamespace)); + + // Attemp to resize the array. + IfFailGo(ReSizeNoThrow(i+1)); + pTlbRef = &operator[](i); + pTlbRef->guid = pAttr->guid; + pTlbRef->ar = ar; + IfNullGo(pTlbRef->szNameSpace = SysAllocString(wzNamespace)); + IfNullGo(pTlbRef->szAsmName = SysAllocString(wzAsmName)); + pTlbRef->pDefItfToClassItfMap = pDefItfToClassItfMap; + pTlbRef->Asm = assm; + +ErrExit: + if (pAttr) + pITLB->ReleaseTLibAttr(pAttr); + if (FAILED(hr)) + { + if (pTlbRef && pTlbRef->szNameSpace) + SysFreeString(pTlbRef->szNameSpace); + if (pTlbRef && pTlbRef->szAsmName) + SysFreeString(pTlbRef->szAsmName); + delete pDefItfToClassItfMap; + } + else + { + *ppMap = pDefItfToClassItfMap; + } + + return hr; +} // void CImpTlbLibRef::Add() + +//***************************************************************************** +// Find an existing typelib reference. +//***************************************************************************** +int CImpTlbLibRef::Find( + ITypeLib *pITLB, + mdAssemblyRef *par, + BSTR *pwzNamespace, + BSTR *pwzAsmName, + Assembly** assm, + CImpTlbDefItfToClassItfMap **ppDefItfToClassItfMap) +{ + HRESULT hr; // A result. + TLIBATTR *pAttr=0; // A typelib attribute. + int rslt = FALSE; // Return result. + ULONG i; // Loop control. + + _ASSERTE(pwzNamespace); + _ASSERTE(pwzAsmName); + + // Initalize the out parameters to NULL. + *pwzNamespace = NULL; + *pwzAsmName = NULL; + + if (assm) + *assm = NULL; + + IfFailGo(pITLB->GetLibAttr(&pAttr)); + + for (i=0; i<Size(); ++i) + { + if (operator[](i).guid == pAttr->guid) + { + *par = operator[](i).ar; + IfNullGo(*pwzNamespace = SysAllocString(operator[](i).szNameSpace)); + IfNullGo(*pwzAsmName = SysAllocString(operator[](i).szAsmName)); + if (ppDefItfToClassItfMap) + *ppDefItfToClassItfMap = operator[](i).pDefItfToClassItfMap; + if (assm) + *assm = operator[](i).Asm; + rslt = TRUE; + goto ErrExit; + } + } + +ErrExit: + if (FAILED(hr)) + { + if (*pwzNamespace) + SysFreeString(*pwzNamespace); + if (*pwzAsmName) + SysFreeString(*pwzAsmName); + } + if (pAttr) + pITLB->ReleaseTLibAttr(pAttr); + return rslt; +} // void CImpTlbLibRef::Find() + +//***************************************************************************** +// unpack variant to an ELEMENT_TYPE_* plus a blob value +// If VT_BOOL, it is a two-byte value. +//***************************************************************************** +HRESULT _UnpackVariantToConstantBlob(VARIANT *pvar, BYTE *pcvType, void **pvValue, __int64 *pd) +{ + HRESULT hr = NOERROR; + + switch (pvar->vt) + { + case VT_BOOL: + *pcvType = ELEMENT_TYPE_BOOLEAN; + *((VARIANT_BOOL **)pvValue) = &(pvar->boolVal); + break; + case VT_I1: + *pcvType = ELEMENT_TYPE_I1; + *((CHAR **)pvValue) = &(pvar->cVal); + break; + case VT_UI1: + *pcvType = ELEMENT_TYPE_U1; + *((BYTE **)pvValue) = &(pvar->bVal); + break; + case VT_I2: + *pcvType = ELEMENT_TYPE_I2; + *((SHORT **)pvValue) = &(pvar->iVal); + break; + case VT_UI2: + *pcvType = ELEMENT_TYPE_U2; + *((USHORT **)pvValue) = &(pvar->uiVal); + break; + case VT_I4: + case VT_INT: + *pcvType = ELEMENT_TYPE_I4; + *((LONG **)pvValue) = &(pvar->lVal); + break; + case VT_UI4: + case VT_UINT: + *pcvType = ELEMENT_TYPE_U4; + *((ULONG **)pvValue) = &(pvar->ulVal); + break; + case VT_R4: + *pcvType = ELEMENT_TYPE_R4; + *((float **)pvValue) = &(pvar->fltVal); + break; + case VT_I8: + *pcvType = ELEMENT_TYPE_I8; + *((LONGLONG **)pvValue) = &(pvar->cyVal.int64); + break; + case VT_R8: + *pcvType = ELEMENT_TYPE_R8; + *((double **)pvValue) = &(pvar->dblVal); + break; + case VT_BSTR: + *pcvType = ELEMENT_TYPE_STRING; + *((BSTR *)pvValue) = pvar->bstrVal; + break; + + case VT_DATE: + *pcvType = ELEMENT_TYPE_I8; + *pd = _DoubleDateToTicks(pvar->date); + *((LONGLONG **)pvValue) = pd; + break; + case VT_UNKNOWN: + case VT_DISPATCH: + *pcvType = ELEMENT_TYPE_CLASS; + _ASSERTE(pvar->punkVal == NULL); + *((IUnknown ***)pvValue) = &(pvar->punkVal); + break; + default: + _ASSERTE(!"Not a valid type to specify default value!"); + IfFailGo( META_E_BAD_INPUT_PARAMETER ); + break; + } +ErrExit: + return hr; +} // HRESULT _UnpackVariantToConstantBlob() + +//***************************************************************************** +// Stolen from classlib. +//***************************************************************************** +INT64 _DoubleDateToTicks(const double d) +{ + const INT64 MillisPerSecond = 1000; + const INT64 MillisPerDay = MillisPerSecond * 60 * 60 * 24; + const INT64 TicksPerMillisecond = 10000; + const INT64 TicksPerSecond = TicksPerMillisecond * 1000; + const INT64 TicksPerMinute = TicksPerSecond * 60; + const INT64 TicksPerHour = TicksPerMinute * 60; + const INT64 TicksPerDay = TicksPerHour * 24; + const int DaysPer4Years = 365 * 4 + 1; + const int DaysPer100Years = DaysPer4Years * 25 - 1; + const int DaysPer400Years = DaysPer100Years * 4 + 1; + const int DaysTo1899 = DaysPer400Years * 4 + DaysPer100Years * 3 - 367; + const INT64 DoubleDateOffset = DaysTo1899 * TicksPerDay; + const int DaysTo10000 = DaysPer400Years * 25 - 366; + const INT64 MaxMillis = DaysTo10000 * MillisPerDay; + + INT64 millis = (INT64)(d * MillisPerDay + (d >= 0? 0.5: -0.5)); + if (millis < 0) millis -= (millis % MillisPerDay) * 2; + millis += DoubleDateOffset / TicksPerMillisecond; + if (millis < 0 || millis >= MaxMillis) { + return 0; + } + return millis * TicksPerMillisecond; +} // INT64 _DoubleDateToTicks() + + +//***************************************************************************** +// Wrapper for GetFuncDesc to catch errors. +//***************************************************************************** +static HRESULT TryGetFuncDesc( // S_OK or error. + ITypeInfo *pITI, // ITypeInfo with function. + int i, // Function index. + FUNCDESC **ppFunc) // Put FUNCDESC here. +{ + HRESULT hr; // A return code. + __try + { + hr = pITI->GetFuncDesc(i, ppFunc); + } + __except(1) + { + hr = PostError(TLBX_E_TLB_EXCEPTION, _exception_code()); + } + + return hr; +} // static HRESULT TryGetFuncDesc() + +//***************************************************************************** +// Implementation of a hashed ResolutionScope+Name to TypeRef map. +//***************************************************************************** +void CImpTlbTypeRef::CTokenOfTypeRefHash::Clear() +{ +#if defined(_DEBUG) + // printf("Name to TypeRef cache: %d buckets, %d used, %d collisions\n", Buckets(), Count(), Collisions()); +#endif + CClosedHash<class TokenOfTypeRefHashKey>::Clear(); +} // void CImpTlbTypeRef::CTokenOfTypeRefHash::Clear() + +unsigned int CImpTlbTypeRef::CTokenOfTypeRefHash::Hash(const TokenOfTypeRefHashKey *pData) +{ + // Starting value for hash. + ULONG hash = 5381; + + // Hash in the resolution scope token. + const BYTE *pbData = reinterpret_cast<const BYTE *>(&pData->tkResolutionScope); + int iSize = 4; + while (--iSize >= 0) + { + hash = ((hash << 5) + hash) ^ *pbData; + ++pbData; + } + + // Hash in the typeref name. + LPCWSTR szStr = pData->szName; + int c; + while ((c = *szStr) != 0) + { + hash = ((hash << 5) + hash) ^ c; + ++szStr; + } + + return hash; +} // unsigned int CImpTlbTypeRef::CTokenOfTypeRefHash::Hash() + +unsigned int CImpTlbTypeRef::CTokenOfTypeRefHash::Compare(const TokenOfTypeRefHashKey *p1, TokenOfTypeRefHashKey *p2) +{ + // Resolution scopes are fast to compare. + if (p1->tkResolutionScope < p2->tkResolutionScope) + return -1; + if (p1->tkResolutionScope > p2->tkResolutionScope) + return 1; + // But if they are the same, compare the names. + return wcscmp(p1->szName, p2->szName); +} // unsigned int CImpTlbTypeRef::CTokenOfTypeRefHash::Compare() + +CImpTlbTypeRef::CTokenOfTypeRefHash::ELEMENTSTATUS CImpTlbTypeRef::CTokenOfTypeRefHash::Status(TokenOfTypeRefHashKey *p) +{ + if (p->tkResolutionScope == static_cast<mdToken>(FREE)) + return (FREE); + if (p->tkResolutionScope == static_cast<mdToken>(DELETED)) + return (DELETED); + return (USED); +} // CImpTlbTypeRef::CTokenOfTypeRefHash::ELEMENTSTATUS CImpTlbTypeRef::CTokenOfTypeRefHash::Status() + +void CImpTlbTypeRef::CTokenOfTypeRefHash::SetStatus(TokenOfTypeRefHashKey *p, ELEMENTSTATUS s) +{ + p->tkResolutionScope = static_cast<mdToken>(s); +} // void CImpTlbTypeRef::CTokenOfTypeRefHash::SetStatus() + +void *CImpTlbTypeRef::CTokenOfTypeRefHash::GetKey(TokenOfTypeRefHashKey *p) +{ + return p; +} // void *CImpTlbTypeRef::CTokenOfTypeRefHash::GetKey() + +CImpTlbTypeRef::TokenOfTypeRefHashKey* CImpTlbTypeRef::CTokenOfTypeRefHash::Add(const TokenOfTypeRefHashKey *pData) +{ + LPWSTR pName; + const void *pvData = pData; + TokenOfTypeRefHashKey *pNew = Super::Add(const_cast<void*>(pvData)); + if (pNew == 0) + return 0; + pNew->szName = pName = m_Names.Alloc((ULONG)wcslen(pData->szName)+1); + if (pNew->szName == 0) + return 0; + wcscpy_s(pName, wcslen(pData->szName)+1, pData->szName); + pNew->tkResolutionScope = pData->tkResolutionScope; + pNew->tr = pData->tr; + + return pNew; +} // TokenOfTypeRefHashKey* CImpTlbTypeRef::CTokenOfTypeRefHash::Add() + +//***************************************************************************** +// Implementation of a hashed ITypeInfo * source interface to event information +// map. +//***************************************************************************** +HRESULT CImpTlbEventInfoMap::AddEventInfo(LPCWSTR szSrcItfName, mdTypeRef trEventItf, LPCWSTR szEventItfName, LPCWSTR szEventProviderName, Assembly* SrcItfAssembly) +{ + ImpTlbEventInfo sNew; + sNew.szSrcItfName = szSrcItfName; + sNew.trEventItf = trEventItf; + sNew.szEventItfName = szEventItfName; + sNew.szEventProviderName = szEventProviderName; + sNew.SrcItfAssembly = SrcItfAssembly; + return Add(&sNew) != NULL ? S_OK : E_OUTOFMEMORY; +} // BOOL CImpTlbEventInfoMap::AddEventInfo() + +ImpTlbEventInfo *CImpTlbEventInfoMap::FindEventInfo(LPCWSTR szSrcItfName) +{ + ImpTlbEventInfo sSearch, *pMapped; + sSearch.szSrcItfName = szSrcItfName; + pMapped = Find(&sSearch); + return pMapped; +} // ImpTlbEventInfo *CImpTlbEventInfoMap::FindEventInfo() + +HRESULT CImpTlbEventInfoMap::GetEventInfoList(CQuickArray<ImpTlbEventInfo*> &qbEvInfoList) +{ + HRESULT hr = S_OK; + int cCurrEvInfo = 0; + + // Resise the event info list. + IfFailGo(qbEvInfoList.ReSizeNoThrow(Count())); + + // Retrieve the first event info. + ImpTlbEventInfo *pEvInfo = GetFirst(); + + // Add all the event info's to the list. + while (pEvInfo) + { + qbEvInfoList[cCurrEvInfo++] = pEvInfo; + pEvInfo = GetNext(pEvInfo); + } + +ErrExit: + return hr; +} // HRESULT CImpTlbEventInfoMap::GetEventInfoList() + +unsigned int CImpTlbEventInfoMap::Hash(const ImpTlbEventInfo *pData) +{ + // Starting value for hash. + ULONG hash = 5381; + + // Hash in the source interface name. + LPCWSTR szStr = pData->szSrcItfName; + int c; + while ((c = *szStr) != 0) + { + hash = ((hash << 5) + hash) ^ c; + ++szStr; + } + + return hash; +} // unsigned int CImpTlbEventInfoMap::Hash() + +unsigned int CImpTlbEventInfoMap::Compare(const ImpTlbEventInfo *p1, ImpTlbEventInfo *p2) +{ + // Compare the source interface names. + return wcscmp(p1->szSrcItfName, p2->szSrcItfName); +} // unsigned int CImpTlbEventInfoMap::Compare() + +CImpTlbEventInfoMap::ELEMENTSTATUS CImpTlbEventInfoMap::Status(ImpTlbEventInfo *p) +{ + if (p->szSrcItfName == reinterpret_cast<LPCWSTR>(FREE)) + return (FREE); + if (p->szSrcItfName == reinterpret_cast<LPCWSTR>(DELETED)) + return (DELETED); + return (USED); +} // CImpTlbEventInfoMap::ELEMENTSTATUS CImpTlbEventInfoMap::Status() + +void CImpTlbEventInfoMap::SetStatus(ImpTlbEventInfo *p, ELEMENTSTATUS s) +{ + p->szSrcItfName = reinterpret_cast<LPCWSTR>(s); +} // void CImpTlbEventInfoMap::SetStatus() + +void *CImpTlbEventInfoMap::GetKey(ImpTlbEventInfo *p) +{ + return p; +} // void *CImpTlbEventInfoMap::GetKey() + +ImpTlbEventInfo* CImpTlbEventInfoMap::Add(const ImpTlbEventInfo *pData) +{ + // Add the new entry to the map. + const void *pvData = pData; + ImpTlbEventInfo *pNew = Super::Add(const_cast<void*>(pvData)); + if (pNew == 0) + return 0; + + // Copy the source interface name. + pNew->szSrcItfName = m_Names.Alloc((ULONG)wcslen(pData->szSrcItfName)+1); + if (pNew->szSrcItfName == 0) + return 0; + wcscpy_s((LPWSTR)pNew->szSrcItfName, wcslen(pData->szSrcItfName)+1, pData->szSrcItfName); + + // Copy the event interface type def. + pNew->trEventItf = pData->trEventItf; + + // Copy the event interface name. + pNew->szEventItfName = m_Names.Alloc((ULONG)wcslen(pData->szEventItfName)+1); + if (pNew->szEventItfName == 0) + return 0; + wcscpy_s((LPWSTR)pNew->szEventItfName, wcslen(pData->szEventItfName)+1, pData->szEventItfName); + + // Copy the event provider name. + pNew->szEventProviderName = m_Names.Alloc((ULONG)wcslen(pData->szEventProviderName)+1); + if (pNew->szEventProviderName == 0) + return 0; + wcscpy_s((LPWSTR)pNew->szEventProviderName, wcslen(pData->szEventProviderName)+1, pData->szEventProviderName); + + // Copy the Source Interface Assembly pointer + pNew->SrcItfAssembly = pData->SrcItfAssembly; + + // Return the new entry. + return pNew; +} // ImpTlbEventInfo* CImpTlbEventInfoMap::Add() + +CImpTlbDefItfToClassItfMap::CImpTlbDefItfToClassItfMap() +: CClosedHash<class ImpTlbClassItfInfo>(101) +, m_bstrNameSpace(NULL) +{ +} + +CImpTlbDefItfToClassItfMap::~CImpTlbDefItfToClassItfMap() +{ + Clear(); + if (m_bstrNameSpace) + { + ::SysFreeString(m_bstrNameSpace); + m_bstrNameSpace = NULL; + } +} + +HRESULT CImpTlbDefItfToClassItfMap::Init(ITypeLib *pTlb, BSTR bstrNameSpace) +{ + HRESULT hr; // A result. + int cTi; // Count of TypeInfos. + int i; // Loop control. + TYPEATTR *psAttr=0; // TYPEATTR for the ITypeInfo. + TYPEATTR *psDefItfAttr=0; // TYPEATTR for the default interface. + ITypeInfo *pITI=0; // The ITypeInfo. + ITypeInfo *pDefItfITI=0; // The ITypeInfo for the default interface. + + // Save the namespace. + IfNullGo(m_bstrNameSpace = SysAllocString(bstrNameSpace)); + + // How many TypeInfos? + IfFailGo(cTi = pTlb->GetTypeInfoCount()); + + // Iterate over them. + for (i = 0; i < cTi; ++i) + { + // Get the TypeInfo. + hr = pTlb->GetTypeInfo(i, &pITI); + if (SUCCEEDED(hr)) + { + // Retrieve the attributes of the type info. + IfFailGo(pITI->GetTypeAttr(&psAttr)); + + // If we are dealing with a CoClass, then set up the default interface to + // class interface mapping. + if (psAttr->typekind == TKIND_COCLASS) + IfFailGo(AddCoClassInterfaces(pITI, psAttr)); + + // Release for next TypeInfo. + if (psAttr) + { + pITI->ReleaseTypeAttr(psAttr); + psAttr = 0; + } + if (pITI) + { + pITI->Release(); + pITI = 0; + } + } + } + +ErrExit: + if (psAttr) + pITI->ReleaseTypeAttr(psAttr); + if (pITI) + pITI->Release(); + + return (hr); +} + +HRESULT CImpTlbDefItfToClassItfMap::AddCoClassInterfaces(ITypeInfo *pCoClassITI, TYPEATTR *pCoClassTypeAttr) +{ + HRESULT hr; // A result + HREFTYPE href; // HREFTYPE of an implemented interface. + INT ImplFlags; // ImplType flags. + int NumInterfaces; // The number of interfaces on the coclass. + int i; // A counter. + ITypeInfo *pItfITI=0; // The ITypeInfo for the current interface. + ITypeInfo *pBaseItfITI=0; // The ITypeInfo for the base interface. + TYPEATTR *psItfAttr=0; // TYPEATTR for the interface. + BSTR bstrClassItfName=0; // The name of the class interface. + + // Retrieve the name of the CoClass. + IfFailGo(GetManagedNameForTypeInfo(pCoClassITI, m_bstrNameSpace, NULL, &bstrClassItfName)); + + // Retrieve the default interface for the CoClass. + IfFailGo(CImportTlb::GetDefaultInterface(pCoClassITI, &pItfITI)); + + // If there is a default interface, then add it to the map. + if (hr == S_OK) + { + // Retrieve the attributes of the default interface type info. + IfFailGo(pItfITI->GetTypeAttr(&psItfAttr)); + + // If there already is a CoClass that implements this + // interface then we do not want to do the mapping. + ImpTlbClassItfInfo sSearch, *pMapped; + sSearch.ItfIID = psItfAttr->guid; + pMapped = Find(&sSearch); + if (pMapped) + { + // There already is a CoClass that implements the interface so + // we set the class itf name to NULL to indicate not to do the def + // itf to class itf convertion for this interface. + pMapped->szClassItfName = NULL; + } + else + { + // Unless the default interface is IUnknown or IDispatch, add the + // def itf to class itf entry to the map. + if (psItfAttr->guid != IID_IUnknown && psItfAttr->guid != IID_IDispatch) + { + ImpTlbClassItfInfo sNew; + sNew.ItfIID = psItfAttr->guid; + sNew.szClassItfName = bstrClassItfName; + IfNullGo(Add(&sNew)); + } + } + + // Release for next interface. + pItfITI->ReleaseTypeAttr(psItfAttr); + psItfAttr = 0; + pItfITI->Release(); + pItfITI = 0; + } + + // Retrieve the number of interfaces the coclass has + NumInterfaces = pCoClassTypeAttr->cImplTypes; + + // Go through all the interfaces and add them to the map. + for (i=0; i < NumInterfaces; i++) + { + // Get the impl flags. + IfFailGo(pCoClassITI->GetImplTypeFlags(i, &ImplFlags)); + + // If this is an implemented interface. + if (!(ImplFlags & IMPLTYPEFLAG_FSOURCE)) + { + IfFailGo(pCoClassITI->GetRefTypeOfImplType(i, &href)); + IfFailGo(pCoClassITI->GetRefTypeInfo(href, &pItfITI)); + + do + { + // Retrieve the attributes of the interface type info. + IfFailGo(pItfITI->GetTypeAttr(&psItfAttr)); + + // If there already is a CoClass that implements this + // interface then we do not want to do the mapping. + ImpTlbClassItfInfo sSearch, *pMapped; + sSearch.ItfIID = psItfAttr->guid; + pMapped = Find(&sSearch); + if (pMapped) + { + // There already is a CoClass that implements the interface. If that + // CoClass is not the current one, then we we set the class itf name + // to NULL to indicate not to do the def itf to class itf convertion + // for this interface. + if (pMapped->szClassItfName && wcscmp(pMapped->szClassItfName, bstrClassItfName) != 0) + pMapped->szClassItfName = NULL; + } + else + { + // Add an entry with a NULL name to prevent future substitutions. + ImpTlbClassItfInfo sNew; + sNew.ItfIID = psItfAttr->guid; + sNew.szClassItfName = NULL; + IfNullGo(Add(&sNew)); + } + + // If there is a base interface, then handle it also. + if (psItfAttr->cImplTypes == 1) + { + IfFailGo(pItfITI->GetRefTypeOfImplType(0, &href)); + IfFailGo(pItfITI->GetRefTypeInfo(href, &pBaseItfITI)); + } + + // Release for next interface. + if (psItfAttr) + { + pItfITI->ReleaseTypeAttr(psItfAttr); + psItfAttr = 0; + } + if (pItfITI) + { + pItfITI->Release(); + pItfITI = 0; + } + + // Set the current interface to the base interface. + pItfITI = pBaseItfITI; + pBaseItfITI = 0; + } + while(pItfITI); + } + } + +ErrExit: + if (psItfAttr) + pItfITI->ReleaseTypeAttr(psItfAttr); + if (pItfITI) + pItfITI->Release(); + if (bstrClassItfName) + ::SysFreeString(bstrClassItfName); + + return hr; +} + +LPCWSTR CImpTlbDefItfToClassItfMap::GetClassItfName(IID &rItfIID) +{ + ImpTlbClassItfInfo sSearch, *pMapped; + sSearch.ItfIID = rItfIID; + pMapped = Find(&sSearch); + return pMapped ? pMapped->szClassItfName : NULL; +} + +unsigned int CImpTlbDefItfToClassItfMap::Hash(const ImpTlbClassItfInfo *pData) +{ + // Starting value for hash. + ULONG hash = 5381; + + // Hash in the IID. + const BYTE *pbData = reinterpret_cast<const BYTE *>(&pData->ItfIID); + int iSize = sizeof(IID); + while (--iSize >= 0) + { + hash = ((hash << 5) + hash) ^ *pbData; + ++pbData; + } + + return hash; +} // unsigned int CImpTlbDefItfToClassItfMap::Hash() + +unsigned int CImpTlbDefItfToClassItfMap::Compare(const ImpTlbClassItfInfo *p1, ImpTlbClassItfInfo *p2) +{ + // Compare the IID's. + return memcmp(&p1->ItfIID, &p2->ItfIID, sizeof(IID)); +} // unsigned int CImpTlbEventInfoMap::Compare() + +CImpTlbDefItfToClassItfMap::ELEMENTSTATUS CImpTlbDefItfToClassItfMap::Status(ImpTlbClassItfInfo *p) +{ + if (IsEqualGUID(p->ItfIID, FREE_STATUS_GUID)) + { + return (FREE); + } + else if (IsEqualGUID(p->ItfIID, DELETED_STATUS_GUID)) + { + return (DELETED); + } + return (USED); +} // CImpTlbDefItfToClassItfMap::ELEMENTSTATUS CImpTlbEventInfoMap::Status() + +void CImpTlbDefItfToClassItfMap::SetStatus(ImpTlbClassItfInfo *p, ELEMENTSTATUS s) +{ + if (s == FREE) + { + p->ItfIID = FREE_STATUS_GUID; + } + else if (s == DELETED) + { + p->ItfIID = DELETED_STATUS_GUID; + } + else + { + _ASSERTE(!"Invalid status!"); + } +} // void CImpTlbDefItfToClassItfMap::SetStatus() + +void *CImpTlbDefItfToClassItfMap::GetKey(ImpTlbClassItfInfo *p) +{ + return p; +} // void *CImpTlbDefItfToClassItfMap::GetKey() + +ImpTlbClassItfInfo* CImpTlbDefItfToClassItfMap::Add(const ImpTlbClassItfInfo *pData) +{ + // Add the new entry to the map. + const void *pvData = pData; + ImpTlbClassItfInfo *pNew = Super::Add(const_cast<void*>(pvData)); + if (pNew == 0) + return 0; + + // Copy the IID. + pNew->ItfIID = pData->ItfIID; + + // Copy the class interface name. + if (pData->szClassItfName) + { + pNew->szClassItfName = m_Names.Alloc((ULONG)wcslen(pData->szClassItfName)+1); + if (pNew->szClassItfName == 0) + return 0; + wcscpy_s((LPWSTR)pNew->szClassItfName, wcslen(pData->szClassItfName)+1, pData->szClassItfName); + } + else + { + pNew->szClassItfName = NULL; + } + + // Return the new entry. + return pNew; +} // ImpTlbEventInfo* CImpTlbEventInfoMap::Add() + +// EOF ======================================================================= diff --git a/src/md/enc/liteweightstgdbrw.cpp b/src/md/enc/liteweightstgdbrw.cpp new file mode 100644 index 0000000000..9e75ae7635 --- /dev/null +++ b/src/md/enc/liteweightstgdbrw.cpp @@ -0,0 +1,1273 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** + +// +// LiteWeightStgdb.cpp +// +// This contains definition of class CLiteWeightStgDB. This is light weight +// read-only implementation for accessing compressed meta data format. +// +//***************************************************************************** +#include "stdafx.h" // Precompiled header. + +#include "metamodelrw.h" +#include "liteweightstgdb.h" + +// include stgdatabase.h for GUID_POOL_STREAM definition +// #include "stgdatabase.h" + +// include StgTiggerStorage for TiggerStorage definition +#include "stgtiggerstorage.h" +#include "stgio.h" +#include "pedecoder.h" + +#include <log.h> + + +#ifndef TYPELIB_SIG +#define TYPELIB_SIG_MSFT 0x5446534D // MSFT +#define TYPELIB_SIG_SLTG 0x47544C53 // SLTG +#endif + +//***************************************************************************** +// Checks the given storage object to see if it is an NT PE image. +//***************************************************************************** +int _IsNTPEImage( // true if file is NT PE image. + StgIO *pStgIO) // Storage object. +{ + LONG lfanew=0; // Offset in DOS header to NT header. + ULONG lSignature=0; // For NT header signature. + HRESULT hr; + + // Read DOS header to find the NT header offset. + if (FAILED(hr = pStgIO->Seek(60, FILE_BEGIN)) || + FAILED(hr = pStgIO->Read(&lfanew, sizeof(LONG), 0))) + { + return (false); + } + + // Seek to the NT header and read the signature. + if (FAILED(hr = pStgIO->Seek(VAL32(lfanew), FILE_BEGIN)) || + FAILED(hr = pStgIO->Read(&lSignature, sizeof(ULONG), 0)) || + FAILED(hr = pStgIO->Seek(0, FILE_BEGIN))) + { + return (false); + } + + // If the signature is a match, then we have a PE format. + if (lSignature == VAL32(IMAGE_NT_SIGNATURE)) + return (true); + else + return (false); +} + +BOOL _GetFileTypeForPathExt(StgIO * pStgIO, FILETYPE * piType) +{ + // Avoid confusion. + *piType = pStgIO->GetFileType(); + + // All file types except .obj have a signature built in. You should + // not get to this code for those file types unless that file is corrupt, + // or someone has changed a format without updating this code. + _ASSERTE((*piType == FILETYPE_UNKNOWN) || (*piType == FILETYPE_NTOBJ) || (*piType == FILETYPE_TLB)); + + // If we found a type, then you're ok. + return (*piType != FILETYPE_UNKNOWN); +} + +HRESULT _GetFileTypeForPath(StgIO *pStgIO, FILETYPE *piType) +{ + ULONG lSignature=0; + HRESULT hr; + + // Assume native file. + *piType = FILETYPE_CLB; + + // Need to read signature to see what type it is. + if (!(pStgIO->GetFlags() & DBPROP_TMODEF_CREATE)) + { + if (FAILED(hr = pStgIO->Read(&lSignature, sizeof(ULONG), 0)) || + FAILED(hr = pStgIO->Seek(0, FILE_BEGIN))) + { + return (hr); + } + lSignature = VAL32(lSignature); + if (lSignature == STORAGE_MAGIC_SIG) + *piType = FILETYPE_CLB; + else if ((WORD) lSignature ==IMAGE_DOS_SIGNATURE && _IsNTPEImage(pStgIO)) + *piType = FILETYPE_NTPE; + else if (lSignature == TYPELIB_SIG_MSFT || lSignature == TYPELIB_SIG_SLTG) + *piType = FILETYPE_TLB; + else if (!_GetFileTypeForPathExt(pStgIO, piType)) + return CLDB_E_FILE_CORRUPT; + } + return S_OK; +} + +//***************************************************************************** +// Prepare to go away. +//***************************************************************************** +CLiteWeightStgdbRW::~CLiteWeightStgdbRW() +{ + // Free up this stacks reference on the I/O object. + if (m_pStgIO != NULL) + { + m_pStgIO->Release(); + m_pStgIO = NULL; + } + + if (m_pStreamList != NULL) + { + delete m_pStreamList; + } + + if (m_wszFileName != NULL) + { + delete [] m_wszFileName; + } +} + +//***************************************************************************** +// Open an in-memory metadata section for read +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::InitOnMem( + ULONG cbData, // count of bytes in pData + LPCVOID pData, // points to meta data section in memory + int bReadOnly) // If true, read-only. +{ + StgIO *pStgIO = NULL; // For file i/o. + HRESULT hr = NOERROR; + + if ((pStgIO = new (nothrow) StgIO) == 0) + IfFailGo( E_OUTOFMEMORY); + + // Open the storage based on the pbData and cbData + IfFailGo( pStgIO->Open( + NULL, // filename + STGIO_READ, + pData, + cbData, + NULL, // IStream* + NULL) // LPSecurityAttributes + ); + + IfFailGo( InitFileForRead(pStgIO, bReadOnly) ); + +ErrExit: + if (SUCCEEDED(hr)) + { + m_pStgIO = pStgIO; + } + else + { + if (pStgIO) + pStgIO->Release(); + } + return hr; +} // CLiteWeightStgdbRW::InitOnMem + + +//***************************************************************************** +// Given an StgIO, opens compressed streams and do proper initialization. +// This is a helper for other Init functions. +//***************************************************************************** +__checkReturn +HRESULT +CLiteWeightStgdbRW::InitFileForRead( + StgIO * pStgIO, // For file i/o. + int bReadOnly) // If read-only open. +{ + TiggerStorage * pStorage = NULL; + void * pvData; + ULONG cbData; + HRESULT hr = NOERROR; + + // Allocate a new storage object which has IStorage on it. + pStorage = new (nothrow) TiggerStorage(); + IfNullGo(pStorage); + + // Init the storage object on the backing storage. + OptionValue ov; + IfFailGo(m_MiniMd.GetOption(&ov)); + IfFailGo(pStorage->Init(pStgIO, ov.m_RuntimeVersion)); + + // Save pointers to header structure for version string. + _ASSERTE((m_pvMd == NULL) && (m_cbMd == 0)); + IfFailGo(pStorage->GetHeaderPointer(&m_pvMd, &m_cbMd)); + + // Check to see if this is a minimal metadata + if (SUCCEEDED(pStorage->OpenStream(MINIMAL_MD_STREAM, &cbData, &pvData))) + { + m_MiniMd.m_fMinimalDelta = TRUE; + } + + // Load the string pool. + if (SUCCEEDED(hr = pStorage->OpenStream(STRING_POOL_STREAM, &cbData, &pvData))) + { + // String pool has to end with a null-terminator, therefore we don't have to check string pool + // content on access. + // Shrink size of the pool to the last null-terminator found. + while (cbData != 0) + { + if (((LPBYTE)pvData)[cbData - 1] == 0) + { // We have found last null terminator + break; + } + // Shrink size of the pool + cbData--; + Debug_ReportError("String heap/pool does not end with null-terminator ... shrinking the heap."); + } + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolStrings, pvData, cbData, bReadOnly)); + } + else + { + if (hr != STG_E_FILENOTFOUND) + { + IfFailGo(hr); + } + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolStrings, NULL, 0, bReadOnly)); + } + + // Load the user string blob pool. + if (SUCCEEDED(hr = pStorage->OpenStream(US_BLOB_POOL_STREAM, &cbData, &pvData))) + { + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolUSBlobs, pvData, cbData, bReadOnly)); + } + else + { + if (hr != STG_E_FILENOTFOUND) + { + IfFailGo(hr); + } + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolUSBlobs, NULL, 0, bReadOnly)); + } + + // Load the guid pool. + if (SUCCEEDED(hr = pStorage->OpenStream(GUID_POOL_STREAM, &cbData, &pvData))) + { + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolGuids, pvData, cbData, bReadOnly)); + } + else + { + if (hr != STG_E_FILENOTFOUND) + { + IfFailGo(hr); + } + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolGuids, NULL, 0, bReadOnly)); + } + + // Load the blob pool. + if (SUCCEEDED(hr = pStorage->OpenStream(BLOB_POOL_STREAM, &cbData, &pvData))) + { + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolBlobs, pvData, cbData, bReadOnly)); + } + else + { + if (hr != STG_E_FILENOTFOUND) + { + IfFailGo(hr); + } + IfFailGo(m_MiniMd.InitPoolOnMem(MDPoolBlobs, NULL, 0, bReadOnly)); + } + + // Open the metadata. + hr = pStorage->OpenStream(COMPRESSED_MODEL_STREAM, &cbData, &pvData); + if (hr == STG_E_FILENOTFOUND) + { + IfFailGo(pStorage->OpenStream(ENC_MODEL_STREAM, &cbData, &pvData)); + } + IfFailGo(m_MiniMd.InitOnMem(pvData, cbData, bReadOnly)); + IfFailGo(m_MiniMd.PostInit(0)); + +ErrExit: + if (pStorage != NULL) + { + delete pStorage; + } + return hr; +} // CLiteWeightStgdbRW::InitFileForRead + +//***************************************************************************** +// Open a metadata section for read +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::OpenForRead( + LPCWSTR szDatabase, // Name of database. + void *pbData, // Data to open on top of, 0 default. + ULONG cbData, // How big is the data. + DWORD dwFlags) // Flags for the open. +{ + LPCWSTR pNoFile=W(""); // Constant for empty file name. + StgIO *pStgIO = NULL; // For file i/o. + HRESULT hr; + + m_pImage = NULL; + m_dwImageSize = 0; + m_eFileType = FILETYPE_UNKNOWN; + // szDatabase, and pbData are mutually exclusive. Only one may be + // non-NULL. Having both NULL means empty stream creation. + // + _ASSERTE(!(szDatabase && (pbData))); + _ASSERTE(!(pbData && (szDatabase))); + + // Open on memory needs there to be something to work with. + if (pbData && cbData == 0) + IfFailGo(CLDB_E_NO_DATA); + + // Make sure we have a path to work with. + if (!szDatabase) + szDatabase = pNoFile; + + // Sanity check the name lentgh. + if (!IsValidFileNameLength(szDatabase)) + { + IfFailGo(E_INVALIDARG); + } + + // If we have storage to work with, init it and get type. + if (*szDatabase || pbData) + { + // Allocate a storage instance to use for i/o. + if ((pStgIO = new (nothrow) StgIO) == 0) + IfFailGo( E_OUTOFMEMORY ); + + DBPROPMODE dmOpenFlags = DBPROP_TMODEF_READ; + + // If we're taking ownership of this memory..... + if (IsOfTakeOwnership(dwFlags)) + { +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + // Shared memory uses ole32.dll - we cannot depend on it in the standalone WinRT Read-Only DLL + IfFailGo(E_INVALIDARG); +#else + dmOpenFlags = (DBPROPMODE)(dmOpenFlags | DBPROP_TMODEF_SHAREDMEM); +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + } +#ifdef FEATURE_METADATA_LOAD_TRUSTED_IMAGES + if (IsOfTrustedImage(dwFlags)) + dmOpenFlags = (DBPROPMODE)(dmOpenFlags | DBPROP_TMODEF_TRYLOADLIBRARY); +#endif + + // Open the storage so we can read the signature if there is already data. + IfFailGo( pStgIO->Open(szDatabase, + dmOpenFlags, + pbData, + cbData, + 0, // IStream* + NULL) ); + + // Determine the type of file we are working with. + IfFailGo( _GetFileTypeForPath(pStgIO, &m_eFileType) ); + } + + // Check for default type. + if (m_eFileType == FILETYPE_CLB) + { + // If user wanted us to make a local copy of the data, do that now. + if (IsOfCopyMemory(dwFlags)) + IfFailGo(pStgIO->LoadFileToMemory()); + + // Try the native .clb file. + IfFailGo( InitFileForRead(pStgIO, IsOfRead(dwFlags)) ); + } + // PE/COFF executable/object format. This requires us to find the .clb + // inside the binary before doing the Init. + else if (m_eFileType == FILETYPE_NTPE || m_eFileType == FILETYPE_NTOBJ) + { + //<TODO>@FUTURE: Ideally the FindImageMetaData function + //@FUTURE: would take the pStgIO and map only the part of the file where + //@FUTURE: our data lives, leaving the rest alone. This would be smaller + //@FUTURE: working set for us.</TODO> + void *ptr; + ULONG cbSize; + + // Map the entire binary for the FindImageMetaData function. + IfFailGo( pStgIO->MapFileToMem(ptr, &cbSize) ); + + // Find the .clb inside of the content. + if (m_eFileType == FILETYPE_NTPE) + { + m_pImage = ptr; + m_dwImageSize = cbSize; + hr = FindImageMetaData(ptr, + cbSize, + pStgIO->GetMemoryMappedType() == MTYPE_IMAGE, + &ptr, + &cbSize); + } + else + { + _ASSERTE(pStgIO->GetMemoryMappedType() != MTYPE_IMAGE); + hr = FindObjMetaData(ptr, cbSize, &ptr, &cbSize); + } + // Was the metadata found inside the PE file? + if (FAILED(hr)) + { + if (hr == E_OUTOFMEMORY) + IfFailGo(E_OUTOFMEMORY); + + // No clb in the PE, assume it is a type library. + m_eFileType = FILETYPE_TLB; + + // Let the caller deal with a TypeLib. + IfFailGo(hr); + } + else + { + // Metadata was found inside the file. + // Now reset the base of the stg object so that all memory accesses + // are relative to the .clb content. + // + IfFailGo( pStgIO->SetBaseRange(ptr, cbSize) ); + + // If user wanted us to make a local copy of the data, do that now. + if (IsOfCopyMemory(dwFlags)) + { + // Cache the PEKind, Machine. + GetPEKind(pStgIO->GetMemoryMappedType(), NULL, NULL); + // Copy the file into memory; releases the file. + IfFailGo(pStgIO->LoadFileToMemory()); + // No longer have the image. + m_pImage = NULL; + m_dwImageSize = 0; + } + + // Defer to the normal lookup. + IfFailGo( InitFileForRead(pStgIO, IsOfRead(dwFlags)) ); + } + } + else if (m_eFileType == FILETYPE_TLB) + { + // Let the caller deal with a TypeLib. + IfFailGo(CLDB_E_NO_DATA); + } + // This spells trouble, we need to handle all types we might find. + else + { + _ASSERTE(!"Unknown file type."); + IfFailGo( E_FAIL ); + } + + // Save off everything. + IfFailGo(SetFileName(szDatabase)); + + // If this was a file... + if (pbData == NULL) + { + WIN32_FILE_ATTRIBUTE_DATA faData; + if (!WszGetFileAttributesEx(szDatabase, GetFileExInfoStandard, &faData)) + IfFailGo(E_FAIL); + m_dwDatabaseLFS = faData.nFileSizeLow; + m_dwDatabaseLFT = faData.ftLastWriteTime.dwLowDateTime; + } + +ErrExit: + if (SUCCEEDED(hr)) + { + m_pStgIO = pStgIO; + } + else + { + if (pStgIO != NULL) + pStgIO->Release(); + } + return hr; +} + +#ifdef FEATURE_METADATA_CUSTOM_DATA_SOURCE +// Open a metadata section for read/write +__checkReturn +HRESULT CLiteWeightStgdbRW::OpenForRead( + IMDCustomDataSource *pDataSource, // data to open on top of + DWORD dwFlags) // Flags for the open. +{ + LPCWSTR pNoFile = W(""); // Constant for empty file name. + StgIO *pStgIO = NULL; // For file i/o. + HRESULT hr; + + m_pImage = NULL; + m_dwImageSize = 0; + m_eFileType = FILETYPE_UNKNOWN; + + IfFailGo(m_MiniMd.InitOnCustomDataSource(pDataSource)); + IfFailGo(m_MiniMd.PostInit(0)); + + // Save off everything. + IfFailGo(SetFileName(pNoFile)); + +ErrExit: + return hr; +} +#endif + +// Read/Write versions. +//***************************************************************************** +// Init the Stgdb and its subcomponents. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::InitNew() +{ + InitializeLogging(); + LOG((LF_METADATA, LL_INFO10, "Metadata logging enabled\n")); + + //<TODO>@FUTURE: should probably init the pools here instead of in the MiniMd.</TODO> + return m_MiniMd.InitNew(); +} + +//***************************************************************************** +// Determine what the size of the saved data will be. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::GetSaveSize(// S_OK or error. + CorSaveSize fSave, // Quick or accurate? + UINT32 *pcbSaveSize, // Put the size here. + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) // Profile data for working set optimization +{ + HRESULT hr = S_OK; // A result. + UINT32 cbTotal = 0; // The total size. + UINT32 cbSize = 0; // Size of a component. + + m_cbSaveSize = 0; + + // Allocate stream list if not already done. + if (m_pStreamList == NULL) + { + IfNullGo(m_pStreamList = new (nothrow) STORAGESTREAMLST); + } + else + { + m_pStreamList->Clear(); + } + + // Make sure the user string pool is not empty. An empty user string pool causes + // problems with edit and continue + + if (m_MiniMd.m_UserStringHeap.GetUnalignedSize() <= 1) + { + if (!IsENCDelta(m_MiniMd.m_OptionValue.m_UpdateMode) && + !m_MiniMd.IsMinimalDelta()) + { + BYTE rgData[] = {' ', 0, 0}; + UINT32 nIndex_Ignore; + IfFailGo(m_MiniMd.PutUserString( + MetaData::DataBlob(rgData, sizeof(rgData)), + &nIndex_Ignore)); + } + } + + // If we're saving a delta metadata, figure out how much space it will take to + // save the minimal metadata stream (used only to identify that we have a delta + // metadata... nothing should be in that stream. + if ((m_MiniMd.m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateDelta) + { + IfFailGo(AddStreamToList(0, MINIMAL_MD_STREAM)); + // Ask the storage system to add stream fixed overhead. + IfFailGo(TiggerStorage::GetStreamSaveSize(MINIMAL_MD_STREAM, 0, &cbSize)); + cbTotal += cbSize; + } + + if (reorderingOptions & ReArrangeStringPool) + { + if (pProfileData != NULL) + { + UINT32 cbHotSize = 0; // Size of pool data. + UINT32 cbStream; // Size of just the stream. + DWORD bCompressed; // Will the stream be compressed data? + + // Ask the metadata to size its hot data. + IfFailGo(m_MiniMd.GetSaveSize(fSave, &cbHotSize, &bCompressed, reorderingOptions, pProfileData)); + cbStream = cbHotSize; + m_bSaveCompressed = bCompressed; + + if (cbHotSize != 0) + { + // Add this item to the save list. + IfFailGo(AddStreamToList(cbHotSize, HOT_MODEL_STREAM)); + + // Ask the storage system to add stream fixed overhead. + IfFailGo(TiggerStorage::GetStreamSaveSize(HOT_MODEL_STREAM, cbHotSize, &cbHotSize)); + + // Log the size info. + LOG((LF_METADATA, LL_INFO10, "Metadata: GetSaveSize for %ls: %d data, %d total.\n", + HOT_MODEL_STREAM, cbStream, cbHotSize)); + + cbTotal += cbHotSize; + } + } + + // get string pool save size + IfFailGo(GetPoolSaveSize(STRING_POOL_STREAM, MDPoolStrings, &cbSize)); + cbTotal += cbSize; + } + + // Query the MiniMd for its size. + IfFailGo(GetTablesSaveSize(fSave, &cbSize, reorderingOptions, pProfileData)); + cbTotal += cbSize; + + // Get the pools' sizes. + if( !(reorderingOptions & ReArrangeStringPool) ) + { + IfFailGo(GetPoolSaveSize(STRING_POOL_STREAM, MDPoolStrings, &cbSize)); + cbTotal += cbSize; + } + IfFailGo(GetPoolSaveSize(US_BLOB_POOL_STREAM, MDPoolUSBlobs, &cbSize)); + cbTotal += cbSize; + IfFailGo(GetPoolSaveSize(GUID_POOL_STREAM, MDPoolGuids, &cbSize)); + cbTotal += cbSize; + IfFailGo(GetPoolSaveSize(BLOB_POOL_STREAM, MDPoolBlobs, &cbSize)); + cbTotal += cbSize; + + // Finally, ask the storage system to add fixed overhead it needs for the + // file format. The overhead of each stream has already be calculated as + // part of GetStreamSaveSize. What's left is the signature and header + // fixed size overhead. + IfFailGo(TiggerStorage::GetStorageSaveSize((ULONG *)&cbTotal, 0, m_MiniMd.m_OptionValue.m_RuntimeVersion)); + + // Log the size info. + LOG((LF_METADATA, LL_INFO10, "Metadata: GetSaveSize total is %d.\n", cbTotal)); + + // The list of streams that will be saved are now in the stream save list. + // Next step is to walk that list and fill out the correct offsets. This is + // done here so that the data can be streamed without fixing up the header. + TiggerStorage::CalcOffsets(m_pStreamList, 0, m_MiniMd.m_OptionValue.m_RuntimeVersion); + + if (pcbSaveSize != NULL) + { + *pcbSaveSize = cbTotal; + } + + // Don't cache the value for the EnC case + if (!IsENCDelta(m_MiniMd.m_OptionValue.m_UpdateMode)) + m_cbSaveSize = cbTotal; + +ErrExit: + return hr; +} // CLiteWeightStgdbRW::GetSaveSize + +//***************************************************************************** +// Get the save size of one of the pools. Also adds the pool's stream to +// the list of streams to be saved. +//***************************************************************************** +__checkReturn +HRESULT +CLiteWeightStgdbRW::GetPoolSaveSize( + LPCWSTR szHeap, // Name of the heap stream. + int iPool, // The pool of which to get size. + UINT32 *pcbSaveSize) // Add pool data to this value. +{ + UINT32 cbSize = 0; // Size of pool data. + UINT32 cbStream; // Size of just the stream. + HRESULT hr; + + *pcbSaveSize = 0; + + // If there is no data, then don't bother. + if (m_MiniMd.IsPoolEmpty(iPool)) + return (S_OK); + + // Ask the pool to size its data. + IfFailGo(m_MiniMd.GetPoolSaveSize(iPool, &cbSize)); + cbStream = cbSize; + + // Add this item to the save list. + IfFailGo(AddStreamToList(cbSize, szHeap)); + + + // Ask the storage system to add stream fixed overhead. + IfFailGo(TiggerStorage::GetStreamSaveSize(szHeap, cbSize, &cbSize)); + + // Log the size info. + LOG((LF_METADATA, LL_INFO10, "Metadata: GetSaveSize for %ls: %d data, %d total.\n", + szHeap, cbStream, cbSize)); + + // Give the size of the pool to the caller's total. + *pcbSaveSize = cbSize; + +ErrExit: + return hr; +} + +//***************************************************************************** +// Get the save size of the metadata tables. Also adds the tables stream to +// the list of streams to be saved. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::GetTablesSaveSize( + CorSaveSize fSave, + UINT32 *pcbSaveSize, + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) // Add pool data to this value. +{ + UINT32 cbSize = 0; // Size of pool data. + UINT32 cbHotSize = 0; // Size of pool data. + UINT32 cbStream; // Size of just the stream. + DWORD bCompressed; // Will the stream be compressed data? + LPCWSTR szName; // What will the name of the pool be? + HRESULT hr; + + *pcbSaveSize = 0; + + if( !(reorderingOptions & ReArrangeStringPool) ) + { + if (pProfileData != NULL) + { + // Ask the metadata to size its hot data. + IfFailGo(m_MiniMd.GetSaveSize(fSave, &cbHotSize, &bCompressed, reorderingOptions, pProfileData)); + cbStream = cbHotSize; + m_bSaveCompressed = bCompressed; + + if (cbHotSize != 0) + { + szName = HOT_MODEL_STREAM; + + // Add this item to the save list. + IfFailGo(AddStreamToList(cbHotSize, szName)); + + // Ask the storage system to add stream fixed overhead. + IfFailGo(TiggerStorage::GetStreamSaveSize(szName, cbHotSize, &cbHotSize)); + + // Log the size info. + LOG((LF_METADATA, LL_INFO10, "Metadata: GetSaveSize for %ls: %d data, %d total.\n", + szName, cbStream, cbHotSize)); + } + } + } + // Ask the metadata to size its data. + IfFailGo(m_MiniMd.GetSaveSize(fSave, &cbSize, &bCompressed)); + cbStream = cbSize; + m_bSaveCompressed = bCompressed; + szName = m_bSaveCompressed ? COMPRESSED_MODEL_STREAM : ENC_MODEL_STREAM; + + // Add this item to the save list. + IfFailGo(AddStreamToList(cbSize, szName)); + + // Ask the storage system to add stream fixed overhead. + IfFailGo(TiggerStorage::GetStreamSaveSize(szName, cbSize, &cbSize)); + + // Log the size info. + LOG((LF_METADATA, LL_INFO10, "Metadata: GetSaveSize for %ls: %d data, %d total.\n", + szName, cbStream, cbSize)); + + // Give the size of the pool to the caller's total. + *pcbSaveSize = cbHotSize + cbSize; + +ErrExit: + return hr; +} // CLiteWeightStgdbRW::GetTablesSaveSize + +//***************************************************************************** +// Add a stream, and its size, to the list of streams to be saved. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::AddStreamToList( + UINT32 cbSize, + LPCWSTR szName) +{ + HRESULT hr = S_OK; + PSTORAGESTREAM pItem; // New item to allocate & fill. + + // Add a new item to the end of the list. + IfNullGo(pItem = m_pStreamList->Append()); + + // Fill out the data. + pItem->SetOffset(0); + pItem->SetSize((ULONG)cbSize); + pItem->SetName(szName); + +ErrExit: + return hr; +} + +//***************************************************************************** +// Save the data to a stream. A TiggerStorage sub-allocates streams within +// the stream. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::SaveToStream( + IStream *pIStream, + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) +{ + HRESULT hr = S_OK; // A result. + StgIO *pStgIO = 0; + TiggerStorage *pStorage = 0; + + // Allocate a storage subsystem and backing store. + IfNullGo(pStgIO = new (nothrow) StgIO); + IfNullGo(pStorage = new (nothrow) TiggerStorage); + + // Open around this stream for write. + IfFailGo(pStgIO->Open(W(""), + DBPROP_TMODEF_DFTWRITEMASK, + 0, 0, // pbData, cbData + pIStream, + 0)); // LPSecurityAttributes + OptionValue ov; + IfFailGo(m_MiniMd.GetOption(&ov)); + IfFailGo(pStorage->Init(pStgIO, ov.m_RuntimeVersion)); + + // Save worker will do tables, pools. + IfFailGo(SaveToStorage(pStorage, reorderingOptions, pProfileData)); + +ErrExit: + if (pStgIO != NULL) + pStgIO->Release(); + if (pStorage != NULL) + delete pStorage; + return hr; +} // CLiteWeightStgdbRW::SaveToStream + +//***************************************************************************** +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::SaveToStorage( + TiggerStorage *pStorage, + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) +{ + HRESULT hr; // A result. + LPCWSTR szName; // Name of the tables stream. + IStream *pIStreamTbl = 0; + UINT32 cb; + UINT32 cbSaveSize = m_cbSaveSize; + + // Must call GetSaveSize to cache the streams up front. + // Don't trust cached values in the delta case... if there was a previous call to get + // a non-delta size, it will be incorrect. + if ((m_cbSaveSize == 0) || IsENCDelta(m_MiniMd.m_OptionValue.m_UpdateMode)) + { + IfFailGo(GetSaveSize(cssAccurate, &cbSaveSize)); + } + + // Save the header of the data file. + IfFailGo(pStorage->WriteHeader(m_pStreamList, 0, NULL)); + + // If this is a minimal delta, write a stream marker + if (IsENCDelta(m_MiniMd.m_OptionValue.m_UpdateMode)) + { + IfFailGo(pStorage->CreateStream(MINIMAL_MD_STREAM, + STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, + 0, 0, &pIStreamTbl)); + pIStreamTbl->Release(); + pIStreamTbl = 0; + } + + if (pProfileData != NULL) + { + DWORD bCompressed; + UINT32 cbHotSize; + // Will the stream be compressed data? + + // Only create this additional stream if it will be non-empty + IfFailGo(m_MiniMd.GetSaveSize(cssAccurate, &cbHotSize, &bCompressed, reorderingOptions, pProfileData)); + + if (cbHotSize > 0) + { + // Create a stream and save the hot tables. + szName = HOT_MODEL_STREAM; + IfFailGo(pStorage->CreateStream(szName, + STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, + 0, 0, &pIStreamTbl)); + IfFailGo(m_MiniMd.SaveTablesToStream(pIStreamTbl, reorderingOptions, pProfileData)); + pIStreamTbl->Release(); + pIStreamTbl = 0; + } + } + + if (reorderingOptions & ReArrangeStringPool) + { + // Save the string pool before the tables when we do not have the string pool cache + IfFailGo(SavePool(STRING_POOL_STREAM, pStorage, MDPoolStrings)); + } + + // Create a stream and save the tables. + szName = m_bSaveCompressed ? COMPRESSED_MODEL_STREAM : ENC_MODEL_STREAM; + IfFailGo(pStorage->CreateStream(szName, + STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, + 0, 0, &pIStreamTbl)); + IfFailGo(m_MiniMd.SaveTablesToStream(pIStreamTbl, NoReordering, NULL)); + pIStreamTbl->Release(); + pIStreamTbl = 0; + + // Save the pools. + if (!(reorderingOptions & ReArrangeStringPool)) + { + // string pool must be saved after the tables when we have the string pool cache + IfFailGo(SavePool(STRING_POOL_STREAM, pStorage, MDPoolStrings)); + } + IfFailGo(SavePool(US_BLOB_POOL_STREAM, pStorage, MDPoolUSBlobs)); + IfFailGo(SavePool(GUID_POOL_STREAM, pStorage, MDPoolGuids)); + IfFailGo(SavePool(BLOB_POOL_STREAM, pStorage, MDPoolBlobs)); + + // Write the header to disk. + OptionValue ov; + IfFailGo(m_MiniMd.GetOption(&ov)); + + IfFailGo(pStorage->WriteFinished(m_pStreamList, (ULONG *)&cb, IsENCDelta(ov.m_UpdateMode))); + + _ASSERTE(cbSaveSize == cb); + + // Let the Storage release some memory. + pStorage->ResetBackingStore(); + + IfFailGo(m_MiniMd.SaveDone()); + +ErrExit: + if (pIStreamTbl != NULL) + pIStreamTbl->Release(); + delete m_pStreamList; + m_pStreamList = 0; + m_cbSaveSize = 0; + return hr; +} // CLiteWeightStgdbRW::SaveToStorage + +//***************************************************************************** +// Save a pool of data out to a stream. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::SavePool( // Return code. + LPCWSTR szName, // Name of stream on disk. + TiggerStorage *pStorage, // The storage to put data in. + int iPool) // The pool to save. +{ + IStream *pIStream=0; // For writing. + HRESULT hr; + + // If there is no data, then don't bother. + if (m_MiniMd.IsPoolEmpty(iPool)) + return (S_OK); + + // Create the new stream to hold this table and save it. + IfFailGo(pStorage->CreateStream(szName, + STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, + 0, 0, &pIStream)); + IfFailGo(m_MiniMd.SavePoolToStream(iPool, pIStream)); + +ErrExit: + if (pIStream) + pIStream->Release(); + return hr; +} // CLiteWeightStgdbRW::SavePool + + +//***************************************************************************** +// Save the metadata to a file. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::Save( + LPCWSTR szDatabase, // Name of file to which to save. + DWORD dwSaveFlags) // Flags for the save. +{ + TiggerStorage * pStorage = NULL; // IStorage object. + StgIO * pStgIO = NULL; // Backing storage. + HRESULT hr = S_OK; + + if (m_wszFileName == NULL) + { + if (szDatabase == NULL) + { + // Make sure that a NULL is not passed in the first time around. + _ASSERTE(!"Not allowed to pass a NULL for filename on the first call to Save."); + return E_INVALIDARG; + } + else + { + // Save the file name. + IfFailGo(SetFileName(szDatabase)); + } + } + else if ((szDatabase != NULL) && (SString::_wcsicmp(szDatabase, m_wszFileName) != 0)) + { + // Save the file name. + IfFailGo(SetFileName(szDatabase)); + } + + // Sanity check the name. + if (!IsValidFileNameLength(m_wszFileName)) + { + IfFailGo(E_INVALIDARG); + } + + m_eFileType = FILETYPE_CLB; + + // Allocate a new storage object. + IfNullGo(pStgIO = new (nothrow) StgIO); + + // Create the output file. + IfFailGo(pStgIO->Open(m_wszFileName, + DBPROP_TMODEF_DFTWRITEMASK, + 0,0, // pbData, cbData + 0, // IStream* + 0)); // LPSecurityAttributes + + // Allocate an IStorage object to use. + IfNullGo(pStorage = new (nothrow) TiggerStorage); + + // Init the storage object on the i/o system. + OptionValue ov; + IfFailGo(m_MiniMd.GetOption(&ov)); + IfFailGo(pStorage->Init(pStgIO, ov.m_RuntimeVersion)); + + // Save the data. + IfFailGo(SaveToStorage(pStorage)); + +ErrExit: + if (pStgIO != NULL) + pStgIO->Release(); + if (pStorage != NULL) + delete pStorage; + return hr; +} // CLiteWeightStgdbRW::Save + +//***************************************************************************** +// Pull the PEKind and Machine out of PE headers -- if we have PE headers. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::GetPEKind( // S_OK or error. + MAPPINGTYPE mtMapping, // The type of mapping the image has + DWORD *pdwPEKind, // [OUT] The kind of PE (0 - not a PE) + DWORD *pdwMachine) // [OUT] Machine as defined in NT header +{ + HRESULT hr = NOERROR; + DWORD dwPEKind=0; // Working copy of pe kind. + DWORD dwMachine=0; // Working copy of machine. + +#ifndef DACCESS_COMPILE + // Do we already have cached information? + if (m_dwPEKind != (DWORD)(-1)) + { + dwPEKind = m_dwPEKind; + dwMachine = m_dwMachine; + } + else if (m_pImage) + { + PEDecoder pe; + + // We need to use different PEDecoder initialization based on the type of data we give it. + // We use the one with a 'bool' as the second argument when dealing with a mapped file, + // and we use the one that takes a COUNT_T as the second argument when dealing with a + // flat file. + + if (mtMapping == MTYPE_IMAGE) + { + if (FAILED(pe.Init(m_pImage, false)) || + !pe.CheckNTHeaders()) + { + IfFailRet(COR_E_BADIMAGEFORMAT); + } + } + else + { + pe.Init(m_pImage, (COUNT_T)(m_dwImageSize)); + } + + if (pe.HasContents() && pe.HasNTHeaders()) + { + pe.GetPEKindAndMachine(&dwPEKind, &dwMachine); + + + // Cache entries. + m_dwPEKind = dwPEKind; + m_dwMachine = dwMachine; + } + else // if (pe.HasContents()... + { + hr = COR_E_BADIMAGEFORMAT; + } + } + else + { + hr = S_FALSE; + } +#endif + if (pdwPEKind) + *pdwPEKind = dwPEKind; + if (pdwMachine) + *pdwMachine = dwMachine; + + return hr; +} // CLiteWeightStgdbRW::GetPEKind + +//***************************************************************************** +// Low level access to the data. Intended for metainfo, and such. +//***************************************************************************** +__checkReturn +HRESULT CLiteWeightStgdbRW::GetRawData( + const void **ppvMd, // [OUT] put pointer to MD section here (aka, 'BSJB'). + ULONG *pcbMd) // [OUT] put size of the stream here. +{ +#ifdef FEATURE_METADATA_CUSTOM_DATA_SOURCE + if (m_pStgIO == NULL) + return COR_E_NOTSUPPORTED; +#endif + + *ppvMd = (const void*) m_pStgIO->m_pData; + *pcbMd = m_pStgIO->m_cbData; + return S_OK; +} // CLiteWeightStgdbRW::GetRawData + +//***************************************************************************** +// Get info about the MD stream. +// Low level access to stream data. Intended for metainfo, and such. +//***************************************************************************** +__checkReturn +STDMETHODIMP +CLiteWeightStgdbRW::GetRawStreamInfo( + ULONG ix, // [IN] Stream ordinal desired. + const char **ppchName, // [OUT] put pointer to stream name here. + const void **ppv, // [OUT] put pointer to MD stream here. + ULONG *pcb) // [OUT] put size of the stream here. +{ + HRESULT hr = NOERROR; + STORAGEHEADER sHdr; // Header for the storage. + PSTORAGESTREAM pStream; // Pointer to each stream. + ULONG i; // Loop control. + +#ifdef FEATURE_METADATA_CUSTOM_DATA_SOURCE + if (m_pStgIO == NULL) + IfFailGo(COR_E_NOTSUPPORTED); +#endif + + void *pData = m_pStgIO->m_pData; + ULONG cbData = m_pStgIO->m_cbData; + + // Validate the signature of the format, or it isn't ours. + IfFailGo(MDFormat::VerifySignature((PSTORAGESIGNATURE) pData, cbData)); + + // Get back the first stream. + pStream = MDFormat::GetFirstStream(&sHdr, pData); + if (pStream == NULL) + { + Debug_ReportError("Invalid MetaData storage signature - cannot get the first stream header."); + IfFailGo(CLDB_E_FILE_CORRUPT); + } + + // Check that the requested stream exists. + if (ix >= sHdr.GetiStreams()) + return S_FALSE; + + // Skip to the desired stream. + for (i = 0; i < ix; i++) + { + PSTORAGESTREAM pNext = pStream->NextStream(); + + // Check that stream header is within the buffer. + if (((LPBYTE)pStream >= ((LPBYTE)pData + cbData)) || + ((LPBYTE)pNext > ((LPBYTE)pData + cbData))) + { + Debug_ReportError("Stream header is not within MetaData block."); + hr = CLDB_E_FILE_CORRUPT; + goto ErrExit; + } + + // Check that the stream data starts and fits within the buffer. + // need two checks on size because of wraparound. + if ((pStream->GetOffset() > cbData) || + (pStream->GetSize() > cbData) || + ((pStream->GetSize() + pStream->GetOffset()) > cbData)) + { + Debug_ReportError("Stream data are not within MetaData block."); + hr = CLDB_E_FILE_CORRUPT; + goto ErrExit; + } + + // Pick off the next stream if there is one. + pStream = pNext; + } + + if (pStream != NULL) + { + *ppv = (const void *)((const BYTE *)pData + pStream->GetOffset()); + *pcb = pStream->GetSize(); + *ppchName = pStream->GetName(); + } + else + { + *ppv = NULL; + *pcb = 0; + *ppchName = NULL; + + // Invalid input to the method + hr = CLDB_E_FILE_CORRUPT; + } + +ErrExit: + return hr; +} // CLiteWeightStgdbRW::GetRawStreamInfo + +//======================================================================================= +// +// Set file name of this database (makes copy of the file name). +// +// Return value: S_OK or E_OUTOFMEMORY +// +__checkReturn +HRESULT +CLiteWeightStgdbRW::SetFileName( + const WCHAR * wszFileName) +{ + HRESULT hr = S_OK; + + if (m_wszFileName != NULL) + { + delete [] m_wszFileName; + m_wszFileName = NULL; + } + + if ((wszFileName == NULL) || (*wszFileName == 0)) + { // The new file name is empty + _ASSERTE(m_wszFileName == NULL); + + // No need to allocate anything, NULL means empty name + hr = S_OK; + goto ErrExit; + } + + // Size of the file name incl. null terminator + size_t cchFileName; + cchFileName = wcslen(wszFileName) + 1; + + // Allocate and copy the file name + m_wszFileName = new (nothrow) WCHAR[cchFileName]; + IfNullGo(m_wszFileName); + wcscpy_s(m_wszFileName, cchFileName, wszFileName); + +ErrExit: + return hr; +} // CLiteWeightStgdbRW::SetFileName + +//======================================================================================= +// +// Returns TRUE if wszFileName has valid path length (MAX_PATH or 32767 if prefixed with \\?\). +// +//static +BOOL +CLiteWeightStgdbRW::IsValidFileNameLength( + const WCHAR * wszFileName) +{ + static const WCHAR const_wszLongPathPrefix[] = W("\\\\?\\"); + + if (wszFileName == NULL) + { + return TRUE; + } + size_t cchFileName = wcslen(wszFileName); + if (cchFileName < _MAX_PATH) + { + return TRUE; + } + if (SString::_wcsnicmp(wszFileName, const_wszLongPathPrefix, _countof(const_wszLongPathPrefix) - 1) != 0) + { // Path does not have long path prefix \\?\ (as required by CreateFile API) + return FALSE; + } + if (cchFileName < 32767) + { // Limit for the long path length as defined in CreateFile API + return TRUE; + } + return FALSE; +} // CLiteWeightStgdbRW::IsValidFileNameLength diff --git a/src/md/enc/mdinternalrw.cpp b/src/md/enc/mdinternalrw.cpp new file mode 100644 index 0000000000..0495101bc8 --- /dev/null +++ b/src/md/enc/mdinternalrw.cpp @@ -0,0 +1,4356 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// =========================================================================== +// File: MDInternalRW.cpp +// + +// Notes: +// +// +// =========================================================================== +#include "stdafx.h" +#include "../runtime/mdinternalro.h" +#include "../compiler/regmeta.h" +#include "../compiler/importhelper.h" +#include "mdinternalrw.h" +#include "metamodelro.h" +#include "liteweightstgdb.h" + +#ifdef FEATURE_METADATA_INTERNAL_APIS + +__checkReturn +HRESULT _GetFixedSigOfVarArg( // S_OK or error. + PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob of COM+ method signature + ULONG cbSigBlob, // [IN] size of signature + CQuickBytes *pqbSig, // [OUT] output buffer for fixed part of VarArg Signature + ULONG *pcbSigBlob); // [OUT] number of bytes written to the above output buffer + +__checkReturn +HRESULT _FillMDDefaultValue( + BYTE bType, + void const *pValue, + ULONG cbValue, + MDDefaultValue *pMDDefaultValue); + + +//***************************************************************************** +// Serve as a delegator to call ImportHelper::MergeUpdateTokenInSig. Or we will +// need to include ImportHelper into our md\runtime directory. +//***************************************************************************** +__checkReturn +HRESULT TranslateSigHelper( // S_OK or error. + IMDInternalImport* pImport, // [IN] import scope. + IMDInternalImport* pAssemImport, // [IN] import assembly scope. + const void* pbHashValue, // [IN] hash value for the import assembly. + ULONG cbHashValue, // [IN] count of bytes in the hash value. + PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope + ULONG cbSigBlob, // [IN] count of bytes of signature + IMetaDataAssemblyEmit* pAssemEmit, // [IN] assembly emit scope. + IMetaDataEmit* emit, // [IN] emit interface + CQuickBytes* pqkSigEmit, // [OUT] buffer to hold translated signature + ULONG* pcbSig) // [OUT] count of bytes in the translated signature +{ +#ifdef FEATURE_METADATA_EMIT + IMetaModelCommon *pCommon = pImport->GetMetaModelCommon(); + RegMeta *pAssemEmitRM = static_cast<RegMeta*>(pAssemEmit); + RegMeta *pEmitRM = static_cast<RegMeta*>(emit); + + CMiniMdRW *pMiniMdAssemEmit = pAssemEmitRM ? &pAssemEmitRM->m_pStgdb->m_MiniMd : NULL; + CMiniMdRW *pMiniMdEmit = &(pEmitRM->m_pStgdb->m_MiniMd); + IMetaModelCommon *pCommonAssemImport = pAssemImport ? pAssemImport->GetMetaModelCommon() : NULL; + + return ImportHelper::MergeUpdateTokenInSig( + pMiniMdAssemEmit, // The assembly emit scope. + pMiniMdEmit, // The emit scope. + pCommonAssemImport, // Assembly scope where the signature is from. + pbHashValue, // Hash value for the import scope. + cbHashValue, // Size in bytes. + pCommon, // The scope where signature is from. + pbSigBlob, // signature from the imported scope + NULL, // Internal OID mapping structure. + pqkSigEmit, // [OUT] translated signature + NULL, // start from first byte of the signature + NULL, // don't care how many bytes consumed + pcbSig); // [OUT] total number of bytes write to pqkSigEmit + +#else //!FEATURE_METADATA_EMIT + // This API doesn't make sense without supporting public Emit APIs + return E_NOTIMPL; +#endif //!FEATURE_METADATA_EMIT +} // TranslateSigHelper + + +//***************************************************************************** +// Given an IMDInternalImport on a CMiniMd[RO], convert to CMiniMdRW. +//***************************************************************************** +__checkReturn +STDAPI ConvertRO2RW( + IUnknown *pRO, // [IN] The RO interface to convert. + REFIID riid, // [IN] The interface desired. + void **ppIUnk) // [OUT] Return interface on success. +{ + HRESULT hr = S_OK; // A result. + IMDInternalImportENC *pRW = 0; // To test the RW-ness of the input iface. + MDInternalRW *pInternalRW = 0; // Gets the new RW object. + MDInternalRO *pTrustedRO = 0; + + // Avoid confusion. + *ppIUnk = 0; + + IfFailGo(VerifyNotWinMD(pRO, "ConvertRO2RW() not supported on .winmd files.")); + + // If the interface is already RW, done, just return. + if (pRO->QueryInterface(IID_IMDInternalImportENC, (void**)&pRW) == S_OK) + { + hr = pRO->QueryInterface(riid, ppIUnk); + goto ErrExit; + } + + // Create the new RW object. + pInternalRW = new (nothrow) MDInternalRW; + IfNullGo( pInternalRW ); + + // Init from the RO object. Convert as read-only; QI will make writable if + // so needed. + + // ! QI for IID_IUnknown will return MDInternalRO*. Not that COM guarantees such a thing but MDInternalRO knows about + IfFailGo( pRO->QueryInterface(IID_IUnknown, (void**)&pTrustedRO) ); + IfFailGo( pInternalRW->InitWithRO(pTrustedRO, true)); + IfFailGo( pInternalRW->QueryInterface(riid, ppIUnk) ); + +ErrExit: + if (pRW) + pRW->Release(); + if (pTrustedRO) + pTrustedRO->Release(); + // Clean up the object and [OUT] interface on error. + if (FAILED(hr)) + { + if (pInternalRW) + delete pInternalRW; + *ppIUnk = 0; + } + else if (pInternalRW) + pInternalRW->Release(); + + return hr; +} // ConvertRO2RW + + +//***************************************************************************** +// Helper to get the internal interface with RW format +//***************************************************************************** +__checkReturn +HRESULT GetInternalWithRWFormat( + LPVOID pData, + ULONG cbData, + DWORD flags, // [IN] MDInternal_OpenForRead or MDInternal_OpenForENC + REFIID riid, // [in] The interface desired. + void **ppIUnk) // [out] Return interface on success. +{ + MDInternalRW *pInternalRW = NULL; + HRESULT hr; + + *ppIUnk = 0; + pInternalRW = new (nothrow) MDInternalRW; + IfNullGo( pInternalRW ); + IfFailGo( pInternalRW->Init( + const_cast<void*>(pData), + cbData, + (flags == ofRead) ? true : false) ); + IfFailGo( pInternalRW->QueryInterface(riid, ppIUnk) ); +ErrExit: + if (FAILED(hr)) + { + if (pInternalRW) + delete pInternalRW; + *ppIUnk = 0; + } + else if ( pInternalRW ) + pInternalRW->Release(); + return hr; +} // GetInternalWithRWFormat + + +//***************************************************************************** +// This function returns a IMDInternalImport interface based on the given +// public import interface i.e IMetaDataEmit or IMetaDataImport. +//***************************************************************************** +__checkReturn +STDAPI GetMDInternalInterfaceFromPublic( + IUnknown *pIUnkPublic, // [IN] Given public interface. Must be QI of IUnknown + REFIID riid, // [in] The interface desired. + void **ppIUnkInternal) // [out] Return interface on success. +{ + HRESULT hr = S_OK; + ReleaseHolder<IGetIMDInternalImport> pGetIMDInternalImport; + + // IMDInternalImport is the only internal import interface currently supported by + // this function. + _ASSERTE(riid == IID_IMDInternalImport && pIUnkPublic && ppIUnkInternal); + + if (riid != IID_IMDInternalImport || pIUnkPublic == NULL || ppIUnkInternal == NULL) + IfFailGo(E_INVALIDARG); + IfFailGo( pIUnkPublic->QueryInterface(IID_IGetIMDInternalImport, &pGetIMDInternalImport)); + IfFailGo( pGetIMDInternalImport->GetIMDInternalImport((IMDInternalImport **)ppIUnkInternal)); + +ErrExit: + if (FAILED(hr)) + { + if (ppIUnkInternal) + *ppIUnkInternal = 0; + } + return hr; +} // GetMDInternalInterfaceFromPublic + + +//***************************************************************************** +// This function returns the requested public interface based on the given +// internal import interface. It is caller's responsibility to Release ppIUnkPublic +//***************************************************************************** +__checkReturn +STDAPI GetMDPublicInterfaceFromInternal( + void *pIUnkInternal, // [IN] Given internal interface. + REFIID riid, // [in] The interface desired. + void **ppIUnkPublic) // [out] Return interface on success. +{ + HRESULT hr = S_OK; + IMDInternalImport *pInternalImport = 0;; + IUnknown *pIUnkPublic = NULL; + OptionValue optVal = { MDDupAll, MDRefToDefDefault, MDNotifyDefault, MDUpdateFull, MDErrorOutOfOrderDefault , MDThreadSafetyOn}; + RegMeta *pMeta = 0; + bool isLockedForWrite = false; + + + _ASSERTE(pIUnkInternal && ppIUnkPublic); + *ppIUnkPublic = 0; + + IfFailGo(VerifyNotWinMD((IUnknown*)pIUnkInternal, "GetMDPublicInterfaceFromInternal() not supported on .winmd files.")); + + IfFailGo(ConvertRO2RW((IUnknown*)pIUnkInternal, IID_IMDInternalImport, (void **)&pInternalImport)); + + pIUnkPublic = pInternalImport->GetCachedPublicInterface(TRUE); + if ( pIUnkPublic ) + { + // There is already a cached public interface. GetCachedPublicInterface already AddRef the returned + // public interface. We want to QueryInterface the riid... + // We are done! + hr = pIUnkPublic->QueryInterface(riid, ppIUnkPublic); + pIUnkPublic->Release(); + goto ErrExit; + } + + // grab the write lock when we are creating the corresponding regmeta for the public interface + _ASSERTE( pInternalImport->GetReaderWriterLock() != NULL ); + isLockedForWrite = true; + IfFailGo(pInternalImport->GetReaderWriterLock()->LockWrite()); + + // check again. Maybe someone else beat us to setting the public interface while we are waiting + // for the write lock. Don't need to grab the read lock since we already have the write lock. + *ppIUnkPublic = pInternalImport->GetCachedPublicInterface(FALSE); + if ( *ppIUnkPublic ) + { + // there is already a cached public interface. GetCachedPublicInterface already AddRef the returned + // public interface. + // We are done! + goto ErrExit; + } + + pMeta = new (nothrow) RegMeta(); + IfNullGo(pMeta); + IfFailGo(pMeta->SetOption(&optVal)); + IfFailGo( pMeta->InitWithStgdb((IUnknown*)pInternalImport, ((MDInternalRW*)pInternalImport)->GetMiniStgdb()) ); + IfFailGo( pMeta->QueryInterface(riid, ppIUnkPublic) ); + + // The following makes the public object and the internal object point to each other. + _ASSERTE( pMeta->GetReaderWriterLock() == NULL ); + IfFailGo( pMeta->SetCachedInternalInterface(pInternalImport) ); + IfFailGo( pInternalImport->SetCachedPublicInterface((IUnknown *) *ppIUnkPublic) ); + IfFailGo( pMeta->SetReaderWriterLock(pInternalImport->GetReaderWriterLock() )); + + // Add the new RegMeta to the cache. + IfFailGo( pMeta->AddToCache() ); + +ErrExit: + if (isLockedForWrite) + pInternalImport->GetReaderWriterLock()->UnlockWrite(); + + if (pInternalImport) + pInternalImport->Release(); + + if (FAILED(hr)) + { + if (pMeta) + delete pMeta; + *ppIUnkPublic = 0; + } + return hr; +} // GetMDPublicInterfaceFromInternal + +//***************************************************************************** +// Converts an internal MD import API into the read/write version of this API. +// This could support edit and continue, or modification of the metadata at +// runtime (say for profiling). +//***************************************************************************** +__checkReturn +STDAPI ConvertMDInternalImport( // S_OK, S_FALSE (no conversion), or error. + IMDInternalImport *pIMD, // [in] The metadata to be updated. + IMDInternalImport **ppIMD) // [out] Put the RW here. +{ + HRESULT hr; // A result. + IMDInternalImportENC *pENC = NULL; // ENC interface on the metadata. + + _ASSERTE(pIMD != NULL); + _ASSERTE(ppIMD != NULL); + + // Test whether the MD is already RW. + hr = pIMD->QueryInterface(IID_IMDInternalImportENC, (void**)&pENC); + if (FAILED(hr)) + { // Not yet RW, so do the conversion. + IfFailGo(ConvertRO2RW(pIMD, IID_IMDInternalImport, (void**)ppIMD)); + } + else + { // Already converted; give back same pointer. + *ppIMD = pIMD; + hr = S_FALSE; + } + +ErrExit: + if (pENC) + pENC->Release(); + return hr; +} // ConvertMDInternalImport + + + + + +//***************************************************************************** +// Constructor +//***************************************************************************** +MDInternalRW::MDInternalRW() + : m_pStgdb(NULL), + m_cRefs(1), + m_fOwnStgdb(false), + m_pUnk(NULL), + m_pUserUnk(NULL), + m_pIMetaDataHelper(NULL), + m_pSemReadWrite(NULL), + m_fOwnSem(false) +{ +} // MDInternalRW::MDInternalRW + + + +//***************************************************************************** +// Destructor +//***************************************************************************** +MDInternalRW::~MDInternalRW() +{ + HRESULT hr = S_OK; + + LOCKWRITENORET(); + + // This should have worked if we've cached the internal interface in the past + _ASSERTE(SUCCEEDED(hr) || m_pIMetaDataHelper == NULL || m_pIMetaDataHelper->GetCachedInternalInterface(false) == NULL); + + + if (SUCCEEDED(hr)) + { + + if (m_pIMetaDataHelper) + { + // The internal object is going away before the public object. + // If the internal object owns the reader writer lock, transfer the ownership + // to the public object and clear the cached internal interface from the public interface. + + m_pIMetaDataHelper->SetCachedInternalInterface(NULL); + m_pIMetaDataHelper = NULL; + m_fOwnSem = false; + + } + + UNLOCKWRITE(); + } + if (m_pSemReadWrite && m_fOwnSem) + delete m_pSemReadWrite; + + if ( m_pStgdb && m_fOwnStgdb ) + { + // we own the stgdb so we need to uninit and delete it. + m_pStgdb->Uninit(); + delete m_pStgdb; + } + if ( m_pUserUnk ) + m_pUserUnk->Release(); + if ( m_pUnk ) + m_pUnk->Release(); +} // MDInternalRW::~MDInternalRW + + +//***************************************************************************** +// Set or clear the cached public interfaces. +// NOTE:: Caller should take a Write lock on the reader writer lock. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::SetCachedPublicInterface(IUnknown * pUnk) +{ + IMetaDataHelper * pHelper = NULL; + HRESULT hr = S_OK; + + if (pUnk != NULL) + { + // Internal object and public regmeta should be one to one mapping!! + _ASSERTE(m_pIMetaDataHelper == NULL); + + IfFailRet(pUnk->QueryInterface(IID_IMetaDataHelper, (void **) &pHelper)); + _ASSERTE(pHelper != NULL); + + m_pIMetaDataHelper = pHelper; + pHelper->Release(); + } + else + { + // public object is going away before the internal object. If we don't own the + // reader writer lock, just take over the ownership. + m_fOwnSem = true; + m_pIMetaDataHelper = NULL; + } + return hr; +} // MDInternalRW::SetCachedPublicInterface + + +//***************************************************************************** +// Clear the cached public interfaces. +//***************************************************************************** +IUnknown * MDInternalRW::GetCachedPublicInterface(BOOL fWithLock) +{ + HRESULT hr = S_OK; + IUnknown * pRet = NULL; + if (fWithLock) + { + LOCKREAD(); + + pRet = m_pIMetaDataHelper; + if (pRet != NULL) + pRet->AddRef(); + } + else + { + pRet = m_pIMetaDataHelper; + if (pRet != NULL) + pRet->AddRef(); + } + +ErrExit: + return pRet; +} // MDInternalRW::GetCachedPublicInterface + + +//***************************************************************************** +// Get the Reader-Writer lock +//***************************************************************************** +UTSemReadWrite * MDInternalRW::GetReaderWriterLock() +{ + return getReaderWriterLock(); +} // MDInternalRW::GetReaderWriterLock + +//***************************************************************************** +// IUnknown +//***************************************************************************** +ULONG MDInternalRW::AddRef() +{ + return InterlockedIncrement(&m_cRefs); +} // MDInternalRW::AddRef + +ULONG MDInternalRW::Release() +{ + ULONG cRef; + + cRef = InterlockedDecrement(&m_cRefs); + if (cRef == 0) + { + LOG((LOGMD, "MDInternalRW(0x%08x)::destruction\n", this)); + delete this; + } + return cRef; +} // MDInternalRW::Release + +__checkReturn +HRESULT MDInternalRW::QueryInterface(REFIID riid, void **ppUnk) +{ + *ppUnk = 0; + + if (riid == IID_IUnknown) + *ppUnk = (IUnknown *) (IMDInternalImport *) this; + + else if (riid == IID_IMDInternalImport) + *ppUnk = (IMDInternalImport *) this; + + else if (riid == IID_IMDInternalImportENC) + *ppUnk = (IMDInternalImportENC *) this; + + else if (riid == IID_IMDCommon) + *ppUnk = (IMDCommon*)this; + + else + return E_NOINTERFACE; + AddRef(); + return S_OK; +} // MDInternalRW::QueryInterface + + +//***************************************************************************** +// Initialize +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::Init( + LPVOID pData, // points to meta data section in memory + ULONG cbData, // count of bytes in pData + int bReadOnly) // Is it open for read only? +{ + CLiteWeightStgdbRW * pStgdb = NULL; + HRESULT hr = NOERROR; + OptionValue optVal = { MDDupAll, MDRefToDefDefault, MDNotifyDefault, MDUpdateFull, MDErrorOutOfOrderDefault, MDThreadSafetyOn }; + + pStgdb = new (nothrow) CLiteWeightStgdbRW; + IfNullGo(pStgdb); + + m_pSemReadWrite = new (nothrow) UTSemReadWrite; + IfNullGo(m_pSemReadWrite); + IfFailGo(m_pSemReadWrite->Init()); + m_fOwnSem = true; + INDEBUG(pStgdb->m_MiniMd.Debug_SetLock(m_pSemReadWrite);) + + IfFailGo(pStgdb->InitOnMem(cbData, (BYTE*)pData, bReadOnly)); + IfFailGo(pStgdb->m_MiniMd.SetOption(&optVal)); + m_tdModule = COR_GLOBAL_PARENT_TOKEN; + m_fOwnStgdb = true; + m_pStgdb = pStgdb; + +ErrExit: + // clean up upon errors + if (FAILED(hr) && (pStgdb != NULL)) + { + delete pStgdb; + } + return hr; +} // MDInternalRW::Init + + +//***************************************************************************** +// Initialize with an existing RegMeta. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::InitWithStgdb( + IUnknown *pUnk, // The IUnknow that owns the life time for the existing stgdb + CLiteWeightStgdbRW *pStgdb) // existing lightweight stgdb +{ + // m_fOwnSem should be false because this is the case where we create the internal interface given a public + // interface. + + m_tdModule = COR_GLOBAL_PARENT_TOKEN; + m_fOwnStgdb = false; + m_pStgdb = pStgdb; + + // remember the owner of the light weight stgdb + // AddRef it to ensure the lifetime + // + m_pUnk = pUnk; + m_pUnk->AddRef(); + return NOERROR; +} // MDInternalRW::InitWithStgdb + + +//***************************************************************************** +// Initialize with an existing RO format +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::InitWithRO( + MDInternalRO * pRO, + int bReadOnly) +{ + CLiteWeightStgdbRW * pStgdb = NULL; + HRESULT hr = NOERROR; + OptionValue optVal = { MDDupAll, MDRefToDefDefault, MDNotifyDefault, MDUpdateFull, MDErrorOutOfOrderDefault, MDThreadSafetyOn }; + + pStgdb = new (nothrow) CLiteWeightStgdbRW; + IfNullGo(pStgdb); + + m_pSemReadWrite = new (nothrow) UTSemReadWrite; + IfNullGo(m_pSemReadWrite); + IfFailGo(m_pSemReadWrite->Init()); + m_fOwnSem = true; + INDEBUG(pStgdb->m_MiniMd.Debug_SetLock(m_pSemReadWrite);) + + IfFailGo(pStgdb->m_MiniMd.InitOnRO(&pRO->m_LiteWeightStgdb.m_MiniMd, bReadOnly)); + IfFailGo(pStgdb->m_MiniMd.SetOption(&optVal)); + m_tdModule = COR_GLOBAL_PARENT_TOKEN; + m_fOwnStgdb = true; + pStgdb->m_pvMd=pRO->m_LiteWeightStgdb.m_pvMd; + pStgdb->m_cbMd=pRO->m_LiteWeightStgdb.m_cbMd; + m_pStgdb = pStgdb; + +ErrExit: + // clean up upon errors + if (FAILED(hr) && (pStgdb != NULL)) + { + delete pStgdb; + } + return hr; +} // MDInternalRW::InitWithRO + + +#ifndef DACCESS_COMPILE +//***************************************************************************** +// Given a scope, determine whether imported from a typelib. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::TranslateSigWithScope( + IMDInternalImport* pAssemImport, // [IN] import assembly scope. + const void* pbHashValue, // [IN] hash value for the import assembly. + ULONG cbHashValue, // [IN] count of bytes in the hash value. + PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope + ULONG cbSigBlob, // [IN] count of bytes of signature + IMetaDataAssemblyEmit* pAssemEmit, // [IN] assembly emit scope. + IMetaDataEmit* emit, // [IN] emit interface + CQuickBytes* pqkSigEmit, // [OUT] buffer to hold translated signature + ULONG* pcbSig) // [OUT] count of bytes in the translated signature +{ + return TranslateSigHelper( + this, + pAssemImport, + pbHashValue, + cbHashValue, + pbSigBlob, + cbSigBlob, + pAssemEmit, + emit, + pqkSigEmit, + pcbSig); +} // MDInternalRW::TranslateSigWithScope + +__checkReturn +HRESULT MDInternalRW::GetTypeDefRefTokenInTypeSpec(// return S_FALSE if enclosing type does not have a token + mdTypeSpec tkTypeSpec, // [IN] TypeSpec token to look at + mdToken *tkEnclosedToken) // [OUT] The enclosed type token +{ + return m_pStgdb->m_MiniMd.GetTypeDefRefTokenInTypeSpec(tkTypeSpec, tkEnclosedToken); +}// MDInternalRW::GetTypeDefRefTokenInTypeSpec + +//***************************************************************************** +// Given a scope, return the number of tokens in a given table +//***************************************************************************** +ULONG MDInternalRW::GetCountWithTokenKind( // return hresult + DWORD tkKind) // [IN] pass in the kind of token. +{ + ULONG ulCount = 0; + HRESULT hr = S_OK; + LOCKREAD(); + + switch (tkKind) + { + case mdtTypeDef: + ulCount = m_pStgdb->m_MiniMd.getCountTypeDefs(); + // Remove global typedef from the count of typedefs (and handle the case where there is no global typedef) + if (ulCount > 0) + ulCount--; + break; + case mdtTypeRef: + ulCount = m_pStgdb->m_MiniMd.getCountTypeRefs(); + break; + case mdtMethodDef: + ulCount = m_pStgdb->m_MiniMd.getCountMethods(); + break; + case mdtFieldDef: + ulCount = m_pStgdb->m_MiniMd.getCountFields(); + break; + case mdtMemberRef: + ulCount = m_pStgdb->m_MiniMd.getCountMemberRefs(); + break; + case mdtInterfaceImpl: + ulCount = m_pStgdb->m_MiniMd.getCountInterfaceImpls(); + break; + case mdtParamDef: + ulCount = m_pStgdb->m_MiniMd.getCountParams(); + break; + case mdtFile: + ulCount = m_pStgdb->m_MiniMd.getCountFiles(); + break; + case mdtAssemblyRef: + ulCount = m_pStgdb->m_MiniMd.getCountAssemblyRefs(); + break; + case mdtAssembly: + ulCount = m_pStgdb->m_MiniMd.getCountAssemblys(); + break; + case mdtCustomAttribute: + ulCount = m_pStgdb->m_MiniMd.getCountCustomAttributes(); + break; + case mdtModule: + ulCount = m_pStgdb->m_MiniMd.getCountModules(); + break; + case mdtPermission: + ulCount = m_pStgdb->m_MiniMd.getCountDeclSecuritys(); + break; + case mdtSignature: + ulCount = m_pStgdb->m_MiniMd.getCountStandAloneSigs(); + break; + case mdtEvent: + ulCount = m_pStgdb->m_MiniMd.getCountEvents(); + break; + case mdtProperty: + ulCount = m_pStgdb->m_MiniMd.getCountPropertys(); + break; + case mdtModuleRef: + ulCount = m_pStgdb->m_MiniMd.getCountModuleRefs(); + break; + case mdtTypeSpec: + ulCount = m_pStgdb->m_MiniMd.getCountTypeSpecs(); + break; + case mdtExportedType: + ulCount = m_pStgdb->m_MiniMd.getCountExportedTypes(); + break; + case mdtManifestResource: + ulCount = m_pStgdb->m_MiniMd.getCountManifestResources(); + break; + case mdtGenericParam: + ulCount = m_pStgdb->m_MiniMd.getCountGenericParams(); + break; + case mdtGenericParamConstraint: + ulCount = m_pStgdb->m_MiniMd.getCountGenericParamConstraints(); + break; + case mdtMethodSpec: + ulCount = m_pStgdb->m_MiniMd.getCountMethodSpecs(); + break; + default: +#ifdef _DEBUG + if(REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_AssertOnBadImageFormat, 1)) + _ASSERTE(!"Invalid Blob Offset"); +#endif + ulCount = 0; + break; + } + +ErrExit: + + return ulCount; +} // MDInternalRW::GetCountWithTokenKind +#endif //!DACCESS_COMPILE + + +//******************************************************************************* +// Enumerator helpers +//******************************************************************************* + + +//***************************************************************************** +// enumerator init for typedef +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::EnumTypeDefInit( // return hresult + HENUMInternal *phEnum) // [OUT] buffer to fill for enumerator data +{ + HRESULT hr = NOERROR; + LOCKREAD(); + + _ASSERTE(phEnum); + + memset(phEnum, 0, sizeof(HENUMInternal)); + phEnum->m_tkKind = mdtTypeDef; + + if ( m_pStgdb->m_MiniMd.HasDelete() ) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + + phEnum->m_tkKind = mdtTypeDef; + for (ULONG index = 2; index <= m_pStgdb->m_MiniMd.getCountTypeDefs(); index ++ ) + { + TypeDefRec *pTypeDefRec; + IfFailGo(m_pStgdb->m_MiniMd.GetTypeDefRecord(index, &pTypeDefRec)); + LPCSTR szTypeDefName; + IfFailGo(m_pStgdb->m_MiniMd.getNameOfTypeDef(pTypeDefRec, &szTypeDefName)); + if (IsDeletedName(szTypeDefName)) + { + continue; + } + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(index, mdtTypeDef) ) ); + } + } + else + { + phEnum->m_EnumType = MDSimpleEnum; + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountTypeDefs(); + + // Skip over the global model typedef + // + // phEnum->u.m_ulCur : the current rid that is not yet enumerated + // phEnum->u.m_ulStart : the first rid that will be returned by enumerator + // phEnum->u.m_ulEnd : the last rid that will be returned by enumerator + phEnum->u.m_ulStart = phEnum->u.m_ulCur = 2; + phEnum->u.m_ulEnd = phEnum->m_ulCount + 1; + if (phEnum->m_ulCount > 0) + phEnum->m_ulCount --; + } +ErrExit: + + return hr; +} // MDInternalRW::EnumTypeDefInit + + +//***************************************************************************** +// get the number of typedef in a scope +//***************************************************************************** +ULONG MDInternalRW::EnumTypeDefGetCount( + HENUMInternal *phEnum) // [IN] the enumerator to retrieve information +{ + _ASSERTE(phEnum->m_tkKind == mdtTypeDef); + return phEnum->m_ulCount; +} // MDInternalRW::EnumTypeDefGetCount + + +//***************************************************************************** +// enumerator for typedef +//***************************************************************************** +bool MDInternalRW::EnumTypeDefNext( // return hresult + HENUMInternal *phEnum, // [IN] input enum + mdTypeDef *ptd) // [OUT] return token +{ + return EnumNext( + phEnum, + ptd); +} // MDInternalRW::EnumTypeDefNext + + +//***************************************** +// Reset the enumerator to the beginning. +//***************************************** +void MDInternalRW::EnumTypeDefReset( + HENUMInternal *phEnum) // [IN] the enumerator to be reset +{ + EnumReset(phEnum); +} // MDInternalRW::EnumTypeDefReset + + +//***************************************** +// Close the enumerator. Only for read/write mode that we need to close the cursor. +// Hopefully with readonly mode, it will be a no-op +//***************************************** +void MDInternalRW::EnumTypeDefClose( + HENUMInternal *phEnum) // [IN] the enumerator to be closed +{ + EnumClose(phEnum); +} // MDInternalRW::EnumTypeDefClose + +#ifndef DACCESS_COMPILE +//***************************************************************************** +// enumerator init for MethodImpl +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::EnumMethodImplInit( // return hresult + mdTypeDef td, // [IN] TypeDef over which to scope the enumeration. + HENUMInternal *phEnumBody, // [OUT] buffer to fill for enumerator data for MethodBody tokens. + HENUMInternal *phEnumDecl) // [OUT] buffer to fill for enumerator data for MethodDecl tokens. +{ + HRESULT hr = NOERROR; + int ridCur; + mdToken tkMethodBody; + mdToken tkMethodDecl; + MethodImplRec *pRec; + HENUMInternal hEnum; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(td) == mdtTypeDef && !IsNilToken(td)); + _ASSERTE(phEnumBody && phEnumDecl); + + memset(phEnumBody, 0, sizeof(HENUMInternal)); + memset(phEnumDecl, 0, sizeof(HENUMInternal)); + memset(&hEnum, 0, sizeof(HENUMInternal)); + + HENUMInternal::InitDynamicArrayEnum(phEnumBody); + HENUMInternal::InitDynamicArrayEnum(phEnumDecl); + + phEnumBody->m_tkKind = (TBL_MethodImpl << 24); + phEnumDecl->m_tkKind = (TBL_MethodImpl << 24); + + // Get the range of rids. + IfFailGo( m_pStgdb->m_MiniMd.FindMethodImplHelper(td, &hEnum) ); + + while (HENUMInternal::EnumNext(&hEnum, (mdToken *)&ridCur)) + { + // Get the MethodBody and MethodDeclaration tokens for the current + // MethodImpl record. + IfFailGo(m_pStgdb->m_MiniMd.GetMethodImplRecord(ridCur, &pRec)); + tkMethodBody = m_pStgdb->m_MiniMd.getMethodBodyOfMethodImpl(pRec); + tkMethodDecl = m_pStgdb->m_MiniMd.getMethodDeclarationOfMethodImpl(pRec); + + // Add the Method body/declaration pairs to the Enum + IfFailGo( HENUMInternal::AddElementToEnum(phEnumBody, tkMethodBody ) ); + IfFailGo( HENUMInternal::AddElementToEnum(phEnumDecl, tkMethodDecl ) ); + } +ErrExit: + HENUMInternal::ClearEnum(&hEnum); + return hr; +} // MDInternalRW::EnumMethodImplInit + +//***************************************************************************** +// get the number of MethodImpls in a scope +//***************************************************************************** +ULONG MDInternalRW::EnumMethodImplGetCount( + HENUMInternal *phEnumBody, // [IN] MethodBody enumerator. + HENUMInternal *phEnumDecl) // [IN] MethodDecl enumerator. +{ + _ASSERTE((phEnumBody->m_tkKind >> 24) == TBL_MethodImpl && + (phEnumDecl->m_tkKind >> 24) == TBL_MethodImpl); + _ASSERTE(phEnumBody->m_EnumType == MDDynamicArrayEnum && + phEnumDecl->m_EnumType == MDDynamicArrayEnum); + _ASSERTE(phEnumBody->m_ulCount == phEnumDecl->m_ulCount); + + return phEnumBody->m_ulCount; +} // MDInternalRW::EnumMethodImplGetCount + + +//***************************************************************************** +// enumerator for MethodImpl. +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::EnumMethodImplNext( // return hresult + HENUMInternal *phEnumBody, // [IN] input enum for MethodBody + HENUMInternal *phEnumDecl, // [IN] input enum for MethodDecl + mdToken *ptkBody, // [OUT] return token for MethodBody + mdToken *ptkDecl) // [OUT] return token for MethodDecl +{ + _ASSERTE((phEnumBody->m_tkKind >> 24) == TBL_MethodImpl && + (phEnumDecl->m_tkKind >> 24) == TBL_MethodImpl); + _ASSERTE(phEnumBody->m_EnumType == MDDynamicArrayEnum && + phEnumDecl->m_EnumType == MDDynamicArrayEnum); + _ASSERTE(phEnumBody->m_ulCount == phEnumDecl->m_ulCount); + _ASSERTE(ptkBody && ptkDecl); + + EnumNext(phEnumBody, ptkBody); + return EnumNext(phEnumDecl, ptkDecl) ? S_OK : S_FALSE; +} // MDInternalRW::EnumMethodImplNext + +//***************************************** +// Reset the enumerator to the beginning. +//***************************************** +void MDInternalRW::EnumMethodImplReset( + HENUMInternal *phEnumBody, // [IN] MethodBody enumerator. + HENUMInternal *phEnumDecl) // [IN] MethodDecl enumerator. +{ + _ASSERTE((phEnumBody->m_tkKind >> 24) == TBL_MethodImpl && + (phEnumDecl->m_tkKind >> 24) == TBL_MethodImpl); + _ASSERTE(phEnumBody->m_EnumType == MDDynamicArrayEnum && + phEnumDecl->m_EnumType == MDDynamicArrayEnum); + _ASSERTE(phEnumBody->m_ulCount == phEnumDecl->m_ulCount); + + EnumReset(phEnumBody); + EnumReset(phEnumDecl); +} // MDInternalRW::EnumMethodImplReset + + +//***************************************** +// Close the enumerator. +//***************************************** +void MDInternalRW::EnumMethodImplClose( + HENUMInternal *phEnumBody, // [IN] MethodBody enumerator. + HENUMInternal *phEnumDecl) // [IN] MethodDecl enumerator. +{ + _ASSERTE((phEnumBody->m_tkKind >> 24) == TBL_MethodImpl && + (phEnumDecl->m_tkKind >> 24) == TBL_MethodImpl); + _ASSERTE(phEnumBody->m_EnumType == MDDynamicArrayEnum && + phEnumDecl->m_EnumType == MDDynamicArrayEnum); + _ASSERTE(phEnumBody->m_ulCount == phEnumDecl->m_ulCount); + + EnumClose(phEnumBody); + EnumClose(phEnumDecl); +} // MDInternalRW::EnumMethodImplClose +#endif //!DACCESS_COMPILE + +//****************************************************************************** +// enumerator for global functions +//****************************************************************************** +__checkReturn +HRESULT MDInternalRW::EnumGlobalFunctionsInit( // return hresult + HENUMInternal *phEnum) // [OUT] buffer to fill for enumerator data +{ + return EnumInit(mdtMethodDef, m_tdModule, phEnum); +} // MDInternalRW::EnumGlobalFunctionsInit + + +//****************************************************************************** +// enumerator for global fields +//****************************************************************************** +__checkReturn +HRESULT MDInternalRW::EnumGlobalFieldsInit( // return hresult + HENUMInternal *phEnum) // [OUT] buffer to fill for enumerator data +{ + return EnumInit(mdtFieldDef, m_tdModule, phEnum); +} // MDInternalRW::EnumGlobalFieldsInit + + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +//***************************************** +// Enumerator initializer +//***************************************** +__checkReturn +HRESULT MDInternalRW::EnumInit( // return S_FALSE if record not found + DWORD tkKind, // [IN] which table to work on + mdToken tkParent, // [IN] token to scope the search + HENUMInternal *phEnum) // [OUT] the enumerator to fill +{ + HRESULT hr = S_OK; + ULONG ulStart, ulEnd, ulMax; + ULONG index; + LOCKREAD(); + + // Vars for query. + _ASSERTE(phEnum); + memset(phEnum, 0, sizeof(HENUMInternal)); + + // cache the tkKind and the scope + phEnum->m_tkKind = TypeFromToken(tkKind); + + TypeDefRec *pRec; + + phEnum->m_EnumType = MDSimpleEnum; + + switch (TypeFromToken(tkKind)) + { + case mdtFieldDef: + IfFailGo(m_pStgdb->m_MiniMd.GetTypeDefRecord(RidFromToken(tkParent), &pRec)); + ulStart = m_pStgdb->m_MiniMd.getFieldListOfTypeDef(pRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndFieldListOfTypeDef(RidFromToken(tkParent), &ulEnd)); + if ( m_pStgdb->m_MiniMd.HasDelete() ) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + FieldRec *pFieldRec; + RID fieldRid; + IfFailGo(m_pStgdb->m_MiniMd.GetFieldRid(index, &fieldRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetFieldRecord(index, &pFieldRec)); + LPCSTR szFieldName; + IfFailGo(m_pStgdb->m_MiniMd.getNameOfField(pFieldRec, &szFieldName)); + if (IsFdRTSpecialName(pFieldRec->GetFlags()) && IsDeletedName(szFieldName) ) + { + continue; + } + IfFailGo(m_pStgdb->m_MiniMd.GetFieldRid(index, &fieldRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(fieldRid, mdtFieldDef))); + } + } + else if (m_pStgdb->m_MiniMd.HasIndirectTable(TBL_Field)) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + RID fieldRid; + IfFailGo(m_pStgdb->m_MiniMd.GetFieldRid(index, &fieldRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(fieldRid, mdtFieldDef))); + } + } + else + { + HENUMInternal::InitSimpleEnum( mdtFieldDef, ulStart, ulEnd, phEnum); + } + break; + + case mdtMethodDef: + IfFailGo(m_pStgdb->m_MiniMd.GetTypeDefRecord(RidFromToken(tkParent), &pRec)); + ulStart = m_pStgdb->m_MiniMd.getMethodListOfTypeDef(pRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndMethodListOfTypeDef(RidFromToken(tkParent), &ulEnd)); + if ( m_pStgdb->m_MiniMd.HasDelete() ) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + MethodRec *pMethodRec; + RID methodRid; + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRid(index, &methodRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRecord(methodRid, &pMethodRec)); + LPCSTR szMethodName; + IfFailGo(m_pStgdb->m_MiniMd.getNameOfMethod(pMethodRec, &szMethodName)); + if (IsMdRTSpecialName(pMethodRec->GetFlags()) && IsDeletedName(szMethodName)) + { + continue; + } + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRid(index, &methodRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(methodRid, mdtMethodDef))); + } + } + else if (m_pStgdb->m_MiniMd.HasIndirectTable(TBL_Method)) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + RID methodRid; + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRid(index, &methodRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(methodRid, mdtMethodDef))); + } + } + else + { + HENUMInternal::InitSimpleEnum( mdtMethodDef, ulStart, ulEnd, phEnum); + } + break; + + case mdtInterfaceImpl: + if (!m_pStgdb->m_MiniMd.IsSorted(TBL_InterfaceImpl) && !m_pStgdb->m_MiniMd.IsTableVirtualSorted(TBL_InterfaceImpl)) + { + // virtual sort table will be created! + // + CONVERT_READ_TO_WRITE_LOCK(); + } + + IfFailGo( m_pStgdb->m_MiniMd.GetInterfaceImplsForTypeDef(RidFromToken(tkParent), &ulStart, &ulEnd) ); + if ( m_pStgdb->m_MiniMd.IsSorted( TBL_InterfaceImpl ) ) + { + // These are index to InterfaceImpl table directly + HENUMInternal::InitSimpleEnum( mdtInterfaceImpl, ulStart, ulEnd, phEnum); + } + else + { + // These are index to VirtualSort table. Skip over one level direction. + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(m_pStgdb->m_MiniMd.GetInterfaceImplRid(index), mdtInterfaceImpl) ) ); + } + } + break; + + case mdtGenericParam: + //@todo: deal with non-sorted case. + + if (TypeFromToken(tkParent) == mdtTypeDef) + { + IfFailGo(m_pStgdb->m_MiniMd.getGenericParamsForTypeDef( + RidFromToken(tkParent), + &phEnum->u.m_ulEnd, + &(phEnum->u.m_ulStart))); + } + else + { + IfFailGo(m_pStgdb->m_MiniMd.getGenericParamsForMethodDef( + RidFromToken(tkParent), + &phEnum->u.m_ulEnd, + &(phEnum->u.m_ulStart))); + } + break; + + case mdtGenericParamConstraint: + if ( !m_pStgdb->m_MiniMd.IsSorted(TBL_GenericParamConstraint) && !m_pStgdb->m_MiniMd.IsTableVirtualSorted(TBL_GenericParamConstraint)) + { + // virtual sort table will be created! + // + CONVERT_READ_TO_WRITE_LOCK(); + } + + IfFailGo( m_pStgdb->m_MiniMd.GetGenericParamConstraintsForToken(RidFromToken(tkParent), &ulStart, &ulEnd) ); + if ( m_pStgdb->m_MiniMd.IsSorted( TBL_GenericParamConstraint ) ) + { + // These are index to GenericParamConstraint table directly + HENUMInternal::InitSimpleEnum( mdtGenericParamConstraint, ulStart, ulEnd, phEnum); + } + else + { + // These are index to VirtualSort table. Skip over one level direction. + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(m_pStgdb->m_MiniMd.GetGenericParamConstraintRid(index), mdtGenericParamConstraint) ) ); + } + } + break; + + case mdtProperty: + RID ridPropertyMap; + PropertyMapRec *pPropertyMapRec; + + // get the starting/ending rid of properties of this typedef + IfFailGo(m_pStgdb->m_MiniMd.FindPropertyMapFor(RidFromToken(tkParent), &ridPropertyMap)); + if (!InvalidRid(ridPropertyMap)) + { + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyMapRecord(ridPropertyMap, &pPropertyMapRec)); + ulStart = m_pStgdb->m_MiniMd.getPropertyListOfPropertyMap(pPropertyMapRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndPropertyListOfPropertyMap(ridPropertyMap, &ulEnd)); + ulMax = m_pStgdb->m_MiniMd.getCountPropertys() + 1; + if(ulStart == 0) ulStart = 1; + if(ulEnd > ulMax) ulEnd = ulMax; + if(ulStart > ulEnd) ulStart = ulEnd; + if ( m_pStgdb->m_MiniMd.HasDelete() ) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + PropertyRec *pPropertyRec; + RID propertyRid; + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRid(index, &propertyRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRecord( + propertyRid, + &pPropertyRec)); + LPCSTR szPropertyName; + IfFailGo(m_pStgdb->m_MiniMd.getNameOfProperty(pPropertyRec, &szPropertyName)); + if (IsPrRTSpecialName(pPropertyRec->GetPropFlags()) && IsDeletedName(szPropertyName)) + { + continue; + } + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRid(index, &propertyRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(propertyRid, mdtProperty))); + } + } + else if (m_pStgdb->m_MiniMd.HasIndirectTable(TBL_Property)) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + RID propertyRid; + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRid(index, &propertyRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(propertyRid, mdtProperty))); + } + } + else + { + HENUMInternal::InitSimpleEnum( mdtProperty, ulStart, ulEnd, phEnum); + } + } + break; + + case mdtEvent: + RID ridEventMap; + EventMapRec *pEventMapRec; + + // get the starting/ending rid of events of this typedef + IfFailGo(m_pStgdb->m_MiniMd.FindEventMapFor(RidFromToken(tkParent), &ridEventMap)); + if (!InvalidRid(ridEventMap)) + { + IfFailGo(m_pStgdb->m_MiniMd.GetEventMapRecord(ridEventMap, &pEventMapRec)); + ulStart = m_pStgdb->m_MiniMd.getEventListOfEventMap(pEventMapRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndEventListOfEventMap(ridEventMap, &ulEnd)); + ulMax = m_pStgdb->m_MiniMd.getCountEvents() + 1; + if(ulStart == 0) ulStart = 1; + if(ulEnd > ulMax) ulEnd = ulMax; + if(ulStart > ulEnd) ulStart = ulEnd; + if ( m_pStgdb->m_MiniMd.HasDelete() ) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + EventRec *pEventRec; + RID eventRid; + IfFailGo(m_pStgdb->m_MiniMd.GetEventRid(index, &eventRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetEventRecord(eventRid, &pEventRec)); + LPCSTR szEventName; + IfFailGo(m_pStgdb->m_MiniMd.getNameOfEvent(pEventRec, &szEventName)); + if (IsEvRTSpecialName(pEventRec->GetEventFlags()) && IsDeletedName(szEventName)) + { + continue; + } + IfFailGo(m_pStgdb->m_MiniMd.GetEventRid(index, &eventRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(eventRid, mdtEvent))); + } + } + else if (m_pStgdb->m_MiniMd.HasIndirectTable(TBL_Event)) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + RID eventRid; + IfFailGo(m_pStgdb->m_MiniMd.GetEventRid(index, &eventRid)); + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(eventRid, mdtEvent) ) ); + } + } + else + { + HENUMInternal::InitSimpleEnum( mdtEvent, ulStart, ulEnd, phEnum); + } + } + break; + + case mdtParamDef: + _ASSERTE(TypeFromToken(tkParent) == mdtMethodDef); + + MethodRec *pMethodRec; + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(tkParent), &pMethodRec)); + + // figure out the start rid and end rid of the parameter list of this methoddef + ulStart = m_pStgdb->m_MiniMd.getParamListOfMethod(pMethodRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndParamListOfMethod(RidFromToken(tkParent), &ulEnd)); + if (m_pStgdb->m_MiniMd.HasIndirectTable(TBL_Param)) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + RID paramRid; + IfFailGo(m_pStgdb->m_MiniMd.GetParamRid(index, ¶mRid)); + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(paramRid, mdtParamDef))); + } + } + else + { + HENUMInternal::InitSimpleEnum( mdtParamDef, ulStart, ulEnd, phEnum); + } + break; + + case mdtCustomAttribute: + if (!m_pStgdb->m_MiniMd.IsSorted(TBL_CustomAttribute) && !m_pStgdb->m_MiniMd.IsTableVirtualSorted(TBL_CustomAttribute)) + { + // CA's map table table will be sorted! + // + CONVERT_READ_TO_WRITE_LOCK(); + } + + IfFailGo( m_pStgdb->m_MiniMd.GetCustomAttributeForToken(tkParent, &ulStart, &ulEnd) ); + if ( m_pStgdb->m_MiniMd.IsSorted( TBL_CustomAttribute ) ) + { + // These are index to CustomAttribute table directly + HENUMInternal::InitSimpleEnum( mdtCustomAttribute, ulStart, ulEnd, phEnum); + } + else + { + // These are index to VirtualSort table. Skip over one level direction. + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = ulStart; index < ulEnd; index ++ ) + { + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(m_pStgdb->m_MiniMd.GetCustomAttributeRid(index), mdtCustomAttribute) ) ); + } + } + break; + case mdtAssemblyRef: + _ASSERTE(IsNilToken(tkParent)); + phEnum->u.m_ulStart = 1; + phEnum->u.m_ulEnd = m_pStgdb->m_MiniMd.getCountAssemblyRefs() + 1; + break; + case mdtFile: + _ASSERTE(IsNilToken(tkParent)); + phEnum->u.m_ulStart = 1; + phEnum->u.m_ulEnd = m_pStgdb->m_MiniMd.getCountFiles() + 1; + break; + case mdtExportedType: + _ASSERTE(IsNilToken(tkParent)); + if ( m_pStgdb->m_MiniMd.HasDelete() ) + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + + phEnum->m_tkKind = mdtExportedType; + for (ULONG typeindex = 1; typeindex <= m_pStgdb->m_MiniMd.getCountExportedTypes(); typeindex ++ ) + { + ExportedTypeRec *pExportedTypeRec; + IfFailGo(m_pStgdb->m_MiniMd.GetExportedTypeRecord(typeindex, &pExportedTypeRec)); + LPCSTR szTypeName; + IfFailGo(m_pStgdb->m_MiniMd.getTypeNameOfExportedType(pExportedTypeRec, &szTypeName)); + if (IsDeletedName(szTypeName)) + { + continue; + } + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(typeindex, mdtExportedType) ) ); + } + } + else + { + phEnum->u.m_ulStart = 1; + phEnum->u.m_ulEnd = m_pStgdb->m_MiniMd.getCountExportedTypes() + 1; + } + break; + case mdtManifestResource: + _ASSERTE(IsNilToken(tkParent)); + phEnum->u.m_ulStart = 1; + phEnum->u.m_ulEnd = m_pStgdb->m_MiniMd.getCountManifestResources() + 1; + break; + case mdtModuleRef: + _ASSERTE(IsNilToken(tkParent)); + phEnum->u.m_ulStart = 1; + phEnum->u.m_ulEnd = m_pStgdb->m_MiniMd.getCountModuleRefs() + 1; + break; + default: + _ASSERTE(!"ENUM INIT not implemented for the uncompressed format!"); + IfFailGo(E_NOTIMPL); + break; + } + + // If the count is negative, the metadata is corrupted somehow. + if (phEnum->u.m_ulEnd < phEnum->u.m_ulStart) + IfFailGo(CLDB_E_FILE_CORRUPT); + + phEnum->m_ulCount = phEnum->u.m_ulEnd - phEnum->u.m_ulStart; + phEnum->u.m_ulCur = phEnum->u.m_ulStart; +ErrExit: + // we are done + + return hr; +} // MDInternalRW::EnumInit +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +//***************************************** +// Enumerator initializer +//***************************************** +__checkReturn +HRESULT MDInternalRW::EnumAllInit( // return S_FALSE if record not found + DWORD tkKind, // [IN] which table to work on + HENUMInternal *phEnum) // [OUT] the enumerator to fill +{ + HRESULT hr = S_OK; + LOCKREAD(); + + // Vars for query. + _ASSERTE(phEnum); + memset(phEnum, 0, sizeof(HENUMInternal)); + + // cache the tkKind and the scope + phEnum->m_tkKind = TypeFromToken(tkKind); + phEnum->m_EnumType = MDSimpleEnum; + + switch (TypeFromToken(tkKind)) + { + case mdtTypeRef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountTypeRefs(); + break; + + case mdtMemberRef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountMemberRefs(); + break; + + case mdtSignature: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountStandAloneSigs(); + break; + + case mdtMethodDef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountMethods(); + break; + + case mdtMethodSpec: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountMethodSpecs(); + break; + + case mdtFieldDef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountFields(); + break; + + case mdtTypeSpec: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountTypeSpecs(); + break; + + case mdtAssemblyRef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountAssemblyRefs(); + break; + + case mdtModuleRef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountModuleRefs(); + break; + + case mdtTypeDef: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountTypeDefs(); + break; + + case mdtFile: + phEnum->m_ulCount = m_pStgdb->m_MiniMd.getCountFiles(); + break; + + default: + _ASSERTE(!"Bad token kind!"); + break; + } + phEnum->u.m_ulStart = phEnum->u.m_ulCur = 1; + phEnum->u.m_ulEnd = phEnum->m_ulCount + 1; + +ErrExit: + // we are done + + return hr; +} // MDInternalRW::EnumAllInit + + +//***************************************** +// get the count +//***************************************** +ULONG MDInternalRW::EnumGetCount( + HENUMInternal *phEnum) // [IN] the enumerator to retrieve information +{ + _ASSERTE(phEnum); + return phEnum->m_ulCount; +} // MDInternalRW::EnumGetCount + +//***************************************** +// Get next value contained in the enumerator +//***************************************** +bool MDInternalRW::EnumNext( + HENUMInternal *phEnum, // [IN] the enumerator to retrieve information + mdToken *ptk) // [OUT] token to scope the search +{ + _ASSERTE(phEnum && ptk); + if (phEnum->u.m_ulCur >= phEnum->u.m_ulEnd) + return false; + + if ( phEnum->m_EnumType == MDSimpleEnum ) + { + *ptk = phEnum->u.m_ulCur | phEnum->m_tkKind; + phEnum->u.m_ulCur++; + } + else + { + TOKENLIST *pdalist = (TOKENLIST *)&(phEnum->m_cursor); + + _ASSERTE( phEnum->m_EnumType == MDDynamicArrayEnum ); + *ptk = *( pdalist->Get(phEnum->u.m_ulCur++) ); + } + return true; +} // MDInternalRW::EnumNext + + +//***************************************** +// Reset the enumerator to the beginning. +//***************************************** +void MDInternalRW::EnumReset( + HENUMInternal *phEnum) // [IN] the enumerator to be reset +{ + _ASSERTE(phEnum); + _ASSERTE( phEnum->m_EnumType == MDSimpleEnum || phEnum->m_EnumType == MDDynamicArrayEnum); + + phEnum->u.m_ulCur = phEnum->u.m_ulStart; +} // MDInternalRW::EnumReset + + +//***************************************** +// Close the enumerator. Only for read/write mode that we need to close the cursor. +// Hopefully with readonly mode, it will be a no-op +//***************************************** +void MDInternalRW::EnumClose( + HENUMInternal *phEnum) // [IN] the enumerator to be closed +{ + _ASSERTE( phEnum->m_EnumType == MDSimpleEnum || + phEnum->m_EnumType == MDDynamicArrayEnum || + phEnum->m_EnumType == MDCustomEnum ); + if (phEnum->m_EnumType == MDDynamicArrayEnum) + HENUMInternal::ClearEnum(phEnum); +} // MDInternalRW::EnumClose + + +#ifndef DACCESS_COMPILE +//--------------------------------------------------------------------------------------- +// +// Initialize enumerator of PermissionSets. +// +// Return Value: +// CLDB_E_RECORD_NOTFOUND ... If record not found. +// S_OK and empty enumeration ... If tkParent is nil token and Action is dclActionNil. +// +__checkReturn +HRESULT +MDInternalRW::EnumPermissionSetsInit( + mdToken tkParent, // [IN] Token to scope the search. + CorDeclSecurity Action, // [IN] Action to scope the search. + HENUMInternal *phEnum) // [OUT] Enumerator to fill. +{ + HRESULT hr = S_OK; + DeclSecurityRec *pDecl; + RID ridCur; + RID ridEnd; + + LOCKREAD(); + + _ASSERTE(phEnum != NULL); + + phEnum->m_EnumType = MDSimpleEnum; + + if (!m_pStgdb->m_MiniMd.IsSorted(TBL_DeclSecurity) && !m_pStgdb->m_MiniMd.IsTableVirtualSorted(TBL_DeclSecurity)) + { + // DeclSecurity lookup table might be created! + CONVERT_READ_TO_WRITE_LOCK(); + } + + // This call requires write-lock if the table is not sorted and doesn't have VirtualSort: + IfFailGo(m_pStgdb->m_MiniMd.GetDeclSecurityForToken(tkParent, &ridCur, &ridEnd)); + if (m_pStgdb->m_MiniMd.IsSorted(TBL_DeclSecurity)) + { + // These are index to DeclSecurity table directly + if (Action != dclActionNil) + { + for (; ridCur < ridEnd; ridCur++) + { + IfFailGo(m_pStgdb->m_MiniMd.GetDeclSecurityRecord(ridCur, &pDecl)); + if (Action == m_pStgdb->m_MiniMd.getActionOfDeclSecurity(pDecl)) + { + // found a match + HENUMInternal::InitSimpleEnum(mdtPermission, ridCur, ridCur + 1, phEnum); + goto ErrExit; + } + } + hr = CLDB_E_RECORD_NOTFOUND; + } + else + { + HENUMInternal::InitSimpleEnum(mdtPermission, ridCur, ridEnd, phEnum); + } + } + else + { + // These are index to VirtualSort table. Skip over one level direction. + if (Action != dclActionNil) + { + RID ridActual; + + for (; ridCur < ridEnd; ridCur++) + { + ridActual = m_pStgdb->m_MiniMd.GetDeclSecurityRid(ridCur); + IfFailGo(m_pStgdb->m_MiniMd.GetDeclSecurityRecord(ridActual, &pDecl)); + if (Action == m_pStgdb->m_MiniMd.getActionOfDeclSecurity(pDecl)) + { + // found a match + HENUMInternal::InitSimpleEnum(mdtPermission, ridActual, ridActual + 1, phEnum); + goto ErrExit; + } + } + hr = CLDB_E_RECORD_NOTFOUND; + } + else + { + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (; ridCur < ridEnd; ridCur++) + { + IfFailGo(HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(m_pStgdb->m_MiniMd.GetDeclSecurityRid(ridCur), mdtPermission))); + } + } + } + +ErrExit: + return hr; +} // MDInternalRW::EnumPermissionSetsInit +#endif //!DACCESS_COMPILE + +//***************************************** +// Enumerator initializer for CustomAttributes +//***************************************** +__checkReturn +HRESULT MDInternalRW::EnumCustomAttributeByNameInit(// return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + LPCSTR szName, // [IN] CustomAttribute's name to scope the search + HENUMInternal *phEnum) // [OUT] the enumerator to fill +{ + return m_pStgdb->m_MiniMd.CommonEnumCustomAttributeByName(tkParent, szName, false, phEnum); +} // MDInternalRW::EnumCustomAttributeByNameInit + +//***************************************** +// Enumerator for CustomAttributes which doesn't +// allocate any memory +//***************************************** +__checkReturn +HRESULT MDInternalRW::SafeAndSlowEnumCustomAttributeByNameInit(// return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + LPCSTR szName, // [IN] CustomAttribute's name to scope the search + HENUMInternal *phEnum) // [OUT] The enumerator +{ + _ASSERTE(phEnum); + + HRESULT hr; + ULONG ridStart, ridEnd; // Loop start and endpoints. + + // Get the list of custom values for the parent object. + if (m_pStgdb->m_MiniMd.IsSorted(TBL_CustomAttribute)) + { + IfFailRet(m_pStgdb->m_MiniMd.getCustomAttributeForToken(tkParent, &ridEnd, &ridStart)); + // If found none, done. + if (ridStart == 0) + goto NoMatch; + } + else + { + // linear scan of entire table. + ridEnd = m_pStgdb->m_MiniMd.getCountCustomAttributes() + 1; + if (ridEnd == 1) + goto NoMatch; + + ridStart = 1; + } + phEnum->m_EnumType = MDCustomEnum; + phEnum->m_tkKind = mdtCustomAttribute; + phEnum->u.m_ulStart = ridStart; + phEnum->u.m_ulEnd = ridEnd; + phEnum->u.m_ulCur = ridStart; + + return S_OK; + +NoMatch: + return S_FALSE; +} // MDInternalRW::SafeAndSlowEnumCustomAttributeByNameInit + +__checkReturn +HRESULT MDInternalRW::SafeAndSlowEnumCustomAttributeByNameNext(// return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + LPCSTR szName, // [IN] CustomAttribute's name to scope the search + HENUMInternal *phEnum, // [IN] The enumerator + mdCustomAttribute *mdAttribute) // [OUT] The custom attribute that was found +{ + _ASSERTE(phEnum); + _ASSERTE(phEnum->m_EnumType == MDCustomEnum); + _ASSERTE(phEnum->m_tkKind == mdtCustomAttribute); + + // Look for one with the given name. + for (; phEnum->u.m_ulCur < phEnum->u.m_ulEnd; ++phEnum->u.m_ulCur) + { + if (S_OK == m_pStgdb->m_MiniMd.CompareCustomAttribute( tkParent, szName, phEnum->u.m_ulCur)) + { + // If here, found a match. + *mdAttribute = TokenFromRid(phEnum->u.m_ulCur, mdtCustomAttribute); + phEnum->u.m_ulCur++; + return S_OK; + } + } + // No match... + return S_FALSE; +}// MDInternalRW::SafeAndSlowEnumCustomAttributeByNameNext + +//***************************************** +// Nagivator helper to navigate back to the parent token given a token. +// For example, given a memberdef token, it will return the containing typedef. +// +// the mapping is as following: +// ---given child type---------parent type +// mdMethodDef mdTypeDef +// mdFieldDef mdTypeDef +// mdInterfaceImpl mdTypeDef +// mdParam mdMethodDef +// mdProperty mdTypeDef +// mdEvent mdTypeDef +// +//***************************************** +__checkReturn +HRESULT MDInternalRW::GetParentToken( + mdToken tkChild, // [IN] given child token + mdToken *ptkParent) // [OUT] returning parent +{ + HRESULT hr = NOERROR; + LOCKREAD(); + + _ASSERTE(ptkParent); + + switch (TypeFromToken(tkChild)) + { + case mdtTypeDef: + { + RID rid; + if (!m_pStgdb->m_MiniMd.IsSorted(TBL_NestedClass) && !m_pStgdb->m_MiniMd.IsTableVirtualSorted(TBL_NestedClass)) + { + // NestedClass table is not sorted. + CONVERT_READ_TO_WRITE_LOCK(); + } + IfFailGo(m_pStgdb->m_MiniMd.FindNestedClassFor(RidFromToken(tkChild), &rid)); + + if (InvalidRid(rid)) + { + // If not found, the *ptkParent has to be left unchanged! (callers depend on that) + hr = S_OK; + } + else + { + NestedClassRec *pRecord; + IfFailGo(m_pStgdb->m_MiniMd.GetNestedClassRecord(rid, &pRecord)); + *ptkParent = m_pStgdb->m_MiniMd.getEnclosingClassOfNestedClass(pRecord); + } + break; + } + case mdtMethodDef: + IfFailGo(m_pStgdb->m_MiniMd.FindParentOfMethodHelper(RidFromToken(tkChild), ptkParent)); + RidToToken(*ptkParent, mdtTypeDef); + break; + case mdtMethodSpec: + { + MethodSpecRec *pRec; + IfFailGo(m_pStgdb->m_MiniMd.GetMethodSpecRecord(RidFromToken(tkChild), &pRec)); + *ptkParent = m_pStgdb->m_MiniMd.getMethodOfMethodSpec(pRec); + } + break; + case mdtFieldDef: + IfFailGo(m_pStgdb->m_MiniMd.FindParentOfFieldHelper(RidFromToken(tkChild), ptkParent)); + RidToToken(*ptkParent, mdtTypeDef); + break; + case mdtParamDef: + IfFailGo(m_pStgdb->m_MiniMd.FindParentOfParamHelper(RidFromToken(tkChild), ptkParent)); + RidToToken(*ptkParent, mdtMethodDef); + break; + case mdtMemberRef: + { + MemberRefRec *pRec; + IfFailGo(m_pStgdb->m_MiniMd.GetMemberRefRecord(RidFromToken(tkChild), &pRec)); + *ptkParent = m_pStgdb->m_MiniMd.getClassOfMemberRef(pRec); + break; + } + case mdtCustomAttribute: + { + CustomAttributeRec *pRec; + IfFailGo(m_pStgdb->m_MiniMd.GetCustomAttributeRecord(RidFromToken(tkChild), &pRec)); + *ptkParent = m_pStgdb->m_MiniMd.getParentOfCustomAttribute(pRec); + break; + } + case mdtEvent: + IfFailGo(m_pStgdb->m_MiniMd.FindParentOfEventHelper(tkChild, ptkParent)); + break; + case mdtProperty: + IfFailGo(m_pStgdb->m_MiniMd.FindParentOfPropertyHelper(tkChild, ptkParent)); + break; + default: + _ASSERTE(!"NYI: for compressed format!"); + break; + } +ErrExit: + return hr; +} // MDInternalRW::GetParentToken + +//***************************************************************************** +// Get information about a CustomAttribute. +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetCustomAttributeProps( // S_OK or error. + mdCustomAttribute at, // The attribute. + mdToken *pTkType) // Put attribute type here. +{ + HRESULT hr; + // Getting the custom value prop with a token, no need to lock! + + _ASSERTE(TypeFromToken(at) == mdtCustomAttribute); + + // Do a linear search on compressed version as we do not want to + // depend on ICR. + // + CustomAttributeRec *pCustomAttributeRec; + + IfFailRet(m_pStgdb->m_MiniMd.GetCustomAttributeRecord(RidFromToken(at), &pCustomAttributeRec)); + *pTkType = m_pStgdb->m_MiniMd.getTypeOfCustomAttribute(pCustomAttributeRec); + return S_OK; +} // MDInternalRW::GetCustomAttributeProps + + +//***************************************************************************** +// return custom value +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetCustomAttributeAsBlob( + mdCustomAttribute cv, // [IN] given custom attribute token + void const **ppBlob, // [OUT] return the pointer to internal blob + ULONG *pcbSize) // [OUT] return the size of the blob +{ + // Getting the custom value prop with a token, no need to lock! + HRESULT hr; + _ASSERTE(ppBlob && pcbSize && TypeFromToken(cv) == mdtCustomAttribute); + + CustomAttributeRec *pCustomAttributeRec; + + IfFailRet(m_pStgdb->m_MiniMd.GetCustomAttributeRecord(RidFromToken(cv), &pCustomAttributeRec)); + + IfFailRet(m_pStgdb->m_MiniMd.getValueOfCustomAttribute(pCustomAttributeRec, reinterpret_cast<const BYTE **>(ppBlob), pcbSize)); + return S_OK; +} // MDInternalRW::GetCustomAttributeAsBlob + +//***************************************************************************** +// Helper function to lookup and retrieve a CustomAttribute. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetCustomAttributeByName( // S_OK or error. + mdToken tkObj, // [IN] Object with Custom Attribute. + LPCUTF8 szName, // [IN] Name of desired Custom Attribute. + __deref_out_bcount(*pcbData) const void **ppData, // [OUT] Put pointer to data here. + __out ULONG *pcbData) // [OUT] Put size of data here. +{ + HRESULT hr = S_OK; + LOCKREADIFFAILRET(); + return m_pStgdb->m_MiniMd.CommonGetCustomAttributeByName(tkObj, szName, ppData, pcbData); +} // MDInternalRW::GetCustomAttributeByName + +//***************************************************************************** +// return the name of a custom attribute +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetNameOfCustomAttribute( // S_OK or error. + mdCustomAttribute mdAttribute, // [IN] The Custom Attribute + LPCUTF8 *pszNamespace, // [OUT] Namespace of Custom Attribute. + LPCUTF8 *pszName) // [OUT] Name of Custom Attribute. +{ + HRESULT hr = S_OK; + LOCKREADIFFAILRET(); + hr = m_pStgdb->m_MiniMd.CommonGetNameOfCustomAttribute(RidFromToken(mdAttribute), pszNamespace, pszName); + return (hr == S_FALSE) ? E_FAIL : hr; +} // MDInternalRW::GetNameOfCustomAttribute + +//***************************************************************************** +// return scope properties +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetScopeProps( + LPCSTR *pszName, // [OUT] scope name + GUID *pmvid) // [OUT] version id +{ + HRESULT hr = S_OK; + LOCKREAD(); + + ModuleRec *pModuleRec; + + // there is only one module record + IfFailGo(m_pStgdb->m_MiniMd.GetModuleRecord(1, &pModuleRec)); + + if (pmvid != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getMvidOfModule(pModuleRec, pmvid)); + } + + if (pszName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfModule(pModuleRec, pszName)); + } + +ErrExit: + return hr; +} // MDInternalRW::GetScopeProps + +//***************************************************************************** +// This function gets the "built for" version of a metadata scope. +// NOTE: if the scope has never been saved, it will not have a built-for +// version, and an empty string will be returned. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetVersionString( // S_OK or error. + LPCSTR *pVer) // [OUT] Put version string here. +{ + HRESULT hr = NOERROR; + + if (m_pStgdb->m_pvMd != NULL) + { + // For convenience, get a pointer to the version string. + // @todo: get from alternate locations when there is no STOREAGESIGNATURE. + *pVer = reinterpret_cast<const char*>(reinterpret_cast<const STORAGESIGNATURE*>(m_pStgdb->m_pvMd)->pVersion); + } + else + { // No string. + *pVer = NULL; + } + + return hr; +} // MDInternalRW::GetVersionString + +//***************************************************************************** +// Find a given member in a TypeDef (typically a class). +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindMethodDef(// S_OK or error. + mdTypeDef classdef, // The owning class of the member. + LPCSTR szName, // Name of the member in utf8. + PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of COM+ signature + ULONG cbSigBlob, // [IN] count of bytes in the signature blob + mdMethodDef *pmethoddef) // Put MemberDef token here. +{ + HRESULT hr = S_OK; + LOCKREAD(); + + _ASSERTE(szName && pmethoddef); + + IfFailGo(ImportHelper::FindMethod(&(m_pStgdb->m_MiniMd), + classdef, + szName, + pvSigBlob, + cbSigBlob, + pmethoddef)); + +ErrExit: + return hr; +} + +//***************************************************************************** +// Find a given member in a TypeDef (typically a class). +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindMethodDefUsingCompare(// S_OK or error. + mdTypeDef classdef, // The owning class of the member. + LPCSTR szName, // Name of the member in utf8. + PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of COM+ signature + ULONG cbSigBlob, // [IN] count of bytes in the signature blob + PSIGCOMPARE pSignatureCompare, // [IN] Routine to compare signatures + void* pSignatureArgs, // [IN] Additional info to supply the compare function + mdMethodDef *pmethoddef) // Put MemberDef token here. +{ + HRESULT hr = S_OK; + LOCKREAD(); + + _ASSERTE(szName && pmethoddef); + + IfFailGo(ImportHelper::FindMethod(&(m_pStgdb->m_MiniMd), + classdef, + szName, + pvSigBlob, + cbSigBlob, + pmethoddef, + 0, + pSignatureCompare, + pSignatureArgs)); + +ErrExit: + return hr; +} + +//***************************************************************************** +// Find a given param of a Method. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindParamOfMethod(// S_OK or error. + mdMethodDef md, // [IN] The owning method of the param. + ULONG iSeq, // [IN] The sequence # of the param. + mdParamDef *pparamdef) // [OUT] Put ParamDef token here. +{ + ParamRec *pParamRec; + RID ridStart, ridEnd; + HRESULT hr = NOERROR; + MethodRec *pMethodRec = NULL; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(md) == mdtMethodDef && pparamdef); + + // get the methoddef record + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(md), &pMethodRec)); + + // figure out the start rid and end rid of the parameter list of this methoddef + ridStart = m_pStgdb->m_MiniMd.getParamListOfMethod(pMethodRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndParamListOfMethod(RidFromToken(md), &ridEnd)); + + // loop through each param + // + for (; ridStart < ridEnd; ridStart++) + { + RID paramRid; + IfFailGo(m_pStgdb->m_MiniMd.GetParamRid(ridStart, ¶mRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetParamRecord(paramRid, &pParamRec)); + if (iSeq == m_pStgdb->m_MiniMd.getSequenceOfParam( pParamRec) ) + { + // parameter has the sequence number matches what we are looking for + *pparamdef = TokenFromRid(paramRid, mdtParamDef ); + goto ErrExit; + } + } + hr = CLDB_E_RECORD_NOTFOUND; +ErrExit: + + return hr; +} // MDInternalRW::FindParamOfMethod + + + +//***************************************************************************** +// return a pointer which points to meta data's internal string +// return the the type name in utf8 +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNameOfTypeDef( // return hresult + mdTypeDef classdef, // given typedef + LPCSTR* pszname, // pointer to an internal UTF8 string + LPCSTR* psznamespace) // pointer to the namespace. +{ + // No need to lock this method. + HRESULT hr; + + if (pszname != NULL) + { + *pszname = NULL; + } + if (psznamespace != NULL) + { + *psznamespace = NULL; + } + + if (TypeFromToken(classdef) == mdtTypeDef) + { + TypeDefRec *pTypeDefRec; + IfFailRet(m_pStgdb->m_MiniMd.GetTypeDefRecord(RidFromToken(classdef), &pTypeDefRec)); + + if (pszname != NULL) + { + IfFailRet(m_pStgdb->m_MiniMd.getNameOfTypeDef(pTypeDefRec, pszname)); + } + + if (psznamespace != NULL) + { + IfFailRet(m_pStgdb->m_MiniMd.getNamespaceOfTypeDef(pTypeDefRec, psznamespace)); + } + return S_OK; + } + + _ASSERTE(!"Invalid argument(s) of GetNameOfTypeDef"); + return CLDB_E_INTERNALERROR; +} // MDInternalRW::GetNameOfTypeDef + +//***************************************************************************** +// return pDual indicating if the given TypeDef is marked as a Dual interface +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetIsDualOfTypeDef(// return hresult + mdTypeDef classdef, // given classdef + ULONG *pDual) // [OUT] return dual flag here. +{ + ULONG iFace=0; // Iface type. + HRESULT hr; // A result. + + // no need to lock at this level + + hr = GetIfaceTypeOfTypeDef(classdef, &iFace); + if (hr == S_OK) + *pDual = (iFace == ifDual); + else + *pDual = 1; + + return hr; +} // MDInternalRW::GetIsDualOfTypeDef + +__checkReturn +HRESULT MDInternalRW::GetIfaceTypeOfTypeDef( + mdTypeDef classdef, // [IN] given classdef. + ULONG *pIface) // [OUT] 0=dual, 1=vtable, 2=dispinterface +{ + HRESULT hr; // A result. + const BYTE *pVal; // The custom value. + ULONG cbVal; // Size of the custom value. + ULONG ItfType = DEFAULT_COM_INTERFACE_TYPE; // Set the interface type to the default. + + // all of the public functions that it calls have proper locked + + // If the value is not present, the class is assumed dual. + hr = GetCustomAttributeByName(classdef, INTEROP_INTERFACETYPE_TYPE, (const void**)&pVal, &cbVal); + if (hr == S_OK) + { + _ASSERTE("The ComInterfaceType custom attribute is invalid" && cbVal); + _ASSERTE("ComInterfaceType custom attribute does not have the right format" && (*pVal == 0x01) && (*(pVal + 1) == 0x00)); + ItfType = *(pVal + 2); + if (ItfType >= ifLast) + ItfType = DEFAULT_COM_INTERFACE_TYPE; + } + + // Set the return value. + *pIface = ItfType; + + return hr; +} // MDInternalRW::GetIfaceTypeOfTypeDef + +//***************************************************************************** +// Given a methoddef, return a pointer to methoddef's name +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNameOfMethodDef( + mdMethodDef md, + LPCSTR *pszMethodName) +{ + // name of method will not change. So no need to lock + HRESULT hr; + MethodRec *pMethodRec; + *pszMethodName = NULL; + IfFailRet(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(md), &pMethodRec)); + IfFailRet(m_pStgdb->m_MiniMd.getNameOfMethod(pMethodRec, pszMethodName)); + return S_OK; +} // MDInternalRW::GetNameOfMethodDef + + +//***************************************************************************** +// Given a methoddef, return a pointer to methoddef's signature and methoddef's name +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNameAndSigOfMethodDef( + mdMethodDef methoddef, // [IN] given memberdef + PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to a blob value of COM+ signature + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + LPCSTR *pszMethodName) +{ + HRESULT hr; + // we don't need lock here because name and signature will not change + + // Output parameter should not be NULL + _ASSERTE(ppvSigBlob && pcbSigBlob); + _ASSERTE(TypeFromToken(methoddef) == mdtMethodDef); + + MethodRec *pMethodRec; + *pszMethodName = NULL; + *ppvSigBlob = NULL; + *ppvSigBlob = NULL; + IfFailRet(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(methoddef), &pMethodRec)); + IfFailRet(m_pStgdb->m_MiniMd.getSignatureOfMethod(pMethodRec, ppvSigBlob, pcbSigBlob)); + + return GetNameOfMethodDef(methoddef, pszMethodName); +} // MDInternalRW::GetNameAndSigOfMethodDef + + +//***************************************************************************** +// Given a FieldDef, return a pointer to FieldDef's name in UTF8 +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNameOfFieldDef( // return hresult + mdFieldDef fd, // given field + LPCSTR *pszFieldName) +{ + // we don't need lock here because name of field will not change + HRESULT hr; + + FieldRec *pFieldRec; + *pszFieldName = NULL; + IfFailRet(m_pStgdb->m_MiniMd.GetFieldRecord(RidFromToken(fd), &pFieldRec)); + IfFailRet(m_pStgdb->m_MiniMd.getNameOfField(pFieldRec, pszFieldName)); + return S_OK; +} // MDInternalRW::GetNameOfFieldDef + + +//***************************************************************************** +// Given a classdef, return a pointer to classdef's name in UTF8 +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNameOfTypeRef( // return TypeDef's name + mdTypeRef classref, // [IN] given typeref + LPCSTR *psznamespace, // [OUT] return typeref name + LPCSTR *pszname) // [OUT] return typeref namespace +{ + _ASSERTE(TypeFromToken(classref) == mdtTypeRef); + HRESULT hr; + + *psznamespace = NULL; + *pszname = NULL; + + // we don't need lock here because name of a typeref will not change + + TypeRefRec *pTypeRefRec; + IfFailRet(m_pStgdb->m_MiniMd.GetTypeRefRecord(RidFromToken(classref), &pTypeRefRec)); + IfFailRet(m_pStgdb->m_MiniMd.getNamespaceOfTypeRef(pTypeRefRec, psznamespace)); + IfFailRet(m_pStgdb->m_MiniMd.getNameOfTypeRef(pTypeRefRec, pszname)); + return S_OK; +} // MDInternalRW::GetNameOfTypeRef + +//***************************************************************************** +// return the resolutionscope of typeref +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetResolutionScopeOfTypeRef( + mdTypeRef classref, // given classref + mdToken *ptkResolutionScope) +{ + HRESULT hr = S_OK; + TypeRefRec *pTypeRefRec = NULL; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(classref) == mdtTypeRef && RidFromToken(classref)); + + IfFailGo(m_pStgdb->m_MiniMd.GetTypeRefRecord(RidFromToken(classref), &pTypeRefRec)); + _ASSERTE(hr == S_OK); + *ptkResolutionScope = m_pStgdb->m_MiniMd.getResolutionScopeOfTypeRef(pTypeRefRec); + return S_OK; + +ErrExit: + *ptkResolutionScope = mdTokenNil; + return hr; +} // MDInternalRW::GetResolutionScopeOfTypeRef + +//***************************************************************************** +// Given a name, find the corresponding TypeRef. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindTypeRefByName( // S_OK or error. + LPCSTR szNamespace, // [IN] Namespace for the TypeRef. + LPCSTR szName, // [IN] Name of the TypeRef. + mdToken tkResolutionScope, // [IN] Resolution Scope fo the TypeRef. + mdTypeRef *ptk) // [OUT] TypeRef token returned. +{ + HRESULT hr = NOERROR; + ULONG cTypeRefRecs; + TypeRefRec *pTypeRefRec; + LPCUTF8 szNamespaceTmp; + LPCUTF8 szNameTmp; + mdToken tkRes; + + LOCKREAD(); + _ASSERTE(ptk); + + // initialize the output parameter + *ptk = mdTypeRefNil; + + // Treat no namespace as empty string. + if (!szNamespace) + szNamespace = ""; + + // It is a linear search here. Do we want to instantiate the name hash? + cTypeRefRecs = m_pStgdb->m_MiniMd.getCountTypeRefs(); + + for (ULONG i = 1; i <= cTypeRefRecs; i++) + { + IfFailGo(m_pStgdb->m_MiniMd.GetTypeRefRecord(i, &pTypeRefRec)); + + tkRes = m_pStgdb->m_MiniMd.getResolutionScopeOfTypeRef(pTypeRefRec); + if (IsNilToken(tkRes)) + { + if (!IsNilToken(tkResolutionScope)) + continue; + } + else if (tkRes != tkResolutionScope) + continue; + + IfFailGo(m_pStgdb->m_MiniMd.getNamespaceOfTypeRef(pTypeRefRec, &szNamespaceTmp)); + if (strcmp(szNamespace, szNamespaceTmp)) + continue; + + IfFailGo(m_pStgdb->m_MiniMd.getNameOfTypeRef(pTypeRefRec, &szNameTmp)); + if (!strcmp(szNameTmp, szName)) + { + *ptk = TokenFromRid(i, mdtTypeRef); + goto ErrExit; + } + } + + // cannot find the typedef + hr = CLDB_E_RECORD_NOTFOUND; +ErrExit: + return hr; +} // MDInternalRW::FindTypeRefByName + +//***************************************************************************** +// return flags for a given class +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetTypeDefProps( + mdTypeDef td, // given classdef + DWORD *pdwAttr, // return flags on class + mdToken *ptkExtends) // [OUT] Put base class TypeDef/TypeRef here. +{ + HRESULT hr = S_OK; + TypeDefRec *pTypeDefRec = NULL; + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.GetTypeDefRecord(RidFromToken(td), &pTypeDefRec)); + + if (ptkExtends) + { + *ptkExtends = m_pStgdb->m_MiniMd.getExtendsOfTypeDef(pTypeDefRec); + } + if (pdwAttr) + { + *pdwAttr = m_pStgdb->m_MiniMd.getFlagsOfTypeDef(pTypeDefRec); + } + +ErrExit: + + return hr; +} // MDInternalRW::GetTypeDefProps + + +//***************************************************************************** +// return guid pointer to MetaData internal guid pool given a given class +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetItemGuid( // return hresult + mdToken tkObj, // given item. + CLSID *pGuid) // [OUT] put guid here. +{ + + HRESULT hr; // A result. + const BYTE *pBlob = NULL; // Blob with dispid. + ULONG cbBlob; // Length of blob. + WCHAR wzBlob[40]; // Wide char format of guid. + int ix; // Loop control. + + // Get the GUID, if any. + hr = GetCustomAttributeByName(tkObj, INTEROP_GUID_TYPE, (const void**)&pBlob, &cbBlob); + if (hr != S_FALSE) + { + // Should be in format. Total length == 41 + // <0x0001><0x24>01234567-0123-0123-0123-001122334455<0x0000> + if ((cbBlob != 41) || (GET_UNALIGNED_VAL16(pBlob) != 1)) + IfFailGo(E_INVALIDARG); + for (ix=1; ix<=36; ++ix) + wzBlob[ix] = pBlob[ix+2]; + wzBlob[0] = '{'; + wzBlob[37] = '}'; + wzBlob[38] = 0; + hr = IIDFromString(wzBlob, pGuid); + } + else + *pGuid = GUID_NULL; + +ErrExit: + return hr; +} // MDInternalRW::GetItemGuid + +//***************************************************************************** +// // get enclosing class of NestedClass +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNestedClassProps( + mdTypeDef tkNestedClass, // [IN] NestedClass token. + mdTypeDef *ptkEnclosingClass) // [OUT] EnclosingClass token. +{ + HRESULT hr = NOERROR; + RID rid; + + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return COR_E_STACKOVERFLOW); + + LOCKREAD(); + + if (!m_pStgdb->m_MiniMd.IsSorted(TBL_NestedClass) && !m_pStgdb->m_MiniMd.IsTableVirtualSorted(TBL_NestedClass)) + { + // NestedClass table is not sorted. + CONVERT_READ_TO_WRITE_LOCK(); + } + + // This is a binary search thus we need to grap a read lock. Or this table + // might be sorted underneath our feet. + + _ASSERTE(TypeFromToken(tkNestedClass) == mdtTypeDef && ptkEnclosingClass); + + IfFailGo(m_pStgdb->m_MiniMd.FindNestedClassFor(RidFromToken(tkNestedClass), &rid)); + + if (InvalidRid(rid)) + { + hr = CLDB_E_RECORD_NOTFOUND; + } + else + { + NestedClassRec *pRecord; + IfFailGo(m_pStgdb->m_MiniMd.GetNestedClassRecord(rid, &pRecord)); + *ptkEnclosingClass = m_pStgdb->m_MiniMd.getEnclosingClassOfNestedClass(pRecord); + } + +ErrExit: + END_SO_INTOLERANT_CODE; + return hr; +} // MDInternalRW::GetNestedClassProps + +//******************************************************************************* +// Get count of Nested classes given the enclosing class. +//******************************************************************************* +__checkReturn +HRESULT +MDInternalRW::GetCountNestedClasses( // return count of Nested classes. + mdTypeDef tkEnclosingClass, // [IN]Enclosing class. + ULONG *pcNestedClassesCount) +{ + HRESULT hr; + ULONG ulCount; + ULONG ulRetCount = 0; + NestedClassRec *pRecord; + + _ASSERTE(TypeFromToken(tkEnclosingClass) == mdtTypeDef && !IsNilToken(tkEnclosingClass)); + + *pcNestedClassesCount = 0; + + ulCount = m_pStgdb->m_MiniMd.getCountNestedClasss(); + + for (ULONG i = 1; i <= ulCount; i++) + { + IfFailRet(m_pStgdb->m_MiniMd.GetNestedClassRecord(i, &pRecord)); + if (tkEnclosingClass == m_pStgdb->m_MiniMd.getEnclosingClassOfNestedClass(pRecord)) + ulRetCount++; + } + *pcNestedClassesCount = ulRetCount; + return S_OK; +} // MDInternalRW::GetCountNestedClasses + +//******************************************************************************* +// Return array of Nested classes given the enclosing class. +//******************************************************************************* +__checkReturn +HRESULT +MDInternalRW::GetNestedClasses( // Return actual count. + mdTypeDef tkEnclosingClass, // [IN] Enclosing class. + mdTypeDef *rNestedClasses, // [OUT] Array of nested class tokens. + ULONG ulNestedClasses, // [IN] Size of array. + ULONG *pcNestedClasses) +{ + HRESULT hr; + ULONG ulCount; + ULONG ulRetCount = 0; + NestedClassRec *pRecord; + + _ASSERTE(TypeFromToken(tkEnclosingClass) == mdtTypeDef && + !IsNilToken(tkEnclosingClass)); + + *pcNestedClasses = 0; + + ulCount = m_pStgdb->m_MiniMd.getCountNestedClasss(); + + for (ULONG i = 1; i <= ulCount; i++) + { + IfFailRet(m_pStgdb->m_MiniMd.GetNestedClassRecord(i, &pRecord)); + if (tkEnclosingClass == m_pStgdb->m_MiniMd.getEnclosingClassOfNestedClass(pRecord)) + { + if (ovadd_le(ulRetCount, 1, ulNestedClasses)) // ulRetCount is 0 based. + rNestedClasses[ulRetCount] = m_pStgdb->m_MiniMd.getNestedClassOfNestedClass(pRecord); + ulRetCount++; + } + } + *pcNestedClasses = ulRetCount; + return S_OK; +} // MDInternalRW::GetNestedClasses + +//******************************************************************************* +// return the ModuleRef properties +//******************************************************************************* +__checkReturn +HRESULT MDInternalRW::GetModuleRefProps( // return hresult + mdModuleRef mur, // [IN] moduleref token + LPCSTR *pszName) // [OUT] buffer to fill with the moduleref name +{ + _ASSERTE(TypeFromToken(mur) == mdtModuleRef); + _ASSERTE(pszName); + + HRESULT hr = S_OK; + ModuleRefRec *pModuleRefRec = NULL; + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.GetModuleRefRecord(RidFromToken(mur), &pModuleRefRec)); + IfFailGo(m_pStgdb->m_MiniMd.getNameOfModuleRef(pModuleRefRec, pszName)); + +ErrExit: + + return hr; +} // MDInternalRW::GetModuleRefProps + + + +//***************************************************************************** +// Given a scope and a methoddef, return a pointer to methoddef's signature +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetSigOfMethodDef( + mdMethodDef methoddef, // given a methoddef + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + PCCOR_SIGNATURE *ppSig) +{ + // Output parameter should not be NULL + _ASSERTE(pcbSigBlob); + _ASSERTE(TypeFromToken(methoddef) == mdtMethodDef); + + HRESULT hr; + // We don't change MethodDef signature. No need to lock. + + MethodRec *pMethodRec; + *ppSig = NULL; + *pcbSigBlob = 0; + IfFailRet(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(methoddef), &pMethodRec)); + IfFailRet(m_pStgdb->m_MiniMd.getSignatureOfMethod(pMethodRec, ppSig, pcbSigBlob)); + return S_OK; +} // MDInternalRW::GetSigOfMethodDef + + +//***************************************************************************** +// Given a scope and a fielddef, return a pointer to fielddef's signature +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetSigOfFieldDef( + mdFieldDef fielddef, // given a methoddef + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + PCCOR_SIGNATURE *ppSig) +{ + _ASSERTE(pcbSigBlob); + _ASSERTE(TypeFromToken(fielddef) == mdtFieldDef); + + HRESULT hr; + // We don't change Field's signature. No need to lock. + + FieldRec *pFieldRec; + *ppSig = NULL; + *pcbSigBlob = 0; + IfFailRet(m_pStgdb->m_MiniMd.GetFieldRecord(RidFromToken(fielddef), &pFieldRec)); + IfFailRet(m_pStgdb->m_MiniMd.getSignatureOfField(pFieldRec, ppSig, pcbSigBlob)); + return S_OK; +} // MDInternalRW::GetSigOfFieldDef + + +//***************************************************************************** +// Get signature for the token (FieldDef, MethodDef, Signature, or TypeSpec). +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetSigFromToken( + mdToken tk, + ULONG * pcbSig, + PCCOR_SIGNATURE * ppSig) +{ + HRESULT hr; + // We don't change token's signature. Thus no need to lock. + + *ppSig = NULL; + *pcbSig = 0; + switch (TypeFromToken(tk)) + { + case mdtSignature: + { + StandAloneSigRec *pRec; + IfFailGo(m_pStgdb->m_MiniMd.GetStandAloneSigRecord(RidFromToken(tk), &pRec)); + IfFailGo(m_pStgdb->m_MiniMd.getSignatureOfStandAloneSig(pRec, ppSig, pcbSig)); + return S_OK; + } + case mdtTypeSpec: + { + TypeSpecRec *pRec; + IfFailGo(m_pStgdb->m_MiniMd.GetTypeSpecRecord(RidFromToken(tk), &pRec)); + IfFailGo(m_pStgdb->m_MiniMd.getSignatureOfTypeSpec(pRec, ppSig, pcbSig)); + return S_OK; + } + case mdtMethodDef: + { + IfFailGo(GetSigOfMethodDef(tk, pcbSig, ppSig)); + return S_OK; + } + case mdtFieldDef: + { + IfFailGo(GetSigOfFieldDef(tk, pcbSig, ppSig)); + return S_OK; + } + } + + // not a known token type. +#ifdef _DEBUG + if(REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_AssertOnBadImageFormat, 1)) + _ASSERTE(!"Unexpected token type"); +#endif + *pcbSig = 0; + hr = META_E_INVALID_TOKEN_TYPE; + +ErrExit: + return hr; +} // MDInternalRW::GetSigFromToken + + +//***************************************************************************** +// Given methoddef, return the flags +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetMethodDefProps( + mdMethodDef md, + DWORD *pdwFlags) // return mdPublic, mdAbstract, etc +{ + HRESULT hr = S_OK; + MethodRec *pMethodRec = NULL; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(md), &pMethodRec)); + _ASSERTE(hr == S_OK); + *pdwFlags = m_pStgdb->m_MiniMd.getFlagsOfMethod(pMethodRec); + return S_OK; + +ErrExit: + *pdwFlags = (DWORD)-1; + return hr; +} // MDInternalRW::GetMethodDefProps + +//***************************************************************************** +// Given a scope and a methoddef, return RVA and impl flags +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetMethodImplProps( + mdToken tk, // [IN] MethodDef + ULONG *pulCodeRVA, // [OUT] CodeRVA + DWORD *pdwImplFlags) // [OUT] Impl. Flags +{ + _ASSERTE(TypeFromToken(tk) == mdtMethodDef); + HRESULT hr = S_OK; + MethodRec *pMethodRec = NULL; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.GetMethodRecord(RidFromToken(tk), &pMethodRec)); + + if (pulCodeRVA) + { + *pulCodeRVA = m_pStgdb->m_MiniMd.getRVAOfMethod(pMethodRec); + } + + if (pdwImplFlags) + { + *pdwImplFlags = m_pStgdb->m_MiniMd.getImplFlagsOfMethod(pMethodRec); + } + +ErrExit: + + return hr; +} // MDInternalRW::GetMethodImplProps + + +//***************************************************************************** +// return the field RVA +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetFieldRVA( + mdToken fd, // [IN] FieldDef + ULONG *pulCodeRVA) // [OUT] CodeRVA +{ + _ASSERTE(TypeFromToken(fd) == mdtFieldDef); + _ASSERTE(pulCodeRVA); + ULONG iRecord; + HRESULT hr = NOERROR; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.FindFieldRVAHelper(fd, &iRecord)); + if (InvalidRid(iRecord)) + { + if (pulCodeRVA) + *pulCodeRVA = 0; + hr = CLDB_E_RECORD_NOTFOUND; + } + else + { + FieldRVARec *pFieldRVARec; + IfFailGo(m_pStgdb->m_MiniMd.GetFieldRVARecord(iRecord, &pFieldRVARec)); + + *pulCodeRVA = m_pStgdb->m_MiniMd.getRVAOfFieldRVA(pFieldRVARec); + } + +ErrExit: + return hr; +} // MDInternalRW::GetFieldRVA + + +//***************************************************************************** +// Given a fielddef, return the flags. Such as fdPublic, fdStatic, etc +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetFieldDefProps( + mdFieldDef fd, // given memberdef + DWORD *pdwFlags) // [OUT] return fdPublic, fdPrive, etc flags +{ + _ASSERTE(TypeFromToken(fd) == mdtFieldDef); + HRESULT hr = S_OK; + FieldRec *pFieldRec = NULL; + + LOCKREAD(); + + IfFailRet(m_pStgdb->m_MiniMd.GetFieldRecord(RidFromToken(fd), &pFieldRec)); + _ASSERTE(hr == S_OK); + *pdwFlags = m_pStgdb->m_MiniMd.getFlagsOfField(pFieldRec); + return S_OK; + +ErrExit: + *pdwFlags = (DWORD)-1; + return hr; +} // MDInternalRW::GetFieldDefProps + +//***************************************************************************** +// return default value of a token(could be paramdef, fielddef, or property) +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetDefaultValue( // return hresult + mdToken tk, // [IN] given FieldDef, ParamDef, or Property + MDDefaultValue *pMDDefaultValue) // [OUT] default value +{ + _ASSERTE(pMDDefaultValue); + + HRESULT hr; + BYTE bType; + const VOID *pValue; + ULONG cbValue; + RID rid; + ConstantRec *pConstantRec; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.FindConstantHelper(tk, &rid)); + if (InvalidRid(rid)) + { + pMDDefaultValue->m_bType = ELEMENT_TYPE_VOID; + return S_OK; + } + IfFailGo(m_pStgdb->m_MiniMd.GetConstantRecord(rid, &pConstantRec)); + + // get the type of constant value + bType = m_pStgdb->m_MiniMd.getTypeOfConstant(pConstantRec); + + // get the value blob + IfFailGo(m_pStgdb->m_MiniMd.getValueOfConstant(pConstantRec, reinterpret_cast<const BYTE **>(&pValue), &cbValue)); + + // convert it to our internal default value representation + hr = _FillMDDefaultValue(bType, pValue, cbValue, pMDDefaultValue); + +ErrExit: + + return hr; +} // MDInternalRW::GetDefaultValue + + +//***************************************************************************** +// Given a scope and a methoddef/fielddef, return the dispid +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetDispIdOfMemberDef( // return hresult + mdToken tk, // given methoddef or fielddef + ULONG *pDispid) // Put the dispid here. +{ +#ifdef FEATURE_COMINTEROP + HRESULT hr; // A result. + const BYTE *pBlob; // Blob with dispid. + ULONG cbBlob; // Length of blob. + + // No need to lock this function. All of the function that it is calling is already locked! + + // Get the DISPID, if any. + _ASSERTE(pDispid); + + *pDispid = DISPID_UNKNOWN; + hr = GetCustomAttributeByName(tk, INTEROP_DISPID_TYPE, (const void**)&pBlob, &cbBlob); + if (hr != S_FALSE) + { + // Check that this might be a dispid. + if (cbBlob >= (sizeof(*pDispid)+2)) + *pDispid = GET_UNALIGNED_VAL32(pBlob+2); + else + IfFailGo(E_INVALIDARG); + } + +ErrExit: + return hr; +#else // FEATURE_COMINTEROP + _ASSERTE(false); + return E_NOTIMPL; +#endif // FEATURE_COMINTEROP +} // MDInternalRW::GetDispIdOfMemberDef + + +//***************************************************************************** +// Given interfaceimpl, return the TypeRef/TypeDef and flags +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetTypeOfInterfaceImpl( // return hresult + mdInterfaceImpl iiImpl, // given a interfaceimpl + mdToken *ptkType) +{ + HRESULT hr; + // no need to lock this function. + + _ASSERTE(TypeFromToken(iiImpl) == mdtInterfaceImpl); + + *ptkType = mdTypeDefNil; + + InterfaceImplRec *pIIRec; + IfFailRet(m_pStgdb->m_MiniMd.GetInterfaceImplRecord(RidFromToken(iiImpl), &pIIRec)); + *ptkType = m_pStgdb->m_MiniMd.getInterfaceOfInterfaceImpl(pIIRec); + return S_OK; +} // MDInternalRW::GetTypeOfInterfaceImpl + +//***************************************************************************** +// This routine gets the properties for the given MethodSpec token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetMethodSpecProps( // S_OK or error. + mdMethodSpec mi, // [IN] The method instantiation + mdToken *tkParent, // [OUT] MethodDef or MemberRef + PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data + ULONG *pcbSigBlob) // [OUT] actual size of signature blob +{ + HRESULT hr = NOERROR; + MethodSpecRec *pMethodSpecRec; + + _ASSERTE(TypeFromToken(mi) == mdtMethodSpec); + + IfFailGo(m_pStgdb->m_MiniMd.GetMethodSpecRecord(RidFromToken(mi), &pMethodSpecRec)); + + if (tkParent) + *tkParent = m_pStgdb->m_MiniMd.getMethodOfMethodSpec(pMethodSpecRec); + + if (ppvSigBlob || pcbSigBlob) + { + // caller wants signature information + PCCOR_SIGNATURE pvSigTmp; + ULONG cbSig; + IfFailGo(m_pStgdb->m_MiniMd.getInstantiationOfMethodSpec(pMethodSpecRec, &pvSigTmp, &cbSig)); + if ( ppvSigBlob ) + *ppvSigBlob = pvSigTmp; + if ( pcbSigBlob) + *pcbSigBlob = cbSig; + } + +ErrExit: + return hr; +} // MDInternalRW::GetMethodSpecProps + +//***************************************************************************** +// Given a classname, return the typedef +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindTypeDef( // return hresult + LPCSTR szNamespace, // [IN] Namespace for the TypeDef. + LPCSTR szName, // [IN] Name of the TypeDef. + mdToken tkEnclosingClass, // [IN] TypeDef/TypeRef of enclosing class. + mdTypeDef *ptypedef) // [OUT] return typedef +{ + HRESULT hr = S_OK; + LOCKREADIFFAILRET(); + + _ASSERTE(ptypedef); + + // initialize the output parameter + *ptypedef = mdTypeDefNil; + + return ImportHelper::FindTypeDefByName(&(m_pStgdb->m_MiniMd), + szNamespace, + szName, + tkEnclosingClass, + ptypedef); +} // MDInternalRW::FindTypeDef + +//***************************************************************************** +// Given a memberref, return a pointer to memberref's name and signature +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetNameAndSigOfMemberRef( // meberref's name + mdMemberRef memberref, // given a memberref + PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to a blob value of COM+ signature + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + LPCSTR *pszMemberRefName) +{ + HRESULT hr; + + // MemberRef's name and sig won't change. Don't need to lock this. + + _ASSERTE(TypeFromToken(memberref) == mdtMemberRef); + + MemberRefRec *pMemberRefRec; + *pszMemberRefName = NULL; + if (ppvSigBlob != NULL) + { + _ASSERTE(pcbSigBlob != NULL); + *ppvSigBlob = NULL; + *pcbSigBlob = 0; + } + IfFailRet(m_pStgdb->m_MiniMd.GetMemberRefRecord(RidFromToken(memberref), &pMemberRefRec)); + if (ppvSigBlob != NULL) + { + IfFailRet(m_pStgdb->m_MiniMd.getSignatureOfMemberRef(pMemberRefRec, ppvSigBlob, pcbSigBlob)); + } + IfFailRet(m_pStgdb->m_MiniMd.getNameOfMemberRef(pMemberRefRec, pszMemberRefName)); + return S_OK; +} // MDInternalRW::GetNameAndSigOfMemberRef + + + +//***************************************************************************** +// Given a memberref, return parent token. It can be a TypeRef, ModuleRef, or a MethodDef +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetParentOfMemberRef( // return parent token + mdMemberRef memberref, // given a typedef + mdToken *ptkParent) // return the parent token +{ + HRESULT hr = S_OK; + MemberRefRec *pMemberRefRec = NULL; + + LOCKREAD(); + + // parent for MemberRef can change. See SetParent. + + _ASSERTE(TypeFromToken(memberref) == mdtMemberRef); + + IfFailRet(m_pStgdb->m_MiniMd.GetMemberRefRecord(RidFromToken(memberref), &pMemberRefRec)); + _ASSERTE(hr == S_OK); + *ptkParent = m_pStgdb->m_MiniMd.getClassOfMemberRef(pMemberRefRec); + return S_OK; + +ErrExit: + *ptkParent = mdTokenNil; + return hr; +} // MDInternalRW::GetParentOfMemberRef + +//***************************************************************************** +// return properties of a paramdef +//*****************************************************************************/ +__checkReturn +HRESULT +MDInternalRW::GetParamDefProps ( + mdParamDef paramdef, // given a paramdef + USHORT *pusSequence, // [OUT] slot number for this parameter + DWORD *pdwAttr, // [OUT] flags + LPCSTR *pszName) // [OUT] return the name of the parameter +{ + HRESULT hr = S_OK; + ParamRec *pParamRec = NULL; + + LOCKREAD(); + + // parent for MemberRef can change. See SetParamProps. + + _ASSERTE(TypeFromToken(paramdef) == mdtParamDef); + IfFailGo(m_pStgdb->m_MiniMd.GetParamRecord(RidFromToken(paramdef), &pParamRec)); + _ASSERTE(hr == S_OK); + if (pdwAttr != NULL) + { + *pdwAttr = m_pStgdb->m_MiniMd.getFlagsOfParam(pParamRec); + } + if (pusSequence != NULL) + { + *pusSequence = m_pStgdb->m_MiniMd.getSequenceOfParam(pParamRec); + } + IfFailGo(m_pStgdb->m_MiniMd.getNameOfParam(pParamRec, pszName)); + _ASSERTE(hr == S_OK); + return S_OK; + +ErrExit: + *pszName = NULL; + return S_OK; +} // MDInternalRW::GetParamDefProps + +//***************************************************************************** +// Get property info for the method. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetPropertyInfoForMethodDef( // Result. + mdMethodDef md, // [IN] memberdef + mdProperty *ppd, // [OUT] put property token here + LPCSTR *pName, // [OUT] put pointer to name here + ULONG *pSemantic) // [OUT] put semantic here +{ + MethodSemanticsRec *pSemantics; + RID ridCur; + RID ridMax; + USHORT usSemantics; + HRESULT hr = S_OK; + LOCKREAD(); + + ridMax = m_pStgdb->m_MiniMd.getCountMethodSemantics(); + for (ridCur = 1; ridCur <= ridMax; ridCur++) + { + IfFailGo(m_pStgdb->m_MiniMd.GetMethodSemanticsRecord(ridCur, &pSemantics)); + if (md == m_pStgdb->m_MiniMd.getMethodOfMethodSemantics(pSemantics)) + { + // match the method + usSemantics = m_pStgdb->m_MiniMd.getSemanticOfMethodSemantics(pSemantics); + if (usSemantics == msGetter || usSemantics == msSetter) + { + // Make sure that it is not an invalid entry + if (m_pStgdb->m_MiniMd.getAssociationOfMethodSemantics(pSemantics) != mdPropertyNil) + { + // found a match. Fill out the output parameters + PropertyRec *pProperty; + mdProperty prop; + prop = m_pStgdb->m_MiniMd.getAssociationOfMethodSemantics(pSemantics); + + if (ppd) + *ppd = prop; + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRecord(RidFromToken(prop), &pProperty)); + + if (pName) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfProperty(pProperty, pName)); + } + + if (pSemantic) + *pSemantic = usSemantics; + goto ErrExit; + } + } + } + } + + hr = S_FALSE; +ErrExit: + return hr; +} // MDInternalRW::GetPropertyInfoForMethodDef + +//***************************************************************************** +// return the pack size of a class +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetClassPackSize( + mdTypeDef td, // [IN] give typedef + DWORD *pdwPackSize) // [OUT] +{ + HRESULT hr = NOERROR; + RID ridClassLayout = 0; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(td) == mdtTypeDef && pdwPackSize); + + ClassLayoutRec *pRec; + IfFailGo(m_pStgdb->m_MiniMd.FindClassLayoutHelper(td, &ridClassLayout)); + + if (InvalidRid(ridClassLayout)) + { + hr = CLDB_E_RECORD_NOTFOUND; + goto ErrExit; + } + + IfFailGo(m_pStgdb->m_MiniMd.GetClassLayoutRecord(RidFromToken(ridClassLayout), &pRec)); + *pdwPackSize = m_pStgdb->m_MiniMd.getPackingSizeOfClassLayout(pRec); +ErrExit: + return hr; +} // MDInternalRW::GetClassPackSize + + +//***************************************************************************** +// return the total size of a value class +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetClassTotalSize( // return error if a class does not have total size info + mdTypeDef td, // [IN] give typedef + ULONG *pulClassSize) // [OUT] return the total size of the class +{ + CONTRACT_VIOLATION(ThrowsViolation); + + _ASSERTE(TypeFromToken(td) == mdtTypeDef && pulClassSize); + + ClassLayoutRec *pRec; + HRESULT hr = NOERROR; + RID ridClassLayout; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.FindClassLayoutHelper(td, &ridClassLayout)); + if (InvalidRid(ridClassLayout)) + { + hr = CLDB_E_RECORD_NOTFOUND; + goto ErrExit; + } + + IfFailGo(m_pStgdb->m_MiniMd.GetClassLayoutRecord(RidFromToken(ridClassLayout), &pRec)); + *pulClassSize = m_pStgdb->m_MiniMd.getClassSizeOfClassLayout(pRec); +ErrExit: + return hr; +} // MDInternalRW::GetClassTotalSize + + +//***************************************************************************** +// init the layout enumerator of a class +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetClassLayoutInit( + mdTypeDef td, // [IN] give typedef + MD_CLASS_LAYOUT *pmdLayout) // [OUT] set up the status of query here +{ + HRESULT hr = NOERROR; + LOCKREAD(); + _ASSERTE(TypeFromToken(td) == mdtTypeDef); + + // <TODO>Do we need to lock this function? Can clints add more Fields on a TypeDef?</TODO> + + // initialize the output parameter + _ASSERTE(pmdLayout); + memset(pmdLayout, 0, sizeof(MD_CLASS_LAYOUT)); + + TypeDefRec *pTypeDefRec; + + // record for this typedef in TypeDef Table + IfFailGo(m_pStgdb->m_MiniMd.GetTypeDefRecord(RidFromToken(td), &pTypeDefRec)); + + // find the starting and end field for this typedef + pmdLayout->m_ridFieldCur = m_pStgdb->m_MiniMd.getFieldListOfTypeDef(pTypeDefRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndFieldListOfTypeDef(RidFromToken(td), &(pmdLayout->m_ridFieldEnd))); + +ErrExit: + + return hr; +} // MDInternalRW::GetClassLayoutInit + +//***************************************************************************** +// Get the field offset for a given field token +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetFieldOffset( + mdFieldDef fd, // [IN] fielddef + ULONG *pulOffset) // [OUT] FieldOffset +{ + HRESULT hr = S_OK; + FieldLayoutRec *pRec; + + _ASSERTE(pulOffset); + + RID iLayout; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.FindFieldLayoutHelper(fd, &iLayout)); + + if (InvalidRid(iLayout)) + { + hr = S_FALSE; + goto ErrExit; + } + + IfFailGo(m_pStgdb->m_MiniMd.GetFieldLayoutRecord(iLayout, &pRec)); + *pulOffset = m_pStgdb->m_MiniMd.getOffSetOfFieldLayout(pRec); + _ASSERTE(*pulOffset != ULONG_MAX); + +ErrExit: + return hr; +} // MDInternalRW::GetFieldOffset + +//***************************************************************************** +// enum the next the field layout +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetClassLayoutNext( + MD_CLASS_LAYOUT *pLayout, // [IN|OUT] set up the status of query here + mdFieldDef *pfd, // [OUT] field def + ULONG *pulOffset) // [OUT] field offset or sequence +{ + HRESULT hr = S_OK; + + _ASSERTE(pfd && pulOffset && pLayout); + + RID iLayout2; + FieldLayoutRec *pRec; + + LOCKREAD(); + + while (pLayout->m_ridFieldCur < pLayout->m_ridFieldEnd) + { + RID fieldRid; + IfFailGo(m_pStgdb->m_MiniMd.GetFieldRid(pLayout->m_ridFieldCur, &fieldRid)); + mdFieldDef fd = TokenFromRid(fieldRid, mdtFieldDef); + IfFailGo(m_pStgdb->m_MiniMd.FindFieldLayoutHelper(fd, &iLayout2)); + pLayout->m_ridFieldCur++; + if (!InvalidRid(iLayout2)) + { + IfFailGo(m_pStgdb->m_MiniMd.GetFieldLayoutRecord(iLayout2, &pRec)); + *pulOffset = m_pStgdb->m_MiniMd.getOffSetOfFieldLayout(pRec); + _ASSERTE(*pulOffset != ULONG_MAX); + *pfd = fd; + goto ErrExit; + } + } + + *pfd = mdFieldDefNil; + hr = S_FALSE; + + // fall through + +ErrExit: + return hr; +} // MDInternalRW::GetClassLayoutNext + + +//***************************************************************************** +// return the field's native type signature +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetFieldMarshal( // return error if no native type associate with the token + mdToken tk, // [IN] given fielddef or paramdef + PCCOR_SIGNATURE *pSigNativeType, // [OUT] the native type signature + ULONG *pcbNativeType) // [OUT] the count of bytes of *ppvNativeType +{ + // output parameters have to be supplied + _ASSERTE(pcbNativeType); + + RID rid; + FieldMarshalRec *pFieldMarshalRec; + HRESULT hr = NOERROR; + + LOCKREAD(); + + // find the row containing the marshal definition for tk + IfFailGo(m_pStgdb->m_MiniMd.FindFieldMarshalHelper(tk, &rid)); + if (InvalidRid(rid)) + { + hr = CLDB_E_RECORD_NOTFOUND; + goto ErrExit; + } + IfFailGo(m_pStgdb->m_MiniMd.GetFieldMarshalRecord(rid, &pFieldMarshalRec)); + + // get the native type + IfFailGo(m_pStgdb->m_MiniMd.getNativeTypeOfFieldMarshal(pFieldMarshalRec, pSigNativeType, pcbNativeType)); +ErrExit: + return hr; +} // MDInternalRW::GetFieldMarshal + + + +//***************************************** +// property APIs +//***************************************** + +//***************************************************************************** +// Find property by name +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindProperty( + mdTypeDef td, // [IN] given a typdef + LPCSTR szPropName, // [IN] property name + mdProperty *pProp) // [OUT] return property token +{ + HRESULT hr = NOERROR; + LOCKREAD(); + + // output parameters have to be supplied + _ASSERTE(TypeFromToken(td) == mdtTypeDef && pProp); + + PropertyMapRec *pRec; + PropertyRec *pProperty; + RID ridPropertyMap; + RID ridCur; + RID ridEnd; + LPCUTF8 szName; + + IfFailGo(m_pStgdb->m_MiniMd.FindPropertyMapFor(RidFromToken(td), &ridPropertyMap)); + if (InvalidRid(ridPropertyMap)) + { + // not found! + hr = CLDB_E_RECORD_NOTFOUND; + goto ErrExit; + } + + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyMapRecord(ridPropertyMap, &pRec)); + + // get the starting/ending rid of properties of this typedef + ridCur = m_pStgdb->m_MiniMd.getPropertyListOfPropertyMap(pRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndPropertyListOfPropertyMap(ridPropertyMap, &ridEnd)); + + for ( ; ridCur < ridEnd; ridCur ++ ) + { + RID propertyRid; + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRid(ridCur, &propertyRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRecord(propertyRid, &pProperty)); + IfFailGo(m_pStgdb->m_MiniMd.getNameOfProperty(pProperty, &szName)); + if ( strcmp(szName, szPropName) ==0 ) + { + // Found the match. Set the output parameter and we are done. + *pProp = TokenFromRid(propertyRid, mdtProperty); + goto ErrExit; + } + } + + // not found + hr = CLDB_E_RECORD_NOTFOUND; +ErrExit: + return hr; +} // MDInternalRW::FindProperty + + + +//***************************************************************************** +// return the properties of a property +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetPropertyProps( + mdProperty prop, // [IN] property token + LPCSTR *pszProperty, // [OUT] property name + DWORD *pdwPropFlags, // [OUT] property flags. + PCCOR_SIGNATURE *ppvSig, // [OUT] property type. pointing to meta data internal blob + ULONG *pcbSig) // [OUT] count of bytes in *ppvSig +{ + HRESULT hr = S_OK; + LOCKREAD(); + + // output parameters have to be supplied + _ASSERTE(TypeFromToken(prop) == mdtProperty); + + PropertyRec *pProperty; + ULONG cbSig; + + IfFailGo(m_pStgdb->m_MiniMd.GetPropertyRecord(RidFromToken(prop), &pProperty)); + + // get name of the property + if (pszProperty) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfProperty(pProperty, pszProperty)); + } + + // get the flags of property + if (pdwPropFlags) + *pdwPropFlags = m_pStgdb->m_MiniMd.getPropFlagsOfProperty(pProperty); + + // get the type of the property + if (ppvSig) + { + IfFailGo(m_pStgdb->m_MiniMd.getTypeOfProperty(pProperty, ppvSig, &cbSig)); + if (pcbSig) + { + *pcbSig = cbSig; + } + } + +ErrExit: + return hr; +} // MDInternalRW::GetPropertyProps + + +//********************************** +// +// Event APIs +// +//********************************** + +//***************************************************************************** +// return an event by given the name +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindEvent( + mdTypeDef td, // [IN] given a typdef + LPCSTR szEventName, // [IN] event name + mdEvent *pEvent) // [OUT] return event token +{ + HRESULT hr = NOERROR; + LOCKREAD(); + + // output parameters have to be supplied + _ASSERTE(TypeFromToken(td) == mdtTypeDef && pEvent); + + EventMapRec *pRec; + EventRec *pEventRec; + RID ridEventMap; + RID ridCur; + RID ridEnd; + LPCUTF8 szName; + + IfFailGo(m_pStgdb->m_MiniMd.FindEventMapFor(RidFromToken(td), &ridEventMap)); + if (InvalidRid(ridEventMap)) + { + // not found! + hr = CLDB_E_RECORD_NOTFOUND; + goto ErrExit; + } + IfFailGo(m_pStgdb->m_MiniMd.GetEventMapRecord(ridEventMap, &pRec)); + + // get the starting/ending rid of properties of this typedef + ridCur = m_pStgdb->m_MiniMd.getEventListOfEventMap(pRec); + IfFailGo(m_pStgdb->m_MiniMd.getEndEventListOfEventMap(ridEventMap, &ridEnd)); + + for (; ridCur < ridEnd; ridCur ++) + { + RID eventRid; + IfFailGo(m_pStgdb->m_MiniMd.GetEventRid(ridCur, &eventRid)); + IfFailGo(m_pStgdb->m_MiniMd.GetEventRecord(eventRid, &pEventRec)); + IfFailGo(m_pStgdb->m_MiniMd.getNameOfEvent(pEventRec, &szName)); + if ( strcmp(szName, szEventName) ==0 ) + { + // Found the match. Set the output parameter and we are done. + *pEvent = TokenFromRid(eventRid, mdtEvent); + goto ErrExit; + } + } + + // not found + hr = CLDB_E_RECORD_NOTFOUND; +ErrExit: + + return hr; +} // MDInternalRW::FindEvent + + +//***************************************************************************** +// return the properties of an event +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetEventProps( // S_OK, S_FALSE, or error. + mdEvent ev, // [IN] event token + LPCSTR *pszEvent, // [OUT] Event name + DWORD *pdwEventFlags, // [OUT] Event flags. + mdToken *ptkEventType) // [OUT] EventType class +{ + HRESULT hr = S_OK; + LOCKREAD(); + EventRec *pEvent; + + // output parameters have to be supplied + _ASSERTE(TypeFromToken(ev) == mdtEvent); + + IfFailGo(m_pStgdb->m_MiniMd.GetEventRecord(RidFromToken(ev), &pEvent)); + if (pszEvent != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfEvent(pEvent, pszEvent)); + } + if (pdwEventFlags) + *pdwEventFlags = m_pStgdb->m_MiniMd.getEventFlagsOfEvent(pEvent); + if (ptkEventType) + *ptkEventType = m_pStgdb->m_MiniMd.getEventTypeOfEvent(pEvent); + +ErrExit: + + return hr; +} // MDInternalRW::GetEventProps + +//***************************************************************************** +// return the properties of a generic param +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetGenericParamProps( // S_OK or error. + mdGenericParam rd, // [IN] The type parameter + ULONG* pulSequence, // [OUT] Parameter sequence number + DWORD* pdwAttr, // [OUT] Type parameter flags (for future use) + mdToken *ptOwner, // [OUT] The owner (TypeDef or MethodDef) + DWORD *reserved, // [OUT] The kind (TypeDef/Ref/Spec, for future use) + LPCSTR *szName) // [OUT] The name +{ + HRESULT hr = NOERROR; + GenericParamRec *pGenericParamRec = NULL; + + // See if this version of the metadata can do Generics + if (!m_pStgdb->m_MiniMd.SupportsGenerics()) + IfFailGo(CLDB_E_INCOMPATIBLE); + + _ASSERTE(TypeFromToken(rd) == mdtGenericParam); + if (TypeFromToken(rd) != mdtGenericParam) + IfFailGo(CLDB_E_FILE_CORRUPT); + + IfFailGo(m_pStgdb->m_MiniMd.GetGenericParamRecord(RidFromToken(rd), &pGenericParamRec)); + + if (pulSequence) + *pulSequence = m_pStgdb->m_MiniMd.getNumberOfGenericParam(pGenericParamRec); + if (pdwAttr) + *pdwAttr = m_pStgdb->m_MiniMd.getFlagsOfGenericParam(pGenericParamRec); + if (ptOwner) + *ptOwner = m_pStgdb->m_MiniMd.getOwnerOfGenericParam(pGenericParamRec); + if (szName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfGenericParam(pGenericParamRec, szName)); + } + +ErrExit: + return hr; +} // MDInternalRW::GetGenericParamProps + + +//***************************************************************************** +// This routine gets the properties for the given GenericParamConstraint token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetGenericParamConstraintProps( // S_OK or error. + mdGenericParamConstraint rd, // [IN] The constraint token + mdGenericParam *ptGenericParam, // [OUT] GenericParam that is constrained + mdToken *ptkConstraintType) // [OUT] TypeDef/Ref/Spec constraint +{ + HRESULT hr = NOERROR; + GenericParamConstraintRec *pGPCRec; + RID ridRD = RidFromToken(rd); + + // See if this version of the metadata can do Generics + if (!m_pStgdb->m_MiniMd.SupportsGenerics()) + IfFailGo(CLDB_E_INCOMPATIBLE); + + if((TypeFromToken(rd) == mdtGenericParamConstraint) && (ridRD != 0)) + { + IfFailGo(m_pStgdb->m_MiniMd.GetGenericParamConstraintRecord(ridRD, &pGPCRec)); + + if (ptGenericParam) + *ptGenericParam = TokenFromRid(m_pStgdb->m_MiniMd.getOwnerOfGenericParamConstraint(pGPCRec),mdtGenericParam); + if (ptkConstraintType) + *ptkConstraintType = m_pStgdb->m_MiniMd.getConstraintOfGenericParamConstraint(pGPCRec); + } + else + hr = META_E_BAD_INPUT_PARAMETER; + +ErrExit: + return hr; +} // MDInternalRW::GetGenericParamConstraintProps + +//***************************************************************************** +// Find methoddef of a particular associate with a property or an event +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::FindAssociate( + mdToken evprop, // [IN] given a property or event token + DWORD dwSemantics, // [IN] given a associate semantics(setter, getter, testdefault, reset) + mdMethodDef *pmd) // [OUT] return method def token +{ + HRESULT hr = NOERROR; + RID rid; + MethodSemanticsRec *pMethodSemantics; + + // output parameters have to be supplied + _ASSERTE(pmd); + _ASSERTE(TypeFromToken(evprop) == mdtEvent || TypeFromToken(evprop) == mdtProperty); + + LOCKREAD(); + + hr = m_pStgdb->m_MiniMd.FindAssociateHelper(evprop, dwSemantics, &rid); + if (SUCCEEDED(hr)) + { + IfFailGo(m_pStgdb->m_MiniMd.GetMethodSemanticsRecord(rid, &pMethodSemantics)); + *pmd = m_pStgdb->m_MiniMd.getMethodOfMethodSemantics(pMethodSemantics); + } + +ErrExit: + + return hr; +} // MDInternalRW::FindAssociate + + +//***************************************************************************** +// get counts of methodsemantics associated with a particular property/event +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::EnumAssociateInit( + mdToken evprop, // [IN] given a property or an event token + HENUMInternal *phEnum) // [OUT] cursor to hold the query result +{ + HRESULT hr; + + LOCKREAD(); + + // output parameters have to be supplied + _ASSERTE(phEnum); + _ASSERTE(TypeFromToken(evprop) == mdtEvent || TypeFromToken(evprop) == mdtProperty); + + hr = m_pStgdb->m_MiniMd.FindMethodSemanticsHelper(evprop, phEnum); + +ErrExit: + return hr; +} // MDInternalRW::EnumAssociateInit + + +//***************************************************************************** +// get all methodsemantics associated with a particular property/event +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetAllAssociates( + HENUMInternal *phEnum, // [OUT] cursor to hold the query result + ASSOCIATE_RECORD *pAssociateRec, // [OUT] struct to fill for output + ULONG cAssociateRec) // [IN] size of the buffer +{ + HRESULT hr = S_OK; + MethodSemanticsRec *pSemantics; + RID ridCur; + int index = 0; + + LOCKREAD(); + + // <TODO>@FUTURE: rewrite the EnumAssociateInit and GetAllAssociates. Because we might add more properties and events. + // Then we might resort MethodSemantics table. So this can be totally out of sync.</TODO> + + _ASSERTE(phEnum && pAssociateRec); + _ASSERTE(cAssociateRec == phEnum->m_ulCount); + + // Convert from row pointers to RIDs. + while (HENUMInternal::EnumNext(phEnum, (mdToken *)&ridCur)) + { + IfFailGo(m_pStgdb->m_MiniMd.GetMethodSemanticsRecord(ridCur, &pSemantics)); + + pAssociateRec[index].m_memberdef = m_pStgdb->m_MiniMd.getMethodOfMethodSemantics(pSemantics); + pAssociateRec[index].m_dwSemantics = m_pStgdb->m_MiniMd.getSemanticOfMethodSemantics(pSemantics); + index++; + } + +ErrExit: + + return hr; +} // MDInternalRW::GetAllAssociates + + +//***************************************************************************** +// Get the Action and Permissions blob for a given PermissionSet. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetPermissionSetProps( + mdPermission pm, // [IN] the permission token. + DWORD *pdwAction, // [OUT] CorDeclSecurity. + void const **ppvPermission, // [OUT] permission blob. + ULONG *pcbPermission) // [OUT] count of bytes of pvPermission. +{ + HRESULT hr = S_OK; + _ASSERTE(TypeFromToken(pm) == mdtPermission); + _ASSERTE(pdwAction && ppvPermission && pcbPermission); + + DeclSecurityRec *pPerm; + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.GetDeclSecurityRecord(RidFromToken(pm), &pPerm)); + *pdwAction = m_pStgdb->m_MiniMd.getActionOfDeclSecurity(pPerm); + IfFailGo(m_pStgdb->m_MiniMd.getPermissionSetOfDeclSecurity(pPerm, reinterpret_cast<const BYTE **>(ppvPermission), pcbPermission)); + +ErrExit: + + return hr; +} // MDInternalRW::GetPermissionSetProps + + +//***************************************************************************** +// Get the String given the String token. +// Return a pointer to the string, or NULL in case of error. +//***************************************************************************** +__checkReturn +HRESULT +MDInternalRW::GetUserString( // Offset into the string blob heap. + mdString stk, // [IN] the string token. + ULONG *pcchStringSize, // [OUT] count of characters in the string. + BOOL *pfIs80Plus, // [OUT] specifies where there are extended characters >= 0x80. + LPCWSTR *pwszUserString) +{ + HRESULT hr; + LPWSTR wszTmp; + + // no need to lock this function. + + if (pfIs80Plus != NULL) + { + *pfIs80Plus = FALSE; + } + *pwszUserString = NULL; + *pcchStringSize = 0; + + _ASSERTE(pcchStringSize != NULL); + MetaData::DataBlob userString; + IfFailRet(m_pStgdb->m_MiniMd.GetUserString(RidFromToken(stk), &userString)); + + wszTmp = reinterpret_cast<LPWSTR>(userString.GetDataPointer()); + + *pcchStringSize = userString.GetSize() / sizeof(WCHAR); + + if (userString.IsEmpty()) + { + *pwszUserString = NULL; + return S_OK; + } + + if (pfIs80Plus != NULL) + { + if (userString.GetSize() % sizeof(WCHAR) == 0) + { + *pfIs80Plus = TRUE; // no indicator, presume the worst + } + // Return the user string terminator (contains value fIs80Plus) + *pfIs80Plus = *(reinterpret_cast<PBYTE>(wszTmp + *pcchStringSize)); + } + + *pwszUserString = wszTmp; + return S_OK; +} // MDInternalRW::GetUserString + +//***************************************************************************** +// Get the properties for the given Assembly token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetAssemblyProps( + mdAssembly mda, // [IN] The Assembly for which to get the properties. + const void **ppbPublicKey, // [OUT] Pointer to the public key. + ULONG *pcbPublicKey, // [OUT] Count of bytes in the public key. + ULONG *pulHashAlgId, // [OUT] Hash Algorithm. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + AssemblyMetaDataInternal *pMetaData,// [OUT] Assembly MetaData. + DWORD *pdwAssemblyFlags) // [OUT] Flags. +{ + AssemblyRec *pRecord; + HRESULT hr = S_OK; + LOCKREAD(); + + _ASSERTE(TypeFromToken(mda) == mdtAssembly && RidFromToken(mda)); + IfFailGo(m_pStgdb->m_MiniMd.GetAssemblyRecord(RidFromToken(mda), &pRecord)); + + if (ppbPublicKey != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getPublicKeyOfAssembly(pRecord, reinterpret_cast<const BYTE **>(ppbPublicKey), pcbPublicKey)); + } + if (pulHashAlgId) + *pulHashAlgId = m_pStgdb->m_MiniMd.getHashAlgIdOfAssembly(pRecord); + if (pszName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfAssembly(pRecord, pszName)); + } + if (pMetaData) + { + pMetaData->usMajorVersion = m_pStgdb->m_MiniMd.getMajorVersionOfAssembly(pRecord); + pMetaData->usMinorVersion = m_pStgdb->m_MiniMd.getMinorVersionOfAssembly(pRecord); + pMetaData->usBuildNumber = m_pStgdb->m_MiniMd.getBuildNumberOfAssembly(pRecord); + pMetaData->usRevisionNumber = m_pStgdb->m_MiniMd.getRevisionNumberOfAssembly(pRecord); + IfFailGo(m_pStgdb->m_MiniMd.getLocaleOfAssembly(pRecord, &pMetaData->szLocale)); + pMetaData->ulProcessor = 0; + pMetaData->ulOS = 0; + } + if (pdwAssemblyFlags) + { + *pdwAssemblyFlags = m_pStgdb->m_MiniMd.getFlagsOfAssembly(pRecord); + + // Turn on the afPublicKey if PublicKey blob is not empty + DWORD cbPublicKey; + const BYTE *pbPublicKey; + IfFailGo(m_pStgdb->m_MiniMd.getPublicKeyOfAssembly(pRecord, &pbPublicKey, &cbPublicKey)); + if (cbPublicKey) + *pdwAssemblyFlags |= afPublicKey; + } + +ErrExit: + return hr; + +} // MDInternalRW::GetAssemblyProps + +//***************************************************************************** +// Get the properties for the given AssemblyRef token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetAssemblyRefProps( + mdAssemblyRef mdar, // [IN] The AssemblyRef for which to get the properties. + const void **ppbPublicKeyOrToken, // [OUT] Pointer to the public key or token. + ULONG *pcbPublicKeyOrToken, // [OUT] Count of bytes in the public key or token. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + AssemblyMetaDataInternal *pMetaData,// [OUT] Assembly MetaData. + const void **ppbHashValue, // [OUT] Hash blob. + ULONG *pcbHashValue, // [OUT] Count of bytes in the hash blob. + DWORD *pdwAssemblyRefFlags) // [OUT] Flags. +{ + AssemblyRefRec *pRecord; + HRESULT hr = S_OK; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(mdar) == mdtAssemblyRef && RidFromToken(mdar)); + IfFailGo(m_pStgdb->m_MiniMd.GetAssemblyRefRecord(RidFromToken(mdar), &pRecord)); + + if (ppbPublicKeyOrToken != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getPublicKeyOrTokenOfAssemblyRef(pRecord, reinterpret_cast<const BYTE **>(ppbPublicKeyOrToken), pcbPublicKeyOrToken)); + } + if (pszName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfAssemblyRef(pRecord, pszName)); + } + if (pMetaData) + { + pMetaData->usMajorVersion = m_pStgdb->m_MiniMd.getMajorVersionOfAssemblyRef(pRecord); + pMetaData->usMinorVersion = m_pStgdb->m_MiniMd.getMinorVersionOfAssemblyRef(pRecord); + pMetaData->usBuildNumber = m_pStgdb->m_MiniMd.getBuildNumberOfAssemblyRef(pRecord); + pMetaData->usRevisionNumber = m_pStgdb->m_MiniMd.getRevisionNumberOfAssemblyRef(pRecord); + IfFailGo(m_pStgdb->m_MiniMd.getLocaleOfAssemblyRef(pRecord, &pMetaData->szLocale)); + pMetaData->ulProcessor = 0; + pMetaData->ulOS = 0; + } + if (ppbHashValue != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getHashValueOfAssemblyRef(pRecord, reinterpret_cast<const BYTE **>(ppbHashValue), pcbHashValue)); + } + if (pdwAssemblyRefFlags) + *pdwAssemblyRefFlags = m_pStgdb->m_MiniMd.getFlagsOfAssemblyRef(pRecord); + +ErrExit: + return hr; +} // MDInternalRW::GetAssemblyRefProps + +//***************************************************************************** +// Get the properties for the given File token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetFileProps( + mdFile mdf, // [IN] The File for which to get the properties. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + const void **ppbHashValue, // [OUT] Pointer to the Hash Value Blob. + ULONG *pcbHashValue, // [OUT] Count of bytes in the Hash Value Blob. + DWORD *pdwFileFlags) // [OUT] Flags. +{ + FileRec *pRecord; + HRESULT hr = S_OK; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(mdf) == mdtFile && RidFromToken(mdf)); + IfFailGo(m_pStgdb->m_MiniMd.GetFileRecord(RidFromToken(mdf), &pRecord)); + + if (pszName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfFile(pRecord, pszName)); + } + if (ppbHashValue != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getHashValueOfFile(pRecord, reinterpret_cast<const BYTE **>(ppbHashValue), pcbHashValue)); + } + if (pdwFileFlags) + *pdwFileFlags = m_pStgdb->m_MiniMd.getFlagsOfFile(pRecord); + +ErrExit: + return hr; +} // MDInternalRW::GetFileProps + +//***************************************************************************** +// Get the properties for the given ExportedType token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetExportedTypeProps( + mdExportedType mdct, // [IN] The ExportedType for which to get the properties. + LPCSTR *pszNamespace, // [OUT] Buffer to fill with name. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + mdToken *ptkImplementation, // [OUT] mdFile or mdAssemblyRef that provides the ExportedType. + mdTypeDef *ptkTypeDef, // [OUT] TypeDef token within the file. + DWORD *pdwExportedTypeFlags) // [OUT] Flags. +{ + ExportedTypeRec *pRecord; + HRESULT hr = S_OK; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(mdct) == mdtExportedType && RidFromToken(mdct)); + IfFailGo(m_pStgdb->m_MiniMd.GetExportedTypeRecord(RidFromToken(mdct), &pRecord)); + + if (pszNamespace != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getTypeNamespaceOfExportedType(pRecord, pszNamespace)); + } + if (pszName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getTypeNameOfExportedType(pRecord, pszName)); + } + if (ptkImplementation) + *ptkImplementation = m_pStgdb->m_MiniMd.getImplementationOfExportedType(pRecord); + if (ptkTypeDef) + *ptkTypeDef = m_pStgdb->m_MiniMd.getTypeDefIdOfExportedType(pRecord); + if (pdwExportedTypeFlags) + *pdwExportedTypeFlags = m_pStgdb->m_MiniMd.getFlagsOfExportedType(pRecord); + +ErrExit: + return hr; +} // MDInternalRW::GetExportedTypeProps + +//***************************************************************************** +// Get the properties for the given Resource token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetManifestResourceProps( + mdManifestResource mdmr, // [IN] The ManifestResource for which to get the properties. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + mdToken *ptkImplementation, // [OUT] mdFile or mdAssemblyRef that provides the ExportedType. + DWORD *pdwOffset, // [OUT] Offset to the beginning of the resource within the file. + DWORD *pdwResourceFlags) // [OUT] Flags. +{ + ManifestResourceRec *pRecord; + HRESULT hr = S_OK; + + LOCKREAD(); + + _ASSERTE(TypeFromToken(mdmr) == mdtManifestResource && RidFromToken(mdmr)); + IfFailGo(m_pStgdb->m_MiniMd.GetManifestResourceRecord(RidFromToken(mdmr), &pRecord)); + + if (pszName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getNameOfManifestResource(pRecord, pszName)); + } + if (ptkImplementation) + *ptkImplementation = m_pStgdb->m_MiniMd.getImplementationOfManifestResource(pRecord); + if (pdwOffset) + *pdwOffset = m_pStgdb->m_MiniMd.getOffsetOfManifestResource(pRecord); + if (pdwResourceFlags) + *pdwResourceFlags = m_pStgdb->m_MiniMd.getFlagsOfManifestResource(pRecord); + +ErrExit: + return hr; +} // MDInternalRW::GetManifestResourceProps + +//***************************************************************************** +// Find the ExportedType given the name. +//***************************************************************************** +__checkReturn +STDMETHODIMP MDInternalRW::FindExportedTypeByName( // S_OK or error + LPCSTR szNamespace, // [IN] Namespace of the ExportedType. + LPCSTR szName, // [IN] Name of the ExportedType. + mdExportedType tkEnclosingType, // [IN] Enclosing ExportedType. + mdExportedType *pmct) // [OUT] Put ExportedType token here. +{ + _ASSERTE(szName && pmct); + HRESULT hr = S_OK; + LOCKREADIFFAILRET(); + + IMetaModelCommon *pCommon = static_cast<IMetaModelCommon*>(&m_pStgdb->m_MiniMd); + return pCommon->CommonFindExportedType(szNamespace, szName, tkEnclosingType, pmct); +} // MDInternalRW::FindExportedTypeByName + +//***************************************************************************** +// Find the ManifestResource given the name. +//***************************************************************************** +__checkReturn +STDMETHODIMP MDInternalRW::FindManifestResourceByName(// S_OK or error + LPCSTR szName, // [IN] Name of the resource. + mdManifestResource *pmmr) // [OUT] Put ManifestResource token here. +{ + _ASSERTE(szName && pmmr); + + ManifestResourceRec *pRecord; + ULONG cRecords; // Count of records. + LPCUTF8 szNameTmp = 0; // Name obtained from the database. + ULONG i; + HRESULT hr = S_OK; + + LOCKREAD(); + + cRecords = m_pStgdb->m_MiniMd.getCountManifestResources(); + + // Search for the ExportedType. + for (i = 1; i <= cRecords; i++) + { + IfFailGo(m_pStgdb->m_MiniMd.GetManifestResourceRecord(i, &pRecord)); + IfFailGo(m_pStgdb->m_MiniMd.getNameOfManifestResource(pRecord, &szNameTmp)); + if (! strcmp(szName, szNameTmp)) + { + *pmmr = TokenFromRid(i, mdtManifestResource); + goto ErrExit; + } + } + hr = CLDB_E_RECORD_NOTFOUND; +ErrExit: + + return hr; +} // MDInternalRW::FindManifestResourceByName + +//***************************************************************************** +// Get the Assembly token from the given scope. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetAssemblyFromScope( // S_OK or error + mdAssembly *ptkAssembly) // [OUT] Put token here. +{ + _ASSERTE(ptkAssembly); + + if (m_pStgdb->m_MiniMd.getCountAssemblys()) + { + *ptkAssembly = TokenFromRid(1, mdtAssembly); + return S_OK; + } + else + return CLDB_E_RECORD_NOTFOUND; +} // MDInternalRW::GetAssemblyFromScope + +//******************************************************************************* +// return properties regarding a TypeSpec +//******************************************************************************* +//******************************************************************************* +// return properties regarding a TypeSpec +//******************************************************************************* +__checkReturn +HRESULT MDInternalRW::GetTypeSpecFromToken( // S_OK or error. + mdTypeSpec typespec, // [IN] Signature token. + PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to token. + ULONG *pcbSig) // [OUT] return size of signature. +{ + HRESULT hr = NOERROR; + + _ASSERTE(TypeFromToken(typespec) == mdtTypeSpec); + _ASSERTE(ppvSig && pcbSig); + + if (!IsValidToken(typespec)) + return E_INVALIDARG; + + TypeSpecRec *pRec; + IfFailRet(m_pStgdb->m_MiniMd.GetTypeSpecRecord(RidFromToken(typespec), &pRec)); + + if (pRec == NULL) + return CLDB_E_FILE_CORRUPT; + + IfFailRet(m_pStgdb->m_MiniMd.getSignatureOfTypeSpec(pRec, ppvSig, pcbSig)); + + return hr; +} // MDInternalRW::GetTypeSpecFromToken + + +//***************************************************************************** +// Return contents of Pinvoke given the forwarded member token. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetPinvokeMap( + mdToken tk, // [IN] FieldDef, MethodDef or MethodImpl. + DWORD *pdwMappingFlags, // [OUT] Flags used for mapping. + LPCSTR *pszImportName, // [OUT] Import name. + mdModuleRef *pmrImportDLL) // [OUT] ModuleRef token for the target DLL. +{ + ImplMapRec *pRecord; + ULONG iRecord; + HRESULT hr = S_OK; + + LOCKREAD(); + + IfFailGo(m_pStgdb->m_MiniMd.FindImplMapHelper(tk, &iRecord)); + if (InvalidRid(iRecord)) + { + hr = CLDB_E_RECORD_NOTFOUND; + goto ErrExit; + } + else + IfFailGo(m_pStgdb->m_MiniMd.GetImplMapRecord(iRecord, &pRecord)); + + if (pdwMappingFlags) + *pdwMappingFlags = m_pStgdb->m_MiniMd.getMappingFlagsOfImplMap(pRecord); + if (pszImportName != NULL) + { + IfFailGo(m_pStgdb->m_MiniMd.getImportNameOfImplMap(pRecord, pszImportName)); + } + if (pmrImportDLL) + *pmrImportDLL = m_pStgdb->m_MiniMd.getImportScopeOfImplMap(pRecord); +ErrExit: + return hr; +} // MDInternalRW::GetPinvokeMap + +//***************************************************************************** +// convert a text signature to com format +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::ConvertTextSigToComSig(// Return hresult. + BOOL fCreateTrIfNotFound, // create typeref if not found or not + LPCSTR pSignature, // class file format signature + CQuickBytes *pqbNewSig, // [OUT] place holder for COM+ signature + ULONG *pcbCount) // [OUT] the result size of signature +{ + return E_NOTIMPL; +} // _ConvertTextSigToComSig + +//***************************************************************************** +// This is a way for the EE to associate some data with this RW metadata to +// be released when this RW goes away. This is useful when a RO metadata is +// converted to RW, because arbitrary threads can be executing in the RO. +// So, we hold onto the RO here, and when the module shuts down, we release it. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::SetUserContextData(// S_OK or E_NOTIMPL + IUnknown *pIUnk) // The user context. +{ + // Only one chance to do this. + if (m_pUserUnk) + return E_UNEXPECTED; + m_pUserUnk = pIUnk; + return S_OK; +} // MDInternalRW::SetUserContextData + +//***************************************************************************** +// determine if a token is valid or not +//***************************************************************************** +BOOL MDInternalRW::IsValidToken( // True or False. + mdToken tk) // [IN] Given token. +{ + RID rid = RidFromToken(tk); + // no need to lock on this function. + if (rid == 0) + { + return FALSE; + } + switch (TypeFromToken(tk)) + { + case mdtModule: + // can have only one module record + return (rid <= m_pStgdb->m_MiniMd.getCountModules()); + case mdtTypeRef: + return (rid <= m_pStgdb->m_MiniMd.getCountTypeRefs()); + case mdtTypeDef: + return (rid <= m_pStgdb->m_MiniMd.getCountTypeDefs()); + case mdtFieldDef: + return (rid <= m_pStgdb->m_MiniMd.getCountFields()); + case mdtMethodDef: + return (rid <= m_pStgdb->m_MiniMd.getCountMethods()); + case mdtParamDef: + return (rid <= m_pStgdb->m_MiniMd.getCountParams()); + case mdtInterfaceImpl: + return (rid <= m_pStgdb->m_MiniMd.getCountInterfaceImpls()); + case mdtMemberRef: + return (rid <= m_pStgdb->m_MiniMd.getCountMemberRefs()); + case mdtCustomAttribute: + return (rid <= m_pStgdb->m_MiniMd.getCountCustomAttributes()); + case mdtPermission: + return (rid <= m_pStgdb->m_MiniMd.getCountDeclSecuritys()); + case mdtSignature: + return (rid <= m_pStgdb->m_MiniMd.getCountStandAloneSigs()); + case mdtEvent: + return (rid <= m_pStgdb->m_MiniMd.getCountEvents()); + case mdtProperty: + return (rid <= m_pStgdb->m_MiniMd.getCountPropertys()); + case mdtModuleRef: + return (rid <= m_pStgdb->m_MiniMd.getCountModuleRefs()); + case mdtTypeSpec: + return (rid <= m_pStgdb->m_MiniMd.getCountTypeSpecs()); + case mdtAssembly: + return (rid <= m_pStgdb->m_MiniMd.getCountAssemblys()); + case mdtAssemblyRef: + return (rid <= m_pStgdb->m_MiniMd.getCountAssemblyRefs()); + case mdtFile: + return (rid <= m_pStgdb->m_MiniMd.getCountFiles()); + case mdtExportedType: + return (rid <= m_pStgdb->m_MiniMd.getCountExportedTypes()); + case mdtManifestResource: + return (rid <= m_pStgdb->m_MiniMd.getCountManifestResources()); + case mdtMethodSpec: + return (rid <= m_pStgdb->m_MiniMd.getCountMethodSpecs()); + case mdtString: + // need to check the user string heap + return m_pStgdb->m_MiniMd.m_UserStringHeap.IsValidIndex(rid); + } + return FALSE; +} // MDInternalRW::IsValidToken + +mdModule MDInternalRW::GetModuleFromScope(void) +{ + return TokenFromRid(1, mdtModule); +} // MDInternalRW::GetModuleFromScope + +//***************************************************************************** +// Given a MetaData with ENC changes, apply those changes to this MetaData. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::ApplyEditAndContinue( // S_OK or error. + MDInternalRW *pDeltaMD) // Interface to MD with the ENC delta. +{ + HRESULT hr; // A result. + // Get the MiniMd on the delta. + + LOCKWRITEIFFAILRET(); + + CMiniMdRW &mdDelta = pDeltaMD->m_pStgdb->m_MiniMd; + CMiniMdRW &mdBase = m_pStgdb->m_MiniMd; + + + IfFailGo(mdBase.ConvertToRW()); + IfFailGo(mdBase.ApplyDelta(mdDelta)); +ErrExit: + return hr; +} // MDInternalRW::ApplyEditAndContinue + +//***************************************************************************** +// Given a MetaData with ENC changes, enumerate the changed tokens. +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::EnumDeltaTokensInit( // return hresult + HENUMInternal *phEnum) // Enumerator to initialize. +{ + HRESULT hr = S_OK; // A result. + ULONG index; // Loop control. + ENCLogRec *pRec; // An ENCLog record. + + // Vars for query. + _ASSERTE(phEnum); + memset(phEnum, 0, sizeof(HENUMInternal)); + + // cache the tkKind and the scope + phEnum->m_tkKind = 0; + + phEnum->m_EnumType = MDSimpleEnum; + + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = 1; index <= m_pStgdb->m_MiniMd.m_Schema.m_cRecs[TBL_ENCLog]; ++index) + { + // Get the token type; see if it is a real token. + IfFailGo(m_pStgdb->m_MiniMd.GetENCLogRecord(index, &pRec)); + if (CMiniMdRW::IsRecId(pRec->GetToken())) + continue; + // If there is a function code, that means that this flags a child-record + // addition. The child record will generate its own token, so did the + // parent, so skip the record. + if (pRec->GetFuncCode()) + continue; + + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + pRec->GetToken())); + } + +ErrExit: + // we are done + return hr; +} // MDInternalRW::EnumDeltaTokensInit + + +//***************************************************************************** +// Static function to apply a delta md. This is what the EE calls to apply +// the metadata updates from an update PE to live metadata. +// <TODO>MAY REPLACE THE IMDInternalImport POINTER!</TODO> +//***************************************************************************** +__checkReturn +HRESULT MDApplyEditAndContinue( // S_OK or error. + IMDInternalImport **ppIMD, // [in, out] The metadata to be updated. + IMDInternalImportENC *pDeltaMD) // [in] The delta metadata. +{ + HRESULT hr; // A result. + IMDInternalImportENC *pENC; // ENC interface on the metadata. + + // If the input metadata isn't RW, convert it. + hr = (*ppIMD)->QueryInterface(IID_IMDInternalImportENC, (void**)&pENC); + if (FAILED(hr)) + { + IfFailGo(ConvertRO2RW(*ppIMD, IID_IMDInternalImportENC, (void**)&pENC)); + // Replace the old interface pointer with the ENC one. + (*ppIMD)->Release(); + IfFailGo(pENC->QueryInterface(IID_IMDInternalImport, (void**)ppIMD)); + } + + // Apply the delta to the input metadata. + hr = pENC->ApplyEditAndContinue(static_cast<MDInternalRW*>(pDeltaMD)); + +ErrExit: + if (pENC) + pENC->Release(); + return hr; +} // MDApplyEditAndContinue + +//***************************************************************************** +// Given a scope, return the table size and table ptr for a given index +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::GetTableInfoWithIndex( // return size + ULONG index, // [IN] pass in the index + void **pTable, // [OUT] pointer to table at index + void **pTableSize) // [OUT] size of table at index +{ + _ASSERTE(!"NYI"); + return E_NOTIMPL; +} + +//***************************************************************************** +// Given a delta metadata byte stream, apply the changes to the current metadata +// object returning the resulting metadata object in ppv +//***************************************************************************** +__checkReturn +HRESULT MDInternalRW::ApplyEditAndContinue( + void *pDeltaMD, // [IN] the delta metadata + ULONG cbDeltaMD, // [IN] length of pData + IMDInternalImport **ppv) // [OUT] the resulting metadata interface +{ + _ASSERTE(pDeltaMD); + _ASSERTE(ppv); + + // debugging-specific usages don't need SO hardening + SO_NOT_MAINLINE_FUNCTION; + + HRESULT hr = E_FAIL; + IMDInternalImportENC *pDeltaMDImport = NULL; + + IfFailGo(GetInternalWithRWFormat(pDeltaMD, cbDeltaMD, 0, IID_IMDInternalImportENC, (void**)&pDeltaMDImport)); + + *ppv = this; + IfFailGo(MDApplyEditAndContinue(ppv, pDeltaMDImport)); + +ErrExit: + if (pDeltaMDImport) + pDeltaMDImport->Release(); + + return hr; +} // MDInternalRW::ApplyEditAndContinue + +#endif //FEATURE_METADATA_INTERNAL_APIS diff --git a/src/md/enc/metamodelenc.cpp b/src/md/enc/metamodelenc.cpp new file mode 100644 index 0000000000..1b3c733ae4 --- /dev/null +++ b/src/md/enc/metamodelenc.cpp @@ -0,0 +1,468 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// MetaModelENC.cpp +// + +// +// Implementation for applying ENC deltas to a MiniMd. +// +//***************************************************************************** +#include "stdafx.h" +#include <limits.h> +#include <posterror.h> +#include <metamodelrw.h> +#include <stgio.h> +#include <stgtiggerstorage.h> +#include "mdlog.h" +#include "rwutil.h" + +ULONG CMiniMdRW::m_SuppressedDeltaColumns[TBL_COUNT] = {0}; + +//***************************************************************************** +// Copy the data from one MiniMd to another. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ApplyRecordDelta( + CMiniMdRW &mdDelta, // The delta MetaData. + ULONG ixTbl, // The table with the data. + void *pDelta, // The delta MetaData record. + void *pRecord) // The record to update. +{ + HRESULT hr = S_OK; + ULONG mask = m_SuppressedDeltaColumns[ixTbl]; + + for (ULONG ixCol = 0; ixCol<m_TableDefs[ixTbl].m_cCols; ++ixCol, mask >>= 1) + { // Skip certain pointer columns. + if (mask & 0x01) + continue; + + ULONG val = mdDelta.GetCol(ixTbl, ixCol, pDelta); + IfFailRet(PutCol(ixTbl, ixCol, pRecord, val)); + } + return hr; +} // CMiniMdRW::ApplyRecordDelta + +//***************************************************************************** +// Apply a delta record to a table, generically. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ApplyTableDelta( + CMiniMdRW &mdDelta, // Interface to MD with the ENC delta. + ULONG ixTbl, // Table index to update. + RID iRid, // RID of the changed item. + int fc) // Function code of update. +{ + HRESULT hr = S_OK; + void *pRec; // Record in existing MetaData. + void *pDeltaRec; // Record if Delta MetaData. + RID newRid; // Rid of new record. + + // Get the delta record. + IfFailGo(mdDelta.GetDeltaRecord(ixTbl, iRid, &pDeltaRec)); + // Get the record from the base metadata. + if (iRid > m_Schema.m_cRecs[ixTbl]) + { // Added record. Each addition is the next one. + _ASSERTE(iRid == m_Schema.m_cRecs[ixTbl] + 1); + switch (ixTbl) + { + case TBL_TypeDef: + IfFailGo(AddTypeDefRecord(reinterpret_cast<TypeDefRec **>(&pRec), &newRid)); + break; + case TBL_Method: + IfFailGo(AddMethodRecord(reinterpret_cast<MethodRec **>(&pRec), &newRid)); + break; + case TBL_EventMap: + IfFailGo(AddEventMapRecord(reinterpret_cast<EventMapRec **>(&pRec), &newRid)); + break; + case TBL_PropertyMap: + IfFailGo(AddPropertyMapRecord(reinterpret_cast<PropertyMapRec **>(&pRec), &newRid)); + break; + default: + IfFailGo(AddRecord(ixTbl, &pRec, &newRid)); + break; + } + IfNullGo(pRec); + _ASSERTE(iRid == newRid); + } + else + { // Updated record. + IfFailGo(getRow(ixTbl, iRid, &pRec)); + } + + // Copy the record info. + IfFailGo(ApplyRecordDelta(mdDelta, ixTbl, pDeltaRec, pRec)); + +ErrExit: + return hr; +} // CMiniMdRW::ApplyTableDelta + +//***************************************************************************** +// Get the record from a Delta MetaData that corresponds to the actual record. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetDeltaRecord( + ULONG ixTbl, // Table. + ULONG iRid, // Record in the table. + void **ppRecord) +{ + HRESULT hr; + ULONG iMap; // RID in map table. + ENCMapRec *pMap; // Row in map table. + + *ppRecord = NULL; + // If no remap, just return record directly. + if ((m_Schema.m_cRecs[TBL_ENCMap] == 0) || (ixTbl == TBL_Module) || !IsMinimalDelta()) + { + return getRow(ixTbl, iRid, ppRecord); + } + + // Use the remap table to find the physical row containing this logical row. + iMap = (*m_rENCRecs)[ixTbl]; + IfFailRet(GetENCMapRecord(iMap, &pMap)); + + // Search for desired record. + while ((TblFromRecId(pMap->GetToken()) == ixTbl) && (RidFromRecId(pMap->GetToken()) < iRid)) + { + IfFailRet(GetENCMapRecord(++iMap, &pMap)); + } + + _ASSERTE((TblFromRecId(pMap->GetToken()) == ixTbl) && (RidFromRecId(pMap->GetToken()) == iRid)); + + // Relative position within table's group in map is physical rid. + iRid = iMap - (*m_rENCRecs)[ixTbl] + 1; + + return getRow(ixTbl, iRid, ppRecord); +} // CMiniMdRW::GetDeltaRecord + +//***************************************************************************** +// Given a MetaData with ENC changes, apply those changes to this MetaData. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ApplyHeapDeltas( + CMiniMdRW &mdDelta) // Interface to MD with the ENC delta. +{ + if (mdDelta.IsMinimalDelta()) + { + return ApplyHeapDeltasWithMinimalDelta(mdDelta); + } + else + { + return ApplyHeapDeltasWithFullDelta(mdDelta); + } +}// CMiniMdRW::ApplyHeapDeltas + +__checkReturn +HRESULT +CMiniMdRW::ApplyHeapDeltasWithMinimalDelta( + CMiniMdRW &mdDelta) // Interface to MD with the ENC delta. +{ + HRESULT hr = S_OK; + + // Extend the heaps with EnC minimal delta + IfFailGo(m_StringHeap.AddStringHeap( + &(mdDelta.m_StringHeap), + 0)); // Start offset in the mdDelta + IfFailGo(m_BlobHeap.AddBlobHeap( + &(mdDelta.m_BlobHeap), + 0)); // Start offset in the mdDelta + IfFailGo(m_UserStringHeap.AddBlobHeap( + &(mdDelta.m_UserStringHeap), + 0)); // Start offset in the mdDelta + // We never do a minimal delta with the guid heap + IfFailGo(m_GuidHeap.AddGuidHeap( + &(mdDelta.m_GuidHeap), + m_GuidHeap.GetSize())); // Starting offset in the full delta guid heap + +ErrExit: + return hr; +} // CMiniMdRW::ApplyHeapDeltasWithMinimalDelta + +__checkReturn +HRESULT +CMiniMdRW::ApplyHeapDeltasWithFullDelta( + CMiniMdRW &mdDelta) // Interface to MD with the ENC delta. +{ + HRESULT hr = S_OK; + + // Extend the heaps with EnC full delta + IfFailRet(m_StringHeap.AddStringHeap( + &(mdDelta.m_StringHeap), + m_StringHeap.GetUnalignedSize())); // Starting offset in the full delta string heap + IfFailRet(m_BlobHeap.AddBlobHeap( + &(mdDelta.m_BlobHeap), + m_BlobHeap.GetUnalignedSize())); // Starting offset in the full delta blob heap + IfFailRet(m_UserStringHeap.AddBlobHeap( + &(mdDelta.m_UserStringHeap), + m_UserStringHeap.GetUnalignedSize())); // Starting offset in the full delta user string heap + IfFailRet(m_GuidHeap.AddGuidHeap( + &(mdDelta.m_GuidHeap), + m_GuidHeap.GetSize())); // Starting offset in the full delta guid heap + + return hr; +} // CMiniMdRW::ApplyHeapDeltasWithFullDelta + +//***************************************************************************** +// Driver for the delta process. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ApplyDelta( + CMiniMdRW &mdDelta) // Interface to MD with the ENC delta. +{ + HRESULT hr = S_OK; + ULONG iENC; // Loop control. + ULONG iRid; // RID of some record. + ULONG iNew; // RID of a new record. + int i; // Loop control. + ULONG ixTbl; // A table. + +#ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_ApplyDeltaBreak)) + { + _ASSERTE(!"CMiniMDRW::ApplyDelta()"); + } +#endif // _DEBUG + + // Init the suppressed column table. We know this one isn't zero... + if (m_SuppressedDeltaColumns[TBL_TypeDef] == 0) + { + m_SuppressedDeltaColumns[TBL_EventMap] = (1 << EventMapRec::COL_EventList); + m_SuppressedDeltaColumns[TBL_PropertyMap] = (1 << PropertyMapRec::COL_PropertyList); + m_SuppressedDeltaColumns[TBL_EventMap] = (1 << EventMapRec::COL_EventList); + m_SuppressedDeltaColumns[TBL_Method] = (1 << MethodRec::COL_ParamList); + m_SuppressedDeltaColumns[TBL_TypeDef] = (1 << TypeDefRec::COL_FieldList)|(1<<TypeDefRec::COL_MethodList); + } + + // Verify the version of the MD. + if (m_Schema.m_major != mdDelta.m_Schema.m_major || + m_Schema.m_minor != mdDelta.m_Schema.m_minor) + { + _ASSERTE(!"Version of Delta MetaData is a incompatible with current MetaData."); + //<TODO>@FUTURE: unique error in the future since we are not shipping ENC.</TODO> + return E_INVALIDARG; + } + + // verify MVIDs. + ModuleRec *pModDelta; + ModuleRec *pModBase; + IfFailGo(mdDelta.GetModuleRecord(1, &pModDelta)); + IfFailGo(GetModuleRecord(1, &pModBase)); + GUID GuidDelta; + GUID GuidBase; + IfFailGo(mdDelta.getMvidOfModule(pModDelta, &GuidDelta)); + IfFailGo(getMvidOfModule(pModBase, &GuidBase)); + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_DeltaCheck) && (GuidDelta != GuidBase)) + { + _ASSERTE(!"Delta MetaData has different base than current MetaData."); + return E_INVALIDARG; + } + + // Verify that the delta is based on the base. + IfFailGo(mdDelta.getEncBaseIdOfModule(pModDelta, &GuidDelta)); + IfFailGo(getEncBaseIdOfModule(pModBase,&GuidBase)); + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_DeltaCheck) && (GuidDelta != GuidBase)) + { + _ASSERTE(!"The Delta MetaData is based on a different generation than the current MetaData."); + return E_INVALIDARG; + } + + // Let the other md prepare for sparse records. + IfFailGo(mdDelta.StartENCMap()); + + // Fix the heaps. + IfFailGo(ApplyHeapDeltas(mdDelta)); + + // Truncate some tables in preparation to copy in new ENCLog data. + for (i = 0; (ixTbl = m_TruncatedEncTables[i]) != (ULONG)-1; ++i) + { + m_Tables[ixTbl].Delete(); + IfFailGo(m_Tables[ixTbl].InitializeEmpty_WithRecordCount( + m_TableDefs[ixTbl].m_cbRec, + mdDelta.m_Schema.m_cRecs[ixTbl] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + INDEBUG_MD(m_Tables[ixTbl].Debug_SetTableInfo(NULL, ixTbl)); + m_Schema.m_cRecs[ixTbl] = 0; + } + + // For each record in the ENC log... + for (iENC = 1; iENC <= mdDelta.m_Schema.m_cRecs[TBL_ENCLog]; ++iENC) + { + // Get the record, and the updated token. + ENCLogRec *pENC; + IfFailGo(mdDelta.GetENCLogRecord(iENC, &pENC)); + ENCLogRec *pENC2; + IfFailGo(AddENCLogRecord(&pENC2, &iNew)); + IfNullGo(pENC2); + ENCLogRec *pENC3; + _ASSERTE(iNew == iENC); + ULONG func = pENC->GetFuncCode(); + pENC2->SetFuncCode(pENC->GetFuncCode()); + pENC2->SetToken(pENC->GetToken()); + + // What kind of record is this? + if (IsRecId(pENC->GetToken())) + { // Non-token table + iRid = RidFromRecId(pENC->GetToken()); + ixTbl = TblFromRecId(pENC->GetToken()); + } + else + { // Token table. + iRid = RidFromToken(pENC->GetToken()); + ixTbl = GetTableForToken(pENC->GetToken()); + } + + RID rid_Ignore; + // Switch based on the function code. + switch (func) + { + case eDeltaMethodCreate: + // Next ENC record will define the new Method. + MethodRec *pMethodRecord; + IfFailGo(AddMethodRecord(&pMethodRecord, &rid_Ignore)); + IfFailGo(AddMethodToTypeDef(iRid, m_Schema.m_cRecs[TBL_Method])); + break; + + case eDeltaParamCreate: + // Next ENC record will define the new Param. This record is + // tricky because params will be re-ordered based on their sequence, + // but the sequence isn't set until the NEXT record is applied. + // So, for ParamCreate only, apply the param record delta before + // adding the parent-child linkage. + ParamRec *pParamRecord; + IfFailGo(AddParamRecord(&pParamRecord, &rid_Ignore)); + + // Should have recorded a Param delta after the Param add. + _ASSERTE(iENC<mdDelta.m_Schema.m_cRecs[TBL_ENCLog]); + IfFailGo(mdDelta.GetENCLogRecord(iENC+1, &pENC3)); + _ASSERTE(pENC3->GetFuncCode() == 0); + _ASSERTE(GetTableForToken(pENC3->GetToken()) == TBL_Param); + IfFailGo(ApplyTableDelta(mdDelta, TBL_Param, RidFromToken(pENC3->GetToken()), eDeltaFuncDefault)); + + // Now that Param record is OK, set up linkage. + IfFailGo(AddParamToMethod(iRid, m_Schema.m_cRecs[TBL_Param])); + break; + + case eDeltaFieldCreate: + // Next ENC record will define the new Field. + FieldRec *pFieldRecord; + IfFailGo(AddFieldRecord(&pFieldRecord, &rid_Ignore)); + IfFailGo(AddFieldToTypeDef(iRid, m_Schema.m_cRecs[TBL_Field])); + break; + + case eDeltaPropertyCreate: + // Next ENC record will define the new Property. + PropertyRec *pPropertyRecord; + IfFailGo(AddPropertyRecord(&pPropertyRecord, &rid_Ignore)); + IfFailGo(AddPropertyToPropertyMap(iRid, m_Schema.m_cRecs[TBL_Property])); + break; + + case eDeltaEventCreate: + // Next ENC record will define the new Event. + EventRec *pEventRecord; + IfFailGo(AddEventRecord(&pEventRecord, &rid_Ignore)); + IfFailGo(AddEventToEventMap(iRid, m_Schema.m_cRecs[TBL_Event])); + break; + + case eDeltaFuncDefault: + IfFailGo(ApplyTableDelta(mdDelta, ixTbl, iRid, func)); + break; + + default: + _ASSERTE(!"Unexpected function in ApplyDelta"); + IfFailGo(E_UNEXPECTED); + break; + } + } + m_Schema.m_cRecs[TBL_ENCLog] = mdDelta.m_Schema.m_cRecs[TBL_ENCLog]; + +ErrExit: + // Store the result for returning (IfFailRet will modify hr) + HRESULT hrReturn = hr; + IfFailRet(mdDelta.EndENCMap()); + +#ifndef FEATURE_CORECLR + if (SUCCEEDED(hrReturn) && + CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_DeltaCheck) && + CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_UseMinimalDeltas)) + { + GUID GuidNewBase; + + // We'll use the delta's 'delta guid' for our new base guid + IfFailRet(mdDelta.getEncIdOfModule(pModDelta, &GuidNewBase)); + + IfFailRet(PutGuid(TBL_Module, ModuleRec::COL_EncBaseId, pModBase, GuidNewBase)); + } +#endif //!FEATURE_CORECLR + + return hrReturn; +} // CMiniMdRW::ApplyDelta + +//***************************************************************************** +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::StartENCMap() // S_OK or error. +{ + HRESULT hr = S_OK; + ULONG iENC; // Loop control. + ULONG ixTbl; // A table. + int ixTblPrev = -1; // Table previously seen. + + _ASSERTE(m_rENCRecs == 0); + + if (m_Schema.m_cRecs[TBL_ENCMap] == 0) + return S_OK; + + // Build an array of pointers into the ENCMap table for fast access to the ENCMap + // for each table. + m_rENCRecs = new (nothrow) ULONGARRAY; + IfNullGo(m_rENCRecs); + if (!m_rENCRecs->AllocateBlock(TBL_COUNT)) + IfFailGo(E_OUTOFMEMORY); + for (iENC = 1; iENC <= m_Schema.m_cRecs[TBL_ENCMap]; ++iENC) + { + ENCMapRec *pMap; + IfFailGo(GetENCMapRecord(iENC, &pMap)); + ixTbl = TblFromRecId(pMap->GetToken()); + _ASSERTE((int)ixTbl >= ixTblPrev); + _ASSERTE(ixTbl < TBL_COUNT); + _ASSERTE(ixTbl != TBL_ENCMap); + _ASSERTE(ixTbl != TBL_ENCLog); + if ((int)ixTbl == ixTblPrev) + continue; + // Catch up on any skipped tables. + while (ixTblPrev < (int)ixTbl) + { + (*m_rENCRecs)[++ixTblPrev] = iENC; + } + } + while (ixTblPrev < TBL_COUNT-1) + { + (*m_rENCRecs)[++ixTblPrev] = iENC; + } + +ErrExit: + return hr; +} // CMiniMdRW::StartENCMap + +//***************************************************************************** +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::EndENCMap() +{ + if (m_rENCRecs != NULL) + { + delete m_rENCRecs; + m_rENCRecs = NULL; + } + + return S_OK; +} // CMiniMdRW::EndENCMap diff --git a/src/md/enc/metamodelrw.cpp b/src/md/enc/metamodelrw.cpp new file mode 100644 index 0000000000..70b8e72a44 --- /dev/null +++ b/src/md/enc/metamodelrw.cpp @@ -0,0 +1,8894 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// MetaModelRW.cpp +// + +// +// Implementation for the Read/Write MiniMD code. +// +//***************************************************************************** +#include "stdafx.h" +#include <limits.h> +#include <posterror.h> +#include <metamodelrw.h> +#include <stgio.h> +#include <stgtiggerstorage.h> +#include "mdlog.h" +#include "rwutil.h" +#include "../compiler/importhelper.h" +#include "metadata.h" +#include "streamutil.h" + +#include "../hotdata/hotdataformat.h" + +#ifdef FEATURE_PREJIT +#include "corcompile.h" +#endif + +#ifdef _MSC_VER +#pragma intrinsic(memcpy) +#endif + +//********** RidMap *********************************************************** +typedef CDynArray<RID> RIDMAP; + + +//********** Types. *********************************************************** +#define INDEX_ROW_COUNT_THRESHOLD 25 + + +//********** Locals. ********************************************************** +enum MetaDataSizeIndex +{ + // Standard MetaData sizes (from VBA library). + MDSizeIndex_Standard = 0, + // Minimal MetaData sizes used mainly by Reflection.Emit for small assemblies (emitting 1 type per + // assembly). + // Motivated by the performance requirement in collectible types. + MDSizeIndex_Minimal = 1, + + MDSizeIndex_Count +}; // enum MetaDataSizeIndex + +// Gets index of MetaData sizes used to access code:g_PoolSizeInfo, code:g_HashSize and code:g_TblSizeInfo. +static +enum MetaDataSizeIndex +GetMetaDataSizeIndex(const OptionValue *pOptionValue) +{ + if (pOptionValue->m_InitialSize == MDInitialSizeMinimal) + { + return MDSizeIndex_Minimal; + } + _ASSERTE(pOptionValue->m_InitialSize == MDInitialSizeDefault); + return MDSizeIndex_Standard; +} // GetSizeHint + +#define IX_STRING_POOL 0 +#define IX_US_BLOB_POOL 1 +#define IX_GUID_POOL 2 +#define IX_BLOB_POOL 3 + +static +const ULONG +g_PoolSizeInfo[MDSizeIndex_Count][4][2] = +{ + { // Standard pool sizes { Size in bytes, Number of buckets in hash } (code:MDSizeIndex_Standard). + {20000, 449}, // Strings + {5000, 150}, // User literal string blobs + {256, 16}, // Guids + {20000, 449} // Blobs + }, + { // Minimal pool sizes { Size in bytes, Number of buckets in hash } (code:MDSizeIndex_Minimal). + {300, 10}, // Strings + {50, 5}, // User literal string blobs + {16, 3}, // Guids + {200, 10} // Blobs + } +}; + +static +const ULONG +g_HashSize[MDSizeIndex_Count] = +{ + 257, // Standard MetaData size (code:MDSizeIndex_Standard). + 50 // Minimal MetaData size (code:MDSizeIndex_Minimal). +}; + +static +const ULONG +g_TblSizeInfo[MDSizeIndex_Count][TBL_COUNT] = +{ + // Standard table sizes (code:MDSizeIndex_Standard). + { + 1, // Module + 90, // TypeRef + 65, // TypeDef + 0, // FieldPtr + 400, // Field + 0, // MethodPtr + 625, // Method + 0, // ParamPtr + 1200, // Param + 6, // InterfaceImpl + 500, // MemberRef + 400, // Constant + 650, // CustomAttribute + 0, // FieldMarshal + 0, // DeclSecurity + 0, // ClassLayout + 0, // FieldLayout + 175, // StandAloneSig + 0, // EventMap + 0, // EventPtr + 0, // Event + 5, // PropertyMap + 0, // PropertyPtr + 25, // Property + 45, // MethodSemantics + 20, // MethodImpl + 0, // ModuleRef + 0, // TypeSpec + 0, // ImplMap + 0, // FieldRVA + 0, // ENCLog + 0, // ENCMap + 0, // Assembly + 0, // AssemblyProcessor + 0, // AssemblyOS + 0, // AssemblyRef + 0, // AssemblyRefProcessor + 0, // AssemblyRefOS + 0, // File + 0, // ExportedType + 0, // ManifestResource + 0, // NestedClass + 0, // GenericParam + 0, // MethodSpec + 0, // GenericParamConstraint + }, + // Minimal table sizes (code:MDSizeIndex_Minimal). + { + 1, // Module + 2, // TypeRef + 2, // TypeDef + 0, // FieldPtr + 2, // Field + 0, // MethodPtr + 2, // Method + 0, // ParamPtr + 0, // Param + 0, // InterfaceImpl + 1, // MemberRef + 0, // Constant + 0, // CustomAttribute + 0, // FieldMarshal + 0, // DeclSecurity + 0, // ClassLayout + 0, // FieldLayout + 0, // StandAloneSig + 0, // EventMap + 0, // EventPtr + 0, // Event + 0, // PropertyMap + 0, // PropertyPtr + 0, // Property + 0, // MethodSemantics + 0, // MethodImpl + 0, // ModuleRef + 0, // TypeSpec + 0, // ImplMap + 0, // FieldRVA + 0, // ENCLog + 0, // ENCMap + 1, // Assembly + 0, // AssemblyProcessor + 0, // AssemblyOS + 1, // AssemblyRef + 0, // AssemblyRefProcessor + 0, // AssemblyRefOS + 0, // File + 0, // ExportedType + 0, // ManifestResource + 0, // NestedClass + 0, // GenericParam + 0, // MethodSpec + 0, // GenericParamConstraint + } +}; // g_TblSizeInfo + +struct TblIndex +{ + ULONG m_iName; // Name column. + ULONG m_iParent; // Parent column, if any. + ULONG m_Token; // Token of the table. +}; + +// Table to drive generic named-item indexing. +const TblIndex g_TblIndex[TBL_COUNT] = +{ + {(ULONG) -1, (ULONG) -1, mdtModule}, // Module + {TypeRefRec::COL_Name, (ULONG) -1, mdtTypeRef}, // TypeRef + {TypeDefRec::COL_Name, (ULONG) -1, mdtTypeDef}, // TypeDef + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // FieldPtr + {(ULONG) -1, (ULONG) -1, mdtFieldDef}, // Field + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // MethodPtr + {(ULONG) -1, (ULONG) -1, mdtMethodDef}, // Method + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // ParamPtr + {(ULONG) -1, (ULONG) -1, mdtParamDef}, // Param + {(ULONG) -1, (ULONG) -1, mdtInterfaceImpl}, // InterfaceImpl + {MemberRefRec::COL_Name, MemberRefRec::COL_Class, mdtMemberRef}, // MemberRef + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // Constant + {(ULONG) -1, (ULONG) -1, mdtCustomAttribute},// CustomAttribute + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // FieldMarshal + {(ULONG) -1, (ULONG) -1, mdtPermission}, // DeclSecurity + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // ClassLayout + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // FieldLayout + {(ULONG) -1, (ULONG) -1, mdtSignature}, // StandAloneSig + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // EventMap + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // EventPtr + {(ULONG) -1, (ULONG) -1, mdtEvent}, // Event + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // PropertyMap + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // PropertyPtr + {(ULONG) -1, (ULONG) -1, mdtProperty}, // Property + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // MethodSemantics + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // MethodImpl + {(ULONG) -1, (ULONG) -1, mdtModuleRef}, // ModuleRef + {(ULONG) -1, (ULONG) -1, mdtTypeSpec}, // TypeSpec + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // ImplMap <TODO>@FUTURE: Check that these are the right entries here.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // FieldRVA <TODO>@FUTURE: Check that these are the right entries here.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // ENCLog + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // ENCMap + {(ULONG) -1, (ULONG) -1, mdtAssembly}, // Assembly <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // AssemblyProcessor <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // AssemblyOS <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, mdtAssemblyRef}, // AssemblyRef <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // AssemblyRefProcessor <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // AssemblyRefOS <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, mdtFile}, // File <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, mdtExportedType}, // ExportedType <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, mdtManifestResource},// ManifestResource <TODO>@FUTURE: Update with the right number.</TODO> + {(ULONG) -1, (ULONG) -1, (ULONG) -1}, // NestedClass + {(ULONG) -1, (ULONG) -1, mdtGenericParam}, // GenericParam + {(ULONG) -1, (ULONG) -1, mdtMethodSpec}, // MethodSpec + {(ULONG) -1, (ULONG) -1, mdtGenericParamConstraint},// GenericParamConstraint +}; + +ULONG CMiniMdRW::m_TruncatedEncTables[] = +{ + TBL_ENCLog, + TBL_ENCMap, + (ULONG) -1 +}; + +//***************************************************************************** +// Given a token type, return the table index. +//***************************************************************************** +ULONG CMiniMdRW::GetTableForToken( // Table index, or -1. + mdToken tkn) // Token to find. +{ + ULONG ixTbl; // Loop control. + ULONG type = TypeFromToken(tkn); + + // Get the type -- if a string, no associated table. + if (type >= mdtString) + return (ULONG) -1; + // Table number is same as high-byte of token. + ixTbl = type >> 24; + // Make sure. + _ASSERTE(g_TblIndex[ixTbl].m_Token == type); + + return ixTbl; +} // CMiniMdRW::GetTableForToken + +//***************************************************************************** +// Given a Table index, return the Token type. +//***************************************************************************** +mdToken CMiniMdRW::GetTokenForTable( // Token type, or -1. + ULONG ixTbl) // Table index. +{ + _ASSERTE(g_TblIndex[ixTbl].m_Token == (ixTbl<<24) || g_TblIndex[ixTbl].m_Token == (ULONG) -1); + return g_TblIndex[ixTbl].m_Token; +} // CMiniMdRW::GetTokenForTable + +//***************************************************************************** +// Helper classes for sorting MiniMdRW tables. +//***************************************************************************** +class CQuickSortMiniMdRW +{ +protected: + CMiniMdRW &m_MiniMd; // The MiniMd with the data. + ULONG m_ixTbl; // The table. + ULONG m_ixCol; // The column. + int m_iCount; // How many items in array. + int m_iElemSize; // Size of one element. + RIDMAP *m_pRidMap; // Rid map that need to be swapped as we swap data + bool m_bMapToken; // MapToken handling desired. + + BYTE m_buf[128]; // For swapping. + + HRESULT getRow(UINT32 nIndex, void **ppRecord) + { + return m_MiniMd.m_Tables[m_ixTbl].GetRecord(nIndex, reinterpret_cast<BYTE **>(ppRecord)); + } + void SetSorted() { m_MiniMd.SetSorted(m_ixTbl, true); } + + HRESULT PrepMapTokens() + { + HRESULT hr = S_OK; + + // If remap notifications are desired, prepare to collect the info in a RIDMAP. + if (m_bMapToken) + { + _ASSERTE(m_pRidMap == NULL); // Don't call twice. + IfNullGo(m_pRidMap = new (nothrow) RIDMAP); + if (!m_pRidMap->AllocateBlock(m_iCount + 1)) + { + delete m_pRidMap; + m_pRidMap = NULL; + IfFailGo(E_OUTOFMEMORY); + } + for (int i=0; i<= m_iCount; ++i) + *(m_pRidMap->Get(i)) = i; + } + + ErrExit: + return hr; + } // CQuickSortMiniMdRW::PrepMapTokens + + __checkReturn + HRESULT DoMapTokens() + { + HRESULT hr; + if (m_bMapToken) + { + mdToken typ = m_MiniMd.GetTokenForTable(m_ixTbl); + for (int i=1; i<=m_iCount; ++i) + { + IfFailRet(m_MiniMd.MapToken(*(m_pRidMap->Get(i)), i, typ)); + } + } + return S_OK; + } // CQuickSortMiniMdRW::DoMapTokens + +public: + CQuickSortMiniMdRW( + CMiniMdRW &MiniMd, // MiniMd with the data. + ULONG ixTbl, // The table. + ULONG ixCol, // The column. + bool bMapToken) // If true, MapToken handling desired. + : m_MiniMd(MiniMd), + m_ixTbl(ixTbl), + m_ixCol(ixCol), + m_pRidMap(NULL), + m_bMapToken(bMapToken) + { + m_iElemSize = m_MiniMd.m_TableDefs[m_ixTbl].m_cbRec; + _ASSERTE(m_iElemSize <= (int) sizeof(m_buf)); + } + + ~CQuickSortMiniMdRW() + { + if (m_bMapToken) + { + if (m_pRidMap) + { + m_pRidMap->Clear(); + delete m_pRidMap; + m_pRidMap = NULL; + } + m_bMapToken = false; + } + } // CQuickSortMiniMdRW::~CQuickSortMiniMdRW + + // set the RidMap + void SetRidMap(RIDMAP *pRidMap) { m_pRidMap = pRidMap; } + + //***************************************************************************** + // Call to sort the array. + //***************************************************************************** + HRESULT Sort() + { + HRESULT hr = S_OK; + + INDEBUG(m_MiniMd.Debug_CheckIsLockedForWrite();) + + _ASSERTE(m_MiniMd.IsSortable(m_ixTbl)); + m_iCount = m_MiniMd.GetCountRecs(m_ixTbl); + + // If remap notifications are desired, prepare to collect the info in a RIDMAP. + IfFailGo(PrepMapTokens()); + + // We are going to sort tables. Invalidate the hash tables + if ( m_MiniMd.m_pLookUpHashs[m_ixTbl] != NULL ) + { + delete m_MiniMd.m_pLookUpHashs[m_ixTbl]; + m_MiniMd.m_pLookUpHashs[m_ixTbl] = NULL; + } + + IfFailGo(SortRange(1, m_iCount)); + + // The table is sorted until its next change. + SetSorted(); + + // If remap notifications were desired, send them. + IfFailGo(DoMapTokens()); + + ErrExit: + return hr; + } // CQuickSortMiniMdRW::Sort + + //***************************************************************************** + // Call to check whether the array is sorted without altering it. + //***************************************************************************** + HRESULT CheckSortedWithNoDuplicates() + { + HRESULT hr = S_OK; + int iCount = m_MiniMd.GetCountRecs(m_ixTbl); + int nResult; + + m_MiniMd.SetSorted(m_ixTbl, false); + + for (int i = 1; i < iCount; i++) + { + IfFailGo(Compare(i, i+1, &nResult)); + + if (nResult >= 0) + { + return S_OK; + } + } + + // The table is sorted until its next change. + SetSorted(); + + ErrExit: + return hr; + } // CQuickSortMiniMdRW::CheckSortedWithNoDuplicates + + //***************************************************************************** + // Override this function to do the comparison. + //***************************************************************************** + __checkReturn + HRESULT Compare( + int iLeft, // First item to compare. + int iRight, // Second item to compare. + int *pnResult) // -1, 0, or 1 + { + HRESULT hr; + void *pLeft; + void *pRight; + IfFailRet(getRow(iLeft, &pLeft)); + IfFailRet(getRow(iRight, &pRight)); + ULONG ulLeft = m_MiniMd.GetCol(m_ixTbl, m_ixCol, pLeft); + ULONG ulRight = m_MiniMd.GetCol(m_ixTbl, m_ixCol, pRight); + + if (ulLeft < ulRight) + { + *pnResult = -1; + return S_OK; + } + if (ulLeft == ulRight) + { + *pnResult = 0; + return S_OK; + } + *pnResult = 1; + return S_OK; + } // CQuickSortMiniMdRW::Compare + +private: + __checkReturn + HRESULT SortRange( + int iLeft, + int iRight) + { + HRESULT hr; + int iLast; + int nResult; + + for (;;) + { + // if less than two elements you're done. + if (iLeft >= iRight) + { + return S_OK; + } + + // The mid-element is the pivot, move it to the left. + IfFailRet(Compare(iLeft, (iLeft+iRight)/2, &nResult)); + if (nResult != 0) + { + IfFailRet(Swap(iLeft, (iLeft+iRight)/2)); + } + iLast = iLeft; + + // move everything that is smaller than the pivot to the left. + for (int i = iLeft+1; i <= iRight; i++) + { + IfFailRet(Compare(i, iLeft, &nResult)); + if (nResult < 0) + { + IfFailRet(Swap(i, ++iLast)); + } + } + + // Put the pivot to the point where it is in between smaller and larger elements. + IfFailRet(Compare(iLeft, iLast, &nResult)); + if (nResult != 0) + { + IfFailRet(Swap(iLeft, iLast)); + } + + // Sort each partition. + int iLeftLast = iLast - 1; + int iRightFirst = iLast + 1; + if (iLeftLast - iLeft < iRight - iRightFirst) + { // Left partition is smaller, sort it recursively + IfFailRet(SortRange(iLeft, iLeftLast)); + // Tail call to sort the right (bigger) partition + iLeft = iRightFirst; + //iRight = iRight; + continue; + } + else + { // Right partition is smaller, sort it recursively + IfFailRet(SortRange(iRightFirst, iRight)); + // Tail call to sort the left (bigger) partition + //iLeft = iLeft; + iRight = iLeftLast; + continue; + } + } + } // CQuickSortMiniMdRW::SortRange + +protected: + __checkReturn + inline HRESULT Swap( + int iFirst, + int iSecond) + { + HRESULT hr; + void *pFirst; + void *pSecond; + if (iFirst == iSecond) + { + return S_OK; + } + + PREFAST_ASSUME_MSG(m_iElemSize <= (int) sizeof(m_buf), "The MetaData table row has to fit into buffer for swapping."); + + IfFailRet(getRow(iFirst, &pFirst)); + IfFailRet(getRow(iSecond, &pSecond)); + memcpy(m_buf, pFirst, m_iElemSize); + memcpy(pFirst, pSecond, m_iElemSize); + memcpy(pSecond, m_buf, m_iElemSize); + if (m_pRidMap != NULL) + { + RID ridTemp; + ridTemp = *(m_pRidMap->Get(iFirst)); + *(m_pRidMap->Get(iFirst)) = *(m_pRidMap->Get(iSecond)); + *(m_pRidMap->Get(iSecond)) = ridTemp; + } + return S_OK; + } // CQuickSortMiniMdRW::Swap + +}; // class CQuickSortMiniMdRW + +class CStableSortMiniMdRW : public CQuickSortMiniMdRW +{ +public: + CStableSortMiniMdRW( + CMiniMdRW &MiniMd, // MiniMd with the data. + ULONG ixTbl, // The table. + ULONG ixCol, // The column. + bool bMapToken) // Is MapToken handling desired. + : CQuickSortMiniMdRW(MiniMd, ixTbl, ixCol, bMapToken) + {} + + //***************************************************************************** + // Call to sort the array. + //***************************************************************************** + __checkReturn + HRESULT Sort() + { + int i; // Outer loop counter. + int j; // Inner loop counter. + int bSwap; // Early out. + HRESULT hr = S_OK; + int nResult; + + _ASSERTE(m_MiniMd.IsSortable(m_ixTbl)); + m_iCount = m_MiniMd.GetCountRecs(m_ixTbl); + + // If remap notifications are desired, prepare to collect the info in a RIDMAP. + IfFailGo(PrepMapTokens()); + + for (i=m_iCount; i>1; --i) + { + bSwap = 0; + for (j=1; j<i; ++j) + { + IfFailGo(Compare(j, j+1, &nResult)); + if (nResult > 0) + { + IfFailGo(Swap(j, j+1)); + bSwap = 1; + } + } + // If made a full pass w/o swaps, done. + if (!bSwap) + break; + } + + // The table is sorted until its next change. + SetSorted(); + + // If remap notifications were desired, send them. + IfFailGo(DoMapTokens()); + + ErrExit: + return hr; + } // CStableSortMiniMdRW::Sort + +}; // class CStableSortMiniMdRW + +//------------------------------------------------------------------------- +#define SORTER(tbl,key) CQuickSortMiniMdRW sort##tbl(*this, TBL_##tbl, tbl##Rec::COL_##key, false); +#define SORTER_WITHREMAP(tbl,key) CQuickSortMiniMdRW sort##tbl(*this, TBL_##tbl, tbl##Rec::COL_##key, true); +#define STABLESORTER(tbl,key) CStableSortMiniMdRW sort##tbl(*this, TBL_##tbl, tbl##Rec::COL_##key, false); +#define STABLESORTER_WITHREMAP(tbl,key) CStableSortMiniMdRW sort##tbl(*this, TBL_##tbl, tbl##Rec::COL_##key, true); +//------------------------------------------------------------------------- + + + +//********** Code. ************************************************************ + + +//***************************************************************************** +// Ctor / dtor. +//***************************************************************************** +#ifdef _DEBUG +static bool bENCDeltaOnly = false; +#endif +CMiniMdRW::CMiniMdRW() + : m_pMemberRefHash(0), + m_pMemberDefHash(0), + m_pNamedItemHash(0), + m_pHandler(0), + m_cbSaveSize(0), + m_fIsReadOnly(false), + m_bPreSaveDone(false), + m_bPostGSSMod(false), + m_pMethodMap(0), + m_pFieldMap(0), + m_pPropertyMap(0), + m_pEventMap(0), + m_pParamMap(0), + m_pFilterTable(0), + m_pHostFilter(0), + m_pTokenRemapManager(0), + m_fMinimalDelta(FALSE), + m_rENCRecs(0) +{ +#ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_EncDelta)) + { + bENCDeltaOnly = true; + } + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_MiniMDBreak)) + { + _ASSERTE(!"CMiniMdRW::CMiniMdRW()"); + } +#endif // _DEBUG + + ZeroMemory(&m_OptionValue, sizeof(OptionValue)); + + // initialize the embeded lookuptable struct. Further initialization, after constructor. + for (ULONG ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + { + m_pVS[ixTbl] = 0; + m_pLookUpHashs[ixTbl] = 0; + } + + // Assume that we can sort tables as needed. + memset(m_bSortable, 1, sizeof(m_bSortable)); + + // Initialize the global array of Ptr table indices. + g_PtrTableIxs[TBL_Field].m_ixtbl = TBL_FieldPtr; + g_PtrTableIxs[TBL_Field].m_ixcol = FieldPtrRec::COL_Field; + g_PtrTableIxs[TBL_Method].m_ixtbl = TBL_MethodPtr; + g_PtrTableIxs[TBL_Method].m_ixcol = MethodPtrRec::COL_Method; + g_PtrTableIxs[TBL_Param].m_ixtbl = TBL_ParamPtr; + g_PtrTableIxs[TBL_Param].m_ixcol = ParamPtrRec::COL_Param; + g_PtrTableIxs[TBL_Property].m_ixtbl = TBL_PropertyPtr; + g_PtrTableIxs[TBL_Property].m_ixcol = PropertyPtrRec::COL_Property; + g_PtrTableIxs[TBL_Event].m_ixtbl = TBL_EventPtr; + g_PtrTableIxs[TBL_Event].m_ixcol = EventPtrRec::COL_Event; + + // AUTO_GROW initialization + m_maxRid = m_maxIx = 0; + m_limIx = USHRT_MAX >> 1; + m_limRid = USHRT_MAX >> AUTO_GROW_CODED_TOKEN_PADDING; + m_eGrow = eg_ok; +#ifdef _DEBUG + { + ULONG iMax, iCdTkn; + for (iMax=0, iCdTkn=0; iCdTkn<CDTKN_COUNT; ++iCdTkn) + { + CCodedTokenDef const *pCTD = &g_CodedTokens[iCdTkn]; + if (pCTD->m_cTokens > iMax) + iMax = pCTD->m_cTokens; + } + // If assert fires, change define for AUTO_GROW_CODED_TOKEN_PADDING. + _ASSERTE(CMiniMdRW::m_cb[iMax] == AUTO_GROW_CODED_TOKEN_PADDING); + } + dbg_m_pLock = NULL; +#endif //_DEBUG + +} // CMiniMdRW::CMiniMdRW + +CMiniMdRW::~CMiniMdRW() +{ + // Un-initialize the embeded lookuptable struct + for (ULONG ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + { + if (m_pVS[ixTbl]) + { + m_pVS[ixTbl]->Uninit(); + delete m_pVS[ixTbl]; + } + if ( m_pLookUpHashs[ixTbl] != NULL ) + delete m_pLookUpHashs[ixTbl]; + + } + if (m_pFilterTable) + delete m_pFilterTable; + + if (m_rENCRecs) + delete [] m_rENCRecs; + + if (m_pHandler) + m_pHandler->Release(), m_pHandler = 0; + if (m_pHostFilter) + m_pHostFilter->Release(); + if (m_pMemberRefHash) + delete m_pMemberRefHash; + if (m_pMemberDefHash) + delete m_pMemberDefHash; + if (m_pNamedItemHash) + delete m_pNamedItemHash; + if (m_pMethodMap) + delete m_pMethodMap; + if (m_pFieldMap) + delete m_pFieldMap; + if (m_pPropertyMap) + delete m_pPropertyMap; + if (m_pEventMap) + delete m_pEventMap; + if (m_pParamMap) + delete m_pParamMap; + if (m_pTokenRemapManager) + delete m_pTokenRemapManager; +} // CMiniMdRW::~CMiniMdRW + + +//***************************************************************************** +// return all found CAs in an enumerator +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CommonEnumCustomAttributeByName( + mdToken tkObj, // [IN] Object with Custom Attribute. + LPCUTF8 szName, // [IN] Name of desired Custom Attribute. + bool fStopAtFirstFind, // [IN] just find the first one + HENUMInternal *phEnum) // enumerator to fill up +{ + HRESULT hr = S_OK; + HRESULT hrRet = S_FALSE; // Assume that we won't find any + ULONG ridStart, ridEnd; // Loop start and endpoints. + CLookUpHash *pHashTable = m_pLookUpHashs[TBL_CustomAttribute]; + + _ASSERTE(phEnum != NULL); + + memset(phEnum, 0, sizeof(HENUMInternal)); + + HENUMInternal::InitDynamicArrayEnum(phEnum); + + phEnum->m_tkKind = mdtCustomAttribute; + + if (pHashTable) + { + // table is not sorted and hash is not built so we have to create a dynamic array + // create the dynamic enumerator. + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + iHash = HashCustomAttribute(tkObj); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailGo(CompareCustomAttribute( tkObj, szName, RidFromToken(p->tok))); + if (hr == S_OK) + { + hrRet = S_OK; + + // If here, found a match. + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(p->tok, mdtCustomAttribute))); + if (fStopAtFirstFind) + goto ErrExit; + } + } + } + else + { + // Get the list of custom values for the parent object. + if ( IsSorted(TBL_CustomAttribute) ) + { + IfFailGo(getCustomAttributeForToken(tkObj, &ridEnd, &ridStart)); + // If found none, done. + if (ridStart == 0) + goto ErrExit; + } + else + { + // linear scan of entire table. + ridStart = 1; + ridEnd = getCountCustomAttributes() + 1; + } + + // Look for one with the given name. + for (; ridStart < ridEnd; ++ridStart) + { + IfFailGo(CompareCustomAttribute( tkObj, szName, ridStart)); + if (hr == S_OK) + { + // If here, found a match. + hrRet = S_OK; + IfFailGo( HENUMInternal::AddElementToEnum( + phEnum, + TokenFromRid(ridStart, mdtCustomAttribute))); + if (fStopAtFirstFind) + goto ErrExit; + } + } + } + +ErrExit: + if (FAILED(hr)) + return hr; + return hrRet; +} // CMiniMdRW::CommonEnumCustomAttributeByName + + + +//***************************************************************************** +// return just the blob value of the first found CA matching the query. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CommonGetCustomAttributeByNameEx( // S_OK or error. + mdToken tkObj, // [IN] Object with Custom Attribute. + LPCUTF8 szName, // [IN] Name of desired Custom Attribute. + mdCustomAttribute *ptkCA, // [OUT] put custom attribute token here + const void **ppData, // [OUT] Put pointer to data here. + ULONG *pcbData) // [OUT] Put size of data here. +{ + HRESULT hr; + const void *pData; + ULONG cbData; + HENUMInternal hEnum; + mdCustomAttribute ca; + CustomAttributeRec *pRec; + + hr = CommonEnumCustomAttributeByName(tkObj, szName, true, &hEnum); + if (hr != S_OK) + goto ErrExit; + + if (ppData != NULL || ptkCA != NULL) + { + // now get the record out. + if (ppData == 0) + ppData = &pData; + if (pcbData == 0) + pcbData = &cbData; + + + if (HENUMInternal::EnumNext(&hEnum, &ca)) + { + IfFailGo(GetCustomAttributeRecord(RidFromToken(ca), &pRec)); + IfFailGo(getValueOfCustomAttribute(pRec, reinterpret_cast<const BYTE **>(ppData), pcbData)); + if (ptkCA) + *ptkCA = ca; + } + else + { + _ASSERTE(!"Enum returned no items after EnumInit returned S_OK"); + hr = S_FALSE; + } + } +ErrExit: + HENUMInternal::ClearEnum(&hEnum); + return hr; +} // CMiniMdRW::CommonGetCustomAttributeByName + +//***************************************************************************** +// unmark everything in this module +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::UnmarkAll() +{ + HRESULT hr = NOERROR; + ULONG ulSize = 0; + ULONG ixTbl; + FilterTable *pFilter; + + // find the max rec count with all tables + for (ixTbl = 0; ixTbl < TBL_COUNT; ++ixTbl) + { + if (GetCountRecs(ixTbl) > ulSize) + ulSize = GetCountRecs(ixTbl); + } + IfNullGo(pFilter = GetFilterTable()); + IfFailGo(pFilter->UnmarkAll(this, ulSize)); + +ErrExit: + return hr; +} // CMiniMdRW::UnmarkAll + + +//***************************************************************************** +// mark everything in this module +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::MarkAll() +{ + HRESULT hr = NOERROR; + ULONG ulSize = 0; + ULONG ixTbl; + FilterTable *pFilter; + + // find the max rec count with all tables + for (ixTbl = 0; ixTbl < TBL_COUNT; ++ixTbl) + { + if (GetCountRecs(ixTbl) > ulSize) + ulSize = GetCountRecs(ixTbl); + } + IfNullGo(pFilter = GetFilterTable()); + IfFailGo(pFilter->MarkAll(this, ulSize)); + +ErrExit: + return hr; +} // CMiniMdRW::MarkAll + +//***************************************************************************** +// This will trigger FilterTable to be created +//***************************************************************************** +FilterTable *CMiniMdRW::GetFilterTable() +{ + if (m_pFilterTable == NULL) + { + m_pFilterTable = new (nothrow) FilterTable; + } + return m_pFilterTable; +} + + +//***************************************************************************** +// Calculate the map between TypeRef and TypeDef +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CalculateTypeRefToTypeDefMap() +{ + HRESULT hr = NOERROR; + ULONG index; + TypeRefRec *pTypeRefRec; + LPCSTR szName; + LPCSTR szNamespace; + mdToken td; + mdToken tkResScope; + + PREFIX_ASSUME(GetTypeRefToTypeDefMap() != NULL); + + for (index = 1; index <= m_Schema.m_cRecs[TBL_TypeRef]; index++) + { + IfFailRet(GetTypeRefRecord(index, &pTypeRefRec)); + + // Get the name and namespace of the TypeRef. + IfFailRet(getNameOfTypeRef(pTypeRefRec, &szName)); + IfFailRet(getNamespaceOfTypeRef(pTypeRefRec, &szNamespace)); + tkResScope = getResolutionScopeOfTypeRef(pTypeRefRec); + + // If the resolutionScope is an AssemblyRef, then the type is + // external, even if it has the same name as a type in this scope. + if (TypeFromToken(tkResScope) == mdtAssemblyRef) + continue; + + // Iff the name is found in the typedef table, then use + // that value instead. Won't be found if typeref is trully external. + hr = ImportHelper::FindTypeDefByName(this, szNamespace, szName, + (TypeFromToken(tkResScope) == mdtTypeRef) ? tkResScope : mdTokenNil, + &td); + if (hr != S_OK) + { + // don't propagate the error in the Find + hr = NOERROR; + continue; + } + *(GetTypeRefToTypeDefMap()->Get(index)) = td; + } + + return hr; +} // CMiniMdRW::CalculateTypeRefToTypeDefMap + + +//***************************************************************************** +// Set a remap handler. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SetHandler( + IUnknown *pIUnk) +{ + if (m_pHandler != NULL) + { + m_pHandler->Release(); + m_pHandler = NULL; + } + + if (pIUnk != NULL) + { + // ignore the error for QI the IHostFilter + pIUnk->QueryInterface(IID_IHostFilter, reinterpret_cast<void**>(&m_pHostFilter)); + + return pIUnk->QueryInterface(IID_IMapToken, reinterpret_cast<void**>(&m_pHandler)); + } + + return S_OK; +} // CMiniMdRW::SetHandler + +//***************************************************************************** +// Set a Options +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SetOption( + OptionValue *pOptionValue) +{ + HRESULT hr = NOERROR; + ULONG ixTbl = 0; + int i; + + m_OptionValue = *pOptionValue; + + // Turn off delta metadata bit -- can't be used due to EE assumptions about delta PEs. + // Inspect ApplyEditAndContinue for details. + // To enable this, use the EnableDeltaMetadataGeneration/DisableDeltaMetadataGeneration accessors. + _ASSERTE((m_OptionValue.m_UpdateMode & MDUpdateDelta) != MDUpdateDelta); + +#ifdef _DEBUG + if ((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateENC && + bENCDeltaOnly) + m_OptionValue.m_UpdateMode |= MDUpdateDelta; +#endif + + // if a scope is previously updated as incremental, then it should not be open again + // with full update for read/write. + // + if ((m_Schema.m_heaps & CMiniMdSchema::HAS_DELETE) && + (m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateFull && + !m_fIsReadOnly) + { + IfFailGo( CLDB_E_BADUPDATEMODE ); + } + + if ((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateIncremental) + m_Schema.m_heaps |= CMiniMdSchema::HAS_DELETE; + + // Set the value of sortable based on the options. + switch (m_OptionValue.m_UpdateMode & MDUpdateMask) + { + case MDUpdateFull: + // Always sortable. + for (ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + m_bSortable[ixTbl] = 1; + break; + case MDUpdateENC: + // Never sortable. + for (ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + m_bSortable[ixTbl] = 0; + + // Truncate some tables. + for (i=0; (ixTbl = m_TruncatedEncTables[i]) != (ULONG) -1; ++i) + { + m_Tables[ixTbl].Delete(); + IfFailGo(m_Tables[ixTbl].InitializeEmpty_WithRecordCount( + m_TableDefs[ixTbl].m_cbRec, + 0 + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + INDEBUG_MD(m_Tables[ixTbl].Debug_SetTableInfo(NULL, ixTbl)); + m_Schema.m_cRecs[ixTbl] = 0; + } + + // Out-of-order is expected in an ENC scenario, never an error. + m_OptionValue.m_ErrorIfEmitOutOfOrder = MDErrorOutOfOrderNone; + + break; + case MDUpdateIncremental: + // Sortable if no external token. + for (ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + m_bSortable[ixTbl] = (GetTokenForTable(ixTbl) == (ULONG) -1); + break; + case MDUpdateExtension: + // Never sortable. + for (ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + m_bSortable[ixTbl] = 0; + break; + default: + _ASSERTE(!"Internal error -- unknown save mode"); + return E_INVALIDARG; + } + + // If this is an ENC session, track the generations. + if (!m_fIsReadOnly && (m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateENC) + { +#ifdef FEATURE_METADATA_EMIT + ModuleRec *pMod; + GUID encid; + + // Get the module record. + IfFailGo(GetModuleRecord(1, &pMod)); + +/* Do we really want to do this? This would reset the metadata each time we changed an option + // Copy EncId as BaseId. + uVal = GetCol(TBL_Module, ModuleRec::COL_EncId, pMod); + PutCol(TBL_Module, ModuleRec::COL_EncBaseId, pMod, uVal); +*/ + // Allocate a new GUID for EncId. + IfFailGo(CoCreateGuid(&encid)); + IfFailGo(PutGuid(TBL_Module, ModuleRec::COL_EncId, pMod, encid)); +#else //!FEATURE_METADATA_EMIT + IfFailGo(E_INVALIDARG); +#endif //!FEATURE_METADATA_EMIT + } + +ErrExit: + return hr; +} // CMiniMdRW::SetOption + +//***************************************************************************** +// Get Options +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetOption( + OptionValue *pOptionValue) +{ + *pOptionValue = m_OptionValue; + return S_OK; +} // CMiniMdRW::GetOption + +//***************************************************************************** +// Smart MapToken. Only calls client if token really changed. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::MapToken( // Return value from user callback. + RID from, // Old rid. + RID to, // New rid. + mdToken tkn) // Token type. +{ + HRESULT hr = S_OK; + TOKENREC *pTokenRec; + MDTOKENMAP *pMovementMap; + // If not change, done. + if (from == to) + return S_OK; + + pMovementMap = GetTokenMovementMap(); + _ASSERTE(GetTokenMovementMap() != NULL); + if (pMovementMap != NULL) + IfFailRet(pMovementMap->AppendRecord( TokenFromRid(from, tkn), false, TokenFromRid(to, tkn), &pTokenRec )); + + // Notify client. + if (m_pHandler != NULL) + { + LOG((LOGMD, "CMiniMdRW::MapToken (remap): from 0x%08x to 0x%08x\n", TokenFromRid(from,tkn), TokenFromRid(to,tkn))); + return m_pHandler->Map(TokenFromRid(from,tkn), TokenFromRid(to,tkn)); + } + else + { + return hr; + } +} // CMiniMdCreate::MapToken + +//***************************************************************************** +// Set max, lim, based on data. +//***************************************************************************** +void +CMiniMdRW::ComputeGrowLimits( + int bSmall) // large or small tables? +{ + if (bSmall) + { + // Tables will need to grow if any value exceeds what a two-byte column can hold. + m_maxRid = m_maxIx = 0; + m_limIx = USHRT_MAX >> 1; + m_limRid = USHRT_MAX >> AUTO_GROW_CODED_TOKEN_PADDING; + m_eGrow = eg_ok; + } + else + { + // Tables are already large + m_maxRid = m_maxIx = ULONG_MAX; + m_limIx = USHRT_MAX << 1; + m_limRid = USHRT_MAX << 1; + m_eGrow = eg_grown; + } +} // CMiniMdRW::ComputeGrowLimits + +//***************************************************************************** +// Initialization of a new writable MiniMd's pools. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::InitPoolOnMem( + int iPool, // The pool to initialize. + void *pbData, // The data from which to init. + ULONG cbData, // Size of data. + int fIsReadOnly) // Is the memory read-only? +{ + HRESULT hr; + + switch (iPool) + { + case MDPoolStrings: + if (pbData == NULL) + { // Creates new empty string heap with default empty string entry + IfFailRet(m_StringHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + else + { + IfFailRet(m_StringHeap.Initialize( + MetaData::DataBlob((BYTE *)pbData, cbData), + !fIsReadOnly)); + } + break; + case MDPoolGuids: + if (pbData == NULL) + { // Creates new empty guid heap + IfFailRet(m_GuidHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + else + { + IfFailRet(m_GuidHeap.Initialize( + MetaData::DataBlob((BYTE *)pbData, cbData), + !fIsReadOnly)); + } + break; + case MDPoolBlobs: + if (pbData == NULL) + { + if (IsMinimalDelta()) + { // It's EnC minimal delta, don't include default empty blob + IfFailRet(m_BlobHeap.InitializeEmpty_WithoutDefaultEmptyBlob( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + else + { // Creates new empty blob heap with default empty blob entry + IfFailRet(m_BlobHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + } + else + { + IfFailRet(m_BlobHeap.Initialize( + MetaData::DataBlob((BYTE *)pbData, cbData), + !fIsReadOnly)); + } + break; + case MDPoolUSBlobs: + if (pbData == NULL) + { + if (IsMinimalDelta()) + { // It's EnC minimal delta, don't include default empty user string + IfFailRet(m_UserStringHeap.InitializeEmpty_WithoutDefaultEmptyBlob( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + else + { // Creates new empty user string heap (with default empty !!!blob!!! entry) + // Note: backaward compatiblity: doesn't add default empty user string, but default empty + // blob entry + IfFailRet(m_UserStringHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + } + else + { + IfFailRet(m_UserStringHeap.Initialize( + MetaData::DataBlob((BYTE *)pbData, cbData), + !fIsReadOnly)); + } + break; + default: + hr = E_INVALIDARG; + } + return hr; +} // CMiniMdRW::InitPoolOnMem + +//***************************************************************************** +// Initialization of a new writable MiniMd +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::InitOnMem( + const void *pvBuf, // The data from which to init. + ULONG ulBufLen, // The data size + int fIsReadOnly) // Is the memory read-only? +{ + HRESULT hr = S_OK; + UINT32 cbSchemaSize; // Size of the schema structure. + S_UINT32 cbTotalSize; // Size of all data used. + BYTE *pBuf = const_cast<BYTE*>(reinterpret_cast<const BYTE*>(pvBuf)); + int i; + + // post contruction initialize the embeded lookuptable struct + for (ULONG ixTbl = 0; ixTbl < m_TblCount; ++ixTbl) + { + if (m_TableDefs[ixTbl].m_iKey < m_TableDefs[ixTbl].m_cCols) + { + if (m_pVS[ixTbl] == NULL) + { + m_pVS[ixTbl] = new (nothrow) VirtualSort; + IfNullGo(m_pVS[ixTbl]); + + m_pVS[ixTbl]->Init(ixTbl, m_TableDefs[ixTbl].m_iKey, this); + } + } + } + + // Uncompress the schema from the buffer into our structures. + IfFailGo(SchemaPopulate(pvBuf, ulBufLen, (ULONG *)&cbSchemaSize)); + + if (m_fMinimalDelta) + IfFailGo(InitWithLargeTables()); + + // Initialize the pointers to the rest of the data. + pBuf += cbSchemaSize; + cbTotalSize = S_UINT32(cbSchemaSize); + for (i=0; i<(int)m_TblCount; ++i) + { + if (m_Schema.m_cRecs[i] > 0) + { + // Size of one table is rowsize * rowcount. + S_UINT32 cbTableSize = + S_UINT32(m_TableDefs[i].m_cbRec) * + S_UINT32(m_Schema.m_cRecs[i]); + if (cbTableSize.IsOverflow()) + { + Debug_ReportError("Table is too big, its size overflows."); + IfFailGo(METADATA_E_INVALID_FORMAT); + } + cbTotalSize += cbTableSize; + if (cbTotalSize.IsOverflow()) + { + Debug_ReportError("Total tables size is too big, their total size overflows."); + IfFailGo(METADATA_E_INVALID_FORMAT); + } + IfFailGo(m_Tables[i].Initialize( + m_TableDefs[i].m_cbRec, + MetaData::DataBlob(pBuf, cbTableSize.Value()), + !fIsReadOnly)); // fCopyData + INDEBUG_MD(m_Tables[i].Debug_SetTableInfo(NULL, i)); + pBuf += cbTableSize.Value(); + } + else + { + IfFailGo(m_Tables[i].InitializeEmpty_WithRecordCount( + m_TableDefs[i].m_cbRec, + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + INDEBUG_MD(m_Tables[i].Debug_SetTableInfo(NULL, i)); + } + } + + // If the metadata is being opened for read/write, all the updateable columns + // need to be the same width. + if (!fIsReadOnly) + { + // variable to indicate if tables are large, small or mixed. + int fMixed = false; + int iSize = 0; + CMiniColDef *pCols; // The col defs to init. + int iCol; + + // Look at all the tables, or until mixed sizes are discovered. + for (i=0; i<(int)m_TblCount && fMixed == false; i++) + { // Look at all the columns of the table. + pCols = m_TableDefs[i].m_pColDefs; + for (iCol = 0; iCol < m_TableDefs[i].m_cCols && !fMixed; iCol++) + { // If not a fixed size column... + if (!IsFixedType(m_TableDefs[i].m_pColDefs[iCol].m_Type)) + { // If this is the first non-fixed size column... + if (iSize == 0) + { // remember it's size. + iSize = m_TableDefs[i].m_pColDefs[iCol].m_cbColumn; + } + else + { // Not first non-fixed size, so if a different size... + if (iSize != m_TableDefs[i].m_pColDefs[iCol].m_cbColumn) + { // ...the table has mixed column sizes. + fMixed = true; + } + } + } + } + } + if (fMixed) + { + // grow everything to large + IfFailGo(ExpandTables()); + ComputeGrowLimits(FALSE /* ! small*/); + } + else + { + if (iSize == 2) + { + // small schema + ComputeGrowLimits(TRUE /* small */); + } + else + { + // large schema + ComputeGrowLimits(FALSE /* ! small */); + } + } + } + else + { + // Set the limits so we will know when to grow the database. + ComputeGrowLimits(TRUE /* small */); + } + + // Track records that this MD started with. + m_StartupSchema = m_Schema; + + m_fIsReadOnly = fIsReadOnly ? 1 : 0; + +ErrExit: + return hr; +} // CMiniMdRW::InitOnMem + +//***************************************************************************** +// Validate cross-stream consistency. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PostInit( + int iLevel) +{ + return S_OK; +} // CMiniMdRW::PostInit + +//***************************************************************************** +// Init a CMiniMdRW from the data in a CMiniMd [RO]. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::InitOnRO( + CMiniMd *pMd, // The MiniMd to update from. + int fIsReadOnly) // Will updates be allowed? +{ + HRESULT hr = S_OK; + ULONG i; // Loop control. + + // Init the schema. + IfFailGo(SchemaPopulate(*pMd)); + + // Allocate VS structs for tables with key columns. + for (ULONG ixTbl = 0; ixTbl < m_TblCount; ++ixTbl) + { + if (m_TableDefs[ixTbl].m_iKey < m_TableDefs[ixTbl].m_cCols) + { + m_pVS[ixTbl] = new (nothrow) VirtualSort; + IfNullGo(m_pVS[ixTbl]); + + m_pVS[ixTbl]->Init(ixTbl, m_TableDefs[ixTbl].m_iKey, this); + } + } + + // Copy over the column definitions. + for (i = 0; i < m_TblCount; ++i) + { + _ASSERTE(m_TableDefs[i].m_cCols == pMd->m_TableDefs[i].m_cCols); + m_TableDefs[i].m_cbRec = pMd->m_TableDefs[i].m_cbRec; + IfFailGo(SetNewColumnDefinition(&(m_TableDefs[i]), pMd->m_TableDefs[i].m_pColDefs, i)); + } + + // Initialize string heap + if (pMd->m_StringHeap.GetUnalignedSize() > 0) + { + IfFailGo(m_StringHeap.InitializeFromStringHeap( + &(pMd->m_StringHeap), + !fIsReadOnly)); + } + else + { + IfFailGo(m_StringHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + + // Initialize user string heap + if (pMd->m_UserStringHeap.GetUnalignedSize() > 0) + { + IfFailGo(m_UserStringHeap.InitializeFromBlobHeap( + &(pMd->m_UserStringHeap), + !fIsReadOnly)); + } + else + { + IfFailGo(m_UserStringHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + + // Initialize guid heap + if (pMd->m_GuidHeap.GetSize() > 0) + { + IfFailGo(m_GuidHeap.InitializeFromGuidHeap( + &(pMd->m_GuidHeap), + !fIsReadOnly)); + } + else + { + IfFailGo(m_GuidHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + + // Initialize blob heap + if (pMd->m_BlobHeap.GetUnalignedSize() > 0) + { + IfFailGo(m_BlobHeap.InitializeFromBlobHeap( + &(pMd->m_BlobHeap), + !fIsReadOnly)); + } + else + { + IfFailGo(m_BlobHeap.InitializeEmpty( + 0 + COMMA_INDEBUG_MD(!fIsReadOnly))); + } + + // Init the record pools + for (i = 0; i < m_TblCount; ++i) + { + if (m_Schema.m_cRecs[i] > 0) + { + IfFailGo(m_Tables[i].InitializeFromTable( + &(pMd->m_Tables[i]), + m_TableDefs[i].m_cbRec, + m_Schema.m_cRecs[i], + !fIsReadOnly)); // fCopyData + INDEBUG_MD(m_Tables[i].Debug_SetTableInfo(NULL, i)); + + // We set this bit to indicate the compressed, read-only tables are always sorted + // <TODO>This is not true for all tables, so we should set it correctly and flush out resulting bugs</TODO> + SetSorted(i, true); + } + else + { + IfFailGo(m_Tables[i].InitializeEmpty_WithRecordCount( + m_TableDefs[i].m_cbRec, + 2 + COMMA_INDEBUG_MD(!fIsReadOnly))); + INDEBUG_MD(m_Tables[i].Debug_SetTableInfo(NULL, i)); + // An empty table can be considered unsorted. + SetSorted(i, false); + } + } + + // Set the limits so we will know when to grow the database. + ComputeGrowLimits(TRUE /* small */); + + // Track records that this MD started with. + m_StartupSchema = m_Schema; + + m_fIsReadOnly = fIsReadOnly ? 1 : 0; + +ErrExit: + return hr; +} // CMiniMdRW::InitOnRO + +#ifdef FEATURE_METADATA_CUSTOM_DATA_SOURCE + +// This checks that column sizes are reasonable for their types +// The sizes could still be too small to hold all values in the range, or larger +// than they needed, but there must exist some scenario where this size is the +// one we would use. +// As long as this validation passes + we verify that the records actually +// have space for columns of this size then the worst thing that malicious +// data could do is be slightly inneficient, or be unable to address all their data +HRESULT _ValidateColumnSize(BYTE trustedColumnType, BYTE untrustedColumnSize) +{ + // Is the field a RID into a table? + if (trustedColumnType <= iCodedTokenMax) + { + if (untrustedColumnSize != sizeof(USHORT) && untrustedColumnSize != sizeof(ULONG)) + return CLDB_E_FILE_CORRUPT; + } + else + { // Fixed type. + switch (trustedColumnType) + { + case iBYTE: + if (untrustedColumnSize != 1) + return CLDB_E_FILE_CORRUPT; + break; + case iSHORT: + case iUSHORT: + if (untrustedColumnSize != 2) + return CLDB_E_FILE_CORRUPT; + break; + case iLONG: + case iULONG: + if (untrustedColumnSize != 4) + return CLDB_E_FILE_CORRUPT; + break; + case iSTRING: + case iGUID: + case iBLOB: + if (untrustedColumnSize != 2 && untrustedColumnSize != 4) + return CLDB_E_FILE_CORRUPT; + break; + default: + _ASSERTE(!"Unexpected schema type"); + return CLDB_E_FILE_CORRUPT; + } + } + return S_OK; +} + +__checkReturn +HRESULT CMiniMdRW::InitOnCustomDataSource(IMDCustomDataSource* pDataSource) +{ + HRESULT hr = S_OK; + ULONG i; // Loop control. + ULONG key; + BOOL fIsReadOnly = TRUE; + MetaData::DataBlob stringPoolData; + MetaData::DataBlob userStringPoolData; + MetaData::DataBlob guidHeapData; + MetaData::DataBlob blobHeapData; + MetaData::DataBlob tableRecordData; + CMiniTableDef tableDef; + BOOL sortable = FALSE; + + + // the data source owns all the memory backing the storage pools, so we need to ensure it stays alive + // after this method returns. When the CMiniMdRW is destroyed the reference will be released. + pDataSource->AddRef(); + m_pCustomDataSource = pDataSource; + + // Copy over the schema. + IfFailGo(pDataSource->GetSchema(&m_Schema)); + + // Is this the "native" version of the metadata for this runtime? + if ((m_Schema.m_major != METAMODEL_MAJOR_VER) || (m_Schema.m_minor != METAMODEL_MINOR_VER)) + { + // We don't support this version of the metadata + Debug_ReportError("Unsupported version of MetaData."); + return PostError(CLDB_E_FILE_OLDVER, m_Schema.m_major, m_Schema.m_minor); + } + + // How big are the various pool inidices? + m_iStringsMask = (m_Schema.m_heaps & CMiniMdSchema::HEAP_STRING_4) ? 0xffffffff : 0xffff; + m_iGuidsMask = (m_Schema.m_heaps & CMiniMdSchema::HEAP_GUID_4) ? 0xffffffff : 0xffff; + m_iBlobsMask = (m_Schema.m_heaps & CMiniMdSchema::HEAP_BLOB_4) ? 0xffffffff : 0xffff; + + // Copy over TableDefs, column definitions and allocate VS structs for tables with key columns. + for (ULONG ixTbl = 0; ixTbl < m_TblCount; ++ixTbl) + { + IfFailGo(pDataSource->GetTableDef(ixTbl, &tableDef)); + const CMiniTableDef* pTemplate = GetTableDefTemplate(ixTbl); + + // validate that the table def looks safe + // we only allow some very limited differences between the standard template and the data source + key = (pTemplate->m_iKey < pTemplate->m_cCols) ? pTemplate->m_iKey : 0xFF; + if (key != tableDef.m_iKey) { IfFailGo(CLDB_E_FILE_CORRUPT); } + if (pTemplate->m_cCols != tableDef.m_cCols) { IfFailGo(CLDB_E_FILE_CORRUPT); } + ULONG cbRec = 0; + for (ULONG i = 0; i < pTemplate->m_cCols; i++) + { + if (tableDef.m_pColDefs == NULL) { IfFailGo(CLDB_E_FILE_CORRUPT); } + if (pTemplate->m_pColDefs[i].m_Type != tableDef.m_pColDefs[i].m_Type) { IfFailGo(CLDB_E_FILE_CORRUPT); } + IfFailGo(_ValidateColumnSize(pTemplate->m_pColDefs[i].m_Type, tableDef.m_pColDefs[i].m_cbColumn)); + // sometimes, but not always, it seems like columns get alignment padding + // we'll allow it if we see it + if (cbRec > tableDef.m_pColDefs[i].m_oColumn) { IfFailGo(CLDB_E_FILE_CORRUPT); } + if (tableDef.m_pColDefs[i].m_oColumn > AlignUp(cbRec, tableDef.m_pColDefs[i].m_cbColumn)) { IfFailGo(CLDB_E_FILE_CORRUPT); } + cbRec = tableDef.m_pColDefs[i].m_oColumn + tableDef.m_pColDefs[i].m_cbColumn; + } + if (tableDef.m_cbRec != cbRec) { IfFailGo(CLDB_E_FILE_CORRUPT); } + + // tabledef passed validation, copy it in + m_TableDefs[ixTbl].m_iKey = tableDef.m_iKey; + m_TableDefs[ixTbl].m_cCols = tableDef.m_cCols; + m_TableDefs[ixTbl].m_cbRec = tableDef.m_cbRec; + IfFailGo(SetNewColumnDefinition(&(m_TableDefs[ixTbl]), tableDef.m_pColDefs, ixTbl)); + if (m_TableDefs[ixTbl].m_iKey < m_TableDefs[ixTbl].m_cCols) + { + m_pVS[ixTbl] = new (nothrow)VirtualSort; + IfNullGo(m_pVS[ixTbl]); + + m_pVS[ixTbl]->Init(ixTbl, m_TableDefs[ixTbl].m_iKey, this); + } + } + + // Initialize string heap + IfFailGo(pDataSource->GetStringHeap(&stringPoolData)); + m_StringHeap.Initialize(stringPoolData, !fIsReadOnly); + + // Initialize user string heap + IfFailGo(pDataSource->GetUserStringHeap(&userStringPoolData)); + m_UserStringHeap.Initialize(userStringPoolData, !fIsReadOnly); + + // Initialize guid heap + IfFailGo(pDataSource->GetGuidHeap(&guidHeapData)); + m_GuidHeap.Initialize(guidHeapData, !fIsReadOnly); + + // Initialize blob heap + IfFailGo(pDataSource->GetBlobHeap(&blobHeapData)); + m_BlobHeap.Initialize(blobHeapData, !fIsReadOnly); + + // Init the record pools + for (i = 0; i < m_TblCount; ++i) + { + IfFailGo(pDataSource->GetTableRecords(i, &tableRecordData)); + // sanity check record counts and table sizes, this also ensures that cbRec*m_cRecs[x] doesn't overflow + if (m_Schema.m_cRecs[i] > 1000000) { IfFailGo(CLDB_E_FILE_CORRUPT); } + if (tableRecordData.GetSize() < m_TableDefs[i].m_cbRec * m_Schema.m_cRecs[i]) { IfFailGo(CLDB_E_FILE_CORRUPT); } + m_Tables[i].Initialize(m_TableDefs[i].m_cbRec, tableRecordData, !fIsReadOnly); + + IfFailGo(pDataSource->GetTableSortable(i, &sortable)); + m_bSortable[i] = sortable; + } + + // Set the limits so we will know when to grow the database. + ComputeGrowLimits(TRUE /* small */); + + // Track records that this MD started with. + m_StartupSchema = m_Schema; + + m_fIsReadOnly = fIsReadOnly; + +ErrExit: + return hr; +} +#endif + +//***************************************************************************** +// Convert a read-only to read-write. Copies data. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ConvertToRW() +{ + HRESULT hr = S_OK; + int i; // Loop control. + + // Check for already done. + if (!m_fIsReadOnly) + return hr; + + // If this is a minimal delta, then we won't allow it to be RW + if (IsMinimalDelta()) + return CLDB_E_INCOMPATIBLE; + + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return COR_E_STACKOVERFLOW); + + IfFailGo(m_StringHeap.MakeWritable()); + IfFailGo(m_GuidHeap.MakeWritable()); + IfFailGo(m_UserStringHeap.MakeWritable()); + IfFailGo(m_BlobHeap.MakeWritable()); + + // Init the record pools + for (i = 0; i < (int)m_TblCount; ++i) + { + IfFailGo(m_Tables[i].MakeWritable()); + } + + // Grow the tables. + IfFailGo(ExpandTables()); + + // Track records that this MD started with. + m_StartupSchema = m_Schema; + + // No longer read-only. + m_fIsReadOnly = false; + +ErrExit: + ; + END_SO_INTOLERANT_CODE; + return hr; +} // CMiniMdRW::ConvertToRW + +//***************************************************************************** +// Initialization of a new writable MiniMd +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::InitNew() +{ + HRESULT hr = S_OK; + int i; // Loop control. + + // Initialize the Schema. + IfFailGo(m_Schema.InitNew(m_OptionValue.m_MetadataVersion)); + + // Allocate VS structs for tables with key columns. + for (ULONG ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + if (m_TableDefs[ixTbl].m_iKey < m_TableDefs[ixTbl].m_cCols) + { + m_pVS[ixTbl] = new (nothrow) VirtualSort; + IfNullGo(m_pVS[ixTbl]); + + m_pVS[ixTbl]->Init(ixTbl, m_TableDefs[ixTbl].m_iKey, this); + } + } + + enum MetaDataSizeIndex sizeIndex; + sizeIndex = GetMetaDataSizeIndex(&m_OptionValue); + if ((sizeIndex == MDSizeIndex_Standard) || (sizeIndex == MDSizeIndex_Minimal)) + { + // OutputDebugStringA("Default small tables enabled\n"); + // How big are the various pool inidices? + m_Schema.m_heaps = 0; + // How many rows in various tables? + for (i = 0; i < (int)m_TblCount; ++i) + { + m_Schema.m_cRecs[i] = 0; + } + + // Compute how many bits required to hold. + m_Schema.m_rid = 1; + m_maxRid = m_maxIx = 0; + m_limIx = USHRT_MAX >> 1; + m_limRid = USHRT_MAX >> AUTO_GROW_CODED_TOKEN_PADDING; + m_eGrow = eg_ok; + } + + // Now call base class function to calculate the offsets, sizes. + IfFailGo(SchemaPopulate2(NULL)); + + // Initialize the record heaps. + for (i = 0; i < (int)m_TblCount; ++i) + { // Don't really have any records yet. + m_Schema.m_cRecs[i] = 0; + IfFailGo(m_Tables[i].InitializeEmpty_WithRecordCount( + m_TableDefs[i].m_cbRec, + g_TblSizeInfo[sizeIndex][i] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + INDEBUG_MD(m_Tables[i].Debug_SetTableInfo(NULL, i)); + + // Create tables as un-sorted. We hope to add all records, then sort just once. + SetSorted(i, false); + } + + // Initialize heaps + IfFailGo(m_StringHeap.InitializeEmpty_WithItemsCount( + g_PoolSizeInfo[sizeIndex][IX_STRING_POOL][0], + g_PoolSizeInfo[sizeIndex][IX_STRING_POOL][1] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + IfFailGo(m_BlobHeap.InitializeEmpty_WithItemsCount( + g_PoolSizeInfo[sizeIndex][IX_BLOB_POOL][0], + g_PoolSizeInfo[sizeIndex][IX_BLOB_POOL][1] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + IfFailGo(m_UserStringHeap.InitializeEmpty_WithItemsCount( + g_PoolSizeInfo[sizeIndex][IX_US_BLOB_POOL][0], + g_PoolSizeInfo[sizeIndex][IX_US_BLOB_POOL][1] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + IfFailGo(m_GuidHeap.InitializeEmpty_WithItemsCount( + g_PoolSizeInfo[sizeIndex][IX_GUID_POOL][0], + g_PoolSizeInfo[sizeIndex][IX_GUID_POOL][1] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + + // Track records that this MD started with. + m_StartupSchema = m_Schema; + + // New db is never read-only. + m_fIsReadOnly = false; + +ErrExit: + return hr; +} // CMiniMdRW::InitNew + +#ifdef FEATURE_PREJIT +//***************************************************************************** +// Helper function to determine the size of hot tables +//***************************************************************************** +static int ShiftCount(ULONG itemCount, ULONG hotItemCount) +{ + // figure out how many bits are needed to represent the highest rid + ULONG highestRid = itemCount; + int bitCount = 0; + while ((1UL<<bitCount) <= highestRid) + bitCount++; + int shiftCount = bitCount > 8 ? bitCount - 8 : 0; + + // tune the shift count so that we don't need to search more than 4 hot entries on average. + while ((hotItemCount >> shiftCount) > 4) + shiftCount++; + if (shiftCount > 16) + shiftCount = 16; + return shiftCount; +} // ShiftCount + +//***************************************************************************** +// Helper function to qsort hot tokens +//***************************************************************************** + +typedef struct _TokenIndexPair +{ + mdToken token; + WORD index; +} TokenIndexPair; + +static WORD shiftCount; +static int __cdecl TokenCmp(const void *a, const void *b) +{ + mdToken ta = ((const TokenIndexPair *)a)->token; + mdToken tb = ((const TokenIndexPair *)b)->token; + if (shiftCount > 0) + { + // shiftCount is the number of low order bits that are used to index + // into the first level table. The below swaps high and low order bits so + // the values with common low order bits end up together after the sort. + ta = (ta >> shiftCount) | ((ta & ((1<<shiftCount)-1)) << (32-shiftCount)); + tb = (tb >> shiftCount) | ((tb & ((1<<shiftCount)-1)) << (32-shiftCount)); + } + if (ta < tb) + return -1; + else if (ta > tb) + return 1; + else + return 0; +} + +//***************************************************************************** +// A wrapper for metadata's use of CorProfileData::GetHotTokens that recognizes tokens +// flagged with ProfilingFlags_MetaDataSearch and reinterprets them into a corresponding +// set of ProfilingFlags_MetaData tokens. +// +// If you are reading this because you are changing the implementation of one of the searches +// in CMiniMdBase, it should be a mechanical process to copy the new search code below and +// change the row accesses into setting values in the rowFlags array. +// +// Doing so allows us to fix the problem that incremental IBC is fundamentally not suited to +// metadata searches. +// +// For instance, consider the following scenario: +// - You gather IBC data on a scenario that does a metadata binary search +// - The data comes from build X where the table is of size 100 and the target is in row 18 +// - This means the intermediate values touched are rows 50, 25, and 12 +// - You then apply this IBC data to build Y which has changed to include 20 new entries to start the table +// - Naively, incremental IBC would have remapped these tokens and predicted accesses at rows 70, 35, 32, and 38 +// - But this is wrong! And very bad for working set. The search will actually touch 60, 30, and 45 on the way to 38 +// +// The solution is to only store rows in IBC data that were touched intentionally, either as direct +// accesses (with ProfilingFlags_MetaData) or as the result of searches (ProfilingFlags_MetaDataSearch). +// We then expand these "search tokens" here into the set of accesses that would occur on the current +// table as we do our various types of metadata search for them. +// +// Currently, we infer touches for the following types of access: +// - Direct access (getRow) +// - Binary search (CMiniMdBase::vSearchTable or CMiniMdBase::vSearchTableNotGreater) +// - Bounds of a multi-element span (CMiniMdBase::SearchTableForMultipleRows) +// +// In theory, we could have different flags for each type of search (e.g. binary, multiple-row, etc) and +// avoid any over-reporting of intermediate tokens, but in practice the IBC flag bits are scarce and +// measurements show a minimal (<1%) amount of over-reporting. +// +//***************************************************************************** + +enum HotTokenFlags +{ + HotTokenFlags_Cold = 0x0, + HotTokenFlags_ProfiledAccess = 0x1, + HotTokenFlags_IntermediateInBinarySearch = 0x2, + HotTokenFlags_BoundingMultipleRowSearch = 0x4 +}; + +__checkReturn +HRESULT +CMiniMdRW::GetHotMetadataTokensSearchAware( + CorProfileData *pProfileData, + ULONG ixTbl, + ULONG *pResultCount, + mdToken *tokenBuffer, + ULONG maxCount) +{ + HRESULT hr = S_OK; + ULONG resultCount = 0; + + ULONG metadataAccessFlag = 1<<ProfilingFlags_MetaData; + ULONG metadataSearchFlag = 1<<ProfilingFlags_MetaDataSearch; + + // Query the profile data to determine the number of hot search tokens + ULONG numSearchTokens = pProfileData->GetHotTokens(ixTbl, metadataSearchFlag, metadataSearchFlag, NULL, 0); + ULONG cRecs = GetCountRecs(ixTbl); + + if (numSearchTokens == 0 || cRecs == 0) + { + // If there are none, we can simply return the hot access tokens without doing any interesting work + resultCount = pProfileData->GetHotTokens(ixTbl, metadataAccessFlag, metadataAccessFlag, tokenBuffer, maxCount); + } + else + { + // But if there are hot search tokens, we need to infer what intermediate rows will be touched by our various types of metadata searching. + // To do so, retrieve all hot tokens and allocate temporary storage to use to mark which rows should be considered hot and for what reason + // (i.e. an array of HotTokenFlags, one per entry in the table, indexed by RID). + ULONG numAccessTokens = pProfileData->GetHotTokens(ixTbl, metadataAccessFlag, metadataAccessFlag, NULL, 0); + + NewArrayHolder<mdToken> searchTokens = new (nothrow) mdToken[numSearchTokens]; + IfNullGo(searchTokens); + NewArrayHolder<mdToken> accessTokens = new (nothrow) mdToken[numAccessTokens]; + IfNullGo(accessTokens); + NewArrayHolder<BYTE> rowFlags = new (nothrow) BYTE[cRecs + 1]; + IfNullGo(rowFlags); + + pProfileData->GetHotTokens(ixTbl, metadataSearchFlag, metadataSearchFlag, searchTokens, numSearchTokens); + pProfileData->GetHotTokens(ixTbl, metadataAccessFlag, metadataAccessFlag, accessTokens, numAccessTokens); + + // Initially, consider all rows cold + memset(rowFlags, HotTokenFlags_Cold, cRecs + 1); + + // Category 1: Rows may have been touched directly (getRow) + // Simply mark the corresponding entry to each access token + for (ULONG i = 0; i < numAccessTokens; ++i) + { + RID rid = RidFromToken(accessTokens[i]); + + if (rid <= cRecs) + { + rowFlags[rid] |= HotTokenFlags_ProfiledAccess; + } + } + + // Category 2: Rows may have been intermediate touches in a binary search (CMiniMdBase::vSearchTable or CMiniMdBase::vSearchTableNotGreater) + // A search token may indicate where a binary search stopped, so for each of them compute and mark the intermediate set of rows that would have been touched + for (ULONG i = 0; i < numSearchTokens; ++i) + { + RID rid = RidFromToken(searchTokens[i]); + + ULONG lo = 1; + ULONG hi = cRecs; + + while (lo <= hi) + { + ULONG mid = (lo + hi) / 2; + + if (mid <= cRecs) + { + rowFlags[mid] |= HotTokenFlags_IntermediateInBinarySearch; + } + + if (mid == rid) + { + break; + } + + if (mid < rid) + lo = mid + 1; + else + hi = mid - 1; + } + } + + // Category 3: Rows may have been touched to find the bounds of a multiple element span (CMiniMdBase::SearchTableForMultipleRows) + // A search token will indicate where the search stopped, so mark the first row before and after each that was not itself touched + for (ULONG i = 0; i < numSearchTokens; ++i) + { + RID rid = RidFromToken(searchTokens[i]); + + for (RID r = rid - 1; r >= 1 && r <= cRecs; --r) + { + if ((rowFlags[r] & HotTokenFlags_ProfiledAccess) == 0) + { + rowFlags[r] |= HotTokenFlags_BoundingMultipleRowSearch; + break; + } + } + + for (RID r = rid + 1; r <= cRecs; ++r) + { + if ((rowFlags[r] & HotTokenFlags_ProfiledAccess) == 0) + { + rowFlags[r] |= HotTokenFlags_BoundingMultipleRowSearch; + break; + } + } + } + + // Now walk back over our temporary storage, counting and possibly returning the computed hot tokens + resultCount = 0; + for (ULONG i = 1; i <= cRecs; ++i) + { + if (rowFlags[i] != HotTokenFlags_Cold) + { + if (tokenBuffer != NULL && resultCount < maxCount) + tokenBuffer[resultCount] = TokenFromRid(i, ixTbl << 24); + resultCount++; + } + } + } + + if (pResultCount) + *pResultCount = resultCount; + + ErrExit: + return hr; +} // CMiniMdRW::GetHotMetadataTokensSearchAware + + +#endif //FEATURE_PREJIT + +//***************************************************************************** +// Determine how big the tables would be when saved. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetFullSaveSize( + CorSaveSize fSave, // [IN] cssAccurate or cssQuick. + UINT32 *pcbSaveSize, // [OUT] Put the size here. + DWORD *pbSaveCompressed, // [OUT] Will the saved data be fully compressed? + MetaDataReorderingOptions reorderingOptions, // [IN] Metadata reordering options + CorProfileData *pProfileData) // [IN] Optional IBC profile data for working set optimization +{ + HRESULT hr = S_OK; + CMiniTableDef sTempTable; // Definition for a temporary table. + CQuickArray<CMiniColDef> rTempCols; // Definition for a temp table's columns. + BYTE SchemaBuf[sizeof(CMiniMdSchema)]; //Buffer for compressed schema. + ULONG cbAlign; // Bytes needed for alignment. + UINT32 cbTable; // Bytes in a table. + UINT32 cbTotal; // Bytes written. + int i; // Loop control. + + _ASSERTE(m_bPreSaveDone); +#ifndef FEATURE_PREJIT + _ASSERTE(pProfileData == NULL); +#endif //!FEATURE_PREJIT + + // Determine if the stream is "fully compressed", ie no pointer tables. + *pbSaveCompressed = true; + for (i=0; i<(int)m_TblCount; ++i) + { + if (HasIndirectTable(i)) + { + *pbSaveCompressed = false; + break; + } + } + + // Build the header. + CMiniMdSchema Schema = m_Schema; + IfFailGo(m_StringHeap.GetAlignedSize(&cbTable)); + if (cbTable > USHRT_MAX) + { + Schema.m_heaps |= CMiniMdSchema::HEAP_STRING_4; + } + else + { + Schema.m_heaps &= ~CMiniMdSchema::HEAP_STRING_4; + } + + IfFailGo(m_BlobHeap.GetAlignedSize(&cbTable)); + if (cbTable > USHRT_MAX) + { + Schema.m_heaps |= CMiniMdSchema::HEAP_BLOB_4; + } + else + { + Schema.m_heaps &= ~CMiniMdSchema::HEAP_BLOB_4; + } + + if (m_GuidHeap.GetSize() > USHRT_MAX) + { + Schema.m_heaps |= CMiniMdSchema::HEAP_GUID_4; + } + else + { + Schema.m_heaps &= ~CMiniMdSchema::HEAP_GUID_4; + } + + cbTotal = 0; + // schema isn't saved for the hot metadata + if (pProfileData == NULL) + { + cbTotal = Schema.SaveTo(SchemaBuf); + if ( (cbAlign = Align4(cbTotal) - cbTotal) != 0) + cbTotal += cbAlign; + } + + // For each table... + ULONG ixTbl; + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + if (GetCountRecs(ixTbl)) + { + // Determine how big the compressed table will be. + + // Allocate a def for the temporary table. + sTempTable = m_TableDefs[ixTbl]; + if (m_eGrow == eg_grown) + { + IfFailGo(rTempCols.ReSizeNoThrow(sTempTable.m_cCols)); + sTempTable.m_pColDefs = rTempCols.Ptr(); + + // Initialize temp table col defs based on actual counts of data in the + // real tables. + IfFailGo(InitColsForTable(Schema, ixTbl, &sTempTable, 1, FALSE)); + } + + cbTable = sTempTable.m_cbRec * GetCountRecs(ixTbl); + +#ifdef FEATURE_PREJIT + if (pProfileData != NULL) + { + ULONG itemCount = GetCountRecs(ixTbl); + + // determine number of rows touched in this table as indicated by IBC profile data + ULONG hotItemCount = 0; + IfFailGo(GetHotMetadataTokensSearchAware(pProfileData, ixTbl, &hotItemCount, NULL, 0)); + + // assume ManifestResource table is touched completely if touched at all or any hot metadata at all so far + // this is because it's searched linearly, and IBC data misses an unsuccessful search + // after module load + if (ixTbl == TBL_ManifestResource && (hotItemCount > 0 || cbTotal != 0)) + hotItemCount = itemCount; + + // if the hot subset of the rows along with their side lookup tables will occupy more space + // than the full table, keep the full table to both save space and access time. + if (hotItemCount <= USHRT_MAX && itemCount <= USHRT_MAX && m_TableDefs[ixTbl].m_cbRec <= SHRT_MAX) + { + ULONG estimatedSizeUsingSubsetCopy = hotItemCount * (sizeof(WORD) + sizeof(BYTE) + m_TableDefs[ixTbl].m_cbRec); + ULONG estimatedSizeUsingFullCopy = itemCount * m_TableDefs[ixTbl].m_cbRec; + + if (estimatedSizeUsingSubsetCopy > estimatedSizeUsingFullCopy) + hotItemCount = itemCount; + } + + // first level table is array of WORD, so we can't handle more than 2**16 hot items + if (hotItemCount > USHRT_MAX) + hotItemCount = 0; + + cbTable = 0; + if (hotItemCount > 0) + { + cbTotal = Align4(cbTotal); + cbTable = 5*sizeof(DWORD) + sizeof(WORD); // header: count, 4 offsets, shift count + shiftCount = ShiftCount(itemCount, hotItemCount); + if (hotItemCount < itemCount) + { + cbTable += ((1<<shiftCount) + 1) * sizeof(WORD); // 1st level table + cbTable += hotItemCount*sizeof(BYTE); // 2nd level table + cbTable += hotItemCount*sizeof(WORD); // Index mapping table + } + cbTable = Align4(cbTable); // align hot metadata on 4-byte boundary + cbTable += sTempTable.m_cbRec * hotItemCount; // size of hot metadata + + LOG((LOGMD, "CMiniMdRW::GetFullSaveSize: table %2d %5d items %3d hot items %2d shift count %4d total size\n", ixTbl, itemCount, hotItemCount, shiftCount, cbTable)); + } + else + LOG((LOGMD, "CMiniMdRW::GetFullSaveSize: table %2d %5d items\n", ixTbl, itemCount)); + } +#endif //FEATURE_PREJIT + cbTotal += cbTable; + } + } + + // Pad with at least 2 bytes and align on 4 bytes. + cbAlign = Align4(cbTotal) - cbTotal; + if (cbAlign < 2) + cbAlign += 4; + cbTotal += cbAlign; + + if (pProfileData != NULL) + { +#ifdef FEATURE_PREJIT + UINT32 cbHotHeapsSize = 0; + + IfFailGo(GetHotPoolsSaveSize(&cbHotHeapsSize, reorderingOptions, pProfileData)); + cbTotal += cbHotHeapsSize; + + if (cbTotal <= 4) + cbTotal = 0; + else + cbTotal += sizeof(UINT32) + m_TblCount*sizeof(UINT32) + + 2 * sizeof(UINT32); // plus the size of hot metadata header +#endif //FEATURE_PREJIT + } + else + { + m_cbSaveSize = cbTotal; + } + + LOG((LOGMD, "CMiniMdRW::GetFullSaveSize: Total %ssize = %d\n", pProfileData ? "hot " : "", cbTotal)); + + *pcbSaveSize = cbTotal; + +ErrExit: + return hr; +} // CMiniMdRW::GetFullSaveSize + +//***************************************************************************** +// GetSaveSize for saving just the delta (ENC) data. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetENCSaveSize( // S_OK or error. + UINT32 *pcbSaveSize) // [OUT] Put the size here. +{ + HRESULT hr = S_OK; + BYTE SchemaBuf[sizeof(CMiniMdSchema)]; //Buffer for compressed schema. + ULONG cbAlign; // Bytes needed for alignment. + UINT32 cbTable; // Bytes in a table. + UINT32 cbTotal; // Bytes written. + ULONG ixTbl; // Loop control. + + // If not saving deltas, defer to full GetSaveSize. + if ((m_OptionValue.m_UpdateMode & MDUpdateDelta) != MDUpdateDelta) + { + DWORD bCompressed; + return GetFullSaveSize(cssAccurate, pcbSaveSize, &bCompressed); + } + + // Make sure the minimal deltas have expanded tables + IfFailRet(ExpandTables()); + + // Build the header. + CMiniMdSchema Schema = m_Schema; + + if (m_rENCRecs != NULL) + { + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + Schema.m_cRecs[ixTbl] = m_rENCRecs[ixTbl].Count(); + } + else + { + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + Schema.m_cRecs[ixTbl] = 0; + } + + Schema.m_cRecs[TBL_Module] = m_Schema.m_cRecs[TBL_Module]; + Schema.m_cRecs[TBL_ENCLog] = m_Schema.m_cRecs[TBL_ENCLog]; + Schema.m_cRecs[TBL_ENCMap] = m_Schema.m_cRecs[TBL_ENCMap]; + + cbTotal = Schema.SaveTo(SchemaBuf); + if ( (cbAlign = Align4(cbTotal) - cbTotal) != 0) + cbTotal += cbAlign; + + // Accumulate size of each table... + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { // ENC tables are special. + if (ixTbl == TBL_ENCLog || ixTbl == TBL_ENCMap || ixTbl == TBL_Module) + cbTable = m_Schema.m_cRecs[ixTbl] * m_TableDefs[ixTbl].m_cbRec; + else + cbTable = Schema.m_cRecs[ixTbl] * m_TableDefs[ixTbl].m_cbRec; + cbTotal += cbTable; + } + + // Pad with at least 2 bytes and align on 4 bytes. + cbAlign = Align4(cbTotal) - cbTotal; + if (cbAlign < 2) + cbAlign += 4; + cbTotal += cbAlign; + + *pcbSaveSize = cbTotal; + m_cbSaveSize = cbTotal; + +//ErrExit: + return hr; +} // CMiniMdRW::GetENCSaveSize + + +#ifdef FEATURE_PREJIT + +// Determine the size of the hot blob data +// +__checkReturn +HRESULT +CMiniMdRW::GetHotPoolsSaveSize( + UINT32 *pcbSize, + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) +{ + HRESULT hr = S_OK; + UINT32 cbSavedDirSize = 0; + UINT32 cbSavedHeapsSize = 0; + + StreamUtil::NullStream stream; + IfFailGo(SaveHotPoolsToStream( + &stream, + reorderingOptions, + pProfileData, + &cbSavedDirSize, + &cbSavedHeapsSize)); + *pcbSize = cbSavedDirSize + cbSavedHeapsSize; + +ErrExit: + return hr; +} // CMiniMdRW::GetHotPoolsSaveSize + +#endif //FEATURE_PREJIT + + +//***************************************************************************** +// Determine how big the tables would be when saved. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetSaveSize( + CorSaveSize fSave, // [IN] cssAccurate or cssQuick. + UINT32 *pcbSaveSize, // [OUT] Put the size here. + DWORD *pbSaveCompressed, // [OUT] Will the saved data be fully compressed? + MetaDataReorderingOptions reorderingOptions, // [IN] Optional metadata reordering options + CorProfileData *pProfileData) // [IN] Optional IBC profile data for working set optimization +{ + HRESULT hr; + + // Prepare the data for save. + IfFailGo(PreSave()); + + switch (m_OptionValue.m_UpdateMode & MDUpdateMask) + { + case MDUpdateFull: + hr = GetFullSaveSize(fSave, pcbSaveSize, pbSaveCompressed, reorderingOptions, pProfileData); + break; + case MDUpdateIncremental: + case MDUpdateExtension: + case MDUpdateENC: + hr = GetFullSaveSize(fSave, pcbSaveSize, pbSaveCompressed, NoReordering, pProfileData); + // never save compressed if it is incremental compilation. + *pbSaveCompressed = false; + break; + case MDUpdateDelta: + *pbSaveCompressed = false; + hr = GetENCSaveSize(pcbSaveSize); + break; + default: + _ASSERTE(!"Internal error -- unknown save mode"); + return E_INVALIDARG; + } + +ErrExit: + return hr; +} // CMiniMdRW::GetSaveSize + +//***************************************************************************** +// Determine how big a pool would be when saved full size. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetFullPoolSaveSize( // S_OK or error. + int iPool, // The pool of interest. + UINT32 *pcbSaveSize) // [OUT] Put the size here. +{ + HRESULT hr; + + switch (iPool) + { + case MDPoolStrings: + hr = m_StringHeap.GetAlignedSize(pcbSaveSize); + break; + case MDPoolGuids: + *pcbSaveSize = m_GuidHeap.GetSize(); + hr = S_OK; + break; + case MDPoolBlobs: + hr = m_BlobHeap.GetAlignedSize(pcbSaveSize); + break; + case MDPoolUSBlobs: + hr = m_UserStringHeap.GetAlignedSize(pcbSaveSize); + break; + default: + hr = E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::GetFullPoolSaveSize + +//***************************************************************************** +// Determine how big a pool would be when saved ENC size. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetENCPoolSaveSize( + int iPool, // The pool of interest. + UINT32 *pcbSaveSize) // [OUT] Put the size here. +{ + HRESULT hr; + + switch (iPool) + { + case MDPoolStrings: + IfFailRet(m_StringHeap.GetEnCSessionAddedHeapSize_Aligned(pcbSaveSize)); + hr = S_OK; + break; + case MDPoolGuids: + // We never save delta guid heap, we save full guid heap everytime + *pcbSaveSize = m_GuidHeap.GetSize(); + hr = S_OK; + break; + case MDPoolBlobs: + IfFailRet(m_BlobHeap.GetEnCSessionAddedHeapSize_Aligned(pcbSaveSize)); + hr = S_OK; + break; + case MDPoolUSBlobs: + IfFailRet(m_UserStringHeap.GetEnCSessionAddedHeapSize_Aligned(pcbSaveSize)); + hr = S_OK; + break; + default: + hr = E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::GetENCPoolSaveSize + +//***************************************************************************** +// Determine how big a pool would be when saved. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::GetPoolSaveSize( + int iPool, // The pool of interest. + UINT32 *pcbSaveSize) // [OUT] Put the size here. +{ + HRESULT hr; + + switch (m_OptionValue.m_UpdateMode & MDUpdateMask) + { + case MDUpdateFull: + case MDUpdateIncremental: + case MDUpdateExtension: + case MDUpdateENC: + hr = GetFullPoolSaveSize(iPool, pcbSaveSize); + break; + case MDUpdateDelta: + hr = GetENCPoolSaveSize(iPool, pcbSaveSize); + break; + default: + _ASSERTE(!"Internal error -- unknown save mode"); + return E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::GetPoolSaveSize + +//***************************************************************************** +// Is the given pool empty? +//***************************************************************************** +int CMiniMdRW::IsPoolEmpty( // True or false. + int iPool) // The pool of interest. +{ + switch (iPool) + { + case MDPoolStrings: + return m_StringHeap.IsEmpty(); + case MDPoolGuids: + return m_GuidHeap.IsEmpty(); + case MDPoolBlobs: + return m_BlobHeap.IsEmpty(); + case MDPoolUSBlobs: + return m_UserStringHeap.IsEmpty(); + } + return true; +} // CMiniMdRW::IsPoolEmpty + +// -------------------------------------------------------------------------------------- +// +// Gets user string (*Data) at index (nIndex) and fills the index (*pnNextIndex) of the next user string +// in the heap. +// Returns S_OK and fills the string (*pData) and the next index (*pnNextIndex). +// Returns S_FALSE if the index (nIndex) is not valid user string index. +// Returns error code otherwise. +// Clears *pData and sets *pnNextIndex to 0 on error or S_FALSE. +// +__checkReturn +HRESULT +CMiniMdRW::GetUserStringAndNextIndex( + UINT32 nIndex, + MetaData::DataBlob *pData, + UINT32 *pnNextIndex) +{ + HRESULT hr = S_OK; + MINIMD_POSSIBLE_INTERNAL_POINTER_EXPOSED(); + + // First check that the index is valid to avoid debug error reporting + // If this turns out to be slow, then we can add a new API to BlobHeap "GetBlobWithSizePrefix_DontFail" + // to merge this check with following GetBlobWithSizePrefix call + if (!m_UserStringHeap.IsValidIndex(nIndex)) + { + return S_FALSE; + } + + // Get user string at index nIndex (verifies that the user string is in the heap) + IfFailGo(m_UserStringHeap.GetBlobWithSizePrefix( + nIndex, + pData)); + _ASSERTE(hr == S_OK); + + // Get index behind the user string - doesn't overflow, because the user string is in the heap + *pnNextIndex = nIndex + pData->GetSize(); + + UINT32 cbUserStringSize_Ignore; + if (!pData->GetCompressedU(&cbUserStringSize_Ignore)) + { + Debug_ReportInternalError("There's a bug, because previous call to GetBlobWithSizePrefix succeeded."); + IfFailGo(METADATA_E_INTERNAL_ERROR); + } + return S_OK; + +ErrExit: + // Fill output parameters on error + *pnNextIndex = 0; + pData->Clear(); + + return hr; +} // CMiniMdRW::GetUserStringAndNextIndex + +//***************************************************************************** +// Initialized TokenRemapManager +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::InitTokenRemapManager() +{ + HRESULT hr = NOERROR; + + if (m_pTokenRemapManager == NULL) + { + // allocate TokenRemapManager + m_pTokenRemapManager = new (nothrow) TokenRemapManager; + IfNullGo(m_pTokenRemapManager); + } + + // initialize the ref to def optimization map + IfFailGo( m_pTokenRemapManager->ClearAndEnsureCapacity(m_Schema.m_cRecs[TBL_TypeRef], m_Schema.m_cRecs[TBL_MemberRef])); + +ErrExit: + return hr; +} // CMiniMdRW::InitTokenRemapManager + +//***************************************************************************** +// Debug code to check whether a table's objects can have custom attributes +// attached. +//***************************************************************************** +#ifdef _DEBUG +bool CMiniMdRW::CanHaveCustomAttribute( // Can a given table have a custom attribute token? + ULONG ixTbl) // Table in question. +{ + mdToken tk = GetTokenForTable(ixTbl); + size_t ix; + for (ix=0; ix<g_CodedTokens[CDTKN_HasCustomAttribute].m_cTokens; ++ix) + if (g_CodedTokens[CDTKN_HasCustomAttribute].m_pTokens[ix] == tk) + return true; + return false; +} // CMiniMdRW::CanHaveCustomAttribute +#endif //_DEBUG + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +//--------------------------------------------------------------------------------------- +// +// Perform any available pre-save optimizations. +// +__checkReturn +HRESULT +CMiniMdRW::PreSaveFull() +{ + HRESULT hr = S_OK; + RID ridPtr; // A RID from a pointer table. + + if (m_bPreSaveDone) + return hr; + + // Don't yet know what the save size will be. + m_cbSaveSize = 0; + m_bSaveCompressed = false; + + // Convert any END_OF_TABLE values for tables with child pointer tables. + IfFailGo(ConvertMarkerToEndOfTable( + TBL_TypeDef, + TypeDefRec::COL_MethodList, + m_Schema.m_cRecs[TBL_Method] + 1, + m_Schema.m_cRecs[TBL_TypeDef])); + IfFailGo(ConvertMarkerToEndOfTable( + TBL_TypeDef, + TypeDefRec::COL_FieldList, + m_Schema.m_cRecs[TBL_Field] + 1, + m_Schema.m_cRecs[TBL_TypeDef])); + IfFailGo(ConvertMarkerToEndOfTable( + TBL_Method, + MethodRec::COL_ParamList, + m_Schema.m_cRecs[TBL_Param]+1, + m_Schema.m_cRecs[TBL_Method])); + IfFailGo(ConvertMarkerToEndOfTable( + TBL_PropertyMap, + PropertyMapRec::COL_PropertyList, + m_Schema.m_cRecs[TBL_Property] + 1, + m_Schema.m_cRecs[TBL_PropertyMap])); + IfFailGo(ConvertMarkerToEndOfTable( + TBL_EventMap, + EventMapRec::COL_EventList, + m_Schema.m_cRecs[TBL_Event] + 1, + m_Schema.m_cRecs[TBL_EventMap])); + + // If there is a handler and in "Full" mode, eliminate the intermediate tables. + if ((m_pHandler != NULL) && ((m_OptionValue.m_UpdateMode &MDUpdateMask) == MDUpdateFull)) + { + // If there is a handler, and not in E&C, save as fully compressed. + m_bSaveCompressed = true; + + // Temporary tables for new Fields, Methods, Params and FieldLayouts. + MetaData::TableRW newFields; + IfFailGo(newFields.InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_Field].m_cbRec, + m_Schema.m_cRecs[TBL_Field] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(newFields.Debug_SetTableInfo("TBL_Field", TBL_Field)); + + MetaData::TableRW newMethods; + IfFailGo(newMethods.InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_Method].m_cbRec, + m_Schema.m_cRecs[TBL_Method] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(newMethods.Debug_SetTableInfo("TBL_Method", TBL_Method)); + + MetaData::TableRW newParams; + IfFailGo(newParams.InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_Param].m_cbRec, + m_Schema.m_cRecs[TBL_Param] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(newParams.Debug_SetTableInfo("TBL_Param", TBL_Param)); + + MetaData::TableRW newEvents; + IfFailGo(newEvents.InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_Event].m_cbRec, + m_Schema.m_cRecs[TBL_Event] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(newEvents.Debug_SetTableInfo("TBL_Event", TBL_Event)); + + MetaData::TableRW newPropertys; + IfFailGo(newPropertys.InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_Property].m_cbRec, + m_Schema.m_cRecs[TBL_Property] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(newPropertys.Debug_SetTableInfo("TBL_Property", TBL_Property)); + + // If we have any indirect table for Field or Method and we are about to reorder these + // tables, the MemberDef hash table will be invalid after the token movement. So invalidate + // the hash. + if ((HasIndirectTable(TBL_Field) || HasIndirectTable(TBL_Method)) && (m_pMemberDefHash != NULL)) + { + delete m_pMemberDefHash; + m_pMemberDefHash = NULL; + } + + // Enumerate fields and copy. + if (HasIndirectTable(TBL_Field)) + { + for (ridPtr = 1; ridPtr <= m_Schema.m_cRecs[TBL_Field]; ++ridPtr) + { + BYTE * pOldPtr; + IfFailGo(m_Tables[TBL_FieldPtr].GetRecord(ridPtr, &pOldPtr)); + RID ridOld; + ridOld = GetCol(TBL_FieldPtr, FieldPtrRec::COL_Field, pOldPtr); + BYTE * pOld; + IfFailGo(m_Tables[TBL_Field].GetRecord(ridOld, &pOld)); + RID ridNew; + BYTE * pNew; + IfFailGo(newFields.AddRecord(&pNew, (UINT32 *)&ridNew)); + _ASSERTE(ridNew == ridPtr); + memcpy(pNew, pOld, m_TableDefs[TBL_Field].m_cbRec); + + // Let the caller know of the token change. + IfFailGo(MapToken(ridOld, ridNew, mdtFieldDef)); + } + } + + // Enumerate methods and copy. + if (HasIndirectTable(TBL_Method) || HasIndirectTable(TBL_Param)) + { + for (ridPtr = 1; ridPtr <= m_Schema.m_cRecs[TBL_Method]; ++ridPtr) + { + MethodRec * pOld; + RID ridOld; + BYTE * pNew = NULL; + if (HasIndirectTable(TBL_Method)) + { + BYTE * pOldPtr; + IfFailGo(m_Tables[TBL_MethodPtr].GetRecord(ridPtr, &pOldPtr)); + ridOld = GetCol(TBL_MethodPtr, MethodPtrRec::COL_Method, pOldPtr); + IfFailGo(GetMethodRecord(ridOld, &pOld)); + RID ridNew; + IfFailGo(newMethods.AddRecord(&pNew, (UINT32 *)&ridNew)); + _ASSERTE(ridNew == ridPtr); + memcpy(pNew, pOld, m_TableDefs[TBL_Method].m_cbRec); + + // Let the caller know of the token change. + IfFailGo(MapToken(ridOld, ridNew, mdtMethodDef)); + } + else + { + ridOld = ridPtr; + IfFailGo(GetMethodRecord(ridPtr, &pOld)); + } + + // Handle the params of the method. + if (HasIndirectTable(TBL_Method)) + { + IfFailGo(PutCol(TBL_Method, MethodRec::COL_ParamList, pNew, newParams.GetRecordCount() + 1)); + } + RID ixStart = getParamListOfMethod(pOld); + RID ixEnd; + IfFailGo(getEndParamListOfMethod(ridOld, &ixEnd)); + for (; ixStart<ixEnd; ++ixStart) + { + RID ridParam; + if (HasIndirectTable(TBL_Param)) + { + BYTE * pOldPtr; + IfFailGo(m_Tables[TBL_ParamPtr].GetRecord(ixStart, &pOldPtr)); + ridParam = GetCol(TBL_ParamPtr, ParamPtrRec::COL_Param, pOldPtr); + } + else + { + ridParam = ixStart; + } + BYTE * pOldRecord; + IfFailGo(m_Tables[TBL_Param].GetRecord(ridParam, &pOldRecord)); + RID ridNew; + BYTE * pNewRecord; + IfFailGo(newParams.AddRecord(&pNewRecord, (UINT32 *)&ridNew)); + memcpy(pNewRecord, pOldRecord, m_TableDefs[TBL_Param].m_cbRec); + + // Let the caller know of the token change. + IfFailGo(MapToken(ridParam, ridNew, mdtParamDef)); + } + } + } + + // Get rid of EventPtr and PropertyPtr table as well + // Enumerate fields and copy. + if (HasIndirectTable(TBL_Event)) + { + for (ridPtr = 1; ridPtr <= m_Schema.m_cRecs[TBL_Event]; ++ridPtr) + { + BYTE * pOldPtr; + IfFailGo(m_Tables[TBL_EventPtr].GetRecord(ridPtr, &pOldPtr)); + RID ridOld; + ridOld = GetCol(TBL_EventPtr, EventPtrRec::COL_Event, pOldPtr); + BYTE * pOld; + IfFailGo(m_Tables[TBL_Event].GetRecord(ridOld, &pOld)); + RID ridNew; + BYTE * pNew; + IfFailGo(newEvents.AddRecord(&pNew, (UINT32 *)&ridNew)); + _ASSERTE(ridNew == ridPtr); + memcpy(pNew, pOld, m_TableDefs[TBL_Event].m_cbRec); + + // Let the caller know of the token change. + IfFailGo(MapToken(ridOld, ridNew, mdtEvent)); + } + } + + if (HasIndirectTable(TBL_Property)) + { + for (ridPtr = 1; ridPtr <= m_Schema.m_cRecs[TBL_Property]; ++ridPtr) + { + BYTE * pOldPtr; + IfFailGo(m_Tables[TBL_PropertyPtr].GetRecord(ridPtr, &pOldPtr)); + RID ridOld; + ridOld = GetCol(TBL_PropertyPtr, PropertyPtrRec::COL_Property, pOldPtr); + BYTE * pOld; + IfFailGo(m_Tables[TBL_Property].GetRecord(ridOld, &pOld)); + RID ridNew; + BYTE * pNew; + IfFailGo(newPropertys.AddRecord(&pNew, (UINT32 *)&ridNew)); + _ASSERTE(ridNew == ridPtr); + memcpy(pNew, pOld, m_TableDefs[TBL_Property].m_cbRec); + + // Let the caller know of the token change. + IfFailGo(MapToken(ridOld, ridNew, mdtProperty)); + } + } + + + // Replace the old tables with the new, sorted ones. + if (HasIndirectTable(TBL_Field)) + { + m_Tables[TBL_Field].Delete(); + IfFailGo(m_Tables[TBL_Field].InitializeFromTable( + &newFields, + TRUE)); // fCopyData + } + if (HasIndirectTable(TBL_Method)) + { + m_Tables[TBL_Method].Delete(); + IfFailGo(m_Tables[TBL_Method].InitializeFromTable( + &newMethods, + TRUE)); // fCopyData + } + if (HasIndirectTable(TBL_Method) || HasIndirectTable(TBL_Param)) + { + m_Tables[TBL_Param].Delete(); + IfFailGo(m_Tables[TBL_Param].InitializeFromTable( + &newParams, + TRUE)); // fCopyData + } + if (HasIndirectTable(TBL_Property)) + { + m_Tables[TBL_Property].Delete(); + IfFailGo(m_Tables[TBL_Property].InitializeFromTable( + &newPropertys, + TRUE)); // fCopyData + } + if (HasIndirectTable(TBL_Event)) + { + m_Tables[TBL_Event].Delete(); + IfFailGo(m_Tables[TBL_Event].InitializeFromTable( + &newEvents, + TRUE)); // fCopyData + } + + // Empty the pointer tables table. + m_Schema.m_cRecs[TBL_FieldPtr] = 0; + m_Schema.m_cRecs[TBL_MethodPtr] = 0; + m_Schema.m_cRecs[TBL_ParamPtr] = 0; + m_Schema.m_cRecs[TBL_PropertyPtr] = 0; + m_Schema.m_cRecs[TBL_EventPtr] = 0; + + // invalidated the parent look up tables + if (m_pMethodMap) + { + delete m_pMethodMap; + m_pMethodMap = NULL; + } + if (m_pFieldMap) + { + delete m_pFieldMap; + m_pFieldMap = NULL; + } + if (m_pPropertyMap) + { + delete m_pPropertyMap; + m_pPropertyMap = NULL; + } + if (m_pEventMap) + { + delete m_pEventMap; + m_pEventMap = NULL; + } + if (m_pParamMap) + { + delete m_pParamMap; + m_pParamMap = NULL; + } + } + + // Do the ref to def fixup before fix up with token movement + IfFailGo(FixUpRefToDef()); + + //////////////////////////////////////////////////////////////////////////// + // + // We now need to do two kinds of fixups, and the two fixups interact with + // each other. + // 1) We need to sort several tables for binary searching. + // 2) We need to fixup any references to other tables, which may have + // changed due to ref-to-def, ptr-table elimination, or sorting. + // + + + // First do fixups. Some of these are then sorted based on fixed-up columns. + + IfFailGo(FixUpTable(TBL_MemberRef)); + IfFailGo(FixUpTable(TBL_MethodSemantics)); + IfFailGo(FixUpTable(TBL_Constant)); + IfFailGo(FixUpTable(TBL_FieldMarshal)); + IfFailGo(FixUpTable(TBL_MethodImpl)); + IfFailGo(FixUpTable(TBL_DeclSecurity)); + IfFailGo(FixUpTable(TBL_ImplMap)); + IfFailGo(FixUpTable(TBL_FieldRVA)); + IfFailGo(FixUpTable(TBL_FieldLayout)); + + if (SupportsGenerics()) + { + IfFailGo(FixUpTable(TBL_GenericParam)); + IfFailGo(FixUpTable(TBL_MethodSpec)); + } + + // Now sort any tables that are allowed to have custom attributes. + // This block for tables sorted in full mode only -- basically + // tables for which we hand out tokens. + if ((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateFull) + { + if (SupportsGenerics()) + { + // Sort the GenericParam table by the Owner. + // Don't disturb the sequence ordering within Owner + STABLESORTER_WITHREMAP(GenericParam, Owner); + IfFailGo(sortGenericParam.Sort()); + } + + // Sort the InterfaceImpl table by class. + STABLESORTER_WITHREMAP(InterfaceImpl, Class); + IfFailGo(sortInterfaceImpl.Sort()); + + // Sort the DeclSecurity table by parent. + SORTER_WITHREMAP(DeclSecurity, Parent); + IfFailGo(sortDeclSecurity.Sort()); + } + + // The GenericParamConstraint table is parented to the GenericParam table, + // so it needs fixup after sorting GenericParam table. + if (SupportsGenerics()) + { + IfFailGo(FixUpTable(TBL_GenericParamConstraint)); + + // After fixing up the GenericParamConstraint table, we can then + // sort it. + if ((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateFull) + { + // Sort the GenericParamConstraint table by the Owner. + // Don't disturb the sequence ordering within Owner + STABLESORTER_WITHREMAP(GenericParamConstraint, Owner); + IfFailGo(sortGenericParamConstraint.Sort()); + } + } + // Fixup the custom attribute table. After this, do not sort any table + // that is allowed to have a custom attribute. + IfFailGo(FixUpTable(TBL_CustomAttribute)); + + // Sort tables for binary searches. + if (((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateFull) || + ((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateIncremental)) + { + // Sort tables as required + //------------------------------------------------------------------------- + // Module order is preserved + // TypeRef order is preserved + // TypeDef order is preserved + // Field grouped and pointed to by TypeDef + // Method grouped and pointed to by TypeDef + // Param grouped and pointed to by Method + // InterfaceImpl sorted here + // MemberRef order is preserved + // Constant sorted here + // CustomAttribute sorted INCORRECTLY!! here + // FieldMarshal sorted here + // DeclSecurity sorted here + // ClassLayout created in order with TypeDefs + // FieldLayout grouped and pointed to by ClassLayouts + // StandaloneSig order is preserved + // TypeSpec order is preserved + // EventMap created in order at conversion (by Event Parent) + // Event sorted by Parent at conversion + // PropertyMap created in order at conversion (by Property Parent) + // Property sorted by Parent at conversion + // MethodSemantics sorted by Association at conversion. + // MethodImpl sorted here. + // Sort the constant table by parent. + // Sort the nested class table by NestedClass. + // Sort the generic par table by Owner + // MethodSpec order is preserved + + // Always sort Constant table + _ASSERTE(!CanHaveCustomAttribute(TBL_Constant)); + SORTER(Constant, Parent); + sortConstant.Sort(); + + // Always sort the FieldMarshal table by Parent. + _ASSERTE(!CanHaveCustomAttribute(TBL_FieldMarshal)); + SORTER(FieldMarshal, Parent); + sortFieldMarshal.Sort(); + + // Always sort the MethodSematics + _ASSERTE(!CanHaveCustomAttribute(TBL_MethodSemantics)); + SORTER(MethodSemantics, Association); + sortMethodSemantics.Sort(); + + // Always Sort the ClassLayoutTable by parent. + _ASSERTE(!CanHaveCustomAttribute(TBL_ClassLayout)); + SORTER(ClassLayout, Parent); + sortClassLayout.Sort(); + + // Always Sort the FieldLayoutTable by parent. + _ASSERTE(!CanHaveCustomAttribute(TBL_FieldLayout)); + SORTER(FieldLayout, Field); + sortFieldLayout.Sort(); + + // Always Sort the ImplMap table by the parent. + _ASSERTE(!CanHaveCustomAttribute(TBL_ImplMap)); + SORTER(ImplMap, MemberForwarded); + sortImplMap.Sort(); + + // Always Sort the FieldRVA table by the Field. + _ASSERTE(!CanHaveCustomAttribute(TBL_FieldRVA)); + SORTER(FieldRVA, Field); + sortFieldRVA.Sort(); + + // Always Sort the NestedClass table by the NestedClass. + _ASSERTE(!CanHaveCustomAttribute(TBL_NestedClass)); + SORTER(NestedClass, NestedClass); + sortNestedClass.Sort(); + + // Always Sort the MethodImpl table by the Class. + _ASSERTE(!CanHaveCustomAttribute(TBL_MethodImpl)); + SORTER(MethodImpl, Class); + sortMethodImpl.Sort(); + + // Some tokens are not moved in ENC mode; only "full" mode. + if ((m_OptionValue.m_UpdateMode & MDUpdateMask) == MDUpdateFull) + { + // Sort the CustomAttribute table by parent. + _ASSERTE(!CanHaveCustomAttribute(TBL_CustomAttribute)); + SORTER_WITHREMAP(CustomAttribute, Parent); + IfFailGo(sortCustomAttribute.Sort()); + } + + // Determine if the PropertyMap and EventMap are already sorted, and set the flag appropriately + SORTER(PropertyMap, Parent); + sortPropertyMap.CheckSortedWithNoDuplicates(); + + SORTER(EventMap, Parent); + sortEventMap.CheckSortedWithNoDuplicates(); + + //------------------------------------------------------------------------- + } // enclosing scope required for initialization ("goto" above skips initialization). + + m_bPreSaveDone = true; + + // send the Ref->Def optmization notification to host + if (m_pHandler != NULL) + { + TOKENMAP * ptkmap = GetMemberRefToMemberDefMap(); + PREFIX_ASSUME(ptkmap != NULL); // RegMeta always inits this. + MDTOKENMAP * ptkRemap = GetTokenMovementMap(); + int iCount = m_Schema.m_cRecs[TBL_MemberRef]; + mdToken tkTo; + mdToken tkDefTo; + int i; + MemberRefRec * pMemberRefRec; // A MemberRefRec. + const COR_SIGNATURE * pvSig; // Signature of the MemberRef. + ULONG cbSig; // Size of the signature blob. + + // loop through all LocalVar + for (i = 1; i <= iCount; i++) + { + tkTo = *(ptkmap->Get(i)); + if (RidFromToken(tkTo) != mdTokenNil) + { + // so far, the parent of memberref can be changed to only fielddef or methoddef + // or it will remain unchanged. + // + _ASSERTE((TypeFromToken(tkTo) == mdtFieldDef) || (TypeFromToken(tkTo) == mdtMethodDef)); + + IfFailGo(GetMemberRefRecord(i, &pMemberRefRec)); + IfFailGo(getSignatureOfMemberRef(pMemberRefRec, &pvSig, &cbSig)); + + // Don't turn mr's with vararg's into defs, because the variable portion + // of the call is kept in the mr signature. + if ((pvSig != NULL) && isCallConv(*pvSig, IMAGE_CEE_CS_CALLCONV_VARARG)) + continue; + + // ref is optimized to the def + + // now remap the def since def could be moved again. + tkDefTo = ptkRemap->SafeRemap(tkTo); + + // when Def token moves, it will not change type!! + _ASSERTE(TypeFromToken(tkTo) == TypeFromToken(tkDefTo)); + LOG((LOGMD, "MapToken (remap): from 0x%08x to 0x%08x\n", TokenFromRid(i, mdtMemberRef), tkDefTo)); + m_pHandler->Map(TokenFromRid(i, mdtMemberRef), tkDefTo); + } + } + } + + // Ok, we've applied all of the token remaps. Make sure we don't apply them again in the future + if (GetTokenMovementMap() != NULL) + IfFailGo(GetTokenMovementMap()->EmptyMap()); + +ErrExit: + + return hr; +} // CMiniMdRW::PreSaveFull + +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +//--------------------------------------------------------------------------------------- +// +// ENC-specific pre-safe work. +// +__checkReturn +HRESULT +CMiniMdRW::PreSaveEnc() +{ + HRESULT hr; + int iNew; // Insertion point for new tokens. + ULONG *pul; // Found token. + ULONG iRid; // RID from a token. + ULONG ixTbl; // Table from an ENC record. + ULONG cRecs; // Count of records in a table. + + IfFailGo(PreSaveFull()); + + // Turn off pre-save bit so that we can add ENC map records. + m_bPreSaveDone = false; + + if (m_Schema.m_cRecs[TBL_ENCLog]) + { // Keep track of ENC recs we've seen. + _ASSERTE(m_rENCRecs == 0); + m_rENCRecs = new (nothrow) ULONGARRAY[m_TblCount]; + IfNullGo(m_rENCRecs); + + // Create the temporary table. + MetaData::TableRW tempTable; + IfFailGo(tempTable.InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_ENCLog].m_cbRec, + m_Schema.m_cRecs[TBL_ENCLog] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(tempTable.Debug_SetTableInfo("TBL_ENCLog", TBL_ENCLog)); + + // For each row in the data. + RID rid; + ULONG iKept=0; + for (rid=1; rid<=m_Schema.m_cRecs[TBL_ENCLog]; ++rid) + { + ENCLogRec *pFrom; + IfFailGo(m_Tables[TBL_ENCLog].GetRecord(rid, reinterpret_cast<BYTE **>(&pFrom))); + + // Keep this record? + if (pFrom->GetFuncCode() == 0) + { // No func code. Skip if we've seen this token before. + + // What kind of record is this? + if (IsRecId(pFrom->GetToken())) + { // Non-token table + iRid = RidFromRecId(pFrom->GetToken()); + ixTbl = TblFromRecId(pFrom->GetToken()); + } + else + { // Token table. + iRid = RidFromToken(pFrom->GetToken()); + ixTbl = GetTableForToken(pFrom->GetToken()); + + } + + RIDBinarySearch searcher((UINT32 *)m_rENCRecs[ixTbl].Ptr(), m_rENCRecs[ixTbl].Count()); + pul = (ULONG *)(searcher.Find((UINT32 *)&iRid, &iNew)); + // If we found the token, don't keep the record. + if (pul != 0) + { + LOG((LOGMD, "PreSave ENCLog skipping duplicate token %d", pFrom->GetToken())); + continue; + } + // First time token was seen, so keep track of it. + IfNullGo(pul = m_rENCRecs[ixTbl].Insert(iNew)); + *pul = iRid; + } + + // Keeping the record, so allocate the new record to hold it. + ++iKept; + RID ridNew; + ENCLogRec *pTo; + IfFailGo(tempTable.AddRecord(reinterpret_cast<BYTE **>(&pTo), (UINT32 *)&ridNew)); + _ASSERTE(ridNew == iKept); + + // copy the data. + *pTo = *pFrom; + } + + // Keep the expanded table. + m_Tables[TBL_ENCLog].Delete(); + IfFailGo(m_Tables[TBL_ENCLog].InitializeFromTable( + &tempTable, + TRUE)); // fCopyData + INDEBUG_MD(m_Tables[TBL_ENCLog].Debug_SetTableInfo("TBL_ENCLog", TBL_ENCLog)); + m_Schema.m_cRecs[TBL_ENCLog] = iKept; + + // If saving only deltas, build the ENC Map table. + if (((m_OptionValue.m_UpdateMode & MDUpdateDelta)) == MDUpdateDelta) + { + cRecs = 0; + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + cRecs += m_rENCRecs[ixTbl].Count(); + } + m_Tables[TBL_ENCMap].Delete(); + + m_Schema.m_cRecs[TBL_ENCMap] = 0; + + IfFailGo(m_Tables[TBL_ENCMap].InitializeEmpty_WithRecordCount( + m_TableDefs[TBL_ENCMap].m_cbRec, + cRecs + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(m_Tables[TBL_ENCMap].Debug_SetTableInfo("TBL_ENCMap", TBL_ENCMap)); + cRecs = 0; + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + ENCMapRec *pNew; + ULONG nNew; + for (int i=0; i<m_rENCRecs[ixTbl].Count(); ++i) + { + IfFailGo(AddENCMapRecord(&pNew, &nNew)); // pre-allocated for all rows. + _ASSERTE(nNew == ++cRecs); + _ASSERTE(TblFromRecId(RecIdFromRid(m_rENCRecs[ixTbl][i], ixTbl)) < m_TblCount); + pNew->SetToken(RecIdFromRid(m_rENCRecs[ixTbl][i], ixTbl)); + } + } + } + } + + // Turn pre-save bit back on. + m_bPreSaveDone = true; + +ErrExit: + return hr; +} // CMiniMdRW::PreSaveEnc + +//***************************************************************************** +// Perform any appropriate pre-save optimization or reorganization. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PreSave( + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) +{ + HRESULT hr = S_OK; + +#ifdef _DEBUG + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_PreSaveBreak)) + { + _ASSERTE(!"CMiniMdRW::PreSave()"); + } +#endif //_DEBUG + + if (m_bPreSaveDone) + return hr; + +#ifdef FEATURE_PREJIT + // Reorganization should be done at ngen time only + if( reorderingOptions & ReArrangeStringPool ) + { + EX_TRY + { + OrganizeStringPool(pProfileData); + } + EX_CATCH + { + hr = GET_EXCEPTION()->GetHR(); + } + EX_END_CATCH(SwallowAllExceptions) + IfFailRet(hr); + } +#endif // FEATURE_PREJIT + + switch (m_OptionValue.m_UpdateMode & MDUpdateMask) + { + case MDUpdateFull: + case MDUpdateIncremental: + case MDUpdateExtension: + hr = PreSaveFull(); + break; + // PreSaveEnc removes duplicate entries in the ENCLog table, + // which we need to do regardless if we're saving a full MD + // or a minimal delta. + case MDUpdateDelta: + case MDUpdateENC: + hr = PreSaveEnc(); + break; + default: + _ASSERTE(!"Internal error -- unknown save mode"); + return E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::PreSave + +//***************************************************************************** +// Perform any necessary post-save cleanup. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PostSave() +{ + if (m_rENCRecs) + { + delete [] m_rENCRecs; + m_rENCRecs = 0; + } + + m_bPreSaveDone = false; + + return S_OK; +} // CMiniMdRW::PostSave + +//***************************************************************************** +// Save the tables to the stream. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SaveFullTablesToStream( + IStream *pIStream, + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) +{ + HRESULT hr; + CMiniTableDef sTempTable; // Definition for a temporary table. + CQuickArray<CMiniColDef> rTempCols; // Definition for a temp table's columns. + BYTE SchemaBuf[sizeof(CMiniMdSchema)]; //Buffer for compressed schema. + ULONG cbAlign; // Bytes needed for alignment. + UINT32 cbTable; // Bytes in a table. + UINT32 cbTotal; // Bytes written. + static const unsigned char zeros[8] = {0}; // For padding and alignment. + +#ifndef FEATURE_PREJIT + _ASSERTE(pProfileData == NULL); +#endif //!FEATURE_PREJIT + + // Write the header. + CMiniMdSchema Schema = m_Schema; + IfFailGo(m_StringHeap.GetAlignedSize(&cbTable)); + if (cbTable > USHRT_MAX) + { + Schema.m_heaps |= CMiniMdSchema::HEAP_STRING_4; + } + else + { + Schema.m_heaps &= ~CMiniMdSchema::HEAP_STRING_4; + } + + if (m_GuidHeap.GetSize() > USHRT_MAX) + { + Schema.m_heaps |= CMiniMdSchema::HEAP_GUID_4; + } + else + { + Schema.m_heaps &= ~CMiniMdSchema::HEAP_GUID_4; + } + + IfFailGo(m_BlobHeap.GetAlignedSize(&cbTable)); + if (cbTable > USHRT_MAX) + { + Schema.m_heaps |= CMiniMdSchema::HEAP_BLOB_4; + } + else + { + Schema.m_heaps &= ~CMiniMdSchema::HEAP_BLOB_4; + } + + cbTotal = 0; + if (pProfileData == NULL) + { + cbTotal = Schema.SaveTo(SchemaBuf); + IfFailGo(pIStream->Write(SchemaBuf, cbTotal, 0)); + if ( (cbAlign = Align4(cbTotal) - cbTotal) != 0) + IfFailGo(pIStream->Write(&hr, cbAlign, 0)); + cbTotal += cbAlign; + } + + ULONG headerOffset[TBL_COUNT]; + _ASSERTE(m_TblCount <= TBL_COUNT); + + ULONG ixTbl; + // For each table... + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + headerOffset[ixTbl] = ~0UL; + + ULONG itemCount = GetCountRecs(ixTbl); + if (itemCount) + { +#ifdef FEATURE_PREJIT + ULONG hotItemCount = 0; + + NewArrayHolder<mdToken> hotItemList = NULL; + NewArrayHolder<TokenIndexPair> indexMapping = NULL; + + // check if we were asked to generate the hot tables + if (pProfileData != NULL) + { + // obtain the number of tokens in this table whose metadata was touched + IfFailGo(GetHotMetadataTokensSearchAware(pProfileData, ixTbl, &hotItemCount, NULL, 0)); + + // assume ManifestResource table is touched completely if touched at all or any hot metadata at all so far + // this is because it's searched linearly, and IBC data misses an unsuccessful search + // after module load + if (ixTbl == TBL_ManifestResource && (hotItemCount > 0 || cbTotal != 0)) + hotItemCount = itemCount; + + // if the hot subset of the rows along with their side lookup tables will occupy more space + // than the full table, keep the full table to save both space and access time. + if (hotItemCount <= USHRT_MAX && itemCount <= USHRT_MAX && m_TableDefs[ixTbl].m_cbRec <= SHRT_MAX) + { + ULONG estimatedSizeUsingSubsetCopy = hotItemCount * (sizeof(WORD) + sizeof(BYTE) + m_TableDefs[ixTbl].m_cbRec); + ULONG estimatedSizeUsingFullCopy = itemCount * m_TableDefs[ixTbl].m_cbRec; + + if (estimatedSizeUsingSubsetCopy > estimatedSizeUsingFullCopy) + hotItemCount = itemCount; + } + + // first level table is array of WORD, so we can't handle more than 2**16 hot items + if (hotItemCount > USHRT_MAX) + hotItemCount = 0; + + // only generate additional table if any hot items at all + if (hotItemCount > 0) + { + if ( (cbAlign = Align4(cbTotal) - cbTotal) != 0) + IfFailGo(pIStream->Write(&hr, cbAlign, 0)); + cbTotal += cbAlign; + + headerOffset[ixTbl] = cbTotal; + + // write first part of header: hot item count + IfFailGo(pIStream->Write(&hotItemCount, sizeof(hotItemCount), 0)); + cbTotal += sizeof(hotItemCount); + + ULONG offset = 0; + if (hotItemCount < itemCount) + { + // obtain the tokens whose metadata was touched + hotItemList = new (nothrow) mdToken[hotItemCount]; + IfNullGo(hotItemList); + IfFailGo(GetHotMetadataTokensSearchAware(pProfileData, ixTbl, NULL, hotItemList, hotItemCount)); + + // construct an array of token-index pairs and save the original order of the tokens in pProfileData->GetHotTokens + // we want to write hot rows in this order to preserve the ordering optimizations done by IbcMerge + indexMapping = new (nothrow) TokenIndexPair[hotItemCount]; + IfNullGo(indexMapping); + + for (DWORD i = 0; i < hotItemCount; i++) + { + indexMapping[i].token = hotItemList[i]; + indexMapping[i].index = (WORD)i; + } + + // figure out how big the first level table should be + // and sort tokens accordingly + shiftCount = ShiftCount(itemCount, hotItemCount); + qsort(indexMapping, hotItemCount, sizeof(indexMapping[0]), TokenCmp); + + // each table has a header that consists of the hotItemCount, offsets to + // the first and second level tables, an offset to the actual data, and the + // shiftCount that determines the size of the first level table. + // see class HotTableHeader in metamodelro.h + + // we have already written the hotItemCount above. + + // so now write the offset of the first level table (just after the header) + offset = sizeof(hotItemCount) + 4*sizeof(offset) + sizeof(shiftCount); + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + + // figure out first level table size (1 extra entry at the end) + ULONG firstLevelCount = (1<<shiftCount)+1; + offset += firstLevelCount*sizeof(WORD); + + // write offset of second level table. + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + + // second level table has a byte-sized entry for each hot item + offset += hotItemCount*sizeof(BYTE); + + // write offset of index mapping table. + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + + // index mapping table has a word-sized entry for each hot item + offset += hotItemCount*sizeof(WORD); + + // actual data is just behind it, but 4-byte aligned + offset = Align4(offset); + + // write offset of actual hot metadata + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + + // write shiftCount + IfFailGo(pIStream->Write(&shiftCount, sizeof(shiftCount), 0)); + cbTotal += sizeof(shiftCount); + + // allocate tables + NewArrayHolder<WORD> firstLevelTable = new (nothrow) WORD[firstLevelCount]; + IfNullGo(firstLevelTable); + NewArrayHolder<BYTE> secondLevelTable = new (nothrow) BYTE[hotItemCount]; + IfNullGo(secondLevelTable); + NewArrayHolder<WORD> indexMappingTable = new (nothrow) WORD[hotItemCount]; + IfNullGo(indexMappingTable); + + // fill out the tables + ULONG nextFirstLevelIndex = 0; + for (DWORD i = 0; i < hotItemCount; i++) + { + // second level table contains the high order bits for each hot rid + secondLevelTable[i] = (BYTE)(RidFromToken(indexMapping[i].token) >> shiftCount); + + // the index into the first level table is the low order bits. + ULONG firstLevelIndex = indexMapping[i].token & ((1<<shiftCount)-1); + + // first level indicates where to start searching in the second level table + while (nextFirstLevelIndex <= firstLevelIndex) + firstLevelTable[nextFirstLevelIndex++] = (WORD)i; + + // index mapping table converts the index of this hot rid in the second level table + // to the index of the hot data in the cached rows + indexMappingTable[i] = indexMapping[i].index; + } + // fill remaining entries + while (nextFirstLevelIndex < firstLevelCount) + firstLevelTable[nextFirstLevelIndex++] = (WORD)hotItemCount; + + // write first level table + IfFailGo(pIStream->Write(firstLevelTable, sizeof(firstLevelTable[0])*firstLevelCount, 0)); + cbTotal += sizeof(firstLevelTable[0])*firstLevelCount; + + // write second level table + IfFailGo(pIStream->Write(secondLevelTable, sizeof(secondLevelTable[0])*hotItemCount, 0)); + cbTotal += sizeof(secondLevelTable[0])*hotItemCount; + + // write index mapping table + IfFailGo(pIStream->Write(indexMappingTable, sizeof(indexMappingTable[0])*hotItemCount, 0)); + cbTotal += sizeof(indexMappingTable[0])*hotItemCount; + + // NewArrayHolder for firstLevelTable and secondLevelTable going out of scope - no delete[] necessary + } + else + { + // in case the whole table is touched, omit the tables + // we still have a full header though with zero offsets for these tables. + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + + // offset for actual data points immediately after the header + offset += sizeof(hotItemCount) + 4*sizeof(offset) + sizeof(shiftCount); + offset = Align4(offset); + IfFailGo(pIStream->Write(&offset, sizeof(offset), 0)); + cbTotal += sizeof(offset); + shiftCount = 0; + + // write shift count + IfFailGo(pIStream->Write(&shiftCount, sizeof(shiftCount), 0)); + cbTotal += sizeof(shiftCount); + } + if ( (cbAlign = Align4(cbTotal) - cbTotal) != 0) + IfFailGo(pIStream->Write(&hr, cbAlign, 0)); + cbTotal += cbAlign; + _ASSERTE(cbTotal == headerOffset[ixTbl] + offset); + } + } +#endif //FEATURE_PREJIT + + // Compress the records by allocating a new, temporary, table and + // copying the rows from the one to the new. + + // If the table was grown, shrink it as much as possible. + if (m_eGrow == eg_grown) + { + + // Allocate a def for the temporary table. + sTempTable = m_TableDefs[ixTbl]; + IfFailGo(rTempCols.ReSizeNoThrow(sTempTable.m_cCols)); + sTempTable.m_pColDefs = rTempCols.Ptr(); + + // Initialize temp table col defs based on actual counts of data in the + // real tables. + IfFailGo(InitColsForTable(Schema, ixTbl, &sTempTable, 1, FALSE)); + + // Create the temporary table. + MetaData::TableRW tempTable; + IfFailGo(tempTable.InitializeEmpty_WithRecordCount( + sTempTable.m_cbRec, + m_Schema.m_cRecs[ixTbl] + COMMA_INDEBUG_MD(TRUE))); + INDEBUG_MD(tempTable.Debug_SetTableInfo(NULL, ixTbl)); + + // For each row in the data. + RID rid; + for (rid=1; rid<=m_Schema.m_cRecs[ixTbl]; ++rid) + { + RID ridNew; + BYTE *pRow; + IfFailGo(m_Tables[ixTbl].GetRecord(rid, &pRow)); + BYTE *pNew; + IfFailGo(tempTable.AddRecord(&pNew, (UINT32 *)&ridNew)); + _ASSERTE(rid == ridNew); + + // For each column. + for (ULONG ixCol=0; ixCol<sTempTable.m_cCols; ++ixCol) + { + // Copy the data to the temp table. + ULONG ulVal = GetCol(ixTbl, ixCol, pRow); + IfFailGo(PutCol(rTempCols[ixCol], pNew, ulVal)); + } + } // Persist the temp table to the stream. +#ifdef FEATURE_PREJIT + if (pProfileData != NULL) + { + // only write out the hot rows as indicated by profile data + for (DWORD i = 0; i < hotItemCount; i++) + { + BYTE *pRow; + IfFailGo(tempTable.GetRecord( + hotItemList != NULL ? RidFromToken(hotItemList[i]) : i + 1, + &pRow)); + IfFailGo(pIStream->Write(pRow, sTempTable.m_cbRec, 0)); + } + cbTable = sTempTable.m_cbRec*hotItemCount; + } + else +#endif //FEATURE_PREJIT + { + IfFailGo(tempTable.GetRecordsDataSize(&cbTable)); + _ASSERTE(cbTable == sTempTable.m_cbRec * GetCountRecs(ixTbl)); + IfFailGo(tempTable.SaveToStream( + pIStream)); + } + cbTotal += cbTable; + } + else + { // Didn't grow, so just persist directly to stream. +#ifdef FEATURE_PREJIT + if (pProfileData != NULL) + { + // only write out the hot rows as indicated by profile data + for (DWORD i = 0; i < hotItemCount; i++) + { + BYTE *pRow; + IfFailGo(m_Tables[ixTbl].GetRecord( + hotItemList != NULL ? RidFromToken(hotItemList[i]) : i + 1, + &pRow)); + IfFailGo(pIStream->Write(pRow, m_TableDefs[ixTbl].m_cbRec, 0)); + } + cbTable = m_TableDefs[ixTbl].m_cbRec*hotItemCount; + } + else +#endif //FEATURE_PREJIT + { + IfFailGo(m_Tables[ixTbl].GetRecordsDataSize(&cbTable)); + _ASSERTE(cbTable == m_TableDefs[ixTbl].m_cbRec * GetCountRecs(ixTbl)); + IfFailGo(m_Tables[ixTbl].SaveToStream( + pIStream)); + } + cbTotal += cbTable; + } + // NewArrayHolder hotItemList going out of scope - no delete [] necessary + } + } + + // Pad with at least 2 bytes and align on 4 bytes. + cbAlign = Align4(cbTotal) - cbTotal; + if (cbAlign < 2) + cbAlign += 4; + IfFailGo(pIStream->Write(zeros, cbAlign, 0)); + cbTotal += cbAlign; + _ASSERTE((m_cbSaveSize == 0) || (m_cbSaveSize == cbTotal) || (pProfileData != NULL)); + +#ifdef FEATURE_PREJIT + if (pProfileData != NULL) + { + // #WritingHotMetaData write hot table directory (HotTableDirectory in MetaModelRO.h) + + // first write magic + ULONG magic = 0x484f4e44; + IfFailGo(pIStream->Write(&magic, sizeof(magic), 0)); + + // compute offsets to table headers + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + if (headerOffset[ixTbl] != ~0u) + { + headerOffset[ixTbl] -= cbTotal; + } + else + { + headerOffset[ixTbl] = 0; + } + + // write the offsets to the table headers + IfFailGo(pIStream->Write(headerOffset, sizeof(headerOffset), 0)); + cbTotal += sizeof(magic) + sizeof(headerOffset); + + UINT32 cbPoolDirSize = 0; + UINT32 cbSavedHeapsSize = 0; + + IfFailGo(SaveHotPoolsToStream( + pIStream, + reorderingOptions, + pProfileData, + &cbPoolDirSize, + &cbSavedHeapsSize)); + + // write hot metadata (including pools) header + IfFailGo(StreamUtil::WriteToStream(pIStream, (DWORD)(cbSavedHeapsSize + cbPoolDirSize))); + IfFailGo(StreamUtil::WriteToStream(pIStream, (DWORD)cbPoolDirSize)); + } +#endif //FEATURE_PREJIT + +ErrExit: + return hr; +} // CMiniMdRW::SaveFullTablesToStream + +//***************************************************************************** +// Check to see if it is safe to reorder the string pool +// The existing implementation of metadata tables is such that string offsets in different tables +// may have different sizes. +// Since we are going to reorder the string pool, offsets of strings would change and that may +// cause overflows if tables have string offsets with different sizes +//***************************************************************************** +BOOL CMiniMdRW::IsSafeToReorderStringPool() +{ +#ifdef FEATURE_PREJIT + BYTE lastColumnSize=0; + ULONG ixTbl=0, ixCol=0; + for (ixTbl=0; ixTbl<m_TblCount; ixTbl++) + { + // for every column in this row + for (ixCol=0; ixCol<m_TableDefs[ixTbl].m_cCols; ixCol++) + { + // proceed only when the column type is iSTRING + if(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iSTRING) + { + if(lastColumnSize == 0) + { + lastColumnSize = m_TableDefs[ixTbl].m_pColDefs[ixCol].m_cbColumn; + } + else if(lastColumnSize != m_TableDefs[ixTbl].m_pColDefs[ixCol].m_cbColumn) + { + return FALSE; + } + } + } + } + return TRUE; +#else + return FALSE; +#endif // FEATURE_PREJIT +} // CMiniMdRW::IsSafeToReorderStringPool + +//***************************************************************************** +// Function to mark hot strings in the marks array based on the token information +// in profile data +//***************************************************************************** +VOID CMiniMdRW::MarkHotStrings(CorProfileData *pProfileData, BYTE * pMarks, ULONG poolSize) +{ +#ifdef FEATURE_PREJIT + if(pProfileData != NULL) + { + ULONG hotItemCount = pProfileData->GetHotTokens( TBL_COUNT + MDPoolStrings, 1 << ProfilingFlags_MetaData, 1 << ProfilingFlags_MetaData, NULL, 0 ); + if(hotItemCount > 0) + { + NewArrayHolder< ULONG > hotItemList = new ULONG[hotItemCount]; + + // get hot tokens + pProfileData->GetHotTokens( TBL_COUNT + MDPoolStrings, 1 << ProfilingFlags_MetaData, 1 << ProfilingFlags_MetaData, reinterpret_cast<mdToken *>(&hotItemList[0]), hotItemCount ); + + for ( ULONG i=0; i<hotItemCount; ++i ) + { + // convert tokens to rids + ULONG ulStringOffset = RidFromToken(hotItemList[i]); + + if (ulStringOffset >= poolSize) + ThrowHR(E_UNEXPECTED); + + pMarks[ulStringOffset] = ReorderData::ProfileData; + } + } + } +#endif // FEATURE_PREJIT +} // CMiniMdRW::MarkHotStrings + +//******************************************************************************* +// Function to mark hot strings referenced by hot tables based on token information in profile data +//******************************************************************************* +VOID CMiniMdRW::MarkStringsInHotTables(CorProfileData *pProfileData, BYTE * pMarks, ULONG poolSize) +{ +#ifdef FEATURE_PREJIT + ULONG ixTbl=0, ixCol=0; + ULONG hotItemCount=0; + RID hotRID=0; + BYTE *pHotRow=NULL; + + if(pProfileData != NULL) + { + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + NewArrayHolder<mdToken> hotItemList = NULL; + // obtain the number of tokens in this table whose metadata was touched + hotItemCount = pProfileData->GetHotTokens(ixTbl, 1<<ProfilingFlags_MetaData, 1<<ProfilingFlags_MetaData, NULL, 0); + + // obtain the tokens whose metadata was touched + if(hotItemCount > 0) + { + hotItemList = new mdToken[hotItemCount]; + pProfileData->GetHotTokens(ixTbl, 1<<ProfilingFlags_MetaData, 1<<ProfilingFlags_MetaData, hotItemList, hotItemCount); + } + + // for every column in this hot row + for (ixCol=0; ixCol<m_TableDefs[ixTbl].m_cCols; ++ixCol) + { + // add the string to the string pool only if it hasn't been added yet + if(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iSTRING) + { + // for every hot token in the list + for(ULONG item=0; item<hotItemCount; item++) + { + // get the rid from the token + hotRID = RidFromToken(hotItemList[item]); + IfFailThrow(m_Tables[ixTbl].GetRecord(hotRID, &pHotRow)); + _ASSERTE(pHotRow != NULL); + + // get column for string; this will get me the current string offset + ULONG ulStringOffset = GetCol(ixTbl, ixCol, pHotRow); + + if (ulStringOffset >= poolSize) + ThrowHR(E_UNEXPECTED); + + pMarks[ulStringOffset] = ReorderData::ProfileData; + } + } + } + } + } +#endif // FEATURE_PREJIT +} // CMiniMdRW::MarkStringsInHotTables + +//***************************************************************************** +// Function to mark strings referenced by the different metadata tables +//***************************************************************************** +VOID CMiniMdRW::MarkStringsInTables(BYTE * pMarks, ULONG poolSize) +{ +#ifdef FEATURE_PREJIT + for (ULONG ixTbl=0; ixTbl<m_TblCount; ixTbl++) + { + // for every row in the table + for (RID ridOld=1; ridOld<=m_Schema.m_cRecs[ixTbl]; ridOld++) + { + // lets assume we do not have any references to the stringpool + BOOL fHasStringData = FALSE; + + // for every column in this row + for (ULONG ixCol=0; ixCol<m_TableDefs[ixTbl].m_cCols; ixCol++) + { + // proceed only when the column type is iSTRING + if(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iSTRING) + { + fHasStringData = TRUE; + // get the current record + BYTE *pOldRow; + IfFailThrow(m_Tables[ixTbl].GetRecord(ridOld, &pOldRow)); + + // get column for string; this will get me the current string offset + ULONG ulStringOffset = GetCol(ixTbl, ixCol, pOldRow); + + // ignore empty strings, they are not moving anywhere + if(ulStringOffset == 0) + continue; + + if (ulStringOffset >= poolSize) + ThrowHR(E_UNEXPECTED); + + BYTE ulBucketType=0; + + switch(ixTbl) + { + case TBL_Method: + ulBucketType = IsMdPublic(GetCol(TBL_Method, MethodRec::COL_Flags, pOldRow)) + ? ReorderData::PublicData + : ReorderData::NonPublicData; + break; + case TBL_Field: + ulBucketType = IsFdPublic(GetCol(TBL_Field, FieldRec::COL_Flags, pOldRow)) + ? ReorderData::PublicData + : ReorderData::NonPublicData; + break; + case TBL_TypeDef: + ulBucketType = IsTdPublic(GetCol(TBL_TypeDef, TypeDefRec::COL_Flags, pOldRow)) + ? ReorderData::PublicData + : ReorderData::NonPublicData; + break; + case TBL_ManifestResource: + ulBucketType = IsMrPublic(GetCol(TBL_ManifestResource, ManifestResourceRec::COL_Flags, pOldRow)) + ? ReorderData::PublicData + : ReorderData::NonPublicData; + break; + default: + ulBucketType = ReorderData::OtherData; + break; + } + + if (pMarks[ulStringOffset] == ReorderData::Undefined || pMarks[ulStringOffset] > ulBucketType) + pMarks[ulStringOffset] = ulBucketType; + } + } + if (!fHasStringData) + break; + } + } +#endif // FEATURE_PREJIT +} // CMiniMdRW::MarkStringsInTables + +// -------------------------------------------------------------------------------------- +// +// Function to mark duplicate strings in the mark array. This step is basically to take care of +// strings that have the same tail. +// Throws on error. +// +VOID CMiniMdRW::MarkDuplicateStrings(BYTE * pMarks, ULONG poolSize) +{ +#ifdef FEATURE_PREJIT + ULONG offset=1; + while (offset<poolSize) + { + if (pMarks[offset] == ReorderData::Undefined) + { + offset++; + continue; + } + + LPCSTR pszString; + IfFailThrow(m_StringHeap.GetString(offset, &pszString)); + + ULONG start = offset; + ULONG end = offset + (ULONG)strlen(pszString); + + BYTE tag = pMarks[offset]; + offset++; + + while (offset <= end) + { + if (pMarks[offset] != ReorderData::Undefined) + { + tag = min(pMarks[offset], tag); + pMarks[offset] = ReorderData::Duplicate; + } + offset++; + } + pMarks[start] = tag; + } +#endif // FEATURE_PREJIT +} // CMiniMdRW::MarkDuplicateStrings + +//***************************************************************************** +// Function to update the tables with the modified string offsets +//***************************************************************************** +VOID CMiniMdRW::FixStringsInTables() +{ +#if defined(FEATURE_PREJIT) && !defined(DACCESS_COMPILE) + for (ULONG ixTbl=0; ixTbl<m_TblCount; ixTbl++) + { + // for every row in the table + for (RID ridOld=1; ridOld<=m_Schema.m_cRecs[ixTbl]; ridOld++) + { + // lets assume we do not have any references to the stringpool + BOOL fHasStringData = FALSE; + + // for every column in this row + for (ULONG ixCol=0; ixCol<m_TableDefs[ixTbl].m_cCols; ixCol++) + { + // proceed only when the column type is iSTRING + if(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iSTRING) + { + fHasStringData = TRUE; + // get the current record + BYTE *pOldRow; + IfFailThrow(m_Tables[ixTbl].GetRecord(ridOld, &pOldRow)); + _ASSERTE(pOldRow != NULL); + + // get column for string; this will get me the current string offset + UINT32 nOldStringOffset = GetCol(ixTbl, ixCol, pOldRow); + + // ignore empty strings, they are not moving anywhere + if (nOldStringOffset == 0) + continue; + + UINT32 nNewStringOffset; + if (!m_StringPoolOffsetHash.Lookup(nOldStringOffset, &nNewStringOffset)) + ThrowHR(E_UNEXPECTED); + + IfFailThrow(PutCol(ixTbl, ixCol, pOldRow, nNewStringOffset)); + } + } + if (!fHasStringData) + break; + } + } +#endif // FEATURE_PREJIT +} // CMiniMdRW::FixStringsInTables + +// -------------------------------------------------------------------------------------- +// +// Function to fill the given string pool with strings from the existing string pool using the mark array. +// Throws on error. +// +VOID +CMiniMdRW::CreateReorderedStringPool( + MetaData::StringHeapRW *pStringHeap, + BYTE *pMarks, + ULONG cbHeapSize, + CorProfileData *pProfileData) +{ +#if defined(FEATURE_PREJIT) && !defined(DACCESS_COMPILE) + ULONG lastOldOffset = 0; + ULONG lastNewOffset = 0; + + // special handling of profile data so as to maintain the same order + // as the hot tokens in the CorProfileData object + if (pProfileData != NULL) + { + ULONG hotItems = pProfileData->GetHotTokens( + TBL_COUNT + MDPoolStrings, + 1 << ProfilingFlags_MetaData, + 1 << ProfilingFlags_MetaData, + NULL, + 0); + if ( hotItems ) + { + NewArrayHolder< ULONG > hotItemArr = new ULONG[ hotItems ]; + pProfileData->GetHotTokens( + TBL_COUNT + MDPoolStrings, + 1 << ProfilingFlags_MetaData, + 1 << ProfilingFlags_MetaData, + reinterpret_cast<mdToken *>(&hotItemArr[0]), + hotItems); + + // convert tokens to rids + for ( ULONG i = 0; i < hotItems ; ++i ) + { + UINT32 newOffset=0, start=0, end=0; + hotItemArr[i] = RidFromToken(hotItemArr[i]); + + for (UINT32 offset = hotItemArr[i]; offset >= 1; offset--) + { + if(pMarks[offset] == ReorderData::ProfileData) + { + LPCSTR szString; + IfFailThrow(m_StringHeap.GetString(offset, &szString)); + IfFailThrow(pStringHeap->AddString(szString, &newOffset)); + start = offset; + end = start + (UINT32)strlen(szString); + break; + } + } + + for (UINT32 offset = start; offset <end; offset++) + { + if(pMarks[offset] == ReorderData::ProfileData || pMarks[offset] == ReorderData::Duplicate) + { + m_StringPoolOffsetHash.Add(offset, newOffset); + } + newOffset++; + } + } + } + } + + for (BYTE priority = ReorderData::ProfileData; priority <= ReorderData::NonPublicData; priority++) + { + for (UINT32 offset = 1; offset < cbHeapSize; offset++) + { + // Since MinReorderBucketType is 0 and MaxReorderBucketType is 255, checking an unsigned BYTE against that gives a "comparison + // is always true" warning. Logically, the assert is: + // _ASSERTE(pMarks[offset] >= ReorderData::MinReorderBucketType && pMarks[offset] <= ReorderData::MaxReorderBucketType); + _ASSERTE(0 == ReorderData::MinReorderBucketType); + _ASSERTE(255 == ReorderData::MaxReorderBucketType); + _ASSERTE(sizeof(pMarks[0]) == 1); + + if (pMarks[offset] == priority) + { + UINT32 newOffset; + + if(!m_StringPoolOffsetHash.Lookup(offset, &newOffset)) + { + LPCSTR szString; + IfFailThrow(m_StringHeap.GetString(offset, &szString)); + IfFailThrow(pStringHeap->AddString(szString, &newOffset)); + m_StringPoolOffsetHash.Add(offset, newOffset); + + lastOldOffset = offset; + lastNewOffset = newOffset; + } + } + else + if (pMarks[offset] == ReorderData::Duplicate) + { + UINT32 newOffset; + if (lastNewOffset != 0 && !m_StringPoolOffsetHash.Lookup(offset, &newOffset)) + m_StringPoolOffsetHash.Add(offset, lastNewOffset + (offset - lastOldOffset)); + } + else + if (pMarks[offset] != ReorderData::Undefined) + { + lastNewOffset = 0; + } + } + } +#endif // FEATURE_PREJIT +} // CMiniMdRW::CreateReorderedStringPool + +// -------------------------------------------------------------------------------------- +// +// Function to reorganize the string pool based on IBC profile data (if available) and static analysis +// Throws on error. +// +VOID CMiniMdRW::OrganizeStringPool(CorProfileData *pProfileData) +{ +#if defined(FEATURE_PREJIT) && !defined(DACCESS_COMPILE) + if(!IsSafeToReorderStringPool()) + { + return; + } + + UINT32 cbStringHeapSize = m_StringHeap.GetUnalignedSize(); + + NewArrayHolder<BYTE> stringMarks = new BYTE[cbStringHeapSize]; + ZeroMemory(stringMarks, cbStringHeapSize); + + // Each string will be assigned a value based on its hotness in the Mark*() functions + // This list will be later traversed to place the strings in the right order in the string pool and also + // to update the references in the metadata tables + + // Mark all hot strings + MarkHotStrings(pProfileData, stringMarks, cbStringHeapSize); + + // Mark all strings in hot rows + MarkStringsInHotTables(pProfileData, stringMarks, cbStringHeapSize); + + // Mark all remaining strings + MarkStringsInTables(stringMarks, cbStringHeapSize); + + // Mark duplicates for interned strings + MarkDuplicateStrings(stringMarks, cbStringHeapSize); + + // Initalize the temporary string heap + MetaData::StringHeapRW tempStringHeap; + + IfFailThrow(tempStringHeap.InitializeEmpty( + cbStringHeapSize + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + + // We will use this hash for fixing the string references in the profile data + m_StringPoolOffsetHash.Reallocate(cbStringHeapSize); + + // Create the temporary string pool using the mark arrays + CreateReorderedStringPool(&tempStringHeap, stringMarks, cbStringHeapSize, pProfileData); + + // Update the tables with string offsets into the temporary string pool + FixStringsInTables(); + + // Replace the existing string pool with the modified version + m_StringHeap.Delete(); + IfFailThrow(m_StringHeap.InitializeFromStringHeap( + &tempStringHeap, + TRUE)); // fCopyData +#endif // FEATURE_PREJIT +} // CMiniMdRW::OrganizeStringPool + +#ifdef FEATURE_PREJIT + +// write hot data of the pools +// +__checkReturn +HRESULT +CMiniMdRW::SaveHotPoolsToStream( + IStream *pStream, + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData, + UINT32 *pnPoolDirSize, + UINT32 *pnHeapsSavedSize) +{ +// @todo: Triton workaround: FEATURE_METADATA_STANDALNE_WINRT_RO is supposed to disable FEATURE_PREJIT - remove this #if once we figure out how to get that working in the CoreClr build. +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + _ASSERTE(!"SaveHotPoolsToStream: This method not supported in RoMetadata.dll"); + return E_NOTIMPL; +#else // FEATURE_METADATA_STANDALONE_WINRT_RO + HRESULT hr = S_OK; + UINT32 rgHeapSavedSize[MDPoolCount] = { 0, 0, 0, 0 }; + + // save pools in the order they are described in MDPools enum + // + // we skip the hot string pool when we reorganize the string pool + if (!(reorderingOptions & ReArrangeStringPool)) + { + MetaData::HotHeapWriter stringHotHeapWriter(&m_StringHeap); + IfFailRet(SaveHotPoolToStream( + pStream, + pProfileData, + &stringHotHeapWriter, + &rgHeapSavedSize[MDPoolStrings])); + } + + // Save guid heap hot data + MetaData::HotHeapWriter guidsHotHeapWriter(&m_GuidHeap); + IfFailRet(SaveHotPoolToStream( + pStream, + pProfileData, + &guidsHotHeapWriter, + &rgHeapSavedSize[MDPoolGuids])); + + // Save blob heap hot data + MetaData::HotHeapWriter blobsHotHeapWriter( + &m_BlobHeap, + FALSE); // fUserStringHeap + IfFailRet(SaveHotPoolToStream( + pStream, + pProfileData, + &blobsHotHeapWriter, + &rgHeapSavedSize[MDPoolBlobs])); + + // Save user string heap hot data + MetaData::HotHeapWriter userStringsHotHeapWriter( + &m_UserStringHeap, + TRUE); // fUserStringHeap + IfFailRet(SaveHotPoolToStream( + pStream, + pProfileData, + &userStringsHotHeapWriter, + &rgHeapSavedSize[MDPoolUSBlobs])); + + // fix pool offsets, they need to point to the header of each saved pool + UINT32 nHeapEndOffset = 0; + for (int i = MDPoolCount; i-- > 0; ) + { + if (rgHeapSavedSize[i] != 0) + { + UINT32 nHeapSavedSize = rgHeapSavedSize[i]; + // Change size of the heap to the (negative) offset of its header + rgHeapSavedSize[i] = sizeof(struct MetaData::HotHeapHeader) + nHeapEndOffset; + nHeapEndOffset += nHeapSavedSize; + } + } + // Store size of all heaps + *pnHeapsSavedSize = nHeapEndOffset; + + // save hot pool dirs + *pnPoolDirSize = 0; + for (int i = 0; i < MDPoolCount; i++) + { + if (rgHeapSavedSize[i] != 0) + { + IfFailRet(StreamUtil::WriteToStream(pStream, i, pnPoolDirSize)); + IfFailRet(StreamUtil::WriteToStream(pStream, (ULONG)rgHeapSavedSize[i], pnPoolDirSize)); + } + } + + return S_OK; +#endif //FEATURE_METADATA_STANDALONE_WINRT_RO +} // CMiniMdRW::SaveHotPoolsToStream + +// write hot data of specific blob +// +__checkReturn +HRESULT +CMiniMdRW::SaveHotPoolToStream( + IStream *pStream, + CorProfileData *pProfileData, + MetaData::HotHeapWriter *pHotHeapWriter, + UINT32 *pnSavedSize) +{ +// @todo: Triton workaround: FEATURE_METADATA_STANDALNE_WINRT_RO is supposed to disable FEATURE_PREJIT - remove this #if once we figure out how to get that working in the CoreClr build. +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + _ASSERTE(!"SaveHotPoolToStream: This method not supported in RoMetadata.dll"); + return E_NOTIMPL; +#else //FEATURE_METADATA_STANDALONE_WINRT_RO + + _ASSERTE(pProfileData != NULL); + + HRESULT hr = S_OK; + // #CallToGetHotTokens + // see code:CMiniMdRW.SaveFullTablesToStream#WritingHotMetaData for the main caller of this. + if (pProfileData->GetHotTokens( + pHotHeapWriter->GetTableIndex(), + 1 << ProfilingFlags_MetaData, + 1 << ProfilingFlags_MetaData, + NULL, + 0) != 0) + { + IfFailRet(pHotHeapWriter->SaveToStream( + pStream, + pProfileData, + pnSavedSize)); + } + else + { + *pnSavedSize = 0; + } + + return S_OK; +#endif // FEATURE_METADATA_STANDALONE_WINRT_RO +} // CMiniMdRW::SaveHotPoolToStream + +#endif //FEATURE_PREJIT + +//***************************************************************************** +// Save the tables to the stream. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SaveENCTablesToStream( + IStream *pIStream) +{ + HRESULT hr; + BYTE SchemaBuf[sizeof(CMiniMdSchema)]; //Buffer for compressed schema. + ULONG cbAlign; // Bytes needed for alignment. + ULONG cbTable; // Bytes in a table. + ULONG cbTotal; // Bytes written. + ULONG ixTbl; // Table counter. + static const unsigned char zeros[8] = {0}; // For padding and alignment. + + // Make sure the minimal delta has a fully expanded table + IfFailRet(ExpandTables()); + + // Write the header. + CMiniMdSchema Schema = m_Schema; + Schema.m_heaps |= CMiniMdSchema::DELTA_ONLY; + + if (m_rENCRecs != NULL) + { + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + Schema.m_cRecs[ixTbl] = m_rENCRecs[ixTbl].Count(); + } + else + { + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + Schema.m_cRecs[ixTbl] = 0; + } + + Schema.m_cRecs[TBL_Module] = m_Schema.m_cRecs[TBL_Module]; + Schema.m_cRecs[TBL_ENCLog] = m_Schema.m_cRecs[TBL_ENCLog]; + Schema.m_cRecs[TBL_ENCMap] = m_Schema.m_cRecs[TBL_ENCMap]; + + cbTotal = Schema.SaveTo(SchemaBuf); + IfFailGo(pIStream->Write(SchemaBuf, cbTotal, 0)); + if ( (cbAlign = Align4(cbTotal) - cbTotal) != 0) + IfFailGo(pIStream->Write(&hr, cbAlign, 0)); + cbTotal += cbAlign; + + // For each table... + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + if (ixTbl == TBL_ENCLog || ixTbl == TBL_ENCMap || ixTbl == TBL_Module) + { + if (m_Schema.m_cRecs[ixTbl] == 0) + continue; // pretty strange if ENC has no enc data. + // Persist the ENC table. + IfFailGo(m_Tables[ixTbl].GetRecordsDataSize((UINT32 *)&cbTable)); + _ASSERTE(cbTable == m_TableDefs[ixTbl].m_cbRec * m_Schema.m_cRecs[ixTbl]); + cbTotal += cbTable; + IfFailGo(m_Tables[ixTbl].SaveToStream( + pIStream)); + } + else + if (Schema.m_cRecs[ixTbl]) + { + // Copy just the delta records. + + // Create the temporary table. + MetaData::TableRW tempTable; + IfFailGo(tempTable.InitializeEmpty_WithRecordCount( + m_TableDefs[ixTbl].m_cbRec, + Schema.m_cRecs[ixTbl] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + INDEBUG_MD(tempTable.Debug_SetTableInfo(NULL, ixTbl)); + + // For each row in the data. + RID rid; + for (ULONG iDelta=0; iDelta<Schema.m_cRecs[ixTbl]; ++iDelta) + { + RID ridNew; + rid = m_rENCRecs[ixTbl][iDelta]; + BYTE *pRow; + IfFailGo(m_Tables[ixTbl].GetRecord(rid, &pRow)); + BYTE *pNew; + IfFailGo(tempTable.AddRecord(&pNew, (UINT32 *)&ridNew)); + _ASSERTE(iDelta+1 == ridNew); + + memcpy(pNew, pRow, m_TableDefs[ixTbl].m_cbRec); + } + // Persist the temp table to the stream. + IfFailGo(tempTable.GetRecordsDataSize((UINT32 *)&cbTable)); + _ASSERTE(cbTable == m_TableDefs[ixTbl].m_cbRec * Schema.m_cRecs[ixTbl]); + cbTotal += cbTable; + IfFailGo(tempTable.SaveToStream( + pIStream)); + } + } + + // Pad with at least 2 bytes and align on 4 bytes. + cbAlign = Align4(cbTotal) - cbTotal; + if (cbAlign < 2) + cbAlign += 4; + IfFailGo(pIStream->Write(zeros, cbAlign, 0)); + cbTotal += cbAlign; + _ASSERTE(m_cbSaveSize == 0 || m_cbSaveSize == cbTotal); + +ErrExit: + return hr; +} // CMiniMdRW::SaveENCTablesToStream + +//***************************************************************************** +// Save the tables to the stream. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SaveTablesToStream( + IStream *pIStream, // The stream. + MetaDataReorderingOptions reorderingOptions, + CorProfileData *pProfileData) +{ + HRESULT hr; + + // Prepare the data for save. + IfFailGo(PreSave()); + + switch (m_OptionValue.m_UpdateMode & MDUpdateMask) + { + case MDUpdateFull: + case MDUpdateIncremental: + case MDUpdateExtension: + case MDUpdateENC: + hr = SaveFullTablesToStream(pIStream, reorderingOptions, pProfileData); + break; + case MDUpdateDelta: + hr = SaveENCTablesToStream(pIStream); + break; + default: + _ASSERTE(!"Internal error -- unknown save mode"); + return E_INVALIDARG; + } + +ErrExit: + return hr; +} // CMiniMdRW::SaveTablesToStream + +//***************************************************************************** +// Save a full pool to the stream. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SaveFullPoolToStream( + int iPool, // The pool. + IStream *pStream) // The stream. +{ + HRESULT hr; + + switch (iPool) + { + case MDPoolStrings: + hr = m_StringHeap.SaveToStream_Aligned( + 0, // Start offset of the data to be stored + pStream); + break; + case MDPoolGuids: + hr = m_GuidHeap.SaveToStream( + pStream); + break; + case MDPoolBlobs: + hr = m_BlobHeap.SaveToStream_Aligned( + 0, // Start offset of the data to be stored + pStream); + break; + case MDPoolUSBlobs: + hr = m_UserStringHeap.SaveToStream_Aligned( + 0, // Start offset of the data to be stored + pStream); + break; + default: + hr = E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::SaveFullPoolToStream + +//***************************************************************************** +// Save a ENC pool to the stream. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SaveENCPoolToStream( + int iPool, // The pool. + IStream *pIStream) // The stream. +{ + HRESULT hr; + + switch (iPool) + { + case MDPoolStrings: + { + UINT32 nEnCDeltaStartOffset = m_StringHeap.GetEnCSessionStartHeapSize(); + hr = m_StringHeap.SaveToStream_Aligned( + nEnCDeltaStartOffset, // Start offset of the data to be stored + pIStream); + break; + } + case MDPoolGuids: + { + // Save full Guid heap (we never save EnC delta) + hr = m_GuidHeap.SaveToStream( + pIStream); + break; + } + case MDPoolBlobs: + { + UINT32 nEnCDeltaStartOffset = m_BlobHeap.GetEnCSessionStartHeapSize(); + hr = m_BlobHeap.SaveToStream_Aligned( + nEnCDeltaStartOffset, // Start offset of the data to be stored + pIStream); + break; + } + case MDPoolUSBlobs: + { + UINT32 nEnCDeltaStartOffset = m_UserStringHeap.GetEnCSessionStartHeapSize(); + hr = m_UserStringHeap.SaveToStream_Aligned( + nEnCDeltaStartOffset, // Start offset of the data to be stored + pIStream); + break; + } + default: + hr = E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::SaveENCPoolToStream + +//***************************************************************************** +// Save a pool to the stream. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SavePoolToStream( + int iPool, // The pool. + IStream *pIStream) // The stream. +{ + HRESULT hr; + switch (m_OptionValue.m_UpdateMode & MDUpdateMask) + { + case MDUpdateFull: + case MDUpdateIncremental: + case MDUpdateExtension: + case MDUpdateENC: + hr = SaveFullPoolToStream(iPool, pIStream); + break; + case MDUpdateDelta: + hr = SaveENCPoolToStream(iPool, pIStream); + break; + default: + _ASSERTE(!"Internal error -- unknown save mode"); + return E_INVALIDARG; + } + + return hr; +} // CMiniMdRW::SavePoolToStream + +//***************************************************************************** +// Expand a table from the initial (hopeful) 2-byte column sizes to the large +// (but always adequate) 4-byte column sizes. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ExpandTables() +{ + HRESULT hr = S_OK; + CMiniMdSchema Schema; // Temp schema by which to build tables. + ULONG ixTbl; // Table counter. + + // Allow function to be called many times. + if (m_eGrow == eg_grown) + return (S_OK); + + // OutputDebugStringA("Growing tables to large size.\n"); + + // Make pool indices the large size. + Schema.m_heaps = 0; + Schema.m_heaps |= CMiniMdSchema::HEAP_STRING_4; + Schema.m_heaps |= CMiniMdSchema::HEAP_GUID_4; + Schema.m_heaps |= CMiniMdSchema::HEAP_BLOB_4; + + // Make Row counts the large size. + memset(Schema.m_cRecs, 0, sizeof(Schema.m_cRecs)); + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + Schema.m_cRecs[ixTbl] = USHRT_MAX+1; + + // Compute how many bits required to hold a rid. + Schema.m_rid = 16; + + for (ixTbl=0; ixTbl<m_TblCount; ++ixTbl) + { + IfFailGo(ExpandTableColumns(Schema, ixTbl)); + } + + // Things are bigger now. + m_Schema.m_rid = 16; + m_Schema.m_heaps |= CMiniMdSchema::HEAP_STRING_4; + m_Schema.m_heaps |= CMiniMdSchema::HEAP_GUID_4; + m_Schema.m_heaps |= CMiniMdSchema::HEAP_BLOB_4; + m_iStringsMask = 0xffffffff; + m_iGuidsMask = 0xffffffff; + m_iBlobsMask = 0xffffffff; + + // Remember that we've grown. + m_eGrow = eg_grown; + m_maxRid = m_maxIx = ULONG_MAX; + +ErrExit: + return hr; +} // CMiniMdRW::ExpandTables + + +__checkReturn +HRESULT +CMiniMdRW::InitWithLargeTables() +{ + CMiniMdSchema Schema; // Temp schema by which to build tables. + HRESULT hr = S_OK; + + // Make pool indices the large size. + Schema.m_heaps = 0; + Schema.m_heaps |= CMiniMdSchema::HEAP_STRING_4; + Schema.m_heaps |= CMiniMdSchema::HEAP_GUID_4; + Schema.m_heaps |= CMiniMdSchema::HEAP_BLOB_4; + + // Make Row counts the large size. + memset(Schema.m_cRecs, 0, sizeof(Schema.m_cRecs)); + for (int ixTbl=0; ixTbl<(int)m_TblCount; ++ixTbl) + Schema.m_cRecs[ixTbl] = USHRT_MAX+1; + + // Compute how many bits required to hold a rid. + Schema.m_rid = 16; + + // For each table... + for (int ixTbl=0; ixTbl<(int)m_TblCount; ++ixTbl) + { + IfFailRet(InitColsForTable(Schema, ixTbl, &m_TableDefs[ixTbl], 0, TRUE)); + } + + + // Things are bigger now. + m_Schema.m_rid = 16; + m_Schema.m_heaps |= CMiniMdSchema::HEAP_STRING_4; + m_Schema.m_heaps |= CMiniMdSchema::HEAP_GUID_4; + m_Schema.m_heaps |= CMiniMdSchema::HEAP_BLOB_4; + m_iStringsMask = 0xffffffff; + m_iGuidsMask = 0xffffffff; + + return hr; +}// CMiniMdRW::InitWithLargeTables + +//***************************************************************************** +// Expand the sizes of a tables columns according to a new schema. When this +// happens, all RID and Pool index columns expand from 2 to 4 bytes. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ExpandTableColumns( + CMiniMdSchema &Schema, + ULONG ixTbl) +{ + HRESULT hr; + CMiniTableDef sTempTable; // Definition for a temporary table. + CQuickBytes qbTempCols; + ULONG ixCol; // Column counter. + ULONG cbFixed; // Count of bytes that don't move. + CMiniColDef *pFromCols; // Definitions of "from" columns. + CMiniColDef *pToCols; // Definitions of "To" columns. + ULONG cMoveCols; // Count of columns to move. + ULONG cFixedCols; // Count of columns to move. + + // Allocate a def for the temporary table. + sTempTable = m_TableDefs[ixTbl]; + IfFailGo(qbTempCols.ReSizeNoThrow(sTempTable.m_cCols * sizeof(CMiniColDef) + 1)); + // Mark the array of columns as not allocated (not ALLOCATED_MEMORY_MARKER) for SetNewColumnDefinition + // call bellow (code:#SetNewColumnDefinition_call) + *(BYTE *)(qbTempCols.Ptr()) = 0; + sTempTable.m_pColDefs = (CMiniColDef *)((BYTE *)(qbTempCols.Ptr()) + 1); + + // Initialize temp table col defs based on counts of data in the tables. + IfFailGo(InitColsForTable(Schema, ixTbl, &sTempTable, 1, FALSE)); + + if (GetCountRecs(ixTbl) > 0) + { + // Analyze the column definitions to determine the unchanged vs changed parts. + cbFixed = 0; + for (ixCol = 0; ixCol < sTempTable.m_cCols; ++ixCol) + { + if (sTempTable.m_pColDefs[ixCol].m_oColumn != m_TableDefs[ixTbl].m_pColDefs[ixCol].m_oColumn || + sTempTable.m_pColDefs[ixCol].m_cbColumn != m_TableDefs[ixTbl].m_pColDefs[ixCol].m_cbColumn) + break; + cbFixed += sTempTable.m_pColDefs[ixCol].m_cbColumn; + } + if (ixCol == sTempTable.m_cCols) + { + // no column is changing. We are done. + goto ErrExit; + } + cFixedCols = ixCol; + pFromCols = &m_TableDefs[ixTbl].m_pColDefs[ixCol]; + pToCols = &sTempTable.m_pColDefs[ixCol]; + cMoveCols = sTempTable.m_cCols - ixCol; + for (; ixCol < sTempTable.m_cCols; ++ixCol) + { + _ASSERTE(sTempTable.m_pColDefs[ixCol].m_cbColumn == 4); + } + + // Create the temporary table. + MetaData::TableRW tempTable; + IfFailGo(tempTable.InitializeEmpty_WithRecordCount( + sTempTable.m_cbRec, + m_Schema.m_cRecs[ixTbl] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + INDEBUG_MD(tempTable.Debug_SetTableInfo(NULL, ixTbl)); + + // For each row in the data. + RID rid; // Row iterator. + + for (rid = 1; rid <= m_Schema.m_cRecs[ixTbl]; ++rid) + { + RID ridNew; + BYTE *pFrom; + BYTE *pTo; + + IfFailGo(m_Tables[ixTbl].GetRecord(rid, &pFrom)); + IfFailGo(tempTable.AddRecord(&pTo, (UINT32 *)&ridNew)); + _ASSERTE(rid == ridNew); + + // Move the fixed part. + memcpy(pTo, pFrom, cbFixed); + + // Expand the expanded parts. + for (ixCol = 0; ixCol < cMoveCols; ++ixCol) + { + if (m_TableDefs[ixTbl].m_pColDefs[cFixedCols + ixCol].m_cbColumn == sizeof(USHORT)) + { + // The places that access expect the int16 to be in the high bytes so we need to the extra swap + SET_UNALIGNED_VAL32((pTo + pToCols[ixCol].m_oColumn), VAL16(*(USHORT*)(pFrom + pFromCols[ixCol].m_oColumn))); + } + else + { + // In this case we're just copying the data over + memcpy(pTo + pToCols[ixCol].m_oColumn, pFrom + pFromCols[ixCol].m_oColumn, sizeof(ULONG)); + } + } + } + + // Keep the expanded table. + m_Tables[ixTbl].Delete(); + IfFailGo(m_Tables[ixTbl].InitializeFromTable( + &tempTable, + TRUE)); // fCopyData + INDEBUG_MD(m_Tables[ixTbl].Debug_SetTableInfo(NULL, ixTbl)); + } + else + { // No data, so just reinitialize. + m_Tables[ixTbl].Delete(); + IfFailGo(m_Tables[ixTbl].InitializeEmpty_WithRecordCount( + sTempTable.m_cbRec, + g_TblSizeInfo[0][ixTbl] + COMMA_INDEBUG_MD(TRUE))); // fIsReadWrite + INDEBUG_MD(m_Tables[ixTbl].Debug_SetTableInfo(NULL, ixTbl)); + } + + //#SetNewColumnDefinition_call + // Keep the new column defs. + IfFailGo(SetNewColumnDefinition(&(m_TableDefs[ixTbl]), sTempTable.m_pColDefs, ixTbl)); + m_TableDefs[ixTbl].m_cbRec = sTempTable.m_cbRec; + +ErrExit: + return hr; +} // CMiniMdRW::ExpandTableColumns + + +//***************************************************************************** +// Used by caller to let us know save is completed. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::SaveDone() +{ + return PostSave(); +} // CMiniMdRW::SaveDone + +//***************************************************************************** +// General post-token-move table fixup. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FixUpTable( + ULONG ixTbl) // Index of table to fix. +{ + HRESULT hr = S_OK; + ULONG i, j; // Loop control. + ULONG cRows; // Count of rows in table. + void *pRec; // Pointer to row data. + mdToken tk; // A token. + ULONG rCols[16]; // List of columns with token data. + ULONG cCols; // Count of columns with token data. + + // If no remaps, nothing to do. + if (GetTokenMovementMap() == NULL) + return S_OK; + + // Find the columns with token data. + cCols = 0; + _ASSERTE(m_TableDefs[ixTbl].m_cCols <= 16); + for (i=0; i<m_TableDefs[ixTbl].m_cCols; ++i) + { + if (m_TableDefs[ixTbl].m_pColDefs[i].m_Type <= iCodedTokenMax) + rCols[cCols++] = i; + } + _ASSERTE(cCols); + if (cCols == 0) + return S_OK; + + cRows = m_Schema.m_cRecs[ixTbl]; + + // loop through all Rows + for (i = 1; i<=cRows; ++i) + { + IfFailGo(getRow(ixTbl, i, &pRec)); + for (j=0; j<cCols; ++j) + { + tk = GetToken(ixTbl, rCols[j], pRec); + tk = GetTokenMovementMap()->SafeRemap(tk); + IfFailGo(PutToken(ixTbl, rCols[j], pRec, tk)); + } + } + +ErrExit: + return hr; +} // CMiniMdRW::FixUpTable + + +//***************************************************************************** +// Fixup all the embedded ref to corresponding def before we remap tokens movement. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FixUpRefToDef() +{ + return NOERROR; +} // CMiniMdRW::FixUpRefToDef + +//***************************************************************************** +// Given a table with a pointer (index) to a sequence of rows in another +// table, get the RID of the end row. This is the STL-ish end; the first row +// not in the list. Thus, for a list of 0 elements, the start and end will +// be the same. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::Impl_GetEndRidForColumn( // The End rid. + UINT32 nTableIndex, + RID nRowIndex, + CMiniColDef &def, // Column containing the RID into other table. + UINT32 nTargetTableIndex, // The other table. + RID *pEndRid) +{ + HRESULT hr; + ULONG ixEnd; + void *pRow; + + // Last rid in range from NEXT record, or count of table, if last record. + _ASSERTE(nRowIndex <= m_Schema.m_cRecs[nTableIndex]); + if (nRowIndex < m_Schema.m_cRecs[nTableIndex]) + { + IfFailRet(getRow(nTableIndex, nRowIndex + 1, &pRow)); + ixEnd = getIX(pRow, def); + // We use a special value, 'END_OF_TABLE' (currently 0), to indicate + // end-of-table. If we find the special value we'll have to compute + // the value to return. If we don't find the special value, then + // the value is correct. + if (ixEnd != END_OF_TABLE) + { + *pEndRid = ixEnd; + return S_OK; + } + } + + // Either the child pointer value in the next row was END_OF_TABLE, or + // the row is the last row of the table. In either case, we must return + // a value which will work out to the END of the child table. That + // value depends on the value in the row itself -- if the row contains + // END_OF_TABLE, there are no children, and to make the subtraction + // work out, we return END_OF_TABLE for the END value. If the row + // contains some value, then we return the actual END count. + IfFailRet(getRow(nTableIndex, nRowIndex, &pRow)); + if (getIX(pRow, def) == END_OF_TABLE) + { + ixEnd = END_OF_TABLE; + } + else + { + ixEnd = m_Schema.m_cRecs[nTargetTableIndex] + 1; + } + + *pEndRid = ixEnd; + return S_OK; +} // CMiniMd::Impl_GetEndRidForColumn + +//***************************************************************************** +// Add a row to any table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddRecord( // S_OK or error. + UINT32 nTableIndex, // The table to expand. + void **ppRow, + RID *pRid) // Put RID here. +{ + HRESULT hr; + + _ASSERTE(nTableIndex < m_TblCount); + _ASSERTE(!m_bPreSaveDone && "Cannot add records after PreSave and before Save."); + IfFailRet(m_Tables[nTableIndex].AddRecord( + reinterpret_cast<BYTE **>(ppRow), + reinterpret_cast<UINT32 *>(pRid))); + if (*pRid > m_maxRid) + { + m_maxRid = *pRid; + if (m_maxRid > m_limRid && m_eGrow == eg_ok) + { + // OutputDebugStringA("Growing tables due to Record overflow.\n"); + m_eGrow = eg_grow, m_maxRid = m_maxIx = ULONG_MAX; + } + } + ++m_Schema.m_cRecs[nTableIndex]; + SetSorted(nTableIndex, false); + if (m_pVS[nTableIndex] != NULL) + { + m_pVS[nTableIndex]->m_isMapValid = false; + } + + return S_OK; +} // CMiniMdRW::AddRecord + +//***************************************************************************** +// Add a row to the TypeDef table, and initialize the pointers to other tables. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddTypeDefRecord( + TypeDefRec **ppRow, + RID *pnRowIndex) +{ + HRESULT hr; + IfFailRet(AddRecord(TBL_TypeDef, (void **)ppRow, pnRowIndex)); + + IfFailRet(PutCol(TBL_TypeDef, TypeDefRec::COL_MethodList, *ppRow, NewRecordPointerEndValue(TBL_Method))); + IfFailRet(PutCol(TBL_TypeDef, TypeDefRec::COL_FieldList, *ppRow, NewRecordPointerEndValue(TBL_Field))); + + return S_OK; +} // CMiniMdRW::AddTypeDefRecord + +//***************************************************************************** +// Add a row to the Method table, and initialize the pointers to other tables. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddMethodRecord( + MethodRec **ppRow, + RID *pnRowIndex) +{ + HRESULT hr; + IfFailRet(AddRecord(TBL_Method, (void **)ppRow, pnRowIndex)); + + IfFailRet(PutCol(TBL_Method, MethodRec::COL_ParamList, *ppRow, NewRecordPointerEndValue(TBL_Param))); + + return S_OK; +} // CMiniMdRW::AddMethodRecord + +//***************************************************************************** +// Add a row to the EventMap table, and initialize the pointers to other tables. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddEventMapRecord( + EventMapRec **ppRow, + RID *pnRowIndex) +{ + HRESULT hr; + IfFailRet(AddRecord(TBL_EventMap, (void **)ppRow, pnRowIndex)); + + IfFailRet(PutCol(TBL_EventMap, EventMapRec::COL_EventList, *ppRow, NewRecordPointerEndValue(TBL_Event))); + + SetSorted(TBL_EventMap, false); + + return S_OK; +} // CMiniMdRW::AddEventMapRecord + +//********************************************************************************* +// Add a row to the PropertyMap table, and initialize the pointers to other tables. +//********************************************************************************* +__checkReturn +HRESULT +CMiniMdRW::AddPropertyMapRecord( + PropertyMapRec **ppRow, + RID *pnRowIndex) +{ + HRESULT hr; + IfFailRet(AddRecord(TBL_PropertyMap, (void **)ppRow, pnRowIndex)); + + IfFailRet(PutCol(TBL_PropertyMap, PropertyMapRec::COL_PropertyList, *ppRow, NewRecordPointerEndValue(TBL_Property))); + + SetSorted(TBL_PropertyMap, false); + + return S_OK; +} // CMiniMdRW::AddPropertyMapRecord + +//***************************************************************************** +// converting a ANSI heap string to unicode string to an output buffer +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::Impl_GetStringW( + ULONG ix, + __out_ecount (cchBuffer) LPWSTR szOut, + ULONG cchBuffer, + ULONG *pcchBuffer) +{ + LPCSTR szString; // Single byte version. + int iSize; // Size of resulting string, in wide chars. + HRESULT hr = NOERROR; + + IfFailGo(getString(ix, &szString)); + + if (*szString == 0) + { + // If emtpy string "", return pccBuffer 0 + if ( szOut && cchBuffer ) + szOut[0] = W('\0'); + if ( pcchBuffer ) + *pcchBuffer = 0; + goto ErrExit; + } + if (!(iSize=::WszMultiByteToWideChar(CP_UTF8, 0, szString, -1, szOut, cchBuffer))) + { + // What was the problem? + DWORD dwNT = GetLastError(); + + // Not truncation? + if (dwNT != ERROR_INSUFFICIENT_BUFFER) + IfFailGo(HRESULT_FROM_NT(dwNT)); + + // Truncation error; get the size required. + if (pcchBuffer) + *pcchBuffer = ::WszMultiByteToWideChar(CP_UTF8, 0, szString, -1, NULL, 0); + + if ((szOut != NULL) && (cchBuffer > 0)) + { // null-terminate the truncated output string + szOut[cchBuffer - 1] = W('\0'); + } + + hr = CLDB_S_TRUNCATION; + goto ErrExit; + } + if (pcchBuffer) + *pcchBuffer = iSize; + +ErrExit: + return hr; +} // CMiniMdRW::Impl_GetStringW + +//***************************************************************************** +// Get a column value from a row. Signed types are sign-extended to the full +// ULONG; unsigned types are 0-extended. +//***************************************************************************** +ULONG CMiniMdRW::GetCol( // Column data. + ULONG ixTbl, // Index of the table. + ULONG ixCol, // Index of the column. + void *pvRecord) // Record with the data. +{ + BYTE *pRecord; // The row. + BYTE *pData; // The item in the row. + ULONG val; // The return value. + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column size, offset + CMiniColDef *pColDef = &m_TableDefs[ixTbl].m_pColDefs[ixCol]; + + pRecord = reinterpret_cast<BYTE*>(pvRecord); + pData = pRecord + pColDef->m_oColumn; + + switch (pColDef->m_cbColumn) + { + case 1: + val = *pData; + break; + case 2: + if (pColDef->m_Type == iSHORT) + val = static_cast<LONG>((INT16)GET_UNALIGNED_VAL16(pData)); + else + val = GET_UNALIGNED_VAL16(pData); + break; + case 4: + val = GET_UNALIGNED_VAL32(pData); + break; + default: + _ASSERTE(!"Unexpected column size"); + return 0; + } + + return val; +} // CMiniMdRW::GetCol + +//***************************************************************************** +// General token column fetcher. +//***************************************************************************** +mdToken CMiniMdRW::GetToken( + ULONG ixTbl, // Index of the table. + ULONG ixCol, // Index of the column. + void *pvRecord) // Record with the data. +{ + ULONG tkn; // Token from the table. + + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column description. + CMiniColDef *pColDef = &m_TableDefs[ixTbl].m_pColDefs[ixCol]; + + // Is the column just a RID? + if (pColDef->m_Type <= iRidMax) + { + tkn = GetCol(ixTbl, ixCol, pvRecord); //pColDef, pvRecord, RidFromToken(tk)); + tkn = TokenFromRid(tkn, GetTokenForTable(pColDef->m_Type)); + } + else // Is it a coded token? + if (pColDef->m_Type <= iCodedTokenMax) + { + ULONG indexCodedToken = pColDef->m_Type - iCodedToken; + if (indexCodedToken < COUNTOF(g_CodedTokens)) + { + const CCodedTokenDef *pCdTkn = &g_CodedTokens[indexCodedToken]; + tkn = decodeToken(GetCol(ixTbl, ixCol, pvRecord), pCdTkn->m_pTokens, pCdTkn->m_cTokens); + } + else + { + _ASSERTE(!"GetToken called on unexpected coded token type"); + tkn = 0; + } + } + else // It is an error. + { + _ASSERTE(!"GetToken called on unexpected column type"); + tkn = 0; + } + + return tkn; +} // CMiniMdRW::GetToken + +//***************************************************************************** +// Put a column value into a row. The value is passed as a ULONG; 1, 2, or 4 +// bytes are stored into the column. No table is specified, and the coldef +// is passed directly. This allows putting data into other buffers, such as +// the temporary table used for saving. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PutCol( // S_OK or E_UNEXPECTED. + CMiniColDef ColDef, // The col def. + void *pvRecord, // The row. + ULONG uVal) // Value to put. +{ + HRESULT hr = S_OK; + BYTE *pRecord; // The row. + BYTE *pData; // The item in the row. + + pRecord = reinterpret_cast<BYTE*>(pvRecord); + pData = pRecord + ColDef.m_oColumn; + + switch (ColDef.m_cbColumn) + { + case 1: + // Don't store a value that would overflow. + if (uVal > UCHAR_MAX) + return E_INVALIDARG; + *pData = static_cast<BYTE>(uVal); + break; + case 2: + if (uVal > USHRT_MAX) + return E_INVALIDARG; + SET_UNALIGNED_VAL16(pData, uVal); + break; + case 4: + SET_UNALIGNED_VAL32(pData, uVal); + break; + default: + _ASSERTE(!"Unexpected column size"); + return E_UNEXPECTED; + } + + return hr; +} // CMiniMdRW::PutCol + +//***************************************************************************** +// Put a column value into a row. The value is passed as a ULONG; 1, 2, or 4 +// bytes are stored into the column. +//***************************************************************************** + +//***************************************************************************** +// Add a string to the string pool, and store the offset in the cell. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PutString( // S_OK or E_UNEXPECTED. + ULONG ixTbl, // The table. + ULONG ixCol, // The column. + void *pvRecord, // The row. + LPCSTR szString) // Value to put. +{ + _ASSERTE(szString != NULL); + + HRESULT hr = S_OK; + UINT32 nStringIndex = 0; + + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column description. + _ASSERTE(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iSTRING); + + // <TODO>@FUTURE: Set iOffset to 0 for empty string. Work around the bug in + // StringPool that does not handle empty strings correctly.</TODO> + if (szString[0] == 0) + { // It's empty string + nStringIndex = 0; + } + else + { // It's non-empty string + IfFailGo(m_StringHeap.AddString( + szString, + &nStringIndex)); + } + + hr = PutCol(m_TableDefs[ixTbl].m_pColDefs[ixCol], pvRecord, nStringIndex); + + if (m_maxIx != ULONG_MAX) + { + IfFailGo(m_StringHeap.GetAlignedSize(&nStringIndex)); + } + if (nStringIndex > m_maxIx) + { + m_maxIx = nStringIndex; + if (m_maxIx > m_limIx && m_eGrow == eg_ok) + { + // OutputDebugStringA("Growing tables due to String overflow.\n"); + m_eGrow = eg_grow, m_maxRid = m_maxIx = ULONG_MAX; + } + } + +ErrExit: + return hr; +} // CMiniMdRW::PutString + +//***************************************************************************** +// Add a string to the string pool, and store the offset in the cell. +// Returns: S_OK or E_UNEXPECTED. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PutStringW( + ULONG ixTbl, // The table. + ULONG ixCol, // The column. + void *pvRecord, // The row. + LPCWSTR wszString) // Value to put. +{ + _ASSERTE(wszString != NULL); + + HRESULT hr = S_OK; + UINT32 nStringIndex = 0; // The new string. + + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column description. + _ASSERTE(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iSTRING); + + // Special case for empty string for StringPool + if (wszString[0] == 0) + { // It's empty string + // TODO: Is it OK that index 0 contains empty blob (00) and not empty string (00 01)? + nStringIndex = 0; + } + else + { // It's non-empty string + IfFailGo(m_StringHeap.AddStringW( + wszString, + &nStringIndex)); + } + + hr = PutCol(m_TableDefs[ixTbl].m_pColDefs[ixCol], pvRecord, nStringIndex); + + if (m_maxIx != ULONG_MAX) + { + IfFailGo(m_StringHeap.GetAlignedSize(&nStringIndex)); + } + if (nStringIndex > m_maxIx) + { + m_maxIx = nStringIndex; + if (m_maxIx > m_limIx && m_eGrow == eg_ok) + { + // OutputDebugStringA("Growing tables due to String overflow.\n"); + m_eGrow = eg_grow, m_maxRid = m_maxIx = ULONG_MAX; + } + } + +ErrExit: + return hr; +} // CMiniMdRW::PutStringW + +//***************************************************************************** +// Add a guid to the guid pool, and store the index in the cell. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PutGuid( // S_OK or E_UNEXPECTED. + ULONG ixTbl, // The table. + ULONG ixCol, // The column. + void *pvRecord, // The row. + REFGUID guid) // Value to put. +{ + HRESULT hr = S_OK; + UINT32 nIndex; + UINT32 cbSize = 0; + + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column description. + _ASSERTE(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iGUID); + + IfFailGo(AddGuid(guid, &nIndex)); + + hr = PutCol(m_TableDefs[ixTbl].m_pColDefs[ixCol], pvRecord, nIndex); + + if (m_maxIx != ULONG_MAX) + { + cbSize = m_GuidHeap.GetSize(); + } + if (cbSize > m_maxIx) + { + m_maxIx = cbSize; + if (m_maxIx > m_limIx && m_eGrow == eg_ok) + { + // OutputDebugStringA("Growing tables due to GUID overflow.\n"); + m_eGrow = eg_grow, m_maxRid = m_maxIx = ULONG_MAX; + } + } + +ErrExit: + return hr; +} // CMiniMdRW::PutGuid + +//***************************************************************************** +// Normally, an MVID is randomly generated for every metadata. +// ChangeMvid() can be used to explicitly set it. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ChangeMvid( // S_OK or E_UNEXPECTED. + REFGUID newMvid) +{ + HRESULT hr = S_OK; + + ModuleRec *pModuleRec; + IfFailRet(GetModuleRecord(1, &pModuleRec)); + UINT32 nGuidIndex = GetCol(TBL_Module, ModuleRec::COL_Mvid, pModuleRec); + + GUID UNALIGNED *pMvid; + IfFailRet(m_GuidHeap.GetGuid( + nGuidIndex, + &pMvid)); + + // Replace the GUID with new MVID. + *pMvid = newMvid; + // This was missing (probably because we don't test on platform with different bitness): + //SwapGuid(pMvid); + + return hr; +} // CMiniMdRW::ChangeMvid + +//***************************************************************************** +// Put a token into a cell. If the column is a coded token, perform the +// encoding first. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PutToken( // S_OK or E_UNEXPECTED. + ULONG ixTbl, // The table. + ULONG ixCol, // The column. + void *pvRecord, // The row. + mdToken tk) // Value to put. +{ + HRESULT hr = S_OK; + ULONG cdTkn; // The new coded token. + + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column description. + CMiniColDef ColDef = m_TableDefs[ixTbl].m_pColDefs[ixCol]; + + // Is the column just a RID? + if (ColDef.m_Type <= iRidMax) + hr = PutCol(ColDef, pvRecord, RidFromToken(tk)); + else // Is it a coded token? + if (ColDef.m_Type <= iCodedTokenMax) + { + ULONG indexCodedToken = ColDef.m_Type - iCodedToken; + if (indexCodedToken < COUNTOF(g_CodedTokens)) + { + const CCodedTokenDef *pCdTkn = &g_CodedTokens[indexCodedToken]; + cdTkn = encodeToken(RidFromToken(tk), TypeFromToken(tk), pCdTkn->m_pTokens, pCdTkn->m_cTokens); + hr = PutCol(ColDef, pvRecord, cdTkn); + } + else + { + _ASSERTE(!"PutToken called on unexpected coded token type"); + hr = E_FAIL; + } + } + else // It is an error. + { + _ASSERTE(!"PutToken called on unexpected column type"); + } + + return hr; +} // CMiniMdRW::PutToken + +//***************************************************************************** +// Add a blob to the blob pool, and store the offset in the cell. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::PutBlob( + ULONG ixTbl, // Table with the row. + ULONG ixCol, // Column to set. + void *pvRecord, // The row. + const void *pvData, // Blob data. + ULONG cbData) // Size of the blob data. +{ + HRESULT hr = S_OK; + UINT32 nBlobIndex; + + // Valid Table, Column, Row? + _ASSERTE(ixTbl < m_TblCount); + _ASSERTE(ixCol < m_TableDefs[ixTbl].m_cCols); + + // Column description. + _ASSERTE(m_TableDefs[ixTbl].m_pColDefs[ixCol].m_Type == iBLOB); + + IfFailGo(m_BlobHeap.AddBlob( + MetaData::DataBlob((BYTE *)pvData, cbData), + &nBlobIndex)); + + hr = PutCol(m_TableDefs[ixTbl].m_pColDefs[ixCol], pvRecord, nBlobIndex); + + if (m_maxIx != ULONG_MAX) + { + IfFailGo(m_BlobHeap.GetAlignedSize(&nBlobIndex)); + } + if (nBlobIndex > m_maxIx) + { + m_maxIx = nBlobIndex; + if (m_maxIx > m_limIx && m_eGrow == eg_ok) + { + // OutputDebugStringA("Growing tables due to Blob overflow.\n"); + m_eGrow = eg_grow, m_maxRid = m_maxIx = ULONG_MAX; + } + } + +ErrExit: + return hr; +} // CMiniMdRW::PutBlob + +//***************************************************************************** +// Given a table with a pointer to another table, add a row in the second table +// at the end of the range of rows belonging to some parent. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddChildRowIndirectForParent( + ULONG tblParent, // Parent table. + ULONG colParent, // Column in parent table. + ULONG tblChild, // Child table, pointed to by parent cell. + RID ridParent, // Rid of parent row. + void **ppRow) +{ + HRESULT hr; + ULONG ixInsert; // Index of new row. + ULONG i; // Loop control. + void *pRow; // A parent row. + ULONG ixChild; // Some child record RID. + + // If the row in the parent table is the last row, just append. + if (ridParent == GetCountRecs(tblParent)) + { + RID nRowIndex_Ignore; + return AddRecord(tblChild, ppRow, &nRowIndex_Ignore); + } + + // Determine the index at which to insert a row. + IfFailRet(getRow(tblParent, ridParent+1, &pRow)); + ixInsert = GetCol(tblParent, colParent, pRow); + + // Insert the row. + IfFailRet(m_Tables[tblChild].InsertRecord(ixInsert, reinterpret_cast<BYTE **>(ppRow))); + // Count the inserted record. + ++m_Schema.m_cRecs[tblChild]; + + if (m_Schema.m_cRecs[tblChild] > m_maxRid) + { + m_maxRid = m_Schema.m_cRecs[tblChild]; + if (m_maxRid > m_limRid && m_eGrow == eg_ok) + m_eGrow = eg_grow, m_maxIx = m_maxRid = ULONG_MAX; + } + + // Adjust the rest of the rows in the table. + for (i=GetCountRecs(tblParent); i>ridParent; --i) + { + IfFailRet(getRow(tblParent, i, &pRow)); + ixChild = GetCol(tblParent, colParent, pRow); + ++ixChild; + IfFailRet(PutCol(tblParent, colParent, pRow, ixChild)); + } + + return S_OK; +} // CMiniMdRW::AddChildRowIndirectForParent + +//***************************************************************************** +// Given a Parent and a Child, this routine figures if there needs to be an +// indirect table and creates it if needed. Else it just update the pointers +// in the entries contained in the parent table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddChildRowDirectForParent( + ULONG tblParent, // Parent table. + ULONG colParent, // Column in parent table. + ULONG tblChild, // Child table, pointed to by parent cell. + RID ridParent) // Rid of parent row. +{ + HRESULT hr = S_OK; + void *pRow; // A row in the parent table. + RID ixChild; // Rid of a child record. + + if (m_Schema.m_cRecs[tblChild-1] != 0) + { + // If there already exists an indirect table, just return. + hr = S_FALSE; + goto ErrExit; + } + + // If the parent record has subsequent parent records with children, + // we will now need to build a pointer table. + // + // The canonical form of a child pointer in a parent record is to point to + // the start of the child list. A record with no children will point + // to the same location as its subsequent record (that is, if A and B *could* + // have a child record, but only B *does*, both A and B will point to the + // same place. If the last record in the parent table has no child records, + // it will point one past the end of the child table. This is patterned + // after the STL's inclusive-BEGIN and exclusive-END. + // This has the unfortunate side effect that if a child record is added to + // a parent not at the end of its table, *all* of the subsequent parent records + // will have to be updated to point to the new "1 past end of child table" + // location. + // Therefore, as an optimization, we will also recognize a special marker, + // END_OF_TABLE (currently 0), to mean "past eot". + // + // If the child pointer of the record getting the new child is END_OF_TABLE, + // then there is no subsequent child pointer. We need to fix up this parent + // record, and any previous parent records with END_OF_TABLE to point to the + // new child record. + // If the child pointer of this parent record is not END_OF_TABLE, but the + // child pointer of the next parent record is, then there is nothing at + // all that needs to be done. + // If the child pointer of the next parent record is not END_OF_TABLE, then + // we will have to build a pointer table. + + // Get the parent record, and see if its child pointer is END_OF_TABLE. If so, + // fix the parent, and all previous END_OF_TABLE valued parent records. + IfFailGo(getRow(tblParent, ridParent, &pRow)); + ixChild = GetCol(tblParent, colParent, pRow); + if (ixChild == END_OF_TABLE) + { + IfFailGo(ConvertMarkerToEndOfTable(tblParent, colParent, m_Schema.m_cRecs[tblChild], ridParent)); + goto ErrExit; + } + + // The parent did not have END_OF_TABLE for its child pointer. If it was the last + // record in the table, there is nothing more to do. + if (ridParent == m_Schema.m_cRecs[tblParent]) + goto ErrExit; + + // The parent didn't have END_OF_TABLE, and there are more rows in parent table. + // If the next parent record's child pointer is END_OF_TABLE, then all of the + // remaining records are OK. + IfFailGo(getRow(tblParent, ridParent+1, &pRow)); + ixChild = GetCol(tblParent, colParent, pRow); + if (ixChild == END_OF_TABLE) + goto ErrExit; + + // The next record was not END_OF_TABLE, so some adjustment will be required. + // If it points to the actual END of the table, there are no more child records + // and the child pointers can be adjusted to the new END of the table. + if (ixChild == m_Schema.m_cRecs[tblChild]) + { + for (ULONG i=m_Schema.m_cRecs[tblParent]; i>ridParent; --i) + { + IfFailGo(getRow(tblParent, i, &pRow)); + IfFailGo(PutCol(tblParent, colParent, pRow, ixChild+1)); + } + goto ErrExit; + } + + // The next record contained a pointer to some actual child data. That means that + // this is an out-of-order insertion. We must create an indirect table. + // Convert any END_OF_TABLE to actual END of table value. Note that a record has + // just been added to the child table, and not yet to the parent table, so the END + // should currently point to the last valid record (instead of the usual first invalid + // rid). + IfFailGo(ConvertMarkerToEndOfTable(tblParent, colParent, m_Schema.m_cRecs[tblChild], m_Schema.m_cRecs[tblParent])); + // Create the indirect table. + IfFailGo(CreateIndirectTable(tblChild)); + hr = S_FALSE; + +ErrExit: + return hr; +} // CMiniMdRW::AddChildRowDirectForParent + +//***************************************************************************** +// Starting with some location, convert special END_OF_TABLE values into +// actual end of table values (count of records + 1). +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::ConvertMarkerToEndOfTable( + ULONG tblParent, // Parent table to convert. + ULONG colParent, // Column in parent table. + ULONG ixEnd, // Value to store to child pointer. + RID ridParent) // Rid of parent row to start with (work down). +{ + HRESULT hr; + void *pRow; // A row in the parent table. + RID ixChild; // Rid of a child record. + + for (; ridParent > 0; --ridParent) + { + IfFailGo(getRow(tblParent, ridParent, &pRow)); + ixChild = GetCol(tblParent, colParent, pRow); + // Finished when rows no longer have special value. + if (ixChild != END_OF_TABLE) + break; + IfFailGo(PutCol(tblParent, colParent, pRow, ixEnd)); + } + // Success. + hr = S_OK; + +ErrExit: + return hr; +} // CMiniMdRW::ConvertMarkerToEndOfTable + +//***************************************************************************** +// Given a Table ID this routine creates the corresponding pointer table with +// the entries in the given Table ID less one. It doesn't create the last +// entry by default, since its the last entry that caused the Indirect table to +// be required in most cases and will need to inserted at the appropriate location +// with AddChildRowIndirectForParent() function. So, be VERY CAREFUL when using this function! +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CreateIndirectTable( + ULONG ixTbl, // Given Table. + BOOL bOneLess /* = true */) // if true, create one entry less. +{ + void *pRecord; + ULONG cRecords; + HRESULT hr = S_OK; + + if (m_OptionValue.m_ErrorIfEmitOutOfOrder) + { + //<TODO> Can we use some bit fields and reduce the code size here?? + //</TODO> + if (ixTbl == TBL_Field && ( m_OptionValue.m_ErrorIfEmitOutOfOrder & MDFieldOutOfOrder ) ) + { + _ASSERTE(!"Out of order emit of field token!"); + return CLDB_E_RECORD_OUTOFORDER; + } + else if (ixTbl == TBL_Method && ( m_OptionValue.m_ErrorIfEmitOutOfOrder & MDMethodOutOfOrder ) ) + { + _ASSERTE(!"Out of order emit of method token!"); + return CLDB_E_RECORD_OUTOFORDER; + } + else if (ixTbl == TBL_Param && ( m_OptionValue.m_ErrorIfEmitOutOfOrder & MDParamOutOfOrder ) ) + { + _ASSERTE(!"Out of order emit of param token!"); + return CLDB_E_RECORD_OUTOFORDER; + } + else if (ixTbl == TBL_Property && ( m_OptionValue.m_ErrorIfEmitOutOfOrder & MDPropertyOutOfOrder ) ) + { + _ASSERTE(!"Out of order emit of property token!"); + return CLDB_E_RECORD_OUTOFORDER; + } + else if (ixTbl == TBL_Event && ( m_OptionValue.m_ErrorIfEmitOutOfOrder & MDEventOutOfOrder ) ) + { + _ASSERTE(!"Out of order emit of event token!"); + return CLDB_E_RECORD_OUTOFORDER; + } + } + + _ASSERTE(! HasIndirectTable(ixTbl)); + + cRecords = GetCountRecs(ixTbl); + if (bOneLess) + cRecords--; + + // Create one less than the number of records in the given table. + for (ULONG i = 1; i <= cRecords ; i++) + { + RID nRowIndex_Ignore; + IfFailGo(AddRecord(g_PtrTableIxs[ixTbl].m_ixtbl, &pRecord, &nRowIndex_Ignore)); + IfFailGo(PutCol(g_PtrTableIxs[ixTbl].m_ixtbl, g_PtrTableIxs[ixTbl].m_ixcol, pRecord, i)); + } +ErrExit: + return hr; +} // CMiniMdRW::CreateIndirectTable + +//--------------------------------------------------------------------------------------- +// +// The new paramter may not have been emitted in sequence order. So +// check the current parameter and move it up in the indirect table until +// we find the right home. +// +__checkReturn +HRESULT +CMiniMdRW::FixParamSequence( + RID md) // Rid of method with new parameter. +{ + HRESULT hr; + MethodRec * pMethod; + IfFailRet(GetMethodRecord(md, &pMethod)); + RID ixStart = getParamListOfMethod(pMethod); + RID ixEnd; + IfFailRet(getEndParamListOfMethod(md, &ixEnd)); + int iSlots = 0; + + // Param table should not be empty at this point. + _ASSERTE(ixEnd > ixStart); + + // Get a pointer to the new guy. + RID ridNew; + ParamPtrRec * pNewParamPtr = NULL; + if (HasIndirectTable(TBL_Param)) + { + IfFailRet(GetParamPtrRecord(--ixEnd, &pNewParamPtr)); + ridNew = GetCol(TBL_ParamPtr, ParamPtrRec::COL_Param, pNewParamPtr); + } + else + { + ridNew = --ixEnd; + } + + ParamRec * pNewParam; + IfFailRet(GetParamRecord(ridNew, &pNewParam)); + + // Walk the list forward looking for the insert point. + for (; ixStart < ixEnd; --ixEnd) + { + // Get the current parameter record. + RID ridOld; + if (HasIndirectTable(TBL_Param)) + { + ParamPtrRec * pParamPtr; + IfFailRet(GetParamPtrRecord(ixEnd - 1, &pParamPtr)); + ridOld = GetCol(TBL_ParamPtr, ParamPtrRec::COL_Param, pParamPtr); + } + else + { + ridOld = ixEnd - 1; + } + + ParamRec * pParamRec; + IfFailRet(GetParamRecord(ridOld, &pParamRec)); + + // If the new record belongs before this existing record, slide + // all of the old stuff down. + if (pNewParam->GetSequence() < pParamRec->GetSequence()) + { + ++iSlots; + } + else + { + break; + } + } + + // If the item is out of order, move everything down one slot and + // copy the new guy into the new location. Because the heap can be + // split, this must be done carefully. + //<TODO>@Future: one could write a more complicated but faster routine that + // copies blocks within heaps.</TODO> + if (iSlots) + { + RID endRid; + // Create an indirect table if there isn't one already. This is because + // we can't change tokens that have been handed out, in this case the + // param tokens. + if (!HasIndirectTable(TBL_Param)) + { + IfFailRet(CreateIndirectTable(TBL_Param, false)); + IfFailRet(getEndParamListOfMethod(md, &endRid)); + IfFailRet(GetParamPtrRecord(endRid - 1, &pNewParamPtr)); + } + int cbCopy = m_TableDefs[TBL_ParamPtr].m_cbRec; + void * pbBackup = _alloca(cbCopy); + memcpy(pbBackup, pNewParamPtr, cbCopy); + + IfFailRet(getEndParamListOfMethod(md, &endRid)); + for (ixEnd = endRid - 1; iSlots; iSlots--, --ixEnd) + { + ParamPtrRec * pTo; + IfFailRet(GetParamPtrRecord(ixEnd, &pTo)); + ParamPtrRec * pFrom; + IfFailRet(GetParamPtrRecord(ixEnd - 1, &pFrom)); + memcpy(pTo, pFrom, cbCopy); + } + + ParamPtrRec * pTo; + IfFailRet(GetParamPtrRecord(ixEnd, &pTo)); + memcpy(pTo, pbBackup, cbCopy); + } + return S_OK; +} // CMiniMdRW::FixParamSequence + +//--------------------------------------------------------------------------------------- +// +// Given a MethodDef and its parent TypeDef, add the MethodDef to the parent, +// adjusting the MethodPtr table if it exists or if it needs to be created. +// +__checkReturn +HRESULT +CMiniMdRW::AddMethodToTypeDef( + RID td, // The TypeDef to which to add the Method. + RID md) // MethodDef to add to TypeDef. +{ + HRESULT hr; + void * pPtr; + + // Add direct if possible. + IfFailGo(AddChildRowDirectForParent(TBL_TypeDef, TypeDefRec::COL_MethodList, TBL_Method, td)); + + // If couldn't add direct... + if (hr == S_FALSE) + { // Add indirect. + IfFailGo(AddChildRowIndirectForParent(TBL_TypeDef, TypeDefRec::COL_MethodList, TBL_MethodPtr, td, &pPtr)); + hr = PutCol(TBL_MethodPtr, MethodPtrRec::COL_Method, pPtr, md); + + // Add the <md, td> to the method parent lookup table. + IfFailGo(AddMethodToLookUpTable(TokenFromRid(md, mdtMethodDef), td) ); + } +ErrExit: + return hr; +} // CMiniMdRW::AddMethodToTypeDef + +//***************************************************************************** +// Given a FieldDef and its parent TypeDef, add the FieldDef to the parent, +// adjusting the FieldPtr table if it exists or if it needs to be created. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddFieldToTypeDef( + RID td, // The TypeDef to which to add the Field. + RID md) // FieldDef to add to TypeDef. +{ + HRESULT hr; + void *pPtr; + + // Add direct if possible. + IfFailGo(AddChildRowDirectForParent(TBL_TypeDef, TypeDefRec::COL_FieldList, TBL_Field, td)); + + // If couldn't add direct... + if (hr == S_FALSE) + { // Add indirect. + IfFailGo(AddChildRowIndirectForParent(TBL_TypeDef, TypeDefRec::COL_FieldList, TBL_FieldPtr, td, &pPtr)); + hr = PutCol(TBL_FieldPtr, FieldPtrRec::COL_Field, pPtr, md); + + // Add the <md, td> to the field parent lookup table. + IfFailGo(AddFieldToLookUpTable(TokenFromRid(md, mdtFieldDef), td)); + } +ErrExit: + return hr; +} // CMiniMdRW::AddFieldToTypeDef + +//***************************************************************************** +// Given a Param and its parent Method, add the Param to the parent, +// adjusting the ParamPtr table if there is an indirect table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddParamToMethod( + RID md, // The MethodDef to which to add the Param. + RID pd) // Param to add to MethodDef. +{ + HRESULT hr; + void *pPtr; + + IfFailGo(AddChildRowDirectForParent(TBL_Method, MethodRec::COL_ParamList, TBL_Param, md)); + if (hr == S_FALSE) + { + IfFailGo(AddChildRowIndirectForParent(TBL_Method, MethodRec::COL_ParamList, TBL_ParamPtr, md, &pPtr)); + IfFailGo(PutCol(TBL_ParamPtr, ParamPtrRec::COL_Param, pPtr, pd)); + + // Add the <pd, md> to the field parent lookup table. + IfFailGo(AddParamToLookUpTable(TokenFromRid(pd, mdtParamDef), md)); + } + IfFailGo(FixParamSequence(md)); + +ErrExit: + return hr; +} // CMiniMdRW::AddParamToMethod + +//***************************************************************************** +// Given a Property and its parent PropertyMap, add the Property to the parent, +// adjusting the PropertyPtr table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddPropertyToPropertyMap( + RID pmd, // The PropertyMap to which to add the Property. + RID pd) // Property to add to PropertyMap. +{ + HRESULT hr; + void *pPtr; + + IfFailGo(AddChildRowDirectForParent(TBL_PropertyMap, PropertyMapRec::COL_PropertyList, + TBL_Property, pmd)); + if (hr == S_FALSE) + { + IfFailGo(AddChildRowIndirectForParent(TBL_PropertyMap, PropertyMapRec::COL_PropertyList, + TBL_PropertyPtr, pmd, &pPtr)); + hr = PutCol(TBL_PropertyPtr, PropertyPtrRec::COL_Property, pPtr, pd); + } + + +ErrExit: + return hr; +} // CMiniMdRW::AddPropertyToPropertyMap + +//***************************************************************************** +// Given a Event and its parent EventMap, add the Event to the parent, +// adjusting the EventPtr table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddEventToEventMap( + ULONG emd, // The EventMap to which to add the Event. + RID ed) // Event to add to EventMap. +{ + HRESULT hr; + void *pPtr; + + IfFailGo(AddChildRowDirectForParent(TBL_EventMap, EventMapRec::COL_EventList, + TBL_Event, emd)); + if (hr == S_FALSE) + { + IfFailGo(AddChildRowIndirectForParent(TBL_EventMap, EventMapRec::COL_EventList, + TBL_EventPtr, emd, &pPtr)); + hr = PutCol(TBL_EventPtr, EventPtrRec::COL_Event, pPtr, ed); + } +ErrExit: + return hr; +} // CMiniMdRW::AddEventToEventMap + +//***************************************************************************** +// Find helper for a constant. This will trigger constant table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindConstantHelper( // return index to the constant table + mdToken tkParent, // Parent token. + RID *pFoundRid) +{ + _ASSERTE(TypeFromToken(tkParent) != 0); + + // If sorted, use the faster lookup + if (IsSorted(TBL_Constant)) + { + return FindConstantFor(RidFromToken(tkParent), TypeFromToken(tkParent), pFoundRid); + } + return GenericFindWithHash(TBL_Constant, ConstantRec::COL_Parent, tkParent, pFoundRid); +} // CMiniMdRW::FindConstantHelper + +//***************************************************************************** +// Find helper for a FieldMarshal. This will trigger FieldMarshal table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindFieldMarshalHelper( // return index to the field marshal table + mdToken tkParent, // Parent token. Can be a FieldDef or ParamDef. + RID *pFoundRid) +{ + _ASSERTE(TypeFromToken(tkParent) != 0); + + // If sorted, use the faster lookup + if (IsSorted(TBL_FieldMarshal)) + { + return FindFieldMarshalFor(RidFromToken(tkParent), TypeFromToken(tkParent), pFoundRid); + } + return GenericFindWithHash(TBL_FieldMarshal, FieldMarshalRec::COL_Parent, tkParent, pFoundRid); +} // CMiniMdRW::FindFieldMarshalHelper + + +//***************************************************************************** +// Find helper for a method semantics. +// This will look up methodsemantics based on its status! +// Can return out of memory error because of the enumerator. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindMethodSemanticsHelper( + mdToken tkAssociate, // Event or property token + HENUMInternal *phEnum) // fill in the enum +{ + ULONG ridStart, ridEnd; + ULONG index; + MethodSemanticsRec *pMethodSemantics; + HRESULT hr = NOERROR; + CLookUpHash *pHashTable = m_pLookUpHashs[TBL_MethodSemantics]; + + _ASSERTE(TypeFromToken(tkAssociate) != 0); + + if (IsSorted(TBL_MethodSemantics)) + { + IfFailGo(getAssociatesForToken(tkAssociate, &ridEnd, &ridStart)); + HENUMInternal::InitSimpleEnum(0, ridStart, ridEnd, phEnum); + } + else if (pHashTable) + { + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + HENUMInternal::InitDynamicArrayEnum(phEnum); + iHash = HashToken(tkAssociate); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailGo(GetMethodSemanticsRecord(p->tok, &pMethodSemantics)); + if (getAssociationOfMethodSemantics(pMethodSemantics) == tkAssociate) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, p->tok) ); + } + } + } + else + { + // linear search + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = 1; index <= getCountMethodSemantics(); index++) + { + IfFailGo(GetMethodSemanticsRecord(index, &pMethodSemantics)); + if (getAssociationOfMethodSemantics(pMethodSemantics) == tkAssociate) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, index) ); + } + } + } +ErrExit: + return hr; +} // CMiniMdRW::FindMethodSemanticsHelper + + +//***************************************************************************** +// Find helper for a method semantics given a associate and semantics. +// This will look up methodsemantics based on its status! +// Return CLDB_E_RECORD_NOTFOUND if cannot find the matching one +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindAssociateHelper( + mdToken tkAssociate, // Event or property token + DWORD dwSemantics, // [IN] given a associate semantics(setter, getter, testdefault, reset) + RID *pRid) // [OUT] return matching row index here +{ + ULONG ridStart, ridEnd; + ULONG index; + MethodSemanticsRec *pMethodSemantics; + HRESULT hr = NOERROR; + CLookUpHash *pHashTable = m_pLookUpHashs[TBL_MethodSemantics]; + + _ASSERTE(TypeFromToken(tkAssociate) != 0); + + if (pHashTable) + { + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + iHash = HashToken(tkAssociate); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailGo(GetMethodSemanticsRecord(p->tok, &pMethodSemantics)); + if (pMethodSemantics->GetSemantic() == dwSemantics && getAssociationOfMethodSemantics(pMethodSemantics) == tkAssociate) + { + *pRid = p->tok; + goto ErrExit; + } + } + } + else + { + if (IsSorted(TBL_MethodSemantics)) + { + IfFailGo(getAssociatesForToken(tkAssociate, &ridEnd, &ridStart)); + } + else + { + ridStart = 1; + ridEnd = getCountMethodSemantics() + 1; + } + + for (index = ridStart; index < ridEnd ; index++) + { + IfFailGo(GetMethodSemanticsRecord(index, &pMethodSemantics)); + if (pMethodSemantics->GetSemantic() == dwSemantics && getAssociationOfMethodSemantics(pMethodSemantics) == tkAssociate) + { + *pRid = index; + goto ErrExit; + } + } + } + hr = CLDB_E_RECORD_NOTFOUND; +ErrExit: + return hr; +} // CMiniMdRW::FindAssociateHelper + + +//***************************************************************************** +// Find helper for a MethodImpl. +// This will trigger MethodImpl table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindMethodImplHelper( + mdTypeDef td, // TypeDef token for the Class. + HENUMInternal *phEnum) // fill in the enum +{ + ULONG ridStart, ridEnd; + ULONG index; + MethodImplRec *pMethodImpl; + HRESULT hr = NOERROR; + CLookUpHash *pHashTable = m_pLookUpHashs[TBL_MethodImpl]; + + _ASSERTE(TypeFromToken(td) == mdtTypeDef); + + if (IsSorted(TBL_MethodImpl)) + { + IfFailGo(getMethodImplsForClass(RidFromToken(td), &ridEnd, &ridStart)); + HENUMInternal::InitSimpleEnum(0, ridStart, ridEnd, phEnum); + } + else if (pHashTable) + { + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + HENUMInternal::InitDynamicArrayEnum(phEnum); + iHash = HashToken(td); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailGo(GetMethodImplRecord(p->tok, &pMethodImpl)); + if (getClassOfMethodImpl(pMethodImpl) == td) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, p->tok) ); + } + } + } + else + { + // linear search + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = 1; index <= getCountMethodImpls(); index++) + { + IfFailGo(GetMethodImplRecord(index, &pMethodImpl)); + if (getClassOfMethodImpl(pMethodImpl) == td) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, index) ); + } + } + } +ErrExit: + return hr; +} // CMiniMdRW::FindMethodImplHelper + + +//***************************************************************************** +// Find helper for a GenericParam. +// This will trigger GenericParam table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindGenericParamHelper( + mdToken tkOwner, // Token for the GenericParams' owner. + HENUMInternal *phEnum) // fill in the enum +{ + HRESULT hr = NOERROR; + ULONG ridStart, ridEnd; // Start, end of range of tokens. + ULONG index; // A loop counter. + GenericParamRec *pGenericParam; + CLookUpHash *pHashTable = m_pLookUpHashs[TBL_GenericParam]; + + if (IsSorted(TBL_GenericParam)) + { + mdToken tk; + tk = encodeToken(RidFromToken(tkOwner), TypeFromToken(tkOwner), mdtTypeOrMethodDef, lengthof(mdtTypeOrMethodDef)); + IfFailGo(SearchTableForMultipleRows(TBL_GenericParam, + _COLDEF(GenericParam,Owner), + tk, + &ridEnd, + &ridStart)); + HENUMInternal::InitSimpleEnum(mdtGenericParam, ridStart, ridEnd, phEnum); + } + else if (pHashTable) + { + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + HENUMInternal::InitDynamicArrayEnum(phEnum); + iHash = HashToken(tkOwner); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailGo(GetGenericParamRecord(p->tok, &pGenericParam)); + if (getOwnerOfGenericParam(pGenericParam) == tkOwner) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, TokenFromRid(p->tok, mdtGenericParam)) ); + } + } + } + else + { + // linear search + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = 1; index <= getCountGenericParams(); index++) + { + IfFailGo(GetGenericParamRecord(index, &pGenericParam)); + if (getOwnerOfGenericParam(pGenericParam) == tkOwner) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, TokenFromRid(index, mdtGenericParam)) ); + } + } + } +ErrExit: + return hr; +} // CMiniMdRW::FindGenericParamHelper + + +//***************************************************************************** +// Find helper for a GenericParamConstraint. +// This will trigger GenericParamConstraint table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindGenericParamConstraintHelper( + mdGenericParam tkParam, // Token for the GenericParam + HENUMInternal *phEnum) // fill in the enum +{ + HRESULT hr = NOERROR; + ULONG ridStart, ridEnd; // Start, end of range of tokens. + ULONG index; // A loop counter. + GenericParamConstraintRec *pConstraint; + CLookUpHash *pHashTable = m_pLookUpHashs[TBL_GenericParamConstraint]; + RID ridParam = RidFromToken(tkParam); + _ASSERTE(TypeFromToken(tkParam) == mdtGenericParam); + + // Extract the rid part of the token for comparison below. Be sure + // that the column is a RID column, so that getGPCFGP() returns a RID. + _ASSERTE(IsRidType(m_TableDefs[TBL_GenericParamConstraint].m_pColDefs[GenericParamConstraintRec::COL_Owner].m_Type)); + + if (IsSorted(TBL_GenericParamConstraint)) + { + IfFailGo(getGenericParamConstraintsForGenericParam(ridParam, &ridEnd, &ridStart)); + HENUMInternal::InitSimpleEnum(mdtGenericParamConstraint, ridStart, ridEnd, phEnum); + } + else if (pHashTable) + { + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + HENUMInternal::InitDynamicArrayEnum(phEnum); + iHash = HashToken(tkParam); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailGo(GetGenericParamConstraintRecord(p->tok, &pConstraint)); + if (getOwnerOfGenericParamConstraint(pConstraint) == tkParam) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, TokenFromRid(p->tok, mdtGenericParamConstraint)) ); + } + } + } + else + { + // linear search + HENUMInternal::InitDynamicArrayEnum(phEnum); + for (index = 1; index <= getCountGenericParamConstraints(); index++) + { + IfFailGo(GetGenericParamConstraintRecord(index, &pConstraint)); + if (getOwnerOfGenericParamConstraint(pConstraint) == tkParam) + { + IfFailGo( HENUMInternal::AddElementToEnum(phEnum, TokenFromRid(index, mdtGenericParamConstraint)) ); + } + } + } +ErrExit: + return hr; +} // CMiniMdRW::FindGenericParamConstraintHelper + + +//***************************************************************************** +// Find helper for a ClassLayout. This will trigger ClassLayout table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindClassLayoutHelper( // return index to the ClassLayout table + mdTypeDef tkParent, // Parent token. + RID *pFoundRid) +{ + _ASSERTE(TypeFromToken(tkParent) == mdtTypeDef); + + // If sorted, use the faster lookup + if (IsSorted(TBL_ClassLayout)) + { + return FindClassLayoutFor(RidFromToken(tkParent), pFoundRid); + } + return GenericFindWithHash(TBL_ClassLayout, ClassLayoutRec::COL_Parent, tkParent, pFoundRid); +} // CMiniMdRW::FindClassLayoutHelper + +//***************************************************************************** +// Find helper for a FieldLayout. This will trigger FieldLayout table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindFieldLayoutHelper( // return index to the FieldLayout table + mdFieldDef tkField, // Field RID. + RID *pFoundRid) +{ + _ASSERTE(TypeFromToken(tkField) == mdtFieldDef); + + // If sorted, use the faster lookup + if (IsSorted(TBL_FieldLayout)) + { + return FindFieldLayoutFor(RidFromToken(tkField), pFoundRid); + } + return GenericFindWithHash(TBL_FieldLayout, FieldLayoutRec::COL_Field, tkField, pFoundRid); +} // CMiniMdRW::FindFieldLayoutHelper + +//***************************************************************************** +// Find helper for a ImplMap. This will trigger ImplMap table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindImplMapHelper( // return index to the ImplMap table + mdToken tk, // Member forwarded token. + RID *pFoundRid) +{ + _ASSERTE(TypeFromToken(tk) != 0); + + // If sorted, use the faster lookup + if (IsSorted(TBL_ImplMap)) + { + return FindImplMapFor(RidFromToken(tk), TypeFromToken(tk), pFoundRid); + } + return GenericFindWithHash(TBL_ImplMap, ImplMapRec::COL_MemberForwarded, tk, pFoundRid); +} // CMiniMdRW::FindImplMapHelper + + +//***************************************************************************** +// Find helper for a FieldRVA. This will trigger FieldRVA table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindFieldRVAHelper( // return index to the FieldRVA table + mdFieldDef tkField, // Field token. + RID *pFoundRid) +{ + _ASSERTE(TypeFromToken(tkField) == mdtFieldDef); + + // If sorted, use the faster lookup + if (IsSorted(TBL_FieldRVA)) + { + return FindFieldRVAFor(RidFromToken(tkField), pFoundRid); + } + return GenericFindWithHash(TBL_FieldRVA, FieldRVARec::COL_Field, tkField, pFoundRid); +} // CMiniMdRW::FindFieldRVAHelper + +//***************************************************************************** +// Find helper for a NestedClass. This will trigger NestedClass table to be sorted if it is not. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindNestedClassHelper( // return index to the NestedClass table + mdTypeDef tkClass, // NestedClass RID. + RID *pFoundRid) +{ + // If sorted, use the faster lookup + if (IsSorted(TBL_NestedClass)) + { + return FindNestedClassFor(RidFromToken(tkClass), pFoundRid); + } + return GenericFindWithHash(TBL_NestedClass, NestedClassRec::COL_NestedClass, tkClass, pFoundRid); +} // CMiniMdRW::FindNestedClassHelper + + +//************************************************************************* +// generic find helper with hash table +//************************************************************************* +__checkReturn +HRESULT +CMiniMdRW::GenericFindWithHash( // Return code. + ULONG ixTbl, // Table with hash + ULONG ixCol, // col that we hash. + mdToken tkTarget, // token to be find in the hash + RID *pFoundRid) +{ + HRESULT hr; + ULONG index; + mdToken tkHash; + BYTE * pRec; + + // Partial check -- only one rid for table 0, so if type is 0, rid should be 1. + _ASSERTE(TypeFromToken(tkTarget) != 0 || RidFromToken(tkTarget) == 1); + + if (m_pLookUpHashs[ixTbl] == NULL) + { + // Just ignore the returned error - the hash is either created or not + (void)GenericBuildHashTable(ixTbl, ixCol); + } + + CLookUpHash * pHashTable = m_pLookUpHashs[ixTbl]; + if (pHashTable != NULL) + { + TOKENHASHENTRY *p; + ULONG iHash; + int pos; + + // Hash the data. + iHash = HashToken(tkTarget); + + // Go through every entry in the hash chain looking for ours. + for (p = pHashTable->FindFirst(iHash, pos); + p; + p = pHashTable->FindNext(pos)) + { + IfFailRet(m_Tables[ixTbl].GetRecord(p->tok, &pRec)); + + // get the column value that we will hash + tkHash = GetToken(ixTbl, ixCol, pRec); + if (tkHash == tkTarget) + { + // found the match + *pFoundRid = p->tok; + return S_OK; + } + } + } + else + { + // linear search + for (index = 1; index <= GetCountRecs(ixTbl); index++) + { + IfFailRet(m_Tables[ixTbl].GetRecord(index, &pRec)); + tkHash = GetToken(ixTbl, ixCol, pRec); + if (tkHash == tkTarget) + { + // found the match + *pFoundRid = index; + return S_OK; + } + } + } + *pFoundRid = 0; + return S_OK; +} // CMiniMdRW::GenericFindWithHash + +//************************************************************************* +// Build a hash table for the specified table if the size exceed the thresholds. +//************************************************************************* +__checkReturn +HRESULT +CMiniMdRW::GenericBuildHashTable( + ULONG ixTbl, // Table with hash. + ULONG ixCol) // Column that we hash. +{ + HRESULT hr = S_OK; + BYTE *pRec; + mdToken tkHash; + ULONG iHash; + TOKENHASHENTRY *pEntry; + + // If the hash table hasn't been built it, see if it should get faulted in. + if (m_pLookUpHashs[ixTbl] == NULL) + { + ULONG ridEnd = GetCountRecs(ixTbl); + + //<TODO>@FUTURE: we need to init the size of the hash table corresponding to the current + // size of table in E&C's case. + //</TODO> + // Avoid prefast warning with "if (ridEnd + 1 > INDEX_ROW_COUNT_THRESHOLD)" + if (ridEnd > INDEX_ROW_COUNT_THRESHOLD - 1) + { + // Create a new hash. + NewHolder<CLookUpHash> pHashTable = new (nothrow) CLookUpHash; + IfNullGo(pHashTable); + IfFailGo(pHashTable->NewInit( + g_HashSize[GetMetaDataSizeIndex(&m_OptionValue)])); + + // Scan every entry already in the table, add it to the hash. + for (ULONG index = 1; index <= ridEnd; index++) + { + IfFailGo(m_Tables[ixTbl].GetRecord(index, &pRec)); + + // get the column value that we will hash + tkHash = GetToken(ixTbl, ixCol, pRec); + + // hash the value + iHash = HashToken(tkHash); + + pEntry = pHashTable->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = index; + + } + + if (InterlockedCompareExchangeT<CLookUpHash *>( + &m_pLookUpHashs[ixTbl], + pHashTable, + NULL) == NULL) + { // We won the initializaion race + pHashTable.SuppressRelease(); + } + } + } +ErrExit: + return hr; +} // CMiniMdRW::GenericBuildHashTable + +//************************************************************************* +// Add a rid from a table into a hash. We will hash on the ixCol of the ixTbl. +//************************************************************************* +__checkReturn +HRESULT +CMiniMdRW::GenericAddToHash( + ULONG ixTbl, // Table with hash + ULONG ixCol, // column that we hash by calling HashToken. + RID rid) // Token of new guy into the ixTbl. +{ + HRESULT hr = S_OK; + CLookUpHash *pHashTable = m_pLookUpHashs[ixTbl]; + void *pRec; + mdToken tkHash; + ULONG iHash; + TOKENHASHENTRY *pEntry; + + // If the hash table hasn't been built it, see if it should get faulted in. + if (pHashTable == NULL) + { + IfFailGo(GenericBuildHashTable(ixTbl, ixCol)); + } + else + { + // Adding into hash table has to be protected by write-lock + INDEBUG(Debug_CheckIsLockedForWrite();) + + IfFailGo(m_Tables[ixTbl].GetRecord(rid, reinterpret_cast<BYTE **>(&pRec))); + + tkHash = GetToken(ixTbl, ixCol, pRec); + iHash = HashToken(tkHash); + pEntry = pHashTable->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = rid; + } + +ErrExit: + return hr; +} // CMiniMdRW::GenericAddToHash + + +//***************************************************************************** +// look up a table by a col given col value is ulVal. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::LookUpTableByCol( + ULONG ulVal, // Value for which to search. + VirtualSort *pVSTable, // A VirtualSort on the table, if any. + RID *pRidStart, // Put RID of first match here. + RID *pRidEnd) // [OPTIONAL] Put RID of end match here. +{ + HRESULT hr = NOERROR; + ULONG ixTbl; + ULONG ixCol; + + _ASSERTE(pVSTable != NULL); + ixTbl = pVSTable->m_ixTbl; + ixCol = pVSTable->m_ixCol; + if (IsSorted(ixTbl)) + { + // Table itself is sorted so we don't need to build a virtual sort table. + // Binary search on the table directly. + // + IfFailGo(SearchTableForMultipleRows( + ixTbl, + m_TableDefs[ixTbl].m_pColDefs[ixCol], + ulVal, + pRidEnd, + pRidStart)); + } + else + { + if (!pVSTable->m_isMapValid) + { + INDEBUG(Debug_CheckIsLockedForWrite();) + + int iCount; + + // build the parallel VirtualSort table + if (pVSTable->m_pMap == NULL) + { + // the first time that we build the VS table. We need to allocate the TOKENMAP + pVSTable->m_pMap = new (nothrow) TOKENMAP; + IfNullGo(pVSTable->m_pMap); + } + + // ensure the look up table is big enough + iCount = pVSTable->m_pMap->Count(); + if (pVSTable->m_pMap->AllocateBlock(m_Schema.m_cRecs[ixTbl] + 1 - iCount) == 0) + { + IfFailGo(E_OUTOFMEMORY); + } + + // now build the table + // Element 0 of m_pMap will never be used, its just being initialized anyway. + for (ULONG i = 0; i <= m_Schema.m_cRecs[ixTbl]; i++) + { + *(pVSTable->m_pMap->Get(i)) = i; + } + // sort the table + IfFailGo(pVSTable->Sort()); + } + // binary search on the LookUp + { + void *pRow; // Row from a table. + ULONG val; // Value from a row. + CMiniColDef *pCol; + int lo,hi,mid=0; // binary search indices. + RID ridEnd, ridBegin; + + pCol = m_TableDefs[ixTbl].m_pColDefs; + + // Start with entire table. + lo = 1; + hi = GetCountRecs( ixTbl ); + // While there are rows in the range... + while ( lo <= hi ) + { // Look at the one in the middle. + mid = (lo + hi) / 2; + IfFailGo(getRow( + ixTbl, + (UINT32)*(pVSTable->m_pMap->Get(mid)), + &pRow)); + val = getIX( pRow, pCol[ixCol] ); + + // If equal to the target, done. + if ( val == ulVal ) + break; + // If middle item is too small, search the top half. + if ( val < ulVal ) + lo = mid + 1; + else // but if middle is to big, search bottom half. + hi = mid - 1; + } + if ( lo > hi ) + { + // Didn't find anything that matched. + *pRidStart = 0; + if (pRidEnd) *pRidEnd = 0; + goto ErrExit; + } + + + // Now mid is pointing to one of the several records that match the search. + // Find the beginning and find the end. + ridBegin = mid; + + // End will be at least one larger than found record. + ridEnd = ridBegin + 1; + + // Search back to start of group. + for (;;) + { + if (ridBegin <= 1) + { + break; + } + IfFailGo(getRow( + ixTbl, + (UINT32)*(pVSTable->m_pMap->Get(ridBegin-1)), + &pRow)); + if (getIX(pRow, pCol[ixCol]) != ulVal) + { + break; + } + --ridBegin; + } + + // If desired, search forward to end of group. + if (pRidEnd != NULL) + { + for (;;) + { + if (ridEnd > GetCountRecs(ixTbl)) + { + break; + } + IfFailGo(getRow( + ixTbl, + (UINT32)*(pVSTable->m_pMap->Get(ridEnd)), + &pRow)); + if (getIX(pRow, pCol[ixCol]) != ulVal) + { + break; + } + ++ridEnd; + } + *pRidEnd = ridEnd; + } + *pRidStart = ridBegin; + } + } + + // fall through +ErrExit: + return hr; +} // CMiniMdRW::LookUpTableByCol + +__checkReturn +HRESULT +CMiniMdRW::Impl_SearchTableRW( + ULONG ixTbl, // Table to search. + ULONG ixCol, // Column to search. + ULONG ulTarget, // Value to search for. + RID *pFoundRid) +{ + HRESULT hr = S_OK; + RID iRid; // The resulting RID. + RID iRidEnd; // Unused. + + // Look up. + hr = LookUpTableByCol(ulTarget, m_pVS[ixTbl], &iRid, &iRidEnd); + if (FAILED(hr)) + { + iRid = 0; + } + else // Convert to real RID. + { + iRid = GetRidFromVirtualSort(ixTbl, iRid); + } + + *pFoundRid = iRid; + return S_OK; +} // CMiniMdRW::Impl_SearchTableRW + +//***************************************************************************** +// Search a table for the row containing the given key value. +// EG. Constant table has pointer back to Param or Field. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::vSearchTable( // RID of matching row, or 0. + ULONG ixTbl, // Table to search. + CMiniColDef sColumn, // Sorted key column, containing search value. + ULONG ulTarget, // Target for search. + RID *pRid) +{ + HRESULT hr; + void *pRow; // Row from a table. + ULONG val; // Value from a row. + + int lo,mid,hi; // binary search indices. + + // Binary search requires sorted table. + // @todo GENERICS: why is IsSorted not true for mdtGenericParam? + // _ASSERTE(IsSorted(ixTbl)); + + // Start with entire table. + lo = 1; + hi = GetCountRecs(ixTbl); + // While there are rows in the range... + while (lo <= hi) + { // Look at the one in the middle. + mid = (lo + hi) / 2; + IfFailRet(getRow(ixTbl, mid, &pRow)); + val = getIX(pRow, sColumn); + // If equal to the target, done. + if (val == ulTarget) + { + *pRid = mid; + return S_OK; + } + // If middle item is too small, search the top half. + if (val < ulTarget || val == END_OF_TABLE) + lo = mid + 1; + else // but if middle is to big, search bottom half. + hi = mid - 1; + } + // Didn't find anything that matched. + + // @todo GENERICS: Work around for refEmit feature. Remove once table is sorted. + if (ixTbl == TBL_GenericParam && !IsSorted(ixTbl)) + { + for (int i = 1; i <= (int)GetCountRecs(ixTbl); i ++) + { + IfFailRet(getRow(ixTbl, i, &pRow)); + if (getIX(pRow, sColumn) == ulTarget) + { + *pRid = i; + return S_OK; + } + } + } + + *pRid = 0; + return S_OK; +} // CMiniMdRW::vSearchTable + +//***************************************************************************** +// Search a table for the highest-RID row containing a value that is less than +// or equal to the target value. EG. TypeDef points to first Field, but if +// a TypeDef has no fields, it points to first field of next TypeDef. +// This is complicated by the possible presence of columns containing +// END_OF_TABLE values, which are not necessarily in greater than +// other values. However, this invalid-rid value will occur only at the +// end of the table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::vSearchTableNotGreater( // RID of matching row, or 0. + ULONG ixTbl, // Table to search. + CMiniColDef sColumn, // the column def containing search value + ULONG ulTarget, // target for search + RID *pRid) +{ + HRESULT hr; + void *pRow; // Row from a table. + ULONG cRecs; // Rows in the table. + ULONG val = 0; // Value from a table. + ULONG lo,mid=0,hi; // binary search indices. + + cRecs = GetCountRecs(ixTbl); + + // Start with entire table. + lo = 1; + hi = cRecs; + // If no recs, return. + if (lo > hi) + { + *pRid = 0; + return S_OK; + } + // While there are rows in the range... + while (lo <= hi) + { // Look at the one in the middle. + mid = (lo + hi) / 2; + IfFailRet(getRow(ixTbl, mid, &pRow)); + val = getIX(pRow, sColumn); + // If equal to the target, done searching. + if (val == ulTarget) + break; + // If middle item is too small, search the top half. + if (val < ulTarget && val != END_OF_TABLE) + lo = mid + 1; + else // but if middle is to big, search bottom half. + hi = mid - 1; + } + // May or may not have found anything that matched. Mid will be close, but may + // be to high or too low. It should point to the highest acceptable + // record. + + // If the value is greater than the target, back up just until the value is + // less than or equal to the target. SHOULD only be one step. + if (val > ulTarget || val == END_OF_TABLE) + { + while (val > ulTarget || val == END_OF_TABLE) + { + _ASSERTE(mid > 1); + // If no recs match, return. + if (mid == 1) + { + *pRid = 0; + return S_OK; + } + --mid; + IfFailRet(getRow(ixTbl, mid, &pRow)); + val = getIX(pRow, sColumn); + } + } + else + { + // Value is less than or equal to the target. As long as the next + // record is also acceptable, move forward. + while (mid < cRecs) + { + // There is another record. Get its value. + IfFailRet(getRow(ixTbl, mid+1, &pRow)); + val = getIX(pRow, sColumn); + // If that record is too high, stop. + if (val > ulTarget || val == END_OF_TABLE) + break; + mid++; + } + } + + // Return the value that's just less than the target. + *pRid = mid; + return S_OK; +} // CMiniMdRW::vSearchTableNotGreater + +//--------------------------------------------------------------------------------------- +// +// Create MemberRef hash table. +// +__checkReturn +HRESULT +CMiniMdRW::CreateMemberRefHash() +{ + HRESULT hr = S_OK; + + if (m_pMemberRefHash == NULL) + { + ULONG ridEnd = getCountMemberRefs(); + if (ridEnd + 1 > INDEX_ROW_COUNT_THRESHOLD) + { + // Create a new hash. + NewHolder<CMemberRefHash> pMemberRefHash = new (nothrow) CMemberRefHash(); + IfNullGo(pMemberRefHash); + IfFailGo(pMemberRefHash->NewInit( + g_HashSize[GetMetaDataSizeIndex(&m_OptionValue)])); + + // Scan every entry already in the table, add it to the hash. + for (ULONG index = 1; index <= ridEnd; index++) + { + MemberRefRec * pMemberRef; + IfFailGo(GetMemberRefRecord(index, &pMemberRef)); + + LPCSTR szMemberRefName; + IfFailGo(getNameOfMemberRef(pMemberRef, &szMemberRefName)); + ULONG iHash = HashMemberRef( + getClassOfMemberRef(pMemberRef), + szMemberRefName); + + TOKENHASHENTRY * pEntry = pMemberRefHash->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = TokenFromRid(index, mdtMemberRef); + } + + if (InterlockedCompareExchangeT<CMemberRefHash *>(&m_pMemberRefHash, pMemberRefHash, NULL) == NULL) + { // We won the initialization race + pMemberRefHash.SuppressRelease(); + } + } + } + +ErrExit: + return hr; +} // CMiniMdRW::CreateMemberRefHash + +//--------------------------------------------------------------------------------------- +// +// Add a new MemberRef to the hash table. +// +__checkReturn +HRESULT +CMiniMdRW::AddMemberRefToHash( + mdMemberRef mr) // Token of new guy. +{ + HRESULT hr = S_OK; + + // If the hash exists, we will add to it - requires write-lock + INDEBUG(Debug_CheckIsLockedForWrite();) + + // If the hash table hasn't been built it, see if it should get faulted in. + if (m_pMemberRefHash == NULL) + { + IfFailGo(CreateMemberRefHash()); + } + else + { + MemberRefRec * pMemberRef; + IfFailGo(GetMemberRefRecord(RidFromToken(mr), &pMemberRef)); + + LPCSTR szMemberRefName; + IfFailGo(getNameOfMemberRef(pMemberRef, &szMemberRefName)); + ULONG iHash = HashMemberRef( + getClassOfMemberRef(pMemberRef), + szMemberRefName); + + TOKENHASHENTRY * pEntry = m_pMemberRefHash->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = TokenFromRid(RidFromToken(mr), mdtMemberRef); + } + +ErrExit: + return hr; +} // CMiniMdRW::AddMemberRefToHash + +//--------------------------------------------------------------------------------------- +// +// If the hash is built, search for the item. Ignore token *ptkMemberRef. +// +CMiniMdRW::HashSearchResult +CMiniMdRW::FindMemberRefFromHash( + mdToken tkParent, // Parent token. + LPCUTF8 szName, // Name of item. + PCCOR_SIGNATURE pvSigBlob, // Signature. + ULONG cbSigBlob, // Size of signature. + mdMemberRef * ptkMemberRef) // IN: Ignored token. OUT: Return if found. +{ + // If the table is there, look for the item in the chain of items. + if (m_pMemberRefHash != NULL) + { + TOKENHASHENTRY * p; + ULONG iHash; + int pos; + + // Hash the data. + iHash = HashMemberRef(tkParent, szName); + + // Go through every entry in the hash chain looking for ours. + for (p = m_pMemberRefHash->FindFirst(iHash, pos); + p != NULL; + p = m_pMemberRefHash->FindNext(pos)) + { + if ((CompareMemberRefs(p->tok, tkParent, szName, pvSigBlob, cbSigBlob) == S_OK) + && (*ptkMemberRef != p->tok)) + { + *ptkMemberRef = p->tok; + return Found; + } + } + + return NotFound; + } + else + { + return NoTable; + } +} // CMiniMdRW::FindMemberRefFromHash + +//***************************************************************************** +// Check a given mr token to see if this one is a match. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CompareMemberRefs( // S_OK match, S_FALSE no match. + mdMemberRef mr, // Token to check. + mdToken tkPar, // Parent token. + LPCUTF8 szNameUtf8, // Name of item. + PCCOR_SIGNATURE pvSigBlob, // Signature. + ULONG cbSigBlob) // Size of signature. +{ + HRESULT hr; + MemberRefRec *pMemberRef; + LPCUTF8 szNameUtf8Tmp; + PCCOR_SIGNATURE pvSigBlobTmp; + ULONG cbSigBlobTmp; + + IfFailRet(GetMemberRefRecord(RidFromToken(mr), &pMemberRef)); + if (!IsNilToken(tkPar)) + { + // If caller specifies the tkPar and tkPar doesn't match, + // try the next memberref. + // + if (tkPar != getClassOfMemberRef(pMemberRef)) + return S_FALSE; + } + + IfFailRet(getNameOfMemberRef(pMemberRef, &szNameUtf8Tmp)); + if (strcmp(szNameUtf8Tmp, szNameUtf8) == 0) + { + if (pvSigBlob == NULL) + { + return S_OK; + } + + // Name matched. Now check the signature if caller supplies signature + // + if ((cbSigBlob != 0) && (pvSigBlob != NULL)) + { + IfFailRet(getSignatureOfMemberRef(pMemberRef, &pvSigBlobTmp, &cbSigBlobTmp)); + if ((cbSigBlobTmp == cbSigBlob) && + (memcmp(pvSigBlob, pvSigBlobTmp, cbSigBlob) == 0)) + { + return S_OK; + } + } + } + return S_FALSE; +} // CMiniMdRW::CompareMemberRefs + + +//***************************************************************************** +// Add a new memberdef to the hash table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddMemberDefToHash( + mdToken tkMember, // Token of new guy. It can be MethodDef or FieldDef + mdToken tkParent) // Parent token. +{ + HRESULT hr = S_OK; + ULONG iHash; + MEMBERDEFHASHENTRY * pEntry; + + // If the hash exists, we will add to it - requires write-lock + INDEBUG(Debug_CheckIsLockedForWrite();) + + // If the hash table hasn't been built it, see if it should get faulted in. + if (m_pMemberDefHash == NULL) + { + IfFailGo(CreateMemberDefHash()); + } + else + { + LPCSTR szName; + if (TypeFromToken(tkMember) == mdtMethodDef) + { + MethodRec * pMethodRecord; + IfFailGo(GetMethodRecord(RidFromToken(tkMember), &pMethodRecord)); + IfFailGo(getNameOfMethod(pMethodRecord, &szName)); + } + else + { + _ASSERTE(TypeFromToken(tkMember) == mdtFieldDef); + FieldRec * pFieldRecord; + IfFailGo(GetFieldRecord(RidFromToken(tkMember), &pFieldRecord)); + IfFailGo(getNameOfField(pFieldRecord, &szName)); + } + + iHash = HashMemberDef(tkParent, szName); + + pEntry = m_pMemberDefHash->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = tkMember; + pEntry->tkParent = tkParent; + } + +ErrExit: + return hr; +} // CMiniMdRW::AddMemberDefToHash + + +//***************************************************************************** +// Create MemberDef Hash +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CreateMemberDefHash() +{ + HRESULT hr = S_OK; + ULONG iHash; + MEMBERDEFHASHENTRY * pEntry; + + // If the hash table hasn't been built it, see if it should get faulted in. + if (m_pMemberDefHash == NULL) + { + ULONG ridMethod = getCountMethods(); + ULONG ridField = getCountFields(); + ULONG iType; + ULONG ridStart; + ULONG ridEnd; + TypeDefRec * pRec; + MethodRec * pMethod; + FieldRec * pField; + + if ((ridMethod + ridField + 1) > INDEX_ROW_COUNT_THRESHOLD) + { + // Create a new hash. + NewHolder<CMemberDefHash> pMemberDefHash = new (nothrow) CMemberDefHash(); + IfNullGo(pMemberDefHash); + IfFailGo(pMemberDefHash->NewInit( + g_HashSize[GetMetaDataSizeIndex(&m_OptionValue)])); + + for (iType = 1; iType <= getCountTypeDefs(); iType++) + { + IfFailGo(GetTypeDefRecord(iType, &pRec)); + ridStart = getMethodListOfTypeDef(pRec); + IfFailGo(getEndMethodListOfTypeDef(iType, &ridEnd)); + + // add all of the methods of this typedef into hash table + for (; ridStart < ridEnd; ridStart++) + { + RID methodRid; + IfFailGo(GetMethodRid(ridStart, &methodRid)); + IfFailGo(GetMethodRecord(methodRid, &pMethod)); + LPCSTR szMethodName; + IfFailGo(getNameOfMethod(pMethod, &szMethodName)); + iHash = HashMemberDef(TokenFromRid(iType, mdtTypeDef), szMethodName); + + pEntry = pMemberDefHash->Add(iHash); + if (pEntry == NULL) + IfFailGo(OutOfMemory()); + pEntry->tok = TokenFromRid(methodRid, mdtMethodDef); + pEntry->tkParent = TokenFromRid(iType, mdtTypeDef); + } + + // add all of the fields of this typedef into hash table + ridStart = getFieldListOfTypeDef(pRec); + IfFailGo(getEndFieldListOfTypeDef(iType, &ridEnd)); + + // Scan every entry already in the Method table, add it to the hash. + for (; ridStart < ridEnd; ridStart++) + { + RID fieldRid; + IfFailGo(GetFieldRid(ridStart, &fieldRid)); + IfFailGo(GetFieldRecord(fieldRid, &pField)); + LPCSTR szFieldName; + IfFailGo(getNameOfField(pField, &szFieldName)); + iHash = HashMemberDef(TokenFromRid(iType, mdtTypeDef), szFieldName); + + pEntry = pMemberDefHash->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = TokenFromRid(fieldRid, mdtFieldDef); + pEntry->tkParent = TokenFromRid(iType, mdtTypeDef); + } + } + + if (InterlockedCompareExchangeT<CMemberDefHash *>(&m_pMemberDefHash, pMemberDefHash, NULL) == NULL) + { // We won the initialization race + pMemberDefHash.SuppressRelease(); + } + } + } +ErrExit: + return hr; +} // CMiniMdRW::CreateMemberDefHash + +//--------------------------------------------------------------------------------------- +// +// If the hash is built, search for the item. Ignore token *ptkMember. +// +CMiniMdRW::HashSearchResult +CMiniMdRW::FindMemberDefFromHash( + mdToken tkParent, // Parent token. + LPCUTF8 szName, // Name of item. + PCCOR_SIGNATURE pvSigBlob, // Signature. + ULONG cbSigBlob, // Size of signature. + mdToken * ptkMember) // IN: Ignored token. OUT: Return if found. It can be MethodDef or FieldDef +{ + // check to see if we need to create hash table + if (m_pMemberDefHash == NULL) + { + // Ignore the failure - the hash won't be created in the worst case + (void)CreateMemberDefHash(); + } + + // If the table is there, look for the item in the chain of items. + if (m_pMemberDefHash != NULL) + { + MEMBERDEFHASHENTRY * pEntry; + ULONG iHash; + int pos; + + // Hash the data. + iHash = HashMemberDef(tkParent, szName); + + // Go through every entry in the hash chain looking for ours. + for (pEntry = m_pMemberDefHash->FindFirst(iHash, pos); + pEntry != NULL; + pEntry = m_pMemberDefHash->FindNext(pos)) + { + if ((CompareMemberDefs(pEntry->tok, pEntry->tkParent, tkParent, szName, pvSigBlob, cbSigBlob) == S_OK) + && (pEntry->tok != *ptkMember)) + { + *ptkMember = pEntry->tok; + return Found; + } + } + + return NotFound; + } + else + { + return NoTable; + } +} // CMiniMdRW::FindMemberDefFromHash + + +//***************************************************************************** +// Check a given memberDef token to see if this one is a match. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CompareMemberDefs( // S_OK match, S_FALSE no match. + mdToken tkMember, // Token to check. It can be MethodDef or FieldDef + mdToken tkParent, // Parent token recorded in the hash entry + mdToken tkPar, // Parent token. + LPCUTF8 szNameUtf8, // Name of item. + PCCOR_SIGNATURE pvSigBlob, // Signature. + ULONG cbSigBlob) // Size of signature. +{ + HRESULT hr; + MethodRec *pMethod; + FieldRec *pField; + LPCUTF8 szNameUtf8Tmp; + PCCOR_SIGNATURE pvSigBlobTmp; + ULONG cbSigBlobTmp; + bool bPrivateScope; + + if (TypeFromToken(tkMember) == mdtMethodDef) + { + IfFailGo(GetMethodRecord(RidFromToken(tkMember), &pMethod)); + IfFailGo(getNameOfMethod(pMethod, &szNameUtf8Tmp)); + IfFailGo(getSignatureOfMethod(pMethod, &pvSigBlobTmp, &cbSigBlobTmp)); + bPrivateScope = IsMdPrivateScope(getFlagsOfMethod(pMethod)); + } + else + { + _ASSERTE(TypeFromToken(tkMember) == mdtFieldDef); + IfFailGo(GetFieldRecord(RidFromToken(tkMember), &pField)); + IfFailGo(getNameOfField(pField, &szNameUtf8Tmp)); + IfFailGo(getSignatureOfField(pField, &pvSigBlobTmp, &cbSigBlobTmp)); + bPrivateScope = IsFdPrivateScope(getFlagsOfField(pField)); + } + if (bPrivateScope || (tkPar != tkParent)) + { + return S_FALSE; + } + + if (strcmp(szNameUtf8Tmp, szNameUtf8) == 0) + { + if (pvSigBlob == NULL) + { + return S_OK; + } + + // Name matched. Now check the signature if caller supplies signature + // + if ((cbSigBlob != 0) && (pvSigBlob != NULL)) + { + if ((cbSigBlobTmp == cbSigBlob) && + (memcmp(pvSigBlob, pvSigBlobTmp, cbSigBlob) == 0)) + { + return S_OK; + } + } + } + hr = S_FALSE; +ErrExit: + return hr; +} // CMiniMdRW::CompareMemberDefs + +//***************************************************************************** +// Add a new NamedItem to the hash table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddNamedItemToHash( + ULONG ixTbl, // Table with the new item. + mdToken tk, // Token of new guy. + LPCUTF8 szName, // Name of item. + mdToken tkParent) // Token of parent, if any. +{ + HRESULT hr = S_OK; + BYTE *pNamedItem; // A named item record. + LPCUTF8 szItem; // Name of the item. + mdToken tkPar = 0; // Parent token of the item. + ULONG iHash; // A named item's hash value. + TOKENHASHENTRY *pEntry; // New hash entry. + + // If the hash table hasn't been built it, see if it should get faulted in. + if (m_pNamedItemHash == NULL) + { + ULONG ridEnd = GetCountRecs(ixTbl); + // Range check avoiding prefast warning with: "if (ridEnd + 1 > INDEX_ROW_COUNT_THRESHOLD)" + if (ridEnd > (INDEX_ROW_COUNT_THRESHOLD - 1)) + { + // This assert causes Dev11 #65887, turn it on when the bug is fixed + //INDEBUG(Debug_CheckIsLockedForWrite();) + + // OutputDebugStringA("Creating TypeRef hash\n"); + // Create a new hash. + m_pNamedItemHash = new (nothrow) CMetaDataHashBase; + IfNullGo(m_pNamedItemHash); + IfFailGo(m_pNamedItemHash->NewInit( + g_HashSize[GetMetaDataSizeIndex(&m_OptionValue)])); + + // Scan every entry already in the table, add it to the hash. + for (ULONG index = 1; index <= ridEnd; index++) + { + IfFailGo(m_Tables[ixTbl].GetRecord(index, &pNamedItem)); + IfFailGo(getString(GetCol(ixTbl, g_TblIndex[ixTbl].m_iName, pNamedItem), &szItem)); + if (g_TblIndex[ixTbl].m_iParent != (ULONG) -1) + tkPar = GetToken(ixTbl, g_TblIndex[ixTbl].m_iParent, pNamedItem); + + iHash = HashNamedItem(tkPar, szItem); + + pEntry = m_pNamedItemHash->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = TokenFromRid(index, g_TblIndex[ixTbl].m_Token); + } + } + } + else + { + tk = RidFromToken(tk); + IfFailGo(m_Tables[ixTbl].GetRecord(tk, &pNamedItem)); + IfFailGo(getString(GetCol(ixTbl, g_TblIndex[ixTbl].m_iName, pNamedItem), &szItem)); + if (g_TblIndex[ixTbl].m_iParent != (ULONG)-1) + tkPar = GetToken(ixTbl, g_TblIndex[ixTbl].m_iParent, pNamedItem); + + iHash = HashNamedItem(tkPar, szItem); + + pEntry = m_pNamedItemHash->Add(iHash); + IfNullGo(pEntry); + pEntry->tok = TokenFromRid(tk, g_TblIndex[ixTbl].m_Token); + } + +ErrExit: + return hr; +} // CMiniMdRW::AddNamedItemToHash + +//***************************************************************************** +// If the hash is built, search for the item. +//***************************************************************************** +CMiniMdRW::HashSearchResult +CMiniMdRW::FindNamedItemFromHash( + ULONG ixTbl, // Table with the item. + LPCUTF8 szName, // Name of item. + mdToken tkParent, // Token of parent, if any. + mdToken * ptk) // Return if found. +{ + // If the table is there, look for the item in the chain of items. + if (m_pNamedItemHash != NULL) + { + TOKENHASHENTRY *p; // Hash entry from chain. + ULONG iHash; // Item's hash value. + int pos; // Position in hash chain. + mdToken type; // Type of the item being sought. + + type = g_TblIndex[ixTbl].m_Token; + + // Hash the data. + iHash = HashNamedItem(tkParent, szName); + + // Go through every entry in the hash chain looking for ours. + for (p = m_pNamedItemHash->FindFirst(iHash, pos); + p != NULL; + p = m_pNamedItemHash->FindNext(pos)) + { // Check that the item is from the right table. + if (TypeFromToken(p->tok) != (ULONG)type) + { + //<TODO>@FUTURE: if using the named item hash for multiple tables, remove + // this check. Until then, debugging aid.</TODO> + _ASSERTE(!"Table mismatch in hash chain"); + continue; + } + // Item is in the right table, do the deeper check. + if (CompareNamedItems(ixTbl, p->tok, szName, tkParent) == S_OK) + { + *ptk = p->tok; + return Found; + } + } + + return NotFound; + } + else + { + return NoTable; + } +} // CMiniMdRW::FindNamedItemFromHash + +//***************************************************************************** +// Check a given mr token to see if this one is a match. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::CompareNamedItems( // S_OK match, S_FALSE no match. + ULONG ixTbl, // Table with the item. + mdToken tk, // Token to check. + LPCUTF8 szName, // Name of item. + mdToken tkParent) // Token of parent, if any. +{ + HRESULT hr; + BYTE *pNamedItem; // Item to check. + LPCUTF8 szNameUtf8Tmp; // Name of item to check. + + // Get the record. + IfFailRet(m_Tables[ixTbl].GetRecord(RidFromToken(tk), &pNamedItem)); + + // Name is cheaper to get than coded token parent, and fails pretty quickly. + IfFailRet(getString(GetCol(ixTbl, g_TblIndex[ixTbl].m_iName, pNamedItem), &szNameUtf8Tmp)); + if (strcmp(szNameUtf8Tmp, szName) != 0) + return S_FALSE; + + // Name matched, try parent, if any. + if (g_TblIndex[ixTbl].m_iParent != (ULONG)-1) + { + mdToken tkPar = GetToken(ixTbl, g_TblIndex[ixTbl].m_iParent, pNamedItem); + if (tkPar != tkParent) + return S_FALSE; + } + + // Made it to here, so everything matched. + return S_OK; +} // CMiniMdRW::CompareNamedItems + +//***************************************************************************** +// Add <md, td> entry to the MethodDef map look up table +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddMethodToLookUpTable( + mdMethodDef md, + mdTypeDef td) +{ + HRESULT hr = NOERROR; + mdToken *ptk; + _ASSERTE((TypeFromToken(md) == mdtMethodDef) && HasIndirectTable(TBL_Method)); + + if (m_pMethodMap != NULL) + { + // Only add to the lookup table if it has been built already by demand. + // + // The first entry in the map is a dummy entry. + // The i'th index entry of the map is the td for methoddef of i. + // We do expect the methoddef tokens are all added when the map exist. + // + _ASSERTE(RidFromToken(md) == (ULONG)m_pMethodMap->Count()); + INDEBUG(Debug_CheckIsLockedForWrite();) + ptk = m_pMethodMap->Append(); + IfNullGo(ptk); + *ptk = td; + } +ErrExit: + return hr; +} // CMiniMdRW::AddMethodToLookUpTable + +//***************************************************************************** +// Add <fd, td> entry to the FieldDef map look up table +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddFieldToLookUpTable( + mdFieldDef fd, + mdTypeDef td) +{ + HRESULT hr = NOERROR; + mdToken *ptk; + _ASSERTE((TypeFromToken(fd) == mdtFieldDef) && HasIndirectTable(TBL_Field)); + if (m_pFieldMap != NULL) + { + // Only add to the lookup table if it has been built already by demand. + // + // The first entry in the map is a dummy entry. + // The i'th index entry of the map is the td for fielddef of i. + // We do expect the fielddef tokens are all added when the map exist. + // + _ASSERTE(RidFromToken(fd) == (ULONG)m_pFieldMap->Count()); + ptk = m_pFieldMap->Append(); + IfNullGo(ptk); + *ptk = td; + } + +ErrExit: + return hr; +} // CMiniMdRW::AddFieldToLookUpTable + +//***************************************************************************** +// Add <pr, td> entry to the Property map look up table +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddPropertyToLookUpTable( + mdProperty pr, + mdTypeDef td) +{ + HRESULT hr = NOERROR; + mdToken *ptk; + _ASSERTE((TypeFromToken(pr) == mdtProperty) && HasIndirectTable(TBL_Property)); + + if (m_pPropertyMap != NULL) + { + // Only add to the lookup table if it has been built already by demand. + // + // The first entry in the map is a dummy entry. + // The i'th index entry of the map is the td for property of i. + // We do expect the property tokens are all added when the map exist. + // + _ASSERTE(RidFromToken(pr) == (ULONG)m_pPropertyMap->Count()); + ptk = m_pPropertyMap->Append(); + IfNullGo(ptk); + *ptk = td; + } +ErrExit: + return hr; +} // CMiniMdRW::AddPropertyToLookUpTable + +//***************************************************************************** +// Add <ev, td> entry to the Event map look up table +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddEventToLookUpTable( + mdEvent ev, + mdTypeDef td) +{ + HRESULT hr = NOERROR; + mdToken *ptk; + _ASSERTE((TypeFromToken(ev) == mdtEvent) && HasIndirectTable(TBL_Event)); + + if (m_pEventMap != NULL) + { + // Only add to the lookup table if it has been built already by demand. + // + // now add to the EventMap table + _ASSERTE(RidFromToken(ev) == (ULONG)m_pEventMap->Count()); + ptk = m_pEventMap->Append(); + IfNullGo(ptk); + *ptk = td; + } +ErrExit: + return hr; +} // CMiniMdRW::AddEventToLookUpTable + +//***************************************************************************** +// Add <pd, md> entry to the Param map look up table +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::AddParamToLookUpTable( + mdParamDef pd, + mdMethodDef md) +{ + HRESULT hr = NOERROR; + mdToken *ptk; + _ASSERTE((TypeFromToken(pd) == mdtParamDef) && HasIndirectTable(TBL_Param)); + + if (m_pParamMap != NULL) + { + // Only add to the lookup table if it has been built already by demand. + // + // now add to the EventMap table + _ASSERTE(RidFromToken(pd) == (ULONG)m_pParamMap->Count()); + ptk = m_pParamMap->Append(); + IfNullGo(ptk); + *ptk = md; + } +ErrExit: + return hr; +} // CMiniMdRW::AddParamToLookUpTable + +//***************************************************************************** +// Find parent for a method token. This will use the lookup table if there is an +// intermediate table. Or it will use FindMethodOfParent helper +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindParentOfMethodHelper( + mdMethodDef md, // [IN] the methoddef token + mdTypeDef *ptd) // [OUT] the parent token +{ + HRESULT hr = NOERROR; + if (HasIndirectTable(TBL_Method)) + { + if (m_pMethodMap == NULL) + { + ULONG indexTd; + ULONG indexMd; + ULONG ridStart; + ULONG ridEnd; + TypeDefRec * pTypeDefRec; + MethodPtrRec * pMethodPtrRec; + + // build the MethodMap table + NewHolder<TOKENMAP> pMethodMap = new (nothrow) TOKENMAP; + IfNullGo(pMethodMap); + ULONG nAllocateSize; + if (!ClrSafeInt<ULONG>::addition(m_Schema.m_cRecs[TBL_Method], 1, nAllocateSize)) + { + IfFailGo(COR_E_OVERFLOW); + } + if (pMethodMap->AllocateBlock(nAllocateSize) == 0) + IfFailGo(E_OUTOFMEMORY); + for (indexTd = 1; indexTd <= m_Schema.m_cRecs[TBL_TypeDef]; indexTd++) + { + IfFailGo(GetTypeDefRecord(indexTd, &pTypeDefRec)); + ridStart = getMethodListOfTypeDef(pTypeDefRec); + IfFailGo(getEndMethodListOfTypeDef(indexTd, &ridEnd)); + + for (indexMd = ridStart; indexMd < ridEnd; indexMd++) + { + IfFailGo(GetMethodPtrRecord(indexMd, &pMethodPtrRec)); + PREFIX_ASSUME(pMethodMap->Get(getMethodOfMethodPtr(pMethodPtrRec)) != NULL); + *(pMethodMap->Get(getMethodOfMethodPtr(pMethodPtrRec))) = indexTd; + } + } + if (InterlockedCompareExchangeT<TOKENMAP *>( + &m_pMethodMap, + pMethodMap, + NULL) == NULL) + { // We won the initializaion race + pMethodMap.SuppressRelease(); + } + } + *ptd = *(m_pMethodMap->Get(RidFromToken(md))); + } + else + { + IfFailGo(FindParentOfMethod(RidFromToken(md), (RID *)ptd)); + } + RidToToken(*ptd, mdtTypeDef); +ErrExit: + return hr; +} // CMiniMdRW::FindParentOfMethodHelper + +//***************************************************************************** +// Find parent for a field token. This will use the lookup table if there is an +// intermediate table. Or it will use FindFieldOfParent helper +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindParentOfFieldHelper( + mdFieldDef fd, // [IN] fielddef token + mdTypeDef *ptd) // [OUT] parent token +{ + HRESULT hr = NOERROR; + if (HasIndirectTable(TBL_Field)) + { + if (m_pFieldMap == NULL) + { + ULONG indexTd; + ULONG indexFd; + ULONG ridStart, ridEnd; + TypeDefRec *pTypeDefRec; + FieldPtrRec *pFieldPtrRec; + + // build the FieldMap table + NewHolder<TOKENMAP> pFieldMap = new (nothrow) TOKENMAP; + IfNullGo(pFieldMap); + ULONG nAllocateSize; + if (!ClrSafeInt<ULONG>::addition(m_Schema.m_cRecs[TBL_Field], 1, nAllocateSize)) + { + IfFailGo(COR_E_OVERFLOW); + } + if (pFieldMap->AllocateBlock(nAllocateSize) == 0) + IfFailGo(E_OUTOFMEMORY); + for (indexTd = 1; indexTd<= m_Schema.m_cRecs[TBL_TypeDef]; indexTd++) + { + IfFailGo(GetTypeDefRecord(indexTd, &pTypeDefRec)); + ridStart = getFieldListOfTypeDef(pTypeDefRec); + IfFailGo(getEndFieldListOfTypeDef(indexTd, &ridEnd)); + + for (indexFd = ridStart; indexFd < ridEnd; indexFd++) + { + IfFailGo(GetFieldPtrRecord(indexFd, &pFieldPtrRec)); + PREFIX_ASSUME(pFieldMap->Get(getFieldOfFieldPtr(pFieldPtrRec)) != NULL); + *(pFieldMap->Get(getFieldOfFieldPtr(pFieldPtrRec))) = indexTd; + } + } + if (InterlockedCompareExchangeT<TOKENMAP *>( + &m_pFieldMap, + pFieldMap, + NULL) == NULL) + { // We won the initializaion race + pFieldMap.SuppressRelease(); + } + } + *ptd = *(m_pFieldMap->Get(RidFromToken(fd))); + } + else + { + IfFailGo(FindParentOfField(RidFromToken(fd), (RID *)ptd)); + } + RidToToken(*ptd, mdtTypeDef); +ErrExit: + return hr; +} // CMiniMdRW::FindParentOfFieldHelper + +//***************************************************************************** +// Find parent for a property token. This will use the lookup table if there is an +// intermediate table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindParentOfPropertyHelper( + mdProperty pr, + mdTypeDef *ptd) +{ + HRESULT hr = NOERROR; + if (HasIndirectTable(TBL_Property)) + { + if (m_pPropertyMap == NULL) + { + ULONG indexMap; + ULONG indexPr; + ULONG ridStart, ridEnd; + PropertyMapRec *pPropertyMapRec; + PropertyPtrRec *pPropertyPtrRec; + + // build the PropertyMap table + NewHolder<TOKENMAP> pPropertyMap = new (nothrow) TOKENMAP; + IfNullGo(pPropertyMap); + ULONG nAllocateSize; + if (!ClrSafeInt<ULONG>::addition(m_Schema.m_cRecs[TBL_Property], 1, nAllocateSize)) + { + IfFailGo(COR_E_OVERFLOW); + } + if (pPropertyMap->AllocateBlock(nAllocateSize) == 0) + IfFailGo( E_OUTOFMEMORY ); + for (indexMap = 1; indexMap<= m_Schema.m_cRecs[TBL_PropertyMap]; indexMap++) + { + IfFailGo(GetPropertyMapRecord(indexMap, &pPropertyMapRec)); + ridStart = getPropertyListOfPropertyMap(pPropertyMapRec); + IfFailGo(getEndPropertyListOfPropertyMap(indexMap, &ridEnd)); + + for (indexPr = ridStart; indexPr < ridEnd; indexPr++) + { + IfFailGo(GetPropertyPtrRecord(indexPr, &pPropertyPtrRec)); + mdToken *tok = pPropertyMap->Get(getPropertyOfPropertyPtr(pPropertyPtrRec)); + PREFIX_ASSUME(tok != NULL); + *tok = getParentOfPropertyMap(pPropertyMapRec); + } + } + if (InterlockedCompareExchangeT<TOKENMAP *>( + &m_pPropertyMap, + pPropertyMap, + NULL) == NULL) + { // We won the initializaion race + pPropertyMap.SuppressRelease(); + } + } + *ptd = *(m_pPropertyMap->Get(RidFromToken(pr))); + } + else + { + RID ridPropertyMap; + PropertyMapRec *pRec; + + IfFailGo(FindPropertyMapParentOfProperty(RidFromToken(pr), &ridPropertyMap)); + IfFailGo(GetPropertyMapRecord(ridPropertyMap, &pRec)); + *ptd = getParentOfPropertyMap(pRec); + } + RidToToken(*ptd, mdtTypeDef); +ErrExit: + return hr; +} // CMiniMdRW::FindParentOfPropertyHelper + +//***************************************************************************** +// Find parent for an Event token. This will use the lookup table if there is an +// intermediate table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindParentOfEventHelper( + mdEvent ev, + mdTypeDef *ptd) +{ + HRESULT hr = NOERROR; + if (HasIndirectTable(TBL_Event)) + { + if (m_pEventMap == NULL) + { + ULONG indexMap; + ULONG indexEv; + ULONG ridStart, ridEnd; + EventMapRec *pEventMapRec; + EventPtrRec *pEventPtrRec; + + // build the EventMap table + NewHolder<TOKENMAP> pEventMap = new (nothrow) TOKENMAP; + IfNullGo(pEventMap); + ULONG nAllocateSize; + if (!ClrSafeInt<ULONG>::addition(m_Schema.m_cRecs[TBL_Event], 1, nAllocateSize)) + { + IfFailGo(COR_E_OVERFLOW); + } + if (pEventMap->AllocateBlock(nAllocateSize) == 0) + IfFailGo(E_OUTOFMEMORY); + for (indexMap = 1; indexMap<= m_Schema.m_cRecs[TBL_EventMap]; indexMap++) + { + IfFailGo(GetEventMapRecord(indexMap, &pEventMapRec)); + ridStart = getEventListOfEventMap(pEventMapRec); + IfFailGo(getEndEventListOfEventMap(indexMap, &ridEnd)); + + for (indexEv = ridStart; indexEv < ridEnd; indexEv++) + { + IfFailGo(GetEventPtrRecord(indexEv, &pEventPtrRec)); + mdToken* tok = pEventMap->Get(getEventOfEventPtr(pEventPtrRec)); + PREFIX_ASSUME(tok != NULL); + *tok = getParentOfEventMap(pEventMapRec); + } + } + if (InterlockedCompareExchangeT<TOKENMAP *>( + &m_pEventMap, + pEventMap, + NULL) == NULL) + { // We won the initializaion race + pEventMap.SuppressRelease(); + } + } + *ptd = *(m_pEventMap->Get(RidFromToken(ev))); + } + else + { + RID ridEventMap; + EventMapRec *pRec; + + IfFailGo(FindEventMapParentOfEvent(RidFromToken(ev), &ridEventMap)); + IfFailGo(GetEventMapRecord(ridEventMap, &pRec)); + *ptd = getParentOfEventMap(pRec); + } + RidToToken(*ptd, mdtTypeDef); +ErrExit: + return hr; +} // CMiniMdRW::FindParentOfEventHelper + +//***************************************************************************** +// Find parent for a ParamDef token. This will use the lookup table if there is an +// intermediate table. +//***************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::FindParentOfParamHelper( + mdParamDef pd, + mdMethodDef *pmd) +{ + HRESULT hr = NOERROR; + if (HasIndirectTable(TBL_Param)) + { + if (m_pParamMap == NULL) + { + ULONG indexMd; + ULONG indexPd; + ULONG ridStart, ridEnd; + MethodRec *pMethodRec; + ParamPtrRec *pParamPtrRec; + + // build the ParamMap table + NewHolder<TOKENMAP> pParamMap = new (nothrow) TOKENMAP; + IfNullGo(pParamMap); + ULONG nAllocateSize; + if (!ClrSafeInt<ULONG>::addition(m_Schema.m_cRecs[TBL_Param], 1, nAllocateSize)) + { + IfFailGo(COR_E_OVERFLOW); + } + if (pParamMap->AllocateBlock(nAllocateSize) == 0) + IfFailGo(E_OUTOFMEMORY); + for (indexMd = 1; indexMd<= m_Schema.m_cRecs[TBL_Method]; indexMd++) + { + IfFailGo(GetMethodRecord(indexMd, &pMethodRec)); + ridStart = getParamListOfMethod(pMethodRec); + IfFailGo(getEndParamListOfMethod(indexMd, &ridEnd)); + + for (indexPd = ridStart; indexPd < ridEnd; indexPd++) + { + IfFailGo(GetParamPtrRecord(indexPd, &pParamPtrRec)); + PREFIX_ASSUME(pParamMap->Get(getParamOfParamPtr(pParamPtrRec)) != NULL); + *(pParamMap->Get(getParamOfParamPtr(pParamPtrRec))) = indexMd; + } + } + if (InterlockedCompareExchangeT<TOKENMAP *>( + &m_pParamMap, + pParamMap, + NULL) == NULL) + { // We won the initializaion race + pParamMap.SuppressRelease(); + } + } + *pmd = *(m_pParamMap->Get(RidFromToken(pd))); + } + else + { + IfFailGo(FindParentOfParam(RidFromToken(pd), (RID *)pmd)); + } + RidToToken(*pmd, mdtMethodDef); +ErrExit: + return hr; +} // CMiniMdRW::FindParentOfParamHelper + + +//****************************************************************************** +// Add an entry in the ENC Log table. +//****************************************************************************** +__checkReturn +HRESULT +CMiniMdRW::UpdateENCLogHelper( + mdToken tk, // Token to be added to the ENCLog table. + CMiniMdRW::eDeltaFuncs funccode) // Specifies the optional function code.. +{ + ENCLogRec *pRecord; + RID iRecord; + HRESULT hr = S_OK; + + // @todo - MD can't handle anything other than functions right now + /* if (TypeFromToken(tk) != mdtMethodDef) + { + _ASSERTE(!"Trying to do something that we can't do"); + return S_OK; + } + */ + IfFailGo(AddENCLogRecord(&pRecord, &iRecord)); + pRecord->SetToken(tk); + pRecord->SetFuncCode(funccode); + +ErrExit: + return hr; +} // CMiniMdRW::UpdateENCLogHelper + +__checkReturn +HRESULT +CMiniMdRW::UpdateENCLogHelper2( + ULONG ixTbl, // Table being updated. + ULONG iRid, // Record within table. + CMiniMdRW::eDeltaFuncs funccode) // Specifies the optional function code.. +{ + ENCLogRec *pRecord; + RID iRecord; + HRESULT hr = S_OK; + + IfFailGo(AddENCLogRecord(&pRecord, &iRecord)); + pRecord->SetToken(RecIdFromRid(iRid, ixTbl)); + pRecord->SetFuncCode(funccode); + +ErrExit: + return hr; +} // CMiniMdRW::UpdateENCLogHelper2 + +__checkReturn +HRESULT +CMiniMdRW::ResetENCLog() +{ +#ifdef FEATURE_METADATA_EMIT + HRESULT hr = S_OK; + ModuleRec * pMod; + + // Get the module record. + IfFailGo(GetModuleRecord(1, &pMod)); + +#ifndef FEATURE_CORECLR + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MD_UseMinimalDeltas)) + { // Update the ENC Guids + GUID encid; + // Copy EncId as BaseId. + ULONG uVal = GetCol(TBL_Module, ModuleRec::COL_EncId, pMod); + IfFailGo(PutCol(TBL_Module, ModuleRec::COL_EncBaseId, pMod, uVal)); + + // Allocate a new GUID for EncId. + IfFailGo(CoCreateGuid(&encid)); + IfFailGo(PutGuid(TBL_Module, ModuleRec::COL_EncId, pMod, encid)); + } +#endif //!FEATURE_CORECLR + + // Reset the pool deltas + m_StringHeap.StartNewEnCSession(); + m_BlobHeap.StartNewEnCSession(); + m_UserStringHeap.StartNewEnCSession(); + + // Clear the ENCLog + m_Tables[TBL_ENCLog].Delete(); + m_Schema.m_cRecs[TBL_ENCLog] = 0; + +ErrExit: + return hr; +#else //!FEATURE_METADATA_EMIT + return S_OK; +#endif //!FEATURE_METADATA_EMIT +} // CMiniMdRW::ResetENCLog + +// ---------------------------------------------------------------------------- +// Workaround for compiler performance issue VSW 584653 for 2.0 RTM. +// Get the table's VirtualSort validity state. +bool +CMiniMdRW::IsTableVirtualSorted(ULONG ixTbl) +{ + _ASSERTE(ixTbl < m_TblCount); + + if (m_pVS[ixTbl] == NULL) + { + return false; + } + return m_pVS[ixTbl]->m_isMapValid; +} // CMiniMdRW::IsTableVirtualSorted + +// ---------------------------------------------------------------------------- +// Workaround for compiler performance issue VSW 584653 for 2.0 RTM. +// +// Validate table's VirtualSort after adding one record into the table. +// Returns new VirtualSort validity state in *pfIsTableVirtualSortValid. +// Assumptions: +// Table's VirtualSort was valid before adding the record to the table. +// The caller must ensure validity of VirtualSort by calling to +// IsTableVirtualSorted or by using the returned state from previous +// call to this method. +__checkReturn +HRESULT +CMiniMdRW::ValidateVirtualSortAfterAddRecord( + ULONG ixTbl, + bool *pfIsTableVirtualSortValid) +{ + _ASSERTE(ixTbl < m_TblCount); + + HRESULT hr; + VirtualSort *pVS = m_pVS[ixTbl]; + + // VirtualSort was valid (had to exist) + _ASSERTE(pVS != NULL); + // Adding record invalidated VirtualSort + _ASSERTE(!pVS->m_isMapValid); + // Only 1 record was added into table (VirtualSort has 1 bogus element) + _ASSERTE(m_Schema.m_cRecs[ixTbl] == (ULONG)pVS->m_pMap->Count()); + + // Append 1 element into VirtualSort + mdToken *pAddedVSToken = pVS->m_pMap->Append(); + if (pAddedVSToken == NULL) + { // There's not enough memory + // Do not handle OOM now, just leave the VirtualSort invalidated, the + // next allocation will take care of OOM or the VirtualSort will be + // resorted when needed (as it was before this performance workaround) + *pfIsTableVirtualSortValid = false; + return S_OK; + } + + // Initialize added element + int iLastElementIndex = pVS->m_pMap->Count() - 1; + *pAddedVSToken = iLastElementIndex; + // Check if the added element extends the VirtualSort (keeps sorting) + if (iLastElementIndex > 2) + { + int nCompareResult; + IfFailRet(pVS->Compare( + iLastElementIndex - 1, + iLastElementIndex, + &nCompareResult)); + if (nCompareResult < 0) + { // VirtualSort was extended - the added element is bigger than + // previously last element in VirtualSort + + // Validate VirtualSort as it is still sorted and covers all elements + // of the MetaData table + pVS->m_isMapValid = true; + *pfIsTableVirtualSortValid = true; + return S_OK; + } + } + // The added element doesn't extend VirtualSort - it is not sorted + + // Keep the VirtualSort invalidated, therefore next binary search will + // force its recreation and resorting (as it did before this performance + // workaround) + *pfIsTableVirtualSortValid = false; + return S_OK; +} // CMiniMdRW::ValidateVirtualSortAfterAddRecord + +#ifdef _DEBUG + +// ---------------------------------------------------------------------------- +void +CMiniMdRW::Debug_CheckIsLockedForWrite() +{ + // If this assert fires, then we are trying to modify MetaData that is not locked for write + _ASSERTE((dbg_m_pLock == NULL) || dbg_m_pLock->Debug_IsLockedForWrite()); +} + +#endif //_DEBUG + +//***************************************************************************** +// +// Sort the whole RID table +// +//***************************************************************************** +__checkReturn +HRESULT +VirtualSort::Sort() +{ + m_isMapValid = true; + // Note that m_pMap stores an additional bogus element at count 0. This is + // just so we can align the index in m_pMap with the Rids which are 1 based. + return SortRange(1, m_pMap->Count() - 1); +} // VirtualSort::Sort + +//***************************************************************************** +// +// Sort the range from iLeft to iRight +// +//***************************************************************************** +__checkReturn +HRESULT +VirtualSort::SortRange( + int iLeft, + int iRight) +{ + HRESULT hr; + int iLast; + + for (;;) + { + // if less than two elements you're done. + if (iLeft >= iRight) + { + return S_OK; + } + + // The mid-element is the pivot, move it to the left. + Swap(iLeft, (iLeft+iRight)/2); + iLast = iLeft; + + // move everything that is smaller than the pivot to the left. + for (int i = iLeft+1; i <= iRight; i++) + { + int nCompareResult; + IfFailRet(Compare(i, iLeft, &nCompareResult)); + if (nCompareResult < 0) + { + Swap(i, ++iLast); + } + } + + // Put the pivot to the point where it is in between smaller and larger elements. + Swap(iLeft, iLast); + + // Sort each partition. + int iLeftLast = iLast - 1; + int iRightFirst = iLast + 1; + if (iLeftLast - iLeft < iRight - iRightFirst) + { // Left partition is smaller, sort it recursively + IfFailRet(SortRange(iLeft, iLeftLast)); + // Tail call to sort the right (bigger) partition + iLeft = iRightFirst; + //iRight = iRight; + continue; + } + else + { // Right partition is smaller, sort it recursively + IfFailRet(SortRange(iRightFirst, iRight)); + // Tail call to sort the left (bigger) partition + //iLeft = iLeft; + iRight = iLeftLast; + continue; + } + } +} // VirtualSort::SortRange + +//***************************************************************************** +// +// Compare two RID base on the m_ixTbl's m_ixCol +// +//***************************************************************************** +__checkReturn +HRESULT +VirtualSort::Compare( + RID iLeft, // First item to compare. + RID iRight, // Second item to compare. + int *pnResult) // -1, 0, or 1 +{ + HRESULT hr; + RID ridLeft = *(m_pMap->Get(iLeft)); + RID ridRight = *(m_pMap->Get(iRight)); + void *pRow; // Row from a table. + ULONG valRight, valLeft; // Value from a row. + + IfFailRet(m_pMiniMd->getRow(m_ixTbl, ridLeft, &pRow)); + valLeft = m_pMiniMd->getIX(pRow, m_pMiniMd->m_TableDefs[m_ixTbl].m_pColDefs[m_ixCol]); + IfFailRet(m_pMiniMd->getRow(m_ixTbl, ridRight, &pRow)); + valRight = m_pMiniMd->getIX(pRow, m_pMiniMd->m_TableDefs[m_ixTbl].m_pColDefs[m_ixCol]); + + if (valLeft < valRight) + { + *pnResult = -1; + return S_OK; + } + if (valLeft > valRight) + { + *pnResult = 1; + return S_OK; + } + // Values are equal -- preserve existing ordering. + if (ridLeft < ridRight) + { + *pnResult = -1; + return S_OK; + } + if (ridLeft > ridRight) + { + *pnResult = 1; + return S_OK; + } + // Comparing an item to itself? + _ASSERTE(!"Comparing an item to itself in sort"); + + *pnResult = 0; + return S_OK; +} // VirtualSort::Compare + +//***************************************************************************** +// +// Initialization function +// +//***************************************************************************** +void VirtualSort::Init( // + ULONG ixTbl, // Table index. + ULONG ixCol, // Column index. + CMiniMdRW *pMiniMd) // MiniMD with data. +{ + m_pMap = NULL; + m_isMapValid = false; + m_ixTbl = ixTbl; + m_ixCol = ixCol; + m_pMiniMd = pMiniMd; +} // VirtualSort::Init + + +//***************************************************************************** +// +// Uninitialization function +// +//***************************************************************************** +void VirtualSort::Uninit() +{ + if ( m_pMap ) + delete m_pMap; + m_pMap = NULL; + m_isMapValid = false; +} // VirtualSort::Uninit + + +//***************************************************************************** +// +// Mark a token +// +//***************************************************************************** +HRESULT FilterTable::MarkToken( + mdToken tk, // token to be marked as to keep + DWORD bitToMark) // bit flag to set in the keep table +{ + HRESULT hr = NOERROR; + RID rid = RidFromToken(tk); + + if ( (Count() == 0) || ((RID)(Count() -1)) < rid ) + { + // grow table + IfFailGo( AllocateBlock( rid + 1 - Count() ) ); + } + +#ifdef _DEBUG + if ( (*Get(rid)) & bitToMark ) + { + // global TypeDef could be marked more than once so don't assert if token is mdtTypeDef + if (TypeFromToken(tk) != mdtTypeDef) + _ASSERTE(!"Token has been Marked"); + } +#endif //_DEBUG + + // set the keep bit + *Get(rid) = (*Get(rid)) | bitToMark; +ErrExit: + return hr; +} // FilterTable::MarkToken + + +//***************************************************************************** +// +// Unmark a token +// +//***************************************************************************** +HRESULT FilterTable::UnmarkToken( + mdToken tk, // token to be unmarked as deleted. + DWORD bitToMark) // bit flag to unset in the keep table +{ + RID rid = RidFromToken(tk); + + if ( (Count() == 0) || ((RID)(Count() -1)) < rid ) + { + // unmarking should not have grown table. It currently only support dropping the transient CAs. + _ASSERTE(!"BAD state!"); + } + +#ifdef _DEBUG + if ( (*Get(rid)) & bitToMark ) + { + // global TypeDef could be marked more than once so don't assert if token is mdtTypeDef + if (TypeFromToken(tk) != mdtTypeDef) + _ASSERTE(!"Token has been Marked"); + } +#endif //_DEBUG + + // unset the keep bit + *Get(rid) = (*Get(rid)) & ~bitToMark; + return NOERROR; +} // FilterTable::MarkToken + + +//***************************************************************************** +// +// Mark an UserString token +// +//***************************************************************************** +HRESULT FilterTable::MarkUserString( + mdString str) +{ + int high, low, mid; + + low = 0; + high = m_daUserStringMarker->Count() - 1; + while (low <= high) + { + mid = (high + low) / 2; + if ((m_daUserStringMarker->Get(mid))->m_tkString > (DWORD) str) + { + high = mid - 1; + } + else if ((m_daUserStringMarker->Get(mid))->m_tkString < (DWORD) str) + { + low = mid + 1; + } + else + { + (m_daUserStringMarker->Get(mid))->m_fMarked = true; + return NOERROR; + } + } + _ASSERTE(!"Bad Token!"); + return NOERROR; +} // FilterTable::MarkUserString + +//***************************************************************************** +// +// Mark a UserString token that was added since our last MarkAll/UnMarkAll +// +//***************************************************************************** +HRESULT FilterTable::MarkNewUserString(mdString str) +{ + FilterUserStringEntry *pItem = m_daUserStringMarker->Append(); + + if (pItem == NULL) + return E_OUTOFMEMORY; + + pItem->m_tkString = str; + pItem->m_fMarked = true; + + return S_OK; +} // FilterTable::MarkNewUserString + +//***************************************************************************** +// +// Unmarking from 1 to ulSize for all tokens. +// +//***************************************************************************** +HRESULT FilterTable::UnmarkAll( + CMiniMdRW *pMiniMd, + ULONG ulSize) +{ + HRESULT hr; + + S_UINT32 nAllocateSize = S_UINT32(ulSize) + S_UINT32(1); + if (nAllocateSize.IsOverflow()) + { + IfFailGo(COR_E_OVERFLOW); + } + if (!AllocateBlock(nAllocateSize.Value())) + { + IfFailGo(E_OUTOFMEMORY); + } + memset(Get(0), 0, nAllocateSize.Value() * sizeof(DWORD)); + + // unmark all of the user string + m_daUserStringMarker = new (nothrow) CDynArray<FilterUserStringEntry>(); + IfNullGo(m_daUserStringMarker); + + for (UINT32 nIndex = 0; ;) + { + MetaData::DataBlob userString; + UINT32 nNextIndex; + hr = pMiniMd->GetUserStringAndNextIndex( + nIndex, + &userString, + &nNextIndex); + IfFailGo(hr); + if (hr == S_FALSE) + { // We reached the last user string + hr = S_OK; + break; + } + _ASSERTE(hr == S_OK); + + // Skip empty strings + if (userString.IsEmpty()) + { + nIndex = nNextIndex; + continue; + } + FilterUserStringEntry *pItem = m_daUserStringMarker->Append(); + pItem->m_tkString = TokenFromRid(nIndex, mdtString); + pItem->m_fMarked = false; + + // Process next user string in the heap + nIndex = nNextIndex; + } + +ErrExit: + return hr; +} // FilterTable::UnmarkAll + + + +//***************************************************************************** +// +// Marking from 1 to ulSize for all tokens. +// +//***************************************************************************** +HRESULT FilterTable::MarkAll( + CMiniMdRW *pMiniMd, + ULONG ulSize) +{ + HRESULT hr = S_OK; + + S_UINT32 nAllocateSize = S_UINT32(ulSize) + S_UINT32(1); + if (nAllocateSize.IsOverflow()) + { + IfFailGo(COR_E_OVERFLOW); + } + if (!AllocateBlock(nAllocateSize.Value())) + { + IfFailGo(E_OUTOFMEMORY); + } + memset(Get(0), 0xFFFFFFFF, nAllocateSize.Value() * sizeof(DWORD)); + + // mark all of the user string + m_daUserStringMarker = new (nothrow) CDynArray<FilterUserStringEntry>(); + IfNullGo(m_daUserStringMarker); + + for (UINT32 nIndex = 0; ;) + { + MetaData::DataBlob userString; + UINT32 nNextIndex; + hr = pMiniMd->GetUserStringAndNextIndex( + nIndex, + &userString, + &nNextIndex); + IfFailGo(hr); + if (hr == S_FALSE) + { // We reached the last user string + hr = S_OK; + break; + } + _ASSERTE(hr == S_OK); + + // Skip empty strings + if (userString.IsEmpty()) + { + nIndex = nNextIndex; + continue; + } + FilterUserStringEntry *pItem = m_daUserStringMarker->Append(); + pItem->m_tkString = TokenFromRid(nIndex, mdtString); + pItem->m_fMarked = true; + + // Process next user string in the heap + nIndex = nNextIndex; + } + +ErrExit: + return hr; +} // FilterTable::MarkAll + +//***************************************************************************** +// +// return true if a token is marked. Otherwise return false. +// +//***************************************************************************** +bool FilterTable::IsTokenMarked( + mdToken tk, // Token to inquiry + DWORD bitMarked) // bit flag to check in the deletion table +{ + RID rid = RidFromToken(tk); + + //<TODO>@FUTURE: inconsistency!!! + // If caller unmarked everything while the module has 2 typedef and 10 methodef. + // We will have 11 rows in the FilterTable. Then user add the 3 typedef, it is + // considered unmarked unless we mark it when we do DefineTypeDef. However, if user + // add another MethodDef, it will be considered marked unless we unmarked..... + // Maybe the solution is not to support DefineXXXX if you use the filter interface??</TODO> + + if ( (Count() == 0) || ((RID)(Count() - 1)) < rid ) + { + // If UnmarkAll has never been called or tk is added after UnmarkAll, + // tk is considered marked. + // + return true; + } + return ( (*Get(rid)) & bitMarked ? true : false); +} // FilterTable::IsTokenMarked + + +//***************************************************************************** +// +// return true if a token is marked. Otherwise return false. +// +//***************************************************************************** +bool FilterTable::IsTokenMarked( + mdToken tk) // Token to inquiry +{ + + switch ( TypeFromToken(tk) ) + { + case mdtTypeRef: + return IsTypeRefMarked(tk); + case mdtTypeDef: + return IsTypeDefMarked(tk); + case mdtFieldDef: + return IsFieldMarked(tk); + case mdtMethodDef: + return IsMethodMarked(tk); + case mdtParamDef: + return IsParamMarked(tk); + case mdtMemberRef: + return IsMemberRefMarked(tk); + case mdtCustomAttribute: + return IsCustomAttributeMarked(tk); + case mdtPermission: + return IsDeclSecurityMarked(tk); + case mdtSignature: + return IsSignatureMarked(tk); + case mdtEvent: + return IsEventMarked(tk); + case mdtProperty: + return IsPropertyMarked(tk); + case mdtModuleRef: + return IsModuleRefMarked(tk); + case mdtTypeSpec: + return IsTypeSpecMarked(tk); + case mdtInterfaceImpl: + return IsInterfaceImplMarked(tk); + case mdtMethodSpec: + return IsMethodSpecMarked(tk); + case mdtString: + return IsUserStringMarked(tk); + default: + _ASSERTE(!"Bad token type!"); + break; + } + return false; +} // FilterTable::IsTokenMarked + +//***************************************************************************** +// +// return true if an UserString is marked. +// +//***************************************************************************** +bool FilterTable::IsUserStringMarked(mdString str) +{ + int low, mid, high, count; + + // if m_daUserStringMarker is not created, UnmarkAll has never been called + if (m_daUserStringMarker == NULL) + return true; + + low = 0; + count = m_daUserStringMarker->Count(); + + if (count == 0) + { + // No strings are marked. + return false; + } + + high = m_daUserStringMarker->Count() - 1; + + while (low <= high) + { + mid = (high + low) / 2; + if ((m_daUserStringMarker->Get(mid))->m_tkString > (DWORD) str) + { + high = mid - 1; + } + else if ((m_daUserStringMarker->Get(mid))->m_tkString < (DWORD) str) + { + low = mid + 1; + } + else + { + return (m_daUserStringMarker->Get(mid))->m_fMarked; + } + } + _ASSERTE(!"Bad Token!"); + return false; +} // FilterTable::IsUserStringMarked + + + +//***************************************************************************** +// +// destructor +// +//***************************************************************************** +FilterTable::~FilterTable() +{ + if (m_daUserStringMarker) + delete m_daUserStringMarker; + Clear(); +} // FilterTable::~FilterTable + diff --git a/src/md/enc/peparse.cpp b/src/md/enc/peparse.cpp new file mode 100644 index 0000000000..583ad32d2f --- /dev/null +++ b/src/md/enc/peparse.cpp @@ -0,0 +1,149 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "stdafx.h" + +#include <windows.h> +#include <corhdr.h> +#include "corerror.h" +#include "pedecoder.h" + + +static const char g_szCORMETA[] = ".cormeta"; + + +HRESULT CLiteWeightStgdbRW::FindImageMetaData(PVOID pImage, DWORD dwFileLength, BOOL bMappedImage, PVOID *ppMetaData, ULONG *pcbMetaData) +{ +#ifndef DACCESS_COMPILE + PEDecoder pe; + + // We need to use different PEDecoder initialization based on the type of data we give it. + // We use the one with a 'bool' as the second argument when dealing with a mapped file, + // and we use the one that takes a COUNT_T as the second argument when dealing with a + // flat file. + if (bMappedImage) + { + if (FAILED(pe.Init(pImage, false)) || + !pe.CheckNTHeaders()) + { + return COR_E_BADIMAGEFORMAT; + } + } + else + { + pe.Init(pImage, (COUNT_T)dwFileLength); + } + + // Minimally validate image + if (!pe.CheckCorHeader()) + return COR_E_BADIMAGEFORMAT; + + + COUNT_T size = 0; + + *ppMetaData = (void *)pe.GetMetadata(&size); + + // Couldn't find any IL metadata in this image + if (*ppMetaData == NULL) + return CLDB_E_NO_DATA; + + if (pcbMetaData != NULL) + *pcbMetaData = size; + + return S_OK; +#else + DacNotImpl(); + return E_NOTIMPL; +#endif +} // CLiteWeightStgdbRW::FindImageMetaData + +// +// Note: Remove once defined in winnt.h +// +typedef struct ANON_OBJECT_HEADER2 { + WORD Sig1; // Must be IMAGE_FILE_MACHINE_UNKNOWN + WORD Sig2; // Must be 0xffff + WORD Version; // >= 2 (implies the CLSID field, Flags and metadata info are present) + WORD Machine; + DWORD TimeDateStamp; + CLSID ClassID; // Used to invoke CoCreateInstance + DWORD SizeOfData; // Size of data that follows the header + DWORD Flags; + DWORD MetaDataSize; // Size of CLR metadata + DWORD MetaDataOffset; // Offset of CLR metadata +} ANON_OBJECT_HEADER2; + +#define ANON_OBJECT_HAS_CORMETA 0x00000001 +#define ANON_OBJECT_IS_PUREMSIL 0x00000002 + +HRESULT CLiteWeightStgdbRW::FindObjMetaData(PVOID pImage, DWORD dwFileLength, PVOID *ppMetaData, ULONG *pcbMetaData) +{ + DWORD dwSize = 0; + DWORD dwOffset = 0; + + ANON_OBJECT_HEADER2 *pAnonImageHdr = (ANON_OBJECT_HEADER2 *) pImage; // Anonymous object header + + // Check to see if this is a LTCG object + if (dwFileLength >= sizeof(ANON_OBJECT_HEADER2) && + pAnonImageHdr->Sig1 == VAL16(IMAGE_FILE_MACHINE_UNKNOWN) && + pAnonImageHdr->Sig2 == VAL16(IMPORT_OBJECT_HDR_SIG2)) + { + // Version 1 anonymous objects don't have metadata info + if (VAL16(pAnonImageHdr->Version) < 2) + goto BadFormat; + + // Anonymous objects contain the metadata info in the header + dwOffset = VAL32(pAnonImageHdr->MetaDataOffset); + dwSize = VAL32(pAnonImageHdr->MetaDataSize); + } + else + { + // Check to see if we have enough data + if (dwFileLength < sizeof(IMAGE_FILE_HEADER)) + goto BadFormat; + + IMAGE_FILE_HEADER *pImageHdr = (IMAGE_FILE_HEADER *) pImage; // Header for the .obj file. + + // Walk each section looking for .cormeta. + DWORD nSections = VAL16(pImageHdr->NumberOfSections); + + // Check to see if we have enough data + S_UINT32 nSectionsSize = S_UINT32(sizeof(IMAGE_FILE_HEADER)) + S_UINT32(nSections) * S_UINT32(sizeof(IMAGE_SECTION_HEADER)); + if (nSectionsSize.IsOverflow() || (dwFileLength < nSectionsSize.Value())) + goto BadFormat; + + IMAGE_SECTION_HEADER *pSectionHdr = (IMAGE_SECTION_HEADER *)(pImageHdr + 1); // Section header. + + for (DWORD i=0; i<nSections; i++, pSectionHdr++) + { + // Simple comparison to section name. + if (memcmp((const char *) pSectionHdr->Name, g_szCORMETA, sizeof(pSectionHdr->Name)) == 0) + { + dwOffset = VAL32(pSectionHdr->PointerToRawData); + dwSize = VAL32(pSectionHdr->SizeOfRawData); + break; + } + } + } + + if (dwOffset == 0 || dwSize == 0) + goto BadFormat; + + // Check that raw data in the section is actually within the file. + { + S_UINT32 dwEndOffset = S_UINT32(dwOffset) + S_UINT32(dwSize); + if ((dwOffset >= dwFileLength) || dwEndOffset.IsOverflow() || (dwEndOffset.Value() > dwFileLength)) + goto BadFormat; + } + + *ppMetaData = (PVOID) ((ULONG_PTR) pImage + dwOffset); + *pcbMetaData = dwSize; + return (S_OK); + +BadFormat: + *ppMetaData = NULL; + *pcbMetaData = 0; + return (COR_E_BADIMAGEFORMAT); +} diff --git a/src/md/enc/rwutil.cpp b/src/md/enc/rwutil.cpp new file mode 100644 index 0000000000..7267301c47 --- /dev/null +++ b/src/md/enc/rwutil.cpp @@ -0,0 +1,1413 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// RWUtil.cpp +// + +// +// contains utility code to MD directory +// +//***************************************************************************** +#include "stdafx.h" +#include "metadata.h" +#include "rwutil.h" +#include "utsem.h" +#include "../inc/mdlog.h" + +//***************************************************************************** +// Helper methods +//***************************************************************************** +void +Unicode2UTF( + LPCWSTR wszSrc, // The string to convert. + __out_ecount(cbDst) + LPUTF8 szDst, // Buffer for the output UTF8 string. + int cbDst) // Size of the buffer for UTF8 string. +{ + int cchSrc = (int)wcslen(wszSrc); + int cchRet; + + cchRet = WszWideCharToMultiByte( + CP_UTF8, + 0, + wszSrc, + cchSrc + 1, + szDst, + cbDst, + NULL, + NULL); + + if (cchRet == 0) + { + _ASSERTE_MSG(FALSE, "Converting unicode string to UTF8 string failed!"); + szDst[0] = '\0'; + } +} // Unicode2UTF + + +HRESULT HENUMInternal::CreateSimpleEnum( + DWORD tkKind, // kind of token that we are iterating + ULONG ridStart, // starting rid + ULONG ridEnd, // end rid + HENUMInternal **ppEnum) // return the created HENUMInternal +{ + HENUMInternal *pEnum; + HRESULT hr = NOERROR; + + // Don't create an empty enum. + if (ridStart >= ridEnd) + { + *ppEnum = 0; + goto ErrExit; + } + + pEnum = new (nothrow) HENUMInternal; + + // check for out of memory error + if (pEnum == NULL) + IfFailGo( E_OUTOFMEMORY ); + + memset(pEnum, 0, sizeof(HENUMInternal)); + pEnum->m_tkKind = tkKind; + pEnum->m_EnumType = MDSimpleEnum; + pEnum->u.m_ulStart = pEnum->u.m_ulCur = ridStart; + pEnum->u.m_ulEnd = ridEnd; + pEnum->m_ulCount = ridEnd - ridStart; + + *ppEnum = pEnum; +ErrExit: + return hr; + +} // CreateSimpleEnum + + +//***************************************************************************** +// Helper function to destroy Enumerator +//***************************************************************************** +void HENUMInternal::DestroyEnum( + HENUMInternal *pmdEnum) +{ + if (pmdEnum == NULL) + return; + + if (pmdEnum->m_EnumType == MDDynamicArrayEnum) + { + TOKENLIST *pdalist; + pdalist = (TOKENLIST *) &(pmdEnum->m_cursor); + + // clear the embedded dynamic array before we delete the enum + pdalist->Clear(); + } + delete pmdEnum; +} // DestroyEnum + + +//***************************************************************************** +// Helper function to destroy Enumerator if the enumerator is empty +//***************************************************************************** +void HENUMInternal::DestroyEnumIfEmpty( + HENUMInternal **ppEnum) // reset the enumerator pointer to NULL if empty +{ + + if (*ppEnum == NULL) + return; + + _ASSERTE((*ppEnum)->m_EnumType != MDCustomEnum); + + if ((*ppEnum)->m_ulCount == 0) + { + HENUMInternal::DestroyEnum(*ppEnum); + *ppEnum = NULL; + } +} // DestroyEnumIfEmpty + + +void HENUMInternal::ClearEnum( + HENUMInternal *pmdEnum) +{ + if (pmdEnum == NULL) + return; + + if (pmdEnum->m_EnumType == MDDynamicArrayEnum) + { + TOKENLIST *pdalist; + pdalist = (TOKENLIST *) &(pmdEnum->m_cursor); + + // clear the embedded dynamic array before we delete the enum + pdalist->Clear(); + } +} // ClearEnum + + +//***************************************************************************** +// Helper function to iterate the enum +//***************************************************************************** +bool HENUMInternal::EnumNext( + HENUMInternal *phEnum, // [IN] the enumerator to retrieve information + mdToken *ptk) // [OUT] token to scope the search +{ + _ASSERTE(phEnum && ptk); + _ASSERTE(phEnum->m_EnumType != MDCustomEnum); + + if (phEnum->u.m_ulCur >= phEnum->u.m_ulEnd) + return false; + + if ( phEnum->m_EnumType == MDSimpleEnum ) + { + *ptk = phEnum->u.m_ulCur | phEnum->m_tkKind; + phEnum->u.m_ulCur++; + } + else + { + TOKENLIST *pdalist = (TOKENLIST *)&(phEnum->m_cursor); + + _ASSERTE( phEnum->m_EnumType == MDDynamicArrayEnum ); + *ptk = *( pdalist->Get(phEnum->u.m_ulCur++) ); + } + return true; +} // EnumNext + +//***************************************************************************** +// Number of items in the enumerator. +//***************************************************************************** +HRESULT HENUMInternal::GetCount( + HENUMInternal *phEnum, // [IN] the enumerator to retrieve information + ULONG *pCount) // ]OUT] the index of the desired item +{ + // Check for empty enum. + if (phEnum == 0) + return S_FALSE; + + _ASSERTE(phEnum->m_EnumType != MDCustomEnum); + + + *pCount = phEnum->u.m_ulEnd - phEnum->u.m_ulStart; + return S_OK; +} + +//***************************************************************************** +// Get a specific element. +//***************************************************************************** +HRESULT HENUMInternal::GetElement( + HENUMInternal *phEnum, // [IN] the enumerator to retrieve information + ULONG ix, // ]IN] the index of the desired item + mdToken *ptk) // [OUT] token to fill +{ + // Check for empty enum. + if (phEnum == 0) + return S_FALSE; + + if (ix > (phEnum->u.m_ulEnd - phEnum->u.m_ulStart)) + return S_FALSE; + + if ( phEnum->m_EnumType == MDSimpleEnum ) + { + *ptk = (phEnum->u.m_ulStart + ix) | phEnum->m_tkKind; + } + else + { + TOKENLIST *pdalist = (TOKENLIST *)&(phEnum->m_cursor); + + _ASSERTE( phEnum->m_EnumType == MDDynamicArrayEnum ); + *ptk = *( pdalist->Get(ix) ); + } + + return S_OK; +} + +//***************************************************************************** +// Helper function to fill output token buffers given an enumerator +//***************************************************************************** +HRESULT HENUMInternal::EnumWithCount( + HENUMInternal *pEnum, // enumerator + ULONG cMax, // max tokens that caller wants + mdToken rTokens[], // output buffer to fill the tokens + ULONG *pcTokens) // number of tokens fill to the buffer upon return +{ + ULONG cTokens; + HRESULT hr = NOERROR; + + // Check for empty enum. + if (pEnum == 0) + { + if (pcTokens) + *pcTokens = 0; + return S_FALSE; + } + + // we can only fill the minimun of what caller asked for or what we have left + cTokens = min ( (pEnum->u.m_ulEnd - pEnum->u.m_ulCur), cMax); + + if (pEnum->m_EnumType == MDSimpleEnum) + { + + // now fill the output + for (ULONG i = 0; i < cTokens; i ++, pEnum->u.m_ulCur++) + { + rTokens[i] = TokenFromRid(pEnum->u.m_ulCur, pEnum->m_tkKind); + } + + } + else + { + // cannot be any other kind! + _ASSERTE( pEnum->m_EnumType == MDDynamicArrayEnum ); + + // get the embedded dynamic array + TOKENLIST *pdalist = (TOKENLIST *)&(pEnum->m_cursor); + + for (ULONG i = 0; i < cTokens; i ++, pEnum->u.m_ulCur++) + { + rTokens[i] = *( pdalist->Get(pEnum->u.m_ulCur) ); + } + } + + if (pcTokens) + *pcTokens = cTokens; + + if (cTokens == 0) + hr = S_FALSE; + return hr; +} // EnumWithCount + + +//***************************************************************************** +// Helper function to fill output token buffers given an enumerator +// This is a variation that takes two output arrays. The tokens in the +// enumerator are interleaved, one for each array. This is currently used by +// EnumMethodImpl which needs to return two arrays. +//***************************************************************************** +HRESULT HENUMInternal::EnumWithCount( + HENUMInternal *pEnum, // enumerator + ULONG cMax, // max tokens that caller wants + mdToken rTokens1[], // first output buffer to fill the tokens + mdToken rTokens2[], // second output buffer to fill the tokens + ULONG *pcTokens) // number of tokens fill to each buffer upon return +{ + ULONG cTokens; + HRESULT hr = NOERROR; + + // cannot be any other kind! + _ASSERTE( pEnum->m_EnumType == MDDynamicArrayEnum ); + + // Check for empty enum. + if (pEnum == 0) + { + if (pcTokens) + *pcTokens = 0; + return S_FALSE; + } + + // Number of tokens must always be a multiple of 2. + _ASSERTE(! ((pEnum->u.m_ulEnd - pEnum->u.m_ulCur) % 2) ); + + // we can only fill the minimun of what caller asked for or what we have left + cTokens = min ( (pEnum->u.m_ulEnd - pEnum->u.m_ulCur), cMax * 2); + + // get the embedded dynamic array + TOKENLIST *pdalist = (TOKENLIST *)&(pEnum->m_cursor); + + for (ULONG i = 0; i < (cTokens / 2); i++) + { + rTokens1[i] = *( pdalist->Get(pEnum->u.m_ulCur++) ); + rTokens2[i] = *( pdalist->Get(pEnum->u.m_ulCur++) ); + } + + if (pcTokens) + *pcTokens = cTokens / 2; + + if (cTokens == 0) + hr = S_FALSE; + return hr; +} // EnumWithCount + + +//***************************************************************************** +// Helper function to create HENUMInternal +//***************************************************************************** +HRESULT HENUMInternal::CreateDynamicArrayEnum( + DWORD tkKind, // kind of token that we are iterating + HENUMInternal **ppEnum) // return the created HENUMInternal +{ + HENUMInternal *pEnum; + HRESULT hr = NOERROR; + TOKENLIST *pdalist; + + pEnum = new (nothrow) HENUMInternal; + + // check for out of memory error + if (pEnum == NULL) + IfFailGo( E_OUTOFMEMORY ); + + memset(pEnum, 0, sizeof(HENUMInternal)); + pEnum->m_tkKind = tkKind; + pEnum->m_EnumType = MDDynamicArrayEnum; + + // run the constructor in place + pdalist = (TOKENLIST *) &(pEnum->m_cursor); + ::new (pdalist) TOKENLIST; + + *ppEnum = pEnum; +ErrExit: + return hr; + +} // _CreateDynamicArrayEnum + + + +//***************************************************************************** +// Helper function to init HENUMInternal +//***************************************************************************** +void HENUMInternal::InitDynamicArrayEnum( + HENUMInternal *pEnum) // HENUMInternal to be initialized +{ + TOKENLIST *pdalist; + + memset(pEnum, 0, sizeof(HENUMInternal)); + pEnum->m_EnumType = MDDynamicArrayEnum; + pEnum->m_tkKind = (DWORD) -1; + + // run the constructor in place + pdalist = (TOKENLIST *) &(pEnum->m_cursor); + ::new (pdalist) TOKENLIST; +} // CreateDynamicArrayEnum + + +//***************************************************************************** +// Helper function to init HENUMInternal +//***************************************************************************** +void HENUMInternal::InitSimpleEnum( + DWORD tkKind, // kind of token that we are iterating + ULONG ridStart, // starting rid + ULONG ridEnd, // end rid + HENUMInternal *pEnum) // HENUMInternal to be initialized +{ + pEnum->m_EnumType = MDSimpleEnum; + pEnum->m_tkKind = tkKind; + pEnum->u.m_ulStart = pEnum->u.m_ulCur = ridStart; + pEnum->u.m_ulEnd = ridEnd; + pEnum->m_ulCount = ridEnd - ridStart; + +} // InitSimpleEnum + + + + +//***************************************************************************** +// Helper function to init HENUMInternal +//***************************************************************************** +HRESULT HENUMInternal::AddElementToEnum( + HENUMInternal *pEnum, // return the created HENUMInternal + mdToken tk) // token value to be stored +{ + HRESULT hr = NOERROR; + TOKENLIST *pdalist; + mdToken *ptk; + + pdalist = (TOKENLIST *) &(pEnum->m_cursor); + + { + // TODO: Revisit this violation. + CONTRACT_VIOLATION(ThrowsViolation); + ptk = ((mdToken *)pdalist->Append()); + } + if (ptk == NULL) + IfFailGo( E_OUTOFMEMORY ); + *ptk = tk; + + // increase the count + pEnum->m_ulCount++; + pEnum->u.m_ulEnd++; +ErrExit: + return hr; + +} // _AddElementToEnum + + + + + +//***************************************************************************** +// find a token in the tokenmap. +//***************************************************************************** +MDTOKENMAP::~MDTOKENMAP() +{ + if (m_pMap) + m_pMap->Release(); +} // MDTOKENMAP::~MDTOKENMAP() + +HRESULT MDTOKENMAP::Init( + IUnknown *pImport) // The import that this map is for. +{ + HRESULT hr; // A result. + IMetaDataTables *pITables=0; // Table information. + ULONG cRows; // Count of rows in a table. + ULONG cTotal; // Running total of rows in db. + TOKENREC *pRec; // A TOKENREC record. + mdToken tkTable; // Token kind for a table. + + hr = pImport->QueryInterface(IID_IMetaDataTables, (void**)&pITables); + if (hr == S_OK) + { + // Determine the size of each table. + cTotal = 0; + for (ULONG ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) + { + // Where does this table's data start. + m_TableOffset[ixTbl] = cTotal; + // See if this table has tokens. + tkTable = CMiniMdRW::GetTokenForTable(ixTbl); + if (tkTable == (ULONG) -1) + { + // It doesn't have tokens, so we won't see any tokens for the table. + } + else + { // It has tokens, so we may see a token for every row. + pITables->GetTableInfo(ixTbl, 0, &cRows, 0,0,0); + // Safe: cTotal += cRows + if (!ClrSafeInt<ULONG>::addition(cTotal, cRows, cTotal)) + { + IfFailGo(COR_E_OVERFLOW); + } + } + } + m_TableOffset[TBL_COUNT] = cTotal; + m_iCountIndexed = cTotal; + // Attempt to allocate space for all of the possible remaps. + if (!AllocateBlock(cTotal)) + IfFailGo(E_OUTOFMEMORY); + // Note that no sorts are needed. + m_sortKind = Indexed; + // Initialize entries to "not found". + for (ULONG i=0; i<cTotal; ++i) + { + pRec = Get(i); + pRec->SetEmpty(); + } + } +#if defined(_DEBUG) + if (SUCCEEDED(pImport->QueryInterface(IID_IMetaDataImport, (void**)&m_pImport))) + { + // Ok, here's a pretty nasty workaround. We're going to make a big assumption here + // that we're owned by the pImport, and so we don't need to keep a refcount + // on the pImport object. + // + // If we did, we'd create a circular reference and neither this object nor + // the RegMeta would be freed. + m_pImport->Release(); + + } + + + +#endif + +ErrExit: + if (pITables) + pITables->Release(); + return hr; +} // HRESULT MDTOKENMAP::Init() + +HRESULT MDTOKENMAP::EmptyMap() +{ + int nCount = Count(); + for (int i=0; i<nCount; ++i) + { + Get(i)->SetEmpty(); + } + + return S_OK; +}// HRESULT MDTOKENMAP::Clear() + + +//***************************************************************************** +// find a token in the tokenmap. +//***************************************************************************** +bool MDTOKENMAP::Find( + mdToken tkFind, // [IN] the token value to find + TOKENREC **ppRec) // [OUT] point to the record found in the dynamic array +{ + int lo,mid,hi; // binary search indices. + TOKENREC *pRec = NULL; + + if (m_sortKind == Indexed && TypeFromToken(tkFind) != mdtString) + { + // Get the entry. + ULONG ixTbl = CMiniMdRW::GetTableForToken(tkFind); + if(ixTbl == (ULONG) -1) + return false; + ULONG iRid = RidFromToken(tkFind); + if((m_TableOffset[ixTbl] + iRid) > m_TableOffset[ixTbl+1]) + return false; + pRec = Get(m_TableOffset[ixTbl] + iRid - 1); + // See if it has been set. + if (pRec->IsEmpty()) + return false; + // Verify that it is what we think it is. + _ASSERTE(pRec->m_tkFrom == tkFind); + *ppRec = pRec; + return true; + } + else + { // Shouldn't be any unsorted records, and table must be sorted in proper ordering. + _ASSERTE( m_iCountTotal == m_iCountSorted && + (m_sortKind == SortByFromToken || m_sortKind == Indexed) ); + _ASSERTE( (m_iCountIndexed + m_iCountTotal) == (ULONG)Count() ); + + // Start with entire table. + lo = m_iCountIndexed; + hi = Count() - 1; + + // While there are rows in the range... + while (lo <= hi) + { // Look at the one in the middle. + mid = (lo + hi) / 2; + + pRec = Get(mid); + + // If equal to the target, done. + if (tkFind == pRec->m_tkFrom) + { + *ppRec = Get(mid); + return true; + } + + // If middle item is too small, search the top half. + if (pRec->m_tkFrom < tkFind) + lo = mid + 1; + else // but if middle is to big, search bottom half. + hi = mid - 1; + } + } + + // Didn't find anything that matched. + return false; +} // bool MDTOKENMAP::Find() + + + +//***************************************************************************** +// remap the token +//***************************************************************************** +HRESULT MDTOKENMAP::Remap( + mdToken tkFrom, + mdToken *ptkTo) +{ + HRESULT hr = NOERROR; + TOKENREC *pRec; + + // Remap nil to same thing (helps because System.Object has no base class.) + if (IsNilToken(tkFrom)) + { + *ptkTo = tkFrom; + return hr; + } + + if ( Find(tkFrom, &pRec) ) + { + *ptkTo = pRec->m_tkTo; + } + else + { + _ASSERTE( !" Bad lookup map!"); + hr = META_E_BADMETADATA; + } + return hr; +} // HRESULT MDTOKENMAP::Remap() + + + +//***************************************************************************** +// find a token in the tokenmap. +//***************************************************************************** +HRESULT MDTOKENMAP::InsertNotFound( + mdToken tkFind, + bool fDuplicate, + mdToken tkTo, + TOKENREC **ppRec) +{ + HRESULT hr = NOERROR; + int lo, mid, hi; // binary search indices. + TOKENREC *pRec; + + // If possible, validate the input. + _ASSERTE(!m_pImport || m_pImport->IsValidToken(tkFind)); + + if (m_sortKind == Indexed && TypeFromToken(tkFind) != mdtString) + { + // Get the entry. + ULONG ixTbl = CMiniMdRW::GetTableForToken(tkFind); + _ASSERTE(ixTbl != (ULONG) -1); + ULONG iRid = RidFromToken(tkFind); + _ASSERTE((m_TableOffset[ixTbl] + iRid) <= m_TableOffset[ixTbl+1]); + pRec = Get(m_TableOffset[ixTbl] + iRid - 1); + // See if it has been set. + if (!pRec->IsEmpty()) + { // Verify that it is what we think it is. + _ASSERTE(pRec->m_tkFrom == tkFind); + } + // Store the data. + pRec->m_tkFrom = tkFind; + pRec->m_isDuplicate = fDuplicate; + pRec->m_tkTo = tkTo; + pRec->m_isFoundInImport = false; + // Return the result. + *ppRec = pRec; + } + else + { // Shouldn't be any unsorted records, and table must be sorted in proper ordering. + _ASSERTE( m_iCountTotal == m_iCountSorted && + (m_sortKind == SortByFromToken || m_sortKind == Indexed) ); + + if ((Count() - m_iCountIndexed) > 0) + { + // Start with entire table. + lo = m_iCountIndexed; + hi = Count() - 1; + + // While there are rows in the range... + while (lo < hi) + { // Look at the one in the middle. + mid = (lo + hi) / 2; + + pRec = Get(mid); + + // If equal to the target, done. + if (tkFind == pRec->m_tkFrom) + { + *ppRec = Get(mid); + goto ErrExit; + } + + // If middle item is too small, search the top half. + if (pRec->m_tkFrom < tkFind) + lo = mid + 1; + else // but if middle is to big, search bottom half. + hi = mid - 1; + } + _ASSERTE(hi <= lo); + pRec = Get(lo); + + if (tkFind == pRec->m_tkFrom) + { + if (tkTo == pRec->m_tkTo && fDuplicate == pRec->m_isDuplicate) + { + *ppRec = pRec; + } + else + { + _ASSERTE(!"inconsistent token has been added to the table!"); + IfFailGo( E_FAIL ); + } + } + + if (tkFind < pRec->m_tkFrom) + { + // insert before lo; + pRec = Insert(lo); + } + else + { + // insert after lo + pRec = Insert(lo + 1); + } + } + else + { + // table is empty + pRec = Insert(m_iCountIndexed); + } + + + // If pRec == NULL, return E_OUTOFMEMORY + IfNullGo(pRec); + + m_iCountTotal++; + m_iCountSorted++; + + *ppRec = pRec; + + // initialize the record + pRec->m_tkFrom = tkFind; + pRec->m_isDuplicate = fDuplicate; + pRec->m_tkTo = tkTo; + pRec->m_isFoundInImport = false; + } + +ErrExit: + return hr; +} // HRESULT MDTOKENMAP::InsertNotFound() + + +//***************************************************************************** +// find a "to" token in the tokenmap. Now that we are doing the ref to def optimization, +// we might have several from tokens map to the same to token. We need to return a range of index +// instead.... +//***************************************************************************** +bool MDTOKENMAP::FindWithToToken( + mdToken tkFind, // [IN] the token value to find + int *piPosition) // [OUT] return the first from-token that has the matching to-token +{ + int lo, mid, hi; // binary search indices. + TOKENREC *pRec; + TOKENREC *pRec2; + + // This makes sure that no insertions take place between calls to FindWithToToken. + // We want to avoid repeated sorting of the table. + _ASSERTE(m_sortKind != SortByToToken || m_iCountTotal == m_iCountSorted); + + // If the map is sorted with From tokens, change it to be sorted with To tokens. + if (m_sortKind != SortByToToken) + SortTokensByToToken(); + + // Start with entire table. + lo = 0; + hi = Count() - 1; + + // While there are rows in the range... + while (lo <= hi) + { // Look at the one in the middle. + mid = (lo + hi) / 2; + + pRec = Get(mid); + + // If equal to the target, done. + if (tkFind == pRec->m_tkTo) + { + for (int i = mid-1; i >= 0; i--) + { + pRec2 = Get(i); + if (tkFind != pRec2->m_tkTo) + { + *piPosition = i + 1; + return true; + } + } + *piPosition = 0; + return true; + } + + // If middle item is too small, search the top half. + if (pRec->m_tkTo < tkFind) + lo = mid + 1; + else // but if middle is to big, search bottom half. + hi = mid - 1; + } + // Didn't find anything that matched. + return false; +} // bool MDTOKENMAP::FindWithToToken() + + + +//***************************************************************************** +// output a remapped token +//***************************************************************************** +mdToken MDTOKENMAP::SafeRemap( + mdToken tkFrom) // [IN] the token value to find +{ + TOKENREC *pRec; + + // If possible, validate the input. + _ASSERTE(!m_pImport || m_pImport->IsValidToken(tkFrom)); + + SortTokensByFromToken(); + + if ( Find(tkFrom, &pRec) ) + { + return pRec->m_tkTo; + } + + return tkFrom; +} // mdToken MDTOKENMAP::SafeRemap() + + +//***************************************************************************** +// Sorting +//***************************************************************************** +void MDTOKENMAP::SortTokensByToToken() +{ + // Only sort if there are unsorted records or the sort kind changed. + if (m_iCountSorted < m_iCountTotal || m_sortKind != SortByToToken) + { + // Sort the entire array. + m_iCountTotal = Count(); + m_iCountIndexed = 0; + SortRangeToToken(0, m_iCountTotal - 1); + m_iCountSorted = m_iCountTotal; + m_sortKind = SortByToToken; + } +} // void MDTOKENMAP::SortTokensByToToken() + +void MDTOKENMAP::SortRangeFromToken( + int iLeft, + int iRight) +{ + int iLast; + int i; // loop variable. + + // if less than two elements you're done. + if (iLeft >= iRight) + return; + + // The mid-element is the pivot, move it to the left. + Swap(iLeft, (iLeft+iRight)/2); + iLast = iLeft; + + // move everything that is smaller than the pivot to the left. + for(i = iLeft+1; i <= iRight; i++) + if (CompareFromToken(i, iLeft) < 0) + Swap(i, ++iLast); + + // Put the pivot to the point where it is in between smaller and larger elements. + Swap(iLeft, iLast); + + // Sort the each partition. + SortRangeFromToken(iLeft, iLast-1); + SortRangeFromToken(iLast+1, iRight); +} // void MDTOKENMAP::SortRangeFromToken() + + +//***************************************************************************** +// Sorting +//***************************************************************************** +void MDTOKENMAP::SortRangeToToken( + int iLeft, + int iRight) +{ + int iLast; + int i; // loop variable. + + // if less than two elements you're done. + if (iLeft >= iRight) + return; + + // The mid-element is the pivot, move it to the left. + Swap(iLeft, (iLeft+iRight)/2); + iLast = iLeft; + + // move everything that is smaller than the pivot to the left. + for(i = iLeft+1; i <= iRight; i++) + if (CompareToToken(i, iLeft) < 0) + Swap(i, ++iLast); + + // Put the pivot to the point where it is in between smaller and larger elements. + Swap(iLeft, iLast); + + // Sort the each partition. + SortRangeToToken(iLeft, iLast-1); + SortRangeToToken(iLast+1, iRight); +} // void MDTOKENMAP::SortRangeToToken() + + +//***************************************************************************** +// find a token in the tokenmap. +//***************************************************************************** +HRESULT MDTOKENMAP::AppendRecord( + mdToken tkFind, + bool fDuplicate, + mdToken tkTo, + TOKENREC **ppRec) +{ + HRESULT hr = NOERROR; + TOKENREC *pRec; + + // If possible, validate the input. + _ASSERTE(!m_pImport || m_pImport->IsValidToken(tkFind)); + + // If the map is indexed, and this is a table token, update-in-place. + if (m_sortKind == Indexed && TypeFromToken(tkFind) != mdtString) + { + // Get the entry. + ULONG ixTbl = CMiniMdRW::GetTableForToken(tkFind); + _ASSERTE(ixTbl != (ULONG) -1); + ULONG iRid = RidFromToken(tkFind); + _ASSERTE((m_TableOffset[ixTbl] + iRid) <= m_TableOffset[ixTbl+1]); + pRec = Get(m_TableOffset[ixTbl] + iRid - 1); + // See if it has been set. + if (!pRec->IsEmpty()) + { // Verify that it is what we think it is. + _ASSERTE(pRec->m_tkFrom == tkFind); + } + } + else + { + pRec = Append(); + IfNullGo(pRec); + + // number of entries increased but not the sorted entry + m_iCountTotal++; + } + + // Store the data. + pRec->m_tkFrom = tkFind; + pRec->m_isDuplicate = fDuplicate; + pRec->m_tkTo = tkTo; + pRec->m_isFoundInImport = false; + *ppRec = pRec; + +ErrExit: + return hr; +} // HRESULT MDTOKENMAP::AppendRecord() + + + + +//********************************************************************************************************* +// +// Merge Token manager's constructor +// +//********************************************************************************************************* +MergeTokenManager::MergeTokenManager(MDTOKENMAP *pTkMapList, IUnknown *pHandler) +{ + m_cRef = 1; + m_pTkMapList = pTkMapList; + m_pDefaultHostRemap = NULL; + if (pHandler) + pHandler->QueryInterface(IID_IMapToken, (void **) &m_pDefaultHostRemap); +} // TokenManager::TokenManager() + + + +//********************************************************************************************************* +// +// Merge Token manager's destructor +// +//********************************************************************************************************* +MergeTokenManager::~MergeTokenManager() +{ + if (m_pDefaultHostRemap) + m_pDefaultHostRemap->Release(); +} // TokenManager::~TokenManager() + + + + +ULONG MergeTokenManager::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} // TokenManager::AddRef() + + + +ULONG MergeTokenManager::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (!cRef) + delete this; + return (cRef); +} // TokenManager::Release() + + +HRESULT MergeTokenManager::QueryInterface(REFIID riid, void **ppUnk) +{ + *ppUnk = 0; + + if (riid == IID_IMapToken) + *ppUnk = (IUnknown *) (IMapToken *) this; + else + return (E_NOINTERFACE); + AddRef(); + return (S_OK); +} // TokenManager::QueryInterface + + + +//********************************************************************************************************* +// +// Token manager keep tracks a list of tokenmaps. Each tokenmap corresponding +// to an imported scope. Note that with this, we do have problem in how to +// tell linker regarding the token movement when the token is added by Define +// rather than merge. This should be fixed with new merge implementation. +// The tkImp is the old tokens in the emit scope, tkEmit is the new token in the +// emit scope. We need to find the token from an import scope that is resolved +// to the tkImp. We then need to tell linker about this token movement. +// If we don't find any import scope which generates the tkImp token, that is +// this tkImp is generated by calling DefinXXX directly on the final merged scope. +// Then we use the default host remap to send the notification. +// +//********************************************************************************************************* +HRESULT MergeTokenManager::Map(mdToken tkImp, mdToken tkEmit) +{ + HRESULT hr = NOERROR; + MDTOKENMAP *pTkMapList = m_pTkMapList; + bool fFoundInImport = false; + int iPosition; + TOKENREC *pRec; + + _ASSERTE(m_pTkMapList); + while ( pTkMapList ) + { + // FindWithToToken will return the first match with the To token. + // pTkMapList is sorted with To token. It might contain several From tokens + // that map to the To token due to ref to def optimiation. Make sure that + // all notification is sent to all of these From tokens. + // + if ( pTkMapList->FindWithToToken(tkImp, &iPosition) ) + { + // make sure that we don't walk over the last entry + while (iPosition < pTkMapList->Count()) + { + pRec = pTkMapList->Get(iPosition); + if (pRec->m_tkTo != tkImp) + { + // we are done! + break; + } + + // more matching record... + fFoundInImport = true; + if (pTkMapList->m_pMap) + hr = pTkMapList->m_pMap->Map(pRec->m_tkFrom, tkEmit); + _ASSERTE(SUCCEEDED(hr)); + IfFailGo( hr ); + iPosition++; + } + } + pTkMapList = pTkMapList->m_pNextMap; + } + + if (fFoundInImport == false && m_pDefaultHostRemap) + { + // use the default remap to send the notification + IfFailGo( m_pDefaultHostRemap->Map(tkImp, tkEmit) ); + } +ErrExit: + return hr; +} + + + +//********************************************************************************************************* +// +// CMapToken's constructor +// +//********************************************************************************************************* +CMapToken::CMapToken() +{ + m_cRef = 1; + m_pTKMap = NULL; + m_isSorted = true; +} // TokenManager::TokenManager() + + + +//********************************************************************************************************* +// +// CMapToken's destructor +// +//********************************************************************************************************* +CMapToken::~CMapToken() +{ + delete m_pTKMap; +} // CMapToken::~CMapToken() + + +ULONG CMapToken::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} // CMapToken::AddRef() + + + +ULONG CMapToken::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (!cRef) + delete this; + return (cRef); +} // CMapToken::Release() + + +HRESULT CMapToken::QueryInterface(REFIID riid, void **ppUnk) +{ + *ppUnk = 0; + + if (riid == IID_IMapToken) + *ppUnk = (IUnknown *) (IMapToken *) this; + else + return (E_NOINTERFACE); + AddRef(); + return (S_OK); +} // CMapToken::QueryInterface + + + +//********************************************************************************************************* +// +// Track the token mapping +// +//********************************************************************************************************* +HRESULT CMapToken::Map( + mdToken tkFrom, + mdToken tkTo) +{ + HRESULT hr = NOERROR; + TOKENREC *pTkRec; + + if (m_pTKMap == NULL) + m_pTKMap = new (nothrow) MDTOKENMAP; + + IfNullGo( m_pTKMap ); + + IfFailGo( m_pTKMap->AppendRecord(tkFrom, false, tkTo, &pTkRec) ); + _ASSERTE( pTkRec ); + + m_isSorted = false; +ErrExit: + return hr; +} + + +//********************************************************************************************************* +// +// return what tkFrom is mapped to ptkTo. If there is no remap +// (ie the token from is filtered out by the filter mechanism, it will return false. +// +//********************************************************************************************************* +bool CMapToken::Find( + mdToken tkFrom, + TOKENREC **pRecTo) +{ + TOKENREC *pRec; + bool bRet; + if ( m_isSorted == false ) + { + // sort the map + m_pTKMap->SortTokensByFromToken(); + m_isSorted = true; + } + + bRet = m_pTKMap->Find(tkFrom, &pRec) ; + if (bRet) + { + _ASSERTE(pRecTo); + *pRecTo = pRec; + } + else + { + pRec = NULL; + } + return bRet; +} + + +//********************************************************************************************************* +// +// This function returns true if tkFrom is resolved to a def token. Otherwise, it returns +// false. +// +//********************************************************************************************************* +bool TokenRemapManager::ResolveRefToDef( + mdToken tkRef, // [IN] ref token + mdToken *ptkDef) // [OUT] def token that it resolves to. If it does not resolve to a def + // token, it will return the tkRef token here. +{ + mdToken tkTo; + + _ASSERTE(ptkDef); + + if (TypeFromToken(tkRef) == mdtTypeRef) + { + tkTo = m_TypeRefToTypeDefMap[RidFromToken(tkRef)]; + } + else + { + _ASSERTE( TypeFromToken(tkRef) == mdtMemberRef ); + tkTo = m_MemberRefToMemberDefMap[RidFromToken(tkRef)]; + } + if (RidFromToken(tkTo) == mdTokenNil) + { + *ptkDef = tkRef; + return false; + } + *ptkDef = tkTo; + return true; +} // ResolveRefToDef + + + +//********************************************************************************************************* +// +// Destructor +// +//********************************************************************************************************* +TokenRemapManager::~TokenRemapManager() +{ + m_TypeRefToTypeDefMap.Clear(); + m_MemberRefToMemberDefMap.Clear(); +} // ~TokenRemapManager + + +//********************************************************************************************************* +// +// Initialize the size of Ref to Def optimization table. We will grow the tables in this function. +// We also initialize the table entries to zero. +// +//********************************************************************************************************* +HRESULT TokenRemapManager::ClearAndEnsureCapacity( + ULONG cTypeRef, + ULONG cMemberRef) +{ + HRESULT hr = NOERROR; + if ( ((ULONG) (m_TypeRefToTypeDefMap.Count())) < (cTypeRef + 1) ) + { + if ( m_TypeRefToTypeDefMap.AllocateBlock(cTypeRef + 1 - m_TypeRefToTypeDefMap.Count() ) == 0 ) + IfFailGo( E_OUTOFMEMORY ); + } + memset( m_TypeRefToTypeDefMap.Get(0), 0, (cTypeRef + 1) * sizeof(mdToken) ); + + if ( ((ULONG) (m_MemberRefToMemberDefMap.Count())) < (cMemberRef + 1) ) + { + if ( m_MemberRefToMemberDefMap.AllocateBlock(cMemberRef + 1 - m_MemberRefToMemberDefMap.Count() ) == 0 ) + IfFailGo( E_OUTOFMEMORY ); + } + memset( m_MemberRefToMemberDefMap.Get(0), 0, (cMemberRef + 1) * sizeof(mdToken) ); + +ErrExit: + return hr; +} // HRESULT TokenRemapManager::ClearAndEnsureCapacity() + + + +//********************************************************************************************************* +// +// Constructor +// +//********************************************************************************************************* +CMDSemReadWrite::CMDSemReadWrite( + UTSemReadWrite * pSem) +{ + m_fLockedForRead = false; + m_fLockedForWrite = false; + m_pSem = pSem; +} // CMDSemReadWrite::CMDSemReadWrite + + + +//********************************************************************************************************* +// +// Destructor +// +//********************************************************************************************************* +CMDSemReadWrite::~CMDSemReadWrite() +{ + _ASSERTE(!m_fLockedForRead || !m_fLockedForWrite); + if (m_pSem == NULL) + { + return; + } + if (m_fLockedForRead) + { + LOG((LF_METADATA, LL_EVERYTHING, "UnlockRead called from CSemReadWrite::~CSemReadWrite \n")); + m_pSem->UnlockRead(); + } + if (m_fLockedForWrite) + { + LOG((LF_METADATA, LL_EVERYTHING, "UnlockWrite called from CSemReadWrite::~CSemReadWrite \n")); + m_pSem->UnlockWrite(); + } +} // CMDSemReadWrite::~CMDSemReadWrite + +//********************************************************************************************************* +// +// Used to obtain the read lock +// +//********************************************************************************************************* +HRESULT CMDSemReadWrite::LockRead() +{ + HRESULT hr = S_OK; + + _ASSERTE(!m_fLockedForRead && !m_fLockedForWrite); + + if (m_pSem == NULL) + { + INDEBUG(m_fLockedForRead = true); + return hr; + } + + LOG((LF_METADATA, LL_EVERYTHING, "LockRead called from CSemReadWrite::LockRead \n")); + IfFailRet(m_pSem->LockRead()); + m_fLockedForRead = true; + + return hr; +} // CMDSemReadWrite::LockRead + +//********************************************************************************************************* +// +// Used to obtain the read lock +// +//********************************************************************************************************* +HRESULT CMDSemReadWrite::LockWrite() +{ + HRESULT hr = S_OK; + + _ASSERTE(!m_fLockedForRead && !m_fLockedForWrite); + + if (m_pSem == NULL) + { + INDEBUG(m_fLockedForWrite = true); + return hr; + } + + LOG((LF_METADATA, LL_EVERYTHING, "LockWrite called from CSemReadWrite::LockWrite \n")); + IfFailRet(m_pSem->LockWrite()); + m_fLockedForWrite = true; + + return hr; +} + +//********************************************************************************************************* +// +// Convert a read lock to a write lock +// +//********************************************************************************************************* +HRESULT CMDSemReadWrite::ConvertReadLockToWriteLock() +{ + _ASSERTE(!m_fLockedForWrite); + + HRESULT hr = S_OK; + + if (m_pSem == NULL) + { + INDEBUG(m_fLockedForRead = false); + INDEBUG(m_fLockedForWrite = true); + return hr; + } + + if (m_fLockedForRead) + { + LOG((LF_METADATA, LL_EVERYTHING, "UnlockRead called from CSemReadWrite::ConvertReadLockToWriteLock \n")); + m_pSem->UnlockRead(); + m_fLockedForRead = false; + } + LOG((LF_METADATA, LL_EVERYTHING, "LockWrite called from CSemReadWrite::ConvertReadLockToWriteLock\n")); + IfFailRet(m_pSem->LockWrite()); + m_fLockedForWrite = true; + + return hr; +} // CMDSemReadWrite::ConvertReadLockToWriteLock + + +//********************************************************************************************************* +// +// Unlocking for write +// +//********************************************************************************************************* +void CMDSemReadWrite::UnlockWrite() +{ + _ASSERTE(!m_fLockedForRead); + + if (m_pSem == NULL) + { + INDEBUG(m_fLockedForWrite = false); + return; + } + if (m_fLockedForWrite) + { + LOG((LF_METADATA, LL_EVERYTHING, "UnlockWrite called from CSemReadWrite::UnlockWrite \n")); + m_pSem->UnlockWrite(); + m_fLockedForWrite = false; + } +} // CMDSemReadWrite::UnlockWrite diff --git a/src/md/enc/stdafx.cpp b/src/md/enc/stdafx.cpp new file mode 100644 index 0000000000..8bffbd1bf6 --- /dev/null +++ b/src/md/enc/stdafx.cpp @@ -0,0 +1,13 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// stdafx.cpp +// + +// +// Precompiled headers. +// +//***************************************************************************** +#include "stdafx.h" diff --git a/src/md/enc/stdafx.h b/src/md/enc/stdafx.h new file mode 100644 index 0000000000..62f41ae3c7 --- /dev/null +++ b/src/md/enc/stdafx.h @@ -0,0 +1,27 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// stdafx.h +// + +// +// Precompiled headers. +// +//***************************************************************************** +#ifndef __STDAFX_H_ +#define __STDAFX_H_ + +#include <crtwrap.h> +#include <winwrap.h> +#include <utilcode.h> + +#include <cor.h> +#include <corpriv.h> + +#include "mdcommon.h" + +#include "utsem.h" + +#endif // __STDAFX_H__ diff --git a/src/md/enc/stgio.cpp b/src/md/enc/stgio.cpp new file mode 100644 index 0000000000..020cf66b05 --- /dev/null +++ b/src/md/enc/stgio.cpp @@ -0,0 +1,1443 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// StgIO.h +// + +// +// This module handles disk/memory i/o for a generic set of storage solutions, +// including: +// * File system handle (HFILE) +// * IStream +// * User supplied memory buffer (non-movable) +// +// The Read, Write, Seek, ... functions are all directed to the corresponding +// method for each type of file, allowing the consumer to use one set of api's. +// +// File system data can be paged fully into memory in two scenarios: +// read: Normal memory mapped file is created to manage paging. +// write: A custom paging system provides storage for pages as required. This +// data is invalidated when you call Rewrite on the file. +// +// Transactions and backups are handled in the existing file case only. The +// Rewrite function can make a backup of the current contents, and the Restore +// function can be used to recover the data into the current scope. The backup +// file is flushed to disk (which is slower but safer) after the copy. The +// Restore also flushed the recovered changes to disk. Worst case scenario you +// get a crash after calling Rewrite but before Restore, in which case you will +// have a foo.clb.txn file in the same directory as the source file, foo.clb in +// this example. +//<REVISIT_TODO> +// @FUTURE: issues, +// 1. For reading a .clb in an image, it would be great to memory map +// only the portion of the file with the .clb in it. +//</REVISIT_TODO> +//***************************************************************************** +#include "stdafx.h" // Standard headers. +#include "stgio.h" // Our definitions. +#include "corerror.h" +#include "posterror.h" +#include "pedecoder.h" +#include "pedecoder.inl" + +//********** Types. *********************************************************** +#if !defined(FEATURE_METADATA_STANDALONE_WINRT_RO) +#define SMALL_ALLOC_MAP_SIZE (64 * 1024) // 64 kb is the minimum size of virtual + // memory you can allocate, so anything + // less is a waste of VM resources. + +#else //FEATURE_METADATA_STANDALONE_WINRT_RO +// RoMetadata.dll is required to call CreateFileMapping on all WinMD files (even small ones) to use +// Code Intergrity checks on Win8 - see code:#EnableCodeIntegrity +#define SMALL_ALLOC_MAP_SIZE 0 + +#endif //FEATURE_METADATA_STANDALONE_WINRT_RO + +#define MIN_WRITE_CACHE_BYTES (16 * 1024) // 16 kb for a write back cache + + +//********** Locals. ********************************************************** +HRESULT MapFileError(DWORD error); +static void *AllocateMemory(int iSize); +static void FreeMemory(void *pbData); +inline HRESULT MapFileError(DWORD error) +{ + return (PostError(HRESULT_FROM_WIN32(error))); +} + +// Static to class. +int StgIO::m_iPageSize=0; // Size of an OS page. +int StgIO::m_iCacheSize=0; // Size for the write cache. + + + +//********** Code. ************************************************************ +StgIO::StgIO( + bool bAutoMap) : // Memory map for read on open? + m_bAutoMap(bAutoMap) +{ + CtorInit(); + + // If the system page size has not been queried, do so now. + if (m_iPageSize == 0) + { + SYSTEM_INFO sInfo; // Some O/S information. + + // Query the system page size. + GetSystemInfo(&sInfo); + m_iPageSize = sInfo.dwPageSize; + m_iCacheSize = ((MIN_WRITE_CACHE_BYTES - 1) & ~(m_iPageSize - 1)) + m_iPageSize; + } +} + + +void StgIO::CtorInit() +{ + m_bWriteThrough = false; + m_bRewrite = false; + m_bFreeMem = false; + m_pIStream = 0; + m_hFile = INVALID_HANDLE_VALUE; + m_hModule = NULL; + m_hMapping = 0; + m_pBaseData = 0; + m_pData = 0; + m_cbData = 0; + m_fFlags = 0; + m_iType = STGIO_NODATA; + m_cbOffset = 0; + m_rgBuff = 0; + m_cbBuff = 0; + m_rgPageMap = 0; + m_FileType = FILETYPE_UNKNOWN; + *m_rcShared = '\0'; + m_cRef = 1; + m_mtMappedType = MTYPE_NOMAPPING; +} + + + +StgIO::~StgIO() +{ + if (m_rgBuff) + { + FreeMemory(m_rgBuff); + m_rgBuff = 0; + } + + Close(); +} + + +//***************************************************************************** +// Open the base file on top of: (a) file, (b) memory buffer, or (c) stream. +// If create flag is specified, then this will create a new file with the +// name supplied. No data is read from an opened file. You must call +// MapFileToMem before doing direct pointer access to the contents. +//***************************************************************************** +HRESULT StgIO::Open( // Return code. + LPCWSTR szName, // Name of the storage. + int fFlags, // How to open the file. + const void *pbBuff, // Optional buffer for memory. + ULONG cbBuff, // Size of buffer. + IStream *pIStream, // Stream for input. + LPSECURITY_ATTRIBUTES pAttributes) // Security token. +{ + HRESULT hr; + + // If we were given the storage memory to begin with, then use it. + if (pbBuff && cbBuff) + { + _ASSERTE((fFlags & DBPROP_TMODEF_WRITE) == 0); + + // Save the memory address and size only. No handles. + m_pData = (void *) pbBuff; + m_cbData = cbBuff; + +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + // All access to data will be by memory provided. + if ((fFlags & DBPROP_TMODEF_SHAREDMEM) == DBPROP_TMODEF_SHAREDMEM) + { + // We're taking ownership of this memory + m_pBaseData = m_pData; + m_iType = STGIO_SHAREDMEM; + } + else +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + { + m_iType = STGIO_MEM; + } + goto ErrExit; + } + // Check for data backed by a stream pointer. + else if (pIStream) + { + // If this is for the non-create case, get the size of existing data. + if ((fFlags & DBPROP_TMODEF_CREATE) == 0) + { + LARGE_INTEGER iMove = { { 0, 0 } }; + ULARGE_INTEGER iSize; + + // Need the size of the data so we can map it into memory. + if (FAILED(hr = pIStream->Seek(iMove, STREAM_SEEK_END, &iSize))) + return (hr); + m_cbData = iSize.u.LowPart; + } + // Else there is nothing. + else + m_cbData = 0; + + // Save an addref'd copy of the stream. + m_pIStream = pIStream; + m_pIStream->AddRef(); + + // All access to data will be by memory provided. + m_iType = STGIO_STREAM; + goto ErrExit; + } + + // If not on memory, we need a file to do a create/open. + if (!szName || !*szName) + { + return (PostError(E_INVALIDARG)); + } + // Check for create of a new file. + else if (fFlags & DBPROP_TMODEF_CREATE) + { + //<REVISIT_TODO>@future: This could chose to open the file in write through + // mode, which would provide better Duribility (from ACID props), + // but would be much slower.</REVISIT_TODO> + + // Create the new file, overwriting only if caller allows it. + if ((m_hFile = WszCreateFile(szName, GENERIC_READ | GENERIC_WRITE, 0, 0, + (fFlags & DBPROP_TMODEF_FAILIFTHERE) ? CREATE_NEW : CREATE_ALWAYS, + 0, 0)) == INVALID_HANDLE_VALUE) + { + return (MapFileError(GetLastError())); + } + + // Data will come from the file. + m_iType = STGIO_HFILE; + } + // For open in read mode, need to open the file on disk. If opening a shared + // memory view, it has to be opened already, so no file open. + else if ((fFlags & DBPROP_TMODEF_WRITE) == 0) + { + // We have not opened the file nor loaded it as module + _ASSERTE(m_hFile == INVALID_HANDLE_VALUE); + _ASSERTE(m_hModule == NULL); + + // Open the file for read. Sharing is determined by caller, it can + // allow other readers or be exclusive. + DWORD dwFileSharingFlags = FILE_SHARE_DELETE; + if (!(fFlags & DBPROP_TMODEF_EXCLUSIVE)) + { + dwFileSharingFlags |= FILE_SHARE_READ; + +#if !defined(DACCESS_COMPILE) && !defined(FEATURE_PAL) + // PEDecoder is not defined in DAC + + // We prefer to use LoadLibrary if we can because it will share already loaded images (used for execution) + // which saves virtual memory. We only do this if our caller has indicated that this PE file is trusted + // and thus it is OK to do LoadLibrary (note that we still only load it as a resource, which mitigates + // most of the security risk anyway). + if ((fFlags & DBPROP_TMODEF_TRYLOADLIBRARY) != 0) + { + m_hModule = WszLoadLibraryEx(szName, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE); + if (m_hModule != NULL) + { + m_iType = STGIO_HMODULE; + + m_mtMappedType = MTYPE_IMAGE; + + // LoadLibraryEx returns 2 lowest bits indicating how the module was loaded + m_pBaseData = m_pData = (void *)(((INT_PTR)m_hModule) & ~(INT_PTR)0x3); + + PEDecoder peDecoder; + if (SUCCEEDED(peDecoder.Init( + m_pBaseData, + false)) && // relocated + peDecoder.CheckNTHeaders()) + { + m_cbData = peDecoder.GetNTHeaders32()->OptionalHeader.SizeOfImage; + } + else + { + // PEDecoder failed on loaded library, let's backout all our changes to this object + // and fall back to file mapping + m_iType = STGIO_NODATA; + m_mtMappedType = MTYPE_NOMAPPING; + m_pBaseData = m_pData = NULL; + + FreeLibrary(m_hModule); + m_hModule = NULL; + } + } + } +#endif //!DACCESS_COMPILE && !FEATURE_PAL + } + + if (m_hModule == NULL) + { // We didn't get the loaded module (we either didn't want to or it failed) + HandleHolder hFile(WszCreateFile(szName, + GENERIC_READ, + dwFileSharingFlags, + 0, + OPEN_EXISTING, + 0, + 0)); + + if (hFile == INVALID_HANDLE_VALUE) + return (MapFileError(GetLastError())); + + // Get size of file. + m_cbData = ::SetFilePointer(hFile, 0, 0, FILE_END); + + // Can't read anything from an empty file. + if (m_cbData == 0) + return (PostError(CLDB_E_NO_DATA)); + + // Data will come from the file. + m_hFile = hFile.Extract(); + + m_iType = STGIO_HFILE; + } + } + +ErrExit: + + // If we will ever write, then we need the buffer cache. + if (fFlags & DBPROP_TMODEF_WRITE) + { + // Allocate a cache buffer for writing. + if ((m_rgBuff = (BYTE *) AllocateMemory(m_iCacheSize)) == NULL) + { + Close(); + return PostError(OutOfMemory()); + } + m_cbBuff = 0; + } + + // Save flags for later. + m_fFlags = fFlags; + if ((szName != NULL) && (*szName != 0)) + { + WCHAR rcExt[_MAX_PATH]; + SplitPath(szName, NULL, 0, NULL, 0, NULL, 0, rcExt, _MAX_PATH); + if (SString::_wcsicmp(rcExt, W(".obj")) == 0) + { + m_FileType = FILETYPE_NTOBJ; + } + else if (SString::_wcsicmp(rcExt, W(".tlb")) == 0) + { + m_FileType = FILETYPE_TLB; + } + } + + // For auto map case, map the view of the file as part of open. + if (m_bAutoMap && + (m_iType == STGIO_HFILE || m_iType == STGIO_STREAM) && + !(fFlags & DBPROP_TMODEF_CREATE)) + { + void * ptr; + ULONG cb; + + if (FAILED(hr = MapFileToMem(ptr, &cb, pAttributes))) + { + Close(); + return hr; + } + } + return S_OK; +} // StgIO::Open + + +//***************************************************************************** +// Shut down the file handles and allocated objects. +//***************************************************************************** +void StgIO::Close() +{ + switch (m_iType) + { +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + // Free any allocated memory. + case STGIO_SHAREDMEM: + if (m_pBaseData != NULL) + { + CoTaskMemFree(m_pBaseData); + m_pBaseData = NULL; + break; + } +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + + case STGIO_MEM: + case STGIO_HFILEMEM: + if (m_bFreeMem && m_pBaseData) + { + FreeMemory(m_pBaseData); + m_pBaseData = m_pData = 0; + } + // Intentional fall through to file case, if we kept handle open. + + case STGIO_HFILE: + { + // Free the file handle. + if (m_hFile != INVALID_HANDLE_VALUE) + CloseHandle(m_hFile); + + // If we allocated space for in memory paging, then free it. + } + break; + + case STGIO_HMODULE: + { + if (m_hModule != NULL) + FreeLibrary(m_hModule); + m_hModule = NULL; + break; + } + + // Free the stream pointer. + case STGIO_STREAM: + { + if (m_pIStream != NULL) + m_pIStream->Release(); + } + break; + + // Weird to shut down what you didn't open, isn't it? Allow for + // error case where dtor shuts down as an afterthought. + case STGIO_NODATA: + default: + return; + } + + // Free any page map and base data. + FreePageMap(); + + // Reset state values so we don't get confused. + CtorInit(); +} + +//***************************************************************************** +// Called to read the data into allocated memory and release the backing store. +// Only available on read-only data. +//***************************************************************************** +HRESULT +StgIO::LoadFileToMemory() +{ + HRESULT hr; + void *pData; // Allocated buffer for file. + ULONG cbData; // Size of the data. + ULONG cbRead = 0; // Data actually read. + + // Make sure it is a read-only file. + if (m_fFlags & DBPROP_TMODEF_WRITE) + return E_INVALIDARG; + + // Try to allocate the buffer. + cbData = m_cbData; + pData = AllocateMemory(cbData); + IfNullGo(pData); + + // Try to read the file into the buffer. + IfFailGo(Read(pData, cbData, &cbRead)); + if (cbData != cbRead) + { + _ASSERTE_MSG(FALSE, "Read didn't succeed."); + IfFailGo(CLDB_E_FILE_CORRUPT); + } + + // Done with the old data. + Close(); + + // Open with new data. + hr = Open(NULL /* szName */, STGIO_READ, pData, cbData, NULL /* IStream* */, NULL /* lpSecurityAttributes */); + _ASSERTE(SUCCEEDED(hr)); // should not be a failure code path with open on buffer. + + // Mark the new memory so that it will be freed later. + m_pBaseData = m_pData; + m_bFreeMem = true; + +ErrExit: + if (FAILED(hr) && pData) + FreeMemory(pData); + + return hr; +} // StgIO::LoadFileToMemory + + +//***************************************************************************** +// Read data from the storage source. This will handle all types of backing +// storage from mmf, streams, and file handles. No read ahead or MRU +// caching is done. +//***************************************************************************** +HRESULT StgIO::Read( // Return code. + void *pbBuff, // Write buffer here. + ULONG cbBuff, // How much to read. + ULONG *pcbRead) // How much read. +{ + ULONG cbCopy; // For boundary checks. + void *pbData; // Data buffer for mem read. + HRESULT hr = S_OK; + + // Validate arguments, don't call if you don't need to. + _ASSERTE(pbBuff != 0); + _ASSERTE(cbBuff > 0); + + // Get the data based on type. + switch (m_iType) + { + // For data on file, there are two possiblities: + // (1) We have an in memory backing store we should use, or + // (2) We just need to read from the file. + case STGIO_HFILE: + case STGIO_HMODULE: + { + _ASSERTE((m_hFile != INVALID_HANDLE_VALUE) || (m_hModule != NULL)); + + // Backing store does its own paging. + if (IsBackingStore() || IsMemoryMapped()) + { + // Force the data into memory. + if (FAILED(hr = GetPtrForMem(GetCurrentOffset(), cbBuff, pbData))) + goto ErrExit; + + // Copy it back for the user and save the size. + memcpy(pbBuff, pbData, cbBuff); + if (pcbRead) + *pcbRead = cbBuff; + } + // If there is no backing store, this is just a read operation. + else + { + _ASSERTE((m_iType == STGIO_HFILE) && (m_hFile != INVALID_HANDLE_VALUE)); + _ASSERTE(m_hModule == NULL); + + ULONG cbTemp = 0; + if (!pcbRead) + pcbRead = &cbTemp; + hr = ReadFromDisk(pbBuff, cbBuff, pcbRead); + m_cbOffset += *pcbRead; + } + } + break; + + // Data in a stream is always just read. + case STGIO_STREAM: + { + _ASSERTE((IStream *) m_pIStream); + if (!pcbRead) + pcbRead = &cbCopy; + *pcbRead = 0; + hr = m_pIStream->Read(pbBuff, cbBuff, pcbRead); + if (SUCCEEDED(hr)) + m_cbOffset += *pcbRead; + } + break; + + // Simply copy the data from our data. + case STGIO_MEM: +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + case STGIO_SHAREDMEM: +#endif + case STGIO_HFILEMEM: + { + _ASSERTE(m_pData && m_cbData); + + // Check for read past end of buffer and adjust. + if (GetCurrentOffset() + cbBuff > m_cbData) + cbCopy = m_cbData - GetCurrentOffset(); + else + cbCopy = cbBuff; + + // Copy the data into the callers buffer. + memcpy(pbBuff, (void *) ((DWORD_PTR)m_pData + GetCurrentOffset()), cbCopy); + if (pcbRead) + *pcbRead = cbCopy; + + // Save a logical offset. + m_cbOffset += cbCopy; + } + break; + + case STGIO_NODATA: + default: + _ASSERTE(0); + break; + } + +ErrExit: + return (hr); +} + + +//***************************************************************************** +// Write to disk. This function will cache up to a page of data in a buffer +// and peridocially flush it on overflow and explicit request. This makes it +// safe to do lots of small writes without too much performance overhead. +//***************************************************************************** +HRESULT StgIO::Write( // true/false. + const void *pbBuff, // Data to write. + ULONG cbWrite, // How much data to write. + ULONG *pcbWritten) // How much did get written. +{ + ULONG cbWriteIn=cbWrite; // Track amount written. + ULONG cbCopy; + HRESULT hr = S_OK; + + _ASSERTE(m_rgBuff != 0); + _ASSERTE(cbWrite); + + while (cbWrite) + { + // In the case where the buffer is already huge, write the whole thing + // and avoid the cache. + if (m_cbBuff == 0 && cbWrite >= (ULONG) m_iPageSize) + { + if (SUCCEEDED(hr = WriteToDisk(pbBuff, cbWrite, pcbWritten))) + m_cbOffset += cbWrite; + break; + } + // Otherwise cache as much as we can and flush. + else + { + // Determine how much data goes into the cache buffer. + cbCopy = m_iPageSize - m_cbBuff; + cbCopy = min(cbCopy, cbWrite); + + // Copy the data into the cache and adjust counts. + memcpy(&m_rgBuff[m_cbBuff], pbBuff, cbCopy); + pbBuff = (void *) ((DWORD_PTR)pbBuff + cbCopy); + m_cbBuff += cbCopy; + m_cbOffset += cbCopy; + cbWrite -= cbCopy; + + // If there is enough data, then flush it to disk and reset count. + if (m_cbBuff >= (ULONG) m_iPageSize) + { + if (FAILED(hr = FlushCache())) + break; + } + } + } + + // Return value for caller. + if (SUCCEEDED(hr) && pcbWritten) + *pcbWritten = cbWriteIn; + return (hr); +} + + +//***************************************************************************** +// Moves the file pointer to the new location. This handles the different +// types of storage systems. +//***************************************************************************** +HRESULT StgIO::Seek( // New offset. + int lVal, // How much to move. + ULONG fMoveType) // Direction, use Win32 FILE_xxxx. +{ + ULONG cbRtn = 0; + HRESULT hr = NOERROR; + + _ASSERTE(fMoveType >= FILE_BEGIN && fMoveType <= FILE_END); + + // Action taken depends on type of storage. + switch (m_iType) + { + case STGIO_HFILE: + { + // Use the file system's move. + _ASSERTE(m_hFile != INVALID_HANDLE_VALUE); + cbRtn = ::SetFilePointer(m_hFile, lVal, 0, fMoveType); + + // Save the location redundantly. + if (cbRtn != 0xffffffff) + { + // make sure that m_cbOffset will stay within range + if (cbRtn > m_cbData || cbRtn < 0) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = cbRtn; + } + } + break; + + case STGIO_STREAM: + { + LARGE_INTEGER iMove; + ULARGE_INTEGER iNewLoc; + + // Need a 64-bit int. + iMove.QuadPart = lVal; + + // The move types are named differently, but have same value. + if (FAILED(hr = m_pIStream->Seek(iMove, fMoveType, &iNewLoc))) + return (hr); + + // make sure that m_cbOffset will stay within range + if (iNewLoc.u.LowPart > m_cbData || iNewLoc.u.LowPart < 0) + IfFailGo(STG_E_INVALIDFUNCTION); + + // Save off only out location. + m_cbOffset = iNewLoc.u.LowPart; + } + break; + + case STGIO_MEM: +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + case STGIO_SHAREDMEM: +#endif + case STGIO_HFILEMEM: + case STGIO_HMODULE: + { + // We own the offset, so change our value. + switch (fMoveType) + { + case FILE_BEGIN: + + // make sure that m_cbOffset will stay within range + if ((ULONG) lVal > m_cbData || lVal < 0) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = lVal; + break; + + case FILE_CURRENT: + + // make sure that m_cbOffset will stay within range + if (m_cbOffset + lVal > m_cbData) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = m_cbOffset + lVal; + break; + + case FILE_END: + _ASSERTE(lVal < (LONG) m_cbData); + // make sure that m_cbOffset will stay within range + if (m_cbData + lVal > m_cbData) + { + IfFailGo(STG_E_INVALIDFUNCTION); + } + m_cbOffset = m_cbData + lVal; + break; + } + + cbRtn = m_cbOffset; + } + break; + + // Weird to seek with no data. + case STGIO_NODATA: + default: + _ASSERTE(0); + break; + } + +ErrExit: + return hr; +} + + +//***************************************************************************** +// Retrieves the current offset for the storage being used. This value is +// tracked based on Read, Write, and Seek operations. +//***************************************************************************** +ULONG StgIO::GetCurrentOffset() // Current offset. +{ + return (m_cbOffset); +} + + +//***************************************************************************** +// Map the file contents to a memory mapped file and return a pointer to the +// data. For read/write with a backing store, map the file using an internal +// paging system. +//***************************************************************************** +HRESULT StgIO::MapFileToMem( // Return code. + void *&ptr, // Return pointer to file data. + ULONG *pcbSize, // Return size of data. + LPSECURITY_ATTRIBUTES pAttributes) // Security token. +{ + char rcShared[MAXSHMEM]; // ANSI version of shared name. + HRESULT hr = S_OK; + + // Don't penalize for multiple calls. Also, allow calls for mem type so + // callers don't need to do so much checking. + if (IsBackingStore() || + IsMemoryMapped() || + (m_iType == STGIO_MEM) || +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + (m_iType == STGIO_SHAREDMEM) || +#endif + (m_iType == STGIO_HFILEMEM)) + { + ptr = m_pData; + if (pcbSize) + *pcbSize = m_cbData; + return (S_OK); + } + + //#CopySmallFiles + // Check the size of the data we want to map. If it is small enough, then + // simply allocate a chunk of memory from a finer grained heap. This saves + // virtual memory space, page table entries, and should reduce overall working set. + // Note that any shared memory objects will require the handles to be in place + // and are not elligible. Also, open for read/write needs a full backing + // store. + if (!*m_rcShared && (m_cbData <= SMALL_ALLOC_MAP_SIZE) && (SMALL_ALLOC_MAP_SIZE > 0)) + { + DWORD cbRead = m_cbData; + _ASSERTE(m_pData == 0); + + // Just malloc a chunk of data to use. + m_pBaseData = m_pData = AllocateMemory(m_cbData); + if (!m_pData) + { + hr = OutOfMemory(); + goto ErrExit; + } + + // Read all of the file contents into this piece of memory. + IfFailGo( Seek(0, FILE_BEGIN) ); + if (FAILED(hr = Read(m_pData, cbRead, &cbRead))) + { + FreeMemory(m_pData); + m_pData = 0; + goto ErrExit; + } + _ASSERTE(cbRead == m_cbData); + + // If the file isn't being opened for exclusive mode, then free it. + // If it is for exclusive, then we need to keep the handle open so the + // file is locked, preventing other readers. Also leave it open if + // in read/write mode so we can truncate and rewrite. + if (m_hFile == INVALID_HANDLE_VALUE || + ((m_fFlags & DBPROP_TMODEF_EXCLUSIVE) == 0 && (m_fFlags & DBPROP_TMODEF_WRITE) == 0)) + { + // If there was a handle open, then free it. + if (m_hFile != INVALID_HANDLE_VALUE) + { + VERIFY(CloseHandle(m_hFile)); + m_hFile = INVALID_HANDLE_VALUE; + } + // Free the stream pointer. + else + if (m_pIStream != 0) + { + m_pIStream->Release(); + m_pIStream = 0; + } + + // Switch the type to memory only access. + m_iType = STGIO_MEM; + } + else + m_iType = STGIO_HFILEMEM; + + // Free the memory when we shut down. + m_bFreeMem = true; + } + // Finally, a real mapping file must be created. + else + { + // Now we will map, so better have it right. + _ASSERTE(m_hFile != INVALID_HANDLE_VALUE || m_iType == STGIO_STREAM); + _ASSERTE(m_rgPageMap == 0); + + // For read mode, use a memory mapped file since the size will never + // change for the life of the handle. + if ((m_fFlags & DBPROP_TMODEF_WRITE) == 0 && m_iType != STGIO_STREAM) + { + _ASSERTE(!*m_rcShared); + + // Create a mapping object for the file. + _ASSERTE(m_hMapping == 0); + + DWORD dwProtectionFlags = PAGE_READONLY; +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + //#EnableCodeIntegrity + // RoMetadata.dll is required to always map (WinMD) files with SEC_IMAGE to enable Code Integrity checkes on Win8 + // Note: MidlRtMd.dll cannot do the same, because it runs on pre-Win8 OS versions where SEC_IMAGE-mapping will likely + // refuse WinMD files (they are Win8+ only in PE headers) + dwProtectionFlags |= SEC_IMAGE; +#endif + + if ((m_hMapping = WszCreateFileMapping(m_hFile, pAttributes, dwProtectionFlags, + 0, 0, *m_rcShared ? m_rcShared : 0)) == 0) + { + return (MapFileError(GetLastError())); + } +#ifdef FEATURE_METADATA_STANDALONE_WINRT_RO + m_mtMappedType = MTYPE_IMAGE; +#else // FEATURE_METADATA_STANDALONE_WINRT_RO + m_mtMappedType = MTYPE_FLAT; +#endif // FEATURE_METADATA_STANDALONE_WINRT_RO + // Check to see if the memory already exists, in which case we have + // no guarantees it is the right piece of data. + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + hr = PostError(CLDB_E_SMDUPLICATE, rcShared); + goto ErrExit; + } + + // Now map the file into memory so we can read from pointer access. + // <REVISIT_TODO>Note: Added a check for IsBadReadPtr per the Services team which + // indicates that under some conditions this API can give you back + // a totally bogus pointer.</REVISIT_TODO> + if ((m_pBaseData = m_pData = MapViewOfFile(m_hMapping, FILE_MAP_READ, + 0, 0, 0)) == 0) + { + hr = MapFileError(GetLastError()); + if (SUCCEEDED(hr)) + { + _ASSERTE_MSG(FALSE, "Error code doesn't indicate error."); + hr = PostError(CLDB_E_FILE_CORRUPT); + } + + // In case we got back a bogus pointer. + m_pBaseData = m_pData = NULL; + goto ErrExit; + } + } + // In write mode, we need the hybrid combination of being able to back up + // the data in memory via cache, but then later rewrite the contents and + // throw away our cached copy. Memory mapped files are not good for this + // case due to poor write characteristics. + else + { + ULONG iMaxSize; // How much memory required for file. + + // Figure out how many pages we'll require, round up actual data + // size to page size. + iMaxSize = (((m_cbData - 1) & ~(m_iPageSize - 1)) + m_iPageSize); + // Check integer overflow in previous statement + if (iMaxSize < m_cbData) + { + IfFailGo(PostError(COR_E_OVERFLOW)); + } + + // Allocate a bit vector to track loaded pages. + if ((m_rgPageMap = new (nothrow) BYTE[iMaxSize / m_iPageSize]) == 0) + return (PostError(OutOfMemory())); + memset(m_rgPageMap, 0, sizeof(BYTE) * (iMaxSize / m_iPageSize)); + + // Allocate space for the file contents. + if ((m_pBaseData = m_pData = ::ClrVirtualAlloc(0, iMaxSize, MEM_RESERVE, PAGE_NOACCESS)) == 0) + { + hr = PostError(OutOfMemory()); + goto ErrExit; + } + } + } + + // Reset any changes made by mapping. + IfFailGo( Seek(0, FILE_BEGIN) ); + +ErrExit: + + // Check for errors and clean up. + if (FAILED(hr)) + { + if (m_hMapping) + CloseHandle(m_hMapping); + m_hMapping = 0; + m_pBaseData = m_pData = 0; + m_cbData = 0; + } + ptr = m_pData; + if (pcbSize) + *pcbSize = m_cbData; + return (hr); +} + + +//***************************************************************************** +// Free the mapping object for shared memory but keep the rest of the internal +// state intact. +//***************************************************************************** +HRESULT StgIO::ReleaseMappingObject() // Return code. +{ + // Check type first. +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + if (m_iType != STGIO_SHAREDMEM) + { + _ASSERTE(FALSE); + return S_OK; + } + + // Must have an allocated handle. + _ASSERTE(m_hMapping != 0); + + // Freeing the mapping object doesn't do any good if you still have the file. + _ASSERTE(m_hFile == INVALID_HANDLE_VALUE); + + // Unmap the memory we allocated before freeing the handle. But keep the + // memory address intact. + if (m_pData) + VERIFY(UnmapViewOfFile(m_pData)); + + // Free the handle. + if (m_hMapping != 0) + { + VERIFY(CloseHandle(m_hMapping)); + m_hMapping = 0; + } +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + return S_OK; +} + + + +//***************************************************************************** +// Resets the logical base address and size to the value given. This is for +// cases like finding a section embedded in another format, like the .clb inside +// of an image. GetPtrForMem, Read, and Seek will then behave as though only +// data from pbStart to cbSize is valid. +//***************************************************************************** +HRESULT StgIO::SetBaseRange( // Return code. + void *pbStart, // Start of file data. + ULONG cbSize) // How big is the range. +{ +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + if (m_iType == STGIO_SHAREDMEM) + { + // The base range must be inside of the current range. + _ASSERTE((m_pBaseData != NULL) && (m_cbData != 0)); + _ASSERTE(((LONG_PTR) pbStart >= (LONG_PTR) m_pBaseData)); + _ASSERTE(((LONG_PTR) pbStart + cbSize <= (LONG_PTR) m_pBaseData + m_cbData)); + } +#endif //!FEATURE_METADATA_STANDALONE_WINRT_RO + + // Save the base range per user request. + m_pData = pbStart; + m_cbData = cbSize; + return S_OK; +} + + +//***************************************************************************** +// Caller wants a pointer to a chunk of the file. This function will make sure +// that the memory for that chunk has been committed and will load from the +// file if required. This algorithm attempts to load no more data from disk +// than is necessary. It walks the required pages from lowest to highest, +// and for each block of unloaded pages, the memory is committed and the data +// is read from disk. If all pages are unloaded, all of them are loaded at +// once to speed throughput from disk. +//***************************************************************************** +HRESULT StgIO::GetPtrForMem( // Return code. + ULONG cbStart, // Where to start getting memory. + ULONG cbSize, // How much data. + void *&ptr) // Return pointer to memory here. +{ + int iFirst, iLast; // First and last page required. + ULONG iOffset, iSize; // For committing ranges of memory. + int i, j; // Loop control. + HRESULT hr; + + // We need either memory (mmf or user supplied) or a backing store to + // return a pointer. Call Read if you don't have these. + if (!IsBackingStore() && m_pData == 0) + return (PostError(BadError(E_UNEXPECTED))); + + // Validate the caller isn't asking for a data value out of range. + if (!(ClrSafeInt<ULONG>::addition(cbStart, cbSize, iOffset) + && (iOffset <= m_cbData))) + return (PostError(E_INVALIDARG)); + + // This code will check for pages that need to be paged from disk in + // order for us to return a pointer to that memory. + if (IsBackingStore()) + { + // Backing store is bogus when in rewrite mode. + if (m_bRewrite) + return (PostError(BadError(E_UNEXPECTED))); + + // Must have the page map to continue. + _ASSERTE(m_rgPageMap && m_iPageSize && m_pData); + + // Figure out the first and last page that are required for commit. + iFirst = cbStart / m_iPageSize; + iLast = (cbStart + cbSize - 1) / m_iPageSize; + + // Avoid confusion. + ptr = 0; + + // Do a smart load of every page required. Do not reload pages that have + // already been brought in from disk. + //<REVISIT_TODO>@FUTURE: add an optimization so that when all pages have been faulted, we no + // longer to a page by page search.</REVISIT_TODO> + for (i=iFirst; i<=iLast; ) + { + // Find the first page that hasn't already been loaded. + while (GetBit(m_rgPageMap, i) && i<=iLast) + ++i; + if (i > iLast) + break; + + // Offset for first thing to load. + iOffset = i * m_iPageSize; + iSize = 0; + + // See how many in a row have not been loaded. + for (j=i; i<=iLast && !GetBit(m_rgPageMap, i); i++) + { + // Safe: iSize += m_iPageSize; + if (!(ClrSafeInt<ULONG>::addition(iSize, m_iPageSize, iSize))) + { + return PostError(E_INVALIDARG); + } + } + + // First commit the memory for this part of the file. + if (::ClrVirtualAlloc((void *) ((DWORD_PTR) m_pData + iOffset), + iSize, MEM_COMMIT, PAGE_READWRITE) == 0) + return (PostError(OutOfMemory())); + + // Now load that portion of the file from disk. + if (FAILED(hr = Seek(iOffset, FILE_BEGIN)) || + FAILED(hr = ReadFromDisk((void *) ((DWORD_PTR) m_pData + iOffset), iSize, 0))) + { + return (hr); + } + + // Change the memory to read only to avoid any modifications. Any faults + // that occur indicate a bug whereby the engine is trying to write to + // protected memory. + _ASSERTE(::ClrVirtualAlloc((void *) ((DWORD_PTR) m_pData + iOffset), + iSize, MEM_COMMIT, PAGE_READONLY) != 0); + + // Record each new loaded page. + for (; j<i; j++) + SetBit(m_rgPageMap, j, true); + } + + // Everything was brought into memory, so now return pointer to caller. + ptr = (void *) ((DWORD_PTR) m_pData + cbStart); + } + // Memory version or memory mapped file work the same way. + else if (IsMemoryMapped() || + (m_iType == STGIO_MEM) || +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + (m_iType == STGIO_SHAREDMEM) || +#endif + (m_iType == STGIO_HFILEMEM)) + { + if (!(cbStart <= m_cbData)) + return (PostError(E_INVALIDARG)); + + ptr = (void *) ((DWORD_PTR) m_pData + cbStart); + } + // What's left?! Add some defense. + else + { + _ASSERTE(0); + ptr = 0; + return (PostError(BadError(E_UNEXPECTED))); + } + return (S_OK); +} + + +//***************************************************************************** +// For cached writes, flush the cache to the data store. +//***************************************************************************** +HRESULT StgIO::FlushCache() +{ + ULONG cbWritten; + HRESULT hr; + + if (m_cbBuff) + { + if (FAILED(hr = WriteToDisk(m_rgBuff, m_cbBuff, &cbWritten))) + return (hr); + m_cbBuff = 0; + } + return (S_OK); +} + +//***************************************************************************** +// Tells the file system to flush any cached data it may have. This is +// expensive, but if successful guarantees you won't lose writes short of +// a disk failure. +//***************************************************************************** +HRESULT StgIO::FlushFileBuffers() +{ + _ASSERTE(!IsReadOnly()); + + if (m_hFile != INVALID_HANDLE_VALUE) + { + if (::FlushFileBuffers(m_hFile)) + return (S_OK); + else + return (MapFileError(GetLastError())); + } + return (S_OK); +} + + +//***************************************************************************** +// Called after a successful rewrite of an existing file. The in memory +// backing store is no longer valid because all new data is in memory and +// on disk. This is essentially the same state as created, so free up some +// working set and remember this state. +//***************************************************************************** +HRESULT StgIO::ResetBackingStore() // Return code. +{ + // Don't be calling this function for read only data. + _ASSERTE(!IsReadOnly()); + + // Free up any backing store data we no longer need now that everything + // is in memory. + FreePageMap(); + return (S_OK); +} + + +// +// Private. +// + + + +//***************************************************************************** +// This version will force the data in cache out to disk for real. The code +// can handle the different types of storage we might be sitting on based on +// the open type. +//***************************************************************************** +HRESULT StgIO::WriteToDisk( // Return code. + const void *pbBuff, // Buffer to write. + ULONG cbWrite, // How much. + ULONG *pcbWritten) // Return how much written. +{ + ULONG cbWritten; // Buffer for write funcs. + HRESULT hr = S_OK; + + // Pretty obvious. + _ASSERTE(!IsReadOnly()); + + // Always need a buffer to write this data to. + if (!pcbWritten) + pcbWritten = &cbWritten; + + // Action taken depends on type of storage. + switch (m_iType) + { + case STGIO_HFILE: + case STGIO_HFILEMEM: + { + // Use the file system's move. + _ASSERTE(m_hFile != INVALID_HANDLE_VALUE); + + // Do the write to disk. + if (!::WriteFile(m_hFile, pbBuff, cbWrite, pcbWritten, 0)) + hr = MapFileError(GetLastError()); + } + break; + + // Free the stream pointer. + case STGIO_STREAM: + { + // Delegate write to stream code. + hr = m_pIStream->Write(pbBuff, cbWrite, pcbWritten); + } + break; + + // We cannot write to fixed read/only memory or LoadLibrary module. + case STGIO_HMODULE: + case STGIO_MEM: +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + case STGIO_SHAREDMEM: +#endif + _ASSERTE(0); + hr = BadError(E_UNEXPECTED); + break; + + // Weird to seek with no data. + case STGIO_NODATA: + default: + _ASSERTE(0); + break; + } + return (hr); +} + + +//***************************************************************************** +// This version only reads from disk. +//***************************************************************************** +HRESULT StgIO::ReadFromDisk( // Return code. + void *pbBuff, // Write buffer here. + ULONG cbBuff, // How much to read. + ULONG *pcbRead) // How much read. +{ + ULONG cbRead; + + _ASSERTE(m_iType == STGIO_HFILE || m_iType == STGIO_STREAM); + + // Need to have a buffer. + if (!pcbRead) + pcbRead = &cbRead; + + // Read only from file to avoid recursive logic. + if (m_iType == STGIO_HFILE || m_iType == STGIO_HFILEMEM) + { + if (::ReadFile(m_hFile, pbBuff, cbBuff, pcbRead, 0)) + return (S_OK); + return (MapFileError(GetLastError())); + } + // Read directly from stream. + else + { + return (m_pIStream->Read(pbBuff, cbBuff, pcbRead)); + } +} + + +//***************************************************************************** +// Copy the contents of the file for this storage to the target path. +//***************************************************************************** +HRESULT StgIO::CopyFileInternal( // Return code. + LPCWSTR szTo, // Target save path for file. + int bFailIfThere, // true to fail if target exists. + int bWriteThrough) // Should copy be written through OS cache. +{ + DWORD iCurrent; // Save original location. + DWORD cbRead; // Byte count for buffer. + DWORD cbWrite; // Check write of bytes. + const DWORD cbBuff = 4096; // Size of buffer for copy (in bytes). + BYTE *pBuff = (BYTE*)alloca(cbBuff); // Buffer for copy. + HANDLE hFile; // Target file. + HRESULT hr = S_OK; + + // Create target file. + if ((hFile = ::WszCreateFile(szTo, GENERIC_WRITE, 0, 0, + (bFailIfThere) ? CREATE_NEW : CREATE_ALWAYS, + (bWriteThrough) ? FILE_FLAG_WRITE_THROUGH : 0, + 0)) == INVALID_HANDLE_VALUE) + { + return (MapFileError(GetLastError())); + } + + // Save current location and reset it later. + iCurrent = ::SetFilePointer(m_hFile, 0, 0, FILE_CURRENT); + ::SetFilePointer(m_hFile, 0, 0, FILE_BEGIN); + + // Copy while there are bytes. + while (::ReadFile(m_hFile, pBuff, cbBuff, &cbRead, 0) && cbRead) + { + if (!::WriteFile(hFile, pBuff, cbRead, &cbWrite, 0) || cbWrite != cbRead) + { + hr = STG_E_WRITEFAULT; + break; + } + } + + // Reset file offset. + ::SetFilePointer(m_hFile, iCurrent, 0, FILE_BEGIN); + + // Close target. + if (!bWriteThrough) + VERIFY(::FlushFileBuffers(hFile)); + ::CloseHandle(hFile); + return (hr); +} + + +//***************************************************************************** +// Free the data used for backing store from disk in read/write scenario. +//***************************************************************************** +void StgIO::FreePageMap() +{ + // If a small file was allocated, then free that memory. + if (m_bFreeMem && m_pBaseData) + FreeMemory(m_pBaseData); + // For mmf, close handles and free resources. + else if (m_hMapping && m_pBaseData) + { + VERIFY(UnmapViewOfFile(m_pBaseData)); + VERIFY(CloseHandle(m_hMapping)); + } + // For our own system, free memory. + else if (m_rgPageMap && m_pBaseData) + { + delete [] m_rgPageMap; + m_rgPageMap = 0; + VERIFY(::ClrVirtualFree(m_pBaseData, (((m_cbData - 1) & ~(m_iPageSize - 1)) + m_iPageSize), MEM_DECOMMIT)); + VERIFY(::ClrVirtualFree(m_pBaseData, 0, MEM_RELEASE)); + m_pBaseData = 0; + m_cbData = 0; + } + + m_pBaseData = 0; + m_hMapping = 0; + m_cbData = 0; +} + + +//***************************************************************************** +// Check the given pointer and ensure it is aligned correct. Return true +// if it is aligned, false if it is not. +//***************************************************************************** +int StgIO::IsAlignedPtr(ULONG_PTR Value, int iAlignment) +{ + HRESULT hr; + void *ptrStart = NULL; + + if ((m_iType == STGIO_STREAM) || +#ifndef FEATURE_METADATA_STANDALONE_WINRT_RO + (m_iType == STGIO_SHAREDMEM) || +#endif + (m_iType == STGIO_MEM)) + { + return ((Value - (ULONG_PTR) m_pData) % iAlignment == 0); + } + else + { + hr = GetPtrForMem(0, 1, ptrStart); + _ASSERTE(hr == S_OK && "GetPtrForMem failed"); + _ASSERTE(Value > (ULONG_PTR) ptrStart); + return (((Value - (ULONG_PTR) ptrStart) % iAlignment) == 0); + } +} // int StgIO::IsAlignedPtr() + + + + + +//***************************************************************************** +// These helper functions are used to allocate fairly large pieces of memory, +// more than should be taken from the runtime heap, but less that would require +// virtual memory overhead. +//***************************************************************************** +// #define _TRACE_MEM_ 1 + +void *AllocateMemory(int iSize) +{ + void * ptr; + ptr = new (nothrow) BYTE[iSize]; + +#if defined(_DEBUG) && defined(_TRACE_MEM_) + static int i=0; + DbgWriteEx(W("AllocateMemory: (%d) 0x%08x, size %d\n"), ++i, ptr, iSize); +#endif + return (ptr); +} + + +void FreeMemory(void *pbData) +{ +#if defined(_DEBUG) && defined(_TRACE_MEM_) + static int i=0; + DbgWriteEx(W("FreeMemory: (%d) 0x%08x\n"), ++i, pbData); +#endif + + _ASSERTE(pbData); + delete [] (BYTE *) pbData; +} + diff --git a/src/md/enc/stgtiggerstorage.cpp b/src/md/enc/stgtiggerstorage.cpp new file mode 100644 index 0000000000..fde3e933f0 --- /dev/null +++ b/src/md/enc/stgtiggerstorage.cpp @@ -0,0 +1,1026 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// StgTiggerStorage.cpp +// + +// +// TiggerStorage is a stripped down version of compound doc files. Doc files +// have some very useful and complex features to them, unfortunately nothing +// comes for free. Given the incredibly tuned format of existing .tlb files, +// every single byte counts and 10% added by doc files is just too expensive. +// +//***************************************************************************** +#include "stdafx.h" // Standard header. +#include "stgio.h" // I/O subsystem. +#include "stgtiggerstorage.h" // Our interface. +#include "stgtiggerstream.h" // Stream interface. +#include "corerror.h" +#include "posterror.h" +#include "mdfileformat.h" +#include "sstring.h" + +//#CLRRuntimeHostInternal_GetImageVersionString +// External implementation of call to code:ICLRRuntimeHostInternal::GetImageVersionString. +// Implemented in clr.dll and mscordbi.dll. +HRESULT +CLRRuntimeHostInternal_GetImageVersionString( + __out_ecount(*pcchBuffer) + LPWSTR wszBuffer, + DWORD * pcchBuffer); + +TiggerStorage::TiggerStorage() : + m_pStgIO(0), + m_cRef(1), + m_pStreamList(0), + m_pbExtra(0) +{ + memset(&m_StgHdr, 0, sizeof(STORAGEHEADER)); +} + + +TiggerStorage::~TiggerStorage() +{ + if (m_pStgIO) + { + m_pStgIO->Release(); + m_pStgIO = 0; + } +} + + +//***************************************************************************** +// Init this storage object on top of the given storage unit. +//***************************************************************************** +HRESULT +TiggerStorage::Init( + StgIO *pStgIO, // The I/O subsystem. + __in __in_z LPSTR pVersion) // 'Compiled for' CLR version +{ + PSTORAGESIGNATURE pSig; // Signature data for file. + ULONG cbData; // Offset of header data. + void *ptr; // Signature. + HRESULT hr = S_OK; + + // Make sure we always start at the beginning. + // + pStgIO->Seek(0, FILE_BEGIN); + + // Save the storage unit. + m_pStgIO = pStgIO; + m_pStgIO->AddRef(); + + // For cases where the data already exists, verify the signature. + if ((pStgIO->GetFlags() & DBPROP_TMODEF_CREATE) == 0) + { + // Map the contents into memory for easy access. + IfFailGo(pStgIO->MapFileToMem(ptr, &cbData)); + + // Get a pointer to the signature of the file, which is the first part. + IfFailGo(pStgIO->GetPtrForMem(0, sizeof(STORAGESIGNATURE), ptr)); + + // Finally, we can check the signature. + pSig = (PSTORAGESIGNATURE)ptr; + IfFailGo(MDFormat::VerifySignature(pSig, cbData)); + + // Read and verify the header. + IfFailGo(ReadHeader()); + } + // For write case, dump the signature into the file up front. + else + { + IfFailGo(WriteSignature(pVersion)); + } + +ErrExit: + if (FAILED(hr) && (m_pStgIO != NULL)) + { + m_pStgIO->Release(); + m_pStgIO = NULL; + } + return hr; +} // TiggerStorage::Init + +//***************************************************************************** +// This function is a workaround to allow access to the "version requested" string. +//***************************************************************************** +HRESULT +TiggerStorage::GetHeaderPointer( + const void **ppv, // Put pointer to header here. + ULONG *pcb) // Put size of pointer here. +{ + void *ptr; // Working pointer. + HRESULT hr; + + // Read the signature + if (FAILED(hr = m_pStgIO->GetPtrForMem(0, sizeof(STORAGESIGNATURE), ptr))) + return hr; + + PSTORAGESIGNATURE pStorage = (PSTORAGESIGNATURE) ptr; + // Header data starts after signature. + *pcb = sizeof(STORAGESIGNATURE) + pStorage->GetVersionStringLength(); + + *ppv = ptr; + + return S_OK; + +} // TiggerStorage::GetHeaderPointer + +//***************************************************************************** +// Get the default "Compiled for" version used to emit the meta-data +//***************************************************************************** +HRESULT +TiggerStorage::GetDefaultVersion( + LPCSTR *ppVersion) +{ + static LPSTR g_pDefaultVersion; + + if (g_pDefaultVersion == NULL) + { +#ifdef FEATURE_METADATA_STANDALONE_WINRT + g_pDefaultVersion = ""; +#else //!FEATURE_METADATA_STANDALONE_WINRT +#ifndef DACCESS_COMPILE + HRESULT hr; + + WCHAR wszVersion[_MAX_PATH]; + DWORD cchVersion = _MAX_PATH; + //#CallTo_CLRRuntimeHostInternal_GetImageVersionString + IfFailRet(CLRRuntimeHostInternal_GetImageVersionString(wszVersion, &cchVersion)); + + CHAR szVersion[_MAX_PATH]; + DWORD dwSize = WszWideCharToMultiByte(CP_UTF8, 0, wszVersion, -1, szVersion, _MAX_PATH, NULL, NULL); + if (dwSize == 0) + { + _ASSERTE_MSG(FALSE, "WideCharToMultiByte conversion failed"); + szVersion[0] = 0; + dwSize = 1; + } + + NewArrayHolder<CHAR> pVersion = new (nothrow) CHAR[dwSize]; + IfNullRet(pVersion); + + memcpy(pVersion, szVersion, dwSize); + + if (InterlockedCompareExchangeT<CHAR *>(&g_pDefaultVersion, pVersion, NULL) == NULL) + { // We won the initialization race + pVersion.SuppressRelease(); + } +#else + DacNotImpl(); +#endif //DACCESS_COMPILE +#endif //!FEATURE_METADATA_STANDALONE_WINRT + } + + *ppVersion = g_pDefaultVersion; + return S_OK; +} // TiggerStorage::GetDefaultVersion + +HRESULT +TiggerStorage::SizeOfStorageSignature(LPCSTR pVersion, ULONG *pcbSignatureSize) +{ + HRESULT hr; + + if (pVersion == NULL) + { + IfFailRet(GetDefaultVersion(&pVersion)); + } + _ASSERTE(pVersion != NULL); + + ULONG versionSize = (ULONG)strlen(pVersion)+1; + ULONG alignedVersionSize = (ULONG)ALIGN_UP(versionSize, 4); + + *pcbSignatureSize = sizeof(STORAGESIGNATURE) + alignedVersionSize; + return S_OK; +} + + +//***************************************************************************** +// Retrieves a the size and a pointer to the extra data that can optionally be +// written in the header of the storage system. This data is not required to +// be in the file, in which case *pcbExtra will come back as 0 and pbData will +// be set to NULL. You must have initialized the storage using Init() before +// calling this function. +// +// Return value: S_OK if found, S_FALSE, or error. +//***************************************************************************** +HRESULT +TiggerStorage::GetExtraData( + ULONG *pcbExtra, // Return size of extra data. + BYTE *&pbData) // Return a pointer to extra data. +{ + // Assuming there is extra data, then return the size and a pointer to it. + if (m_pbExtra != NULL) + { + if ((m_StgHdr.GetFlags() & STGHDR_EXTRADATA) == 0) + { + Debug_ReportError("Inconsistent information about extra data in MetaData."); + return PostError(CLDB_E_FILE_CORRUPT); + } + *pcbExtra = *(ULONG *)m_pbExtra; + pbData = (BYTE *)((ULONG *) m_pbExtra + 1); + } + else + { + *pcbExtra = 0; + pbData = NULL; + return S_FALSE; + } + return S_OK; +} // TiggerStorage::GetExtraData + + +//***************************************************************************** +// Called when this stream is going away. +//***************************************************************************** +HRESULT +TiggerStorage::WriteHeader( + STORAGESTREAMLST *pList, // List of streams. + ULONG cbExtraData, // Size of extra data, may be 0. + BYTE *pbExtraData) // Pointer to extra data for header. +{ + ULONG iLen; // For variable sized data. + ULONG cbWritten; // Track write quantity. + HRESULT hr; + SAVETRACE(ULONG cbDebugSize); // Track debug size of header. + + SAVETRACE(DbgWriteEx(W("PSS: Header:\n"))); + + // Save the count and set flags. + m_StgHdr.SetiStreams(pList->Count()); + if (cbExtraData != 0) + m_StgHdr.AddFlags(STGHDR_EXTRADATA); + + // Write out the header of the file. + IfFailRet(m_pStgIO->Write(&m_StgHdr, sizeof(STORAGEHEADER), &cbWritten)); + + // Write out extra data if there is any. + if (cbExtraData != 0) + { + _ASSERTE(pbExtraData); + _ASSERTE((cbExtraData % 4) == 0); + + // First write the length value. + IfFailRet(m_pStgIO->Write(&cbExtraData, sizeof(ULONG), &cbWritten)); + + // And then the data. + IfFailRet(m_pStgIO->Write(pbExtraData, cbExtraData, &cbWritten)); + SAVETRACE(DbgWriteEx(W("PSS: extra data size %d\n"), m_pStgIO->GetCurrentOffset() - cbDebugSize);cbDebugSize=m_pStgIO->GetCurrentOffset()); + } + + // Save off each data stream. + for (int i = 0; i < pList->Count(); i++) + { + PSTORAGESTREAM pStream = pList->Get(i); + + // How big is the structure (aligned) for this struct. + iLen = (ULONG)(sizeof(STORAGESTREAM) - MAXSTREAMNAME + strlen(pStream->GetName()) + 1); + + // Write the header including the name to disk. Does not include + // full name buffer in struct, just string and null terminator. + IfFailRet(m_pStgIO->Write(pStream, iLen, &cbWritten)); + + // Align the data out to 4 bytes. + if (iLen != ALIGN4BYTE(iLen)) + { + IfFailRet(m_pStgIO->Write(&hr, ALIGN4BYTE(iLen) - iLen, 0)); + } + SAVETRACE(DbgWriteEx(W("PSS: Table %hs header size %d\n"), pStream->rcName, m_pStgIO->GetCurrentOffset() - cbDebugSize);cbDebugSize=m_pStgIO->GetCurrentOffset()); + } + SAVETRACE(DbgWriteEx(W("PSS: Total size of header data %d\n"), m_pStgIO->GetCurrentOffset())); + // Make sure the whole thing is 4 byte aligned. + _ASSERTE((m_pStgIO->GetCurrentOffset() % 4) == 0); + return S_OK; +} // TiggerStorage::WriteHeader + + +//***************************************************************************** +// Called when all data has been written. Forces cached data to be flushed +// and stream lists to be validated. +//***************************************************************************** +HRESULT +TiggerStorage::WriteFinished( + STORAGESTREAMLST *pList, // List of streams. + ULONG *pcbSaveSize, // Return size of total data. + BOOL fDeltaSave) // Was this a delta +{ + PSTORAGESTREAM pEntry; // Loop control. + HRESULT hr; + + // If caller wants the total size of the file, we are there right now. + if (pcbSaveSize != NULL) + *pcbSaveSize = m_pStgIO->GetCurrentOffset(); + + // Flush our internal write cache to disk. + IfFailRet(m_pStgIO->FlushCache()); + + // Force user's data onto disk right now so that Commit() can be + // more accurate (although not totally up to the D in ACID). + hr = m_pStgIO->FlushFileBuffers(); + _ASSERTE(SUCCEEDED(hr)); + + // Run through all of the streams and validate them against the expected + // list we wrote out originally. + + // Robustness check: stream counts must match what was written. + _ASSERTE(pList->Count() == m_Streams.Count()); + if (pList->Count() != m_Streams.Count()) + { + _ASSERTE_MSG(FALSE, "Mismatch in streams, save would cause corruption."); + return PostError(CLDB_E_FILE_CORRUPT); + } + + // If we're saving a true delta, then this sanity check won't help. + // @TODO - Implement a sanity check for the deltas + if (!fDeltaSave) + { + // Sanity check each saved stream data size and offset. + for (int i = 0; i < pList->Count(); i++) + { + pEntry = pList->Get(i); + + _ASSERTE(pEntry->GetOffset() == m_Streams[i].GetOffset()); + _ASSERTE(pEntry->GetSize() == m_Streams[i].GetSize()); + _ASSERTE(strcmp(pEntry->GetName(), m_Streams[i].GetName()) == 0); + + // For robustness, check that everything matches expected value, + // and if it does not, refuse to save the data and force a rollback. + // The alternative is corruption of the data file. + if ((pEntry->GetOffset() != m_Streams[i].GetOffset()) || + (pEntry->GetSize() != m_Streams[i].GetSize()) || + (strcmp(pEntry->GetName(), m_Streams[i].GetName()) != 0)) + { + _ASSERTE_MSG(FALSE, "Mismatch in streams, save would cause corruption."); + hr = PostError(CLDB_E_FILE_CORRUPT); + break; + } + + //<REVISIT_TODO>@future: + // if iOffset or iSize mismatches, it means a bug in GetSaveSize + // which we can successfully detect right here. In that case, we + // could use the pStgIO and seek back to the header and correct the + // mistmake. This will break any client who lives on the GetSaveSize + // value which came back originally, but would be more robust than + // simply throwing back an error which will corrupt the file.</REVISIT_TODO> + } + } + return hr; +} // TiggerStorage::WriteFinished + + +//***************************************************************************** +// Called after a successful rewrite of an existing file. The in memory +// backing store is no longer valid because all new data is in memory and +// on disk. This is essentially the same state as created, so free up some +// working set and remember this state. +//***************************************************************************** +HRESULT TiggerStorage::ResetBackingStore() // Return code. +{ + return (m_pStgIO->ResetBackingStore()); +} + + +//***************************************************************************** +// Given the name of a stream that will be persisted into a stream in this +// storage type, figure out how big that stream would be including the user's +// stream data and the header overhead the file format incurs. The name is +// stored in ANSI and the header struct is aligned to 4 bytes. +//***************************************************************************** +HRESULT +TiggerStorage::GetStreamSaveSize( + LPCWSTR szStreamName, // Name of stream. + UINT32 cbDataSize, // Size of data to go into stream. + UINT32 *pcbSaveSize) // Return data size plus stream overhead. +{ + UINT32 cbTotalSize; // Add up each element. + + // Find out how large the name will be. + cbTotalSize = ::WszWideCharToMultiByte(CP_ACP, 0, szStreamName, -1, 0, 0, 0, 0); + _ASSERTE(cbTotalSize != 0); + + // Add the size of the stream header minus the static name array. + cbTotalSize += sizeof(STORAGESTREAM) - MAXSTREAMNAME; + + // Finally align the header value. + cbTotalSize = ALIGN4BYTE(cbTotalSize); + + // Return the size of the user data and the header data. + *pcbSaveSize = cbTotalSize + cbDataSize; + return S_OK; +} // TiggerStorage::GetStreamSaveSize + + +//***************************************************************************** +// Return the fixed size overhead for the storage implementation. This includes +// the signature and fixed header overhead. The overhead in the header for each +// stream is calculated as part of GetStreamSaveSize because these structs are +// variable sized on the name. +//***************************************************************************** +HRESULT TiggerStorage::GetStorageSaveSize( // Return code. + ULONG *pcbSaveSize, // [in] current size, [out] plus overhead. + ULONG cbExtra, // How much extra data to store in header. + LPCSTR pRuntimeVersion) +{ + HRESULT hr; + + ULONG cbSignatureSize; + IfFailRet(SizeOfStorageSignature(pRuntimeVersion, &cbSignatureSize)); + + *pcbSaveSize += cbSignatureSize + sizeof(STORAGEHEADER); + if (cbExtra) + *pcbSaveSize += sizeof(ULONG) + cbExtra; + return (S_OK); +} + + +//***************************************************************************** +// Adjust the offset in each known stream to match where it will wind up after +// a save operation. +//***************************************************************************** +HRESULT TiggerStorage::CalcOffsets( // Return code. + STORAGESTREAMLST *pStreamList, // List of streams for header. + ULONG cbExtra, // Size of variable extra data in header. + LPCSTR pRuntimeVersion) // The version string as it's length is part of the total size. +{ + PSTORAGESTREAM pEntry; // Each entry in the list. + ULONG cbOffset=0; // Running offset for streams. + int i; // Loop control. + + // Prime offset up front. + GetStorageSaveSize(&cbOffset, cbExtra, pRuntimeVersion); + + // Add on the size of each header entry. + for (i=0; i<pStreamList->Count(); i++) + { + VERIFY(pEntry = pStreamList->Get(i)); + cbOffset += sizeof(STORAGESTREAM) - MAXSTREAMNAME; + cbOffset += (ULONG)(strlen(pEntry->GetName()) + 1); + cbOffset = ALIGN4BYTE(cbOffset); + } + + // Go through each stream and reset its expected offset. + for (i=0; i<pStreamList->Count(); i++) + { + VERIFY(pEntry = pStreamList->Get(i)); + pEntry->SetOffset(cbOffset); + cbOffset += pEntry->GetSize(); + } + return (S_OK); +} + + + +HRESULT STDMETHODCALLTYPE TiggerStorage::CreateStream( + const OLECHAR *pwcsName, + DWORD grfMode, + DWORD reserved1, + DWORD reserved2, + IStream **ppstm) +{ + char rcStream[MAXSTREAMNAME];// For converted name. + VERIFY(Wsz_wcstombs(rcStream, pwcsName, sizeof(rcStream))); + return (CreateStream(rcStream, grfMode, reserved1, reserved2, ppstm)); +} + + +#ifndef DACCESS_COMPILE +HRESULT STDMETHODCALLTYPE TiggerStorage::CreateStream( + LPCSTR szName, + DWORD grfMode, + DWORD reserved1, + DWORD reserved2, + IStream **ppstm) +{ + PSTORAGESTREAM pStream; // For lookup. + HRESULT hr; + + _ASSERTE(szName && *szName); + + // Check for existing stream, which might be an error or more likely + // a rewrite of a file. + if (SUCCEEDED(FindStream(szName, &pStream))) + { + // <REVISIT_TODO>REVIEW: STGM_FAILIFTHERE is 0, the following condition will be always false</REVISIT_TODO> + if (pStream->GetOffset() != 0xffffffff && ((grfMode & STGM_CREATE) == STGM_FAILIFTHERE)) + return (PostError(STG_E_FILEALREADYEXISTS)); + } + // Add a control to track this stream. + else if (!pStream && (pStream = m_Streams.Append()) == 0) + return (PostError(OutOfMemory())); + pStream->SetOffset(0xffffffff); + pStream->SetSize(0); + strcpy_s(pStream->GetName(), 32, szName); + + // Now create a stream object to allow reading and writing. + TiggerStream *pNew = new (nothrow) TiggerStream; + if (!pNew) + return (PostError(OutOfMemory())); + *ppstm = (IStream *) pNew; + + // Init the new object. + if (FAILED(hr = pNew->Init(this, pStream->GetName()))) + { + delete pNew; + return (hr); + } + return (S_OK); +} +#endif //!DACCESS_COMPILE + + +HRESULT STDMETHODCALLTYPE TiggerStorage::OpenStream( + const OLECHAR *pwcsName, + void *reserved1, + DWORD grfMode, + DWORD reserved2, + IStream **ppstm) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::CreateStorage( + const OLECHAR *pwcsName, + DWORD grfMode, + DWORD dwStgFmt, + DWORD reserved2, + IStorage **ppstg) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE +TiggerStorage::OpenStorage( + const OLECHAR * wcsName, + IStorage * pStgPriority, + DWORD dwMode, + __in + SNB snbExclude, + DWORD reserved, + IStorage ** ppStg) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +TiggerStorage::CopyTo( + DWORD cIidExclude, + const IID * rgIidExclude, + __in + SNB snbExclude, + IStorage * pStgDest) +{ + return E_NOTIMPL; +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::MoveElementTo( + const OLECHAR *pwcsName, + IStorage *pstgDest, + const OLECHAR *pwcsNewName, + DWORD grfFlags) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::Commit( + DWORD grfCommitFlags) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::Revert() +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::EnumElements( + DWORD reserved1, + void *reserved2, + DWORD reserved3, + IEnumSTATSTG **ppenum) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::DestroyElement( + const OLECHAR *pwcsName) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::RenameElement( + const OLECHAR *pwcsOldName, + const OLECHAR *pwcsNewName) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::SetElementTimes( + const OLECHAR *pwcsName, + const FILETIME *pctime, + const FILETIME *patime, + const FILETIME *pmtime) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::SetClass( + REFCLSID clsid) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::SetStateBits( + DWORD grfStateBits, + DWORD grfMask) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStorage::Stat( + STATSTG *pstatstg, + DWORD grfStatFlag) +{ + return (E_NOTIMPL); +} + + + +HRESULT STDMETHODCALLTYPE TiggerStorage::OpenStream( + LPCWSTR szStream, + ULONG *pcbData, + void **ppAddress) +{ + PSTORAGESTREAM pStream; // For lookup. + char rcName[MAXSTREAMNAME]; // For conversion. + HRESULT hr; + + // Convert the name for internal use. + VERIFY(::WszWideCharToMultiByte(CP_ACP, 0, szStream, -1, rcName, sizeof(rcName), 0, 0)); + + // Look for the stream which must be found for this to work. Note that + // this error is explicitly not posted as an error object since unfound streams + // are a common occurence and do not warrant a resource file load. + IfFailRet(FindStream(rcName, &pStream)); + + // Get the memory for the stream. + IfFailRet( m_pStgIO->GetPtrForMem(pStream->GetOffset(), pStream->GetSize(), *ppAddress) ); + *pcbData = pStream->GetSize(); + return (S_OK); +} + + + +// +// Protected. +// + + +//***************************************************************************** +// Called by the stream implementation to write data out to disk. +//***************************************************************************** +HRESULT +TiggerStorage::Write( + LPCSTR szName, // Name of stream we're writing. + const void *pData, // Data to write. + ULONG cbData, // Size of data. + ULONG *pcbWritten) // How much did we write. +{ + PSTORAGESTREAM pStream; // Update size data. + ULONG iOffset = 0; // Offset for write. + ULONG cbWritten; // Handle null case. + HRESULT hr; + + // Get the stream descriptor. + if (FAILED(FindStream(szName, &pStream))) + return CLDB_E_FILE_BADWRITE; + + // If we need to know the offset, keep it now. + if (pStream->GetOffset() == 0xffffffff) + { + iOffset = m_pStgIO->GetCurrentOffset(); + + // Align the storage on a 4 byte boundary. + if ((iOffset % 4) != 0) + { + ULONG cb; + ULONG pad = 0; + + if (FAILED(hr = m_pStgIO->Write(&pad, ALIGN4BYTE(iOffset) - iOffset, &cb))) + return hr; + iOffset = m_pStgIO->GetCurrentOffset(); + + _ASSERTE((iOffset % 4) == 0); + } + } + + // Avoid confusion. + if (pcbWritten == NULL) + pcbWritten = &cbWritten; + *pcbWritten = 0; + + // Let OS do the write. + if (SUCCEEDED(hr = m_pStgIO->Write(pData, cbData, pcbWritten))) + { + // On success, record the new data. + if (pStream->GetOffset() == 0xffffffff) + pStream->SetOffset(iOffset); + pStream->SetSize(pStream->GetSize() + *pcbWritten); + return S_OK; + } + else + { + return hr; + } +} // TiggerStorage::Write + + +// +// Private +// + +HRESULT +TiggerStorage::FindStream( + LPCSTR szName, + __out PSTORAGESTREAM *stream) +{ + *stream = NULL; + // In read mode, just walk the list and return one. + if (m_pStreamList != NULL) + { + PSTORAGESTREAM p = m_pStreamList; + + SIZE_T pStartMD = (SIZE_T)(m_pStgIO->m_pData); + SIZE_T pEndMD = NULL; + + if (!ClrSafeInt<SIZE_T>::addition(pStartMD, m_pStgIO->m_cbData, pEndMD)) + { + Debug_ReportError("Invalid MetaData storage headers - size overflow."); + return CLDB_E_FILE_CORRUPT; + } + + for (int i = 0; i < m_StgHdr.GetiStreams(); i++) + { + // Make sure this stream pointer is still inside the metadata + if (((SIZE_T)p < pStartMD) || ((SIZE_T)p > pEndMD)) + { + Debug_ReportError("Invalid MetaData storage header - reached outside headers block."); + return CLDB_E_FILE_CORRUPT; + } + + if (SString::_stricmp(p->GetName(), szName) == 0) + { + *stream = p; + return S_OK; + } + p = p->NextStream(); + } + } + // In write mode, walk the array which is not on disk yet. + else + { + for (int j = 0; j < m_Streams.Count(); j++) + { + if (SString::_stricmp(m_Streams[j].GetName(), szName) == 0) + { + *stream = &m_Streams[j]; + return S_OK; + } + } + } + return STG_E_FILENOTFOUND; +} // TiggerStorage::FindStream + + +//***************************************************************************** +// Write the signature area of the file format to disk. This includes the +// "magic" identifier and the version information. +//***************************************************************************** +HRESULT +TiggerStorage::WriteSignature( + LPCSTR pVersion) +{ + STORAGESIGNATURE sSig; + ULONG cbWritten; + HRESULT hr = S_OK; + + if (pVersion == NULL) + { + IfFailRet(GetDefaultVersion(&pVersion)); + } + _ASSERTE(pVersion != NULL); + + ULONG versionSize = (ULONG)strlen(pVersion) + 1; + ULONG alignedVersionSize = (ULONG)ALIGN_UP(versionSize, 4); + + // Signature belongs at the start of the file. + _ASSERTE(m_pStgIO->GetCurrentOffset() == 0); + + sSig.SetSignature(STORAGE_MAGIC_SIG); + sSig.SetMajorVer(FILE_VER_MAJOR); + sSig.SetMinorVer(FILE_VER_MINOR); + sSig.SetExtraDataOffset(0); // We have no extra inforation + sSig.SetVersionStringLength(alignedVersionSize); + IfFailRet(m_pStgIO->Write(&sSig, sizeof(STORAGESIGNATURE), &cbWritten)); + IfFailRet(m_pStgIO->Write(pVersion, versionSize, &cbWritten)); + + // Write padding + if (alignedVersionSize - versionSize != 0) + { + BYTE padding[4]; + ZeroMemory(padding, sizeof(padding)); + IfFailRet(m_pStgIO->Write(padding, alignedVersionSize - versionSize, &cbWritten)); + } + + return hr; +} // TiggerStorage::WriteSignature + + +//***************************************************************************** +// Read the header from disk. This reads the header for the most recent version +// of the file format which has the header at the front of the data file. +//***************************************************************************** +HRESULT +TiggerStorage::ReadHeader() +{ + PSTORAGESTREAM pAppend, pStream; // For copy of array. + void *ptr; // Working pointer. + ULONG iOffset; // Offset of header data. + ULONG cbExtra; // Size of extra data. + ULONG cbRead; // For calc of read sizes. + HRESULT hr; + + // Read the signature + if (FAILED(hr = m_pStgIO->GetPtrForMem(0, sizeof(STORAGESIGNATURE), ptr))) + { + Debug_ReportError("Cannot read MetaData storage signature header."); + return hr; + } + + PSTORAGESIGNATURE pStorage = (PSTORAGESIGNATURE)ptr; + + // Header data starts after signature. + iOffset = sizeof(STORAGESIGNATURE) + pStorage->GetVersionStringLength(); + + // Read the storage header which has the stream counts. Throw in the extra + // count which might not exist, but saves us down stream. + if (FAILED(hr = m_pStgIO->GetPtrForMem(iOffset, sizeof(STORAGEHEADER) + sizeof(ULONG), ptr))) + { + Debug_ReportError("Cannot read first MetaData storage header."); + return hr; + } + _ASSERTE(m_pStgIO->IsAlignedPtr((ULONG_PTR) ptr, 4)); + + // Read the storage header which has the stream counts. Throw in the extra + // count which might not exist, but saves us down stream. + if (FAILED(hr = m_pStgIO->GetPtrForMem(iOffset, sizeof(STORAGEHEADER) + sizeof(ULONG), ptr))) + { + Debug_ReportError("Cannot read second MetaData storage header."); + return hr; + } + if (!m_pStgIO->IsAlignedPtr((ULONG_PTR)ptr, 4)) + { + Debug_ReportError("Invalid MetaData storage headers - unaligned size."); + return PostError(CLDB_E_FILE_CORRUPT); + } + + // Copy the header into memory and check it. + memcpy(&m_StgHdr, ptr, sizeof(STORAGEHEADER)); + IfFailRet( VerifyHeader() ); + ptr = (void *)((PSTORAGEHEADER)ptr + 1); + iOffset += sizeof(STORAGEHEADER); + + // Save off a pointer to the extra data. + if ((m_StgHdr.GetFlags() & STGHDR_EXTRADATA) != 0) + { + m_pbExtra = ptr; + cbExtra = sizeof(ULONG) + *(ULONG *)ptr; + + // Force the extra data to get faulted in. + IfFailRet(m_pStgIO->GetPtrForMem(iOffset, cbExtra, ptr)); + if (!m_pStgIO->IsAlignedPtr((ULONG_PTR)ptr, 4)) + { + Debug_ReportError("Invalid MetaData storage signature - unaligned extra data."); + return PostError(CLDB_E_FILE_CORRUPT); + } + } + else + { + m_pbExtra = 0; + cbExtra = 0; + } + iOffset += cbExtra; + + // Force the worst case scenario of bytes to get faulted in for the + // streams. This makes the rest of this code very simple. + cbRead = sizeof(STORAGESTREAM) * m_StgHdr.GetiStreams(); + if (cbRead != 0) + { + cbRead = min(cbRead, m_pStgIO->GetDataSize() - iOffset); + if (FAILED(hr = m_pStgIO->GetPtrForMem(iOffset, cbRead, ptr))) + { + Debug_ReportError("Invalid MetaData stogare headers."); + return hr; + } + if (!m_pStgIO->IsAlignedPtr((ULONG_PTR)ptr, 4)) + { + Debug_ReportError("Invalid MetaData stogare headers - unaligned start."); + return PostError(CLDB_E_FILE_CORRUPT); + } + + // For read only, just access the header data. + if (m_pStgIO->IsReadOnly()) + { + // Save a pointer to the current list of streams. + m_pStreamList = (PSTORAGESTREAM)ptr; + } + // For writeable, need a copy we can modify. + else + { + pStream = (PSTORAGESTREAM)ptr; + + // Copy each of the stream headers. + for (int i = 0; i < m_StgHdr.GetiStreams(); i++) + { + if ((pAppend = m_Streams.Append()) == NULL) + return PostError(OutOfMemory()); + // Validate that the stream header is not too big. + ULONG sz = pStream->GetStreamSize(); + if (sz > sizeof(STORAGESTREAM)) + { + Debug_ReportError("Invalid MetaData storage stream - data too big."); + return PostError(CLDB_E_FILE_CORRUPT); + } + memcpy (pAppend, pStream, sz); + pStream = pStream->NextStream(); + if (!m_pStgIO->IsAlignedPtr((ULONG_PTR)pStream, 4)) + { + Debug_ReportError("Invalid MetaData storage stream - unaligned data."); + return PostError(CLDB_E_FILE_CORRUPT); + } + } + + // All must be loaded and accounted for. + _ASSERTE(m_StgHdr.GetiStreams() == m_Streams.Count()); + } + } + return S_OK; +} // TiggerStorage::ReadHeader + + +//***************************************************************************** +// Verify the header is something this version of the code can support. +//***************************************************************************** +HRESULT TiggerStorage::VerifyHeader() +{ + //<REVISIT_TODO>@FUTURE: add version check for format.</REVISIT_TODO> + return S_OK; +} + +//***************************************************************************** +// Print the sizes of the various streams. +//***************************************************************************** +#if defined(_DEBUG) +ULONG TiggerStorage::PrintSizeInfo(bool verbose) +{ + ULONG total = 0; + + printf("Storage Header: %d\n", sizeof(STORAGEHEADER)); + if (m_pStreamList != NULL) + { + PSTORAGESTREAM storStream = m_pStreamList; + PSTORAGESTREAM pNext; + for (int i = 0; i < m_StgHdr.GetiStreams(); i++) + { + pNext = storStream->NextStream(); + printf("Stream #%d (%s) Header: %d, Data: %d\n",i,storStream->GetName(), (BYTE*)pNext - (BYTE*)storStream, storStream->GetSize()); + total += storStream->GetSize(); + storStream = pNext; + } + } + else + { + //<REVISIT_TODO>todo: Add support for the case where m_Streams exists and m_pStreamList does not</REVISIT_TODO> + } + + if (m_pbExtra != NULL) + { + printf("Extra bytes: %d\n",*(ULONG*)m_pbExtra); + total += *(ULONG*)m_pbExtra; + } + return total; +} +#endif // _DEBUG diff --git a/src/md/enc/stgtiggerstream.cpp b/src/md/enc/stgtiggerstream.cpp new file mode 100644 index 0000000000..dfa5c27c37 --- /dev/null +++ b/src/md/enc/stgtiggerstream.cpp @@ -0,0 +1,138 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// StgTiggerStream.h +// + +// +// TiggerStream is the companion to the TiggerStorage CoClass. It handles the +// streams managed inside of the storage and does the direct file i/o. +// +//***************************************************************************** +#include "stdafx.h" +#include "stgtiggerstream.h" +#include "stgtiggerstorage.h" +#include "posterror.h" + +// +// +// IStream +// +// + + +HRESULT STDMETHODCALLTYPE TiggerStream::Read( + void *pv, + ULONG cb, + ULONG *pcbRead) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::Write( + const void *pv, + ULONG cb, + ULONG *pcbWritten) +{ + return (m_pStorage->Write(m_rcStream, pv, cb, pcbWritten)); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::Seek( + LARGE_INTEGER dlibMove, + DWORD dwOrigin, + ULARGE_INTEGER *plibNewPosition) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::SetSize( + ULARGE_INTEGER libNewSize) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::CopyTo( + IStream *pstm, + ULARGE_INTEGER cb, + ULARGE_INTEGER *pcbRead, + ULARGE_INTEGER *pcbWritten) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::Commit( + DWORD grfCommitFlags) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::Revert() +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::LockRegion( + ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, + DWORD dwLockType) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::UnlockRegion( + ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, + DWORD dwLockType) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::Stat( + STATSTG *pstatstg, + DWORD grfStatFlag) +{ + return (E_NOTIMPL); +} + + +HRESULT STDMETHODCALLTYPE TiggerStream::Clone( + IStream **ppstm) +{ + return (E_NOTIMPL); +} + + + + + + +HRESULT TiggerStream::Init( // Return code. + TiggerStorage *pStorage, // Parent storage. + LPCSTR szStream) // Stream name. +{ + // Save off the parent data source object and stream name. + m_pStorage = pStorage; + strncpy_s(m_rcStream, sizeof(m_rcStream), szStream, sizeof(m_rcStream)-1); + m_rcStream[sizeof(m_rcStream)-1] = '\0'; // force nul termination + return (S_OK); +} + + +ULONG TiggerStream::GetStreamSize() +{ + PSTORAGESTREAM pStreamInfo; + if (FAILED(m_pStorage->FindStream(m_rcStream, &pStreamInfo))) + return 0; + return (pStreamInfo->GetSize()); +} diff --git a/src/md/enc/wks/.gitmirror b/src/md/enc/wks/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/md/enc/wks/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/md/enc/wks/CMakeLists.txt b/src/md/enc/wks/CMakeLists.txt new file mode 100644 index 0000000000..e31d8ec056 --- /dev/null +++ b/src/md/enc/wks/CMakeLists.txt @@ -0,0 +1,2 @@ +include(../../md_wks.cmake) +add_library(mdruntimerw_wks ${MDRUNTIMERW_SOURCES}) diff --git a/src/md/enc/wks/MDRuntimeRW.nativeproj b/src/md/enc/wks/MDRuntimeRW.nativeproj new file mode 100644 index 0000000000..b3116c5abb --- /dev/null +++ b/src/md/enc/wks/MDRuntimeRW.nativeproj @@ -0,0 +1,19 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <PropertyGroup> + <!-- All features are set in file:..\..\MD.props --> + <MetadataFlavor>wks</MetadataFlavor> + </PropertyGroup> + + <!--Leaf project Properties--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\MD\enc\enc.settings.targets" /> + + <!--Leaf project Properties--> + <PropertyGroup> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + <OutputName>MDRuntimeRW</OutputName> + </PropertyGroup> + + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" /> +</Project> |