3

I have downloaded opensource delphi twain component (TDelphiTwain). The interesting thing is, that when placed and saved on the form it creates bad dfm entry for itself.

  object DelphiTwain: TDelphiTwain
    OnSourceDisable = DelphiTwainSourceDisable
    OnSourceSetupFileXfer = DelphiTwainSourceSetupFileXfer
    TransferMode = ttmMemory
    SourceCount = 0
    Info.MajorVersion = 1
    Info.MinorVersion = 0
    Info.Language = tlDanish
    Info.CountryCode = 1
    Info.Groups = [tgControl, tgImage, tgAudio, MinorVersion]
    Info.VersionInfo = 'Application name'
    Info.Manufacturer = 'Application manufacturer'
    Info.ProductFamily = 'App product family'
    Info.ProductName = 'App product name'
    LibraryLoaded = False
    SourceManagerLoaded = False
    Left = 520
    Top = 136
  end

The problem is with the line:

   Info.Groups = [tgControl, tgImage, tgAudio, MinorVersion]

There are only three possible elements:

tgControl, tgImage and tgAudio

It adds MinorVersion everytime I Save the form. When the app is run I get the error that there is invalid property for Info.Groups. When i rmeove the bad part manually and without leaving dfm file the app starts ok.

I looked in the internet and there was one inquire regarding these strange issue, unfortunately it hasn't been resolved.

I think that there is some sort of memory corruption. In the post in teh internet, strange signs were displayed ...

Has anyone worked with that component or could give me some hint how this could be fixed?

Johan
  • 74,508
  • 24
  • 191
  • 319
John
  • 1,834
  • 5
  • 32
  • 60

2 Answers2

5

The error seems to be in TTwainIdentity.GetGroups where result is not initialized. You can try to change the code by replacing

Include(Result, tgControl);

with

Result := [tgControl]; 

You have to recompile the package to make this change work inside the IDE.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • Or better yet, `Result := []; if DG_CONTROL AND Structure.SupportedGroups <> 0 then Include(Result, tgControl);` (and similarly for `SetGroups`) –  Dec 27 '12 at 15:34
  • It solves the problem. I tick your answer because you were first. Thank You – John Dec 28 '12 at 07:26
4

I don't know the component, but I think the problem lies in the TTwainIdentity.GetGroups method. It starts like this:

begin
  Include(Result, tgControl);

This means that it assumes that Result is initialized to an empty set. However, Result may contain garbage, and not necessarily an empty set. Change this method to look like this:

function TTwainIdentity.GetGroups(): TTwainGroups;
  {Convert from Structure.SupportedGroups to TTwainGroups}
begin
  Result := [tgControl];
  if DG_IMAGE AND Structure.SupportedGroups <> 0 then
    Include(Result, tgImage);
  if DG_AUDIO AND Structure.SupportedGroups <> 0 then
    Include(Result, tgAudio);
end;

Some result types will not throw a compiler warning about not being initialized, but that doesn't mean they are empty. Same goes, for instance, for strings. See also: http://qc.embarcadero.com/wc/qcmain.aspx?d=894

But still, it is odd that this happens. Apparently, Delphi tries to find the name of the given item in the set and accidentally finds the name of another property. It seems to me that quite some checks in writing the dfm are missing if this happens. :)

GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • "It seems to me that quite some checks in writing the dfm are missing if this happens. :)" -- Delphi assumes but does not check that the value of the property is a valid value of the property's type, probably because in the general case, it cannot check. It would not surprise you to see an access violation on saving if reading a string property gives you `string(-1)`, would it? :) –  Dec 27 '12 at 15:47
  • You can easily set items in a set that don't exist in the enumeration on which the set is based. A set is just an ordinal after all. I don't know if Delphi can check this properly when writing the dfm file, and maybe it isn't the right place to check it. But then again, it is odd that a Result variable for a set isn't initialized and can by default contain an invalid value without the compiler complaining. It's not an error per se, but there are some loose ends which apparently create an easy trap. A string won't be -1 unless you try really hard. :) – GolezTrol Dec 27 '12 at 16:01
  • `Result` is a local variable, and local variables are not initialized. This means that `Result` is initially pointing to random memory content, which is apparently what's causing the problem here. The quoted line of code basically says "take the random memory at wherever `Result` is pointing, treat it like it's a `TTwainGroups` set, and include the `tgControl` value in it". – Ken White Dec 27 '12 at 16:08
  • +1, BTW. Your answer would solve the problem completely, because it properly initializes `Result`. :-) – Ken White Dec 27 '12 at 16:13
  • @KenWhite Thanks! The reason mistakes like this are made, is that *some* types are initialized, for instance arrays and interfaces. Other types are not, but usually generate compiler warnings. And there's the third kind that isn't intialized and doesn't generate a warning either. Strings are the most common example of this type. Many people think that Delphi initializes a Result to `''`, but it doesn't. – GolezTrol Dec 27 '12 at 16:17
  • "You can easily set items in a set that don't exist in the enumeration on which the set is based." -- As this question shows, it doesn't work reliably. Is it valid, or are you merely getting away with it because Delphi doesn't check in other contexts either? Agreed that corruption is far less likely to happen by accident for strings, I think that was a bad example. –  Dec 27 '12 at 16:18
  • @GolezTrol Arrays and interfaces are initialised in the same way strings are, as far as I know: unless you try really hard (as you pointed out), arrays and interfaces never hold garbage, but they don't necessarily get initialised to `nil`. –  Dec 27 '12 at 16:19
  • @hwd: No, corruption is *highly likely* with string `Result` values that are not initialized. It's a good example (although in that case the compiler would warn you if you have hints and warnings enabled). You can easily test this to see for yourself. :-) – Ken White Dec 27 '12 at 16:20
  • @KenWhite No, not corruption, not in this sense. You will always get a valid string, it just won't necessarily be the string you're expecting. –  Dec 27 '12 at 16:23
  • @hvd Apparently so. If I read [this discussion](http://objectmix.com/delphi/402471-dynamic-arrays-automatically-initialized-empty-array.html) it seems there is a difference between 'normal' locals and `result`. Local arrays and string variables are initializes to 'emtpy', but result is not. However, you are right that they are always valid. – GolezTrol Dec 27 '12 at 16:23
  • @GolezTrol Yes, because `Result` for those types is usually treated as a `var` parameter, so it does get initialised to empty/nil, but it gets initialised not in the function itself, but in the caller. Which effectively means they don't get initialised reliably. :) –  Dec 27 '12 at 16:25
  • @hvd: That's true since D2009 (it was a change made during the switch to Unicode chars), but not for versions prior to that; D2007 and earlier can lead to problems with uninitialized string `Result` values. (And, since this question is tagged 'delphi-7', it's a pre-2009 version.) – Ken White Dec 27 '12 at 16:31
  • @KenWhite As far as I know, `AnsiString` behaves the same way, and testing in D7 shows nothing like what you describe. [This](http://pastebin.com/wnmykr8y) shows the abc I expect. Do you have a counterexample? –  Dec 27 '12 at 16:35
  • @hvd: The second pass of your loop (when `i = 1`) sets the value of Result, and it's never changed again between there and the place you call `writeln(s);`. Chang it to `writeln(i)` inside the loop, and look at the first value output (when `i - 0`). – Ken White Dec 27 '12 at 16:39
  • @KenWhite [Like this?](http://pastebin.com/mSTTegH4) Sure, I get "0: " / "1: abc" / "2: abc" / ..., that's the behaviour I expected, that's how newer versions behave with `UnicodeString` types too. –  Dec 27 '12 at 16:41
  • @hvd: The first thing I get is a compiler warning that `Result might be uninitialized` (which is there for a reason). :-) We've strayed off the topic of this question, though, which was about an uninitialized set being modified and returned. – Ken White Dec 27 '12 at 16:47
  • @KenWhite Yes, of course (actually, I don't get that warning in D7, but I know it's issued sometimes), but you said it could lead to corruption. My testing still only shows valid strings as the result (`f(2)` "returning" `'abc'`). Where is the corruption? –  Dec 27 '12 at 16:50
  • @hvd: So your very simple test doesn't produce it. As I said, in D2007 (don't have D7 here) generates a warning. If there was not chance of getting a bad value because `Result` is always initialized, why is that warning emitted? It's not a *hint*. (And I don't have a test case to produce the problem off-hand; I don't write code like that, and it may just not happen in your mocked up test because of it's simplicity. Because you can't produce it in 10 lines of code doesn't mean it's not possible.) And once again, we're off the subject of the question asked here. – Ken White Dec 27 '12 at 16:58
  • @KenWhite The warning is emitted because having `f(2)` return `'abc'`, even though `Result` is never set to `'abc'` when `i = 2`, is an unintuitive result that the programmer probably didn't intend to have happen. Except when weird people like me write weird programs for demonstration purposes. And to be clear, I don't defend writing code like that for any other reason. Can we leave it at that? :) –  Dec 27 '12 at 17:04
  • @hvd: No, it's caused because calling the function with any value **other** than `1` will leave it uninitialized. :-) But other than that, no quibbles. :-) – Ken White Dec 27 '12 at 17:15