Getting VCP codes from external monitor in C# - c#

I need to get various VCP codes from an DDC/CI enabled external monitor in a C# application running on a Windows machine.
I have been able to find the WIN32 API which can does exactly this, but uses unmanaged code, specifically C++. Additionally, I have searched for native C# ways to do this and only 1 solution came up, namely the Nuget package MonitorControl. However, this Nuget package only support the highlevel, not lowlevel API, plus when I downloaded the package I wasn't able to use it due to missing files.
So from this website (Using the Low-Level Monitor Configuration Functions) I have been able to come up with some C++ code(largely copy/pasted from the example on the website), which I have implemented successfully.
#include "Windows.h"
#include "PhysicalMonitorEnumerationAPI.h"
#include "lowlevelmonitorconfigurationapi.h"
#pragma comment(lib, "Dxva2.lib")
void readVCPFromMonitor(BYTE vcpCode)
{
HMONITOR hMonitor = NULL;
DWORD cPhysicalMonitors;
LPPHYSICAL_MONITOR pPhysicalMonitors = NULL;
HWND hWnd = ::GetDesktopWindow();
// Get the monitor handle.
hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTOPRIMARY);
// Get the number of physical monitors.
BOOL bSuccess = GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, &cPhysicalMonitors);
if (bSuccess)
{
// Allocate the array of PHYSICAL_MONITOR structures.
pPhysicalMonitors = (LPPHYSICAL_MONITOR)malloc(cPhysicalMonitors * sizeof(PHYSICAL_MONITOR));
if (pPhysicalMonitors != NULL)
{
// Get the array.
bSuccess = GetPhysicalMonitorsFromHMONITOR(hMonitor, cPhysicalMonitors, pPhysicalMonitors);
// Get physical monitor handle.
HANDLE hPhysicalMonitor = pPhysicalMonitors[0].hPhysicalMonitor;
MC_VCP_CODE_TYPE pvct = MC_SET_PARAMETER;
DWORD pdwCurrentValue = 0;
DWORD pdwMaximvumValue = 0;
bSuccess = GetVCPFeatureAndVCPFeatureReply(hPhysicalMonitor, vcpCode, &pvct, &pdwCurrentValue, &pdwMaximvumValue);
if (bSuccess == FALSE)
{
std::cout << "Error getting VCP Code" << std::endl;
}
else
{
std::cout << "VCP Code Value : " << std::dec << pdwCurrentValue << std::endl;
if (pvct != 0)
{
std::cout << "VCP Code Type: " << std::dec << pvct << std::endl;
}
if (pdwMaximvumValue != 0)
{
std::cout << "VCP Code Max value: " << std::dec << pdwMaximvumValue << std::endl;
}
}
// Close the monitor handles.
bSuccess = DestroyPhysicalMonitors(cPhysicalMonitors, pPhysicalMonitors);
// Free the array.
free(pPhysicalMonitors);
}
}
}
This code can be called with a specific VCP code as a parameter and spits out the result to the console, just for testing purposes. But now I need to "translate" this code to C#, and I dont know how to do the proper marshalling.
Any pointers, example code or other help would be greatly appreciated!
Final note: I have also found the Nuget package CsWin32 which should automate much of the marshalling, but I have not yet succeded.
If anyone know of a library or anything else that I can use, I am also very open to that.

Related

passing BSTR string as a perimeter between managed and unmanaged code (COM interop)

While working on com interop ,i followed the tutorial on this link.The code runs fine as i have done some modification depending on my requirement but the problem comes while dealing with the string.I am using BSTR string here as a perimeter here.
Here is the function in c# that i am calling from c++
public string ShowDialog([MarshalAs(UnmanagedType.BStr)] string stringToPrint)
{
// Console.WriteLine(" Enter TOTP input:");
// stringToPrint = Console.ReadLine();
if (stringToPrint == "111111")
{
MessageBox.Show("true");
}
else
{
MessageBox.Show("false");
}
return stringToPrint;
}
here is my C++ main function section of the code where the calls are being made
CoInitialize(NULL);
MyInterop::IMyDotNetInterfacePtr pDotNetCOMPtr;
HRESULT hRes = pDotNetCOMPtr.CreateInstance(MyInterop::CLSID_MyDotNetClass);
if (hRes == S_OK)
{
BSTR lResult ;
cout << "enter TOTP input" << endl;
_bstr_t bstrStatus = SysAllocString(L"111111");
pDotNetCOMPtr->ShowDialog(bstrStatus,&lResult);
SysFreeString(bstrStatus);
}
CoUninitialize();
system("pause");
here is the output
The issues that i am facing are as follows:
BSTR string is not being returned on the console after it is passed from c++ code although i am using a returning function in c#
Is it possible to insert input dynamically on the console as i am using SysAllocString("") here which makes it somewhat hard coded.
When you're using Visual Studio and the #import directive, the generated code uses _bstr_t which is a smart wrapper class over BSTR (the raw Windows type).
So, you don't have to use SysAllocString nor SysFreeString, you can just use _bstr_t very naturally. For example, in your case, if your C# method signature is like this:
public string ShowDialog(string stringToPrint) // you don't need the MarshalAs here, the TLB will build it as a BSTR
then you can use a C++ code like this:
... other imports or includes
// import mscorlib so we have a full generated cool code (I recommend not to use raw_interfaces_only)
// we rename 'or' to something that doesn't pose problems. Your mileage can vary depending on your building context...
#import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb" rename("or","whatever")
#import "C:\myPath\MyClassLibrary1.tlb" // adapt to your path
int main()
{
CoInitialize(NULL);
{
MyClassLibrary1::_Class1Ptr ptr;
HRESULT hr = ptr.CreateInstance(__uuidof(MyClassLibrary1::Class1)); // should return S_OK/0
_bstr_t input = L"111111";
_bstr_t res = ptr->ShowDialog(input); // assign the return string
wprintf(L"res:%s\n", res.GetBSTR()); // output (unicode) result to console
}
CoUninitialize();
}
You could also directly write this:
_bstr_t res = ptr->ShowDialog(L"111111");
// or this (with automatic ansi to unicode conversion)
_bstr_t res = ptr->ShowDialog("111111");
All _bstr_t are automatically allocated and freed.

C++ Dll in WPF C# application

After looking for solutions already proposed to a similar question to mine,
and since this is the first time I'm using a non-.NET DLL in a .NET application,
I really need your help.
I have an WPF application, using MVVM Pattern, and in my ViewModel class I need to use a DLL done in C++ to recover a token. I have an example in C++ that uses this DLL, so I have the method's names, but I can't do the same in C#. I know that I must use DllImport to use this methods, but how implement it and use the pointer in C#??
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <string>
int main()
{
HINSTANCE hinst = NULL;
typedef bool ( *GetTokenProto )( char ** );
typedef void ( *FreeTokenProto )( char * );
GetTokenProto GetToken;
FreeTokenProto FreeToken;
std::string str = "DllName.dll";
std::string token;
if ( (hinst = LoadLibraryA(str.c_str()) ) )
{
GetToken = (GetTokenProto) GetProcAddress(hinst, "GetToken");
FreeToken = (FreeTokenProto) GetProcAddress(hinst, "FreeToken");
if (GetToken && FreeToken)
{
char *buf;
if (GetToken(&buf))
{
token = buf;
FreeToken(buf);
std::cout << "Token:" << token << std::endl;
}
else
{
std::cerr << "DLL loaded but no token" << std::endl;
exit(1);
}
}
else
{
std::cerr << "DLL loaded but missing proc address(es)" << std::endl;
exit(1);
}
FreeLibrary(hinst);
}
else
{
std::cerr << "Failed to load DLL" << std::endl;
exit(1);
}
return 0;
}
Update
[DllImport("DllName.dll", EntryPoint = "GetToken", CallingConvention = CallingConvention.Cdecl)]
public static extern bool get_token(ref string token);
[DllImport("DllName.dll", EntryPoint = "FreeToken", CallingConvention = CallingConvention.Cdecl)]
public static extern void free_token(ref string token);
public static string a_token;
public string get_token_method()
{
try
{
string buffer = null;
if (get_token(ref buffer))
{
a_token = buffer;
free_token(ref buffer);
Debug.WriteLine("token : " + a_refresh_token);
}
else
{
Debug.WriteLine("DLL Loaded but no token");
}
}
catch (Exception ex)
{
Debug.WriteLine("\n" + ex.Message);
}
return a_refresh_token;
}
The error
I have an Exception "System.DllNotFoundException" : Unable to load DLL
'DllName.dll': The specified module could not be found. (Exception
from HRESULT: 0x8007007E).
Dll file is in the same folder of the .exe (..\bin\Debug)
If your DLL is in the same Directory, there are still a few things that might be the problem.
Firstly
The DLL may have dependencies
The native DLL you use may have other dependencies which have to be
installed (try Dependency Walker). If the native DLL requires for
example registry settings, config files etc. these should also be
present. It should be distributed to user machines the same way you
installed it on the dev machine.
Most likely you are missing the C++ redistributable package (which one I'm not sure) however Dependency Walker should tell you.
Secondly
It could be targeting a different bitness i.e x86 x64, so I'd try changing your project to see if that helps
Right click your project, and select properties.
In properties, select the build tab. Under platform target, select x86.
Hit Ctrl+Shift+S to save all files, right click the solution and select "Clean" to get rid of old binaries. Any builds after that
should be 32 bit
You can use SetDllDirectory method to set path of your dll before invoking your dll
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetDllDirectory(string lpPathName);
You should also use StringBuilder instead of string on c# side if you want to get string from c++.

Adjust brightness the native way in Windows

I was wondering what is the native way to adjust the brightness in Windows?
By native, I mean the method that also displays the brightness overlay in the top left corner in Windows 8, 8.1, and 10, as if the special brightness keys have been pressed.
I was looking all over the Internet for this, but some solutions that do work, adjust the brightness, but no overlay is shown. Any idea? Is there something like
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2);
which turns off the monitor, but for brightness, that can be used from C++? Or C#? Thanks.
Update: here is the sample code.
HMONITOR hMonitor = NULL;
DWORD cPhysicalMonitors;
LPPHYSICAL_MONITOR pPhysicalMonitors = NULL;
// Get the monitor handle.
hMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY);
// Get the number of physical monitors.
BOOL bSuccess = GetNumberOfPhysicalMonitorsFromHMONITOR(
hMonitor,
&cPhysicalMonitors
);
if (bSuccess)
{
// Allocate the array of PHYSICAL_MONITOR structures.
pPhysicalMonitors = (LPPHYSICAL_MONITOR)malloc(
cPhysicalMonitors * sizeof(PHYSICAL_MONITOR));
if (pPhysicalMonitors != NULL)
{
// Get the array.
bSuccess = GetPhysicalMonitorsFromHMONITOR(
hMonitor, cPhysicalMonitors, pPhysicalMonitors);
// Use the monitor handles (not shown).
DWORD pdwMinimumBrightness = 0;
DWORD pdwCurrentBrightness = 0;
DWORD pdwMaximumBrightness = 0;
DWORD dwMonitorCapabilities = 0;
DWORD dwSupportedColorTemperatures = 0;
bSuccess = GetMonitorCapabilities(pPhysicalMonitors, &dwMonitorCapabilities, &dwSupportedColorTemperatures);
cout << bSuccess << endl;
// Close the monitor handles.
bSuccess = DestroyPhysicalMonitors(
cPhysicalMonitors,
pPhysicalMonitors);
// Free the array.
free(pPhysicalMonitors);
}
}
cout << bSuccess always writes 0 in terminal.
GetMonitorCapabilities fails with error -1071241854 (0xC0262582: "An error occurred while transmitting data to the device on the I2C bus."). Then how do the brightness keys on my computer work?

DLL won't work in C#

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.

How do I save a copy of the clipboard and then revert back to it?

I'm reading words in a text box by simulating the key presses required to highligh and copy the text. When I'm done, I want the clipboard to be exactly how I found it.
I was hoping I'd be able to do something like this:
IDataObject clipboardBackup = Clipboard.GetDataObject();
Clipboard.Clear();
//Save other things into the clipboard here, etc//
Clipboard.SetDataObject(clipboardBackup);
But that doesn't seem to work. It looks like you can go the route of specifically trying for text, audio, pictures, etc. and then saving them accordingly. (I guess 'data object' is specialized like that in my example too, I was hoping it was generic.)
I'd prefer not to use cases for every possible type of clipboard data, both to be more concise and to make sure I never lose the data regardless of format.
Any tips for grabbing any and all of the clipboard and then restoring it?
Do not use the clipboard unless directly instructed to do so by the logged-on user. It's not for general application storage - it's for the user to use for what they want to clip.
If you want to get text from an arbitrary textbox, locate the window (in the Win32) sense and send an EM_GETTEXT Win32 message.
The following article presents some code to backup and restore the Clipboard. There's more to it than one might imagine, so I won't repost the code here.
Clipboard Backup In C#
It is impossible to fully restore the clipboard in cases where delayed rendering is used, or where the clipboard data actually contains pointers back into the local program that are not intended for other programs to use them (dumb, but I've seen it). Consider the classic example of Excel, using delayed rendering to offer the same selection in dozens of different formats, including hazardous ones like Bitmap and HTML (could consume hundreds of MB and several minutes of clock time for rendering, if thousands of cells are copied).
And then there is the whole situation of other clipboard viewers reacting to your clipboard manipulations. They'll get duplicate data, altered data, or notification that the clipboard has been erased.
I think this sums it up best:
“Programs should not transfer data into our out of the clipboard without an explicit instruction from the user.”
— Charles Petzold, Programming Windows 3.1, Microsoft Press, 1992
I've implement one sample with c++.
#include "stdafx.h"
#include "TestClip.h"
#include <Windows.h>
#include <queue>
using namespace std;
typedef struct
{
UINT format;
LPVOID content;
SIZE_T size;
} ClipBoardItem;
void InspectClipboard()
{
UINT uFormat = 0;
HGLOBAL hglb;
LPVOID hMem;
float totalSize = 0;
if (!OpenClipboard(NULL))
{
cout << "Open clipboard failed!" << endl;
system("pause");
return;
}
int countFormat = CountClipboardFormats();
cout << "Clipboard formats count: " << countFormat << endl;
uFormat = EnumClipboardFormats(uFormat);
while (uFormat)
{
cout << "Clipboard format:" << uFormat;
hglb = GetClipboardData(uFormat);
if (hglb != NULL)
{
hMem = GlobalLock(hglb);
SIZE_T size = GlobalSize(hMem);
cout << ", size:" << size << endl;
totalSize += size;
if (hMem != NULL)
{
GlobalUnlock(hglb);
}
}
else
{
cout << " data is NULL" << endl;
}
uFormat = EnumClipboardFormats(uFormat);
}
CloseClipboard();
string unit = "bytes";
if (totalSize >= 1024)
{
totalSize /= 1024;
unit = "KB";
}
if (totalSize >= 1024)
{
totalSize /= 1024;
unit = "MB";
}
if (totalSize >= 1024)
{
totalSize /= 1024;
unit = "GB";
}
cout << "Total size is: " << totalSize << " " << unit.data() << endl;
}
queue<ClipBoardItem> BackupClipboard()
{
queue<ClipBoardItem> clipQueue;
UINT uFormat = 0;
HGLOBAL hglb;
LPTSTR lptstr;
LPVOID hMem;
if (!OpenClipboard(NULL))
{
cout << "Open clipboard failed" << endl;
return clipQueue;
}
uFormat = EnumClipboardFormats(uFormat);
while (uFormat)
{
cout << "Backup clipboard format:" << uFormat << endl;
hglb = GetClipboardData(uFormat);
if (hglb != NULL)
{
hMem = GlobalLock(hglb);
SIZE_T size = GlobalSize(hMem);
if (size > 0)
{
ClipBoardItem clipitem;
clipitem.format = uFormat;
clipitem.content = malloc(size);
clipitem.size = size;
memcpy(clipitem.content, hMem, size);
clipQueue.push(clipitem);
}
if (hMem != NULL)
{
GlobalUnlock(hglb);
}
}
uFormat = EnumClipboardFormats(uFormat);
}
EmptyClipboard();
CloseClipboard();
cout << "Clipboard has been cleaned" << endl;
return clipQueue;
}
void RestoreClipboard(queue<ClipBoardItem> clipQueue)
{
if (!OpenClipboard(NULL)) return;
while (!clipQueue.empty())
{
ClipBoardItem clipitem = clipQueue.front();
HGLOBAL hResult = GlobalAlloc(GMEM_MOVEABLE, clipitem.size);
if (hResult == NULL)
{
cout << "GlobalAlloc failed" << endl;
clipQueue.pop();
continue;
}
memcpy(GlobalLock(hResult), clipitem.content, clipitem.size);
GlobalUnlock(hResult);
if (SetClipboardData(clipitem.format, hResult) == NULL) {
cout << "Set clipboard data failed" << endl;
}
cout << "Resotred clipboard format:" << clipitem.format << endl;
GlobalFree(hResult);
free(clipitem.content);
clipQueue.pop();
}
CloseClipboard();
}
int _tmain(int argc, TCHAR* argv [], TCHAR* envp [])
{
InspectClipboard();
cout << "Press any key to backup and empty clipboard" << endl;
system("pause");
queue<ClipBoardItem> clipQueue = BackupClipboard();
InspectClipboard();
cout << "Press any key to restore clipboard" << endl;
system("pause");
RestoreClipboard(clipQueue);
cout << "Clipboard has been restored" << endl;
system("pause");
InspectClipboard();
system("pause");
return 0;
}
Hope this helps!
I'd consider the requirement for restoring the clipboard at all, if I were in your position. More often than not, the amount of data on the clipboard is going to be relatively small (a block of text, an image from a website, something like that), but every now and then you'll come across a situation where the user has placed a large amount of data on the clipboard, possibly some time ago, and possibly no longer required. Your app will have to be robust enough to cope with temporarily storing all that data during the lifetime of your operation. I personally don't think it is likely to be worth the effort, although you know the situation better than I could.
In short, don't. Just clear the clipboard with Clipboard.Clear(); after you're done.

Categories

Resources