У меня есть управляемая dll C #, которая использует неуправляемую dll C ++ с помощью DLLImport. Все отлично работает. Однако я хочу встроить эту неуправляемую DLL в свою управляемую DLL, как объясняет Microsoft:
Поэтому я добавил неуправляемый файл dll в свой проект управляемой dll, установил для свойства значение «Встроенный ресурс» и изменил DLLImport на что-то вроде:
[DllImport("Unmanaged Driver.dll, Wrapper Engine, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null",
CallingConvention = CallingConvention.Winapi)]
где 'Wrapper Engine' - имя сборки моей управляемой DLL, 'Unmanaged Driver.dll' - неуправляемая DLL
Когда я бегу, то получаю:
Доступ запрещен. (Исключение из HRESULT: 0x80070005 (E_ACCESSDENIED))
Я видел из MSDN и http://blogs.msdn.com/suzcook/, что это должно быть возможно ...
Ответы:
Вы можете встроить неуправляемую DLL в качестве ресурса, если извлечете ее самостоятельно во временный каталог во время инициализации и загрузите ее явно с помощью LoadLibrary перед использованием P / Invoke. Я использовал эту технику, и она хорошо работает. Как заметил Майкл, вы можете просто связать его со сборкой как отдельный файл, но наличие всего этого в одном файле имеет свои преимущества. Вот подход, который я использовал:
// Get a temporary directory in which we can store the unmanaged DLL, with // this assembly's version number in the path in order to avoid version // conflicts in case two applications are running at once with different versions string dirName = Path.Combine(Path.GetTempPath(), "MyAssembly." + Assembly.GetExecutingAssembly().GetName().Version.ToString()); if (!Directory.Exists(dirName)) Directory.CreateDirectory(dirName); string dllPath = Path.Combine(dirName, "MyAssembly.Unmanaged.dll"); // Get the embedded resource stream that holds the Internal DLL in this assembly. // The name looks funny because it must be the default namespace of this project // (MyAssembly.) plus the name of the Properties subdirectory where the // embedded resource resides (Properties.) plus the name of the file. using (Stream stm = Assembly.GetExecutingAssembly().GetManifestResourceStream( "MyAssembly.Properties.MyAssembly.Unmanaged.dll")) { // Copy the assembly to the temporary file try { using (Stream outFile = File.Create(dllPath)) { const int sz = 4096; byte[] buf = new byte[sz]; while (true) { int nRead = stm.Read(buf, 0, sz); if (nRead < 1) break; outFile.Write(buf, 0, nRead); } } } catch { // This may happen if another process has already created and loaded the file. // Since the directory includes the version number of this assembly we can // assume that it's the same bits, so we just ignore the excecption here and // load the DLL. } } // We must explicitly load the DLL here because the temporary directory // is not in the PATH. // Once it is loaded, the DllImport directives that use the DLL will use // the one that is already loaded into the process. IntPtr h = LoadLibrary(dllPath); Debug.Assert(h != IntPtr.Zero, "Unable to load library " + dllPath);
источник
Вот мое решение, которое представляет собой модифицированную версию ответа JayMcClellan. Сохраните файл ниже в файл class.cs.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.IO; using System.Reflection; using System.Diagnostics; using System.ComponentModel; namespace Qromodyn { /// <summary> /// A class used by managed classes to managed unmanaged DLLs. /// This will extract and load DLLs from embedded binary resources. /// /// This can be used with pinvoke, as well as manually loading DLLs your own way. If you use pinvoke, you don't need to load the DLLs, just /// extract them. When the DLLs are extracted, the %PATH% environment variable is updated to point to the temporary folder. /// /// To Use /// <list type=""> /// <item>Add all of the DLLs as binary file resources to the project Propeties. Double click Properties/Resources.resx, /// Add Resource, Add Existing File. The resource name will be similar but not exactly the same as the DLL file name.</item> /// <item>In a static constructor of your application, call EmbeddedDllClass.ExtractEmbeddedDlls() for each DLL that is needed</item> /// <example> /// EmbeddedDllClass.ExtractEmbeddedDlls("libFrontPanel-pinv.dll", Properties.Resources.libFrontPanel_pinv); /// </example> /// <item>Optional: In a static constructor of your application, call EmbeddedDllClass.LoadDll() to load the DLLs you have extracted. This is not necessary for pinvoke</item> /// <example> /// EmbeddedDllClass.LoadDll("myscrewball.dll"); /// </example> /// <item>Continue using standard Pinvoke methods for the desired functions in the DLL</item> /// </list> /// </summary> public class EmbeddedDllClass { private static string tempFolder = ""; /// <summary> /// Extract DLLs from resources to temporary folder /// </summary> /// <param name="dllName">name of DLL file to create (including dll suffix)</param> /// <param name="resourceBytes">The resource name (fully qualified)</param> public static void ExtractEmbeddedDlls(string dllName, byte[] resourceBytes) { Assembly assem = Assembly.GetExecutingAssembly(); string[] names = assem.GetManifestResourceNames(); AssemblyName an = assem.GetName(); // The temporary folder holds one or more of the temporary DLLs // It is made "unique" to avoid different versions of the DLL or architectures. tempFolder = String.Format("{0}.{1}.{2}", an.Name, an.ProcessorArchitecture, an.Version); string dirName = Path.Combine(Path.GetTempPath(), tempFolder); if (!Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); } // Add the temporary dirName to the PATH environment variable (at the head!) string path = Environment.GetEnvironmentVariable("PATH"); string[] pathPieces = path.Split(';'); bool found = false; foreach (string pathPiece in pathPieces) { if (pathPiece == dirName) { found = true; break; } } if (!found) { Environment.SetEnvironmentVariable("PATH", dirName + ";" + path); } // See if the file exists, avoid rewriting it if not necessary string dllPath = Path.Combine(dirName, dllName); bool rewrite = true; if (File.Exists(dllPath)) { byte[] existing = File.ReadAllBytes(dllPath); if (resourceBytes.SequenceEqual(existing)) { rewrite = false; } } if (rewrite) { File.WriteAllBytes(dllPath, resourceBytes); } } [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] static extern IntPtr LoadLibrary(string lpFileName); /// <summary> /// managed wrapper around LoadLibrary /// </summary> /// <param name="dllName"></param> static public void LoadDll(string dllName) { if (tempFolder == "") { throw new Exception("Please call ExtractEmbeddedDlls before LoadDll"); } IntPtr h = LoadLibrary(dllName); if (h == IntPtr.Zero) { Exception e = new Win32Exception(); throw new DllNotFoundException("Unable to load library: " + dllName + " from " + tempFolder, e); } } } }
источник
Я не знал, что это возможно - я предполагал, что CLR нужно где-то извлечь встроенную собственную DLL (Windows должен иметь файл для DLL, чтобы загрузить его - она не может загрузить изображение из необработанной памяти) и где угодно он пытается сделать так, чтобы у процесса нет разрешения.
Что-то вроде Process Monitor от SysInternals может дать вам ключ к разгадке, если проблема в том, что создание файла DLL не удается ...
Обновить:
Ах ... теперь, когда я смог прочитать статью Сюзанны Кук (эта страница мне раньше не показывалась), обратите внимание, что она не говорит о встраивании собственной DLL в качестве ресурса в управляемую DLL, а скорее в качестве связанного ресурса - собственная DLL по-прежнему должна быть собственным файлом в файловой системе.
См. Http://msdn.microsoft.com/en-us/library/xawyf94k.aspx , где говорится:
Похоже, что это добавляет метаданные в сборку, что приводит к тому, что собственная DLL логически становится частью сборки (хотя физически это отдельный файл). Таким образом, такие вещи, как размещение управляемой сборки в GAC, автоматически включают встроенную DLL и т. Д.
источник
Вы можете попробовать Costura.Fody . В документации сказано, что он может обрабатывать неуправляемые файлы. Я использовал его только для управляемых файлов, и он отлично работает :)
источник
Можно также просто скопировать библиотеки DLL в любую папку, а затем вызвать SetDllDirectory в эту папку. В этом случае вызов LoadLibrary не требуется.
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool SetDllDirectory(string lpPathName);
источник