将 C# .NET 6.0 Core DLL 导入没有 TLB 文件的 C++

Oli*_*man 7 c# c++ interop marshalling .net-core

我有很多在 .NET 4.6 Framework 中制作的 C# DLL,需要升级到 .NET Core 6 - 这部分相对简单。

但是,该库还可以通过 COM Interop 在 C++ 应用程序中使用。此选项在 .NET 4.6 中相对简单,因为在构建时有一个选项可以公开给 COM Interop。这创建了一个.tlb文件,可以直接导入到 C++ 应用程序中。所有必要的编组都是在 C# 代码中完成的 - 数组作为 SafeArray 等传递(请参见下面的示例)。Regasm.exe 在事后运行以注册必要的 C# 代码。

升级到 .NET Core 6 后,失去了在运行时创建 .tlb 文件的功能。然而,(显然)互操作性应该是简单的。我见过像 DllExport 这样的 Nuget 包(其中有 SafeArrays 的不清楚的示例)。我已经阅读了一页又一页的 Microsoft 关于 COM 互操作和 COM 托管的参考资料,并阅读了相关示例。我简要地研究了构建我自己的 .tlb 文件的兔子洞。我见过许多自定义示例,但它们并没有扩展到这个确切的问题。

以下是我尝试连接在一起的一些代码,作为您的示例。我会做出什么样的改变?

要导入的 C# 代码

public void CSharpMacro(
  [MarshalAs(UnmanagedType.SafeArray)] double[] D,
  [MarshalAs(UnmanagedType.SafeArray)] double[] O,
  [MarshalAs(UnmanagedType.SafeArray)] double[] H,
  [MarshalAs(UnmanagedType.SafeArray)] double[] L,
  [MarshalAs(UnmanagedType.SafeArray)] double[] C,
  [MarshalAs(UnmanagedType.SafeArray)] double[] V,
  [MarshalAs(UnmanagedType.LPStr)] string FilePath,
  [MarshalAs(UnmanagedType.SafeArray)] ref double[] sOutput,
  [MarshalAs(UnmanagedType.I8)] long CustNum,
  [MarshalAs(UnmanagedType.R8)] double TSDate) 
{
  
  String path = @ "...\Errors.txt";
  
  try 
  {
    //some code
  } 
  catch (Exception e) 
  {
    //error handling
  }
    
}
Run Code Online (Sandbox Code Playgroud)

C++ 导入示例(当前方法)- 请注意,这是很多代码,但目的是向您展示如何.tlb导入这些代码以及如何在代码中使用 C# 中的方法,以及围绕此进行的一些操作,例如SafeArray 的使用:

// In the below import statement, use the location of the Release version of your C# DLL 
#import "...\PSP_CSLibrary.tlb" no_namespace

//later in the code... (EasyObject is from external library, don't worry about it)
void far __declspec(dllexport) __stdcall UMacro(EasyObject* pELObj, char* path)
{

    // Initialize the COM interface
    HRESULT hr = CoInitialize(NULL);
    try
    {
        if (SUCCEEDED(hr) && pELObj != NULL)
        {
            // Init pointer to C# Library
            PSP_CSLibraryDLLClassPtr p(__uuidof(PSP_CSLibrary));
            if (p != NULL)
            {
                int datanumber = 1;
                double *dOutput = new double[100];
                for (int j = 0; j < 100; j++) dOutput[j] = 0;
                SAFEARRAY* sOutput = doubleToSA(dOutput, 100);
                EN_DATA_STREAM datastream = (datanumber == 1) ? pELObj->DataStream : GetDataStream(datanumber);
                double TSDate = pELObj->DateTimeMD[datastream]->AsDateTime[0];
                long CustomerNumber = pELObj->Platform->CustomerID;
                int length = pELObj->CloseMD[datastream]->BarsBack;
                if (length > 0)
                {
                    double *dDate = new double[length];
                    double *dOpen = new double[length];
                    double *dHigh = new double[length];
                    double *dLow = new double[length];
                    double *dClose = new double[length];
                    double *dVolume = new double[length];
                    // Load the double arrays with LEAST recent 0
                    for (int i = 0; i < length; i++)
                    {
                        dVolume[length - i - 1]     = pELObj->VolumeMD[datastream]->AsDouble[i];
                        dOpen[length - i - 1]       = pELObj->OpenMD[datastream]->AsDouble[i];
                        dHigh[length - i - 1]       = pELObj->HighMD[datastream]->AsDouble[i];
                        dLow[length - i - 1]        = pELObj->LowMD[datastream]->AsDouble[i];
                        dClose[length - i - 1]      = pELObj->CloseMD[datastream]->AsDouble[i];
                        dDate[length - i - 1]       = pELObj->DateTimeMD[datastream]->AsDateTime[i];
                    }
                    // Convert to safe arrays from double arrays
                    SAFEARRAY* sVolume = doubleToSA(dVolume, length);
                    SAFEARRAY* sHigh = doubleToSA(dHigh, length);
                    SAFEARRAY* sOpen = doubleToSA(dOpen, length);
                    SAFEARRAY* sLow = doubleToSA(dLow, length);
                    SAFEARRAY* sClose = doubleToSA(dClose, length);
                    SAFEARRAY* sDate = doubleToSA(dDate, length);


/////IMPORTANT PART
                    if (sOpen != nullptr && sHigh != nullptr && sLow != nullptr && sOutput != nullptr && p != nullptr &&  sClose != nullptr && dOutput != nullptr)
                    {
                        p->CSharpMacro(sDate, sOpen, sHigh, sLow, sClose, sVolume, path, &sOutput, CustomerNumber, TSDate);
                    }

/////
                    // Release memory
                    if (sDate != nullptr)   SafeArrayDestroy(sDate);
                    if (sVolume != nullptr) SafeArrayDestroy(sVolume);
                    if (sOpen != nullptr)   SafeArrayDestroy(sOpen);
                    if (sHigh != nullptr)   SafeArrayDestroy(sHigh);
                    if (sLow != nullptr)    SafeArrayDestroy(sLow);
                    if (sClose != nullptr)  SafeArrayDestroy(sClose);
                    if (sOutput != nullptr) SafeArrayDestroy(sOutput);
                    if (dDate != nullptr)   delete[] dDate;
                    if (dVolume != nullptr) delete[] dVolume;
                    if (dOpen != nullptr)   delete[] dOpen;
                    if (dHigh != nullptr)   delete[] dHigh;
                    if (dLow != nullptr)    delete[] dLow;
                    if (dClose != nullptr)  delete[] dClose;
                    if (dOutput != nullptr) delete[] dOutput;
                }
            }
            p->Release();
        }

    }
    catch (exception &e)
    {
        // did we get it?
        string out_ = "UMacro error= ";
        std::ostringstream strs;
        strs << e.what();
        std::string str = strs.str();
        out_ += str;
        appendLineToFile("PSP_Interface_Errors.txt", out_);
    }
    CoUninitialize();
    return;
}
Run Code Online (Sandbox Code Playgroud)

如上所示,这种交互是有效的。有谁知道如何继续将这两者联系起来,但使用 .NET Core 6 中的 C# 吗?最好以最小的努力。我想大部分工作已经完成,因为类型已经整理好,如您所见。现在如何在没有 tlb 文件的情况下导入 C# DLL?

编辑

请注意,这必须是非托管C++ 中的。我目前正在研究使用 C++/CLR 接口层,并取得了一定的成功。成功的话会更新。

更新

看来我的方法没有奏效。我为其创建此接口的程序不接受 CLR DLL,即使它们是由本机 C++ DLL 链接/导入的。因此,该代码适用于本机 C++,但不适用于预期环境,即股票交易程序。我有点回到了原点。

注意:我怀疑 .tlb 文件之所以有效,是因为它使用 COM,而不是尝试引入 CLR dll。因此某种形式的 COM 互操作或手动构建 .tlb 文件应该可以工作。

epe*_*shk 2

COM 互操作方法:

  • 尝试使用dscom工具从 .NET 6 程序集生成 *.tlb。是的,它是一个预发布工具,但您的界面看起来不太复杂,所以它应该可以工作。

在没有 COM Interop 的情况下导入 .NET 库:

  • 摆脱 COM/SafeArray,在方法签名中仅使用原始类型以及手动编组数组和字符串参数。

    不要double[]传递两个参数:指向第一个元素的指针和数组大小,如下所示:double* d_ptr, int d_size,然后将其包装在 Span 中。如果您想继续使用数组而不是跨度,只需将其转换为span.ToArray()

    而不是string传递指向第一个符号的指针,然后使用Marshal.PtrToStringAnsi它将其转换为托管字符串。

    您可以从 DllExport文档教程中了解有关手动编组的更多信息。

    然后使用DllExport库或通过NativeAOT从 C# 代码编译本机库。

[UnmanagedCallersOnly(EntryPoint="CSharpMacro")] // this attribute also prevents from using incompatible non-primitive types
public static void CSharpMacro(
    double* dArray, int dSize,
    double* oArray, int oSize,
    double* hArray, int hSize,
    double* lArray, int lSize,
    double* cArray, int cSize,
    double* vArray, int vSize,
    IntPtr filePath, // string is null-terminated, don't need to pass a length
    double* sArray, int sSize,
    long CustNum,
    double TSDate) 
{
    // manual marshalling
    var D = new Span<double>(dArray, dSize);
    var DArray = D.ToArray(); // just an example how to convert a Span to Array.
    var O = new Span<double>(oArray, oSize);
    var H = new Span<double>(hArray, hSize);
    var L = new Span<double>(lArray, lSize);
    var C = new Span<double>(cArray, cSize);
    var V = new Span<double>(vArray, vSize);
    var SOutput = new Span<double>(sArray, sSize);
    var FilePath = Marshal.PtrToStringAnsi(filePath);

    try 
    {
        //some code
    } 
    catch (Exception e) 
    {
        //error handling
    }
}

Run Code Online (Sandbox Code Playgroud)