Monday, December 19, 2011

WinRT internals: WinMD files

One of the main design goals of the Windows Runtime is to provide a broad range of APIs that can be consumed across a variety of technologies and languages. In fact, everything designed for WinRT can be made accessible to other languages through the use of language projections. Every class Microsoft provides and every class you write in C++ and C# in Visual Studio generates .winmd files that describe to other bindings how to use that. You simply need a tool that turns those meta data files into code in your language of choice. I suggest you take a look at Microsofts documentation on how to Consume and Emit .winmd Files to get some direction on where to begin with some other language, such as Delphi.

Okay, that was a little tongue in cheek; At the time of publishing this article, that MSDN article was essentially blank, a stub for future documentation. But some other MSDN articles did give me some more clues. The overview of the Windows Runtime design describes the .winmd files: "The Windows Runtime is exposed using API metadata (.winmd files). This is the same format used by the .NET framework (Ecma-335). The underlying binary contract makes it easy for you to access the Windows Runtime APIs directly in the development language of your choice." And if that wasn't enough the new APIs to query for meta data of APIs (RoGetMetaDataFile) return a IMetaDataImport2 and a mdTypeDef. These are exactly the same things you need to inspect metadata provided by .NET assemblies, which you can already do in Delphi using the APIs in Winapi.Cor and Winapi.CorHdr (which come from cor.h and corhdr.h in the platform sdk).

Another great tip I found somewhere, because the metadata files are just Ecma-335 metadata, they look just like .NET assemblies to tools such as .NET Reflector, and Microsofts own IL Disassembler. So out of the box in Windows 8, you can use ildasm.exe to peek at the contents of .winmd files. In the image to the right here, we can see the metadata in Windows.UI.Xaml.winmd. Every class is described in detail: what interfaces it implements, what methods and properties and events are available. Every enumeration and structure is described in detail as well. All that needs to be done now is develop the tool that turns this into Delphi interfaces. While I'm not going to go into the specifics of this tool, I'd like to point out some follies I fell into when translating these files.

Namespaces

The first thing I noticed is that Windows is doing a lot to break the API down into Namespaces, and for the most part each metadata file describes one namespace. So my first reaction was to put the contents of each metadata file into one Delphi unit. This doesn't quite work though because of a few things: One metadata file may actually contain multiple namespaces, one namespace might be included in several metadata files, and there are circular dependancies between types in the various metadata files. Since in delphi one unit = one namespace, I've lumped everything into one big unit that exposes all of the Apis defined in the winmd files provided by Microsoft to work around this issue for now. If you've looked at my code on git, you'll also notice that the dotted namespaced types have been flattened, using "_" instead of ".". Nesting the classes even within this one large file becomes difficult because of the interdependencies among classes and the need for things to be defined before they can be used. A smarter tool should be able to create an tree of all the dependancies and actually construct a more OO mapping similar to the C++, C# and Javascript ones.

Classes

Classes from the point of view of Delphi are exposed in much the same way as COM objects, as interfaces. You tend to create them with RoActivateInsance, which is similar to CoCreateInstance, or RoGetActivationFactory. For classes that implement "static" methods, these methods are acquired by requesting an interface. For example, Windows.UI.Xaml.Application implements Windows.UI.Xaml.IApplicationFactory (for constructing an Windows.UI.Xaml.IApplication) and a Windows.UI.Xaml.IApplicationStatics. A Delphi wrapper will need to implement these likely by storing the reference to the interface in a class private field and implementing each method. I imagine something clever might be possible using RTTI to define class static interfaces and do something similar to dynamic aggregation (the implements keyword), injecting the interface into the class VMT.

Enums

WinRT Enums will almost certainly need to be scoped. There are enums with the same name in different namespaces, and it's very possible for the enum values to be different, especially as more third parties add their own. Microsoft ordered the values of Windows.Graphics.FontStyle (Normal, Italic, Oblique) differently than Windows.UI.Xaml.FontStyle (Normal, Oblique, Italic) for example. It shouldn't be an issue if they're scoped; the compiler will type check and it won't be a problem.

Events

Something that caught me off guard: the interfaces for Event handlers don't inherit from IInspectable, dropping the pattern that "Everything is IInspectable". This seems somewhat weird; I thought that I should be able to implement say, Windows.UI.ActivatedEventHandler on any class I want, and since it's an interface, WinRT will do QueryInterface to get the correct interface before calling Invoke. This isn't the case as all; QueryInterface is never called for EventHanders, it just assumes you're giving it the right one. Presumably this makes it faster to call the Invoke behind the scenes but it seems to me a single QI at the beginning wouldn't be too much of a hit on performance. So EventHandlers all inherit from IUnknown. In my previous post I have an example of a wrapper class for an event that takes an arbitrary procedure reference; this is likely going to be a good way to create event delegate objects.

Generic Interfaces

Generics are used pretty heavily throughout WinRT. In the C++ language bindings, they end up translating down to a template class, and the pre-compiled headers actually have to parametrize every instance of a generic class. This is pretty ugly and it'd be much nicer to just declare the interface and have the compiler do the right thing. Delphi has a similar problem for similar reasons. Generic Interfaces are supported by the compiler, but the problem arises in that you cannot specify a GUID for parametrized instances of a given generic interface. So for now, I've had to create a unique interface for every parameterized interface in WinRT. But how do you go about assigning a GUID to the interfaces? The answer lies in RoGetParameterizedTypeInstanceIID.

Lets take a look at the IL of something that uses a generic. How about Windows.Storage.FilePropoerties.IStorageItem.ExtraProperties.SavePropertiesAsync:

.method public hidebysig newslot abstract virtual 
        instance class Windows.Storage.FileProperties.SavePropertiesOperation 
        SavePropertiesAsync([in] class [Windows.Foundation]Windows.Foundation.Collections.IIterable`1<class [Windows.Foundation]Windows.Foundation.Collections.IKeyValuePair`2<string,object>> propertiesToSave) cil managed
{
  .custom instance void [Windows.Foundation]Windows.Foundation.Metadata.OverloadAttribute::.ctor(string) = ( 01 00 13 53 61 76 65 50 72 6F 70 65 72 74 69 65   // ...SavePropertie
                                                                                                             73 41 73 79 6E 63 00 00 )                         // sAsync..
  .param [1]
  .custom instance void [Windows.Foundation]Windows.Foundation.Metadata.HasVariantAttribute::.ctor() = ( 01 00 00 00 ) 
} // end of method IStorageItemExtraProperties::SavePropertiesAsync
Actually let me touch on those attributes since that's sort of interesting. There is an OverloadAttribute, which says this method overloads another method named SavePropertiesAsync. This is sort of interesting because in C++, the methods can't be overloaded with the same name: This one is SavePropertiesAsync, the other is SavePropertiesAsyncOverloadDefault, as described by it's own OverloadAttribute. This is useful information if your language doesn't support overloading methods with different arguments. Delphi doesn't have that problem, since there are different arguments, we can name them both the same.

Okay, so the method has one parameter propertiesToSave, of type Windows.Foundation.Collections.IIterable`1<Windows.Foundation.Collections.IKeyValuePair`2<string,object>>. If you're not familiar with IL, the "[Windows.Foundation]" denotes that the type is a typeref, and the complete typedef can be found in the assembly (or in this case, metadata file) Windows.Foundation. The `1 and `2 after the generic type names is an indication of how many generic arguments there are. To me this seems like an odd convention because it should be given how many arguments there are going to be, and it's strange that it's called out in every type. But it's a clue to some of the new APIs, such as RoParseTypeName which will validate if a type name is valid, given the number of generic parameters, and for the IRoSimpleMetaDataBuilder sink for actually getting the GUID. In order to use RoGetParameterizedTypeInstanceIID, we need a class that implements IRoMetaDataLocator, which uses the Locate method as a call back to query for more information about "unknown" types. It actually ends up querying about every Interface type, but knows how to handle basic types such as "int", "string", and "object". Remember, in the context of WinRT, a string is a HSTRING, and a Object ends up being some class that implements IInvokable. But for the purposes of this API, they're "string" and "object".

So we need a class that implements IRoMetaDataLocator. There's a pretty big gotcha here that caught me off guard. The documentation for IRoMetaDataLocator states "The IRoMetaDataLocator interfaces inherits from the IUnknown interface." That statement isn't surprising, but what is surprising is it's absolutely false. Look at the definition from roparameterizediid.h:

struct IRoMetaDataLocator
{
    STDMETHOD(Locate)(
        PCWSTR                         nameElement,
        _In_ IRoSimpleMetaDataBuilder& metaDataDestination
    ) const = 0;
};
If I declared the interface as
type
  IRoMetaDataLocator = interface(IUnknown)
    function Locate(nameElement: PCWSTR; metaDataDestination: IRoSimpleMetaDataBuilder): HRESULT; stdcall;
  end;
My program would crash in QueryInterface. In Delphi, there is no such way to define a naked "interface" with no IUnknown methods. So I had to fake it with a virtual abstract class.
type
  IRoMetaDataLocator = class
  protected
    function Locate(nameElement: PCWSTR; 
      metaDataDestination: IRoSimpleMetaDataBuilder): HRESULT; virtual; stdcall; abstract;
  end;
Looking at it now, potentially I should be using a record to more closely match the C++. IRoSimpleMetaDataBuilder actually has the same issue: there is no IUnknown members for that interface.

As I said before, the locate method gets called with each part of the type that is being fetched, so the first time it's invoked, we get "Windows.Foundation.Collections.IIterable`1", the second time we get "Windows.Foundation.Collections.IKeyValuePair`2". We don't get called for "string" or "object" because it handles them internally. My Locator needs to lookup each string and provide the metaDataDestination with it's guid, and specify if it's generic or not. I'll give a sneak peek at that code, although it's using some helper classes that aren't publicly available to make using the Cor interfaces easier to use.

function TLocator.Locate(nameElement: PCWSTR;
  metaDataDestination: TRoSimpleMetaDataBuilder): HRESULT;
var
  name: HSTRING;
  imp: IMetaDataImport2;
  tok: mdTypeDef;
  md: TMetaDataImport;
  info: TTypeInfo;
  genArgs: TArray;
begin
  Result := S_OK;

  WindowsCreateString(nameElement, StrLen(nameElement), name );
  try
    RoGetMetaDataFile(name, nil, nil, @imp, @tok);
    if imp = nil then
      Exit(E_UNEXPECTED);

    md := TMetaDataImport.Create(imp);
    try
      info := md.GetTypeInfo(tok, genArgs);
      if info.GenericParamCount = 0 then
        metaDataDestination.SetWinRtInterface(info.Guid)
      else
        metaDataDestination.SetParameterizedInterface(info.Guid, info.GenericParamCount);
    finally
      md.Free;
    end;
  finally
    WindowsDeleteString(name);
  end;
end;
Note there are other types that could be passed in to a parameterized type, Enums, Delegates, or Structs. I'm ignoring these for now because I didn't actually encounter any API that uses those types.

The somewhat problematic thing about this is this might be difficult to include in a compiler, because the APIs are only available on Win8, so there's no way to generate the GUIDs from a older version of Windows. It'd be nice if Microsoft provided information on how those GUIDs are constructed so other tools that run on other Windows platforms can cross-build for Win8.

I hope this helps to de-mystify some of the information provided in the Windows WinMD metadata files.

48 comments:

  1. The "Windows Runtime design" link now leads to "This content has been removed".

    ReplyDelete
  2. The Delphi IDE is a speedy claim development (RAD) environment, accordingly it is likely to have the rudimentary framework of a encode obstinate absent inside a few notes, every the underlying Opportunity and object making is taken thoughtfulness of automatically. mobilsuv2012

    ReplyDelete
  3. Packers and movers in mumbai are one amongst India’s leading Surface transporters with over 20 and years during this business our name speaks for our performance. With a team of trusted associates and experienced workers we guarantee to understand our roads and stretch our limits to deliver to your desires and on time.

    Movers and Packers Hyderabad
    Movers and Packers in Kondapur
    Movers and Packers in Gachibowli
    Movers and Packers in Kukatpally
    Movers and Packers in Chanda Nagar
    Movers and Packers in Manikonda

    ReplyDelete
  4. Packers and movers in mumbai are one amongst India’s leading Surface transporters with over 20 and years during this business our name speaks for our performance. With a team of trusted associates and experienced workers we guarantee to understand our roads and stretch our limits to deliver to your desires and on time.

    Packers and Movers in Hyderabad
    Packers and Movers in Jubilee Hills
    Packers and Movers in Banjara Hills
    Packers and Movers in Kondapur
    Packers and Movers in Madhapur
    Packers and Movers in Gachibowli
    Packers and Movers in Chanda Nagar

    ReplyDelete
  5. Thanks for your stop at Indian Packers and Movers in Mumbai. We really know the intentions of the people who are moving their homes or offices in Mumbai. We are one of the best Packers And Movers in Mumbai that gives values to the suggestions and recommendations of the client. It is this heart reading and friendly packing and moving service that made us the most loved packers and movers in Mumbai to depend for professional local transport in Mumbai, courier services in Mumbai and relocation services Mumbai and more.
    Packers and Movers in Mumbai
    Packers and Movers in Dadar
    Packers and Movers in Thane
    Packers and Movers in Panvel

    ReplyDelete
  6. Algorithm is codified here:
    https://docs.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system

    ReplyDelete
  7. Really Awesome Blog this is, You can also check more details on how we are providing 100% verified packers and movers list in your city, checkout.

    Packers and Movers in Ahmedabad
    Bike Transport Ahmedabad
    Car Transport Ahmedabad
    Packers and Movers in Gandhinagar
    Packers and Movers in Mundra

    ReplyDelete
  8. I have study your article, it is very informative and beneficial for me. I recognize the valuable statistics you offer in your articles. Thanks for posting it.
    Hand Sanitizer Bottle and Spray Online

    ReplyDelete
  9. Archie 420 Dispensary is a trusted Cannabis dispensary base in Los Angeles California USA. It is one of the top dispensary in this part of the country. They do deliver Marijuana in the USA and to over 25 countries in the world. Buy Death Star weed online You can always visit their dispensary in Los Angeles using the address on their website. Place your order and get served by the best dispensary in the planet. Have fun.

    ReplyDelete
  10. Are you looking to get a good sleep, relief depression, pain relieve, sex enhancement, good trip, visit the best online pharmacy to get all your required pills, shrooms and psychedelics with or without prescriptions now.

    https://stdavidpharmaceuticals.com/
    https://stdavidpharmaceuticals.com/product-category/psychedelics/
    https://stdavidpharmaceuticals.com/product-category/pills/
    https://stdavidpharmaceuticals.com/product-category/chemicals/
    https://stdavidpharmaceuticals.com/product-category/ibogain/

    ReplyDelete
  11. Are you looking to get a good sleep, relief depression, pain relieve, sex enhancement, good trip, visit the best online pharmacy to get all your required pills, shrooms and psychedelics with or without prescriptions now.

    https://stdavidpharmaceuticals.com/
    https://stdavidpharmaceuticals.com/product-category/psychedelics/
    https://stdavidpharmaceuticals.com/product-category/pills/
    https://stdavidpharmaceuticals.com/product-category/chemicals/
    https://stdavidpharmaceuticals.com/product-category/ibogain/

    ReplyDelete
  12. 국내 최고 스포츠 토토, 바카라, 우리카지노, 바이너리 옵션 등 검증완료된 메이져 온라인게임 사이트 추천해 드립니다. 공식인증업체, 먹튀 검증 완료된 오라인 사이트만 한 곳에 모아 추천해 드립니다 - 카지노 사이트 - 바카라 사이트 - 안전 놀이터 - 사설 토토 - 카지노 솔루션.

    온라인 카지노, 바카라, 스포츠 토토, 바이너리 옵션 등 온라인 게임의 최신 정보를 제공해 드립니다.

    탑 카지노 게임즈에서는 이용자 분들의 안전한 이용을 약속드리며 100% 신뢰할 수 있고 엄선된 바카라, 스포츠 토토, 온라인 카지노, 바이너리 옵션 게임 사이트 만을 추천해 드립니다.

    ReplyDelete
  13. I’m gone to inform my little brother, that he should also pay a quick visit this blog on regular basis to obtain updated from most recent
    news.ライブカジノの

    ReplyDelete
  14. Thankfulness to my father who told me about this
    webpage, this weblog is in fact remarkable.
    content writing company in delhi
    travel content writer in delhi

    ReplyDelete
  15. Greetings! Very helpful advice in this particular post!
    It is the little changes which will make the largest changes. Thanks a lot for sharing!

    Click here to chceck my blog :: 오피사이트
    (jk)

    ReplyDelete
  16. People in the Netherlands speak Dutch. In 1945, the Netherlands became a sovereign political entity. The largest cities in the Netherlands are Amsterdam, The Hague, Rotterdam. http://www.confiduss.com/en/jurisdictions/netherlands/infrastructure/

    ReplyDelete
  17. To learn about the peculiarities of language, religion, age, gender distribution and promotion of people in Lithuania, see the sections below, and also visit the section on education in the country. http://www.confiduss.com/en/jurisdictions/lithuania/geography/

    ReplyDelete
  18. Exellent blog. Thanks again. Nice... Foreign Citizens can easily apply for the Indian 30 days tourist visa, The process is completely online. And Within 5 to 10 minutes Foreign can apply for an Indian tourist e-visa.

    ReplyDelete
  19. PG SLOT มีเกมหลากหลายให ้เลือก พรอ้ มกราฟฟิกแอนนเิมชนั่ สวยงามทสี่ ดุ ในยคุ นี้แจ็คพอตแตกง่าย pgslot โปรโมชนั่ พเิศษส าหรับทงั้สมาชก เกา่ และสมาชกิ ใหม่ ระบบฝาก-ถอนเงินออโต ้เงินเข ้าเร็วทันใจมีแอดมิ ดูแลตลอด 24 ชวั่ โมง ต ้องที่ pgslot.bid มั่นคงจ่ายจริง ไม่ว่าคุณจะถอนเงิ มากมายเท่าไหร่ ทางเว็บจ่ายเต็มไม่มีหัก pgslot ปั่นง่ายแตกง่าย

    pg slot ผู ้ให ้บริการเว็บตรง ที่แตกง่ายที่สุด ปั่นสนุก ฝากเงินรับเครดิตฟร สูงสุด 15000 บาท สมัคร พีจีสล็อต หรือ PGSLOT กับเราเพื่อความมั่นคงของคุณเอง.

    ReplyDelete
  20. 토토사이트 이용을 하다 보면 먹튀 피해에 대한 두려움이 갑자기 생기거나 두려움을 가지고 이용하는 경우가 대반사입니다. 토토마추자는 그러한 두려움을 없애드리기 위해 만들어진 먹튀검증커뮤니티 입니다. 다양한 검증 활동을 통하여 축적된 데이터로 방문해 주시는 모든 회원님들께 무료로 제공해드리고 있습니다. 토토사이트를 이용 하는 회원님들은 메이저사이트를 이용하고 싶은 마음이 크다는 걸 알 수 있습니다. 하지만 해당 토토사이트가 메이저인지 아닌지 확인할 방법은 민간인에게 어려움이 있다는 걸 알 수 있습니다. 이용전 꼭 토토마추자를 통하여 먹튀검증 서비스를 받아보시기 바랍니다안전놀이터.
    국내에 합법 토토사이트는 배트맨토토 에서 발매하는 정식서비스만 이용을 할 수 있으나 배팅금액 제한이 있기에 우후죽순 사설토토사이트가 생겨나고 있습니다. 대체적으로 이를 방지를 하려면 배트맨토토 자체에서 배팅금액 한도를 늘리는 수밖에 없다고 생각됩니다.

    ReplyDelete
  21. This comment has been removed by the author.

    ReplyDelete
  22. The beguiling hot ESC0RTS IN ZIRAKPUR are therefore chosen to be the best of associates to enthrall clients in right directions.
    Follow us:-

    https://bit.ly/3Ihzf3z
    https://bit.ly/3GDhZp4
    https://bit.ly/3qGGkEZ
    https://bit.ly/3nDqPvL
    https://bit.ly/33N44OS

    ReplyDelete
  23. I have read this article many times, and I found it very helpful. I really appreciate the time and effort you put into this and click here to apply for an urgent visa for India from Australia. An e-visa to India has now become easier to obtain.

    ReplyDelete
  24. Hello! I was sure I've been to this blog before, however after checking through some of the posts I realized it's actually a new one to me. Anyhow, I am definitely delighted I found it and I will book-mark and check back often. Indian visa online you can apply via India evisa website.It's easy and fast, you don't need to go to embassy and consulate, you can get online.

    ReplyDelete
  25. The website is good and the stock is good. Thanks for all you do. India urgent visa, you can fill an online Indian emergency visa application form. Within 5 to 10 minutes you can fill your India emergency visa application form. Urgent visa to India you get in 1 to 3 business days.

    ReplyDelete

  26. You wrote a very interesting and informative post. Thanks for taking the time to write it. Very useful for readers interested in travel information. Do you want to apply for a Turkish e visa, we provide Turkey e Visa information. You can visit our visa to Turkey page. Read more about e visa of Turkey.

    ReplyDelete
  27. Just 99 Web Design offers affordable web design packages for every business, from basic one-page websites to full eCommerce website design services. inexpensive web design companies

    ReplyDelete
  28. Avantajlý ve kaliteli bir dünyaya Superbetin ile giriþ yapýn. süperbetin global ölçekte bahis ve casino hizmet veren bir þirkettir. Kullanýcýlar kaliteli bir hizmet sunmakta. Bahsedeceklerimizden ziyade sizlerde Superbetin giriþ yaparak hizmetleri görebilirsiniz. Makalemizde sizlere Superbetin güvenilir mi? Süperbetin giriþ bilgileri gibi konularda bahsedeceðiz. Ayrýca Superbetin kayýt ol sayesinde özel bonuslara ulaþýn. Sizlerde süperbetin sayesinde avantajlý bir bahis ve casino hizmeti ulaþýn.
    Süperbetin sunduðu hizmetler hakkýnda detaylý bilgiler vereceðiz. Lakin öncelikle sitenin güvenilirliðinden bahsedelim. Ýlk olarak güvenilir olmayan bir site sektörde uzun yýllar hizmet veremez. Dolandýrýcý siteler zaten en fazla 3 ay hizmet verebilmiþtir. Superbetin ise sektörde 10 yýlý aþkýn zamandýr hizmet vermekte. Lisansýný bahis ve casino sektörünün en göze lisans kurumu olan çuraçao hükümetinden almýþtýr. Superbetin oyunlarý bahis ve casino oyunlarýndan saðladýklarý kazançlarýný sorun yaþamadan çekebilmekte.
    Superbetin
    Süperbetin giriþ yaparak sizlerde avantajlý bir bahis ve casino hizmetine ulaþabilirsiniz. Türkiye’deki kullanýcýlar güncel giriþ adresine sayfamýzda her zaman ulaþabilmekte. Avantajlý bir bahis ve casino hizmetine doðru siteyi tercih ederek ulaþýn. Hesabý olmayan kullanýcýlar Süperbetin kayýt iþlemini kolay bir þekilde yapabilmekte. Hiçbir ek belge göndermeden ve ayrýca kimlik bilgisi vermeden kolay bir þekilde hesabýnýzý oluþturun. Ayrýca yeni üyelere özel bonuslar bulunmakta. Deneme bonusu ve hoþgeldin bonusu yeni üyelere özeldir.
    Süperbetin bahis bölümünde geniþ bir bahis bülteni canlý olarak iddaa oynayabilirsiniz. Ayrýca tek maç üzerinden canlý bahisler oynanabilmekte. Binlere farklý müsabakaya yüksek oranlardan canlý bahis oynayabileceðiniz bir hizmet sizleri beklemekte. Sadece spor bahisleri de deðil. E-sports bahisleri, sanal bahisleri gibi farklý bahis seçenekleri de bulunmakta.
    Superbetin sayesinde gerçek bir casino hizmetine de ulaþabilirsiniz. Gerçek casino salonlarýndan canlý kurpiyerlerin sunumlarýyla 4K görüntü kalitesi üzerinden hizmet alýn. Canlý casino oyunlarý ve binlerce farklý slot oyunlarý sizleri beklemekte.
    Süperbetin bahis ve casino oyunlarýndan saðladýðýnýz kazançlarýnýzý sorun yaþamadan çekebilirsiniz. Sonuç olarak istediðiniz finansal yöntem üzerinden para yatýrma ve çekme iþlemlerinizi kolay bir þekilde yapýn.
    Sizlerde Süperbetin giriþ yaparak avantajlý bahis ve casino dünyasýna giriþ yapabilirsiniz.
    Süperbetin kullanýcýlarý para yatýrma ve çekme iþlemlerinizi yapabilirsiniz. Sonuç olarak BTC, usdt gibi coinler üzerinden finansal iþlem sunan sitelerden birisidir.
    Geniþ bir iddaa bülteni üzerinden binlerce farklý müsabakaya ayrýca yüksek oranlardan canlý iddaa oynayýn.
    Gerçek casino salonlarýndan özel olarak bir hizmete ulaþýn. Canlý casino oyunlarý ve slot oyunlarý superbetin dünyasýnda sizleri beklemekte.

    ReplyDelete
  29. Ghostwriters are basically the professionals who write the content for you but does not put their name on the work or takes any credit for their writing. Ghost writing simply means professional who are hired to write your memories, blogs or speeches and many more such scripts for you and your brand but doesn’t tag themselves on the work or you can say they don’t take the credit for their work and you are not allowed to share your work anywhere else. Ebook Writing Services

    ReplyDelete