A pointer to char in C is not equivalent to a pointer to CHAR in Modula-2.
In C a string may be either a char array or a pointer to a single char where the lack of type safety is then EXPLOITED to ignore the fact that the pointer type points to a single char, not a character string, and with DEVASTATING CONSEQUENCES !!!
By contrast, in Modula-2 a string is a character array with a maximum capacity associated to the type and type safety is enforced, thus a pointer to a singe character is always interpreted correctly as having a payload of only one single character.
Thus, the closest equivalent of
char* str;
in Modula-2 would be
POINTER TO ARRAY [0..MaxStrLen] OF CHAR;
where MaxStrLen must be a compile time constant, that is, it cannot be changed dynamically at runtime.
And if you have a static character array string in Modula-2, like
VAR str : ARRAY [0..80] OF CHAR;
then you can't just pass str to a char* parameter of a C function. Instead you need to pass a pointer to it.
TYPE Str80 = ARRAY [0..80] OF CHAR;
VAR str : Str80;
TYPE Str80Ptr = POINTER TO Str80;
VAR strPtr : Str80Ptr;
then
str := "the quick brown fox jumps over the lazy dog.";
strPtr := VAL(Str80Ptr, ADR(str));
then
passToC(strPtr);
assuming
void passToC(const char* s);
Although GM2 may already map an argument of a character array type to char* when using the DEFINITION MODULE FOR "C" syntax to map C functions. Even if it does, it likely won't do the same for char** and char***.
Thus, if the C function parameters are char** then you need
POINTER TO POINTER TO ARRAY [0..MaxStrLen] OF CHAR;
Likewise for char*** you need
POINTER TO POINTER TO POINTER TO ARRAY [0..MaxStrLen] OF CHAR;
As I have mentioned before, the best way to interface to C APIs is to use a layered approach where the lowest level interfaces directly with the C API and a user level provides a wrapped Modula-2 representation that is independent of the C API. In the lower level library you can then convert and cast types as needed to pass between C and Modula-2.