See the question and my original answer on StackOverflow

What happens is Microsoft has removed support for ITypeInfo from the COM Callable wrapper implementation in .NET Core (all versions), the object which also implements IDispatch. This is briefly discussed here https://github.com/dotnet/runtime/issues/86751 and in fact mentioned in documentation:

enter image description here

Contrary to what the github discussion seems to imply, this is a big breaking change that causes lots of trouble, which is difficult to diagnose, and has no easy solution.

The ITypeInfo reference is retrieved by the dynamic code using IDispatch::GetTypeInfo when you try to call any member by a name (such as myadd) on your object:

Retrieves the type information for an object, which can then be used to get the type information for an interface.

HRESULT GetTypeInfo( UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo );

Now, to add to the problem, your calling code is .NET Framework, and the dynamic implementation is different between the two:

.NET Framework (throws even if type info count = 0):

internal static ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch, bool throwIfMissingExpectedTypeInfo)
{
  int errorCode = dispatch.TryGetTypeInfoCount(out var pctinfo);
  Marshal.ThrowExceptionForHR(errorCode); // noooooooo !
  if (pctinfo == 0)
    return null;
  ...
}

This is what ultimately causes the System.Runtime.InteropServices.COMException: 'Typelib export: Type library is not registered. (Exception from HRESULT: 0x80131165)' error

.NET Core (more recent, less stupid):

internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch)
{
    int hresult = dispatch.TryGetTypeInfoCount(out uint typeCount);
    if (typeCount == 0)
    {
        // COM objects should return a type count of 0 to indicate that type info is not exposed.
        // Some COM objects may return a non-success HRESULT when type info is not supported, so
        // we only check the count and not the HRESULT in this case.
        return null;
    }

    Marshal.ThrowExceptionForHR(hresult); // yeeeeeees !
    ...
}

There are multiple solutions:

IDispatch based: If you want to continue using dynamic, you can either use a .NET Core client or use the "old" reflection way, like this (doesn't uses ITypeInfo, goes directly to IDispatch GetIDsOfNames and Invoke):

var sum = obj.GetType().InvokeMember(
    "myadd",
    BindingFlags.Public | BindingFlags.InvokeMethod,
    null,
    obj,
    new object[] { 5, 6 });

IUnknown based: Since you have defined a dual ICalculator interface (IDispatch + IUnknown-derived), you can use it instead of using a late-bound (IDispatch) approach.

For that, the system must know the details of that interface to be able to marshal it across threads/processes/machines, ie: it needs a .TLB (also make sure you stick to already known data types or you'll need a proxy / stub .dll which is out of .NET reach). So you must 1) export a .TLB (a Type Library file, you can also embed it in a .dll or a .exe) from the .NET Code and 2) register it, using for example dscom, an unofficial tool which does work fine with this sample code (Microsoft has also removed the .TLB generation and registration facility that existed in regasm for .NET Framework). Make sure you build everything in x64 and use dscom for x64 too (not x86), something like this:

dscom.exe tlbexport c:\temp\core dll\bin\Debug\net6.0\Library1.dll
dscom.exe tlbregister Library1.tlb

and then change your calling .NET Framework code like this:

var calc = (ICalculator)Marshal.GetActiveObject("Library1.Calculator");
var res = calc.myadd(5, 6);
TextBox1.Text = res.ToString();

PS: There's another solution more complex which is to expose a wrapper on the object, on the .NET Core host side, instead of the object itself, that would reimplement IDispatch w/o reporting any error when .NET Framework calls IDispatch::GetTypeInfo and internally calls the object's IDispatch implementation