Merge pull request #20 from paulcbetts/winrt-bitmap-cleanup
WinRT bitmap cleanup
This commit is contained in:
Коммит
acc7f09fde
|
@ -106,7 +106,9 @@
|
|||
<Compile Include="PlatformModeDetector.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WinRT\Bitmaps.cs" />
|
||||
<Compile Include="WinRT\BitmapsFallback.cs" />
|
||||
<Compile Include="WinRT\Color.cs" />
|
||||
<Compile Include="WinRT\NativeMethods.cs" />
|
||||
<Compile Include="WinRT\Point.cs" />
|
||||
<Compile Include="WinRT\Rect.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -20,7 +20,23 @@ namespace Splat
|
|||
using (var rwStream = new InMemoryRandomAccessStream()) {
|
||||
await sourceStream.CopyToAsync(rwStream.AsStreamForWrite());
|
||||
|
||||
var decoder = await BitmapDecoder.CreateAsync(rwStream);
|
||||
var decoder = default(BitmapDecoder);
|
||||
|
||||
bool tryFallback = false;
|
||||
try {
|
||||
decoder = await BitmapDecoder.CreateAsync(rwStream);
|
||||
} catch (Exception ex) {
|
||||
if (ex.Message.Contains("0x88982F50") || ex.Message.Contains("0x88982F60")) {
|
||||
// NB: Can't await in a catch block, have to do some silliness
|
||||
tryFallback = true;
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (tryFallback) {
|
||||
return await new FallbackBitmapLoader().Load(sourceStream, desiredWidth, desiredHeight);
|
||||
}
|
||||
|
||||
var transform = new BitmapTransform();
|
||||
if (desiredWidth != null) {
|
||||
|
@ -31,7 +47,7 @@ namespace Splat
|
|||
var pixelData = await decoder.GetPixelDataAsync(decoder.BitmapPixelFormat, decoder.BitmapAlphaMode, transform, ExifOrientationMode.RespectExifOrientation, ColorManagementMode.ColorManageToSRgb);
|
||||
var pixels = pixelData.DetachPixelData();
|
||||
|
||||
WriteableBitmap bmp = new WriteableBitmap((int)decoder.OrientedPixelWidth, (int)decoder.OrientedPixelHeight);
|
||||
var bmp = new WriteableBitmap((int)decoder.OrientedPixelWidth, (int)decoder.OrientedPixelHeight);
|
||||
using (var bmpStream = bmp.PixelBuffer.AsStream()) {
|
||||
bmpStream.Seek(0, SeekOrigin.Begin);
|
||||
bmpStream.Write(pixels, 0, (int)bmpStream.Length);
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
using Splat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
|
||||
namespace Splat
|
||||
{
|
||||
class FallbackBitmapLoader : IBitmapLoader
|
||||
{
|
||||
public async Task<IBitmap> Load(Stream sourceStream, float? desiredWidth, float? desiredHeight)
|
||||
{
|
||||
var factory = new ImagingFactory();
|
||||
var decoder = factory.CreateDecoderFromStream(sourceStream, WICDecodeOptions.WICDecodeMetadataCacheOnLoad);
|
||||
var frame = decoder.GetFrame(0);
|
||||
var frameSize = frame.GetSize();
|
||||
var convertedSource = default(BitmapSource);
|
||||
|
||||
if (!frame.GetPixelFormat().Equals(NativeMethods.WICPixelFormat32bppBGRA)) {
|
||||
var converter = factory.CreateFormatConverter();
|
||||
|
||||
converter.Initialize(frame, NativeMethods.WICPixelFormat32bppBGRA);
|
||||
convertedSource = converter;
|
||||
} else {
|
||||
convertedSource = frame;
|
||||
}
|
||||
|
||||
var buffer = convertedSource.CopyPixels();
|
||||
|
||||
var bmp = new WriteableBitmap(frameSize.Width, frameSize.Height);
|
||||
using (var stream = bmp.PixelBuffer.AsStream()) {
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
bmp.Invalidate();
|
||||
|
||||
return new WriteableBitmapImageBitmap(bmp);
|
||||
}
|
||||
|
||||
public async Task<IBitmap> LoadFromResource(string source, float? desiredWidth, float? desiredHeight)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IBitmap Create(float width, float height)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
class ImagingFactory : IDisposable
|
||||
{
|
||||
IDisposable inner;
|
||||
IWICImagingFactory comObject;
|
||||
IntPtr nativePointer;
|
||||
IntPtr riidPointer;
|
||||
|
||||
public ImagingFactory()
|
||||
{
|
||||
var riid = typeof(IWICImagingFactory).GetTypeInfo().GUID;
|
||||
byte[] riidBytes = riid.ToByteArray();
|
||||
|
||||
riidPointer = Marshal.AllocHGlobal(riidBytes.Length);
|
||||
Marshal.Copy(riidBytes, 0, riidPointer, riidBytes.Length);
|
||||
|
||||
var localQuery = new MultiQueryInterface() {
|
||||
InterfaceIID = riidPointer,
|
||||
IUnknownPointer = IntPtr.Zero,
|
||||
ResultCode = 0,
|
||||
};
|
||||
|
||||
var result = NativeMethods.CoCreateInstanceFromApp(NativeMethods.CLSID_WICImagingFactory, IntPtr.Zero, CLSCTX.CLSCTX_INPROC_SERVER, IntPtr.Zero, 1, ref localQuery);
|
||||
if (result != NativeMethods.S_OK || localQuery.ResultCode != NativeMethods.S_OK) {
|
||||
throw new Exception("CoCreateInstanceFromApp failed");
|
||||
}
|
||||
|
||||
nativePointer = localQuery.IUnknownPointer;
|
||||
comObject = (IWICImagingFactory)Marshal.GetObjectForIUnknown(nativePointer);
|
||||
|
||||
inner = new DelegateDisposable(() => {
|
||||
Marshal.FreeHGlobal(riidPointer);
|
||||
Marshal.ReleaseComObject(comObject);
|
||||
});
|
||||
}
|
||||
|
||||
~ImagingFactory() { Dispose(); }
|
||||
public void Dispose()
|
||||
{
|
||||
Interlocked.Exchange(ref inner, DelegateDisposable.Empty).Dispose();
|
||||
}
|
||||
|
||||
public BitmapDecoder CreateDecoderFromStream(Stream stream, WICDecodeOptions wicDecodeOptions)
|
||||
{
|
||||
IntPtr nativePointer;
|
||||
var nullGuid = Guid.Empty;
|
||||
|
||||
var result = comObject.CreateDecoderFromStream(new ManagedIStream(stream), ref nullGuid, wicDecodeOptions, out nativePointer);
|
||||
if (result != NativeMethods.S_OK) throw new Exception("CreateDecoderFromStream failed");
|
||||
|
||||
return new BitmapDecoder(nativePointer);
|
||||
}
|
||||
|
||||
public FormatConverter CreateFormatConverter()
|
||||
{
|
||||
IntPtr nativePointer;
|
||||
var result = comObject.CreateFormatConverter(out nativePointer);
|
||||
|
||||
if (result != NativeMethods.S_OK)
|
||||
throw new Exception("CreateFormatConverter failed");
|
||||
|
||||
return new FormatConverter(nativePointer);
|
||||
}
|
||||
}
|
||||
|
||||
class BitmapDecoder : IDisposable
|
||||
{
|
||||
IWICBitmapDecoder comObject;
|
||||
IntPtr nativePointer;
|
||||
IDisposable inner;
|
||||
|
||||
internal BitmapDecoder(IntPtr nativePointer)
|
||||
{
|
||||
this.nativePointer = nativePointer;
|
||||
this.comObject = (IWICBitmapDecoder)Marshal.GetObjectForIUnknown(nativePointer);
|
||||
inner = new DelegateDisposable(() => Marshal.ReleaseComObject(comObject));
|
||||
}
|
||||
|
||||
public BitmapFrameDecode GetFrame(uint index)
|
||||
{
|
||||
IntPtr nativePointer;
|
||||
var result = comObject.GetFrame(index, out nativePointer);
|
||||
|
||||
if (result != NativeMethods.S_OK) {
|
||||
throw new Exception("GetFrame failed");
|
||||
}
|
||||
|
||||
return new BitmapFrameDecode(nativePointer);
|
||||
}
|
||||
|
||||
~BitmapDecoder() { Dispose(); }
|
||||
public void Dispose()
|
||||
{
|
||||
Interlocked.Exchange(ref inner, DelegateDisposable.Empty).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class BitmapSource : IDisposable
|
||||
{
|
||||
IWICBitmapSource comObject;
|
||||
public IntPtr NativePointer { get; private set; }
|
||||
IDisposable inner;
|
||||
|
||||
internal BitmapSource(IntPtr nativePointer)
|
||||
{
|
||||
NativePointer = nativePointer;
|
||||
comObject = (IWICBitmapSource)Marshal.GetObjectForIUnknown(NativePointer);
|
||||
inner = new DelegateDisposable(() => Marshal.ReleaseComObject(comObject));
|
||||
}
|
||||
|
||||
public System.Drawing.Size GetSize()
|
||||
{
|
||||
uint width, height;
|
||||
comObject.GetSize(out width, out height);
|
||||
|
||||
// this will throw an exception if dimensions are > Int32.MaxValue
|
||||
// in fact all of this code assumes dimensions will not overflow under arithmetic
|
||||
return new System.Drawing.Size(checked((int)width), checked((int)height));
|
||||
}
|
||||
|
||||
public Guid GetPixelFormat()
|
||||
{
|
||||
Guid pixelFormat;
|
||||
comObject.GetPixelFormat(out pixelFormat);
|
||||
return pixelFormat;
|
||||
}
|
||||
|
||||
public byte[] CopyPixels()
|
||||
{
|
||||
uint width, height;
|
||||
GetSizeInternal(out width, out height);
|
||||
|
||||
byte[] pixels = new byte[width * height * 4];
|
||||
uint bufferSize = width * height * 32;
|
||||
IntPtr buffer = Marshal.AllocCoTaskMem(checked((int)bufferSize));
|
||||
|
||||
comObject.CopyPixels(
|
||||
IntPtr.Zero,
|
||||
width * 4,
|
||||
bufferSize,
|
||||
buffer);
|
||||
|
||||
Marshal.Copy(buffer, pixels, 0, pixels.Length);
|
||||
Marshal.FreeCoTaskMem(buffer);
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private void GetSizeInternal(out uint width, out uint height)
|
||||
{
|
||||
comObject.GetSize(out width, out height);
|
||||
}
|
||||
|
||||
~BitmapSource() { Dispose(); }
|
||||
public void Dispose()
|
||||
{
|
||||
Interlocked.Exchange(ref inner, DelegateDisposable.Empty).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BitmapFrameDecode : BitmapSource
|
||||
{
|
||||
internal BitmapFrameDecode(IntPtr nativePointer) : base(nativePointer) { }
|
||||
}
|
||||
|
||||
class FormatConverter : BitmapSource
|
||||
{
|
||||
IWICFormatConverter comObject;
|
||||
|
||||
internal FormatConverter(IntPtr nativePointer)
|
||||
: base(nativePointer)
|
||||
{
|
||||
this.comObject = (IWICFormatConverter)Marshal.GetObjectForIUnknown(NativePointer);
|
||||
}
|
||||
|
||||
public void Initialize(BitmapSource bitmapSource, Guid pixelFormat)
|
||||
{
|
||||
comObject.Initialize(bitmapSource.NativePointer, pixelFormat, 0, IntPtr.Zero, 0.0f, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class ManagedIStream : IStream
|
||||
{
|
||||
Stream _stream;
|
||||
|
||||
public ManagedIStream(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
public void Read(byte[] pv, int cb, IntPtr pcbRead)
|
||||
{
|
||||
int val = _stream.Read(pv, 0, cb);
|
||||
if (pcbRead != IntPtr.Zero) {
|
||||
Marshal.WriteInt32(pcbRead, val);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(byte[] pv, int cb, IntPtr pcbWritten)
|
||||
{
|
||||
_stream.Write(pv, 0, cb);
|
||||
|
||||
if (pcbWritten != IntPtr.Zero) {
|
||||
Marshal.WriteInt32(pcbWritten, cb);
|
||||
}
|
||||
}
|
||||
|
||||
public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
|
||||
{
|
||||
SeekOrigin origin;
|
||||
switch (dwOrigin)
|
||||
{
|
||||
case 0: origin = SeekOrigin.Begin; break;
|
||||
case 1: origin = SeekOrigin.Current; break;
|
||||
case 2: origin = SeekOrigin.End; break;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException("dwOrigin");
|
||||
}
|
||||
|
||||
long val = _stream.Seek(dlibMove, origin);
|
||||
if (plibNewPosition != IntPtr.Zero) {
|
||||
Marshal.WriteInt64(plibNewPosition, val);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSize(long libNewSize)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Stat(out STATSTG pstatstg, int grfStatFlag)
|
||||
{
|
||||
pstatstg = new STATSTG {
|
||||
type = 2,
|
||||
cbSize = _stream.Length,
|
||||
};
|
||||
|
||||
if (_stream.CanRead && _stream.CanWrite) {
|
||||
pstatstg.grfMode = 0x00000002;
|
||||
} else if (_stream.CanWrite) {
|
||||
pstatstg.grfMode = 0x00000001;
|
||||
} else if (_stream.CanRead) {
|
||||
pstatstg.grfMode = 0x00000000;
|
||||
} else {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clone(out IStream ppstm)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Commit(int grfCommitFlags)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LockRegion(long libOffset, long cb, int dwLockType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Revert()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UnlockRegion(long libOffset, long cb, int dwLockType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DelegateDisposable : IDisposable
|
||||
{
|
||||
Action block;
|
||||
|
||||
static IDisposable empty;
|
||||
public static IDisposable Empty {
|
||||
get { return empty ?? (empty = new DelegateDisposable(() => {})); }
|
||||
}
|
||||
|
||||
public DelegateDisposable(Action block)
|
||||
{
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
block();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Splat
|
||||
{
|
||||
internal static class NativeMethods
|
||||
{
|
||||
public const int S_OK = 0;
|
||||
|
||||
public static readonly Guid CLSID_WICImagingFactory = new Guid("cacaf262-9370-4615-a13b-9f5539da4c0a");
|
||||
|
||||
public static readonly Guid WICPixelFormat1bppIndexed = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc901");
|
||||
public static readonly Guid WICPixelFormatBlackWhite = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc905");
|
||||
public static readonly Guid WICPixelFormat8bppIndexed = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc904");
|
||||
public static readonly Guid WICPixelFormatDontCare = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc900");
|
||||
public static readonly Guid WICPixelFormat24bppBGR = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc90c");
|
||||
public static readonly Guid WICPixelFormat32bppBGRA = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc90e");
|
||||
public static readonly Guid WICPixelFormat48bppRGB = new Guid("6fddc324-4e03-4bfe-b185-3d77768dc915");
|
||||
|
||||
[DllImport("ole32.dll", ExactSpelling = true, EntryPoint = "CoCreateInstanceFromApp", PreserveSig = true)]
|
||||
public static extern int CoCreateInstanceFromApp(
|
||||
[In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
|
||||
IntPtr pUnkOuter,
|
||||
CLSCTX dwClsContext,
|
||||
IntPtr reserved,
|
||||
int countMultiQuery,
|
||||
ref MultiQueryInterface query);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum CLSCTX : uint
|
||||
{
|
||||
CLSCTX_INPROC_SERVER = 0x1,
|
||||
CLSCTX_INPROC_HANDLER = 0x2,
|
||||
CLSCTX_LOCAL_SERVER = 0x4,
|
||||
CLSCTX_INPROC_SERVER16 = 0x8,
|
||||
CLSCTX_REMOTE_SERVER = 0x10,
|
||||
CLSCTX_INPROC_HANDLER16 = 0x20,
|
||||
CLSCTX_RESERVED1 = 0x40,
|
||||
CLSCTX_RESERVED2 = 0x80,
|
||||
CLSCTX_RESERVED3 = 0x100,
|
||||
CLSCTX_RESERVED4 = 0x200,
|
||||
CLSCTX_NO_CODE_DOWNLOAD = 0x400,
|
||||
CLSCTX_RESERVED5 = 0x800,
|
||||
CLSCTX_NO_CUSTOM_MARSHAL = 0x1000,
|
||||
CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000,
|
||||
CLSCTX_NO_FAILURE_LOG = 0x4000,
|
||||
CLSCTX_DISABLE_AAA = 0x8000,
|
||||
CLSCTX_ENABLE_AAA = 0x10000,
|
||||
CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000,
|
||||
CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000,
|
||||
CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000,
|
||||
CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
|
||||
CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,
|
||||
CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER
|
||||
}
|
||||
|
||||
internal enum WICDecodeOptions : uint
|
||||
{
|
||||
WICDecodeMetadataCacheOnDemand = 0,
|
||||
WICDecodeMetadataCacheOnLoad = 1,
|
||||
WICMETADATACACHEOPTION_FORCE_DWORD = 0x7fffffff
|
||||
}
|
||||
|
||||
[Guid("ec5ec8a9-c395-4314-9c77-54d7a935ff70")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport]
|
||||
internal interface IWICImagingFactory
|
||||
{
|
||||
void CreateDecoderFromFilenameDummy();
|
||||
int CreateDecoderFromStream(
|
||||
IStream pIStream,
|
||||
ref Guid pguidVendor,
|
||||
WICDecodeOptions metadataOptions,
|
||||
out IntPtr ppIDecoder);
|
||||
void CreateDecoderFromFileHandleDummy();
|
||||
void CreateComponentInfoDummy();
|
||||
void CreateDecoderDummy();
|
||||
void CreateEncoderDummy();
|
||||
void CreatePaletteDummy();
|
||||
int CreateFormatConverter(out IntPtr ppIFormatConverter);
|
||||
}
|
||||
|
||||
[Guid("9EDDE9E7-8DEE-47ea-99DF-E6FAF2ED44BF")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport]
|
||||
internal interface IWICBitmapDecoder
|
||||
{
|
||||
void QueryCapabilityDummy();
|
||||
void InitializeDummy();
|
||||
void GetContainerFormatDummy();
|
||||
void GetDecoderInfoDummy();
|
||||
void CopyPaletteDummy();
|
||||
void GetMetadataQueryReaderDummy();
|
||||
void GetPreviewDummy();
|
||||
void GetColorContextsDummy();
|
||||
void GetThumbnailDummy();
|
||||
void GetFrameCountDummy();
|
||||
int GetFrame(uint index, out IntPtr ppIFrameDecode);
|
||||
}
|
||||
|
||||
[Guid("00000120-a8f2-4877-ba0a-fd2b6645fb94")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport]
|
||||
internal interface IWICBitmapSource
|
||||
{
|
||||
void GetSize(out uint puiWidth, out uint puiHeight);
|
||||
void GetPixelFormat(out Guid pPixelFormat);
|
||||
void GetResolutionDummy();
|
||||
void CopyPaletteDummy();
|
||||
void CopyPixels(
|
||||
IntPtr prc, // WICRect
|
||||
uint cbStride,
|
||||
uint cbBufferSize,
|
||||
IntPtr pbBuffer);
|
||||
}
|
||||
|
||||
[Guid("00000301-a8f2-4877-ba0a-fd2b6645fb94")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport]
|
||||
internal interface IWICFormatConverter
|
||||
{
|
||||
#region IWICBitmapSource
|
||||
void GetSizeDummy();
|
||||
void GetPixelFormatDummy();
|
||||
void GetResolutionDummy();
|
||||
void CopyPaletteDummy();
|
||||
void CopyPixelsDummy();
|
||||
#endregion
|
||||
void Initialize(
|
||||
IntPtr pISource,
|
||||
[MarshalAs(UnmanagedType.LPStruct)]
|
||||
Guid dstFormat,
|
||||
uint dither,
|
||||
IntPtr pIPalette,
|
||||
double alphaThresholdPercent,
|
||||
uint paletteTranslate);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MultiQueryInterface
|
||||
{
|
||||
public IntPtr InterfaceIID;
|
||||
public IntPtr IUnknownPointer;
|
||||
public int ResultCode;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче