Search Unity

Windows Store Native IAP Plugin (until IL2CPP is supported in Unity IAP)

Discussion in 'Windows' started by AVOlight, Jan 26, 2016.

  1. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    All i know about c++ is it looks confusing (and i don't want to cause anything crazy to happen)
    i know a bit about c# because of working with unity (worst thing i could do there is an infinite loop)

    been looking at the c++ examples for Windows Store IAP
    and i'm trying to bridge the functions i need to do a simple consumable purchase



    Code (CSharp):
    1.  
    2. extern "C" {
    3.    bool ConfigStore() {
    4.      ConfigureSimulatorAsync("in-app-purchase-consumables.xml");
    5.    }
    6.    bool Buy() {
    7.      create_task(CurrentAppSimulator::RequestProductPurchaseAsync("product1"))
    8.        .then([](task<PurchaseResults^> currentTask) {
    9.        try
    10.        {
    11.          PurchaseResults^ results = currentTask.get();
    12.          switch (results->Status)
    13.          {
    14.          case ProductPurchaseStatus::Succeeded:
    15.            return Fulfill("product1", results->TransactionId);
    16.            break;
    17.          default:
    18.            return false;
    19.            break;
    20.          }
    21.        }
    22.        catch (Platform::Exception^ exception)
    23.        {
    24.          return false;
    25.        }
    26.      });
    27.    }
    28.    
    29. }
    30. bool Fulfill(Platform::String^ productId, Platform::Guid transactionId)
    31. {
    32.    create_task(CurrentAppSimulator::ReportConsumableFulfillmentAsync(productId, transactionId))
    33.      .then([](task<FulfillmentResult> currentTask)
    34.    {
    35.      try
    36.      {
    37.        FulfillmentResult result = currentTask.get();
    38.        switch (result)
    39.        {
    40.        case FulfillmentResult::Succeeded:
    41.          return true;
    42.          break;
    43.        default:
    44.          return false;
    45.          break;
    46.        }
    47.      }
    48.      catch (Platform::Exception^ exception)
    49.      {
    50.        return false;
    51.      }
    52.    });
    53. }
    54.  
     
    Last edited: Jan 26, 2016
  2. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    If you're trying to make a native plugin... I thought that was possible in VS using C# for some reason... :D

    Anyway, I do know a little C++, (Enough to make a rudimentary game engine, in fact) but I'm no John Carmack here. ;) I guess I could have a look at the API and lend some help when I get time. (I'm still working on my submission! :D)
     
  3. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    yes, but Windows Store IL2CPP managed plugins aren't supported yet, unless that's changed

    Thanks :), yea i'm still working on mine too
    hoping i can possibly get a unity pro license to offset my costs
     
  4. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    If you are more comfortable with C#, then you may want to write a working C# plugin, then rewrite it in C++/CX and only in the last step wrap that with C API for you application. The first two steps can be done in separate app using .NET scripting and conversion from C# to C++/CX is quite straight forward, as you only have to convert to a more cumbersome syntax.
     
    AVOlight likes this.
  5. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    @Aurimas Cernius Definitely more comfortable with c# (can't believe i've been using unity for almost 2 years now)

    Thank you that makes a lot of sense :)
     
  6. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    CX? Never heard of it... :D
     
  7. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    That's C++ with extensions for WinRT, where you easily interop between C++ and C#.

    Edit: it's called C++/CX.
     
  8. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    Sounds like C++ and C# had a baby... for Microsoft. :D Sounds interesting. :)
     
  9. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    ok how do i return back the information from a concurrency task in a static function

    Code (CSharp):
    1. DLLExport bool __stdcall Buy() {
    2.         create_task(Windows::ApplicationModel::Store::CurrentAppSimulator::RequestProductPurchaseAsync("product1"))
    3.             .then([](task<PurchaseResults^> currentTask) {
    4.             try {
    5.                 PurchaseResults^ results = currentTask.get();
    6.                 switch (results->Status)
    7.                 {
    8.                 case ProductPurchaseStatus::Succeeded:
    9.                     return Fulfill("product1", results->TransactionId);
    10.                     break;
    11.                 default:
    12.                     return false;
    13.                     break;
    14.                 }
    15.             }
    16.             catch (Platform::Exception^ exception) { return false; }
    17.         });
    18.         //return inner result ?;
    19.     }
     
  10. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    If you wanted, you should be able to wrap your C# IAP code in a DLL, then use the DLL from Unity.
    http://docs.unity3d.com/Manual/windowsstore-plugins-il2cpp.html

    As for how to return back information, I usually announce IAP results through a callback or event. In other words, I'd make public void Buy(Action<bool> resultCallback) instead of public bool Buy().

    This callback or event doesn't get run right away though, I usually queue it up and run it on the Unity thread, since I normally have things in game that need to act on the results of the IAP.
    http://docs.unity3d.com/ScriptReference/WSA.Application.InvokeOnAppThread.html (Haven't used this, found out about it after I made my own implementation.)
     
  11. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    @Garth Smith Thank you for the info :)

    haven't really seen any callbacks in the scripts i've learn t from so..
    the concept seems very useful for ui tho, i'm used to every thing going through my update methods

    ? so are you saying there's a way for me to completely avoid c++ and get Unity IAP to work with windows store IL2CPP ?
     
  12. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    I haven't done it for IL2CPP, but the documentation seems to say you can P/Invoke into a native DLL that you compile outside of Unity.

    The documentation then shows an example with a DLL compiled with C. Not sure what you would need to P/Invoke into a C# plugin. I'm used to using managed plugins, so I'm not sure what would go in place of [MarshalAs(UnmanagedType.LPWSTR)]...
    http://docs.unity3d.com/Manual/windowsstore-plugins-il2cpp.html
     
  13. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    i'm so lost...

    Code (CSharp):
    1. UnityEngine.WSA.Application.InvokeOnUIThread(ConfigStore, false);
    breaking on Concurrency, ppltasks.h
    Code (CSharp):
    1. if (_M_exceptionObserved == 0)
    2.             {
    3.                 // If you are trapped here, it means an exception thrown in task chain didn't get handled.
    4.                 // Please add task-based continuation to handle all exceptions coming from tasks.
    5.                 // this->_M_stackTrace keeps the creation callstack of the task generates this exception.
    6.                 _REPORT_PPLTASK_UNOBSERVED_EXCEPTION();
    7.             }
    think i'm just going to spend this time working on my game mechanics, or maybe add some sound, havent done that yet
     
  14. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    then() continuator returns you a new task object. Call wait() on it, check status and if success, call get(), that will give you what passed in function/lambda returned.
     
    AVOlight likes this.
  15. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    Using C# dll from an IL2CPP project is a bad idea. You'll have two garbage collectors running at once.
     
    AVOlight likes this.
  16. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Thank you, yes i've set up a callback for buy, but i'm not invoking these methods right
    or
    theres really is a problem with my ConfigStore method

    Code (CSharp):
    1. Concurrency::task<void> ConfigureSimulatorAsync(Platform::String ^ filename) {
    2.         return Concurrency::create_task(Windows::ApplicationModel::Package::Current->InstalledLocation->GetFileAsync("data\\" + filename))
    3.             .then([](Windows::Storage::StorageFile^ proxyFile)
    4.         {
    5.             return Windows::ApplicationModel::Store::CurrentAppSimulator::ReloadSimulatorAsync(proxyFile);
    6.         });
    7.     }
    8.     DLLExport void __stdcall ConfigStore() {
    9.         ConfigureSimulatorAsync("in-app-purchase-consumables.xml");
    10.     }
     
  17. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    What are the problem with this?
    All I see is that launch an async task, but don't do anything about it. ConfigureSimulatorAsync() will return before the task is completed.
     
    AVOlight likes this.
  18. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    i'm just configuring the simulator so that i can then attempt a simulated purchase
    the buy button isn't set to run after ConfigureSimulatorAsync(); is called

    havent got through getting the ConfigureSimulatorAsync() to complete whatever it does
    without breaking on a exception
     
  19. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    i see right so turn this Concurrency::task<void> to void , and don't return anything

    Thank You!
     
    Last edited: Jan 27, 2016
  20. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    ok now i need to know how to stop my static reference to the callback from being cleaned up before the purchase is done

    // Marshaling cleanup of parameter '___cb' native representation

    Code (CSharp):
    1. typedef void(__stdcall *CallMeLater)(int);
    2. static CallMeLater cb;
    3.  
    4. extern "C"  {
    5. DLLExport void Buy(CallMeLater callback) {
    6.         cb = callback;
    7.         if (!cb) { return; }
    8.         create_task(Windows::ApplicationModel::Store::CurrentAppSimulator::RequestProductPurchaseAsync("product1"))
    9.             .then([](task<PurchaseResults^> currentTask) {
    10.             try {
    11.                 PurchaseResults^ results = currentTask.get();
    12.                 switch (results->Status)
    13.                 {
    14.                 case ProductPurchaseStatus::Succeeded:
    15.                     cb(1); //return Fulfill("product1", results->TransactionId);
    16.                     break;
    17.                 default:
    18.                     cb(0);
    19.                     break;
    20.                 }
    21.             }
    22.             catch (Platform::Exception^ exception) { cb(0); }
    23.         });
    24.     }
    25. }
     
  21. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    That is only a comment. There should be nothing that it does, since there's no cleanup needed for function pointers. Are you crashing when you call the function?
     
  22. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    Show the code how are you passing callback to this function.
     
  23. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Code (CSharp):
    1. public delegate void CallMeLater(int result);
    2.   [DllImport("SimpleStore.dll")] static extern void ConfigStore();
    3.   [DllImport("SimpleStore.dll")] static extern void Buy(CallMeLater cb);
    4.   [DllImport("SimpleStore.dll")] static extern void Test(CallMeLater cb);
    5.  
    6.   void OnGUI() {
    7.     if(GUI.Button(new Rect(100, 300, 200, 80), "Test")) {
    8.       Button.Test(Button.Tester);
    9.     }
    10.     if(GUI.Button(new Rect(100, 100, 200, 80), "ConfigStore")) {
    11.       UnityEngine.WSA.Application.InvokeOnUIThread(Button.ConfigStore, false);
    12.       Debug.LogError("StoreConfigured");
    13.     }
    14.     if(GUI.Button(new Rect(300, 100, 200, 80), "Buy")) {
    15.       UnityEngine.WSA.Application.InvokeOnUIThread(() => Button.Buy(ProcessPurchase), false);
    16.       Debug.LogError("Buy?");
    17.     }
    18.   }
    19.   [AOT.MonoPInvokeCallback(typeof(CallMeLater))]
    20.   public static void ProcessPurchase(int result) {
    21.     if(result == 1) { Debug.LogError("purchase success"); }
    22.     else { Debug.LogError("purchase fail"); }
    23.   }
    24.   [AOT.MonoPInvokeCallback(typeof(CallMeLater))]
    25.   public static void Tester(int result) {
    26.     Debug.LogError("Callback Worked: " + result);
    27.   }
    breaking on

    Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
     
  24. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Code (CSharp):
    1. // System.Void Button::Buy(Button/CallMeLater)
    2. extern "C"  void Button_Buy_m4233563334 (Object_t * __this /* static, unused */, CallMeLater_t2468943012 * ___cb, const MethodInfo* method)
    3. {
    4.     typedef void (DEFAULT_CALL *PInvokeFunc) (methodPointerType);
    5.     static PInvokeFunc _il2cpp_pinvoke_func;
    6.     if (!_il2cpp_pinvoke_func)
    7.     {
    8.         int parameterSize = sizeof(void*);
    9.         _il2cpp_pinvoke_func = il2cpp_codegen_resolve_pinvoke<PInvokeFunc>("SimpleStore.dll", "Buy", IL2CPP_CALL_DEFAULT, CHARSET_UNICODE, parameterSize, false);
    10.  
    11.         if (_il2cpp_pinvoke_func == NULL)
    12.         {
    13.             IL2CPP_RAISE_MANAGED_EXCEPTION(il2cpp_codegen_get_not_supported_exception("Unable to find method for p/invoke: 'Buy'"));
    14.         }
    15.     }
    16.  
    17.     // Marshaling of parameter '___cb' to native representation
    18.     methodPointerType ____cb_marshaled = { 0 };
    19.     ____cb_marshaled = il2cpp_codegen_marshal_delegate(reinterpret_cast<Il2CppCodeGenMulticastDelegate*>(___cb));
    20.  
    21.     // Native function invocation
    22.     _il2cpp_pinvoke_func(____cb_marshaled);
    23.  
    24.     // Marshaling cleanup of parameter '___cb' native representation
    25.  
    26. }
    yellow arrow for break points to the end this function }
     
  25. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    You forgot __stdcall after "void". Should be like this:

    Code (csharp):
    1. DLLExport void __stdcall Buy(CallMeLater callback) {
     
    AVOlight likes this.
  26. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,735
    Try playing with call conventions for your delegate/callback. I.e. mark delegate on C# with UnmanagedFunctionPointer and try various call convention. Now you have stdcall on C++ side, you use the same in C#.
     
    AVOlight likes this.
  27. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Last edited: Jan 27, 2016
    GarthSmith likes this.
  28. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Code (CSharp):
    1.  
    2. FulfillmentResult result
    3. PurchaseResults^ results
    whats with ^ on PurchaseResults^ and not on FulfillmentResult ?

    and is it safe for me to returns these to unity with a callback ?
     
  29. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    The hat ('^') denotes a WinRT pointer type that is automatically reference counted. It is used to reference WinRT classes and interfaces. PurchaseResults is such a class. You can pass it to C# as "IntPtr", but there's not much you can do with it from C#, apart from holding it. You'll also need to increment its reference count on C++ side if you're going to do it.

    FulfillmentResult is an enum, so it doesn't have to be a pointer - it can be just a value (just like in C#). It can be passed to C# safely as an int or an enum defined on C# side.
     
    AVOlight likes this.
  30. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Thank you @Tautvydas Zilys I've learned so much from you :)

    i'll just cast the ints to enums for status on the c# side,
    Platform::Guid =?= System.Guid for transaction id and
    Platform::String^ =?= System.String for product id
     
  31. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    AVOlight likes this.
  32. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    will try all of this after sleep, cheers
     
  33. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    do i have to clean up the stuff i create in these c++ methods?

    right now i'm trying to transfer the transaction id to and from the c++ side using a unsigned char* == byte[]
    wondering why i can't do this:
    Code (CSharp):
    1. unsigned char last = { b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15] };

    going from Platform::String to unsigned char* , how?
    going from unsigned char* to Platform::String ?
     
    Last edited: Jan 28, 2016
  34. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    ok ended up turning the transactionId into a unsigned char* and using Marshal.Copy(System.IntPtr, byte[] dest, 0, 16);
    don't have to send the transactionId back to c# side but i would like to record fulfilled items with transactionIds
    currently i'm getting a nullptr break
    going to try this GCHandle thing to get a pointer on the c# side instead of the c++ side, should i put my Free() call in OnDisable() ?

    and for now i'll just hard code the product string + index


    ----
    really awesome that you can access the same byte[] on both sides with out copying it over and over :)
     
    Last edited: Jan 28, 2016
  35. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Don't convert it to char. Use "Data()" method on the Platform::String, which returns "wchar_t*". Pass that to C# and you should automatically get a C# string.
     
    AVOlight likes this.
  36. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Thank you, next time i will definitely use that to get a string back from the c++ side,

    do you know why these pointers aren't used for stuff like setting vertex mesh data and setting matrix[] on shader material properties?
    i guess in theory the IL2CPP convector could just handle all that automatically ?
     
  37. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Could you clarify your question? What pointers are you talking about?
     
  38. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    using GCHandle.Alloc and then AddrOfPinnedObject to get an IntPtr

    how do i call Data on Platform::String^ ?
     
  39. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    That is pretty similar to how those APIs in Unity are implemented.

    Code (csharp):
    1. Platform::String^ someStr = "blah";
    2. callback(someStr->Data());
     
    AVOlight likes this.
  40. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
  41. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    do i need to do anything after sending the callback the string?

    Code (CSharp):
    1.  
    2. typedef void(__stdcall *VoidCallbackString)(const wchar_t*);
    3.  
    4. VoidCallbackString StringResult;
    5.  
    6. StringResult(e->ToString()->Data());
    7.  
    8. [AOT.MonoPInvokeCallback(typeof(VoidCallbackString))]
    9.     static void StringResult([MarshalAs(UnmanagedType.LPWStr)] string arg) {
    10.  
    11. }
     
  42. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    I suppose it's a receipt you get? I guess you could save it. I'm not exactly sure what it is used for.
     
  43. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    sorry, its just for sending error messages on so i can log them
    (trying to integrate advertising for windows store)
    i'm just very unfamiliar with c++ and i don't want to cause any memory leaks
     
  44. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Ah. Can you post the whole function? I can't really tell what's going on from the snippets you posted.
     
  45. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    Code (CSharp):
    1.  
    2. typedef void(__stdcall *VoidCallbackString)(const wchar_t*);
    3. VoidCallbackString StringResult;
    4.  
    5. void Ads::Item::OnErrorOccurred(Platform::Object ^ sender, Microsoft::Advertising::WinRT::UI::AdErrorEventArgs ^ e)
    6.     {
    7.         StringResult(e->ToString()->Data()); // does this get cleaned up automatically after this method ends
    8.     }
    9.  
    10. string logLater;
    11. [AOT.MonoPInvokeCallback(typeof(VoidCallbackString))]
    12. static void StringResult([MarshalAs(UnmanagedType.LPWStr)] string arg) {
    13.    logLater = arg;
    14. }
    15.  
    i'm wondering if the string needs to be cleaned up after using this connection between c++ and c#
     
  46. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    You're good. Nothing needs to be cleaned up manually.
     
    AVOlight likes this.
  47. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
  48. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    Is that in-line C++ in a C# script? I don't think that's it, but...
     
  49. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    427
    hey @FuzzyQuills, c++ is so weird to look at after starting with c#; definitely wouldn't want it in-line with my c# code :confused:

    yea i just pasted the related code snippets together here
     
  50. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    Ah I see, lol. XD
    To me, C# is glorified Java, C++ is low-level hardcore Java. ;)
    That did look a bit odd though seeing them together... XD

    @Tautvydas-Zilys So you're telling me (Or him, I just happened to read it) that data returned through such a callback doesn't leak? Interesting. Anyway, apologise if it's getting off-topic, haven't really touched C++ in a while...
     
    Last edited: Aug 23, 2016