Skip to content

CSharp bindings: add support for file/buffer sizes larger than 2GB#14380

Open
Mbucari wants to merge 4 commits intoOSGeo:masterfrom
Mbucari:csharp_64bit_buffers
Open

CSharp bindings: add support for file/buffer sizes larger than 2GB#14380
Mbucari wants to merge 4 commits intoOSGeo:masterfrom
Mbucari:csharp_64bit_buffers

Conversation

@Mbucari
Copy link
Copy Markdown
Contributor

@Mbucari Mbucari commented Apr 17, 2026

What does this PR do?

  • Adjust FileFromMemBuffer and VSIF* function definitions to work with buffers and files larger than 2GB.
  • Add FileFromMemBufferNoCopy method to create an in-memory file from a managed buffer. Returns a SafeHandle to the memory file, which is released on disposal.
  • Add helper overloads to VSIF* funcitons
  • Use Int64 for GetCacheMax(), GetCacheUsed(), and SetCacheMax()
  • Add tests

@runette

What are related issues/pull requests?

Fixes #6413

AI tool usage

  • AI (Y-a-t-il-un-Copilot-dans-l'avion, Chat-j'ai-pété, Jean-Claude Dusse or something similar) supported my development of this PR. See our policy about AI tool use. Use of AI tools must be indicated.

Tasklist

  • Make sure code is correctly formatted (cf pre-commit configuration)
  • Add test case(s)
  • Add documentation
  • Review
  • Adjust for comments
  • All CI builds and checks have passed

Environment

Provide environment details, if relevant:

  • OS: Windows
  • Compiler: VS 2016 / dotnet 10

Comment thread swig/include/csharp/gdal_csharp.i Outdated
Comment on lines +319 to +339
private class OwnedMemoryFileHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid {
private readonly GCHandle dataHandle;
public string FileName { get; }
public OwnedMemoryFileHandle(string path, byte[] buffer, long offset, long count)
: base(ownsHandle: true) {
FileName = path;
dataHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
ValidateBufferArgs(buffer, offset, count);
IntPtr ptr = AddOffset(dataHandle.AddrOfPinnedObject(), offset);
handle = VSIFileFromMemBuffer(FileName, ptr, (ulong)buffer.LongLength, bTakeOwnership: 0);
}
protected override void Dispose(bool disposing){
base.Dispose(disposing);
dataHandle.Free();
}
protected override bool ReleaseHandle() {
bool success = VSIFCloseL(handle) == 0;
success &= Unlink(FileName) == 0;
return success;
}
}
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.

Pay extra attention to this class. The existing FileFromMemBuffer binding copies the data to Gdal and then closes the VSILFILE.

Here, the VSILFILE is left open. I was torn between returning an open VSILFILE (which is what this does), or closing the VSILFILE and just returning an IDisposable that unpins the memory and unlinks the file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not had a chance to look in detail yet - but my gut is telling me that if we make a change like that to how the memory is handled we are going to break something downstream. But - I could be misunderstanding what you are saying

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.

To clarify, this isn't a change to an existing method. This class is used by the entirely new method FileFromMemBufferNoCopy.

Currently, FileFromMemBuffer is only accessible through wrapper_VSIFileFromMemBuffer, which copies the memory to an unmanaged buffer owned by GDAL. This is inefficient, especially for large buffers (#6413 is trying to create a >2GB memory file). This PR exposes VSIFileFromMemBuffer and, FileFromMemBufferNoCopy pins the managed buffer and retains ownership of memory so that it doesn't need to be copied.

Copy link
Copy Markdown
Contributor Author

@Mbucari Mbucari Apr 20, 2026

Choose a reason for hiding this comment

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

I've thought about this more, and I think FileFromMemBufferNoCopy should behave like wrapper_VSIFileFromMemBuffer by closing the VFFile after it is created. Here's my proposed change:

public sealed class OwnedMemoryFile : IDisposable {
  private readonly GCHandle m_dataHandle;
  private int m_disposed;
  public string Filename { get; }
  public bool IsDisposed => m_disposed != 0;
  internal OwnedMemoryFile(string filename, GCHandle dataHandle) {
    Filename = filename;
    m_dataHandle = dataHandle;
  }
  public void Dispose() {
    if (System.Threading.Interlocked.CompareExchange(ref m_disposed, 1, 0) == 0) {
      m_dataHandle.Free();
      Unlink(Filename);
    }
    GC.SuppressFinalize(this);
  }
  ~OwnedMemoryFile() => Dispose();
}

public static OwnedMemoryFile FileFromMemBufferNoCopy(string utf8_string, byte[] buffer, long offset, long count) {
  ValidateBufferArgs(buffer, offset, count);
  GCHandle dataHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
  IntPtr ptr = AddOffset(dataHandle.AddrOfPinnedObject(), offset);
  IntPtr fp = VSIFileFromMemBuffer(utf8_string, ptr, (ulong)buffer.LongLength, bTakeOwnership: 0);
  if (fp == IntPtr.Zero) {
    dataHandle.Free();
    return null;
  } else {
    VSIFCloseL(fp);
    return new OwnedMemoryFile(utf8_string, dataHandle);
  }
}

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.

I committed the above changes.

@Mbucari Mbucari requested a review from runette April 23, 2026 15:02
@runette
Copy link
Copy Markdown
Contributor

runette commented May 6, 2026

@Mbucari Can you rebase against the new Cmake scripts in master

@Mbucari Mbucari force-pushed the csharp_64bit_buffers branch from c8873c3 to 2d56662 Compare May 6, 2026 17:53
@Mbucari
Copy link
Copy Markdown
Contributor Author

Mbucari commented May 6, 2026

rebase against the new Cmake scripts in master

Done. @rouault can you please re-run the tests?

@rouault
Copy link
Copy Markdown
Member

rouault commented May 6, 2026

@runette Please approve if OK for you

@Mbucari
Copy link
Copy Markdown
Contributor Author

Mbucari commented May 6, 2026

@runette I mistakenly rebased to my master branch, which contains all changes for #14366. So this PR now includes all string changes from that PR. Should I just close that PR and let you review this one which contains all changes?

@runette
Copy link
Copy Markdown
Contributor

runette commented May 6, 2026

@Mbucari - the two PRs seem very different in intent I would be concerned about traceability and auditability if we do that. You could use reflog to revert to before the rebase and start again?

@Mbucari Mbucari force-pushed the csharp_64bit_buffers branch from 2d56662 to 22be4e9 Compare May 7, 2026 00:47
Mbucari added 3 commits May 6, 2026 17:48
- Adjust FileFromMemBuffer and VSIF* function definitions to work with buffers and files larger than 2GB.
- Add FileFromMemBufferNoCopy method to create an in-memory file from a managed buffer. Returns a SafeHandle to the memory file, which is released on disposal.
- Add helper overloads to VSIF* funcitons
- Use Int64 for GetCacheMax(), GetCacheUsed(), and SetCacheMax()
- Add tests
Make FileFromMemBufferNoCopy behave like the existing FileFromMemBuffer method by closing the handle to the VSI File before returning.
@Mbucari Mbucari force-pushed the csharp_64bit_buffers branch from 22be4e9 to b047276 Compare May 7, 2026 00:49
@Mbucari
Copy link
Copy Markdown
Contributor Author

Mbucari commented May 7, 2026

@runette Success. thanks.

@Mbucari Mbucari force-pushed the csharp_64bit_buffers branch from 9c26ae5 to 2f6b47a Compare May 7, 2026 02:45
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.

c# can't configure '/vsimem/' with Big-Tiff file (over 2GB size)

3 participants