如何在 C# 中将 stdole.stdPicture 转换为 .net 图像?

How do I convert stdole.stdPicture to a .net image in C#?

我正在为 Outlook 构建一个加载项,我将所有交换用户从全局地址列表复制到本地联系人。

问题是我也想传输交换用户的图片,但是 exchUser.GetPicture() returns stdole.stdPicture 而且我还没有找到下载或转换它的有效解决方案进入 image/jpg/...

这里是从全局地址列表中获取交换用户的代码:

private void EnumerateGAL()
{
    Outlook.AddressList gal = Application.Session.GetGlobalAddressList(); 
    if (gal != null) 
    {
        for (int i = 1; i <= gal.AddressEntries.Count - 1; i++)
        {
            Outlook.AddressEntry addrEntry = gal.AddressEntries[i];
            Outlook.ExchangeUser exchUser = addrEntry.GetExchangeUser();
                    
            if (addrEntry.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeUserAddressEntry
                && exchUser.CompanyName == "")
            {
                CreateContact(exchUser);
                //exchUser.GetPicture() returns stdole.stdPicture
            }
        }
    }
    return;
}

我找到的最接近的解决方案是 stdole.IPictureDisp 的转换,其中 returns 是位图,但 IPuctureDisp 和 stdPicture 与我在某处读到的不一样。

public static System.Drawing.Image ConvertPicture(stdole.IPictureDisp image)
{
    int type = image.Type;
    if (type == 1)
    {
        IntPtr hPal = (IntPtr)image.hPal;
        return Image.FromHbitmap((IntPtr)image.Handle, hPal);
    }
    return null;
}

最后我需要下载图片,因为我只能将图片上传到有路径的联系人。 那么,有没有办法下载一个stdPicture或将其转换为能够下载呢?

完成工作有四种主要方法。

“传统”方法是使用 System.Windows.Forms.AxHost class 中的 GetIPictureDispFromPictureGetPictureFromIPicture 方法。它们都是 class 的受保护成员,因此您不能在外部使用它们。出于这个原因,subclass AxHost class 和公开 public 内部调用基本 class 受保护方法的方法是很常见的。这种方法允许您双向转换:

internal class AxHostConverter : AxHost
{
    private AxHostConverter() : base("") { }

    static public stdole.IPictureDisp ImageToPictureDisp(Image image)
    {
       return (stdole.IPictureDisp)GetIPictureDispFromPicture(image);
    }

    static public Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    {
        return GetPictureFromIPicture(pictureDisp);
    }
}

您的第二个选择是使用 OleLoadPictureOleCreatePictureIndirect。这里有一篇关于该主题的旧支持文章。 OleLoadPicture 创建一个新的图片对象并根据流的内容对其进行初始化。

internal class OleCreateConverter

{

    [DllImport("oleaut32.dll", EntryPoint = "OleCreatePictureIndirect",

        CharSet = CharSet.Ansi, ExactSpelling = true, PreserveSig = true)]

    private static extern int OleCreatePictureIndirect(

        [In] PictDescBitmap pictdesc, ref Guid iid, bool fOwn,

        [MarshalAs(UnmanagedType.Interface)] out object ppVoid);

    const short _PictureTypeBitmap = 1;
    [StructLayout(LayoutKind.Sequential)]
    internal class PictDescBitmap
    {
        internal int cbSizeOfStruct = Marshal.SizeOf(typeof(PictDescBitmap));
        internal int pictureType = _PictureTypeBitmap;
        internal IntPtr hBitmap = IntPtr.Zero;
        internal IntPtr hPalette = IntPtr.Zero;
        internal int unused = 0;

        internal PictDescBitmap(Bitmap bitmap)
        {
            this.hBitmap = bitmap.GetHbitmap();
        }
    }

    public static stdole.IPictureDisp ImageToPictureDisp(Image image)
    {
       if (image == null || !(image is Bitmap))
        {
            return null;
        }

        PictDescBitmap pictDescBitmap = new PictDescBitmap((Bitmap)image);
        object ppVoid = null;
        Guid iPictureDispGuid = typeof(stdole.IPictureDisp).GUID;
        OleCreatePictureIndirect(pictDescBitmap, ref iPictureDispGuid, true, out ppVoid);
        stdole.IPictureDisp picture = (stdole.IPictureDisp)ppVoid;
        return picture;
    }

    public static Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    {
        Image image = null;
        if (pictureDisp != null && pictureDisp.Type == _PictureTypeBitmap)
        {
            IntPtr paletteHandle = new IntPtr(pictureDisp.hPal);
            IntPtr bitmapHandle = new IntPtr(pictureDisp.Handle);
            image = Image.FromHbitmap(bitmapHandle, paletteHandle);
        }
        return image;
    }
}

您的第三个选择是使用此处记录的 VB6 兼容库。要使用它,您需要添加对 Microsoft.VisualBasic.Compatibility.dll 的引用,它在 Add References 对话框的 .NET 选项卡上列出(它位于 GAC 中)。然后,可以在Supportclass中使用ImageToIPictureDispIPictureDispToImage方法。这显然是迄今为止最简单的方法,尽管它确实引入了 VB6 兼容性 DLL。在内部,VB6 兼容性代码看起来很像上面的第二个选项——使用 OleCreatePictureIndirect.

using Microsoft.VisualBasic.Compatibility.VB6;

internal class VB6CompatibilityConverter
{
    public static stdole.IPictureDisp ImageToPictureDisp(Image image)
    {
        return (stdole.IPictureDisp)Support.ImageToIPictureDisp(image);
    }

    public static Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    {
        return Support.IPictureDispToImage(pictureDisp);
    }
}

最后,您可以自己实现IPictureDispIPicture。如果您只想将图像转换为 IPictureDisp,这很好,但不会帮助您在另一个方向上进行转换。下面的实现依赖于 Image 实际上是派生的 Bitmap 类型,因为我们在内部调用 Bitmap.GetHbitmap 。如果你想保持对通用 Image 类型的支持,你将不得不做更多的工作来 p/invoke 一堆未记录的 GDI 方法而不是

internal class PictureDispConverter
{
    public static stdole.IPictureDisp BitmapToPictureDisp(Bitmap bitmap)
    {
        return new PictureDispImpl(bitmap);
    }
 
    public static Image PictureDispToBitmap(stdole.IPictureDisp pictureDisp)
    {
        // TODO
        return null;
    }
}

internal class PictureDispImpl : stdole.IPictureDisp, stdole.IPicture
{
    #region Init

    [DllImport("gdi32.dll")]
    static extern void DeleteObject(IntPtr handle);

    private Bitmap _image;
    private IntPtr _handle;
 
    public PictureDispImpl(Bitmap image)
    {
        _image = image;
    }
 
    ~PictureDispImpl()
    {
        if (_handle != IntPtr.Zero)
        {
            DeleteObject(_handle);
        }
    }

    #endregion

    #region IPictureDisp Members

    public int Width
    {
        get { return _image.Width; }
    }

    public int Height
    {
        get { return _image.Height; }
    }

    public short Type
    {
        get { return 1; }
    }

    public int Handle
    {
        get
        {
            if (_handle == IntPtr.Zero)
            {
                _handle = _image.GetHbitmap();
            }
            return _handle.ToInt32();
        }
    }
 
    public int hPal
    {
        get { return 0; }
        set { }
    }
 
    public void Render(
        int hdc, int x, int y, int cx, int cy, int xSrc, int ySrc, int cxSrc, int cySrc, IntPtr prcWBounds)
    {
        Graphics graphics = Graphics.FromHdc(new IntPtr(hdc));
        graphics.DrawImage(
            _image, new Rectangle(x, y, cx, cy), xSrc, ySrc, cxSrc, cySrc, GraphicsUnit.Pixel);
    }
 
    #endregion

    #region IPicture Members
 
    public int Attributes
    {
        get { return 0; }
    }

    public int CurDC
    {
        get { return 0; }
    }

    public bool KeepOriginalFormat
    {
        get { return false; }
        set { }
    }
 
    public void PictureChanged()
    {
    }
 
    public void SaveAsFile(IntPtr pstm, bool fSaveMemCopy, out int pcbSize)
    {
        pcbSize = 0;
    }

    public void SelectPicture(int hdcIn, out int phdcOut, out int phbmpOut)
    {
        phdcOut = 0;
        phbmpOut = 0;
    }

    public void SetHdc(int hdc)
    {

    }
    #endregion
}