Проблема
При подключении к системе 1С: Предприятие 8 из программ, разработанных в среде программирования Delphi, возникает ошибка с неверными обращениями к объекту автоматизации сервера. Например, подобная строка кода:
1CEnterprise.Справочники.Номенклатура.Выбрать();
, где 1CEnterprise – переменная типа Variant с указателем на интерфейс IDispatch COM-объекта 1С: Предприятие, приведет к появлению сообщения об ошибке: Method ‘Справочники ‘ not supported by automation object.
Данная проблема возникает из-за того что идентификаторы содержат русские буквы, а передача их интерфейсу IDispatch происходит в кодировке Юникод. Но из-за ошибок в среде Delphi происходит искажение имён. Проблема нивелируется, пока можно использовать английские синонимы, но они определены только для встроенных объектов и функций платформы! Множество прикладных функций не имеет их…
Решение
Вначале рассмотрим типичный сценарий подключения к системе 1С:Предприятие:
Var 1CEnterprise: Variant;
begin
1CEnterprise := CreateOleObject(‘V82.COMConnector’). Connect(‘File="С:1CBase"; Usr="Администратор"; Pwd="Пароль";’);
…
Далее при обращении к свойству Справочники происходит неявное обращение к интерфейсу IDispatch (что-то вроде вызова Invoke(‘Справочники’) если упрощено). Осуществляется же это благодаря функциональности типа Variant.
Для трансляции обращения к интерфейсу вызывается функция, назначенная в глобальную переменную VarDispProc. По умолчанию в неё назначена процедура из модуля ComObj. Но ничего не мешает заместить её на свою собственную и работать с интерфейсом IDispatch напрямую.
procedure MyVarDispInvoke(Result: PVariant; const Instance: Variant;
CallDesc: PCallDesc; Params: Pointer); cdecl;
procedure RaiseException;
begin
raise EOleError.CreateRes(@SVarNotObject);
end;
var
Dispatch: Pointer;
DispIDs: array[0..MaxDispArgs - 1] of Integer;
begin
if (CallDesc#k8SjZc9Dxk.ArgCount) > MaxDispArgs then raise EOleError.CreateRes(@STooManyParams);
if TVarData(Instance).VType = varDispatch then
Dispatch := TVarData(Instance).VDispatch
else if TVarData(Instance).VType = (varDispatch or varByRef) then
Dispatch := Pointer(TVarData(Instance).VPointer#k8SjZc9Dxk)
else RaiseException;
GetIDsOfNames(IDispatch(Dispatch), @CallDesc#k8SjZc9Dxk.ArgTypes[CallDesc#k8SjZc9Dxk.ArgCount],
CallDesc#k8SjZc9Dxk.NamedArgCount + 1, @DispIDs);
if Result <> nil then VarClear(Result#k8SjZc9Dxk);
DispatchInvoke(IDispatch(Dispatch), CallDesc, @DispIDs, Params, Result);
end;
Естественно лучше использовать некое подобие первоначальной функции, рассматривая которую можно заметить участок кода который и отвечает за передачу имен:
GetIDsOfNames(IDispatch(Dispatch), @CallDesc#k8SjZc9Dxk.ArgTypes[CallDesc#k8SjZc9Dxk.ArgCount], CallDesc#k8SjZc9Dxk.NamedArgCount + 1, @DispIDs);
Процедура GetIDsOfNames преобразует строковое имя в целочисленный идентификатор для вызова Invoke. Второй параметр содержит строку с именем вызываемого метода, но что важно — в каком формате? Выяснить это можно с помощью отладчика. Так вот в разных версиях Delphi формат разный и именно поэтому возникает ошибка, но только при работе с системой 1С: Предриятие 8 — подавляющее число интерфейсов у систем имеет идентификаторы только на английском языке! Здесь следует проникнуться гордостью за отечественных разработчиков и осыпать проклятьями их зарубежных коллег… Само же решение проблемы сильно зависит от формата параметра и содержимого процедуры GetIDsOfNames. В Delphi 2006 параметр передается уже в формате Юникод, а метод преобразует его в Юникод повторно, что и вызывает ошибку
Достаточно просто сконвертировать строку из Юникода, например так:
procedure GetIDsOfNames(const Dispatch: IDispatch; Names: PChar;
NameCount: Integer; DispIDs: PDispIDList);
procedure RaiseNameException;
begin
raise EOleError.CreateResFmt(@SNoMethod, [Names]);
end;
type
PNamesArray = #k8SjZc9DxkTNamesArray;
TNamesArray = array[0..0] of PWideChar;
var
N, SrcLen, DestLen: Integer;
Src: PChar;
Dest: PWideChar;
NameRefs: PNamesArray;
StackTop: Pointer;
Temp: Integer;
begin
//Src := Names; ---------------------------------------- БЫЛО
Src := PChar(Utf8ToAnsi(Names)); // ------------------- СТАЛО
N := 0;
asm
MOV StackTop, ESP
MOV EAX, NameCount
INC EAX
...
Всё — этого достаточно. Советы из разряда «установи старую Delphi» теперь можете смело игнорировать.
Не забудьте только перед использованием подменить адрес процедуры в глобальной переменной VarDispProc.
VarDispProc := MyVarDispInvoke;
В архиве содержится исходный код и пример (пытается выполнить ShowMessage(obj.Метаданные.Справочники.Банки.Комментарий); ). Удачного внешнего управления системой 1С Предприятие!
PS. Если вы думаете что IDispatch что-то специфичное и нужное только в Delphi, то задумайтесь — как 1С работает со значениями через точку? Когда вы работаете в конфигураторе с переменной содержимое которой не определяете самостоятельно (например параметр), то после точки может быть любой идентификатор — ошибки при проверке не будет (тип переменной неизвестен), а ошибка может произойти только при исполнении, когда в работу вступит интерфейс… да-да IDispatch объекта 1С !!!
Однозначно +!!!!! В пору начала освоения 1с пытался подружить 1с и delphi — уперся именно в русские буквы.
Это применимо к какой версии Delphi? Ведь версии Delphi XE и выше изначально считают всё юникодом.
+
А есть у кого пример для работой с 7ой?
Никак не могу брать значения справочников, которые в 7ке берутся из «Перечислений»
Теме уже 3 года, но тем не менее понадобилось написать внешнее приложение на Дельфи через Ole
Используется Delphi последней версии (Embarcadero® RAD Studio 10.1 Berlin)
Почему отдельные русские свойства и процедуры нормально отрабатываются, а отдельные нет. Была рабочая программа, добавили свойство в документ, дельфи спотыкается на нем, старые свойства обрабатывает нормально.
Все не так однозначно.
Наоборот, в моей практике Delphi нормально вызов передает, это сообщение об ошибке возвращается кракозябрами.
Его надо раскодировать примерно так AnsiToUtf8(Utf8ToAnsi(E.Message))
(4) Курить надо в сторону не кривизны Delphi, а учить матчасть — V83.Application и V83.COMConnector разные наборы методов поддерживают
Простейший пример — oCOMConnector.ИмяКомпьютера();