// And now it's time to take that shiny brand new type library, and shove it into a dll.
var typeLibSize = new FileInfo(tempTypeLibPath).Length;
// UpdateResource takes a ushort for the number of bytes to write, but the type lib size
// comes back as a long, so we need to check that the type lib will actually fit, and
// abort if it doesn't fit
if (typeLibSize > uint.MaxValue)
throw new Exception("Type library is too big to embed as a resource");
using var hTypeLib = PInvoke.CreateFile2(tempTypeLibPath, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, 0, FILE_CREATION_DISPOSITION.OPEN_EXISTING, null);
if (hTypeLib.IsInvalid)
throw new Win32Exception();
using var hMap = PInvoke.CreateFileMapping(hTypeLib, null, PAGE_PROTECTION_FLAGS.PAGE_READONLY, 0, 0, null);
if (hMap.IsInvalid)
throw new Win32Exception();
var lpData = PInvoke.MapViewOfFileEx(hMap, FILE_MAP.FILE_MAP_READ, 0, 0, UIntPtr.Zero, null);
if (lpData == null)
throw new Win32Exception();
try
{
// PInvoke wraps the returned handle in a SafeFileHandle, which is annoying because the
// handle from BeginUpdateResource needs to be closed by EndUpdateResource instead of
// the usual CloseHandle call. So we need to make sure to do that manually and then
// mark the handle as invalid so that SafeFileHandle doesn't die trying to close it for us
using var hUpdate = PInvoke.BeginUpdateResource(destDllPath, false);
if (hUpdate.IsInvalid)
throw new Win32Exception();
try
{
// In order to properly embed the type library (and make sure we replace the existing one)
// we have to use a very specific 'type' and 'name' that are defined by convention.
// The 'type' is straightforward, but while the 'name' is typed as a LPWSTR, it can also
// take the result of the `MAKEINTRESOURCE(i)` macro, which essentially passes an integer
// constant instead of a pointer (i.e. `(char*)1` but more complicated). This is very
// straightforward in C++, but the C# bindings generated by PInvoke take a normal string,
// which doesn't let us pass a specific "address" like we need to. So we had to copy/
// define our own overload that lets us do all the unsafe things that the
// API allows (and requires, in this case).
if (!PInvoke.UpdateResource(hUpdate, "TYPELIB", 1, 0, lpData, (uint)typeLibSize))
{
// We don't care if this fails, we're already aborting anyway
PInvoke.EndUpdateResource(hUpdate, true);
throw new Win32Exception();
}
// This gave me a lot of trouble during development, returning a failed status but not actually
// reporting any errors. I finally narrowed it down to some leaked ITypeInfo objects up above,
// and after properly releasing those, this started working. Why those particular objects
// matter is beyond me, I was leaking other ITypeInfo objects and ICreateTypeInfo objects
// in CopyTypeInfo with no issues, but I've cleaned those leaks up as well and things
// seem to be working just as well as the C++ implementation now.
if (!PInvoke.EndUpdateResource(hUpdate, false))
throw new Win32Exception();
}
finally
{
// Set the handle as closed/invalid so that SafeFileHandle doesn't try to close it for us
// with the wrong function and throw an exception.
hUpdate.SetHandleAsInvalid();
}
}
finally
{
PInvoke.UnmapViewOfFile(lpData);
}
}
Actual behavior
The handle returned by
BeginUpdateResourcemust be closed by a call toEndUpdateResourceinstead of the normalCloseHandlecall. The default destructor forSafeFileHandlecallsCloseHandle, which throws an error because it's attempting to close a handle it was never meant to close. I worked around the issue by setting the handle as invalid after callingEndUpdateResource.Expected behavior
The disposal of the HANDLE from BeginUpdateResource shouldn't throw an exception, which can't be traced because it's hidden in a generated Dispose call at the close of using block.
Repro steps
NativeMethods.txtcontent:NativeMethods.jsoncontent (if present):Code using the various UpdateResource functions
UpdateResource overload
Context
0.1.619-betanetstandard2.0LangVersion:8.0