Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

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:
    426
    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:
    426
    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,624
    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:
    426
    @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,624
    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:
    426
    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:
    426
    @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:
    426
    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,624
    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,624
    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:
    426
    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,624
    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:
    426
    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:
    426
    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:
    426
    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,478
    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,624
    Show the code how are you passing callback to this function.
     
  23. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    426
    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:
    426
    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,478
    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,624
    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:
    426
    Last edited: Jan 27, 2016
    GarthSmith likes this.
  28. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    426
    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,478
    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:
    426
    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,478
    AVOlight likes this.
  32. AVOlight

    AVOlight

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

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    426
    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:
    426
    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,478
    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:
    426
    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,478
    Could you clarify your question? What pointers are you talking about?
     
  38. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    426
    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,478
    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:
    426
  41. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    426
    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,478
    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:
    426
    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,478
    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:
    426
    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,478
    You're good. Nothing needs to be cleaned up manually.
     
    AVOlight likes this.
  47. AVOlight

    AVOlight

    Joined:
    Apr 15, 2014
    Posts:
    426
  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:
    426
    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