Building .NET applications for the app store

Building .NET applications for the App Store is not currently supported. If you are like me, building user interfaces with .NET is quick and easy and so, it would be nice to submit .NET applications. I did manage to submit an application that was built in .NET and also supports the Atom SDK, still remains to be seen if it will be approved but I want to post about how I managed to use the Atom SDK in C#. We also want to provide some level of security, so this topic will be addressed. The SDK provides a really nice .lib file with a C interface that is really simple to use. Just a few methods. I have not looked at the C++ interface since the C version is so easy. The first thing to do is to build a small .dll wrapper for the .lib file. You don't need to put almost any code, just re-export the C methods into DLL methods. Just go in Visual Studio and create a empty .DLL project. I called mine "AdpWrapper". You just add the following exports to the definition (.def ) file: LIBRARY "AdpWrapper" EXPORTS ADP_Get_API_VERSION ADP_Get_API_LEVEL ADP_Get_ADP_DEBUG_APPLICATIONID ADP_Get_ADP_DEBUG_COMPONENTID ADP_Get_ADP_EXPIRED_APPLICATIONID ADP_Get_ADP_EXPIRED_COMPONENTID ADP_Initialize ADP_Close ADPW_IsAuthorized ADPW_IsAppAuthorized ADP_ReportCrash ADP_ApplicationBeginEvent ADP_ApplicationEndEvent Ok, now in the DLL C file, I did add a little bit of code, but it's as little as you can get. It's just a question of re-exporting the C functions as DLL functions. There are many ways of doing this. Of course, don't forget to include the adpcore.h and adpcore.lib in your project. BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } const wchar_t* __stdcall ADP_Get_API_VERSION() { return ADP_API_VERSION; } const unsigned long __stdcall ADP_Get_API_LEVEL() { return ADP_API_LEVEL; } char* __stdcall ADP_Get_ADP_DEBUG_APPLICATIONID() { return (char*)&ADP_DEBUG_APPLICATIONID; } char* __stdcall ADP_Get_ADP_DEBUG_COMPONENTID() { return (char*)&ADP_DEBUG_COMPONENTID; } char* __stdcall ADP_Get_ADP_EXPIRED_APPLICATIONID() { return (char*)&ADP_EXPIRED_APPLICATIONID; } char* __stdcall ADP_Get_ADP_EXPIRED_COMPONENTID() { return (char*)&ADP_EXPIRED_COMPONENTID; } const int __stdcall ADPW_IsAuthorized(char* id) { ADP_APPLICATIONID id2; memcpy(&id2, id, 16); return ADP_IsAuthorized(id2); } const int __stdcall ADPW_IsAppAuthorized(char* id) { ADP_COMPONENTID id2; memcpy(&id2, id, 16); return ADP_IsAppAuthorized(id2); } Ok, so you got everything ready and compiled your first AdpWrapper.dll. Now it's time to head over to C# and write a little code to use this new DLL. You will need to copy the dll into the same folder as your C# binary so the .NET executable can find it. On the C# class you need to use the "System.Runtime.InteropServices" library to import all of the methods. If you add a little code, you can turn all the GUID's into native .NET GUID types. Makes it really easy and you don't need to deal with byte arrays anymore. Here is sample C# code, note the "PerformCheck" at the bottom, we will come back to that. public class AdpWrapperClass { public enum ReturnCode { ADP_FAILURE = -1, ADP_SUCCESS, ADP_NOT_INITIALIZED, ADP_NOT_AVAILABLE, ADP_INCOMPATIBLE_VERSION, ADP_ERR_DATA_TOO_BIG, ADP_AUTHORIZED, ADP_NOT_AUTHORIZED, ADP_AUTHORIZATION_EXPIRED, ADP_NO_APP_BEGIN_EVENT } [DllImport("AdpWrapper.dll")] private static extern ReturnCode ADP_Initialize(); [DllImport("AdpWrapper.dll")] private static extern ReturnCode ADP_Close(); [DllImport("AdpWrapper.dll")] private static extern ReturnCode ADPW_IsAuthorized(byte[] id); [DllImport("AdpWrapper.dll")] private static extern ReturnCode ADPW_IsAppAuthorized(byte[] id); // ADP_ReportCrash [DllImport("AdpWrapper.dll")] private static extern ReturnCode ADP_ApplicationBeginEvent(); [DllImport("AdpWrapper.dll")] private static extern ReturnCode ADP_ApplicationEndEvent(); [DllImport("AdpWrapper.dll")] private static extern IntPtr ADP_Get_API_VERSION(); [DllImport("AdpWrapper.dll")] private static extern ulong ADP_Get_API_LEVEL(); [DllImport("AdpWrapper.dll")] private static extern IntPtr ADP_Get_ADP_DEBUG_APPLICATIONID(); [DllImport("AdpWrapper.dll")] private static extern IntPtr ADP_Get_ADP_DEBUG_COMPONENTID(); [DllImport("AdpWrapper.dll")] private static extern IntPtr ADP_Get_ADP_EXPIRED_APPLICATIONID(); [DllImport("AdpWrapper.dll")] private static extern IntPtr ADP_Get_ADP_EXPIRED_COMPONENTID(); public static ReturnCode Initialize() { if (!PerformCheck()) return ReturnCode.ADP_FAILURE; else return ADP_Initialize(); } public static ReturnCode Close() { return ADP_Close(); } public static ReturnCode IsAuthorized(Guid applicationid) { return ADPW_IsAuthorized(applicationid.ToByteArray()); } public static ReturnCode IsAppAuthorized(Guid componentid) { return ADPW_IsAppAuthorized(componentid.ToByteArray()); } public static ReturnCode ApplicationBeginEvent() { return ADP_ApplicationBeginEvent(); } public static ReturnCode ApplicationEndEvent() { return ADP_ApplicationEndEvent(); } public static string ApiVersion { get { return Marshal.PtrToStringUni(ADP_Get_API_VERSION()); } } public static ulong Apilevel { get { return ADP_Get_API_LEVEL(); } } public static Guid DebugApplicationId { get { byte[] id = new byte[16]; Marshal.Copy(ADP_Get_ADP_DEBUG_APPLICATIONID(), id, 0, 16); return new Guid(id); } } public static Guid DebugComponentId { get { byte[] id = new byte[16]; Marshal.Copy(ADP_Get_ADP_DEBUG_COMPONENTID(), id, 0, 16); return new Guid(id); } } public static Guid ExpiredApplicationId { get { byte[] id = new byte[16]; Marshal.Copy(ADP_Get_ADP_EXPIRED_APPLICATIONID(), id, 0, 16); return new Guid(id); } } public static Guid ExpiredComponentId { get { byte[] id = new byte[16]; Marshal.Copy(ADP_Get_ADP_EXPIRED_COMPONENTID(), id, 0, 16); return new Guid(id); } } private static bool PerformCheck() { FileStream fs = new FileStream(System.Windows.Forms.Application.StartupPath + "\\AdpWrapper.dll", FileMode.Open, FileAccess.Read); if (fs == null) return false; byte[] buf = new byte[(int)fs.Length]; fs.Read(buf, 0, buf.Length); fs.Close(); SHA256 hash = SHA256.Create(); string str = BitConverter.ToString(hash.ComputeHash(buf)); return str.Equals("95-60-40-A3-12-B3-E2-94-92-32-5A-54-5D-53-FD-EC-BD-DD-8F-EF-3C-87-B2-3B-40-8B-1E-55-05-37-D7-31"); } } Ok, so pretty easy so far. You create that C# wrapper class and when you call methods it forward the call into the AdpWrapper.dll. Now, there is still a problem. Someone could swap the wrapper.dll with another one. This is always going to be possible, but we can make it a little more difficult by running a hash on the DLL. The PerformCheck method runs a SHA256 integrity hash on the DLL and returns the result. You then have to hard code the result you expect. Unless your AdpWrapper.dll is byte-per-byte identical to my version, you will get a different hash. That's ok, I just put a break point at the last line, copy the computed hash and paste it in the code. Ok, that's it. It should take no more than 30 minutes to do and there are probably other ways of doing this, just don't forget to run the hash at the end. Happy coding! Ylian

For more complete information about compiler optimizations, see our Optimization Notice.