On Linux / macOS (Mono 6.8, libgdiplus 6.1), ncode-cli generate fails with:
GetImage failed (380): 380 : Error occurred while making image.
even though:
GetTickets(REST) succeeds and returns tickets for the givenappSecretKeyGenerateNcode(REST) succeeds and returns aNcodePagewhosedatafield holds the expected ~118 KB hex-encoded dot streamlibgdiplusitself passes a smoke test (Format8bppIndexedBitmap +LockBits+Saveround-trips cleanly)
The C# CNcodeSDK.GetImage(NcodePage, int dpi, string filename, bool isBold)
public wrapper delegates to a private overload whose IL ends with:
IL_0305: ldfld string NeoLABNcodeSDK.CNcodeSDK::workingFolder
IL_030a: ldstr "\\" ← HARDCODED BACKSLASH
IL_030f: ldarg.s filename
IL_0311: call string System.String::Concat(string, string, string)
IL_0316: callvirt instance void System.Drawing.Image::Save(string)
IL_031b: leave.s IL_0332
IL_031d: catch [mscorlib]System.Exception
IL_0324: stfld string NeoLABNcodeSDK::lastErrorMsg
IL_0329: ldc.i4 0x17c ← returns 380
Key facts:
- The separator is a literal backslash, not
Path.Combineand notPath.DirectorySeparatorChar. - On Linux the backslash is not a directory separator, so
Image::Save("/tmp/ncode-cli-cache" + "\" + "out.png")tries to write a file whose name literally contains a backslash, under whatever the first component resolves to. - Whether the write succeeds or not depends on what the caller passes
as
filename:"/tmp/out.png"— concat yields/tmp/ncode-cli-cache\/tmp/out.png, which Linux parses as a request to place\/tmp/out.pngunderncode-cli-cache. That inner slash means "create intermediate directories", which System.Drawing.Image::Save does not, so the call throws and the SDK swallows it into error 380."out.png"— concat yields/tmp/ncode-cli-cache\out.png, which Linux accepts as a single filename (the\is just another char). Image::Save succeeds, but the file lands at literally/tmp/ncode-cli-cache\out.png(backslash in the name), not/tmp/ncode-cli-cache/out.png.
In short: the SDK was compiled against Windows path semantics. The
catch (Exception) block erases every diagnostic and returns a single
opaque code (380), which makes the failure look like a rendering
problem when it is actually a path construction bug.
ncode-cli generate … --out-png /tmp/t.png on Linux, before the
workaround, produced no file at /tmp/t.png but did produce
/tmp/ncode-cli-cache\test_out.png (literal backslash in basename)
PNG image data, 4962 x 7014, 1-bit colormap, non-interlaced
103,272 bytes
when the CLI was invoked with --out-png test_out.png. The PNG
content is byte-valid and contains the dot pattern for page
3.27.524.0 at 8.27 × 11.69 in @ 600 dpi, matching the reference
sample at cpp/sampleApp(Ncode_cpp)/3_27_524_0_8.270000_11.700000.png
in the upstream NeoSmartpen/Ncode-SDK2.0
repository in both dimensions and encoding class.
- Pass only the basename (
Path.GetFileName(--out-png)) tosdk.GetImage, so the concatenation stays within theworkingFolderdirectory regardless of platform. - After
GetImagereturns zero, locate the produced file:Path.Combine(workingFolder, basename)— the Windows / Mono pathworkingFolder + "\\" + basename— the Linux literal-backslash path
File.Movethe located file to the caller-supplied--out-png.
Net effect:
| Platform | Before | After |
|---|---|---|
| Windows | works | works |
| Linux (Mono) | returns 380, no file | returns 0, file at --out-png |
| macOS (Mono) | expected same as Linux | expected same as Linux |
MONO_IOMAP=all was evaluated and rejected: Mono's IOMAP only
translates DOS-style drive letters, not mid-string backslashes emitted
by managed code, so it does not help here.
- Windows behaviour is unchanged. No regression risk.
- The workaround relies on
GetImagewriting atomically once. If a future SDK version starts producing side files (e.g. a sidecar JSON) we will need to extend the "locate produced file" loop. MONO_IOMAP=allis no longer needed and should not be set — it remains a no-op here and has unrelated side effects on other Mono apps in the process tree.- Reported to NeoLAB is unnecessary for the PoC since the workaround
is one-line and runs on every platform; however if they ship a
non-Windows SDK in the future they should use
Path.DirectorySeparatorCharinstead of the literal"\\".