
[英]Finding the namespace of a C# dll while using it in VB 6.0 through exporting it as .tlb file
[英]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 互操作方法:
在没有 COM 互操作的情况下导入 .NET 库:
摆脱 COM/SafeArray,在方法签名和手动编组数组和字符串参数中仅使用原始类型。
而不是double[]
传递两个参数:指向第一个元素的指针和数组大小,如下所示: double* d_ptr, int d_size
,然后将其包装在 Span 中。 如果您想继续使用数组而不是跨度,只需通过span.ToArray()
进行转换
而不是string
传递一个指向第一个符号的指针,然后使用Marshal.PtrToStringAnsi
将其转换为托管字符串。
[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.