10

I have the following C++ method compiled using Visual Studio 2017:

extern "C" __declspec( dllexport )
Info* __stdcall GetInfo(InfoProvider* infoProvider)
{
   static_assert(std::is_pod<Info>::value, "Must be Plain Old Data in order to be safely copied between DLL boundaries");

   Info info = new Info();
   Info->data1 = infoProvider->data1;
   Info->data2 = infoProvider->data2;

   return info;
}

In Java code, it is mapped by Java Native Runtime using interface method with following signature:

Info GetInfo(Pointer infoProvider);

final class Info extends Struct {

    public final Signed32 data1;

    public final Signed32 data2;

    public R2VInfo(final Runtime runtime) {
        super(runtime);

        data1 = new Signed32();
        data2 = new Signed32();
    }
}

It works.

The above C++ method causes memory leak, so I would like to change it to return result by value:

extern "C" __declspec( dllexport )
Info __stdcall GetInfo(InfoProvider* infoProvider)
{
   static_assert(std::is_pod<Info>::value, "Must be Plain Old Data in order to be safely copied between DLL boundaries");

   Info info{};
   Info.data1 = infoProvider->data1;
   Info.data2 = infoProvider->data2;

   return info;
}

I use the same Java JNR mapping:

Info GetInfo(Pointer infoProvider);

But it does not work - Access Violation. Native method is invoked, but with some dandling pointer value.

How to return by value in JNR?

sgnsajgon
  • 664
  • 2
  • 13
  • 56

1 Answers1

5

JNI build around old pure K&R C to be compatible with all available compilers. Returning a structure from a function was introduced in C89 and was fully implemented significantly later with C++ standard together. Today it is still possible to find such old C compiler in many java-friendly environments like small devices or sim-cards. So I don't think that JNI would be upgraded to C89 or even C99.

For your case, I would recommend to write an additional C-code that handles calling of a library function. The code could be implemented in two ways:

  1. For the Info* __stdcall GetInfo(InfoProvider* infoProvider) you should write free-function like:
extern "C" __declspec( dllexport )
void __stdcall FreeInfo(Info* info)
{
   static_assert(std::is_pod<Info>::value, "Must be Plain Old Data in order to be safely copied between DLL boundaries");

   delete info;
}
  1. For the Info __stdcall GetInfo(InfoProvider* infoProvider) you should write a wrapper:
extern "C" __declspec( dllexport )
void __stdcall GetInfo(InfoProvider* infoProvider, Info* info)
{
   static_assert(std::is_pod<Info>::value, "Must be Plain Old Data in order to be safely copied between DLL boundaries");

   Info infoProvider = GetInfo(infoProvider);
   info->data1 = infoProvider.data1;
   info->data2 = infoProvider.data2;
}
Tania Chistyakova
  • 3,928
  • 6
  • 13
  • Yes, I use second approach. I create new `Info` object on Java side, next I pass it to the native function using `Info` parameter annotated as @Pinned @Out. Then, `Info` object is properly GC collected. – sgnsajgon May 22 '19 at 16:43
  • Thank you for information relative to C89. JNR is additional layer over JNI, and as such I wonder why it does not support returning struct by value. Probably it is hard to implement it reliably or JNR stays compliant with JNI. – sgnsajgon May 22 '19 at 17:01