I'm using a third party library to render an image to a GDI DC and I need to ensure that any text is rendered without any smoothing/antialiasing so that I can convert the image to a predefined palette with indexed colors.
The third party library i'm using for rendering doesn't support this and just renders text as per the current windows settings for font rendering. They've also said that it's unlikely they'll add the ability to switch anti-aliasing off any time soon.
The best work around I've found so far is to call the third party library in this way (error handling and prior settings checks ommitted for brevity):
private static void SetFontSmoothing(bool enabled)
{
int pv = 0;
SystemParametersInfo(Spi.SetFontSmoothing, enabled ? 1 : 0, ref pv, Spif.None);
}
// snip
Graphics graphics = Graphics.FromImage(bitmap)
IntPtr deviceContext = graphics.GetHdc();
SetFontSmoothing(false);
thirdPartyComponent.Render(deviceContext);
SetFontSmoothing(true);
This obviously has a horrible effect on the operating system, other applications flicker from cleartype enabled to disabled and back every time I render the image.
So the question is, does anyone know how I can alter the font rendering settings for a specific DC?
Even if I could just make the changes process or thread specific instead of affecting the whole operating system, that would be a big step forward! (That would give me the option of farming this rendering out to a separate process- the results are written to disk after rendering anyway)
EDIT: I'd like to add that I don't mind if the solution is more complex than just a few API calls. I'd even be happy with a solution that involved hooking system dlls if it was only about a days work.
EDIT: Background Information
The third-party library renders using a palette of about 70 colors. After the image (which is actually a map tile) is rendered to the DC, I convert each pixel from it's 32-bit color back to it's palette index and store the result as an 8bpp greyscale image. This is uploaded to the video card as a texture. During rendering, I re-apply the palette (also stored as a texture) with a pixel shader executing on the video card. This allows me to switch and fade between different palettes instantaneously instead of needing to regenerate all the required tiles. It takes between 10-60 seconds to generate and upload all the tiles for a typical view of the world.
EDIT: Renamed GraphicsDevice to Graphics
The class GraphicsDevice in the previous version of this question is actually System.Drawing.Graphics. I had renamed it (using GraphicsDevice = ...) because the code in question is in the namespace MyCompany.Graphics and the compiler wasn't able resolve it properly.
EDIT: Success!
I even managed to port the PatchIat function below to C# with the help of Marshal.GetFunctionPointerForDelegate. The .NET interop team really did a fantastic job! I'm now using the following syntax, where Patch is an extension method on System.Diagnostics.ProcessModule:
module.Patch(
"Gdi32.dll",
"CreateFontIndirectA",
(CreateFontIndirectA original) => font =>
{
font->lfQuality = NONANTIALIASED_QUALITY;
return original(font);
});
private unsafe delegate IntPtr CreateFontIndirectA(LOGFONTA* lplf);
private const int NONANTIALIASED_QUALITY = 3;
[StructLayout(LayoutKind.Sequential)]
private struct LOGFONTA
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
public unsafe fixed sbyte lfFaceName [32];
}
Unfortunately you cant. The ability to control font anti aliasing is done per font. The GDI call CreateFontIndirect processes members of the LOGFONT struct to determine if its allowed to use cleartype, regular or no anti aliasing.
There are, as you noted, system wide settings. Unfortunately, changing the systemwide setting is pretty much the only (documented) way to downgrade the quality of font rendering on a DC if you cannot control the contents of the LOGFONT.
This code is not mine. Is unmanaged C. And will hook any function imported by a dll or exe file if you know its HMODULE.
#define PtrFromRva( base, rva ) ( ( ( PBYTE ) base ) + rva )
/*++
Routine Description:
Replace the function pointer in a module's IAT.
Parameters:
Module - Module to use IAT from.
ImportedModuleName - Name of imported DLL from which
function is imported.
ImportedProcName - Name of imported function.
AlternateProc - Function to be written to IAT.
OldProc - Original function.
Return Value:
S_OK on success.
(any HRESULT) on failure.
--*/
HRESULT PatchIat(
__in HMODULE Module,
__in PSTR ImportedModuleName,
__in PSTR ImportedProcName,
__in PVOID AlternateProc,
__out_opt PVOID *OldProc
)
{
PIMAGE_DOS_HEADER DosHeader = ( PIMAGE_DOS_HEADER ) Module;
PIMAGE_NT_HEADERS NtHeader;
PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
UINT Index;
assert( Module );
assert( ImportedModuleName );
assert( ImportedProcName );
assert( AlternateProc );
NtHeader = ( PIMAGE_NT_HEADERS )
PtrFromRva( DosHeader, DosHeader->e_lfanew );
if( IMAGE_NT_SIGNATURE != NtHeader->Signature )
{
return HRESULT_FROM_WIN32( ERROR_BAD_EXE_FORMAT );
}
ImportDescriptor = ( PIMAGE_IMPORT_DESCRIPTOR )
PtrFromRva( DosHeader,
NtHeader->OptionalHeader.DataDirectory
[ IMAGE_DIRECTORY_ENTRY_IMPORT ].VirtualAddress );
//
// Iterate over import descriptors/DLLs.
//
for ( Index = 0;
ImportDescriptor[ Index ].Characteristics != 0;
Index++ )
{
PSTR dllName = ( PSTR )
PtrFromRva( DosHeader, ImportDescriptor[ Index ].Name );
if ( 0 == _strcmpi( dllName, ImportedModuleName ) )
{
//
// This the DLL we are after.
//
PIMAGE_THUNK_DATA Thunk;
PIMAGE_THUNK_DATA OrigThunk;
if ( ! ImportDescriptor[ Index ].FirstThunk ||
! ImportDescriptor[ Index ].OriginalFirstThunk )
{
return E_INVALIDARG;
}
Thunk = ( PIMAGE_THUNK_DATA )
PtrFromRva( DosHeader,
ImportDescriptor[ Index ].FirstThunk );
OrigThunk = ( PIMAGE_THUNK_DATA )
PtrFromRva( DosHeader,
ImportDescriptor[ Index ].OriginalFirstThunk );
for ( ; OrigThunk->u1.Function != NULL;
OrigThunk++, Thunk++ )
{
if ( OrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG )
{
//
// Ordinal import - we can handle named imports
// ony, so skip it.
//
continue;
}
PIMAGE_IMPORT_BY_NAME import = ( PIMAGE_IMPORT_BY_NAME )
PtrFromRva( DosHeader, OrigThunk->u1.AddressOfData );
if ( 0 == strcmp( ImportedProcName,
( char* ) import->Name ) )
{
//
// Proc found, patch it.
//
DWORD junk;
MEMORY_BASIC_INFORMATION thunkMemInfo;
//
// Make page writable.
//
VirtualQuery(
Thunk,
&thunkMemInfo,
sizeof( MEMORY_BASIC_INFORMATION ) );
if ( ! VirtualProtect(
thunkMemInfo.BaseAddress,
thunkMemInfo.RegionSize,
PAGE_EXECUTE_READWRITE,
&thunkMemInfo.Protect ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Replace function pointers (non-atomically).
//
if ( OldProc )
{
*OldProc = ( PVOID ) ( DWORD_PTR )
Thunk->u1.Function;
}
#ifdef _WIN64
Thunk->u1.Function = ( ULONGLONG ) ( DWORD_PTR )
AlternateProc;
#else
Thunk->u1.Function = ( DWORD ) ( DWORD_PTR )
AlternateProc;
#endif
//
// Restore page protection.
//
if ( ! VirtualProtect(
thunkMemInfo.BaseAddress,
thunkMemInfo.RegionSize,
thunkMemInfo.Protect,
&junk ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
return S_OK;
}
}
//
// Import not found.
//
return HRESULT_FROM_WIN32( ERROR_PROC_NOT_FOUND );
}
}
//
// DLL not found.
//
return HRESULT_FROM_WIN32( ERROR_MOD_NOT_FOUND );
}
You would call this from your code by doing something like (I haven't checked that this in any way compiles :P):
Declare a pointer type to the funciton you want to hook:
typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*);
Implement a hook function
static PFNCreateFontIndirect OldCreateFontIndirect = NULL;
WINAPI MyNewCreateFontIndirectCall(LOGFONT* plf)
{
// do stuff to plf (probably better to create a copy than tamper with passed in struct)
// chain to old proc
if(OldCreateFontIndirect)
return OldCreateFontIndirect(plf);
}
Hook the function sometime during initialization
HMODULE h = LoadLibrary(TEXT("OtherDll"));
PatchIat(h, "USER32.DLL", "CreateFontIndirectW", MyNewCreateFontIndirectProc, (void**)&OldCreateFontIndirectProc);
Of course, if the module you are hooking exists in .NET land its very unclear as to where the CreateFontIndirect call is going to originate from. mscoree.dll? The actual module you call? Good luck I guess :P
As requested, I have packaged up the code I wrote to solve this problem and placed it in a github repository: http://github.com/jystic/patch-iat
It looks like a lot of code because I had to reproduce all the Win32 structures for this stuff to work, and at the time I chose to put each one in its own file.
If you want to go straight to the meat of of the code it's in: ImportAddressTable.cs
It's licensed very freely and is for all intents and purposes, public domain, so feel free to use it in any project that you like.
Do you need more colours than black and white on your fonts?
If not, you could make your bitmap object a 1 bit per pixel image (Format1bppIndexed?).
The system will probably not smooth font rendering on 1bpp images.
is the GraphicsDevice Class a 3rd party class?
the way i would do this is:
Graphics g = Graphics.FromImage(memImg);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
or in your case:
GraphicsDevice graphics = GraphicsDevice.FromImage(bitmap)
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
if the GraphicsDevice Class inherits the Graphics class (otherwise try using the Graphics class?)
Related
I have a computer vision plugin using Hololens locatable camera on Unity 5.2. I use the class UnityEngine.XR.WSA.WebCam.PhotoCaptureFrame with photo mode enabled at highest resolution, which is 2048x1156, in NV12 color format which is camera native format.
PhotoCaptureFrame class : https://docs.unity3d.com/2017.2/Documentation/ScriptReference/XR.WSA.WebCam.PhotoCaptureFrame.html
At the moment, I do this for my plugin to process any single photo:
PhotoCaptureFrame photo; // created correctly through Unity API
List<byte> photoBuffer = new List<byte>();
photo.CopyRawImageDataIntoBuffer(photoBuffer);
// marshal photoBuffer to plugin (and it works fine for the computer vision part)
However, it copies the raw buffer each time. I want to use the raw buffer directly from my plugin. So I tried this:
IntPtr rawBufferPtr = photo.GetUnsafePointerToBuffer(); // COM pointer to IMFMediaBuffer is incremented
// send rawBufferPtr to plugin (native function described below)
Marshal.Release(rawBufferPtr);
IMFMediaBuffer interface : https://msdn.microsoft.com/en-us/library/windows/desktop/ms696261(v=vs.85).aspx
And in my C++ plugin:
#include <Mfidl.h> // for IMFMediaBuffer support
void process_photo(void *photo_buffer_wrapper, int photo_width, int photo_height)
{
// photo_buffer_wrapper = rawBufferPtr in managed environment
IMFMediaBuffer *media_buffer = reinterpret_cast<IMFMediaBuffer *>(photo_buffer_wrapper);
BYTE *photo_buffer = NULL;
HRESULT result = media_buffer->Lock(&photo_buffer, NULL, NULL);
if (SUCCEEDED(result))
{
// do image processing stuff here (with OpenCV) using photo_buffer
media_buffer->Unlock;
}
}
It appears fine to me. It does compile fine too. But at run time, I get an access violation and the applications crashes on Hololens.
Exception Code: 0xC0000005
Exception Information: The thread tried to read from or write to a virtual address for which it does not have the appropriate access.
Anyone sees the problem? Something to do with the way I pass the IMFMediaBuffer object from managed to unmanaged environment?
Thank you very much!
I will answer my own question.
The photo_buffer_wrapper is not a pointer to IMFMediaBuffer as I thought but a pointer to IUnknown. Here is the modified native function that works as intended:
// UNMANAGED ENVIRONMENT
#include <Mfidl.h> // for IMFMediaBuffer support + other COM stuff
void process_photo(void *photo_buffer_unknown, int photo_width, int photo_height)
{
// photo_buffer_unknown = photoCaptureFrame.GetUnsafePointerToBuffer() in managed environment which is an IntPtr
IMFMediaBuffer *media_buffer;
if (SUCCEEDED(reinterpret_cast<IUnknown *>(photo_buffer_unknown)->QueryInterface<IMFMediaBuffer>(&media_buffer)))
{
BYTE* photo_buffer = NULL;
if (SUCCEEDED(media_buffer->Lock(&photo_buffer, NULL, NULL)))
{
// process photo_buffer with OpenCV (wrapped in a cv::Mat)
media_buffer->Unlock();
media_buffer->Release(); // QueryInterface on IUnknown has incremented reference count by one
}
}
}
NB: The pointer returned from photoCaptureFrame.GetUnsafePointerToBuffer() still has to be released in managed environment like in my question:
// MANAGED ENVIRONMENT
IntPtr mediaBufferUnknownPtr = photoCaptureFrame.GetUnsafePointerToBuffer();
// send mediaBufferUnknownPtr to native function through extern DLL call
Marshal.Release(mediaBufferUnknownPtr)
The following is an attempt to reproduce the CreateHardLink functionality as described here.
The reason I even need to do this is because this is the only way that I know I'll have the necessary permissions (this code is running in .Net, in WinPE and has asserted the necessary privileges for restore). In particular, I'm using the BackupSemantics flag and the SE_RESTORE_NAME privilege. The normal pInvoke mechanism to CreateHardLink has no provisions for a restore program to use BackupSemantics...and there are scads of files my account doesn't have "normal" access to - hence, this mess.
unsafe bool CreateLink( string linkName, string existingFileName )
{
var access =
NativeMethods.EFileAccess.AccessSystemSecurity |
NativeMethods.EFileAccess.WriteAttributes |
NativeMethods.EFileAccess.Synchronize;
var disp = NativeMethods.ECreationDisposition.OpenExisting;
var flags =
NativeMethods.EFileAttributes.BackupSemantics |
NativeMethods.EFileAttributes.OpenReparsePoint;
var share =
FileShare.ReadWrite |
FileShare.Delete;
var handle = NativeMethods.CreateFile
(
existingFileName,
access,
( uint ) share,
IntPtr.Zero,
( uint ) disp,
( uint ) flags,
IntPtr.Zero
);
if ( !handle.IsInvalid )
{
var mem = Marshal.AllocHGlobal( 1024 );
try
{
var linkInfo = new NativeMethods.FILE_LINK_INFORMATION( );
var ioStatus = new NativeMethods.IO_STATUS_BLOCK( );
linkInfo.replaceIfExisting = false;
linkInfo.directoryHandle = IntPtr.Zero;
linkInfo.fileName = linkName;
linkInfo.fileNameLength = ( uint )
Encoding
.Unicode
.GetByteCount( linkInfo.fileName );
Marshal.StructureToPtr( linkInfo, mem, true );
var result = NativeMethods.NtSetInformationFile
(
handle.DangerousGetHandle( ),
ref ioStatus,
mem.ToPointer( ),
1024,
NativeMethods.FILE_INFORMATION_CLASS.FileLinkInformation
);
return result == 0;
}
finally
{
Marshal.FreeHGlobal( mem );
}
}
return false;
}
I keep getting a result from NtSetInformationFile that says I have specified an invalid parameter to a system function. (Result=0xC000000D). I'm unsure about how I've declared the structures - as one of 'em has a file name's length is followed by the "first character" of the name. It's documented here.
Here's how I've declared the structures - and the import. This is just best-guess stuff, as I've not found anyone who's declared this in c# (pinvoke.net and other places) I've messed with a number of permutations...all with the exact same error:
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct FILE_LINK_INFORMATION
{
[MarshalAs( UnmanagedType.Bool )]
public bool replaceIfExisting;
public IntPtr directoryHandle;
public uint fileNameLength;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = MAX_PATH )]
public string fileName;
}
internal struct IO_STATUS_BLOCK
{
uint status;
ulong information;
}
[DllImport( "ntdll.dll", CharSet = CharSet.Unicode )]
unsafe internal static extern uint NtSetInformationFile
(
IntPtr fileHandle,
ref IO_STATUS_BLOCK IoStatusBlock,
void* infoBlock,
uint length,
FILE_INFORMATION_CLASS fileInformation
);
Any light you can shed on the dumb thing I've done would be most appreciated.
EDIT:
At the risk of drawing more downvotes, I'll explain the context, without which, might have had some believing I was looking for a hack. It's a selective backup/restore program that lives in the midst of state-management software - mostly kiosks and POS terminals and library computers. Backup and restore operations happen in a pre-boot environment (WinPE).
What ended up working, with respect to using the function was the need to change the structure FILE_LINK_INFORMATION and a twist in the file naming. First, the working FILE_LINK_INFORMATION needs to go like this:
[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
internal struct FILE_LINK_INFORMATION
{
[MarshalAs( UnmanagedType.U1 )]
public bool ReplaceIfExists;
public IntPtr RootDirectory;
public uint FileNameLength;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = MAX_PATH )]
public string FileName;
}
As Harry Johnston mentioned, the Pack=4 was wrong - and the marshalling of the bool needed to be a little different. The MAX_PATH is 260.
Then, when calling NtSetInformationFile in the context of a file that is opened with Read,Write, and Delete access and sharing:
unsafe bool CreateLink( DirectoryEntry linkEntry, DirectoryEntry existingEntry, SafeFileHandle existingFileHandle )
{
var statusBlock = new NativeMethods.IO_STATUS_BLOCK( );
var linkInfo = new NativeMethods.FILE_LINK_INFORMATION( );
linkInfo.ReplaceIfExists = true;
linkInfo.FileName = #"\??\" + storage.VolumeQualifiedName( streamCatalog.FullName( linkEntry ) );
linkInfo.FileNameLength = ( uint ) linkInfo.FileName.Length * 2;
var size = Marshal.SizeOf( linkInfo );
var buffer = Marshal.AllocHGlobal( size );
try
{
Marshal.StructureToPtr( linkInfo, buffer, false );
var result = NativeMethods.NtSetInformationFile
(
existingFileHandle.DangerousGetHandle( ),
statusBlock,
buffer,
( uint ) size,
NativeMethods.FILE_INFORMATION_CLASS.FileLinkInformation
);
if ( result != 0 )
{
Session.Emit( "{0:x8}: {1}\n{2}", result, linkInfo.FileName, streamCatalog.FullName( existingEntry ) );
}
return ( result == 0 );
}
finally
{
Marshal.FreeHGlobal( buffer );
}
}
Note, in particular, the namespace prefix - didn't work until I added that.
By the way, the DirectoryEntry describes a file that was supposed to be on disk as of the last backup.
With respect to not using CreateHardLink, as the original article describes, there was a vulnerability illustrated using NtSetInformationFile where there caller didn't need any particular permissions to add the link. Bummer! I suspect that when Microsoft closed the hole, they also introduced an issue with CreateHardLink. I will revisit this posting when I know more.
While I wouldn't recommend using the kernel API except as a last resort, I believe your immediate problem is that you are packing the FILE_LINK_INFO structure incorrectly.
You have specified packing of 4 bytes, which according to the documentation will put directoryHandle at an offset of 4. However, you are probably running on a 64-bit system, in which case the correct offset is 8.
I'm not certain how to fix this, but my best guess is that you need to use the default packing rules, i.e., not specify Pack at all. (Note that if you specify a packing of 8 bytes, FileName will presumably be put at offset 24 when it should be at offset 20.)
Whenever I try to reference my own DLL in C# through Visual Studio, it tells me it was unable to make a reference to the DLL as it's not a COM library.
I've searched around the internet to find a solution to this with no clear answer or help any where really. It's a rather "simple" DLL which captures the raw picture data from a Fingerprint Scanner. I have tested that the C++ code worked just fine before I tried to make it into a DLL, just so you know.
I followed Microsofts guide on how to make a DLL and here is what I ended up with:
JTBioCaptureFuncsDll.h
JTBioCaptureFuncsDll.cpp
JTBioCapture.cpp
JTBioCaptureFuncsDll.h
#ifdef JTBIOCAPTUREFUNCSDLL_EXPORTS
#define JTBIOCAPTUREFUNCSDLL_API __declspec(dllexport)
#else
#define JTBIOCAPTUREFUNCSDLL_API __declspec(dllimport)
#endif
using byte = unsigned char*;
struct BioCaptureSample {
INT32 Width;
INT32 Height;
INT32 PixelDepth;
byte Buffer;
};
JTBioCaptureFuncsDll.cpp
// JTBioCapture.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
namespace JTBioCapture
{
using byte = unsigned char*;
class JTBioCapture
{
public:
// Returns a Struct with Information Regarding the Fingerprint Sample
static JTBIOCAPTUREFUNCSDLL_API BioCaptureSample CaptureSample();
};
}
JTBioCapture.cpp
/*
* Courtesy of WinBio God Satish Agrawal on Stackoverflow
*/
BioCaptureSample CaptureSample()
{
HRESULT hr = S_OK;
WINBIO_SESSION_HANDLE sessionHandle = NULL;
WINBIO_UNIT_ID unitId = 0;
WINBIO_REJECT_DETAIL rejectDetail = 0;
PWINBIO_BIR sample = NULL;
SIZE_T sampleSize = 0;
// Connect to the system pool.
hr = WinBioOpenSession(
WINBIO_TYPE_FINGERPRINT, // Service provider
WINBIO_POOL_SYSTEM, // Pool type
WINBIO_FLAG_RAW, // Access: Capture raw data
NULL, // Array of biometric unit IDs
0, // Count of biometric unit IDs
WINBIO_DB_DEFAULT, // Default database
&sessionHandle // [out] Session handle
);
if (FAILED(hr))
{
wprintf_s(L"\n WinBioOpenSession failed. hr = 0x%x\n", hr);
goto e_Exit;
}
// Capture a biometric sample.
wprintf_s(L"\n Calling WinBioCaptureSample - Swipe sensor...\n");
hr = WinBioCaptureSample(
sessionHandle,
WINBIO_NO_PURPOSE_AVAILABLE,
WINBIO_DATA_FLAG_RAW,
&unitId,
&sample,
&sampleSize,
&rejectDetail
);
if (FAILED(hr))
{
if (hr == WINBIO_E_BAD_CAPTURE)
{
wprintf_s(L"\n Bad capture; reason: %d\n", rejectDetail);
}
else
{
wprintf_s(L"\n WinBioCaptureSample failed. hr = 0x%x\n", hr);
}
goto e_Exit;
}
wprintf_s(L"\n Swipe processed - Unit ID: %d\n", unitId);
wprintf_s(L"\n Captured %d bytes.\n", sampleSize);
// Courtesy of Art "Messiah" Baker at Microsoft
PWINBIO_BIR_HEADER BirHeader = (PWINBIO_BIR_HEADER)(((PBYTE)sample) + sample->HeaderBlock.Offset);
PWINBIO_BDB_ANSI_381_HEADER AnsiBdbHeader = (PWINBIO_BDB_ANSI_381_HEADER)(((PBYTE)sample) + sample->StandardDataBlock.Offset);
PWINBIO_BDB_ANSI_381_RECORD AnsiBdbRecord = (PWINBIO_BDB_ANSI_381_RECORD)(((PBYTE)AnsiBdbHeader) + sizeof(WINBIO_BDB_ANSI_381_HEADER));
PBYTE firstPixel = (PBYTE)((PBYTE)AnsiBdbRecord) + sizeof(WINBIO_BDB_ANSI_381_RECORD);
int width = AnsiBdbRecord->HorizontalLineLength;
int height = AnsiBdbRecord->VerticalLineLength;
wprintf_s(L"\n ID: %d\n", AnsiBdbHeader->ProductId.Owner);
wprintf_s(L"\n Width: %d\n", AnsiBdbRecord->HorizontalLineLength);
wprintf_s(L"\n Height: %d\n", AnsiBdbRecord->VerticalLineLength);
BioCaptureSample returnSample;
byte byteBuffer;
for (int i = 0; i < AnsiBdbRecord->BlockLength; i++) {
byteBuffer[i] = firstPixel[i];
}
returnSample.Buffer = byteBuffer;
returnSample.Height = height;
returnSample.Width = width;
returnSample.PixelDepth = AnsiBdbHeader->PixelDepth;
/*
* NOTE: (width / 3) is necessary because we ask for a 24-bit BMP but is only provided
* a greyscale image which is 8-bit. So we have to cut the bytes by a factor of 3.
*/
// Commented out as we only need the Byte buffer. Comment it back in should you need to save a BMP of the fingerprint.
// bool b = SaveBMP(firstPixel, (width / 3), height, AnsiBdbRecord->BlockLength, L"C:\\Users\\smf\\Desktop\\fingerprint.bmp");
// wprintf_s(L"\n Success: %d\n", b);
e_Exit:
if (sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if (sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
wprintf_s(L"\n Press any key to exit...");
_getch();
return returnSample;
}
The idea is that in C# you call "CaptureSample()" and then the code attempts to capture a fingerprint scan. When it does a scan, a struct should be returned to C# that it can work with, holding:
Byte Buffer
Image Height
Image Width
Image Pixeldepth
But when I try to reference the DLL in my C# project I get the following error:
I have also tried to use the TlbImp.exe tool to make the DLL but to no avail. It tells me that the DLL is not a valid type library.
So I'm a bit lost here. I'm new to C++ so making an Interop/COM Component is not something I've done before nor make a DLL for use in C#.
You cannot reference a library of unmanaged code written in C++ in a .NET Project.
So to call code from such library you have to either use DllImport, or use a WrapperClass.
I referred to this answer : https://stackoverflow.com/a/574810/4546874.
I have been trying for the past 4 hours to solve a very mysterious problem.
I am writing some plugin for Notepad++. To achieve syntax highlighting one has to export such a function:
//this function is exported via exports.def file
LexerFactoryFunction SCI_METHOD GetLexerFactory(unsigned int index)
{
return (index == 0) ? RTextLexer::LexerFactory : nullptr;
}
where,
LexerFactoryFunction is typedef ILexer *(*LexerFactoryFunction)();
#define SCI_METHOD __stdcall
I have managed to get this thing working perfectly with C++, however another part of the plugin is written in C#, so I tried to merge the two using Fody Costura NuGet package ( so that the CLI .dll is embedded into the main .dll ), however with no success.
What I've tried :
public ref class RTextLexerCliWrapper
{
public:
delegate ILexer * GetLexerFactoryDelegate();
IntPtr GetLexerFactory()
{
return System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(_lexerFactoryPtr);
}
RTextLexerCliWrapper();
private:
GetLexerFactoryDelegate ^ _lexerFactoryPtr;
GCHandle gch;
~RTextLexerCliWrapper();
};
RTextLexerCliWrapper::RTextLexerCliWrapper()
{
_lexerFactoryPtr = gcnew GetLexerFactoryDelegate(&RTextLexer::LexerFactory);
gch = GCHandle::Alloc(_lexerFactoryPtr);
}
RTextLexerCliWrapper::~RTextLexerCliWrapper()
{
gch.Free();
}
This CLI wrapper, is referenced in my main .dll like this :
static RTextLexerCliWrapper _lexerWrapper = new RTextLexerCliWrapper();
[DllExport(CallingConvention = CallingConvention.Cdecl)]
static IntPtr GetLexerFactory(uint index)
{
return (index == 0) ? _lexerWrapper.GetLexerFactory() : IntPtr.Zero;
}
So what happens is, my .net function gets indeed called and the cli wrapper function is also called, and a function pointer is indeed returned. However any attempts to call that function pointer results in an access violation. Which means that either the type of the pointer is wrong or something else which I am currently missing. I have tried countless variations of the .net exported function with void *, StdCall etc. All result in the same problem.
Is there any other way to return a function pointer of a C++ class? Or well am I doing something completely wrong?
Thanks in advance!
So I have finally managed to found the solution to my problem.
First step was exporting the functions with the correct calling convention:
static RTextLexerCliWrapper _lexerWrapper = new RTextLexerCliWrapper();
[DllExport(CallingConvention = CallingConvention.StdCall)]
static IntPtr GetLexerFactory(uint index)
{
return (index == 0) ? _lexerWrapper.GetLexerFactory() : IntPtr.Zero;
}
The convention in this case had to be StdCall. Otherwise the stack pointer is invalidated, hence the exceptions.
Now in order to return a function pointer of a C++ instance things were a bit more tricky.
I am statically storing an instance of the CLI wrapper class so that it doesn't get GCed. ( _lexerWrapper ).
This instance has a function called GetLexerFactory which return a function pointer of the C++ instance ( which is then used by some other .dll to get actual instances of some object ).
The CLI Wrapper class looks like this:
public ref class RTextLexerCliWrapper
{
public:
delegate ILexer * GetLexerFactoryDelegate();
IntPtr GetLexerFactory()
{
return System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(_lexerFactoryPtr);
}
RTextLexerCliWrapper();
private:
GetLexerFactoryDelegate ^ _lexerFactoryPtr;
GCHandle gch;
~RTextLexerCliWrapper();
};
Where ILexer * is the type of the object we shall be returning later on.
RTextLexerCliWrapper::RTextLexerCliWrapper()
{
_lexerFactoryPtr = gcnew GetLexerFactoryDelegate(&RTextLexer::LexerFactory);
gch = GCHandle::Alloc(_lexerFactoryPtr);
}
RTextLexerCliWrapper::~RTextLexerCliWrapper()
{
gch.Free();
}
So, what we have managed here is, exporting through .NET a function pointer which is able to return a pure C++ object.
i have written a user control for WinCC (Siemens) (SCADA). Now I want to pass a pointer to the control. The only way to do this is: write the pointer to a property.
WinCC has only this methods to set properties
SetPropBOOL
SetPropChar
SetPropDouble
SetPropWord
The property from control has UInt as datatype and i use the SetPropDouble method to set the address from an object.
WinCC Global Script (ANSI-C)
//autoDB is an ADODB.Connection object
//object* autoDB = __object_create("ADODB.Connection");
extern __object* autoDB;
//SetPropDouble( "PictureName", "ControlName", "PropertyName", (DWORD)(&autoDB) );
SetPropDouble( "PictureName", "ControlName", "PropertyName", (DWORD)autoDB );
I've debug my control (hook on WinCC-process) and i see the property-set becomes assigned an address-value e.g. 0x03041080.
Now the question: How can i get the object in c# (.Net) on the address?
My try throws an exception: ExecutionEngineException
private ADODB.Connection _database;
private IntPtr _ptr = IntPtr.Zero;
public uint DataBase{
get{
return (uint)_ptr;
}
set{
if( value != 0 ){
_ptr = (IntPtr)value;
GCHandle gH = GCHandle.FromIntPtr(_ptr); // THIS LINE THROW THE EXCEPTION
_database = gH.Target;
}
}
}
Ok: i've changed my code to use STRING
WinCC
extern __object* autoDB;
DWORD addr = (DWORD)autoDB;
char sAddr[11];
sprintf( sAddr, "%d\0", addr );
SetPropChar( "PictureName", "ControlName", "DataBaseAddr", sAddr );
And c# is now
private string _lpszDataBaseAddr = "";
public string DataBaseAddr{
get{
return _lpszDataBaseAddr;
}
set{
uint addr;
bool ret = uint.TryParse( value, out addr );
if( ! ret ){
return;
}
IntPtr ptr = (IntPtr)addr;
GCHandle gH = GCHandle.FromIntPtr( ptr ); // THE SAME ERROR!
}
}
Other findings!
The address from the ADO-Object is not in the process-memory who called my control (debug with ollydbg). WinCC has two programs: PDLRT.exe for visualisation (this is calling my control) and SCRIPT.exe for running GLOBAL-SCRIPT (Ansi-C).
From PDLRT, i've access to the pointer-address from ADO-Object. By call GCHandle in C# of the ADO-object-address, the exception is thrown. (ExecutionEngineException)
I have no idea if C# can access through a pointer into C++ like that.
Regardless, this: (DWORD)(&autoDB) is wrong, that puts the address of the pointer as the property's value, which is pointless.
You need the pointer's value, i.e. (DWORD) autoDB.
Also, SetPropDouble() accepts a value of type double, i.e. a floating-point number. That will not be a very nice way to share a pointer, which is a (large) integer. Try some different representation, string might work if you don't have access to a large enough integer.
Ok,
long time ago and I've ask the support from Siemens.
Siemens: The loaded Dlls, Controls and so on are load in an separated memory and not in the application (main) memory. Memory-Address-Share between Dlls, Controls .... is not working. All have a separated memory.
Super. Only way: Pipes or other communication implementations (TCP/IP, ...).