See the question and my original answer on StackOverflow

Let's suppose the C# code is this:

namespace ClassLibrary1
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Hello
    {
        public void helloWorld(char[] chars)
        {
           ...
        }
    }
}

Then, you can call it with this C/C++ code, for example:

#import "C:\mycode\ClassLibrary1\bin\Debug\classlibrary1.tlb" raw_interfaces_only

using namespace ClassLibrary1;

HRESULT CallHello(wchar_t* charPtr, int count)
{
  CComPtr<_Hello> p;
  HRESULT hr = p.CoCreateInstance(__uuidof(Hello));
  if (FAILED(hr))
    return hr;

  SAFEARRAY* psa = SafeArrayCreateVector(VT_UI2, 0, count);
  if (!psa)
    return E_OUTOFMEMORY;

  LPVOID pdata;
  hr = SafeArrayAccessData(psa, &pdata);
  if (SUCCEEDED(hr))
  {
    CopyMemory(pdata, charPtr, count * 2); // count is the number of chars
    SafeArrayUnaccessData(psa);
    hr = p->helloWorld(psa);
  }
  SafeArrayDestroy(psa);
  return hr;
}

.NET's char type is unicode, so the binary size is two bytes, the C equivalent is wchar_t (or unsigned short, etc...). So the safearray element type must match that, that's why I used VT_UI2 (VT_R8 that you used is Real of size 8 bytes, so it's equivalent to .NET's double type).

If you really want to use C's char, then you must do some kind of conversion to a 2-byte character.

Also, you can use the SafeArrayCreateVector function which directly allocates a 1-dimension safe array. Don't forget to call cleanup methods.