Ok...I got it (what a pain)! I want to post the answer here for posterity in case somebody else has a similar problem in the future. For the record, I found the answer in a book a co-worker lent me (COM and .NET Interoperability by Andrew Troelsen). The VB function signature:
Public Function MyFunction(varNames() As String) As String()
showed up as this in the assembly like this:
.method public hidebysig newslot virtual
instance string[]
marshal( safearray bstr)
MyFunction([in][out] string[]& marshal( safearray bstr) varNames) runtime managed internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 15 00 03 60 00 00 ) // .....`..
.override COMInterface._COMClass::MyFunction
} // end of method COMClass::MyFunction
The problem was this assembly (and intellisense) was telling it it had to be a safearray and apparently it just wouldn't take a string[]! Not even if it was instantiated on initializaation like:
string[] varNames = new string[] {"One", "Two" };
The intellisense was telling me it had to be a "System.Array", not a string[]! What needs to be done is not a "cast", but intantiating a System.Array and set it equal to the string array that was initialized and then pass in the ref to that array. This was the way to get it working was:
COMInterface.COMObj oCOMObject = new COMInterface.COMObj();
string[] sNames = new string[] { "ProjectName" }; // 1 element array
System.Array oTemp = sNames;
string[] sValues = new string[1];
System.Array oTempVals = sValues;
oTempVals = oCOMObject.TheCOMFunction( ref oTemp );
It wasn't intuitive....but now, in retrospect, it makes sense. Apparently you have to use the "System.Array" type for the "safearray" that it expects in the assembly, and since it doens't like you to cast it from string[] to System.Array, you have to just create an instance and set it equal.
There are only 10 types of people in this world....those that understand binary, and those that do not.