Я начал с очень сложной системы клиентов и серверов с ссылками на COM и другими вещами, и урезал, пока не понял, что не могу даже заставить пример кода Microsoft работать для бесплатной регистрации COM-активации управляемого COM-сервера. написано в C #.
Код сервера:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace ClassLibrary1
{
[Guid("A7AC6D8C-FF17-4D2C-A3B1-2C8690A8EA04")
,ComVisible(true)]
public interface IClass1
{
[DispId(1)]
string DummyFunction(string inputValue);
}
[Guid("81723475-B5E3-4FA0-A3FE-6DE66CEE211C"),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(IClass1)),
ComVisible(true)]
public class Class1 : IClass1
{
public string DummyFunction(string inputValue)
{
return inputValue.Substring(0, 1) + " Inserted " + inputValue.Substring(1);
}
}
}
Код клиента VB6:
Dim c As ClassLibrary1.Class1
Set c = New Class1
MsgBox c.DummyFunction("Ben")
Код клиента C ++:
#include "stdafx.h"
#import <ClassLibrary1.tlb> raw_interfaces_only
using namespace ClassLibrary1;
int _tmain(int argc, _TCHAR* argv[])
{
IClass1Ptr p;
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
hr = CoCreateInstance(__uuidof(Class1), NULL, CLSCTX_INPROC_SERVER, __uuidof(IClass1), (void **)&p);
if (FAILED(hr))
{
_tprintf_s(_T("Error %x\n"), hr);
CoUninitialize();
return 1;
}
_bstr_t b = _T("Bobby");
BSTR b2;
p->DummyFunction(b, &b2);
wprintf_s(L"%s\n", b2);
p->Release();
CoUninitialize();
return 0;
}
Оба клиента работают нормально, когда я удаляю весь Reg-Free COM-код и регистрирую ClassLibrary1.dll с помощью regasm / codebase.
Затем я отменяю регистрацию ClassLibrary1 и пытаюсь представить Reg-Free COM для клиента VB6 с файлом Project1.exe.manifest:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="Project1" version="1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="ClassLibrary1" version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
И ClassLibrary1.manifest:
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="ClassLibrary1" />
<clrClass clsid="{81723475-B5E3-4FA0-A3FE-6DE66CEE211C}" name="ClassLibrary1.Class1" tlbid="{F8A2D334-5BBB-4007-8308-A1417052E6D6}"></clrClass>
<file name="ClassLibrary1.dll" ></file>
</assembly>
Теперь я получаю ошибку 429 (компонент ActiveX не может создать объект) иногда и (необъяснимо) ошибку автоматизации в других случаях:
Ошибка времени выполнения ‘-2146234304 (80131040)’:
Ошибка автоматизации
затем я пытаюсь ввести COM Isolation в клиент C ++:
Теперь, когда я запускаю клиент C ++, вывод просто
Ошибка 800401f9
После многих испытаний, работающих с различными образцами при поддержке Microsoft, я определил много Подводные камни, возникающие при попытке реализовать управляемый COM-сервер с неуправляемым C ++ COM-клиентом. Вот ключевые фрагменты информации, которые я помню, которые можно применить к примеру кода в вопросе, чтобы убедиться, что он работает.
—
#define RT_MANIFEST 24
#define MANIFEST_RESOURCE_ID 1
MANIFEST_RESOURCE_ID RT_MANIFEST ClassLibrary1.manifest
Затем добавьте шаг предварительной сборки следующим образом:
"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\rc.exe" "$(ProjectDir)ClassLibrary1.rc"
Затем в настройках проекта на вкладке «Приложение» измените «Ресурсы», чтобы использовать ClassLibrary1.res вместо «Значок и манифест». Но это связано с проблемами: во-первых, путь к RC.EXE нелегко определить без его жесткого кодирования; во-вторых, информация о версии из AssemblyInfoCommon будет игнорироваться, поскольку ресурсы в файле RC полностью заменяют все ресурсы Win32, которые будут сгенерированы компилятором .NET.
Другая возможность состоит в том, чтобы просто хранить файл манифеста COM DLL сервера отдельно и не встраивать его в качестве ресурса. Я читал, что это может быть ненадежным, но он работает на Windows 7 Enterprise 64-bit SP1.
ConsoleApplication1.exe.config
), который определяет, как загрузить .NET. Для .NET 4.5 я видел эту работу: —
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>
В то время как для .NET 3.5 кажется, что useLegacyV2RuntimeActivationPolicy необходимо переключить:
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="false">
<supportedRuntime version="v3.5"/>
</startup>
</configuration>
Важно убедиться, что все версии фреймворка и CLR синхронизированы. И важно понимать, что версии CLR не совпадают с версиями фреймворка. Например, если вы хотите построить управляемый COM-сервер в .NET 3.5, версия CLR (runtimeVersion) должна быть «2.0.50727», а версия .NET (supportRuntime) должна быть «v3.5».
Убедитесь, что целевая версия .NET Framework COM-сервера соответствует поддерживаемому клиенту Runtime. Если он создается из командной строки, он может не выбирать версию платформы из файла проекта Visual Studio (например, если вы используете компилятор C # VB.NET напрямую, а не вызываете MSBuild), убедитесь, что сборка ориентирована на правильную версию фреймворка.
Я еще не проверил все вышеперечисленное, но собираюсь вскоре пройти весь этот процесс, чтобы убедиться, что я все поймал. Вот что я закончил тем, что еще не упомянул:
ConsoleApplication1.exe.manifest (в исходном каталоге копируется или внедряется в выходной каталог во время сборки)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type = "win32"name = "ConsoleApplication1"version = "1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"name="ClassLibrary1"version="1.0.0.0"publicKeyToken="541b4aff0f04b60a"/>
</dependentAssembly>
</dependency>
</assembly>
ClassLibrary1.manifest
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="ClassLibrary1" version="1.0.0.0" publicKeyToken="541b4aff0f04b60a" />
<clrClass clsid="{81723475-B5E3-4FA0-A3FE-6DE66CEE211C}" progid="ClassLibrary1.Class1" threadingModel="both" name="ClassLibrary1.Class1" runtimeVersion="v2.0.50727"></clrClass>
</assembly>
РЕДАКТИРОВАТЬ:
Теперь, чтобы пройти и проверить каждую деталь с полной информацией об ошибке и т. Д.
Я начинаю с создания единого решения, содержащего два проекта со всеми значениями по умолчанию и кодом, показанным в вопросе. Я начну с файлов манифеста или с любых настроек проекта, упомянутых в вопросе, и буду явно вызывать, когда я внесу эти изменения в процесс ниже. Это шаги и ошибки, которые находятся на пути к созданию этого проекта.
tlbexp ClassLibrary1.dll
#import <ClassLibrary1.tlb> raw_interfaces_only
с кавычками, так что читает #import "ClassLibrary1.tlb" raw_interfaces_only
, Восстановить: успех.Error 80040154
(Класс не зарегистрирован), потому что мы не зарегистрировали компонент и не установили COM без регистрации.Error 800401f9
мы пропустим это и просто попробуем создать манифест клиента. Создайте новый текстовый файл со следующим содержимым и сохраните его как ConsoleApplication1.exe.manifest в каталоге проекта ConsoleApplication1:—
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="ConsoleApplication1" version="1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="ClassLibrary1" version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
—
INFO: Parsing Manifest File C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ConsoleApplication1.exe.
INFO: Manifest Definition Identity is ConsoleApplication1,type="win32",version="1.0.0.0".
INFO: Reference: ClassLibrary1,version="1.0.0.0"INFO: Resolving reference ClassLibrary1,version="1.0.0.0".
INFO: Resolving reference for ProcessorArchitecture ClassLibrary1,version="1.0.0.0".
INFO: Resolving reference for culture Neutral.
INFO: Applying Binding Policy.
INFO: No binding policy redirect found.
INFO: Begin assembly probing.
INFO: Did not find the assembly in WinSxS.
INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1.DLL.
INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1.MANIFEST.
INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1\ClassLibrary1.DLL.
INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1\ClassLibrary1.MANIFEST.
INFO: Did not find manifest for culture Neutral.
INFO: End assembly probing.
ERROR: Cannot resolve reference ClassLibrary1,version="1.0.0.0".
ERROR: Activation Context generation failed.
End Activation Context Generation.
—
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>
Та же самая ошибка все еще произошла бы, если useLegacyV2RuntimeActivationPolicy были исключены в этом случае. К сожалению, я не совсем понимаю, почему, но я подозреваю, что это как-то связано с более новой политикой активации среды выполнения v4.0, по умолчанию загружающей CLR v2.0, если загружаемый исполняемый файл явно не ссылается на .NET 4.0 (который не управляемого кода нет, потому что он явно не ссылается на период .NET).
sn -T ClassLibrary1.dll
из командной строки разработчика. После обновления ClassLibrary1.manifest и ConsoleApplication1.exe.manifest не забудьте перестроить ConsoleApplication1.exe, если манифест встроен, и скопировать ClassLibrary1.manifest в каталог ConsoleApplication1.exe. Запустить снова и?publicKeyToken
а не какое-то другое смешное имя, как privateKeyToken
; б) Убедитесь, что все атрибуты, указанные вами в assemblyIdentity на манифесте на стороне сервера, соответствуют атрибутам на манифесте на стороне клиента, и что у вас нет type="win32"
указано на одном, но не на другом.B Inserted obby
Следует также отметить, что клиент VB6 также работает с использованием следующих файлов вместе с клиентом VB6:
Project1.exe.config:
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>
Project1.exe.manifest:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="Project1" version="1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="ClassLibrary1" version="1.0.0.0" publicKeyToken="541b4aff0f04b60a" />
</dependentAssembly>
</dependency>
</assembly>
Однако, по-видимому, существует тенденция сообщать «Компонент ActiveX не может создать объект» (ошибка времени выполнения 429) в тех редких случаях, когда исполняемый файл создается и запускается до создания файлов конфигурации и манифеста. Восстановление exe-файла, кажется, исправляет это, но тогда я не могу решить проблему, поэтому трудно определить конкретную причину.
Я думал, что я был достаточно хорошим решением проблем, но кое-что из-за многочисленных движущихся частей и многочисленных бесполезных кодов ошибок и сообщений об ошибках, о которых сообщалось в проблемах с конфигурацией без рег. код. Надеюсь, этот ответ поможет другим приобрести аналогичный опыт. Пожалуйста, расширьте этот ответ, если вы узнаете больше!
Манифест управляемого COM-сервера Можно быть правильно и легко внедренным, если вы используете команду «Добавить» -> «Новый элемент …» в проекте, чтобы добавить «Файл манифеста приложения». Это добавляет файл с именем app.manifest в проект. Но настоящая хитрость заключается в том, что это происходит так, что не может быть реплицировано любым другим способом через пользовательский интерфейс Visual Studio, кроме как через один винтик. Поскольку поле «Манифест» на вкладке «Приложение» окна настроек проекта отключено для проектов типа «Библиотека классов», манифест, который обычно устанавливается здесь, не может быть установлен для библиотеки классов. Но вы можете временно изменить проект на приложение Windows, изменить здесь выбор Manifest, а затем восстановить его в библиотеке классов. Настройка будет придерживаться, поэтому выбранный манифест будет правильно встроен. Вы можете проверить настройку в текстовом редакторе, просмотрев файл проекта. Ищу:
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
Ошибка 0x80131040 по-прежнему может возникнуть при соблюдении всех вышеуказанных мер предосторожности. Чтобы сузить причину этого, с помощью средства просмотра журнала Fusion можно увидеть больше информации о том, что происходит, когда сборки загружаются и разрешаются. Google «Fuslogvw» для получения дополнительной информации о том, как просмотреть этот журнал (fuslogvw.exe — это утилита, предоставляемая при установке Visual Studio). Также важно понимать, что это приложение, по умолчанию, по-видимому, не показывает никакой информации, пока вы не настроите его для регистрации информации в файлах, воспроизведения проблемы, а затем перезапустите приложение, чтобы прочитать файлы журнала после их создания. И, согласно документации MSDN, также важно помнить, чтобы запустить эту утилиту от имени администратора.
После того, как вы преодолели все препятствия для запуска fuslogvw.exe, вы можете увидеть что-то вроде этого в журнале:
WRN: Comparing the assembly name resulted in the mismatch: Major Version
ERR: The assembly reference did not match the assembly definition found.
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.
Несмотря на то, что в файле манифеста COM-сервера указана версия 1.0.0.0, это не единственная версия, используемая при привязке ссылки COM-клиента к серверу. Мой клиентский EXE-файл пытался ссылаться на 1.0.0.0, что точно соответствовало версии в файле манифеста COM-сервера, но не совпадало с версией .NET DLL. После исправления файлов манифеста клиента и сервера, чтобы они отражали версию, фактически отображаемую в DLL-библиотеке сервера .NET, ошибка 0x80131040 исчезла, и fuslogvw.exe стал ключом к определению источника ошибки.
Если манифест клиента синхронизирован с фактической версией .NET DLL, но файл манифеста серверной DLL не отражает эту версию, возникнет другая ошибка:
Приложение не удалось запустить, потому что его бок о бок
Конфигурация неверна. Пожалуйста, смотрите журнал событий приложения или
используйте инструмент командной строки sxstrace.exe для более подробной информации.
Ошибка 0xc0150002 или следующее сообщение может быть сообщено:
Приложение не удалось запустить правильно (0xc0150002), нажмите кнопку ОК
закрыть приложение.
Я видел это в случае, когда манифест клиента был встроен в неуправляемую DLL, а не в неуправляемый EXE, а также манифест assemblyIdentity
элемент не точно соответствует серверу assemblyIdentity
, У клиента был дополнительный processorArchitecture="x86"
в этом то, что сервер не указал, вызвав несоответствие. К сожалению, я не знаю, как это узнать, не подумав, чтобы проверить файлы манифеста, чтобы убедиться, что они совпадают (или не читал эту статью). Эта ошибка явно не указывает на то, что источником проблемы является файл манифеста, поэтому вам просто нужно знать, что существует возможная корреляция между этим сообщением об ошибке и этой причиной.
Я видел, как внешние файлы манифеста полностью игнорировались, приводя к совершенно пустому журналу sxstrace, даже когда у исполняемых файлов нет встроенных манифестов. По-видимому, это может произойти в результате кеширования контекста активации (проблема, задокументированная в http://csi-windows.com/blog/all/27-csi-news-general/245-find-out-why-your-external-manifest-is-being-ignored). Чтобы обойти эту проблему, вы можете использовать следующую команду, чтобы коснуться отметки даты файла, манифест которого игнорируется:
copy /b myfile.exe+,,
Я видел еще одну трудную для объяснения ошибку Class Not Registered (0x80040154
— REGDB_E_CLASSNOTREG
) что происходит при вызове CoCreateInstance при следующих условиях:
DllMain
если /clr
переключатель не применяется к файлу CPP или во время .cctor
если /clr
переключатель является применяется к файлу.regasm
,CoCreateInstance
имеет /clr
Переключатель применяется к настройкам компилятора C ++.Если какое-либо из 3 последних условий будет изменено, проблема исчезнет. (Кроме того, если последнее условие изменено, вы можете получить блокировку загрузчика из-за # 1 — читайте о блокировке загрузчика и ее отношении к CLR в Инициализация смешанных сборок). Поэтому, если вы сталкиваетесь с ошибкой Class Not Registered в подобных обстоятельствах, подумайте, можете ли вы изменить какое-либо из этих трех последних условий для устранения ошибки.
Замечания: Я с трудом фиксирую поведение # 6. Кажется, эффект переключения это также зависит от состояния # 1. Это похоже на вызов конструктора (включая его CoCreateInstance
) после полной загрузки DLL все еще вызывает Class Not Registered, тогда как вызов конструктора во время инициализации DLL будет успешным, если /clr
переключатель не указан. Мое решение на данный момент состоит в том, чтобы перекодировать клиентский файл CPP в управляемом C ++, поскольку это был относительно простой интерфейсный класс между компонентом COM и остальной частью неуправляемого кода. Так что теперь в этом клиенте больше нет COM, только ссылка .NET.
Несмотря на все мои разочарования, объяснение BlueMonkMN было действительно полезным, но все же я столкнулся с Class not registered
сообщение.
У меня есть .NET 4.6 COM взаимодействия Dll и клиент C ++, который использует Регистрация Free COM с помощью технологии Windows SxS. Манифест сборки был встроен в COM Dll, а манифест приложения помещен наружу в виде отдельного файла .manifest.
Все работало так, как рекламировалось, но начало получать Class not registered
сообщение во время CreateInstance
когда происходит одно из следующего.
В этой ситуации, чтобы добавить оскорбление травмы, sxstrace
журнал был пуст, журнал событий приложения (eventvwr
) не сообщал об ошибке SideBySide и программе просмотра журнала привязки сборки (FUSLOGVW.exe
) ничего не показывал из клиентского приложения C ++.
Позвольте мне добавить еще одно Дополнение к ответу BlueMonkMN.
Из моих экспериментов я понимаю, что в таком случае контекст активации для клиентского приложения C ++ не создается.
В такой ситуации необходимо создать контекст активации самостоятельно.
Следующий код помещается в клиентское приложение C ++ после вызова CoInitialize(0);
и прежде чем звонить CreateInstance
,
ACTCTX context;
memset(&context, 0, sizeof(context));
context.cbSize = sizeof(context);
context.lpSource = L"CPPClient.exe.manifest";
context.lpAssemblyDirectory = L"D:\\code\\vs2017\\CPPClient\\Debug";
context.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
HANDLE hActCtx = CreateActCtx(&context);
ULONG_PTR cookie = 0;
BOOL result = ActivateActCtx(hActCtx, &cookie);
//Rest of the code goes here.
В качестве меры очистки следующий фрагмент кода помещается непосредственно перед вызовом CoUninitialize();
DeactivateActCtx(0, cookie);
ReleaseActCtx(hActCtx);
Теперь это звучит хорошо. Бок о бок работает очень хорошо. Если что-то пойдет не так, то это можно увидеть в sxstrace или в журнале событий приложения или в программе просмотра журнала привязки сборки.