Skip to content

CSharp bindings: user-defined string encoding and improved safety#14366

Open
Mbucari wants to merge 11 commits into
OSGeo:masterfrom
Mbucari:master
Open

CSharp bindings: user-defined string encoding and improved safety#14366
Mbucari wants to merge 11 commits into
OSGeo:masterfrom
Mbucari:master

Conversation

@Mbucari
Copy link
Copy Markdown
Contributor

@Mbucari Mbucari commented Apr 15, 2026

What does this PR do?

Creates an interface for users to create custom string Encoder/Decoders for Gdal to use for all string operations. This serves a purpose similar to #3825 by allowing users to manage how strings are encoded/decoded to/from Gdal.

Expose I$moduleStringEncoder interfaces which have 2 methods:

  • string FromNullTerminated(IntPtr pStr); - Read native null-terminated data from a Gdal pointer and return a managed string.
  • byte[] ToNullTerminated(string str); - Create a null-terminated array of bytes from a managed string to send to Gdal.

It also simplifies string marshalling changes made in recent PR by no longer requires C# to allocate/free any unmanaged memory. All string/byte[] objects are managed.


Here's an example of a custom StringEncoder.

class CustomGdalEncoder : OSGeo.GDAL.IGdalStringEncoder, OSGeo.OGR.IOgrStringEncoder, OSGeo.OSR.IOsrStringEncoder
{
    public string FromNullTerminated(IntPtr pStr)
      => Marshal.PtrToStringUTF8(pStr);

    public byte[] ToNullTerminated(string str)
    {
        if (str == null)
            return null;
        int byteCount = Encoding.UTF8.GetByteCount(str);
        var bts = new byte[byteCount + 1];
        Encoding.UTF8.GetBytes(str, 0, str.Length, bts, 0);
        return bts;
    }
}
var encoder = new CustomGdalEncoder();
OSGeo.GDAL.Gdal.GlobalStringEncoder = encoder;
OSGeo.OGR.Ogr.GlobalStringEncoder = encoder;
OSGeo.OSR.Osr.GlobalStringEncoder = encoder;

@runette

What are related issues/pull requests?

#3825
#14283

Tasklist

  • Make sure code is correctly formatted
  • Review
  • Adjust for comments

Environment

Provide environment details, if relevant:
Mono and dotnet

@runette
Copy link
Copy Markdown
Collaborator

runette commented Apr 15, 2026

It is probably going to be next week before I can look at this

@Mbucari Mbucari marked this pull request as draft April 15, 2026 16:14
@Mbucari Mbucari changed the title CSharp bindings: user-defined string marshalling and improved safety CSharp bindings: user-defined string encoding and improved safety Apr 15, 2026
@Mbucari Mbucari marked this pull request as ready for review April 15, 2026 22:08
@runette
Copy link
Copy Markdown
Collaborator

runette commented May 6, 2026

@Mbucari can you rebase against master and run the tests again

@Mbucari
Copy link
Copy Markdown
Contributor Author

Mbucari commented May 19, 2026

@runette I rebased to master and incorporated this PR's changes into the new exception helper (#14474).

@runette
Copy link
Copy Markdown
Collaborator

runette commented May 19, 2026

@runette I rebased to master and incorporated this PR's changes into the new exception helper (#14474).

I was about to ask :)

Comment thread swig/include/csharp/csharp_strings.i Outdated
Comment on lines +238 to 239
%typemap(imtype) (char **ignorechange) "byte[]"
%typemap(cstype) (char **ignorechange) "ref string"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new typedef for char **ignorechange is nice because it handles the extra level of indirection in the in definition below, so cstype doesn't even need to be a ref string. This raises the question, how loathed is the GDAL community to change/remove a function after it's already been made public? If we changed this typedef to just accept a string, then only 2 methods would change: SpatialReference.ImportFromWkt(ref string); and Ogr.CreateGeometryFromWkt(ref string, SpatialReference). This would be more convenient, but it would break things downstream (albeit things which are easily fixed).

This question will be relevant to future additions I have planned, such as adding multi-argument typemaps for arrays (like already exist for Java) which will eliminate the need to pass both the array and the array length.

Mbucari and others added 10 commits June 2, 2026 07:34
Expose $module.IStringMarshaller interfaces which have 2 methods:
 - `string FromNullTerminated(IntPtr pStr)` - Read native null-terminated data from a Gdal pointer and return a managed string.
 - `byte[] ToNullTerminated(string str)` - Create a null-terminated array of bytes from a managed string to send to Gdal.

Public static properties `$module.StringMarshaller` can be used to get/set a custom marshaller interfaces. The default marshaller is internal `DefaultStringMarshaller`.

String helper callback has been changed.
When `SWIG_csharp_string_callback` is called, it decodes the unmanaged Gdal string to a managed .NET string (using StringMarshaller.ToNullTerminated()) and then returns a pointer to a pinned GCHandle of that string. StringFromPinnedGCHandle() is called from the C# methods to retrieve the string and free the GCHandle.
Create a dummy SWIG object, CsharpDummyObject, that can be extended using typemap(csimports) to add custom C# types to the module namespace.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants