如何读取 C++ SAFEARRAY**,它是 C# 返回值为 byte[] 的 COM 互操作的结果?

Posted

技术标签:

【中文标题】如何读取 C++ SAFEARRAY**,它是 C# 返回值为 byte[] 的 COM 互操作的结果?【英文标题】:How to read a C++ SAFEARRAY** that is a Result from a COM interop where the C# return value was byte[]? 【发布时间】:2020-02-04 10:03:54 【问题描述】:

我创建了一个 C# DLL,该 DLL 使用创建 QR 图像的 Zebra Crossing Nuget 包 (ZXing.Net.Bindings.CoreCompat.System.Drawing)。

C#方法的签名是:

public interface IWriter

    byte[] CreateQrCode(string content, int width, int height, string imageFormat);
;

我已经成功地在一个 C# 控制台应用程序中使用它来创建各种二维码,其中我将byte[] 返回值作为 png 图像文件写入磁盘。

当然,为了可以从 C++ 中调用它,我在库的属性/构建屏幕上选中了“注册 COM 互操作”复选框,使用强文件名密钥(无密码)对程序集进行签名,并创建了以下内容C++ 应用程序作为概念证明,演示如何使用它:


#include <iostream>

#import "C:\Users\[PATH TO C# BUILD]\ImageGenerator\bin\Debug\ImageGenerator.tlb" raw_interfaces_only

using namespace ImageGenerator;

int main()

    HRESULT hr = CoInitialize(NULL);

    IWriterPtr pICalc(__uuidof(Writer));

    BSTR content = SysAllocString(L"http://www.google.com/");
    BSTR format = SysAllocString(L"png");

    const LONG width = 100;
    const LONG height = 100;

    const LONG count = width * height;

    SAFEARRAY** myArray = NULL;

    pICalc->CreateQrCode(content, width, height, format, myArray);


如何读取myArray 的结果,将其作为文件保存到磁盘?

C# byte[] 数组的长度为 count

C#库代码如下:

using System;
using ZXing;
using System.Drawing;
using ZXing.QrCode;
using ZXing.CoreCompat.System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using BarcodeReader = ZXing.CoreCompat.System.Drawing.BarcodeReader;

namespace ImageGenerator


    public interface IWriter
    
        byte[] CreateQrCode(string content, int width, int height, string imageFormat);
    ;

    /// <summary>
    /// An Image Writer class that creates QR code images in a variety of image formats.
    /// </summary>
    public class Writer : IWriter
    
        public Writer()
        

        

        /// <summary>
        /// Creates a QR Code in a specified image format, of width and height, returning it as byte[].
        /// </summary>
        /// <param name="content">The content that is to be represented by the QR Code.</param>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The Height of the image.</param>
        /// <param name="imageFormat">A text string representing the format of the image, options are png, bmp, emf, exif, gif, icon, jpeg, memorybmp, tiff, and wmf.</param>
        /// <returns></returns>
        public byte[] CreateQrCode(string content, int width, int height, string imageFormat)
        
            ImageFormat format = ImageFormat.Png;

            switch(imageFormat.ToLower())
            
                case "png":
                    format = ImageFormat.Png;
                    break;

                case "bmp":
                    format = ImageFormat.Bmp;
                    break;

                case "emf":
                    format = ImageFormat.Emf;
                    break;

                case "exif":
                    format = ImageFormat.Exif;
                    break;

                case "gif":
                    format = ImageFormat.Gif;
                    break;

                case "icon":
                    format = ImageFormat.Icon;
                    break;

                case "jpeg":
                    format = ImageFormat.Jpeg;
                    break;

                case "memorybmp":
                    format = ImageFormat.MemoryBmp;
                    break;

                case "tiff":
                    format = ImageFormat.Tiff;
                    break;

                case "wmf":
                    format = ImageFormat.Wmf;
                    break;
            

            BarcodeWriter writer = new BarcodeWriter
            
                Format = BarcodeFormat.QR_CODE,
                Options = new QrCodeEncodingOptions
                
                    Width = width,
                    Height = height,
                
            ;

            var qrCodeImage = writer.Write(content); // BOOM!!

            using (var stream = new MemoryStream())
            
                qrCodeImage.Save(stream, format);
                return stream.ToArray();
            
        
    

【问题讨论】:

【参考方案1】:

只需使用SafeArrayAccessData 即可获得返回数据的指针。检查类型,应该是VT_BYTE

你会得到大小(维度)SafeArrayGetDimSafeArrayGetLBoundSafeArrayGetUBound

【讨论】:

【参考方案2】:

在我看来这里有两个主要问题:

    您不能在 COM 互操作方案中使用 ZXing.Net.Bindings.CoreCompat.System.Drawing,因为 .Net Standard 2.0 的 CoreCompat.System.Drawing V2 程序集未签名。如果您检查调用 pICalc->CreateQrCode(...) 的 HRESULT 返回值,您将看到值 0x80131044。如果您真的想使用 CoreCompat.System.Drawing,您必须使用自己的密钥对其进行签名,并针对您的 CoreCompat.System.Drawing 版本构建新版本的 ZXing.Net.Bindings.CoreCompat.System.Drawing。或者,您可以针对 .Net Standard 1.3 定位您的程序集,因为旧版本的 CoreCompat.System.Drawing V1 已签名。但我没有尝试。就我而言,我使用完整的框架 4.6.1 和本机框架的位图功能直接针对 ZXing.Net 检查了您的代码。

    我必须通过以下方式修改您的代码:

    ...
        SAFEARRAY* myArray = NULL;
    
        pICalc->CreateQrCode(content, width, height, format, &myArray);
    
    

    这就是在这种情况下如何使用指向指针的指针。

【讨论】:

以上是关于如何读取 C++ SAFEARRAY**,它是 C# 返回值为 byte[] 的 COM 互操作的结果?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 托管 clr 调用库不在 safearray 参数中返回字符串

将 SAFEARRAY 从 c++ 返回到 c#

如何实现 SAFEARRAY(long) 参数?

遍历 BSTR 的 VARIANT/SAFEARRAY 以在 C++ 中分配值并打印到控制台

SafeArray 的 COM SafeArray

处理从 C# 服务器返回的 SAFEARRAY