Robmaister / SharpFont

Cross-platform FreeType bindings for .NET - Need maintainer
Other
286 stars 104 forks source link

Platform targets (x64 specifically) #99

Open xposure opened 7 years ago

xposure commented 7 years ago

Looking at the SharpFont.Dependencies.props file its still hard coded to msvc10\\x86. I have to go in here and manually edit this file each time I want to build to a different platform. There is also a comment in there with a TODO to make this smarter.

Any reason it doesn't have at least $(PlatformName) instead of x86? I tested this and it appears to work correctly.

Referencing https://github.com/Robmaister/SharpFont/issues/46 and https://github.com/xposure/ImGuiSharp/issues/1

Robmaister commented 7 years ago

When I wrote that a few years ago I wasn't so great at MSBuild and mostly didn't know what I was doing. The solution I went with was to just make it work in code at least for the Examples program: https://github.com/Robmaister/SharpFont/blob/master/Source/Examples/Program.cs

If it's working well that way (can't see why it wouldn't), submit a PR and I'll merge it in right now and probably update the NuGet packages later today. Most downstreams have their own build system so this went largely unnoticed for a while

xposure commented 7 years ago

I'll submit a PR for the platform when I get home. I've been scratching my head for what to do with msvc9-12. Wouldn't that be reliant on what the end user has installed on their machine and irrelevant to the build process?

Robmaister commented 7 years ago

Yup, that depends on what the downstream wants to redistribute/depend on. It would affect dev machines at runtime, though. I would leave that portion of the todo there as it should be possible to do some OS/registry detection of which runtimes are installed to select one that will work on dev machines (the specific version of the VC++ runtime that the DLL was compiled with must be present for the dll to work at all).

When it comes time to publish software, the dev would have to pick a version and have the VC++ redistributable installer exe run as part of software installation. Having distributed commercial software in the past, I forgot this step and pretty quickly had some bug reports about crashes. Next version had all the necessary runtimes bundled with the installer.

The whole process is really bad. If I could find a way to just skip the whole VC++ runtime I would (freetype is pretty self-sufficient as far as I can tell), but the only thing I've been able to find online is "no don't do that". So I decided to build the whole matrix of libraries.

Odds are that you already have a lot of those runtimes installed from other software needing them, and there's one or two (usually the latest one at time of release) installed with each version of Windows. So for some users it will just work. For others, they will see something the infamous "Missing msvcrXX.dll" error.

If anyone knows more about this than me, please enlighten me! This is the only part of SharpFont that doesn't seem to "just work"

pdjonov commented 6 years ago

Sorry to jump in on an ancient thread, but this just bit me on a new project and it seems like it should be an easy fix...

Why can't freetype6.dll just be built with the CRT linked in statically? Then there's no dependency on any version of msvcr---.dll to worry about, and you just need one binary for each platform (x86, x64), and then the link path becomes something like $(MSBuildThisFileDirectory)..\bin\$(Platform)\freetype6.dll, and you're done.

Robmaister commented 6 years ago

@pdjonov I was originally going to say that this would only hide the issue (it would rear it's head with memory shared across CRT boundaries and a few other places), but I'm looking at this again, and given that freetype is fairly self-sufficient, this might be a good solution.

Better yet might be to do this but also use the memory functionality of freetype to inject the C# malloc/free calls in the Marshal class and avoid sharing any memory across the application <-> library boundary. I'll have to look through freetype to see where and how it depends on CRT functionality, but I'd imagine it's minimal.

This probably won't happen for a while, but that's for bringing it up

pdjonov commented 6 years ago

@Robmaister Thanks! And don't rush or anything on my account - I'm just some random on the internet offering suggestions, without so much as a PR to back them. If some time down the road I pull an update and don't have to go editing the .props file in the packages direcory, I'll be happy to have offered whatever help this comment is.

Also, sorry if I've just misunderstood your tone, but it seems like you're a little vague on exactly what folks were warning about when you looked into this a year ago. Really appreciate this project, and I don't mean to be talking down to you. Anyway...

To be precise, sharing memory (or any other resource) isn't really an issue in and of itself, it's mixing up memory management routines that matters. You can pass a pointer across module boundaries all you like, and any module loaded into the same process can read and write what it points to as if it were its own memory (taking care not to write to readonly/const portions, obviously). The thing that matters is that the malloc and free that bound its lifetime have to ultimately resolve to the same routines, which almost always means they must be in the same module.

That's kind of hard to mix up (at least by accident) in P/Invoke code, because you don't get random code from the library's public headers inlining into the caller's module, and you have to be very explicit with which module each token binds to (so nothing can hide in macros or .lib files).

Usually it comes up with poorly written library interfaces like this:

// in mylib.h

__declspec(dllimport) void* mylib_new_obj();
#define mylib_delete_obj( obj ) free( obj )

So the malloc would happen in mylib.dll, but the free would happen in whatever module happens to call it, and then the two modules have to share the same actual CRT implementation. But if mylib_delete_obj were a call into mylib.dll then you'd be back on the right side of the module boundary when free gets called, and it makes no difference.

Same thing arises in C++ libraries that use the STL in their public headers, or have other inlined resource management stuff in their headers (which doesn't take care to avoid this problem), or which throw exceptions across library boundaries.

A less common occurrence is with global variables - imagine some library communicates by setting a global like errno (I don't know why anyone would do this), and then the client has to go and read it. Well, if they don't share a CRT module then the library's writing to its version of errno and the client is reading a different copy of that variable. So, bad news there.

I skimmed the P/Invoke bindings and a few of the Dispose methods (so maybe there's something I didn't spot), but it doesn't look like you've got any such hazards between the FT and C# layers - you P/Invoke into FT to allocate, and P/Invoke back in to release resources, so however the DLL's compiled it's always going to agree with itself as to what malloc and free mean.

Now, if FT itself has direct dependencies on other native DLLs, which can't be statically linked into freetype6.dll, and those boundaries have such violations, then that might actually be a reason to require an external CRT. But it seems pretty self-contained so I wouldn't expect that to be the case.