The whole point is to avoid doing the unsafe code in C#. Pass the managed type (2-d or jagged array) to the C++/CLI ref class. The C++/CLI ref class converts this to the native pointer type and calls the native function. That's how this is typically done.
[Update (this comment vs answer thing is kinda messy!)]
----------------------------------------------------------------
Here's a sample showing how you can pass a 2-d array to a native function expecting a pointer to pointer.
void Foo(double** nums, int xLen, int yLen)
{
for (int i=0; i<xLen; i++)
{
for (int j=0; j<yLen; j++)
{
double d = *(double*)(nums[i] + j);
}
}
}
int main(. . .)
{
array<double, 2>^ numbers = gcnew array<double, 2>(2,3);
numbers[0,0] = 12.3;
numbers[0,1] = 2.1;
numbers[0,2] = 4.3;
numbers[1,0] = 23.2;
numbers[1,1] = 7.7;
numbers[1,2] = 17.6;
pin_ptr<double> p1 = &numbers[0,0];
pin_ptr<double> p2 = &numbers[1,0];
double* p3[2];
p3[0] = p1;
p3[1] = p2;
double** p4 = p3;
Foo(p4, 2, 3);
return 0;
}