0

I'm using Nanopb to try and send protobuf messages from a VxWorks based National Instruments Compact RIO (9025). My cross compilation works great, and I can even send a complete message with data types that don't require extra encoding. What's getting me is the callbacks. My code is cross compiled and called from LabVIEW and the callback based structure of Nanopb seems to break (error out, crash, target reboots, whatever) on the target machine. If I run it without any callbacks it works great.

Here is the code in question:

bool encode_string(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{
    char *str = "Woo hoo!";

    if (!pb_encode_tag_for_field(stream, field))
        return false;

    return pb_encode_string(stream, (uint8_t*)str, strlen(str));
}

extern "C" uint16_t getPacket(uint8_t* packet)
{
    uint8_t buffer[256];
    uint16_t packetSize;

    ExampleMsg msg = {};
    pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

    msg.name.funcs.encode = &encode_string;

    msg.value = 17;
    msg.number = 18;

    pb_encode(&stream, ExampleMsg_fields, &msg);
    packetSize = stream.bytes_written;

    memcpy(packet, buffer, 256);
    return packetSize;
}

And here's the proto file:

syntax = "proto2"

message ExampleMsg {
    required int32 value = 1;
    required int32 number = 2;
    required string name = 3;
}

I have tried making the callback an extern "C" as well and it didn't change anything. I've also tried adding a nanopb options file with a max length and either didn't understand it correctly or it didn't work either.

If I remove the string from the proto message and remove the callback, it works great. It seems like the callback structure is not going to work in this LabVIEW -> C library environment. Is there another way I can encode the message without the callback structure? Or somehow embed the callback into the getPacket() function?

Updated code:

extern "C" uint16_t getPacket(uint8_t* packet)
{
    uint8_t buffer[256];
    for (unsigned int i = 0; i < 256; ++i)
        buffer[i] = 0;

    uint16_t packetSize;

    ExampleMsg msg = {};
    pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

    msg.name.funcs.encode = &encode_string;

    msg.value = 17;
    msg.number = 18;
    char name[] = "Woo hoo!";
    strncpy(msg.name, name, strlen(name));

    pb_encode(&stream, ExampleMsg_fields, &msg);
    packetSize = stream.bytes_written;

    memcpy(packet, buffer, sizeof(buffer));
    return packetSize;
}

Updated proto file:

syntax = "proto2"
import "nanopb.proto";   

message ExampleMsg {
    required int32 value = 1;
    required int32 number = 2;
    required string name = 3 [(nanopb).max_size = 40];
}
Chuck Claunch
  • 1,624
  • 1
  • 17
  • 29

2 Answers2

5

You can avoid callbacks by giving a maximum size for the string field using the option (nanopb).max_size = 123 in the .proto file. Then nanopb can generate a simple char array in the structure (relevant part of documentation).

Regarding why callbacks don't work: just a guess, but try adding extern "C" also to the callback function. I assume you are using C++ there, so perhaps on that platform the C and C++ calling conventions differ and that causes the crash.

Does the VxWorks serial console give any more information about the crash? I don't remember if it does that for functions called from LabView, so running some test code directly from the VxWorks shell may be worth a try also.

jpa
  • 10,351
  • 1
  • 28
  • 45
  • I thought I tried using a max_size, but may not have done it properly and will try it again. I definitely tried adding the extern "C" to the callback and it didn't help. I haven't gotten as far as looking at the serial console output, but will try that as well. Thanks! – Chuck Claunch Jul 10 '15 at 16:21
  • So I tried adding the option to the field in my proto file and the generator spits out: Option "(nanopb)" unknown. Thoughts? – Chuck Claunch Jul 10 '15 at 16:32
  • @ChuckClaunch You have to add `import "nanopb.proto";` to the top: http://koti.kapsi.fi/jpa/nanopb/docs/reference.html#defining-the-options-in-the-proto-file – jpa Jul 10 '15 at 16:33
  • I've updated the post with new code. Now I can't seem to get my code on the full blown protobuf side to parse the message. As before, if I remove the string from the message it works great. Add the string with the max_size option and it fails to parse (the parseFromString() method on the other end of a UDP socket returns false). Is there something I need to do to the proto file on the reader side? – Chuck Claunch Jul 10 '15 at 17:45
  • Ok I have it parsing (seems I had some trash in the buffer from not initializing it). The message parses on the client computer and the integers come out correctly but the string is garbage. – Chuck Claunch Jul 10 '15 at 18:02
  • Ok it looks like it's simply a bad print on the client side. It is working properly! Not sure why printf vs cout on a C++ string would show gibberish vs correct, but whatever. – Chuck Claunch Jul 10 '15 at 18:27
  • Arg, nevermind I'm dumb. Was treating the C++ protobuf string as if it was a C style string. 100% good to go now. Thanks. – Chuck Claunch Jul 10 '15 at 18:36
  • Actually one last question, can I obscure the nanopb options somehow so I could end up not having to maintain two different .proto files (one with the nanopb options and the other without)? – Chuck Claunch Jul 10 '15 at 18:37
  • @ChuckClaunch Yes, you can define the options in a separate .options file (http://koti.kapsi.fi/jpa/nanopb/docs/reference.html#defining-the-options-in-a-options-file). I see you found answers to your further questions yourself, but generally on StackOverflow it would be better to ask a new question than continue the discussion in comments. – jpa Jul 11 '15 at 04:46
  • Understood. I can add the question separately if you believe it will help others. – Chuck Claunch Jul 13 '15 at 13:42
0

Perhaps the first hurdle is how the code handles strings.

LabVIEW's native string representation is not null-terminated like C, but you can configure LabVIEW to use a different representation or update your code to handle LabVIEW's native format.

LabVIEW stores a string in a special format in which the first four bytes of the array of characters form a 32-bit signed integer that stores how many characters appear in the string. Thus, a string with n characters requires n + 4 bytes to store in memory.


LabVIEW Help: Using Arrays and Strings in the Call Library Function Node
http://zone.ni.com/reference/en-XX/help/371361L-01/lvexcodeconcepts/array_and_string_options/

Joe Friedrichsen
  • 1,976
  • 14
  • 14
  • Yes! Definitely understood on LabVIEW with strings. I came across that same issue while researching this. However in my specific case, LabVIEW never touches a string (I'm passing in/out a uint8_t array), so I don't think that's my issue here. The idea with using nanopb is so it takes care of all the encoding and all LabVIEW has to deal with is the raw binary. – Chuck Claunch Jul 10 '15 at 16:24