繁体   English   中英

将 C# .NET 6.0 Core DLL 导入到没有 TF6F87C9FDCF8B3C3F07F93F1EE8712 文件的 ZF6F87C9FDCF8B3C3F07F93F1EE8712 中

[英]Importing a C# .NET 6.0 Core DLL into a C++ without TLB file

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

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

随着升级到 .NET Core 6,失去了在运行时创建 .tlb 文件的能力。 但是,(显然)互操作性应该是直截了当的。 我见过 Nuget 包,如 DllExport(其中包含不明确的 SafeArrays 示例)。 我已经阅读了微软关于 COM 互操作和 COM 托管的参考资料,并阅读了相关示例。 我简要研究了构建自己的.tlb 文件的兔子洞。 我已经看到了许多自定义示例,这些示例并未扩展到这个确切的问题。

这是我试图将一些代码连接在一起,作为您的示例。 我会对 go 做出什么样的改变?

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
  }
    
}

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

// 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;
}

如上所示,这种交互是有效的。 有没有人知道如何继续连接这两者,但使用 .NET Core 6 中的 C#? 最好用最少的努力。 我想大部分工作已经完成,因为你可以看到类型是编组的。 现在如何在没有 tlb 文件的情况下导入 C# DLL?

编辑

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

更新

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

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

COM 互操作方法:

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

在没有 COM 互操作的情况下导入 .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
    }
}

突破,在 dscom 项目人员的帮助下,我得到了 tlb 的工作!! 非常感谢@epeshk 和@halfer!

对于其他人:我忘记的关键是我需要使用 regsvr32.exe 从 C# 项目中注册 comhost.dll。 我还需要注册 tlb。

最后,对于我的 32 位 C++,整个事情必须在 x86 而不是任何 CPU 中构建,以确保 comhost 具有相同的 32 位架构。 在 dscom 上,他们现在有一个制作 32 位 tlb 的工具——他们非常有帮助!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM