I need to return the following nested data from a WCF service as an out
parameter of a call:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PrinterInfo {
public InkInfo Cyan;
public InkInfo Magenta;
public InkInfo Yellow;
public InkInfo Black;
public InkInfo LightCyan;
public InkInfo LightMagenta;
public int MaintenanceTankStatus;
public int WasteInkTankStatus;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct InkInfo {
public int InkStatus;
public double Remaining;
};
Upon return from the service, all fields are empty. Normally, the obvious answer would be a data contract mismatch (something like different namespaces). But note that other calls in the same service, returning another, non-nested struct work flawlessly. Namespaces and everything similar are correct.
Things I tried:
DataContract
and DataMember
attributes (both with namespace inherited from the assembly level and names set specifically, just to be sure). They are not required actually, the inferred contract would be just fine for me (the usual caveats don't apply, this is a fixed printer SDK and both sides of the service are in the same application, under my control).KnownType(typeof(InkInfo))
. No change.[DataContract(IsReference = true)]
. Being a struct, this results in an exception.To recap: plain structs work perfectly. Only structs that contain other structs fail. Is there anything else I forgot to try?
Update: this is a fragment of the service, the other call returns the BaseInfo
structure. And it does return it, no problems there.
[ServiceContract(Namespace = "http://schemas.example.com/2017/06/printer-service", Name = "printer")]
public interface IPrinterService {
[OperationContract]
int GetBaseInfo(uint printerID, out BaseInfo out_BaseInfo);
[OperationContract]
int GetInkInfo(uint printerID, out PrinterInfo out_PrinterInfo);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class BaseInfo {
public string PrinterSerial;
public string Name;
public string ModelName;
};
Normally, unless it turns out to be unavoidable, there is no explicit data contract, just the inferred one. Data contract namespace is provided at the assembly level (and it appears is the SOAP messages all right):
[assembly: ContractNamespace("http://schemas.example.com/2017/06/printer-service", ClrNamespace = "MyApp.Printers")]
The service side happens to be written in C++/CLI but it looks almost the same:
int GetBaseInfo(unsigned int printerID, [Out] BaseInfo% out_BaseInfo) {
...
Log::Debug(out_BaseInfo);
return result;
}
int GetInkInfo(unsigned int printerID, [Out] PrinterInfo% out_PrinterInfo) {
...
Log::Debug(out_PrinterInfo);
return result;
}
Both Log::Debug()
calls clearly show that the variables are populated before leaving the functions.
Once again, the computer was right. :-) There was a difference in some structs, a difference that was easy to miss: the difference between int
and long
. In the C# world, they are 32-bit and 64-bit, respectively, but in C++/CLI (to maintain compatibility) they are both 32-bit. I had a 'long' defined somewhere and it meant a difference...