Skip to content

Commit 14bd67a

Browse files
authored
CSharp bindings: marshal all strings as UTF-8 (#14283)
1 parent 86e9f26 commit 14bd67a

6 files changed

Lines changed: 591 additions & 230 deletions

File tree

swig/csharp/CMakeLists.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ list(APPEND GDAL_SWIG_COMMON_INTERFACE_FILES
137137
${PROJECT_SOURCE_DIR}/swig/include/csharp/ogr_csharp.i
138138
${PROJECT_SOURCE_DIR}/swig/include/csharp/osr_csharp.i
139139
${PROJECT_SOURCE_DIR}/swig/include/csharp/swig_csharp_extensions.i
140-
${PROJECT_SOURCE_DIR}/swig/include/csharp/typemaps_csharp.i)
140+
${PROJECT_SOURCE_DIR}/swig/include/csharp/typemaps_csharp.i
141+
${PROJECT_SOURCE_DIR}/swig/include/csharp/csharp_strings.i)
141142

142143
# function for csharp wrapper build
143144
function (gdal_csharp_wrap)
@@ -154,9 +155,10 @@ function (gdal_csharp_wrap)
154155
COMMAND ${CMAKE_COMMAND} -E make_directory ${_CSHARP_WORKING_DIRECTORY}
155156
# SWIG command
156157
COMMAND
157-
${SWIG_EXECUTABLE} -namespace ${_CSHARP_NAMESPACE} -outdir ${_CSHARP_WORKING_DIRECTORY} -DSWIG2_CSHARP -dllimport
158-
${_CSHARP_WRAPPER} -Wall -I${PROJECT_SOURCE_DIR}/swig/include -I${PROJECT_SOURCE_DIR}/swig/include/csharp
159-
-I${PROJECT_SOURCE_DIR}/gdal -c++ -csharp -o ${_CSHARP_WRAPPER}.cpp ${_CSHARP_SWIG_INTERFACE}
158+
${SWIG_EXECUTABLE} -namespace ${_CSHARP_NAMESPACE} -outdir ${_CSHARP_WORKING_DIRECTORY} -DSWIG2_CSHARP
159+
-DSWIG_CSHARP_NO_STRING_HELPER -dllimport ${_CSHARP_WRAPPER} -Wall -I${PROJECT_SOURCE_DIR}/swig/include
160+
-I${PROJECT_SOURCE_DIR}/swig/include/csharp -I${PROJECT_SOURCE_DIR}/gdal -c++ -csharp -o ${_CSHARP_WRAPPER}.cpp
161+
${_CSHARP_SWIG_INTERFACE}
160162
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
161163
DEPENDS ${GDAL_SWIG_COMMON_INTERFACE_FILES} ${PROJECT_SOURCE_DIR}/swig/include/csharp/typemaps_csharp.i
162164
${_CSHARP_SWIG_INTERFACE})
@@ -708,8 +710,12 @@ if (BUILD_TESTING)
708710
${CMAKE_CURRENT_SOURCE_DIR}/apps/GDALTestUtf8.cs
709711
DEPENDS
710712
gdal_csharp
713+
ogr_csharp
714+
osr_csharp
711715
SYSTEM_DEPENDS
712-
OSGeo.GDAL)
716+
OSGeo.GDAL
717+
OSGeo.OGR
718+
OSGeo.OSR)
713719

714720
gdal_build_csharp_sample(
715721
OUTPUT

swig/csharp/apps/GDALTestUtf8.cs

Lines changed: 192 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
2+
using System.IO;
23

34
using OSGeo.GDAL;
5+
using OSGeo.OGR;
6+
using OSGeo.OSR;
47

58
namespace testapp
69
{
@@ -12,18 +15,41 @@ class GdalTestUtf8
1215
public static void Main()
1316
{
1417
Console.OutputEncoding = System.Text.Encoding.UTF8;
15-
RunTest("ConfigOption", Gdal.GetConfigOption, Gdal.SetConfigOption);
16-
RunTest("ThreadLocalConfigOption", Gdal.GetThreadLocalConfigOption, Gdal.SetThreadLocalConfigOption);
17-
RunTest("ThreadLocalConfigOption", (k, d) => Gdal.GetPathSpecificOption("/", k, d), (k, v) => Gdal.SetPathSpecificOption("/", k, v));
18-
RunTest("Credential", (k, d) => Gdal.GetCredential("/", k, d), (k, v) => Gdal.SetCredential("/", k, v));
18+
Gdal.AllRegister();
19+
TestOptionGettingAndSetting();
20+
TestXMLNodeStrings();
21+
TestUnicodeStringProperties();
22+
TestStringsByReference();
23+
TestUnicodeDatasetLayerName();
24+
TestUnicodeFieldDefs();
25+
TestUnicodeVrtFiles();
1926
}
2027

21-
private static void RunTest(string name, Func<string, string, string> getter, Action<string, string> setter)
28+
private static void AssertEqual(string expected, string actual, string funcName)
29+
{
30+
if (expected == actual)
31+
{
32+
Console.WriteLine($" {funcName} returned the expected value: '{expected ?? "NULL"}'");
33+
}
34+
else
35+
{
36+
throw new Exception($"Expected '{expected}' but {funcName} returned '{actual ?? "NULL"}'");
37+
}
38+
}
39+
private static void TestOptionGettingAndSetting()
40+
{
41+
Console.WriteLine("Test setting, getting, and clearing Unicode string config options.");
42+
TestOptionGettingAndSetting(nameof(Gdal.GetConfigOption), Gdal.GetConfigOption, Gdal.SetConfigOption);
43+
TestOptionGettingAndSetting(nameof(Gdal.GetThreadLocalConfigOption), Gdal.GetThreadLocalConfigOption, Gdal.SetThreadLocalConfigOption);
44+
TestOptionGettingAndSetting(nameof(Gdal.GetPathSpecificOption), (k, d) => Gdal.GetPathSpecificOption("/", k, d), (k, v) => Gdal.SetPathSpecificOption("/", k, v));
45+
TestOptionGettingAndSetting(nameof(Gdal.GetCredential), (k, d) => Gdal.GetCredential("/", k, d), (k, v) => Gdal.SetCredential("/", k, v));
46+
}
47+
private static void TestOptionGettingAndSetting(string name, Func<string, string, string> getter, Action<string, string> setter)
2248
{
2349
Console.WriteLine($"Testing {name}");
2450
Console.WriteLine($" Setting and getting Unicode string");
2551
setter(ConfigKey, UnicodeString);
26-
var configValue = getter(ConfigKey, null);
52+
string configValue = getter(ConfigKey, null);
2753
AssertEqual(UnicodeString, configValue, name);
2854

2955
Console.WriteLine($" Setting null value");
@@ -35,16 +61,171 @@ private static void RunTest(string name, Func<string, string, string> getter, Ac
3561
configValue = getter(ConfigKey, UnicodeString);
3662
AssertEqual(UnicodeString, configValue, name);
3763
}
64+
private static void TestXMLNodeStrings()
65+
{
66+
Console.WriteLine("Test creating and serializing XMLNode with Unicode strings.");
67+
XMLNode node = new XMLNode(XMLNodeType.CXT_Element, "TestElement");
68+
node.SetXMLValue(".", UnicodeString);
69+
string serializedXml = node.SerializeXMLTree();
70+
string expectedXml = $"<TestElement>{UnicodeString}</TestElement>";
71+
AssertEqual(expectedXml, serializedXml?.TrimEnd('\n'), $"{nameof(XMLNode)}.{nameof(node.SerializeXMLTree)}");
72+
}
73+
private static void TestUnicodeStringProperties()
74+
{
75+
Console.WriteLine("Testing C# Property setting and getting with Unicode strings.");
76+
GCP gcp = new GCP(0, 0, 0, 0, 0, UnicodeString, "Id");
77+
AssertEqual(gcp.Info, UnicodeString, $"{nameof(GCP)}.{nameof(gcp.Info)}");
78+
gcp.Id = UnicodeString;
79+
AssertEqual(gcp.Id, UnicodeString, $"{nameof(GCP)}.{nameof(gcp.Id)}");
80+
}
81+
private static void TestStringsByReference()
82+
{
83+
Console.WriteLine("Testing 'out string' and 'ref string' parameters");
84+
string wkText;
85+
using (SpatialReference sr = new SpatialReference(null))
86+
{
87+
sr.ImportFromEPSG(4326); // WGS 84
88+
sr.ExportToWkt(out wkText, null);
89+
string name = sr.GetName();
90+
int nameStart = wkText.IndexOf(name);
91+
wkText = wkText.Substring(0, nameStart) + UnicodeString + wkText.Substring(nameStart + name.Length);
92+
}
3893

39-
private static void AssertEqual(string expected, string actual, string funcName)
94+
using (SpatialReference sr2 = new SpatialReference(null))
95+
{
96+
sr2.ImportFromWkt(ref wkText);
97+
string name = sr2.GetName();
98+
AssertEqual(UnicodeString, name, nameof(sr2.GetName));
99+
sr2.ExportToWkt(out string wkTextImportExport, null);
100+
AssertEqual(wkText, wkTextImportExport, $"{nameof(SpatialReference)}.{nameof(sr2.ExportToWkt)}");
101+
}
102+
103+
}
104+
private static void TestUnicodeDatasetLayerName()
40105
{
41-
if (expected == actual)
106+
Console.WriteLine("Test Unicode layer names");
107+
string fileName = UnicodeString + ".gdb";
108+
if (File.Exists(fileName))
109+
File.Delete(fileName);
110+
using (OSGeo.OGR.Driver shpDriver = Ogr.GetDriverByName("OpenFileGDB"))
42111
{
43-
Console.WriteLine($" {funcName} returned the expected value: '{expected ?? "NULL"}'");
112+
using (DataSource shpSrc = shpDriver.CreateDataSource(fileName, null))
113+
shpSrc.CreateLayer("图层", null, wkbGeometryType.wkbPoint, null).Dispose();
44114
}
45-
else
115+
using (DataSource shpSrc = Ogr.Open(fileName, 0))
46116
{
47-
throw new Exception($"Expected '{expected}' but {funcName} returned '{actual ?? "NULL"}'");
117+
AssertEqual(fileName, shpSrc.GetName(), $"{nameof(DataSource)}.{nameof(shpSrc.GetName)}");
118+
using (Layer shpLyr = shpSrc.GetLayerByName("图层"))
119+
AssertEqual("图层", shpLyr.GetName(), $"{nameof(Layer)}.{nameof(shpSrc.GetName)}");
120+
}
121+
}
122+
private static void TestUnicodeFieldDefs()
123+
{
124+
Console.WriteLine("Test Unicode Field Definitions");
125+
string[] nameFieldValues = new string[]
126+
{
127+
"Drâ’ Berrani",
128+
"Drâ’ Ali Ben Amar",
129+
"图层",
130+
UnicodeString,
131+
};
132+
133+
string fileName = UnicodeString + ".shp";
134+
if (File.Exists(fileName))
135+
File.Delete(fileName);
136+
137+
using (OSGeo.OGR.Driver shpDriver = Ogr.GetDriverByName("ESRI Shapefile"))
138+
{
139+
using (DataSource shpSrc = shpDriver.CreateDataSource(fileName, null))
140+
using (Layer shpLyr = shpSrc.CreateLayer(UnicodeString, null, wkbGeometryType.wkbPoint, new string[] { "ENCODING=UTF-8" }))
141+
{
142+
using (FeatureDefn layerDef = shpLyr.GetLayerDefn())
143+
{
144+
using (FieldDefn fieldDef = new FieldDefn("图层", FieldType.OFTString))
145+
if (shpLyr.CreateField(fieldDef, 1) != 0)
146+
throw new Exception("Failed to create a field definition on layer.");
147+
148+
foreach (string nameString in nameFieldValues)
149+
{
150+
using (Feature feature = new Feature(layerDef))
151+
{
152+
feature.SetField("图层", nameString);
153+
if (shpLyr.CreateFeature(feature) != 0)
154+
throw new Exception("Failed to create feature on layer.");
155+
}
156+
}
157+
}
158+
}
159+
}
160+
using (DataSource shpSrc = Ogr.Open(fileName, 0))
161+
{
162+
if (shpSrc == null)
163+
throw new Exception($"Failed to open dataset: {shpSrc}");
164+
165+
Layer shpLyr = shpSrc.GetLayerByName(UnicodeString)
166+
?? throw new Exception("Failed to get layer from shape file by name.");
167+
168+
int featureIndex = 0;
169+
while (true) using (Feature namFeat = shpLyr.GetNextFeature())
170+
{
171+
if (namFeat == null)
172+
break;
173+
174+
string fieldValue = namFeat.GetFieldAsString("图层");
175+
AssertEqual(nameFieldValues[featureIndex++], fieldValue, $"{nameof(Layer)}.{nameof(shpSrc.GetName)}");
176+
}
177+
}
178+
}
179+
private static void TestUnicodeVrtFiles()
180+
{
181+
Console.WriteLine("Testing BuildVRT with Unicode filenames.");
182+
string fileName1 = "File1 - " + UnicodeString + ".tif";
183+
if (File.Exists(fileName1))
184+
File.Delete(fileName1);
185+
186+
string fileName2 = "File2 - " + UnicodeString + ".tif";
187+
if (File.Exists(fileName2))
188+
File.Delete(fileName2);
189+
190+
string vrtFile = UnicodeString + ".vrt";
191+
if (File.Exists(vrtFile))
192+
File.Delete(vrtFile);
193+
194+
using (SpatialReference webMerc = new SpatialReference(""))
195+
{
196+
webMerc.ImportFromEPSG(3857);
197+
using (OSGeo.GDAL.Driver tifDriver = Gdal.GetDriverByName("GTiff"))
198+
{
199+
using (Dataset ds1 = tifDriver.Create(fileName1, 100, 100, 3, DataType.GDT_Byte, null))
200+
{
201+
ds1.SetSpatialRef(webMerc);
202+
ds1.SetGeoTransform(new double[] { 0, 1, 0, 0, 0, -1 });
203+
}
204+
205+
using (Dataset ds2 = tifDriver.Create(fileName2, 100, 100, 3, DataType.GDT_Byte, null))
206+
{
207+
ds2.SetSpatialRef(webMerc);
208+
ds2.SetGeoTransform(new double[] { 100, 1, 0, 0, 0, -1 });
209+
}
210+
}
211+
}
212+
213+
Gdal.BuildVRT(vrtFile, new string[] { fileName1, fileName2 }, null, null, null).Dispose();
214+
using (Dataset vrt = Gdal.Open(vrtFile, Access.GA_ReadOnly))
215+
{
216+
if (vrt.RasterXSize != 200)
217+
throw new Exception($"Expected VRT width of 200, got {vrt.RasterXSize}");
218+
219+
if (vrt.RasterYSize != 100)
220+
throw new Exception($"Expected VRT height of 100, got {vrt.RasterYSize}");
221+
222+
string[] list = vrt.GetFileList();
223+
if (list.Length != 3)
224+
throw new Exception($"Expected 3 files in VRT file list, got {list.Length}");
225+
226+
AssertEqual(vrtFile, list[0], $"{nameof(Dataset)}.{nameof(vrt.GetFileList)}()[0]");
227+
AssertEqual(fileName1, list[1], $"{nameof(Dataset)}.{nameof(vrt.GetFileList)}()[1]");
228+
AssertEqual(fileName2, list[2], $"{nameof(Dataset)}.{nameof(vrt.GetFileList)}()[2]");
48229
}
49230
}
50231
}

0 commit comments

Comments
 (0)