title: Importing a C# .NET 6.0 Core DLL into a C++ without TLB file
body: <p>I have quite a number of C# DLLs that were made in .NET 4.6 Framework and need to get upgraded to .NET Core 6 - that part is relatively straightforward.</p>
  <p>However, this library ALSO is used within a C++ application, via COM Interop. This option was relatively straightforward in .NET 4.6, because at build time there was an option to expose to COM Interop. This created a <code>.tlb</code> file, which could be imported directly into the C++ app. All of the necessary marshalling was done within the C# code - arrays were passed as SafeArrays, etc. (See example below). Regasm.exe was run after the fact to register the necessary C# code.</p>
  <p>With the upgrade to .NET Core 6, the ability to create a .tlb file at runtime was lost. However, (apparently) interoperability should be straightforward. I have seen Nuget packages like DllExport (which has unclear examples for SafeArrays). I've read page after page of Microsoft's references on COM interop and COM hosting, and read relative examples. I briefly looked into the rabbit hole of building my own .tlb file. I've seen many custom examples that do <em>not</em> extend to this exact issue.</p>
  <p>Here is some of the code that I am trying to hook together, as an example for you. What kind of changes would I go about making?</p>
  <p>C# Code to import</p>
  <pre><code>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 = @ &quot;...\Errors.txt&quot;;
    
    try 
    {
      //some code
    } 
    catch (Exception e) 
    {
      //error handling
    }
      
  }
  </code></pre>
  <p>C++ Importing Example (current method) - Please note, this is a lot of code but the purpose is to show you how the <code>.tlb</code> is imported and how the method from C# is used within the code, and some of what goes on around that, like the use of SafeArrays:</p>
  <pre><code>// In the below import statement, use the location of the Release version of your C# DLL 
  #import &quot;...\PSP_CSLibrary.tlb&quot; 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) &amp;&amp; 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 &lt; 100; j++) dOutput[j] = 0;
                  SAFEARRAY* sOutput = doubleToSA(dOutput, 100);
                  EN_DATA_STREAM datastream = (datanumber == 1) ? pELObj-&gt;DataStream : GetDataStream(datanumber);
                  double TSDate = pELObj-&gt;DateTimeMD[datastream]-&gt;AsDateTime[0];
                  long CustomerNumber = pELObj-&gt;Platform-&gt;CustomerID;
                  int length = pELObj-&gt;CloseMD[datastream]-&gt;BarsBack;
                  if (length &gt; 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 &lt; length; i++)
                      {
                          dVolume[length - i - 1]     = pELObj-&gt;VolumeMD[datastream]-&gt;AsDouble[i];
                          dOpen[length - i - 1]       = pELObj-&gt;OpenMD[datastream]-&gt;AsDouble[i];
                          dHigh[length - i - 1]       = pELObj-&gt;HighMD[datastream]-&gt;AsDouble[i];
                          dLow[length - i - 1]        = pELObj-&gt;LowMD[datastream]-&gt;AsDouble[i];
                          dClose[length - i - 1]      = pELObj-&gt;CloseMD[datastream]-&gt;AsDouble[i];
                          dDate[length - i - 1]       = pELObj-&gt;DateTimeMD[datastream]-&gt;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 &amp;&amp; sHigh != nullptr &amp;&amp; sLow != nullptr &amp;&amp; sOutput != nullptr &amp;&amp; p != nullptr &amp;&amp;  sClose != nullptr &amp;&amp; dOutput != nullptr)
                      {
                          p-&gt;CSharpMacro(sDate, sOpen, sHigh, sLow, sClose, sVolume, path, &amp;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-&gt;Release();
          }
  
      }
      catch (exception &amp;e)
      {
          // did we get it?
          string out_ = &quot;UMacro error= &quot;;
          std::ostringstream strs;
          strs &lt;&lt; e.what();
          std::string str = strs.str();
          out_ += str;
          appendLineToFile(&quot;PSP_Interface_Errors.txt&quot;, out_);
      }
      CoUninitialize();
      return;
  }
  </code></pre>
  <p>This interaction, as shown above, <em>works</em>. Does anyone have ANY insight on how to continue to link up these two but with the C# in .NET Core 6? Preferably with minimal effort. I would like to think most of the work is already done, since types ARE marshalled as you can see. How do I import a C# DLL now, without a tlb file?</p>
  <h2>Edit</h2>
  <p>As a note, this must be in <em>unmanaged</em> C++. I am currently investigating using a C++/CLR interface layer, and have gotten moderate success. Will update if successful.</p>
  <h2>Update</h2>
  <p>Looks like my approach didn't work. The program I'm making this interface for doesn't accept CLR DLLs, even if they are linked/imported by a native C++ DLL. So the code works for native C++, but not for the intended environment, which is a stock trading program. I'm somewhat back to square one.</p>
  <p>Note: my suspicion is that the .tlb file worked because it was using COM, rather than trying to bring in a CLR dll. So some form of COM interop, or manually building the .tlb file should work.</p>