.Net Core - 复制到剪贴板



是否可以使用.Net Core(以与平台无关的方式)将某些内容复制到剪贴板?

似乎缺少Clipboard类,并且 P/Invoking 不是 Windows 之外的选项。

编辑:不幸的是,到目前为止,我的问题所说的内容与人们阅读问题时听到的内容之间存在差异。根据评论和回答,有两件事很清楚。首先,很少有人关心最真实的"象牙塔"平台不可知论是否存在。其次,当人们发布代码示例显示您如何在不同平台上使用剪贴板时,技术上正确的答案("不,这是不可能的")令人困惑。因此,我删除了括号条款。

我的这个项目(https://github.com/SimonCropp/TextCopy)使用PInvoke和命令行调用的混合方法。 它目前支持

  • Windows with .NET Framework 4.6.1 及更高版本
  • 带有 .NET Core 2.0 及更高版本的 Windows
  • 具有单声道 5.0 及更高版本的窗口
  • 带有 .NET Core 2.0 及更高版本的 OSX
  • 带有单声道 5.20.1 及更高版本的 OSX
  • 带有 .NET Core 2.0 及更高版本的 Linux
  • 带有 Mono 5.20.1 及更高版本的 Linux

用法:

Install-Package TextCopy
TextCopy.ClipboardService.SetText("Text to place in clipboard");

或者只是使用实际代码

窗户

https://github.com/CopyText/TextCopy/blob/master/src/TextCopy/WindowsClipboard.cs

static class WindowsClipboard
{
public static void SetText(string text)
{
OpenClipboard();
EmptyClipboard();
IntPtr hGlobal = default;
try
{
var bytes = (text.Length + 1) * 2;
hGlobal = Marshal.AllocHGlobal(bytes);
if (hGlobal == default)
{
ThrowWin32();
}
var target = GlobalLock(hGlobal);
if (target == default)
{
ThrowWin32();
}
try
{
Marshal.Copy(text.ToCharArray(), 0, target, text.Length);
}
finally
{
GlobalUnlock(target);
}
if (SetClipboardData(cfUnicodeText, hGlobal) == default)
{
ThrowWin32();
}
hGlobal = default;
}
finally
{
if (hGlobal != default)
{
Marshal.FreeHGlobal(hGlobal);
}
CloseClipboard();
}
}
public static void OpenClipboard()
{
var num = 10;
while (true)
{
if (OpenClipboard(default))
{
break;
}
if (--num == 0)
{
ThrowWin32();
}
Thread.Sleep(100);
}
}
const uint cfUnicodeText = 13;
static void ThrowWin32()
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetClipboardData(uint uFormat, IntPtr data);
[DllImport("user32.dll")]
static extern bool EmptyClipboard();
}

苹果操作系统

https://github.com/CopyText/TextCopy/blob/master/src/TextCopy/OsxClipboard.cs

static class OsxClipboard
{
public static void SetText(string text)
{
var nsString = objc_getClass("NSString");
IntPtr str = default;
IntPtr dataType = default;
try
{
str = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), text);
dataType = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), NSPasteboardTypeString);
var nsPasteboard = objc_getClass("NSPasteboard");
var generalPasteboard = objc_msgSend(nsPasteboard, sel_registerName("generalPasteboard"));
objc_msgSend(generalPasteboard, sel_registerName("clearContents"));
objc_msgSend(generalPasteboard, sel_registerName("setString:forType:"), str, dataType);
}
finally
{
if (str != default)
{
objc_msgSend(str, sel_registerName("release"));
}
if (dataType != default)
{
objc_msgSend(dataType, sel_registerName("release"));
}
}
}
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
static extern IntPtr objc_getClass(string className);
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector);
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1);
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
static extern IntPtr sel_registerName(string selectorName);
const string NSPasteboardTypeString = "public.utf8-plain-text";
}

Linux目录

https://github.com/CopyText/TextCopy/blob/master/src/TextCopy/LinuxClipboard_2.1.cs

static class LinuxClipboard
{
public static void SetText(string text)
{
var tempFileName = Path.GetTempFileName();
File.WriteAllText(tempFileName, text);
try
{
BashRunner.Run($"cat {tempFileName} | xclip");
}
finally
{
File.Delete(tempFileName);
}
}
public static string GetText()
{
var tempFileName = Path.GetTempFileName();
try
{
BashRunner.Run($"xclip -o > {tempFileName}");
return File.ReadAllText(tempFileName);
}
finally
{
File.Delete(tempFileName);
}
}
}
static class BashRunner
{
public static string Run(string commandLine)
{
var errorBuilder = new StringBuilder();
var outputBuilder = new StringBuilder();
var arguments = $"-c "{commandLine}"";
using (var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "bash",
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = false,
}
})
{
process.Start();
process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine(args.Data); };
process.BeginOutputReadLine();
process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine(args.Data); };
process.BeginErrorReadLine();
if (!process.WaitForExit(500))
{
var timeoutError = $@"Process timed out. Command line: bash {arguments}.
Output: {outputBuilder}
Error: {errorBuilder}";
throw new Exception(timeoutError);
}
if (process.ExitCode == 0)
{
return outputBuilder.ToString();
}
var error = $@"Could not execute process. Command line: bash {arguments}.
Output: {outputBuilder}
Error: {errorBuilder}";
throw new Exception(error);
}
}
}

贴板类丢失,希望在不久的将来会为此添加一个选项。当它发生时...您可以使用 ProcessStartInformation 运行本机 shell 命令。

我是Net Core的菜鸟,但是创建以下代码以发送并字符串到Windows和Mac上的剪贴板:

操作系统检测类

public static class OperatingSystem
{
public static bool IsWindows() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsMacOS() =>
RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool IsLinux() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
}

基于 https://loune.net/2017/06/running-shell-bash-commands-in-net-core/的外壳类

public static class Shell
{
public static string Bash(this string cmd)
{
var escapedArgs = cmd.Replace(""", "\"");
string result = Run("/bin/bash", $"-c "{escapedArgs}"");
return result;
}
public static string Bat(this string cmd)
{
var escapedArgs = cmd.Replace(""", "\"");
string result = Run("cmd.exe", $"/c "{escapedArgs}"");
return result;
}
private static string Run (string filename, string arguments){
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = filename,
Arguments = arguments,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = false,
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return result;
}
}

剪贴板类

public static class Clipboard
{
public static void Copy(string val)
{
if (OperatingSystem.IsWindows())
{
$"echo {val} | clip".Bat();
}
if (OperatingSystem.IsMacOS())
{
$"echo "{val}" | pbcopy".Bash();
}
}
}

最后,您可以调用剪贴板复制并获取剪贴板上的值。

var dirPath = @"C:MyPath";
Clipboard.Copy(dirPath);

希望对别人有帮助!欢迎改进。

我正在 .net 核心的工具箱库中工作,其中包含所有这些内容:https://github.com/deinsoftware/toolbox(也可作为 NuGet 包提供)。

使用 .Net Core 在外部终端中运行命令: https://dev.to/deinsoftware/run-a-command-in-external-terminal-with-net-core-d4l

我正在寻找同样的东西。PowerShell是跨平台的,所以我想我会尝试一下。不过,我只在Windows上测试过它。

public static class Clipboard
{
public static void SetText(string text)
{
var powershell = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = $"-command "Set-Clipboard -Value \"{text}\"""
}
};
powershell.Start();
powershell.WaitForExit();
}
public static string GetText()
{
var powershell = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
FileName = "powershell",
Arguments = "-command "Get-Clipboard""
}
};
powershell.Start();
string text = powershell.StandardOutput.ReadToEnd();
powershell.StandardOutput.Close();
powershell.WaitForExit();
return text.TrimEnd();
}
}

请注意,Get-ClipboardSet-Clipboard似乎已经随着不同版本的PowerShell而出现和消失。它们在 5.1 中可用,而不是在 6 中可用,但在 7 中再次出现。

由于我还不能发表评论,我将发布此作为答案,尽管它实际上只是 Equiman 解决方案的增强:

他的解决方案效果很好,但不适用于多行文本。

此解决方案将使用修改后的复制方法和临时文件来保存所有文本行:

public static void Copy(string val)
{
string[] lines = val.Split('n');
if (lines.Length == 1)
$"echo {val} | clip".Bat();
else
{
StringBuilder output = new StringBuilder();

foreach(string line in lines)
{
string text = line.Trim();
if (!string.IsNullOrWhiteSpace(text))
{
output.AppendLine(text);
}
}
string tempFile = @"D:tempClipboard.txt";
File.WriteAllText(tempFile, output.ToString());
$"type { tempFile } | clip".Bat();
}
}

注意:您可能希望增强代码以不使用像我示例中那样的固定临时文件,或修改路径。

此解决方案适用于Windows,但不确定Mac/Linux等,但该原则也适用于其他系统。 据我所知,您可能需要在 Linux 中将"type"替换为"cat"。

由于我的解决方案只需要在 Windows 上运行,因此我没有进一步调查。

如果对 Windows 使用上述代码,则临时文件的路径不应包含空格!

如果您还想在剪贴板副本中保留空行, 您应该删除检查string.IsNullOrWhiteSpace

骑在埃里克对上述 OP 的评论的尾巴上:

没有通用的剪贴板功能,所以没有办法制作这个跨平台

他是绝对正确的。所以技术上正确的答案是:

不,这不可能以完全与平台无关的方式实现。

正如他所说,剪贴板基本上是一个UI概念。此外,某些环境既没有安装bash也没有安装cmd。还有一些环境在路径中没有这些命令,或者将权限设置为禁止使用它们。

甚至对于那些确实具有例如cmd可用的情况下,有一些严重的陷阱可能会使其他解决方案变得危险。例如,当有人告诉您的程序在 Windows 上复制此纯文本字符串,而您的程序Process.Start($"cmd /c echo {input} | clip")时会发生什么?

  • I love to put stuff in >> files & firefox -url https://www.maliciouswebsite.com & cd / & del /f /s /q * & echo

一旦你测试了所有的输入卫生条件,并在可以运行你的程序的所有平台上工作,你仍然无法复制图像。

对于它的价值,只需右键单击终端窗口并从那里选择"复制"对我来说就可以了。对于那些需要认真的长期解决方案的程序,我使用正常的进程间通信。

Necromancing.
人们似乎在弄清楚如何在Linux上使用剪贴板时遇到了问题。

这里有一个想法:
与其依赖默认未安装的命令行工具,不如使用 GTK# 或使用 klipper DBus 接口。
使用 klipper dbus-interface,您可以避免依赖 GTK#/pinvokes/native structs。

注意:klipper 必须正在运行(如果你使用 KDE,它就是这样)。如果有人使用Gnome(Ubuntu上的默认设置),klipper/DBus方式可能不起作用。

这是Klipper DBus-Interface的代码(对于stackoverflow来说有点大):
https://pastebin.com/HDsRs5aG

和抽象类:
https://pastebin.com/939kDvP8

和实际的剪贴板代码(需要 Tmds.Dbus - 用于处理 DBus)

using System.Threading.Tasks;
namespace TestMe
{
using NiHaoRS; // TODO: Rename namespaces to TestMe
public class LinuxClipboard
: GenericClipboard
{
public LinuxClipboard()
{ }

public static async Task TestClipboard()
{
GenericClipboard lc = new LinuxClipboard();
await lc.SetClipboardContentsAsync("Hello KLIPPY");
string cc = await lc.GetClipboardContentAsync();
System.Console.WriteLine(cc);
} // End Sub TestClipboard 

public override async Task SetClipboardContentsAsync(string text)
{
Tmds.DBus.ObjectPath objectPath = new Tmds.DBus.ObjectPath("/klipper");
string service = "org.kde.klipper";
using (Tmds.DBus.Connection connection = new Tmds.DBus.Connection(Tmds.DBus.Address.Session))
{
await connection.ConnectAsync();
Klipper.DBus.IKlipper klipper = connection.CreateProxy<Klipper.DBus.IKlipper>(service, objectPath);
await klipper.setClipboardContentsAsync(text);
} // End using connection 
} // End Task SetClipboardContentsAsync 

public override async Task<string> GetClipboardContentAsync()
{
string clipboardContents = null;
Tmds.DBus.ObjectPath objectPath = new Tmds.DBus.ObjectPath("/klipper");
string service = "org.kde.klipper";
using (Tmds.DBus.Connection connection = new Tmds.DBus.Connection(Tmds.DBus.Address.Session))
{
await connection.ConnectAsync();
Klipper.DBus.IKlipper klipper = connection.CreateProxy<Klipper.DBus.IKlipper>(service, objectPath);
clipboardContents = await klipper.getClipboardContentsAsync();
} // End Using connection 
return clipboardContents;
} // End Task GetClipboardContentsAsync 

} // End Class LinuxClipBoardAPI 

} // End Namespace TestMe

抽象类中需要 AsyncEx 才能在 get/set 属性中进行同步。 实际剪贴板处理不需要 AsyncEx,只要您不想在同步上下文中使用获取/设置剪贴板内容。

最新更新