How to remove/replace fonts currently in use - c#

I'd like to deploy font packages programmatically during our update process. Therefore, I need to replace the old font files in \Windows\Fonts with the new ones extracted from a ZIP archive. Deleting an existing font file will cause an IOException, because of another process accessing the file.
I tried to release the font by calling RemoveFontResource and propagating the change with a WM_FONTCHANGE message, but no luck. RemoveFontResource returns false with native error 2 (file not found?) and HRESULT -2147467259. The file actually does exist.
Sample code:
[DllImport("coredll.dll", SetLastError = true)]
private static extern int RemoveFontResource(string lpName);
[DllImport("coredll.dll", SetLastError = true)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private const int WM_FONTCHANGE = 0x001D;
private const int HWND_BROADCAST = 0xffff;
public const string SAMPLE_FILE = #"\Windows\Fonts\MyFont.ttf";
public void RemoveFont()
{
if (!RemoveFontResource(SAMPLE_FILE))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
SendMessage(new IntPtr(HWND_BROADCAST), WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
File.Delete(SAMPLE_FILE);
}
Is there actually a clean way to deploy and replace fonts system-wide?

If the font files are part of the OS image, they have the eXecute-In-Place (XIP) attribute set (read-only, system, ROM) and cannot simply replaced. Although one can copy a file of the same name on top, which hides the original file. If the file copied on top is deleted, the original file is back. This is the case with all XIP files.
I recommend you use the SysCache dir to replace the files. This will work with all file replacements, regardless if they are XIP, drivers, DLLs, in-use or otherwise locked for replacement.
If you place files with the same name as the origibal file in the syscache dir, these will be used instead of the original files. But you have to warmboot the device after placing files in the syscache dir. Windows CE/Mobile will recognize and 'use' the syscache files only during the OS startup.
The location of the syscache dir may vary, depending on OS version and OEM decisions. On WM 6 devices it should be \Windows\System\syscache. Check the file system of the device for an existing syscache directory.
BTW: font files do not need to be placed into \Windows\Fonts, it is OK to place them in \Windows.

Related

Why does LoadLibrary fail while DllImportAttribute works?

I'm creating a .NET application for a client that performs I/O with one of their third-party systems. As they regularly change the password of this system, I should retrieve it dynamically by calling a native DLL that they provide in a dedicated directory (not besides my EXE file).
However, I have trouble loading the DLL dynamically using LoadLibraryEx. The weird thing is that I can call the library using the DllImportAttribute.
This is what I have done so far:
According to this SO answer, I use the following code (in a constructor) to try to load the DLL dynamically:
public PasswordProvider(string dllPath)
{
if (!File.Exists(dllPath))
throw new FileNotFoundException($"The DLL \"{dllPath}\" does not exist.");
_dllHandle = NativeMethods.LoadLibraryEx(dllPath, IntPtr.Zero, LoadLibraryFlags.None);
if (_dllHandle == IntPtr.Zero)
throw CreateWin32Exception($"Could not load DLL from \"{dllPath}\".");
var procedureHandle = NativeMethods.GetProcAddress(_dllHandle, GetPasswordEntryPoint);
if (procedureHandle == IntPtr.Zero)
throw CreateWin32Exception("Could not retrieve GetPassword function from DLL.");
_getPassword = Marshal.GetDelegateForFunctionPointer<GetPasswordDelegate>(procedureHandle);
}
When LoadLibraryEx is called, the resulting handle is null, the error code is 126 which usually means that the DLL or one of its dependencies could not be found.
When I call LoadLibraryEx with DoNotResolveDllReferences, then I get a working handle but afterwards, I cannot call GetProcAddress (error code 127) - I suspect that I have to fully load the DLL for this.
When I open the native DLL in Dependencies (which essentially is Dependency Walker for Win10), I can clearly see that one of the statically linked DLLs is missing
However, if I copy the DLL besides my EXE file and use the DllImportAttribute, I can call into the DLL
[DllImport(DllPath, EntryPoint = GetPasswordEntryPoint, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern long GetPassword(long systemId, string user, byte[] password);
How is this possible? I thought that the mechanism behind DllImportAttribute uses LoadLibary internally, too. Where does my code differ? Am I missing something obvious?
Just some notes:
I can't just use DllImportAttribute as I cannot specify searching in a dedicated directory this way (the DLL must lie beside my EXE file or in a common Windows location for this to work).
I also tried LoadLibrary instead of LoadLibraryEx but with the same results.
EDIT after Simons comment:
NativeMethods is defined as followed:
private static class NativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr LoadLibrary(string dllName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr LoadLibraryEx(string dllFileName, IntPtr reservedNull, LoadLibraryFlags flags);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr moduleHandle, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr moduleHandle);
}
[Flags]
private enum LoadLibraryFlags : uint
{
None = 0,
DoNotResolveDllReferences = 0x00000001,
LoadIgnoreCodeAuthorizationLevel = 0x00000010,
LoadLibraryAsDatafile = 0x00000002,
LoadLibraryAsDatafileExclusive = 0x00000040,
LoadLibraryAsImageResource = 0x00000020,
LoadLibrarySearchApplicationDir = 0x00000200,
LoadLibrarySearchDefaultDirs = 0x00001000,
LoadLibrarySearchDllLoadDir = 0x00000100,
LoadLibrarySearchSystem32 = 0x00000800,
LoadLibrarySearchUserDirs = 0x00000400,
LoadWithAlteredSearchPath = 0x00000008
}
EDIT after Hans Passant's comment:
The overall goal is the ability to replace / update the native DLL while my application (a Windows Service) is running. I detect a file change and then reload the DLL. I am not quite sure if this is possible with DllImportAttribute without restarting the service.
And I should be more specific on the actual problem: I couldn't load the native DLL using LoadLibraryEx, no matter if it was placed next to my EXE, or in another random folder, or in SysWow64. Why does it work with DllImportAttribute? I'm pretty sure that the missing FastMM subdependency DLL is not present on my system (neither next to the actual DLL, nor in any Windows directory).
It's because the DLL search order path. In windows when application try to load a DLL the underlying system automatically search some path for the DLL ,So let's pretend Windows's DLL search path looks something like this:
A) . <-- current working directory of the executable, highest priority, first check
B) \Windows
C) \Windows\system32
D) \Windows\syswow64 <-- lowest priority, last check
You can read more about the underlying mechanism in this Microsoft documentation.
Search for DLL which your main DLL has dependency to it and find where it store on system, add the directory of it to DLL search path of Windows using AddDllDirectory or SetDllDirectory.
If the dll already loaded into memory by any of running process Windows automatically use it instead of searching, so you can load FastMM DLL into memory using LoadLibrary manually and then try to load the main DLL and it should solve the problem too.
#HansPassant and #David Heffernan are right: I actually tried to load two different versions of the DLL (one of them had the FastMM subdependency, one did not). Thanks for your help and sorry for the inconvenience.

AddFontFile XP C#

I have a project where I need to load a Postscript font from disk.
I found I could use "AddFontFile". Doing some research I see that I have to pipe the two fonts http://msdn.microsoft.com/en-us/library/system.drawing.text.privatefontcollection.addfontfile.aspx together so I tried:
fontCollection = new PrivateFontCollection();
fontCollection.AddFontFile(#"C:\Temp\Font\myfont.PFM|C:\Temp\Font\myfont.PFB");
I'm getting a a error "Illegal characters in path".
I'm not sure if I'm piping the two fonts correctly.
Any help would be great, I should mention we are still on XP not sure if that makes a differnts or not.
Mike
You cannot have the pipe | character in your filename. PrivateFontCollection.AddFontFile requires a valid filepath. Hence your "Illegal characters in path" exception. The input at MSDN is A String that contains the file name of the font to add. Try passing a single file at a time - I don't know about this piping idea..
As for your Postscript desire, the Remarks section states that OpenTypes have limited support.
After some searching I got it to work and I'd like to share on how I solved the this:
AddFontFile was the wrong API i Needed to use AddFontResource instead
String fontPath = #"C:\Temp\Font2\GXSTRA03.PFM|C:\Temp\Font2\GXSTRA03.PFB";
int result = AddFontResource(fontPath);
long msg = SendMessage(HWND_BROADCAST, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
to remove the font resource
RemoveFontResource(#"C:\Temp\Font2\GXSTRA03.PFM|C:\Temp\Font2\GXSTRA03.PFB");
long msg = SendMessage(HWND_BROADCAST, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
If anyone is new on making a external WinApi call here is the import and import DLL code that I used
using System.Runtime.InteropServices;
private static uint WM_FONTCHANGE = 0x1D;
Import("gdi32.dll")]
static extern int AddFontResource(string lpFilename);
[DllImport("gdi32.dll")]
static extern bool RemoveFontResource(string lpFileName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
This fonts now shows in word and notepad ect..

MoveFileWithProgress throws "The system cannot move the file to a different disk drive" – why?

I have:
[SuppressUnmanagedCodeSecurity]
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool MoveFileWithProgress(
string lpExistingFileName, string lpNewFileName,
CopyProgressRoutine lpProgressRoutine,
int dwFlags);
public enum MoveFileOptions
{
MOVEFILE_COPY_ALLOWED = 0x2
}
And calling it with:
if (!MoveFileWithProgress(source.FullName, destination.FullName, cpr, (int)options)) {
throw new IOException(new Win32Exception().Message);
}
Where: options is MoveFileOptions.MOVEFILE_COPY_ALLOWED
It works fine when moving in the hard drive. But when I try moving to a Flash-drive, I get: The system cannot move the file to a different disk drive.
Why?
Your DllImport is incorrect. Your function has only 4 parameters, but the real function has 5. Presumably what is happening is that MOVEFILE_COPY_ALLOWED is being passed to lpData and is ignored. The dwFlags parameter is just whatever happens to be sitting on the stack.
Fixing your p/invoke will probably solve the problem. Also, dwFlags should be unsigned.
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool MoveFileWithProgress(
string lpExistingFileName,
string lpNewFileName,
CopyProgressRoutine lpProgressRoutine,
IntPtr lpData,
uint dwFlags
);
With this correct you need to decide what to pass to lpData. Since you appear not to be using it at the moment, it doesn't really matter and IntPtr.Zero seems the obvious choice.
From this Microsoft | Technet page, it says:
The file cannot be moved to a different disk drive at the same time you rename it using the Rename command.
Try renaming the file before moving it.
Are you perhaps moving a directory?
According to the documentation for MoveFileWithProgress at MSDN (emphasis added):
When moving a file, lpNewFileName can be on a different file system or volume. If lpNewFileName is on another drive, you must set the MOVEFILE_COPY_ALLOWED flag in dwFlags.
When moving a directory, lpExistingFileName and lpNewFileName must be on the same drive.

how to use ORIYA font in smart device development?

I am developing an application for WINDOWS CE5.0 based device. It requires ORIYA language(INDIAN REGIONAL LANGUAGE) to be used completely. As visual studio use ENGLISH as standard language, please tell me how to proceed? I tried to copy the font in WINDOWS CE device's WINDOWS/FONTS folder but as i restart the device that font file disappears. I developed the application in c# and changed labels text into oriyaa in Development system. It looks fine on the development system but as i deployed it into device, All label text appears in ENGLISH. I dont know whats happening? I also need to set the LABEL.TEXT property in ORIYA language. Is it possible? How to take user input in ORIYA? Please help..... Thanks...
Not very sure as what you meant by browser but for the Forms you can go about using PrivateFontCollection
you can load the font from a folder in your app and then use
AddFontFile or AddMemoryFont as per your need. So now the Client can see the controls in the font you set and its available to it irrespective of its installed or not
I have used the following approach with English-based fonts, but I am not sure if it will work on your case. The original source of this approach is a nice post from Chris Tacke (SO user #ctacke) with some modifications.
[DllImport("coredll.dll")]
private static extern int AddFontResource(string lpszFilename);
[DllImport("coredll.dll", SetLastError = true)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
static IntPtr HWND_BROADCAST = (IntPtr)0xFFFF;
const int WM_Fontchange = 0x001D;
private static void RegisterFont(string aFontPath, string aTargetFontPath)
{
IntPtr thir = (IntPtr)0;
IntPtr fourth = (IntPtr)0;
try
{
if (!System.IO.File.Exists(aTargetFontPath))
System.IO.File.Copy(aFontPath, aFontTargetPath);
}
catch { throw; }
int _Loaded = AddFontResource(aFontTargetPath);
if (_Loaded != 0)
SendMessage(HWND_BROADCAST, WM_Fontchange, thir, fourth);
}

Why can't I get GetPrivateProfileString to work via pinvoke?

Running a c# console app I wrote on 64 bit Vista. Here's the code:
class Class1
{
static void Main(string[] args)
{
Debug.Assert(File.Exists(#"c:\test.ini"));
StringBuilder sb = new StringBuilder(500);
uint res = GetPrivateProfileString("AppName", "KeyName", "", sb, sb.Capacity, #"c:\test.ini");
Console.WriteLine(sb.ToString());
}
[DllImport("kernel32.dll")]
static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName);
}
I'm sure I'll get a big "DUH!" for an answer, but I'm not seeing why this fails to work. Other than the Debug.Assert, this code was cut from the c# sample at this page
This one has been busting my chops all day, too. I think I found a work-around, which I really don't want to have to deal with: insert a blank line before the first section header in your .ini file. Now run your app and see if you don't start seeing the values you were expecting.
Considering this bug has apparently been around for years, I'm surprised MS hasn't fixed it by now. But then, .ini files were supposed to have gone away years ago. Which of course is funny, because there are a lot of places MS uses .ini file (e.g, desktop.ini). But I think MS is aware of the bug, because I notice my desktop.ini files include a leading blank line. Hmmm...
The main thing I see is that you should be passing in an uint for nSize, as well as the return value. This is because the return and nSize parameters of GetPrivateProfileString are DWORD values, which are unsigned 32bit integers.
I personally have used the syntax on PInvoke.net:
[DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);
In addition, you'll need to put the full path to the file in place, unless the file is located in the Windows directory. From the docs:
If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.
According to pinvoke.net, nSize should be a UINT. Also they are using an absolute path in their example.
Other than those differences, I can't see anything else.
Providing it's throwing a invalid format exception, try setting target platform to x86 to solve the problem.
Usage example from pinvoke.net is
[DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder(500);
uint res = GetPrivateProfileString("AppName", "KeyName", "", sb, (uint)sb.Capacity, #"c:\test.ini");
Console.WriteLine(sb.ToString());
}
If no path is specified, GetPrivateProfileString will look for Test.ini in the Windows directory.
Old APIs like GetPrivateProfileString don't handle Unicode well (even though there's a GetPrivateProfileStringW function). If Test.ini contains a UTF header or Unicode characters, that might be enough to prevent GetPrivateProfileString from working correctly.
Also, Vista's UAC can make handling files that are in "special" places tricky (C:\, C:\Windows, C:\Program Files, etc.). Try putting Test.ini in a folder rather than in the root of the C: drive, or turn off UAC. There's a thread on CodeProject that discusses GetPrivateProfileString failing silently when trying to read an .ini from a folder controlled by UAC.
Maybe you should look into looking at an open source solution that will do exactly that without you worrying about p/Invoke's. The project targetted for .NET is called nini I am using this in a project I am working on and I recommend it.
Hope this helps,
Best regards,
Tom.
Can you verify the contents of your test.ini file? Given all of the steps that you've tried, I'm beginning to suspect that your data file is not formatted correctly (misspelling, etc.) In other words, GetPrivateProfileString may be "working", but just not finding your string. Based on the code that you posted, your test.ini file should look something like this:
[AppName]
KeyName=foo

Categories